Create Indestructible SBC's with overlayfs--Part II--uv, flask, and systemd

Last time I covered using overlayfs on a Linux Single Board Computer to survive unwelcomed power hits. 

To understand this post you may want to read or skim the last one...This time I wanted to finish the indestructible SBC setup by adding a Python virtual environment using uv, creating a basic webserver using Python flask, and getting the flask service to auto-start at boot using systemd.

If you are a Linux/Python admin you may already know how to do all of this; consider skipping this post and check out these guys instead. 

As usual, I am writing the steps because I forget everything. 

Pi Zero--512MB RAM but RPi's text-only OS still gave me a surprising amount of extra space to mess around in an overlayfs configuration. 

BUT FIRST--A WORD FROM THIS BLOG'S SPONSOR:


                           
 

After getting your SBC set, you will probably want to design a PCB for your SBC then get it fabricated.

For this, please check out PCBWAY. 

For December 2025 PCBWAY is featuring some terrific holiday specials, details here.

For instance: a special where cool PCB solder mask colors like purple and matte black are a super low price: ten 99 x 99 mm double-sided PCB's for $5USB + tax, tariff, shipping. 

Super affordable!

PCBWAY can fabricate PCBs using full color! Details here

In addition to top shelf PCB fabrication they also do fantastic work with assembly3D printinginjection molding, and much more. 

Their staff is extremely helpful and PCBWAY always turns work around quickly. 

As always--you can help this blog by checking out the PCBWAY site. Thanks.

MAKING CHANGES TO THE READ-ONLY SBC

From last post: we set the Raspberry Lite OS to read-only to survive power hits using overlayfs, but today we are writing OS changes. Here's how:

  • I removed the PI's SD card
  • I mounted it on my PC using an SD to USB adapter  
  • I edited /boot/firmware/cmdline.txt, getting rid of overlayroot=tempfs at the end of the statement, 
  • then saved cmdline.txt. 

Then I put the SD back into the RPI and started it up--it was now a normal read-write device.

When I was ready to go back to overlayfs mode, I added overlayroot=tempfs back to the cmdline.txt using vi, saved, and rebooted. 

WHAT IS UV?

I'd been administering Python environments for years; the number of tools needed to set up/maintain python3 projects-- pip, pip-tools, pipx, poetry, pyenv, twine, virtualenv, and so on, was troublesome. 

Imagine my excitement when I first read about uv, a single tool that kicks the other Python admin tools' butt. 

Read about uv here; a good getting started video is here.  

For Raspberry Pi OS, I opened the Linux terminal  and used the statements below to create a new project called pyflask using uv (Update: if using other linux distros you may need to put sudo in front of these commands or elevate to root):




  
#hint: to make my SSH terminal not look crap, I used UTF-8 

#encoding (I use secureCRT9.6-- 

#properties > appearance > encoding 

#default is "automatic" which wasn't working)

#install uv 

apt update

apt install curl #needed for next command--may already be installed on your SBC

#install uv using less HD space vs using pip install.

curl -LsSf https://astral.sh/uv/install.sh | sh

#SET PATH needed for terminal app to find uv when you try to run it

#put uv into PATH--for RPi I could skip this step and just restart bash; not sure w other distros

echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc

#note, for some distros .local above is replaced by .cargo

#restart bash  

bash  

#is uv working?  This should show a list of basic uv commands.

 uv
 
 
 


CREATE A NEW PYTHON PROJECT AND VIRTUAL ENV USING UV

 




#hint: to make my SSH terminal not look crap, I used UTF-8 
#encoding (I use secureCRT9.6-- 
#properties > appearance > encoding 

#default is "automatic" which wasn't working)

#install uv 

apt update

apt install curl #needed for next command--may already be installed #on your SBC

#install uv using less HD space vs using pip install.

#cd to ~ you should do the next command from your home dir.
uv init pyflask #pyflask is the name of the project

#cd to the project folder you just created.
cd ~/pyflask

uv run main.py  
#shows you successful hello world.

#ACTIVATE THE NEW PROJECT
#you still must be in the project dir.
#must use "source" in the command below or term will open
#a new term, make the change, and pop you back to old one....
source .venv/bin/activate 

#INSTALL PYTHON PACKAGES
#in project root folder....
#uv add [whatever]--use this instead of pip, faster!

#let's add flask
uv add flask

#ASIDE--with UV, WHERE DO YOUR FILES GO?
#see what is where in uv--useful cmd!
uv tree

