Self-Hosting Mini Series
This devlog is different than most. We're taking a short break from the main Breezy project for this miniseries on self hosting. Part one sets up Void Linux on a Raspberry Pi, configures our wi-fi, and sets up a user account with passwordless ssh access. Part two deploys an example web app -- in this case, it's an agenda app I made as a proof of concept for a future project. Then part 3 will set up a VPN that secures our pi and webapp from the outside world while still permitting ourselves access.
Each of the three parts will be released within a couple days of the prior, and this page will be updated with additional info as each part is released.
Part 1: Void Linux on Pi
In this devlog, we install Void Linux on our pi and get it prepared for secure use. If you're following along, the main commands used throughout the video are captured below.
The first step is to download, install, and flash your Raspberry Pi with Void Linux. The process for doing this is shown in this week's video, and there's not really any commands to put here.
Once you have Void Linux installed and running, you'll connect to it and then step through the following:
Switch your shell from
dashtobash:# You can check your shell with this: echo $SHELL # Then to change it (permanently), run this: chsh -s /bin/bash # Lastly, exit and reconnect.Enable your wireless LAN adaptor:
# Confirm your wireless adapter is called "wlan0". If not, adjust the future commands. ip link # "Turn on" your wireless adapter ip link set wlan0 up # Enable the wpa_supplicant service ln -s /etc/sv/wpa_supplicant /var/service/Configure your wireless network:
# Enter the wpa CLI to configure your wlan0 interface wpa_cli -i wlan0 # Scan your network scan scan_results # Create and configure your network. "add_network" returns an ID to use # in the subsequent commands - probably "0". add_network set_network 0 ssid "Your Network Name" set_network 0 psk "y0ur-n3tw0rk-pa55w0rd" # Save and quit save_config quit # Figure out your local IP address, which is next to "inet" under "wlan0". ip addrUpdate/install your XBPS packages:
# Update XBPS, the Void Linux package manager xbps-install -Su xbps-install -u xbps # Search for and install your favorite text editor. Ex: "vim" xbps-query -Rs vim xbps-install -Su vimCreate and configure your user:
# Create your user, and add them to the "wheel" group. # (Replace "ben" w/ your desired username ofc.) useradd -m -s /bin/bash passwd ben usermod -aG wheel ben # Give all "wheel" group users access to "sudo". This opens the "vi" editor: visudo # To edit the file, press the following keys exactly. "<Enter>" and "<Esc>" # should be the actual, full keys. NOT the individual letters. /wheel <Enter> j0xx :wq <Enter> # If things go south, you can quit without saving by doing: <Esc> :q! <Enter>Enable passwordless ssh login
From your source computer -- not your pi session:
# Generate a key. Default values are fine. ssh-keygen -t ed25519 # Print the public key, and then copy it to your clipboard. cat ~/.ssh/id_ed25519.pubFrom your Raspberry Pi session:
mkdir ~/.ssh # Create a new file in that directory called "authorized_keys", # and paste your public key from above as its contents. Either # use your favorite editor (ex: vim ~/.ssh/authorized_keys), # or use this one-liner, replacing the text between the quotes: echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHojeB3gi8e+1ki6r7iQWVey3aIYrDHFslKQnpGUETYK ben@Bens-MacBook-Air.local" > ~/.ssh/authorized_keysFrom your source computer, verify you have ssh access without providing a password:
# ssh username@ip-address ssh ben@10.0.0.120 # Then quit the ssh session. exitBack to your Raspberry Pi session, edit the
/etc/ssh/sshd_configfile with sudo and your editor of choice, making the following change:- #PasswordAuthentication yes + PasswordAuthentication noThen restart the sshd service:
sudo sv restart sshdAs a final sanity check, it's good to confirm your pi access by running these from your source computer:
# Make sure you cannot ssh as root anymore: ssh root@<ip-address> # Make sure you *can* still access with your custom user: ssh ben@<ip-address> # Then quit the ssh session. exit
Part 2: Deploying a Web App
In devlog 6, we deploy an example personal webapp to our prepared pi server, and run it as a runit service. The web app we'll be using is a simple agenda app serving as an experiment for something to integrate into Breezy one day. If you're following along, I've captured the main commands used throughout the video below.
To start off, I'll assume you have an active terminal session with your pi - whether that be via ssh or a direct connection. Given that, to get the agenda web app set up, you'll need to:
Install some prerequisites (
git,curl, andnvm):sudo xbps-install -Su git curl curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.5/install.sh | bashTo activate nvm, you'll need to exit out of your active terminal session and reconnect.
In your new session, you should install
nodeandlibatomic(which is needed by node):# Install node nvm install node sudo xbps-install -Su libatomic # Confirm node/npm are installed node --version npm --versionThen prep the git directory for our agenda app:
mkdir ~/git cd ~/git git clone https://github.com/breezy-os/breezy-agenda.git...and build and run the app:
# Build the frontend cd ~/git/breezy-agenda/frontend npm install npm run build # Build (and run) the backend cd ~/git/breezy-agenda/backend npm install node ./index.ts # Open browser to <ip-address>:3000 to confirm it's working # ...then, Ctrl+C from terminal to stop the process.Now to configure that app as a runit service, first you'll need to note the directory we run the webapp from, and the full path to the node installation. You can get those by running:
# Current directory (where we ran the above "node" command from) pwd # Full path to the node executable which nodeTo keep those values on-screen / visible, I open a second terminal session to my pi to run the below commands from. As long as you know the values, it's up to you how you keep them handy.
Create the "breezy-agenda" service config directory:
sudo mkdir -p /etc/sv/breezy-agendaCreate and edit the
/etc/sv/breezy-agenda/runfile, which is the main "run" file for your service. Give it the following contents, setting the three fields between the angled brackets<...>as appropriate:#!/bin/sh exec chpst -u <username> /usr/bin/env -C <pwd-output> <which-node-output> ./index.ts # For example: # exec chpst -u ben /usr/bin/env -C /home/ben/git/breezy-agenda/backend /home/ben/.nvm/versions/node/v26.3.0/bin/node ./index.tsMake it executable, and then activate the service:
sudo chmod +x /etc/sv/breezy-agenda/run sudo ln -s /etc/sv/breezy-agenda /var/service/ # Confirm the service is running: sudo sv status breezy-agendaAccess the web app in your browser (same IP and port as before) then create a new user account. Delaying this will cause the (upcoming) backup script to fail since the data files wouldn't exist.
Install
snooze, our scheduling service, then configure it to run every day by activating that service.sudo xbps-install -Su snooze sudo ln -s /etc/sv/snooze-daily /var/service/Create and edit
/etc/cron.daily/back-up-agendawith the following contents, updating the path in thecpcommand as appropriate:#!/bin/sh BACKUP_DIR="/root/agenda-backups/$(date +%Y%m%d)" # Copy the web app's data files into a datestamped directory. mkdir -p $BACKUP_DIR cp /home/ben/git/breezy-agenda/backend/data/* $BACKUP_DIR # Delete any backups older than 30 days. find /root/agenda-backups -maxdepth 1 -mindepth 1 -type d -mtime +30 -exec rm -rf {} +Make the backup script executable, and verify it works:
sudo chmod +x /etc/cron.daily/back-up-agenda # Switch to the root user sudo su - # Try to manually execute the backup script /etc/cron.daily/back-up-agenda # And then verify there are results backed up ls -al /root/agenda-backups/*
For tips on how to use your new web app, check out its Github repo's README.
Part 3: Configuring a VPN
Devlog 7 is the epic conclusion to this sidequest of wonderment. We deploy our very own, personal VPN server which restricts external access to both our pi and web app to just ourselves. If you're following along, the main commands used throughout the video are captured below.
Network configurations tend to be a bit finnicky and can differ from one household to the next. If you run into any hiccups with your router or network configuration that you think could be helpful for other readers to know about, please do send your tips my way and I can include them on this page 👍
You can send an email to: breezy@zenittini.dev
To start off, I'd recommend opening two active terminal sessions with your pi, and get any devices ready that you'll want to connect to your pi remotely. Then, step through the following:
From your pi server, start off by installing the WireGuard VPN:
sudo xbps-install -Su wireguardThen, you'll want to generate a key pair for your pi server, and a key pair for each client you want to connect to it.
# For convenience, I'll put all my key files in this directory. mkdir ~/wireguard-keys cd ~/wireguard-keys # Generate a key pair for the pi server wg genkey | tee ./pi-server.priv | wg pubkey > ./pi-server.pub # Generate a key pair for *each* client you want to connect. wg genkey | tee ./client1.priv | wg pubkey > ./client1.pubI'd recommend switching to a different terminal session connected to your pi for the following steps. That way, you still have access to the key files you just generated.
Edit
/etc/wireguard/wg0.confwithsudo, and adapt the following contents to your needs. Repeat the[Peer]section for each client you want to connect, making sure to give each a different IP address that's part of your WireGuard subnet.[Interface] PrivateKey = <contents of pi-server.priv> Address = 10.0.1.1/24 ListenPort = 51820 [Peer] PublicKey = <contents of client1.pub> AllowedIPs = 10.0.1.2/32Start the WireGuard service, and ensure it's running:
# Enable and start the service sudo ln -s /etc/sv/wireguard /var/service/ # Check its status sudo sv status wireguard sudo wg show wg0In each of your clients, create a configuration file that tells your WireGuard client how to connect to your pi server. You can put the file wherever you want, and name it however you'd like. Adapt the following contents to fit your needs, and make sure you assign each the same IP address that you configured for it on the pi server:
[Interface] PrivateKey = <contents of client1.priv> Address = 10.0.1.2/32 [Peer] PublicKey = <contents of pi-server.pub> AllowedIPs = <the internal IP address of your pi>/32 Endpoint = <your external IP address>:51820 PersistentKeepalive = 25Now download the WireGuard client application from WireGuard's website. Import the file you just created as a "tunnel".
Once that's done, you'll need to set up port forwarding in your router. These steps will differ based on your router, but you'll want to log in to your router somehow, poke around for something that says "port forwarding" (oftentimes in your "Advanced Settings"), and set up a port forward with the following configuration:
- Device: Your Raspberry Pi
- Port Number: 51820
- Packet Type: UDP
Once that's all set up, you should be ready to go! You can test your setup by connecting your laptop to a different, public network (such as your phone's hotspot), turning on your VPN, and verifying you can access your pi using its internal IP address.
Small tip for using your hotspot: Also make sure to turn off your phone's wi-fi. If it's still connected to your home wi-fi, then it'll mess up this test.
As a final cleanup step, you can delete the key pair files we generated earlier (
pi-server.pub,pi-server.priv,client1.pub, etc). Those are no longer needed, so best to make sure they don't fall into the wrong hands.# From your pi cd ~ rm -rf ~/wireguard-keys
We're finished with our pi server for now, and moving onto our clients. For each client you want to connect, you'll perform the following steps from the client machine.
Related Links
Third-Party Links
- Void Linux: https://voidlinux.org/
- runit: https://smarden.org/runit/
- Raspberry Pi: https://www.raspberrypi.com/
- WireGuard: https://www.wireguard.com/
- NVM: https://github.com/nvm-sh/nvm
Internal Links
- Breezy Agenda: https://github.com/breezy-os/breezy-agenda
- My Void Linux notes: "Linux > Void Linux"