Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 23 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,16 @@ Lightning) with **zero manual install** — flash, boot, open
Manually installing Alby Hub on a Pi means SSH + `curl | bash` + a signature
prompt + systemd setup. This image bakes all of that in, so onboarding is just:

1. Flash the image with **Raspberry Pi Imager 2.0+** (“Use custom”), set WiFi + password.
2. Insert the card, power on, wait ~2 min.
1. Flash the image with **Raspberry Pi Imager 2.0+** (“Use custom”). Setting WiFi
is optional — you can also configure it on first boot (step 2).
2. Insert the card, power on, wait ~2 min. If the Pi has no internet yet, it
raises an open WiFi network **`albyhub-setup`** — join it from your phone,
pick your home WiFi, and enter the password. (Powered by
[balena wifi-connect](https://github.com/balena-os/wifi-connect).)
3. Open `http://albyhub.local`, set a password, save your seed, open a channel.

No terminal. Tested target: Raspberry Pi Zero 2 W (512 MB) and up.
No terminal, no computer needed for WiFi. Tested target: Raspberry Pi Zero 2 W
(512 MB) and up.

## What's baked in (`src/modules/albyhub`)

Expand All @@ -24,14 +29,20 @@ No terminal. Tested target: Raspberry Pi Zero 2 W (512 MB) and up.
with a low-resource default configuration).
- Port-80 binding via systemd `AmbientCapabilities`; `ld.so.conf.d` entry for the bundled libraries.
- `gpu_mem=16` (frees ~48 MB) and `vm.swappiness=10` + 1 GB swap.
- `avahi-daemon` for `albyhub.local`; default hostname `albyhub`.
- `avahi-daemon` for `albyhub.local`; hostname pinned to `albyhub`.
- **WiFi onboarding** via `wifi-connect` (pinned release, checksum-verified):
`wifi-connect.service` raises the `albyhub-setup` portal on first boot only if
the device is offline, then exits so the hub takes `:80`. NetworkManager +
`dnsmasq-base` back it; a WiFi regulatory domain (`ALBYHUB_WIFI_COUNTRY`) is
baked in because AP mode needs one.

## What Imager handles at flash time (not baked)

WiFi credentials, SSH enable/key, user/password, locale/timezone, hostname —
applied on first boot. **Use Raspberry Pi Imager 2.0 or newer** — older versions
don't reliably apply customization to current Raspberry Pi OS (cloud-init), which
silently drops the SSH/WiFi settings.
SSH enable/key, user/password, locale/timezone — applied on first boot. WiFi is
**optional** here: set it in Imager to skip the portal, or leave it blank and use
the `albyhub-setup` portal on first boot. **Use Raspberry Pi Imager 2.0 or newer**
— older versions don't reliably apply customization to current Raspberry Pi OS
(cloud-init), which silently drops the SSH/WiFi settings.

## Build

Expand Down Expand Up @@ -75,6 +86,7 @@ app auto-updater off entirely.

## Status

Builds green in CI and boots on a Raspberry Pi Zero 2 W (setup screen serves on
`:80`). Flash with **Raspberry Pi Imager 2.0+** so first-boot customization
applies. Not yet tagged as a release.
Builds green in CI and boots on a Raspberry Pi Zero 2 W. On first boot with no
internet, the `albyhub-setup` WiFi portal comes up; once connected, the hub
serves on `:80` at `albyhub.local`. Flash with **Raspberry Pi Imager 2.0+** so
first-boot customization applies. Not yet tagged as a release.
5 changes: 5 additions & 0 deletions src/config
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
export DIST_NAME=HubOS
export DIST_VERSION=0.1.0

# Hostname the device advertises (avahi -> albyhub.local). Without this, the base
# module defaults the hostname to the lowercased DIST_NAME ("hubos"), which would
# break the albyhub.local onboarding story.
export BASE_OVERRIDE_HOSTNAME=albyhub

# rpi-imager metadata
export RPI_IMAGER_NAME="Alby Hub"
export RPI_IMAGER_DESCRIPTION="Self-custodial Lightning wallet on your Pi. No full node, no manual install."
Expand Down
21 changes: 21 additions & 0 deletions src/modules/albyhub/config
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,24 @@ export ALBYHUB_HOME="/opt/albyhub"
export ALBYHUB_USER="albyhub"
export ALBYHUB_SWAP_MB="1024"
export ALBYHUB_HOSTNAME="albyhub"

# --- WiFi onboarding: balena wifi-connect ---
# The de-facto standard first-boot WiFi captive portal for headless Linux devices.
# On first boot with no internet it raises an open AP ("albyhub-setup") hosting a
# portal where the user picks their WiFi; then it writes a NetworkManager profile
# and exits, and Alby Hub comes up at albyhub.local. WiFi-only by design — every
# hub setting is configured in Alby Hub's own onboarding UI once it is online.
#
# Pinned release, downloaded + checksum-verified in the chroot (same reproducible
# pattern as the Alby Hub binary above). balena publishes no checksum manifest, so
# the SHA-256s are pinned here directly.
export WIFICONNECT_VERSION="v4.11.84"
export WIFICONNECT_SHA256_BIN="413d70e6d1c1366cbe2b32555e8476f3e92878178ed1b9c82205985f055f1936"
export WIFICONNECT_SHA256_UI="e57a3cec559729516decf892beb1e7f191b23e71b2e13bcd43d36b980034ffbe"

# WiFi regulatory domain (ISO 3166-1 alpha-2). REQUIRED for the setup AP to come
# up — a fresh image sits in the restrictive "world" (00) domain, which flags
# 2.4GHz channels NO-IR and blocks AP beaconing. wifi-connect does not set a
# regdom itself, so one must be baked in. Channel 6 @ 2.4GHz is permitted in
# essentially every domain; installers elsewhere can change it.
export ALBYHUB_WIFI_COUNTRY="US"
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
[Unit]
Description=WiFi onboarding captive portal (balena wifi-connect)
Documentation=https://github.com/balena-os/wifi-connect
After=NetworkManager.service
Wants=NetworkManager.service
# Run to completion before the hub starts. wifi-connect serves its portal on :80
# (same port as the hub) and exits once WiFi is configured, so ordering it before
# albyhub.service means the hub only claims :80 after onboarding is done — no port
# race, no separate wait gate needed.
Before=albyhub.service

[Service]
Type=oneshot
RemainAfterExit=yes
# AP mode needs the radio usable: a fresh Pi image often has wlan0 rfkill-soft-
# blocked. (The regulatory country AP mode also requires is baked into the image
# by the albyhub module — see ALBYHUB_WIFI_COUNTRY.) Leading "-" = non-fatal.
ExecStartPre=-/usr/sbin/rfkill unblock wifi
ExecStartPre=-/usr/bin/nm-online -s -q -t 30
ExecStartPre=-/usr/bin/nmcli radio wifi on
ExecStart=/usr/local/bin/albyhub-wifi-connect
# The portal blocks until the user picks a network; never time it out.
TimeoutStartSec=infinity
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env bash
# Raise balena wifi-connect's captive portal ("albyhub-setup") on first boot when
# the device has no internet, so the user joins their home WiFi with no computer.
# Once they pick a network, wifi-connect writes a NetworkManager profile and exits;
# NM brings the link up and Alby Hub (also :80) starts. A no-op when already online,
# so normal reboots pass straight through.
set -u

# Already online? Nothing to do — let the boot continue to the hub.
if curl -fsS --max-time 5 -o /dev/null https://getalby.com/api/internal/info; then
exit 0
fi

exec /usr/local/sbin/wifi-connect \
--portal-ssid albyhub-setup \
--ui-directory /usr/local/share/wifi-connect/ui
33 changes: 33 additions & 0 deletions src/modules/albyhub/start_chroot_script
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ unpack /filesystem/root /
# --- dependencies ---
apt-get update
apt-get install -y --no-install-recommends bzip2 ca-certificates gnupg avahi-daemon libcap2-bin unattended-upgrades
# WiFi onboarding deps: NetworkManager (already default on RPi OS, but pin it),
# its dnsmasq backend which wifi-connect drives for the AP's DHCP/DNS, and the
# radio tools the wifi-connect unit calls (rfkill / iw / nmcli).
apt-get install -y --no-install-recommends network-manager dnsmasq-base rfkill iw

# --- dedicated, unprivileged service user ---
if ! id -u "$ALBYHUB_USER" >/dev/null 2>&1; then
Expand Down Expand Up @@ -83,7 +87,36 @@ sed -i "s/127.0.1.1.*/127.0.1.1\t${ALBYHUB_HOSTNAME}/" /etc/hosts || true
# --- auto-update: make the weekly updater executable ---
chmod +x "$ALBYHUB_HOME/autoupdate.sh"

# --- WiFi onboarding: balena wifi-connect ---
# Download the pinned release (binary + web UI) and verify checksums, same
# reproducible pattern as the Alby Hub binary above. The arm64 asset matches the
# image arch, so it installs straight into the (qemu) chroot. WiFi-only: all hub
# settings live in Alby Hub's own onboarding UI once the device is online.
WC_TMP="$(mktemp -d)"
WC_BASE="https://github.com/balena-os/wifi-connect/releases/download/${WIFICONNECT_VERSION}"
wget -q -O "$WC_TMP/wifi-connect.tar.gz" "$WC_BASE/wifi-connect-aarch64-unknown-linux-gnu.tar.gz"
wget -q -O "$WC_TMP/wifi-connect-ui.tar.gz" "$WC_BASE/wifi-connect-ui.tar.gz"
echo "$WIFICONNECT_SHA256_BIN $WC_TMP/wifi-connect.tar.gz" | sha256sum -c -
echo "$WIFICONNECT_SHA256_UI $WC_TMP/wifi-connect-ui.tar.gz" | sha256sum -c -
tar -xzf "$WC_TMP/wifi-connect.tar.gz" -C "$WC_TMP"
install -m 0755 "$WC_TMP/wifi-connect" /usr/local/sbin/wifi-connect
install -d /usr/local/share/wifi-connect/ui
tar -xzf "$WC_TMP/wifi-connect-ui.tar.gz" -C /usr/local/share/wifi-connect/ui
rm -rf "$WC_TMP"
chmod +x /usr/local/bin/albyhub-wifi-connect

# WiFi regulatory country — REQUIRED for the setup AP (world/00 domain flags
# 2.4GHz NO-IR and blocks AP mode). Use the RPi-blessed path when available
# (handles rfkill + persistence), else write the legacy regdb.
if command -v raspi-config >/dev/null 2>&1; then
raspi-config nonint do_wifi_country "$ALBYHUB_WIFI_COUNTRY" || true
else
echo "REGDOMAIN=$ALBYHUB_WIFI_COUNTRY" > /etc/default/crda
fi

# --- enable services (start happens on first boot, not in chroot) ---
systemctl_if_exists enable NetworkManager.service
systemctl_if_exists enable wifi-connect.service
systemctl_if_exists enable albyhub.service
systemctl_if_exists enable avahi-daemon.service
systemctl_if_exists enable albyhub-update.timer
Loading