#in project root.  See below. note-- never edit anything inside the #.venv dir and its subdirs.
#/home/pi/my_weather_bot/  <-- YOUR PROJECT ROOT (You work here)
#├── main.py   <-- Your code goes here
#├── utils.py                   <-- Your code goes here
#├── data/                      <-- Your data folders go here
#│   └── weather_log.csv
#└── .venv/                     <-- THE VIRTUAL ENV (Do not open/edit)
 #   ├── bin/
  #  ├── lib/
   # └── pyvenv.cfg


 

 

After all of this I had a working venv for project pyflask:



GETTING FLASK GOING....

Flask is a simple webserver for Python--more here.  

Using flask I created a webserver that generated "hello world" when viewed from my browser--using only a few lines of Python.  First using the terminal I created a new file to hold the webpage:

touch /home/charlie/pyflask/app.py

vi /home/charlie/pyflask/app.py

I then pasted this python code to the new app.py file:



#we used uv to install the flash module in our venv already.

from flask import Flask 

app = Flask(__name__)

@app.route('/')

def hello():

    return 'Hello, World!'

if __name__ == '__main__':

    app.run(host='0.0.0.0', port=5000, debug=True)

 

...Going back to the terminal I started the new app.




#START PYTHON APP using UV
#use this to test your flask setup.

uv run app.py  

#if you just use python3 app.py the path gets wacked and it won't work

#term will show something like what is below--if flask is ready 
#to process browser GETS and PUTS

########################################

 #* Serving Flask app 'app'
 #* Debug mode: on
#WARNING: This is a development server. Do not use it in a production #deployment. Use a production WSGI server instead.

 #* Running on all addresses (0.0.0.0)
 #* Running on http://127.0.0.1:5000
 #* Running on http://192.168.5.200:5000
#Press CTRL+C to quit

 #* Restarting with stat
 #* Debugger is active!
 #* Debugger PIN: 430-777-572
 
 



ACCESSING THE FLASK WEBPAGE FROM A BROWSER

Once I had everything above working it was time to test from a browser:

#if I open browser or curl using PC on

#the same wifi subnet as RPi Zero using port 5000 

#I see hello world.  

#Your IP will be different, but I hope you get the idea.

http://192.168.5.200:5000/

GETTING FLASK TO AUTO-START

Our indestructible SBC's test app (in this case app.py) had to start on power-up every time.  

There were lots of ways I could have done this, but to me the "Debian way" was to use systemd--video series about how systemd works starts here

Let's get right to it--using the Linux terminal:

#we need to create a .service file for our python app.

#in Raspberry pi and maybe other debian likes create this in this dir.

#/etc/systemd/system/

#remember that systemd only works with

#the full path to a file or executable

#the command to create the .service file using vi on a raspberry pi is this.

sudo vi /etc/systemd/system/flaskapp.service

#what I put in this new file....






[Unit]
Description=My Flask App

# Wait for the network to be ready before starting (crucial for Flask)
After=network.target

[Service]

# The user the app should run as 
User=charlie

# The group  
Group=charlie



# The folder where your app.py is located
WorkingDirectory=/home/charlie/pyflask
 

# The command to start the app. 
# format: /path/to/python /path/to/app.py
ExecStart=/home/charlie/pyflask/.venv/bin/python3 /home/charlie/pyflask/app.py

# Automatically restart the app if it crashes
Restart=always

# Send Python output to the system log immediately
Environment=PYTHONUNBUFFERED=1

[Install]
WantedBy=multi-user.target
 

 
 


FINISHING UP OUR BUILD

#let's load it; first, make the system reread all systemd config files...

sudo systemctl daemon-reload

#enable at boot

sudo systemctl enable flaskapp.service

#start it

sudo systemctl start flaskapp.service

#see its status

sudo systemctl status flaskapp.service

#stop the service (no changes needed to the service file for this)

sudo systemctl stop flaskapp.service

#restart the service

sudo systemctl restart flaskapp.service

OUTTRO


I ran the steps above on a Raspberry Pi Zero W, and it worked great--I was ready to start building Linux- and Python-based projects that power up and down like any other DiWHY build.

Now that I have a framework for a SBC based DiWHY project, what should I build?  

No idea. 

I have read about connecting CODECS and DACS to Raspberry Pi's, I might start there and design my own experimenters' boards; wait, there's more! Tons of support for what we usually do with Microcontrollers and C/C++ but instead, RPi and Python:

OLEDs? Here.
Rotary encoders? here.
ADC?  Here.
GPIO and bit-banging?  Here.
SPI and I2C? Here.

But for now it's time to inhale zero fumes. 

See ya next time!

 

No comments:

Post a Comment