Small Pubnix
I had a dormant Raspberry Pi 4 that I was gifted but never really used, until I permacomputing-shamed myself into figuring out a use for it. I ended up hosting my own websites on and playing around with gemini and the fediverse. I tried to have some other services running, like a rss client or a read later server, but I ended up to wanting to keep it is a small and low as possible. It looks like this currently
- Alpine Linux
- Caddy for static file serving and reverse proxy
- Agate as gemini capsule to serve gemtext files
- Snac doing it’s ActivityPub Fediverse thing
- Radicale for calendars, todos and contacts
- Offpunk which is a luxurious and cool terminal way to access the web
- Watchdog for automatic reboots if the Raspberry Pi hangs
Basic Setup
I used the official Raspberry Pi Imager to burn Alpine to a micro sd card. I have a 32GB card that came with the Raspberry Pi. Next I followed the steps outlined here to have a headless setup, but I didn’t do the memory optimization things yet.
Basically I had to add an archive file to the root of the newly setup sd card, and setup a wireless connection.
$ wget https://github.com/macmpi/alpine-linux-headless-bootstrap/raw/469ee440e7d394cca0976c78f357e7a0e1c82cc4/headless.apkovl.tar.gz
$ cat > wpa_supplicant.conf << EOF
country=FR
network={
key_mgmt=WPA-PSK
ssid="mySSID"
psk="myPassPhrase"
}
EOFNext:
- SSH into the Pi
- Finish Alpine setup
ssh root@local.ip.address
setup-alpine
rebootApplications
- Activate the community repositories
- Install caddy, radicale, and nano
setup-apkrepos -c
apk add caddy radicale nanoFor the moment I ommit information on how to setup the
domain, the dns records, the port forwarding, but that’s all
part of the setup as well if you want to access the locally
running Pi from the outside. I also chose to the applications
running under /srv/* together with their data and
configs. That makes it easier for me to create propoer backups
later, I hope (but I have to set that up yet).
Caddy
Caddy is a web server that can do a lot of things, like serving websites or route incoming requests to the right application. I use it to serve static websites and make calendars and contacts be accessed via subdomain.
After installation I only had to change the config file so I
can have my setup in /srv/caddy/Caddyfile.
nano /etc/init.d/caddyand change the following line
: ${caddy_opts:="--config /srv/caddy/Caddyfile --adapter caddyfile"}Radicale
Radicale is a caldav and carddav server. It’s made for
hosting calendars, todo lists and contacts. Installing it via
Alpine’s package manager does most of the magic, but I adjusted
the path so everything was in /srv/radicale.
nano /etc/init.d/radicaleand changed the following
command_args="--config /srv/radicale/config"
[…]
checkpath -f -o root:radicale -m640 /srv/radicale/config
checkpath -f -o root:radicale -m640 /srv/radicale/rights
checkpath -f -o root:radicale -m640 /srv/radicale/users- Setup to start radicale automatic after reboot with
rc-update add radicale default - Start radicale with
rc-service radicale start
Agate
Agate is a
very simple gemini server, but it does a fantastic job. The
gemtext files live under /srv/agate/data, and I
installed the whole thing like this.
apk add gcompat
cd ~
wget https://github.com/mbrubeck/agate/releases/download/v3.3.20/agate.aarch64-unknown-linux-gnu.gz
gunzip agate.aarch64-unknown-linux-gnu.gz
mv agate.aarch64-unknown-linux-gnu /usr/sbin/agate
chmod +x /usr/sbin/agateThe gcompat package is needed because otherwise the binary wouldn’t run. Since agate wasn’t installed via Alpine package manager, I had to add some configs manually. There needs to be a service entry, which we can add to the default service startup as well
mkdir -p /srv/agate/data
touch /etc/init.d/agate
chmod +x /etc/init.d/agate
nano /etc/init.d/agateMy openrc config for agate looks like this.
#!/sbin/openrc-run
name="agate"
description="agate gemini server"
directory="/srv/agate"
command="/usr/sbin/agate"
command_args="--content data/ --addr [::]:1965 --addr 0.0.0.0:1965 --hostname gems.grandmarais.ch --lang en-US"
command_background=true
pidfile="/run/agate.pid"Now we can add the service and start it.
rc-update add agate default
rc-service agate startsnac
snac2 is an awesome ActivityPub server, and quite easy to install. Grabbed it from the Alpine repository, created the folders and initiated snac, then changed who owns the data folder, so snac can write to it.
apk add snac
mkdir -p /srv/snac
cd /srv/snac
snac init data
chown snac:snac -R data/Don’t forget to setup fediverse users. snac already has an init.d entry, and I just changed the data folder.
rc-update add snac default
nano /etc/init.d/snac
[...]
: ${SNAC_DATA:="/srv/snac/data"}
[...]Start said services and/or reboot.
rc-service snac startWatchdog
Watchdog regularly controls if the Raspberry Pi is still running and reboots if necessary. We have to install it via the edge branch of Alpine.
apk add watchdog --repository=https://dl-cdn.alpinelinux.org/alpine/edge/testing/nano /etc/init.d/watchand add the following lines in the first free space on top.
WATCHDOG_DEV=/dev/watchdog
# WATCHDOG_OPTS="-t 10 -T 20"Offpunk
Offpunk is a really gread terminal browser that can do http, gemini and gopher, as well as subscribe to websites and feeds and has some other luxurious features. Great thing to have around for all things regarding accessing the web.
apk add offpunk --repository=https://dl-cdn.alpinelinux.org/alpine/edge/testing
apk add py3-beautifulsoup4 py3-readability-lxmlBackups
Is done via webdav in my case. Added the following script to
the folder /etc/periodic/daily/ and make it
executeable. Also had to apk add curl (install
curl).
#!/bin/sh
WEBDAV_URL=""
USERNAME=""
PASSWORD=""
APPS="caddy agate radicale snac"
for APP in $APPS; do
SOURCE_DIR="/srv/$APP"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$APP-$TIMESTAMP.tar.gz"
echo "Backing up: $SOURCE_DIR"
# Ensure source dir exists
if [ ! -d "$SOURCE_DIR" ]; then
echo "Source directory $SOURCE_DIR does not exist. Skipping."
continue
fi
# Create archive
tar -czf "$BACKUP_FILE" -C "$SOURCE_DIR" .
if [ $? -ne 0 ]; then
echo "Failed to create archive for $APP"
continue
fi
# Upload via WebDAV
echo "Uploading to: $WEBDAV_URL$BACKUP_FILE"
curl -v -u "$USERNAME:$PASSWORD" -T "$BACKUP_FILE" "$WEBDAV_URL$BACKUP_FILE"
# Check upload result
if [ $? -eq 0 ]; then
echo "Success: $BACKUP_FILE uploaded"
rm "$BACKUP_FILE"
else
echo "Upload failed for $BACKUP_FILE"
fi
echo "---"
done