overview
A Raspberry Pi 5 (8 GB) on my desk runs most of what I host. It's a NAS for movies, shows, music and school files, the backend for this website's misc page, the production server for NEU SquashHub, the SSH portfolio, and Home Assistant. Everything except the OS lives on an external SSD, gets backed up to a second drive every night, and is reachable from my phone and laptop anywhere over Tailscale.
The Flask API behind this site and SquashHub both used to run on an AWS EC2 instance. They moved here and the AWS setup is gone.
storage
nofail and a 10 second timeout. A missing drive can't stop the
Pi from booting.
That decision got tested once: a drive came unplugged mid-write. The recovery was
dmesg and journalctl to confirm what happened, unmount, fsck -f,
remount, restart the file server. No data lost.
The Pi itself also got two stability fixes after an rsync crash: the USB ports are allowed their full current budget so two drives don't brown out, and the hardware watchdog is on, so a hard lockup reboots itself within two minutes instead of waiting for me to get home.
backups
rsnapshot copies the SSD to the HDD every night at 2 AM, then keeps 7 daily, 4 weekly and 6 monthly snapshots. Each snapshot looks like a full copy, but unchanged files are hard links to the previous one, so six months of history costs barely more than one copy.
the setting i'd tell anyone to turn on: no_create_root. if the backup drive isn't mounted, rsnapshot refuses to run instead of happily writing a full backup onto the Pi's SD card and filling it.
One thing that confused me at first: du over-reports a single snapshot, because a
hard-linked file gets counted against whichever snapshot du sees first.
df -h tells the truth.
services
api.vedsite.com through a Cloudflare Tunnel. No open ports
on my router.
neusquashhub.com through the same tunnel.
ssh portfolio.vedsite.com,
reached through a small Oracle Cloud relay.
how i use it: piplay
Most of the time the NAS is a movie library. Browsers can't play most of the audio tracks in my MKV
files (DTS-HD, TrueHD, AC3), and the file server hands the raw file to the browser without
transcoding. So I wrote piplay, a shell function on my Mac that streams any file or
folder straight into mpv.
piplay "movies/Paris, Texas (1984)"
give it a folder and it plays the biggest video inside, which is almost always the film and not the sample or the extras.
Shift+Q and coming back tomorrow picks up at the same frame.
monitoring
The first thing on my wall tablet's dashboard is a live panel for the Pi: CPU, memory, both drives and every container. It's built on Beszel, but the panel itself is mine.
The agent on the Pi reads system stats and the Docker socket. The hub runs on my Oracle Cloud VM, not the Pi, so if the Pi goes down the thing watching it doesn't go down with it. A private route in the same Flask API logs into the hub as a read-only user and flattens the latest sample into one small JSON response, and a plain HTML/CSS/JS page inside Home Assistant renders it. Beszel only shows drives you declare explicitly, so both mounts are listed by hand.
construction
Each service is its own Docker Compose project, and bind mounts always point at the parent directory rather than a single file, because anything that writes atomically (write a temp file, then rename it over the old one) breaks a single-file mount. Logs persist across reboots, capped at 500 MB.
architecture
Public traffic only ever reaches the two things meant to be public, through Cloudflare. Everything
else is private to my tailnet.
Source? This one is mostly configuration rather than code, so there's no repo. The
API it runs is what powers my misc page.