2026-02-01 05:42:05 +00:00
#!/bin/bash
#
# Build Archipelago Auto-Installer ISO (StartOS-like)
#
# This creates an ISO that automatically installs to the internal disk
# with minimal user interaction - similar to StartOS experience.
#
2026-02-03 21:43:33 +00:00
# CRITICAL: This script CAPTURES the LIVE SERVER state by default.
# Set DEV_SERVER to point to your development server.
#
# Usage:
# DEV_SERVER=archipelago@192.168.1.228 ./build-auto-installer-iso.sh
# OR just: ./build-auto-installer-iso.sh (uses default server)
#
# To build from source instead:
# BUILD_FROM_SOURCE=1 ./build-auto-installer-iso.sh
#
2026-02-01 05:42:05 +00:00
# Features:
# - Pre-built root filesystem (no network needed during install)
# - Auto-detects internal disk (skips USB boot drive)
# - Automatic installation with progress display
# - Boots directly to web UI after install
#
2026-03-21 03:06:29 +00:00
# Image versions: sourced from scripts/image-versions.sh (single source of truth).
# All container image references MUST use the $*_IMAGE variables defined there.
#
# --- PLANNED REFACTOR (post-beta) ---
# This script is ~1870 lines and should be split into a modular library.
# Proposed structure:
# image-recipe/
# build-auto-installer-iso.sh — Main orchestrator (config, CLI args, step sequencing)
# lib/
# rootfs.sh — Step 1: Build root filesystem via Docker (~185 lines)
2026-03-27 18:19:49 +00:00
# installer-env.sh — Step 2: Build minimal installer via debootstrap (~80 lines)
2026-03-21 03:06:29 +00:00
# components.sh — Step 3: Add Archipelago components (binary, configs, web UI) (~120 lines)
# container-images.sh — Step 3b: Bundle container images for offline install (~330 lines)
# auto-install-script.sh — Step 4: Generate the embedded auto-install.sh (~615 lines)
# boot-config.sh — Step 5: Configure live boot auto-start + overlay squashfs (~215 lines)
# create-iso.sh — Step 6: Build final bootable ISO with xorriso/grub (~140 lines)
# Each lib/ script exports functions; main script sources them and calls in sequence.
# DO NOT split until tested on the build server — this is critical infrastructure.
# ---
#
2026-02-01 05:42:05 +00:00
set -e
2026-03-21 01:32:28 +00:00
# Source pinned image versions (single source of truth)
SCRIPT_DIR = " $( cd " $( dirname " $0 " ) " && pwd ) "
2026-04-28 15:00:58 -04:00
[ -f " $SCRIPT_DIR /../../scripts/image-versions.sh " ] && . " $SCRIPT_DIR /../../scripts/image-versions.sh "
2026-03-21 01:32:28 +00:00
2026-02-03 21:43:33 +00:00
# Configuration
DEV_SERVER = " ${ DEV_SERVER :- archipelago @192.168.1.228 } "
BUILD_FROM_SOURCE = " ${ BUILD_FROM_SOURCE :- 0 } "
2026-03-10 23:29:05 +00:00
UNBUNDLED = " ${ UNBUNDLED :- 0 } "
2026-03-12 00:19:30 +00:00
ARCH = " ${ ARCH :- x86_64 } "
2026-03-31 23:43:32 +01:00
# ── Sequential build numbering ─────────────────────────────────────────
# Increments on each build. Users see this in UI (Settings, sidebar).
# Counter persists in /opt/archipelago/build-counter (on build machine).
BUILD_COUNTER_FILE = "/opt/archipelago/build-counter"
if [ -f " $BUILD_COUNTER_FILE " ] ; then
BUILD_NUM = $(( $( cat " $BUILD_COUNTER_FILE " ) + 1 ))
else
BUILD_NUM = 1
fi
echo " $BUILD_NUM " | sudo tee " $BUILD_COUNTER_FILE " > /dev/null 2>/dev/null || BUILD_NUM = 1
GIT_SHORT = $( cd " $SCRIPT_DIR /.. " && git rev-parse --short HEAD 2>/dev/null || echo "dev" )
2026-04-02 08:54:39 +01:00
# Version format: major.minor.patch-prerelease (semver)
fix: BUILD_VERSION from Cargo.toml, kiosk scaling, new apps, Rust warnings
Critical:
- BUILD_VERSION was hardcoded as "1.3.0-alpha" — now reads from Cargo.toml
This caused ALL ISOs to show v1.3.0 regardless of actual binary version
Kiosk:
- Remove --disable-gpu flags (broke display scaling on some monitors)
- Add --start-fullscreen --window-size for reliable fullscreen
New apps:
- Nostr VPN, FIPS, Routstr, noStrudel, BotFights, NWNN, 484 Kitchen,
Call the Operator, Arch Presentation, Syntropy Institute, T-0
Rust: suppress dead_code and unused_assignments warnings
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 00:35:52 +01:00
# Read version from Cargo.toml (single source of truth)
BUILD_VERSION = $( grep '^version' " $SCRIPT_DIR /../core/archipelago/Cargo.toml " 2>/dev/null | head -1 | sed 's/version = "//;s/"//' || echo "0.0.0" )
2026-03-31 23:43:32 +01:00
echo " Build # ${ BUILD_NUM } ( ${ BUILD_VERSION } , commit ${ GIT_SHORT } ) "
2026-03-12 00:19:30 +00:00
# Architecture-dependent variables
case " $ARCH " in
x86_64| amd64)
ARCH = "x86_64"
DEB_ARCH = "amd64"
LINUX_IMAGE_PKG = "linux-image-amd64"
GRUB_EFI_PKG = "grub-efi-amd64"
GRUB_EFI_SIGNED_PKG = "grub-efi-amd64-signed"
2026-03-21 01:11:05 +00:00
GRUB_PC_PKG = "grub-pc-bin"
2026-03-12 00:19:30 +00:00
GRUB_TARGET = "x86_64-efi"
2026-03-21 01:11:05 +00:00
GRUB_BIOS_TARGET = "i386-pc"
2026-03-12 00:19:30 +00:00
CONTAINER_PLATFORM = "linux/amd64"
LIB_DIR = " ${ LIB_DIR } "
; ;
arm64| aarch64)
ARCH = "arm64"
DEB_ARCH = "arm64"
LINUX_IMAGE_PKG = "linux-image-arm64"
GRUB_EFI_PKG = "grub-efi-arm64"
GRUB_EFI_SIGNED_PKG = "grub-efi-arm64-signed"
2026-03-21 01:11:05 +00:00
GRUB_PC_PKG = ""
2026-03-12 00:19:30 +00:00
GRUB_TARGET = "arm64-efi"
2026-03-21 01:11:05 +00:00
GRUB_BIOS_TARGET = ""
2026-03-12 00:19:30 +00:00
CONTAINER_PLATFORM = "linux/arm64"
LIB_DIR = "aarch64-linux-gnu"
; ;
*)
echo " ❌ Unsupported architecture: $ARCH (use x86_64 or arm64) "
exit 1
; ;
esac
2026-02-03 21:43:33 +00:00
2026-02-01 05:42:05 +00:00
SCRIPT_DIR = " $( cd " $( dirname " $0 " ) " && pwd ) "
WORK_DIR = " $SCRIPT_DIR /build/auto-installer "
OUTPUT_DIR = " $SCRIPT_DIR /results "
ROOTFS_DIR = " $WORK_DIR /rootfs "
INSTALLER_DIR = " $WORK_DIR /installer "
2026-03-10 23:29:05 +00:00
if [ " $UNBUNDLED " = "1" ] ; then
echo "╔════════════════════════════════════════════════════════════════╗"
echo "║ Building Archipelago UNBUNDLED ISO (no pre-loaded apps) ║"
echo "╚════════════════════════════════════════════════════════════════╝"
else
echo "╔════════════════════════════════════════════════════════════════╗"
echo "║ Building Archipelago Auto-Installer ISO (StartOS-like) ║"
echo "╚════════════════════════════════════════════════════════════════╝"
fi
2026-02-01 05:42:05 +00:00
echo ""
2026-02-03 21:43:33 +00:00
if [ " $BUILD_FROM_SOURCE " = "1" ] ; then
echo "📦 Mode: Building from SOURCE CODE"
2026-03-10 23:29:05 +00:00
elif [ " $UNBUNDLED " = "1" ] ; then
echo "📦 Mode: UNBUNDLED (apps downloaded on-demand from Marketplace)"
echo " Server: $DEV_SERVER (backend + web UI only) "
2026-02-03 21:43:33 +00:00
else
echo "📦 Mode: Capturing LIVE SERVER state"
echo " Server: $DEV_SERVER "
fi
2026-03-12 00:19:30 +00:00
echo " 🏗️ Architecture: $ARCH ( $DEB_ARCH ) "
2026-02-03 21:43:33 +00:00
echo ""
2026-02-01 05:42:05 +00:00
# Check for required tools
check_tools( ) {
local missing = ""
2026-02-14 16:44:20 +00:00
local can_install = false
# Check if we can auto-install (running as root on Debian/Ubuntu)
if [ " $EUID " -eq 0 ] && [ -f /etc/debian_version ] ; then
can_install = true
fi
2026-02-01 18:46:35 +00:00
# Check for docker or podman
if command -v docker >/dev/null 2>& 1; then
CONTAINER_CMD = "docker"
elif command -v podman >/dev/null 2>& 1; then
CONTAINER_CMD = "podman"
else
missing = " $missing docker-or-podman "
fi
2026-03-27 18:19:49 +00:00
for tool in xorriso mksquashfs; do
if ! command -v $tool >/dev/null 2>& 1; then
missing = " $missing $tool "
fi
done
# Check for isolinux MBR (needed for hybrid USB boot)
if [ ! -f /usr/lib/ISOLINUX/isohdpfx.bin ] && [ ! -f /usr/share/syslinux/isohdpfx.bin ] ; then
missing = " $missing isolinux "
2026-02-17 15:03:34 +00:00
fi
2026-03-27 18:19:49 +00:00
2026-02-01 05:42:05 +00:00
if [ -n " $missing " ] ; then
2026-03-27 18:19:49 +00:00
echo " Missing required tools: $missing "
2026-02-14 16:44:20 +00:00
if [ " $can_install " = true ] ; then
2026-03-27 18:19:49 +00:00
echo " Auto-installing missing dependencies..."
2026-02-14 16:44:20 +00:00
apt-get update -qq
2026-03-27 18:19:49 +00:00
2026-02-14 16:44:20 +00:00
if [ [ " $missing " = = *"xorriso" * ] ] ; then
apt-get install -y xorriso
fi
2026-03-27 18:19:49 +00:00
if [ [ " $missing " = = *"mksquashfs" * ] ] ; then
apt-get install -y squashfs-tools
2026-02-17 15:03:34 +00:00
fi
2026-03-27 18:19:49 +00:00
if [ [ " $missing " = = *"isolinux" * ] ] ; then
apt-get install -y isolinux syslinux-common
fi
2026-02-14 16:44:20 +00:00
if [ [ " $missing " = = *"docker-or-podman" * ] ] ; then
echo " Installing podman..."
apt-get install -y podman
CONTAINER_CMD = "podman"
fi
2026-03-27 18:19:49 +00:00
echo " Dependencies installed successfully!"
2026-02-14 16:44:20 +00:00
else
2026-03-27 18:19:49 +00:00
echo " Install with: sudo apt install xorriso squashfs-tools isolinux podman"
2026-02-14 16:44:20 +00:00
echo " Or run this script with sudo to auto-install"
exit 1
fi
fi
# Re-check after potential installation
if command -v docker >/dev/null 2>& 1; then
CONTAINER_CMD = "docker"
elif command -v podman >/dev/null 2>& 1; then
CONTAINER_CMD = "podman"
else
echo "❌ Container runtime still not available after installation"
2026-02-01 05:42:05 +00:00
exit 1
fi
2026-02-01 18:46:35 +00:00
echo " Using container runtime: $CONTAINER_CMD "
2026-03-27 11:12:31 +00:00
2026-03-28 21:01:10 +00:00
# Fix root podman D-Bus issue (sd-bus: Transport endpoint is not connected)
# When running as sudo, systemd cgroup manager can't reach the user D-Bus session.
if [ " $CONTAINER_CMD " = "podman" ] && [ " $( id -u) " = "0" ] ; then
2026-04-09 20:42:09 +02:00
if ! $CONTAINER_CMD run --rm debian:trixie true 2>/dev/null; then
2026-03-28 21:01:10 +00:00
echo " Root podman D-Bus issue detected, using cgroupfs manager"
CONTAINER_CMD = "podman --cgroup-manager=cgroupfs"
fi
fi
2026-05-06 09:23:57 -04:00
# Ensure insecure registry config for Archipelago app registries that are
# intentionally served over HTTP during ISO builds.
if [ [ " $CONTAINER_CMD " = = podman* ] ] ; then
2026-03-27 11:12:31 +00:00
mkdir -p /etc/containers/registries.conf.d
cat > /etc/containers/registries.conf.d/archipelago.conf <<'REGCONF'
2026-05-06 09:23:57 -04:00
[ [ registry] ]
location = "146.59.87.168:3000"
insecure = true
2026-03-27 11:12:31 +00:00
[ [ registry] ]
2026-04-11 09:33:10 -04:00
location = "git.tx1138.com"
2026-03-27 11:12:31 +00:00
insecure = true
REGCONF
fi
2026-02-01 05:42:05 +00:00
}
check_tools
mkdir -p " $WORK_DIR "
mkdir -p " $OUTPUT_DIR "
2026-05-06 09:23:57 -04:00
container_pull( ) {
local image = " $1 "
if [ [ " $CONTAINER_CMD " = = podman* && " $image " = = 146.59.87.168:3000/* ] ] ; then
$CONTAINER_CMD pull --tls-verify= false --platform " $CONTAINER_PLATFORM " " $image "
else
$CONTAINER_CMD pull --platform " $CONTAINER_PLATFORM " " $image "
fi
}
2026-02-01 05:42:05 +00:00
# =============================================================================
# STEP 1: Build complete root filesystem using Docker
# =============================================================================
echo "📦 Step 1: Building root filesystem..."
ROOTFS_TAR = " $WORK_DIR /archipelago-rootfs.tar "
if [ ! -f " $ROOTFS_TAR " ] || [ " $1 " = = "--rebuild" ] ; then
echo " Using Docker to create Debian root filesystem..."
# Create a Dockerfile for building the rootfs
2026-03-12 00:19:30 +00:00
cat > " $WORK_DIR /Dockerfile.rootfs " <<DOCKERFILE
feat(fips): integrate jmcorgan/fips as preferred non-Tor transport + v1.4.0
Bakes the FIPS (Free Internetworking Peering System) mesh daemon into
the node stack, supervised by archipelago alongside Tor. Runs as a
system service, identity derives from the same BIP-39 master seed, and
user-triggered updates track upstream main.
Identity
seed.rs: new HKDF label archipelago/fips/secp256k1/v1 → dedicated
secp256k1 key, distinct from the Nostr-node key for crypto isolation
but still seed-recoverable
identity.rs: writes fips_key[.pub] to /data/identity on onboarding,
chmod 0600; fips_key_exists / load_fips_keys / fips_npub accessors
Transport
TransportKind::Fips=3 inserted between LAN and Tor (Tor bumps to 4)
→ router prefers FIPS over Tor for all peer traffic
PeerRecord gains fips_npub + last_fips fields (serde(default) for
backward-compat with older nodes)
transport/fips.rs: NodeTransport stub, reports unavailable until the
daemon is live so router falls through to Tor cleanly
Federation invites
FederatedNode and FederationInvite carry optional fips_npub
create_invite / accept_invite / peer-joined callback thread it end
to end; signature domain deliberately unchanged — FIPS Noise does
its own session auth, so the unsigned hint only affects path
selection
crate::fips
config.rs: renders /etc/fips/fips.yaml and sudo-installs key material
service.rs: systemctl status/activate/restart/mask wrappers
update.rs: GitHub API check against upstream main; apply stubbed
until per-commit .deb artefact source is decided
RPC + dashboard
fips.status / fips.check-update / fips.apply-update / fips.install /
fips.restart registered in dispatcher
HomeNetworkCard.vue shipped standalone (unmounted — place in Home.vue
when ready); shows state pill, version, FIPS npub, update button,
activate button when key is present but service is down
ISO + systemd
archipelago-fips.service: conditional on key presence, masked by
default — backend unmasks after onboarding writes the key
build-auto-installer-iso.sh: multi-stage Dockerfile builds the FIPS
.deb from jmcorgan/fips main (fail-loud), COPYs it into rootfs, apt
installs it so trixie resolves deps; unit copied + masked
Version bump: 1.3.5 → 1.4.0
Tests: 33 new/updated passing (seed, identity, transport, federation,
fips module, transport::fips).
Known gaps: fips.apply-update returns a clear stub error until
upstream publishes per-commit .deb artefacts; HomeNetworkCard is not
mounted in Home.vue by default.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 22:57:51 -04:00
# ─── Stage 1: Build the FIPS mesh daemon .deb from upstream main ─────────
#
# FIPS (github.com/jmcorgan/fips) is a fast Nostr-keyed mesh routing
# protocol archipelago uses as its preferred non-Tor transport. We track
# upstream main per project decision (2026-04) — v0.2.0 isn't stable yet.
# The .deb is rebuilt every ISO build; Docker layer caching keeps the
# incremental cost low. Failure here fails the ISO build on purpose:
# we don't want to ship an ISO that silently skips FIPS.
FROM rust:1-slim-bookworm AS fips-builder
ENV DEBIAN_FRONTEND = noninteractive
2026-04-19 09:01:59 -04:00
# Build deps tracked as upstream fips adds transitive native deps:
# - libdbus-1-dev: libdbus-sys (observed 2026-04-19 rebuild)
# - libssl-dev: openssl dependencies
# - libnftnl-dev, libmnl-dev, clang, libclang-dev: rustables →
# bindgen (the gateway feature enables rustables for nftables
# integration). bindgen panics without libclang.so.
feat(fips): integrate jmcorgan/fips as preferred non-Tor transport + v1.4.0
Bakes the FIPS (Free Internetworking Peering System) mesh daemon into
the node stack, supervised by archipelago alongside Tor. Runs as a
system service, identity derives from the same BIP-39 master seed, and
user-triggered updates track upstream main.
Identity
seed.rs: new HKDF label archipelago/fips/secp256k1/v1 → dedicated
secp256k1 key, distinct from the Nostr-node key for crypto isolation
but still seed-recoverable
identity.rs: writes fips_key[.pub] to /data/identity on onboarding,
chmod 0600; fips_key_exists / load_fips_keys / fips_npub accessors
Transport
TransportKind::Fips=3 inserted between LAN and Tor (Tor bumps to 4)
→ router prefers FIPS over Tor for all peer traffic
PeerRecord gains fips_npub + last_fips fields (serde(default) for
backward-compat with older nodes)
transport/fips.rs: NodeTransport stub, reports unavailable until the
daemon is live so router falls through to Tor cleanly
Federation invites
FederatedNode and FederationInvite carry optional fips_npub
create_invite / accept_invite / peer-joined callback thread it end
to end; signature domain deliberately unchanged — FIPS Noise does
its own session auth, so the unsigned hint only affects path
selection
crate::fips
config.rs: renders /etc/fips/fips.yaml and sudo-installs key material
service.rs: systemctl status/activate/restart/mask wrappers
update.rs: GitHub API check against upstream main; apply stubbed
until per-commit .deb artefact source is decided
RPC + dashboard
fips.status / fips.check-update / fips.apply-update / fips.install /
fips.restart registered in dispatcher
HomeNetworkCard.vue shipped standalone (unmounted — place in Home.vue
when ready); shows state pill, version, FIPS npub, update button,
activate button when key is present but service is down
ISO + systemd
archipelago-fips.service: conditional on key presence, masked by
default — backend unmasks after onboarding writes the key
build-auto-installer-iso.sh: multi-stage Dockerfile builds the FIPS
.deb from jmcorgan/fips main (fail-loud), COPYs it into rootfs, apt
installs it so trixie resolves deps; unit copied + masked
Version bump: 1.3.5 → 1.4.0
Tests: 33 new/updated passing (seed, identity, transport, federation,
fips module, transport::fips).
Known gaps: fips.apply-update returns a clear stub error until
upstream publishes per-commit .deb artefacts; HomeNetworkCard is not
mounted in Home.vue by default.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 22:57:51 -04:00
RUN apt-get update && apt-get install -y --no-install-recommends \\
git ca-certificates build-essential pkg-config dpkg-dev \\
2026-04-19 08:27:22 -04:00
libdbus-1-dev libssl-dev \\
2026-04-19 09:01:59 -04:00
clang libclang-dev libnftnl-dev libmnl-dev \\
feat(fips): integrate jmcorgan/fips as preferred non-Tor transport + v1.4.0
Bakes the FIPS (Free Internetworking Peering System) mesh daemon into
the node stack, supervised by archipelago alongside Tor. Runs as a
system service, identity derives from the same BIP-39 master seed, and
user-triggered updates track upstream main.
Identity
seed.rs: new HKDF label archipelago/fips/secp256k1/v1 → dedicated
secp256k1 key, distinct from the Nostr-node key for crypto isolation
but still seed-recoverable
identity.rs: writes fips_key[.pub] to /data/identity on onboarding,
chmod 0600; fips_key_exists / load_fips_keys / fips_npub accessors
Transport
TransportKind::Fips=3 inserted between LAN and Tor (Tor bumps to 4)
→ router prefers FIPS over Tor for all peer traffic
PeerRecord gains fips_npub + last_fips fields (serde(default) for
backward-compat with older nodes)
transport/fips.rs: NodeTransport stub, reports unavailable until the
daemon is live so router falls through to Tor cleanly
Federation invites
FederatedNode and FederationInvite carry optional fips_npub
create_invite / accept_invite / peer-joined callback thread it end
to end; signature domain deliberately unchanged — FIPS Noise does
its own session auth, so the unsigned hint only affects path
selection
crate::fips
config.rs: renders /etc/fips/fips.yaml and sudo-installs key material
service.rs: systemctl status/activate/restart/mask wrappers
update.rs: GitHub API check against upstream main; apply stubbed
until per-commit .deb artefact source is decided
RPC + dashboard
fips.status / fips.check-update / fips.apply-update / fips.install /
fips.restart registered in dispatcher
HomeNetworkCard.vue shipped standalone (unmounted — place in Home.vue
when ready); shows state pill, version, FIPS npub, update button,
activate button when key is present but service is down
ISO + systemd
archipelago-fips.service: conditional on key presence, masked by
default — backend unmasks after onboarding writes the key
build-auto-installer-iso.sh: multi-stage Dockerfile builds the FIPS
.deb from jmcorgan/fips main (fail-loud), COPYs it into rootfs, apt
installs it so trixie resolves deps; unit copied + masked
Version bump: 1.3.5 → 1.4.0
Tests: 33 new/updated passing (seed, identity, transport, federation,
fips module, transport::fips).
Known gaps: fips.apply-update returns a clear stub error until
upstream publishes per-commit .deb artefacts; HomeNetworkCard is not
mounted in Home.vue by default.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 22:57:51 -04:00
&& rm -rf /var/lib/apt/lists/*
RUN cargo install --locked cargo-deb
RUN git clone --depth 1 https://github.com/jmcorgan/fips.git /src/fips
WORKDIR /src/fips
2026-04-19 08:54:53 -04:00
# fips-gateway is gated behind the `gateway` Cargo feature (depends on
# `rustables`). Without the feature, cargo doesn't build it, and
# cargo deb --no-build panics hunting for target/release/fips-gateway.
# Inspected upstream Cargo.toml 2026-04-19 — features.gateway = ["dep:rustables"].
2026-04-28 15:00:58 -04:00
RUN cargo build --release
feat(fips): integrate jmcorgan/fips as preferred non-Tor transport + v1.4.0
Bakes the FIPS (Free Internetworking Peering System) mesh daemon into
the node stack, supervised by archipelago alongside Tor. Runs as a
system service, identity derives from the same BIP-39 master seed, and
user-triggered updates track upstream main.
Identity
seed.rs: new HKDF label archipelago/fips/secp256k1/v1 → dedicated
secp256k1 key, distinct from the Nostr-node key for crypto isolation
but still seed-recoverable
identity.rs: writes fips_key[.pub] to /data/identity on onboarding,
chmod 0600; fips_key_exists / load_fips_keys / fips_npub accessors
Transport
TransportKind::Fips=3 inserted between LAN and Tor (Tor bumps to 4)
→ router prefers FIPS over Tor for all peer traffic
PeerRecord gains fips_npub + last_fips fields (serde(default) for
backward-compat with older nodes)
transport/fips.rs: NodeTransport stub, reports unavailable until the
daemon is live so router falls through to Tor cleanly
Federation invites
FederatedNode and FederationInvite carry optional fips_npub
create_invite / accept_invite / peer-joined callback thread it end
to end; signature domain deliberately unchanged — FIPS Noise does
its own session auth, so the unsigned hint only affects path
selection
crate::fips
config.rs: renders /etc/fips/fips.yaml and sudo-installs key material
service.rs: systemctl status/activate/restart/mask wrappers
update.rs: GitHub API check against upstream main; apply stubbed
until per-commit .deb artefact source is decided
RPC + dashboard
fips.status / fips.check-update / fips.apply-update / fips.install /
fips.restart registered in dispatcher
HomeNetworkCard.vue shipped standalone (unmounted — place in Home.vue
when ready); shows state pill, version, FIPS npub, update button,
activate button when key is present but service is down
ISO + systemd
archipelago-fips.service: conditional on key presence, masked by
default — backend unmasks after onboarding writes the key
build-auto-installer-iso.sh: multi-stage Dockerfile builds the FIPS
.deb from jmcorgan/fips main (fail-loud), COPYs it into rootfs, apt
installs it so trixie resolves deps; unit copied + masked
Version bump: 1.3.5 → 1.4.0
Tests: 33 new/updated passing (seed, identity, transport, federation,
fips module, transport::fips).
Known gaps: fips.apply-update returns a clear stub error until
upstream publishes per-commit .deb artefacts; HomeNetworkCard is not
mounted in Home.vue by default.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 22:57:51 -04:00
RUN cargo deb --no-build
RUN cp target/debian/fips_*_amd64.deb /tmp/fips.deb
# ─── Stage 2: The actual Archipelago rootfs ──────────────────────────────
2026-04-09 20:42:09 +02:00
FROM debian:trixie
2026-02-01 05:42:05 +00:00
ENV DEBIAN_FRONTEND = noninteractive
2026-03-26 19:50:59 +00:00
# Preseed keyboard/console config to prevent console-setup.service failure
RUN echo "keyboard-configuration keyboard-configuration/layoutcode string us" | debconf-set-selections && \
echo "keyboard-configuration keyboard-configuration/model select Generic 105-key PC" | debconf-set-selections && \
echo "console-setup console-setup/charmap47 select UTF-8" | debconf-set-selections && \
2026-03-30 23:47:15 +01:00
echo "console-setup console-setup/codeset47 select Uni2" | debconf-set-selections && \
echo "console-setup console-setup/fontface47 select Terminus" | debconf-set-selections && \
echo "console-setup console-setup/fontsize-fb47 select 16" | debconf-set-selections
2026-03-26 19:50:59 +00:00
2026-03-25 21:21:27 +00:00
# Enable non-free-firmware repo — replace DEB822 sources with traditional format
# (DEB822 sed was silently failing, so just overwrite with known-good sources.list)
2026-04-09 20:42:09 +02:00
RUN echo "deb http://deb.debian.org/debian trixie main non-free-firmware" > /etc/apt/sources.list && \
echo "deb http://deb.debian.org/debian trixie-updates main non-free-firmware" >> /etc/apt/sources.list && \
echo "deb http://deb.debian.org/debian-security trixie-security main non-free-firmware" >> /etc/apt/sources.list && \
2026-03-25 21:21:27 +00:00
rm -f /etc/apt/sources.list.d/debian.sources
2026-03-25 16:56:02 +00:00
fix: add 9 missing apps to ISO build (ISO-01)
CAPTURE_PATTERNS: added photoprism, nextcloud, nginx-proxy-manager,
immich, onlyoffice, adguard, penpot patterns.
CONTAINER_IMAGES: added jellyfin, photoprism, nextcloud,
nginx-proxy-manager, immich-server, postgres-immich, redis-immich,
onlyoffice, adguardhome with pinned versions for fallback pull.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 03:17:12 +00:00
# Install all packages we need including nginx, podman, tor, and openssl (for self-signed certs)
2026-05-05 11:29:18 -04:00
RUN apt-get update && apt-get -y full-upgrade && apt-get install -y --no-install-recommends \
2026-03-12 00:19:30 +00:00
${ LINUX_IMAGE_PKG } \
${ GRUB_EFI_PKG } \
${ GRUB_EFI_SIGNED_PKG } \
2026-03-26 09:12:16 +00:00
${ GRUB_PC_PKG } \
2026-02-01 05:42:05 +00:00
systemd \
systemd-sysv \
dbus \
sudo \
network-manager \
openssh-server \
nginx \
podman \
2026-03-28 02:40:39 +00:00
uidmap \
slirp4netns \
fix: ISO boot, container installs, VPN, nginx, companion input
- LUKS auto-unlock: initramfs hook + systemd service + nofail fstab
- Rootfs packages: add passt, aardvark-dns, netavark, nftables for Podman 5.x
- nginx: resolver + variable proxy_pass for external domains (DNS at boot)
- Boot: loglevel=0 suppresses kernel warnings, serial console for QEMU
- Container installs: write configs before chown, sudo chown for LUKS volumes
- Container installs: build UI sidecars locally (not from registry) for auth injection
- Bitcoin UI: inject RPC auth from secrets file, --no-cache rebuild
- Secrets: chown to archipelago user in first-boot (backend needs read access)
- Podman: image_copy_tmp_dir for read-only /var/tmp in user namespace
- NostrVPN: enable service in auto-install, always include public relays
- NostrVPN: read tunnel IP from nvpn status (not just config file)
- VPN invite: v2 base64 no-pad format matching phone app
- Companion input: relay always active, kiosk skips relay listener (prevents double input)
- dev-start.sh: production build includes AIUI deployment
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 03:10:49 -04:00
passt \
aardvark-dns \
netavark \
nftables \
2026-03-28 02:40:39 +00:00
fuse-overlayfs \
fix: add 9 missing apps to ISO build (ISO-01)
CAPTURE_PATTERNS: added photoprism, nextcloud, nginx-proxy-manager,
immich, onlyoffice, adguard, penpot patterns.
CONTAINER_IMAGES: added jellyfin, photoprism, nextcloud,
nginx-proxy-manager, immich-server, postgres-immich, redis-immich,
onlyoffice, adguardhome with pinned versions for fallback pull.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 03:17:12 +00:00
tor \
2026-03-31 04:52:30 +01:00
python3 \
2026-02-01 05:42:05 +00:00
curl \
2026-03-25 15:52:26 +00:00
git \
2026-02-01 05:42:05 +00:00
vim-tiny \
ca-certificates \
2026-03-05 08:34:53 +00:00
openssl \
feat: rootless podman, session hardening, boot stability, sidebar fix
Rootless podman migration (TASK-11):
- Remove sudo from all podman calls in PodmanClient + 8 backend files
- Remove sudo from all podman/docker calls in deploy script
- Restore full systemd security hardening: NoNewPrivileges,
RestrictAddressFamilies, MemoryDenyWriteExecute, RestrictRealtime,
RestrictNamespaces, RestrictSUIDSGID, SystemCallFilter, ProtectSystem=strict
- Enable loginctl linger for rootless container persistence
- Remove Ollama from auto-deploy (marketplace-only)
Session & auth hardening:
- Increase MAX_CONCURRENT_SESSIONS 20→50 (prevents eviction storms)
- Debounced 401 redirect in rpc-client.ts (prevents redirect storms)
Boot stability:
- optimize-debian.sh: adds chrony, swap, removes policy-rc.d
- deploy script: pre-restart chrony + swap setup
- ISO build: chrony package, swap file creation
- BootScreen: no longer clears localStorage (prevents splash replay)
- RootRedirect: sole owner of localStorage clearing on server ready
UI fixes:
- Sidebar opacity default changed from 0→visible (fixes missing sidebar
after page-persistence login without entrance animation)
- Console.log/error wrapped in import.meta.env.DEV guards
- Remove unused route import from RootRedirect
Beta tracking:
- CLAUDE.md: beta freeze protocol added
- MASTER_PLAN.md: TASK-11, TASK-17, phase structure
- BETA-PROGRESS.md: initial tracking doc
- Tagged v1.2.0-alpha.1 as pre-rootless baseline
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 13:53:27 +00:00
chrony \
2026-02-01 05:42:05 +00:00
locales \
console-setup \
keyboard-configuration \
2026-03-26 09:12:16 +00:00
cryptsetup \
2026-04-09 21:07:34 +01:00
cryptsetup-initramfs \
e2fsprogs \
2026-03-25 16:56:02 +00:00
firmware-realtek \
firmware-iwlwifi \
2026-03-28 13:06:34 +00:00
firmware-misc-nonfree \
2026-04-02 20:28:53 +01:00
firmware-linux-nonfree \
2026-03-25 18:25:01 +00:00
intel-microcode \
amd64-microcode \
2026-03-26 09:12:16 +00:00
xorg \
2026-04-01 22:31:45 +01:00
xdotool \
2026-03-26 09:12:16 +00:00
chromium \
unclutter \
2026-03-27 18:19:49 +00:00
fonts-liberation \
xfonts-base \
feat: custom boot branding, MBR fix, Plymouth theme, CI smoke tests
Boot fix:
- Ship proven Debian Live MBR (4552) as branding/isohdpfx.bin — the
ISOLINUX package MBR (33ed) doesn't boot on all hardware. This was
the root cause of "machine doesn't pick up the USB".
Branding:
- Custom GRUB background: pixel-art floating island (1024x574)
- Archipelago pixel-art logo for Plymouth boot splash
- GRUB theme: dark background, orange selected item, no broken font refs
- Plymouth theme: script-based with progress bar, LUKS prompt support
- Plymouth + splash added to target rootfs packages
- GRUB theme installed on both installer ISO and target system
- Serial console (ttyS0) added to kernel params for QEMU debugging
CI improvements:
- Smoke test step: mounts ISO, verifies all critical files, checks
initrd has live-boot, confirms boot=live in grub.cfg. Fails build
before copying to Builds if any check fails.
Dev workflow:
- dev-branding.sh: extract ISO, swap branding, repackage, boot in QEMU
(~10 seconds vs 20 min full rebuild)
- generate-grub-background.py: procedural cyberpunk background generator
- generate-plymouth-logo.py: procedural logo generator
- Improved test-iso-qemu.sh: --bios/--nographic flags, serial logging
Build:
- Simplified live-boot install (clean chroot, no complex fallbacks)
- Static branding images preferred, generators as fallback
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 22:26:56 +00:00
plymouth \
plymouth-themes \
2026-03-28 23:41:40 +00:00
zstd \
2026-04-02 16:15:04 +01:00
socat \
2026-03-31 04:52:30 +01:00
python3 \
2026-04-02 10:34:58 +01:00
apache2-utils \
2026-04-08 13:15:09 +02:00
wireguard-tools \
acpid \
acpi-support-base \
2026-02-01 05:42:05 +00:00
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
2026-03-27 18:19:49 +00:00
# Strip docs, man pages, and unused locales
RUN find /usr/share/doc -depth -type f ! -name copyright -delete 2>/dev/null || true && \
find /usr/share/doc -empty -delete 2>/dev/null || true && \
rm -rf /usr/share/man /usr/share/info /usr/share/lintian /usr/share/linda && \
find /usr/share/locale -maxdepth 1 -mindepth 1 ! -name 'en_US' ! -name 'locale.alias' -exec rm -rf { } + 2>/dev/null || true
fix: add 9 missing apps to ISO build (ISO-01)
CAPTURE_PATTERNS: added photoprism, nextcloud, nginx-proxy-manager,
immich, onlyoffice, adguard, penpot patterns.
CONTAINER_IMAGES: added jellyfin, photoprism, nextcloud,
nginx-proxy-manager, immich-server, postgres-immich, redis-immich,
onlyoffice, adguardhome with pinned versions for fallback pull.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 03:17:12 +00:00
# Install Tailscale from official repo
2026-04-09 20:42:09 +02:00
RUN curl -fsSL https://pkgs.tailscale.com/stable/debian/trixie.noarmor.gpg | tee /usr/share/keyrings/tailscale-archive-keyring.gpg >/dev/null && \
curl -fsSL https://pkgs.tailscale.com/stable/debian/trixie.tailscale-keyring.list | tee /etc/apt/sources.list.d/tailscale.list && \
2026-05-05 11:29:18 -04:00
apt-get update && apt-get -y full-upgrade && apt-get install -y --no-install-recommends tailscale && \
fix: add 9 missing apps to ISO build (ISO-01)
CAPTURE_PATTERNS: added photoprism, nextcloud, nginx-proxy-manager,
immich, onlyoffice, adguard, penpot patterns.
CONTAINER_IMAGES: added jellyfin, photoprism, nextcloud,
nginx-proxy-manager, immich-server, postgres-immich, redis-immich,
onlyoffice, adguardhome with pinned versions for fallback pull.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 03:17:12 +00:00
apt-get clean && rm -rf /var/lib/apt/lists/*
feat(fips): integrate jmcorgan/fips as preferred non-Tor transport + v1.4.0
Bakes the FIPS (Free Internetworking Peering System) mesh daemon into
the node stack, supervised by archipelago alongside Tor. Runs as a
system service, identity derives from the same BIP-39 master seed, and
user-triggered updates track upstream main.
Identity
seed.rs: new HKDF label archipelago/fips/secp256k1/v1 → dedicated
secp256k1 key, distinct from the Nostr-node key for crypto isolation
but still seed-recoverable
identity.rs: writes fips_key[.pub] to /data/identity on onboarding,
chmod 0600; fips_key_exists / load_fips_keys / fips_npub accessors
Transport
TransportKind::Fips=3 inserted between LAN and Tor (Tor bumps to 4)
→ router prefers FIPS over Tor for all peer traffic
PeerRecord gains fips_npub + last_fips fields (serde(default) for
backward-compat with older nodes)
transport/fips.rs: NodeTransport stub, reports unavailable until the
daemon is live so router falls through to Tor cleanly
Federation invites
FederatedNode and FederationInvite carry optional fips_npub
create_invite / accept_invite / peer-joined callback thread it end
to end; signature domain deliberately unchanged — FIPS Noise does
its own session auth, so the unsigned hint only affects path
selection
crate::fips
config.rs: renders /etc/fips/fips.yaml and sudo-installs key material
service.rs: systemctl status/activate/restart/mask wrappers
update.rs: GitHub API check against upstream main; apply stubbed
until per-commit .deb artefact source is decided
RPC + dashboard
fips.status / fips.check-update / fips.apply-update / fips.install /
fips.restart registered in dispatcher
HomeNetworkCard.vue shipped standalone (unmounted — place in Home.vue
when ready); shows state pill, version, FIPS npub, update button,
activate button when key is present but service is down
ISO + systemd
archipelago-fips.service: conditional on key presence, masked by
default — backend unmasks after onboarding writes the key
build-auto-installer-iso.sh: multi-stage Dockerfile builds the FIPS
.deb from jmcorgan/fips main (fail-loud), COPYs it into rootfs, apt
installs it so trixie resolves deps; unit copied + masked
Version bump: 1.3.5 → 1.4.0
Tests: 33 new/updated passing (seed, identity, transport, federation,
fips module, transport::fips).
Known gaps: fips.apply-update returns a clear stub error until
upstream publishes per-commit .deb artefacts; HomeNetworkCard is not
mounted in Home.vue by default.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 22:57:51 -04:00
# Install FIPS mesh daemon from the .deb built in stage 1. apt-get install
# resolves dependencies from trixie so a cross-dist build still lands cleanly.
COPY --from= fips-builder /tmp/fips.deb /tmp/fips.deb
2026-05-05 11:29:18 -04:00
RUN apt-get update && apt-get -y full-upgrade && apt-get install -y --no-install-recommends /tmp/fips.deb && \
feat(fips): integrate jmcorgan/fips as preferred non-Tor transport + v1.4.0
Bakes the FIPS (Free Internetworking Peering System) mesh daemon into
the node stack, supervised by archipelago alongside Tor. Runs as a
system service, identity derives from the same BIP-39 master seed, and
user-triggered updates track upstream main.
Identity
seed.rs: new HKDF label archipelago/fips/secp256k1/v1 → dedicated
secp256k1 key, distinct from the Nostr-node key for crypto isolation
but still seed-recoverable
identity.rs: writes fips_key[.pub] to /data/identity on onboarding,
chmod 0600; fips_key_exists / load_fips_keys / fips_npub accessors
Transport
TransportKind::Fips=3 inserted between LAN and Tor (Tor bumps to 4)
→ router prefers FIPS over Tor for all peer traffic
PeerRecord gains fips_npub + last_fips fields (serde(default) for
backward-compat with older nodes)
transport/fips.rs: NodeTransport stub, reports unavailable until the
daemon is live so router falls through to Tor cleanly
Federation invites
FederatedNode and FederationInvite carry optional fips_npub
create_invite / accept_invite / peer-joined callback thread it end
to end; signature domain deliberately unchanged — FIPS Noise does
its own session auth, so the unsigned hint only affects path
selection
crate::fips
config.rs: renders /etc/fips/fips.yaml and sudo-installs key material
service.rs: systemctl status/activate/restart/mask wrappers
update.rs: GitHub API check against upstream main; apply stubbed
until per-commit .deb artefact source is decided
RPC + dashboard
fips.status / fips.check-update / fips.apply-update / fips.install /
fips.restart registered in dispatcher
HomeNetworkCard.vue shipped standalone (unmounted — place in Home.vue
when ready); shows state pill, version, FIPS npub, update button,
activate button when key is present but service is down
ISO + systemd
archipelago-fips.service: conditional on key presence, masked by
default — backend unmasks after onboarding writes the key
build-auto-installer-iso.sh: multi-stage Dockerfile builds the FIPS
.deb from jmcorgan/fips main (fail-loud), COPYs it into rootfs, apt
installs it so trixie resolves deps; unit copied + masked
Version bump: 1.3.5 → 1.4.0
Tests: 33 new/updated passing (seed, identity, transport, federation,
fips module, transport::fips).
Known gaps: fips.apply-update returns a clear stub error until
upstream publishes per-commit .deb artefacts; HomeNetworkCard is not
mounted in Home.vue by default.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 22:57:51 -04:00
apt-get clean && rm -rf /var/lib/apt/lists/* && rm /tmp/fips.deb
2026-02-01 05:42:05 +00:00
# Configure locale
RUN echo "en_US.UTF-8 UTF-8" > /etc/locale.gen && locale-gen
2026-03-10 23:29:05 +00:00
# Create archipelago user with password "archipelago"
2026-02-01 05:42:05 +00:00
RUN useradd -m -s /bin/bash -G sudo archipelago && \
echo "archipelago:archipelago" | chpasswd && \
2026-03-10 23:29:05 +00:00
echo "root:archipelago" | chpasswd && \
2026-02-01 05:42:05 +00:00
echo "archipelago ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/archipelago
2026-03-10 23:29:05 +00:00
# Verify password hash was set (not locked)
RUN grep -q "^archipelago:\$" /etc/shadow && echo "Password set OK" || echo "WARNING: password may not be set"
2026-02-01 05:42:05 +00:00
# Set hostname
RUN echo "archipelago" > /etc/hostname
# Configure SSH
RUN mkdir -p /etc/ssh && \
sed -i 's/#PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config || true && \
sed -i 's/#PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_config || true
# Configure nginx for Archipelago
RUN rm -f /etc/nginx/sites-enabled/default
COPY nginx-archipelago.conf /etc/nginx/sites-available/archipelago
RUN ln -sf /etc/nginx/sites-available/archipelago /etc/nginx/sites-enabled/archipelago
2026-03-05 08:34:53 +00:00
# Install nginx snippets (PWA config, HTTPS app proxies)
COPY snippets/ /etc/nginx/snippets/
# Generate self-signed SSL certificate for HTTPS (PWA install requires secure context)
RUN mkdir -p /etc/archipelago/ssl && \
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
-keyout /etc/archipelago/ssl/archipelago.key \
-out /etc/archipelago/ssl/archipelago.crt \
-subj "/C=XX/ST=Bitcoin/L=Node/O=Archipelago/CN=archipelago" && \
chmod 600 /etc/archipelago/ssl/archipelago.key
2026-02-01 05:42:05 +00:00
# Create archipelago systemd service
COPY archipelago.service /etc/systemd/system/archipelago.service
2026-03-25 15:52:26 +00:00
COPY archipelago-update.service /etc/systemd/system/archipelago-update.service
COPY archipelago-update.timer /etc/systemd/system/archipelago-update.timer
2026-03-28 11:31:48 +00:00
COPY archipelago-doctor.service /etc/systemd/system/archipelago-doctor.service
COPY archipelago-doctor.timer /etc/systemd/system/archipelago-doctor.timer
2026-03-29 19:26:21 +01:00
COPY archipelago-tor-helper.service /etc/systemd/system/archipelago-tor-helper.service
COPY archipelago-tor-helper.path /etc/systemd/system/archipelago-tor-helper.path
2026-04-07 14:40:33 +01:00
COPY nostr-vpn.service /etc/systemd/system/nostr-vpn.service
2026-04-08 20:27:38 +02:00
COPY archipelago-wg.service /etc/systemd/system/archipelago-wg.service
2026-04-08 13:15:09 +02:00
COPY archipelago-wg-address.service /etc/systemd/system/archipelago-wg-address.service
feat(fips): integrate jmcorgan/fips as preferred non-Tor transport + v1.4.0
Bakes the FIPS (Free Internetworking Peering System) mesh daemon into
the node stack, supervised by archipelago alongside Tor. Runs as a
system service, identity derives from the same BIP-39 master seed, and
user-triggered updates track upstream main.
Identity
seed.rs: new HKDF label archipelago/fips/secp256k1/v1 → dedicated
secp256k1 key, distinct from the Nostr-node key for crypto isolation
but still seed-recoverable
identity.rs: writes fips_key[.pub] to /data/identity on onboarding,
chmod 0600; fips_key_exists / load_fips_keys / fips_npub accessors
Transport
TransportKind::Fips=3 inserted between LAN and Tor (Tor bumps to 4)
→ router prefers FIPS over Tor for all peer traffic
PeerRecord gains fips_npub + last_fips fields (serde(default) for
backward-compat with older nodes)
transport/fips.rs: NodeTransport stub, reports unavailable until the
daemon is live so router falls through to Tor cleanly
Federation invites
FederatedNode and FederationInvite carry optional fips_npub
create_invite / accept_invite / peer-joined callback thread it end
to end; signature domain deliberately unchanged — FIPS Noise does
its own session auth, so the unsigned hint only affects path
selection
crate::fips
config.rs: renders /etc/fips/fips.yaml and sudo-installs key material
service.rs: systemctl status/activate/restart/mask wrappers
update.rs: GitHub API check against upstream main; apply stubbed
until per-commit .deb artefact source is decided
RPC + dashboard
fips.status / fips.check-update / fips.apply-update / fips.install /
fips.restart registered in dispatcher
HomeNetworkCard.vue shipped standalone (unmounted — place in Home.vue
when ready); shows state pill, version, FIPS npub, update button,
activate button when key is present but service is down
ISO + systemd
archipelago-fips.service: conditional on key presence, masked by
default — backend unmasks after onboarding writes the key
build-auto-installer-iso.sh: multi-stage Dockerfile builds the FIPS
.deb from jmcorgan/fips main (fail-loud), COPYs it into rootfs, apt
installs it so trixie resolves deps; unit copied + masked
Version bump: 1.3.5 → 1.4.0
Tests: 33 new/updated passing (seed, identity, transport, federation,
fips module, transport::fips).
Known gaps: fips.apply-update returns a clear stub error until
upstream publishes per-commit .deb artefacts; HomeNetworkCard is not
mounted in Home.vue by default.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 22:57:51 -04:00
COPY archipelago-fips.service /etc/systemd/system/archipelago-fips.service
2026-04-08 15:06:27 +02:00
COPY nostr-relay.service /etc/systemd/system/nostr-relay.service
COPY nostr-relay-config.toml /etc/archipelago/nostr-relay-config.toml
2026-04-08 13:15:09 +02:00
# WireGuard kernel module auto-load on boot
RUN echo "wireguard" >> /etc/modules-load.d/wireguard.conf
2026-02-01 05:42:05 +00:00
2026-04-23 03:04:58 -04:00
# Copy container doctor + reconcile scripts (referenced by services and the
# OTA update RPC; the reconcile systemd timer is gone as of Step 8a, but the
# script stays until Step 8b/c ports all manifests — update.rs still shells
# out to it during package updates).
2026-03-30 13:35:02 +01:00
RUN mkdir -p /home/archipelago/archy/scripts/lib
2026-03-28 23:41:40 +00:00
COPY container-doctor.sh /home/archipelago/archy/scripts/container-doctor.sh
COPY reconcile-containers.sh /home/archipelago/archy/scripts/reconcile-containers.sh
2026-03-30 13:35:02 +01:00
COPY container-specs.sh /home/archipelago/archy/scripts/container-specs.sh
2026-03-29 19:26:21 +01:00
COPY tor-helper.sh /opt/archipelago/scripts/tor-helper.sh
2026-03-30 13:35:02 +01:00
COPY lib/ /home/archipelago/archy/scripts/lib/
RUN chmod +x /home/archipelago/archy/scripts/*.sh /home/archipelago/archy/scripts/lib/*.sh /opt/archipelago/scripts/*.sh && \
2026-03-28 23:41:40 +00:00
chown -R archipelago:archipelago /home/archipelago/archy
2026-04-18 11:07:08 -04:00
# Enable cgroup delegation for rootless podman (CPU/memory limits require this)
RUN mkdir -p /etc/systemd/system/user@.service.d && \
printf '[Service]\nDelegate=cpu cpuset io memory pids\n' > /etc/systemd/system/user@.service.d/delegate.conf
# Allow unprivileged ping inside rootless containers
RUN printf 'net.ipv4.ping_group_range=0 2147483647\n' > /etc/sysctl.d/90-podman-ping.conf
2026-02-01 05:42:05 +00:00
# Enable services
RUN systemctl enable NetworkManager || true && \
systemctl enable ssh || true && \
systemctl enable nginx || true && \
fix: add 9 missing apps to ISO build (ISO-01)
CAPTURE_PATTERNS: added photoprism, nextcloud, nginx-proxy-manager,
immich, onlyoffice, adguard, penpot patterns.
CONTAINER_IMAGES: added jellyfin, photoprism, nextcloud,
nginx-proxy-manager, immich-server, postgres-immich, redis-immich,
onlyoffice, adguardhome with pinned versions for fallback pull.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 03:17:12 +00:00
systemctl enable archipelago || true && \
systemctl enable tor || true && \
feat: rootless podman, session hardening, boot stability, sidebar fix
Rootless podman migration (TASK-11):
- Remove sudo from all podman calls in PodmanClient + 8 backend files
- Remove sudo from all podman/docker calls in deploy script
- Restore full systemd security hardening: NoNewPrivileges,
RestrictAddressFamilies, MemoryDenyWriteExecute, RestrictRealtime,
RestrictNamespaces, RestrictSUIDSGID, SystemCallFilter, ProtectSystem=strict
- Enable loginctl linger for rootless container persistence
- Remove Ollama from auto-deploy (marketplace-only)
Session & auth hardening:
- Increase MAX_CONCURRENT_SESSIONS 20→50 (prevents eviction storms)
- Debounced 401 redirect in rpc-client.ts (prevents redirect storms)
Boot stability:
- optimize-debian.sh: adds chrony, swap, removes policy-rc.d
- deploy script: pre-restart chrony + swap setup
- ISO build: chrony package, swap file creation
- BootScreen: no longer clears localStorage (prevents splash replay)
- RootRedirect: sole owner of localStorage clearing on server ready
UI fixes:
- Sidebar opacity default changed from 0→visible (fixes missing sidebar
after page-persistence login without entrance animation)
- Console.log/error wrapped in import.meta.env.DEV guards
- Remove unused route import from RootRedirect
Beta tracking:
- CLAUDE.md: beta freeze protocol added
- MASTER_PLAN.md: TASK-11, TASK-17, phase structure
- BETA-PROGRESS.md: initial tracking doc
- Tagged v1.2.0-alpha.1 as pre-rootless baseline
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 13:53:27 +00:00
systemctl enable tailscaled || true && \
2026-03-25 15:52:26 +00:00
systemctl enable chrony || true && \
2026-03-28 11:31:48 +00:00
systemctl enable archipelago-update.timer || true && \
systemctl enable archipelago-doctor.timer || true && \
2026-04-08 13:15:09 +02:00
systemctl enable archipelago-tor-helper.path || true && \
2026-04-08 17:48:38 +02:00
systemctl enable nostr-relay || true
fix(fips,iso): bulletproof FIPS from install — no Activate button needed
Problems addressed (all observed on .198):
* fips_key was written as raw 32 bytes; upstream fips daemon reads it
with read_to_string() and bailed with "stream did not contain valid
UTF-8", crashlooping indefinitely.
* Activate button racy: user had to hit it, and it would keep failing
silently because the daemon couldn't parse its own config.
* FIPS schema drift (already fixed in 7d8a5864) put the config write
path behind the same broken "Activate" flow, so the fix alone
didn't help existing nodes.
* Journal was on tmpfs — every reboot wiped install/onboarding history,
making post-hoc debugging impossible.
Changes:
* identity.rs: write fips_key as bech32 nsec + newline. load_fips_keys
now auto-migrates legacy 32-byte files to bech32 the first time it
reads them, so OTA updates from v1.5.0-alpha self-heal without user
action.
* server.rs: post-onboarding auto-activate task runs on every
archipelago startup. If fips_key exists it ensures /etc/fips/fips.yaml
is schema-current and starts archipelago-fips.service. Pre-onboarding
nodes stay quiet (guarded on fips_key_exists).
* ISO build: un-mask archipelago-fips + archipelago-wg + wg-address —
all use ConditionPathExists on their key files, so systemd silently
skips them pre-onboarding (no MOTD [FAILED]). Only nostr-vpn stays
masked (legacy service, superseded by upstream fips).
* Journald made persistent via /var/log/journal + 500M cap, so
install and first-boot logs survive reboots for diagnosis.
After this, a fresh install + onboarding should bring FIPS up automatically
with no user interaction. The UI "Activate" button can stay as an escape
hatch (the RPC is still there) but is no longer on the critical path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 16:33:21 -04:00
# archipelago-fips.service + archipelago-wg.service + archipelago-wg-address.service
# stay installed and enabled. They all use `ConditionPathExists=` on their
# respective seed-derived key files, so on a fresh pre-onboarding boot
# systemd quietly skips them with no [FAILED] in the MOTD. Once the user
# completes the seed onboarding flow, archipelago writes the key files,
# the archipelago backend calls `systemctl start archipelago-fips.service`
# (see server.rs post-onboarding auto-activate block) and the WG setup
# path runs `archipelago-wg setup` directly. No masking, no user-facing
# "Activate" button — install → onboard → FIPS + WG are just running.
RUN systemctl enable archipelago-fips.service || true
# nostr-vpn is the legacy nostr-tunnel service — deprecated in favour of
# the upstream FIPS daemon. It still crash-loops on boot if left enabled
# (env file doesn't exist until onboarding) so we mask it outright.
# `systemctl mask` alone doesn't stick because the real .service file is
# already in place — explicit rm + /dev/null symlink is what sticks.
RUN rm -f /etc/systemd/system/nostr-vpn.service && \\
ln -sf /dev/null /etc/systemd/system/nostr-vpn.service
2026-04-19 08:27:22 -04:00
feat: rootless podman, session hardening, boot stability, sidebar fix
Rootless podman migration (TASK-11):
- Remove sudo from all podman calls in PodmanClient + 8 backend files
- Remove sudo from all podman/docker calls in deploy script
- Restore full systemd security hardening: NoNewPrivileges,
RestrictAddressFamilies, MemoryDenyWriteExecute, RestrictRealtime,
RestrictNamespaces, RestrictSUIDSGID, SystemCallFilter, ProtectSystem=strict
- Enable loginctl linger for rootless container persistence
- Remove Ollama from auto-deploy (marketplace-only)
Session & auth hardening:
- Increase MAX_CONCURRENT_SESSIONS 20→50 (prevents eviction storms)
- Debounced 401 redirect in rpc-client.ts (prevents redirect storms)
Boot stability:
- optimize-debian.sh: adds chrony, swap, removes policy-rc.d
- deploy script: pre-restart chrony + swap setup
- ISO build: chrony package, swap file creation
- BootScreen: no longer clears localStorage (prevents splash replay)
- RootRedirect: sole owner of localStorage clearing on server ready
UI fixes:
- Sidebar opacity default changed from 0→visible (fixes missing sidebar
after page-persistence login without entrance animation)
- Console.log/error wrapped in import.meta.env.DEV guards
- Remove unused route import from RootRedirect
Beta tracking:
- CLAUDE.md: beta freeze protocol added
- MASTER_PLAN.md: TASK-11, TASK-17, phase structure
- BETA-PROGRESS.md: initial tracking doc
- Tagged v1.2.0-alpha.1 as pre-rootless baseline
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 13:53:27 +00:00
# Remove policy-rc.d so services can start on first boot
RUN rm -f /usr/sbin/policy-rc.d
2026-02-01 05:42:05 +00:00
2026-03-05 08:34:53 +00:00
# Create directories (including Cloud storage for FileBrowser)
2026-04-08 15:43:22 +02:00
RUN mkdir -p /var/lib/archipelago/data /var/lib/archipelago/config /var/lib/archipelago/containers /var/lib/archipelago/nostr-relay /var/lib/archipelago/nostr-vpn && \
2026-02-01 05:42:05 +00:00
mkdir -p /etc/archipelago && \
2026-04-08 15:43:22 +02:00
mkdir -p /opt/archipelago/bin /opt/archipelago/scripts /opt/archipelago/web-ui && \
mkdir -p /var/lib/archipelago/data/cloud/Documents /var/lib/archipelago/data/cloud/Photos /var/lib/archipelago/data/cloud/Music /var/lib/archipelago/data/cloud/Videos /var/lib/archipelago/data/cloud/Downloads && \
2026-04-08 15:06:27 +02:00
cp /etc/archipelago/nostr-relay-config.toml /var/lib/archipelago/nostr-relay/config.toml && \
2026-02-01 05:42:05 +00:00
chown -R archipelago:archipelago /var/lib/archipelago /opt/archipelago
fix(fips,iso): bulletproof FIPS from install — no Activate button needed
Problems addressed (all observed on .198):
* fips_key was written as raw 32 bytes; upstream fips daemon reads it
with read_to_string() and bailed with "stream did not contain valid
UTF-8", crashlooping indefinitely.
* Activate button racy: user had to hit it, and it would keep failing
silently because the daemon couldn't parse its own config.
* FIPS schema drift (already fixed in 7d8a5864) put the config write
path behind the same broken "Activate" flow, so the fix alone
didn't help existing nodes.
* Journal was on tmpfs — every reboot wiped install/onboarding history,
making post-hoc debugging impossible.
Changes:
* identity.rs: write fips_key as bech32 nsec + newline. load_fips_keys
now auto-migrates legacy 32-byte files to bech32 the first time it
reads them, so OTA updates from v1.5.0-alpha self-heal without user
action.
* server.rs: post-onboarding auto-activate task runs on every
archipelago startup. If fips_key exists it ensures /etc/fips/fips.yaml
is schema-current and starts archipelago-fips.service. Pre-onboarding
nodes stay quiet (guarded on fips_key_exists).
* ISO build: un-mask archipelago-fips + archipelago-wg + wg-address —
all use ConditionPathExists on their key files, so systemd silently
skips them pre-onboarding (no MOTD [FAILED]). Only nostr-vpn stays
masked (legacy service, superseded by upstream fips).
* Journald made persistent via /var/log/journal + 500M cap, so
install and first-boot logs survive reboots for diagnosis.
After this, a fresh install + onboarding should bring FIPS up automatically
with no user interaction. The UI "Activate" button can stay as an escape
hatch (the RPC is still there) but is no longer on the critical path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 16:33:21 -04:00
# Persist journalctl across reboots — without /var/log/journal systemd
# journal uses tmpfs and everything before the last boot is lost. We
# need the full history to diagnose first-boot / install / onboarding
# issues after the fact. Size cap keeps it from eating the disk.
RUN mkdir -p /var/log/journal && \
systemd-tmpfiles --create --prefix /var/log/journal 2>/dev/null || true && \
install -d -m 0755 /etc/systemd/journald.conf.d && \
printf '[Journal]\nStorage=persistent\nSystemMaxUse=500M\nRuntimeMaxUse=100M\nForwardToSyslog=no\n' > /etc/systemd/journald.conf.d/10-archipelago-persistent.conf
2026-02-01 05:42:05 +00:00
# Clean up
RUN apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
DOCKERFILE
2026-03-05 08:34:53 +00:00
# Copy nginx snippets for HTTPS (PWA, app proxies)
2026-04-28 15:00:58 -04:00
if [ -d " $SCRIPT_DIR /../configs/snippets " ] ; then
2026-03-05 08:34:53 +00:00
mkdir -p " $WORK_DIR /snippets "
2026-04-28 15:00:58 -04:00
cp " $SCRIPT_DIR /../configs/snippets/ " *.conf " $WORK_DIR /snippets/ " 2>/dev/null || true
2026-03-05 08:34:53 +00:00
echo " Using nginx snippets from configs/snippets/"
else
mkdir -p " $WORK_DIR /snippets "
echo " ⚠ No nginx snippets found, HTTPS features may not work"
fi
2026-03-22 03:30:21 +00:00
# Use nginx config from configs/ (includes app proxies for Nextcloud, Vaultwarden, etc.)
2026-04-28 15:00:58 -04:00
if [ -f " $SCRIPT_DIR /../configs/nginx-archipelago.conf " ] ; then
cp " $SCRIPT_DIR /../configs/nginx-archipelago.conf " " $WORK_DIR /nginx-archipelago.conf "
2026-02-25 18:20:50 +00:00
echo " Using nginx config from configs/nginx-archipelago.conf"
else
echo " ⚠ configs/nginx-archipelago.conf not found, using minimal config"
cat > " $WORK_DIR /nginx-archipelago.conf " <<'NGIN XCONF'
2026-02-01 05:42:05 +00:00
server {
listen 80;
server_name _;
root /opt/archipelago/web-ui;
index index.html;
2026-02-25 18:20:50 +00:00
location / { try_files $uri $uri / /index.html; }
location /archipelago/ { proxy_pass http://127.0.0.1:5678; proxy_http_version 1.1; proxy_set_header Host $host ; proxy_set_header X-Real-IP $remote_addr ; }
location /rpc/ { proxy_pass http://127.0.0.1:5678; proxy_http_version 1.1; proxy_set_header Host $host ; proxy_set_header X-Real-IP $remote_addr ; proxy_connect_timeout 300s; proxy_send_timeout 300s; proxy_read_timeout 300s; }
location /ws { proxy_pass http://127.0.0.1:5678; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade ; proxy_set_header Connection "upgrade" ; proxy_set_header Host $host ; proxy_read_timeout 86400s; }
2026-02-01 05:42:05 +00:00
}
NGINXCONF
2026-02-25 18:20:50 +00:00
fi
2026-02-01 05:42:05 +00:00
2026-03-18 10:50:13 +00:00
# Copy udev rule for mesh radio stable naming
2026-04-28 15:00:58 -04:00
if [ -f " $SCRIPT_DIR /../configs/99-mesh-radio.rules " ] ; then
cp " $SCRIPT_DIR /../configs/99-mesh-radio.rules " " $WORK_DIR /99-mesh-radio.rules "
2026-03-18 10:50:13 +00:00
echo " Using 99-mesh-radio.rules from configs/"
fi
2026-03-25 15:52:26 +00:00
# Copy update service and timer
2026-04-28 15:00:58 -04:00
if [ -f " $SCRIPT_DIR /../configs/archipelago-update.service " ] ; then
cp " $SCRIPT_DIR /../configs/archipelago-update.service " " $WORK_DIR /archipelago-update.service "
cp " $SCRIPT_DIR /../configs/archipelago-update.timer " " $WORK_DIR /archipelago-update.timer "
2026-03-25 15:52:26 +00:00
echo " Using archipelago-update.service + timer from configs/"
fi
2026-04-23 03:04:58 -04:00
# Copy container doctor timer + reconcile script (the reconcile systemd
# timer is gone as of Step 8a — BootReconciler replaces it — but the
# reconcile-containers.sh script stays, invoked by the OTA update RPC
# until Step 8b/c ports all manifests to the Rust orchestrator).
2026-04-28 15:00:58 -04:00
if [ -f " $SCRIPT_DIR /../configs/archipelago-doctor.service " ] ; then
cp " $SCRIPT_DIR /../configs/archipelago-doctor.service " " $WORK_DIR /archipelago-doctor.service "
cp " $SCRIPT_DIR /../configs/archipelago-doctor.timer " " $WORK_DIR /archipelago-doctor.timer "
2026-04-23 03:04:58 -04:00
# Copy the actual scripts the services / update RPC reference
2026-03-30 13:35:02 +01:00
for s in container-doctor.sh reconcile-containers.sh container-specs.sh tor-helper.sh; do
2026-04-28 15:00:58 -04:00
if [ -f " $SCRIPT_DIR /../../scripts/ $s " ] ; then
cp " $SCRIPT_DIR /../../scripts/ $s " " $WORK_DIR / $s "
2026-03-28 23:41:40 +00:00
fi
done
2026-03-30 13:35:02 +01:00
# Copy shared script library (mem_limit etc.)
2026-04-28 15:00:58 -04:00
if [ -d " $SCRIPT_DIR /../../scripts/lib " ] ; then
2026-03-30 13:35:02 +01:00
mkdir -p " $WORK_DIR /lib "
2026-04-28 15:00:58 -04:00
cp " $SCRIPT_DIR /../../scripts/lib/ " *.sh " $WORK_DIR /lib/ " 2>/dev/null || true
2026-03-30 13:35:02 +01:00
fi
2026-04-23 03:04:58 -04:00
echo " Using container doctor timer from configs/"
2026-03-28 11:31:48 +00:00
fi
2026-03-29 19:26:21 +01:00
# Copy Tor helper path-activated service (allows backend to manage Tor as non-root)
2026-04-28 15:00:58 -04:00
if [ -f " $SCRIPT_DIR /../configs/archipelago-tor-helper.service " ] ; then
cp " $SCRIPT_DIR /../configs/archipelago-tor-helper.service " " $WORK_DIR /archipelago-tor-helper.service "
cp " $SCRIPT_DIR /../configs/archipelago-tor-helper.path " " $WORK_DIR /archipelago-tor-helper.path "
2026-03-29 19:26:21 +01:00
echo " Using tor-helper path unit from configs/"
fi
2026-04-07 14:40:33 +01:00
# Copy NostrVPN system service (native mesh VPN, not a container)
2026-04-28 15:00:58 -04:00
if [ -f " $SCRIPT_DIR /../configs/nostr-vpn.service " ] ; then
cp " $SCRIPT_DIR /../configs/nostr-vpn.service " " $WORK_DIR /nostr-vpn.service "
2026-04-07 14:40:33 +01:00
echo " Using nostr-vpn.service from configs/"
fi
2026-04-28 15:00:58 -04:00
if [ -f " $SCRIPT_DIR /../configs/archipelago-wg.service " ] ; then
cp " $SCRIPT_DIR /../configs/archipelago-wg.service " " $WORK_DIR /archipelago-wg.service "
2026-04-08 20:27:38 +02:00
echo " Using archipelago-wg.service from configs/"
fi
2026-04-28 15:00:58 -04:00
if [ -f " $SCRIPT_DIR /../configs/archipelago-wg-address.service " ] ; then
cp " $SCRIPT_DIR /../configs/archipelago-wg-address.service " " $WORK_DIR /archipelago-wg-address.service "
2026-04-08 13:15:09 +02:00
echo " Using archipelago-wg-address.service from configs/"
fi
2026-04-28 15:00:58 -04:00
if [ -f " $SCRIPT_DIR /../configs/archipelago-fips.service " ] ; then
cp " $SCRIPT_DIR /../configs/archipelago-fips.service " " $WORK_DIR /archipelago-fips.service "
feat(fips): integrate jmcorgan/fips as preferred non-Tor transport + v1.4.0
Bakes the FIPS (Free Internetworking Peering System) mesh daemon into
the node stack, supervised by archipelago alongside Tor. Runs as a
system service, identity derives from the same BIP-39 master seed, and
user-triggered updates track upstream main.
Identity
seed.rs: new HKDF label archipelago/fips/secp256k1/v1 → dedicated
secp256k1 key, distinct from the Nostr-node key for crypto isolation
but still seed-recoverable
identity.rs: writes fips_key[.pub] to /data/identity on onboarding,
chmod 0600; fips_key_exists / load_fips_keys / fips_npub accessors
Transport
TransportKind::Fips=3 inserted between LAN and Tor (Tor bumps to 4)
→ router prefers FIPS over Tor for all peer traffic
PeerRecord gains fips_npub + last_fips fields (serde(default) for
backward-compat with older nodes)
transport/fips.rs: NodeTransport stub, reports unavailable until the
daemon is live so router falls through to Tor cleanly
Federation invites
FederatedNode and FederationInvite carry optional fips_npub
create_invite / accept_invite / peer-joined callback thread it end
to end; signature domain deliberately unchanged — FIPS Noise does
its own session auth, so the unsigned hint only affects path
selection
crate::fips
config.rs: renders /etc/fips/fips.yaml and sudo-installs key material
service.rs: systemctl status/activate/restart/mask wrappers
update.rs: GitHub API check against upstream main; apply stubbed
until per-commit .deb artefact source is decided
RPC + dashboard
fips.status / fips.check-update / fips.apply-update / fips.install /
fips.restart registered in dispatcher
HomeNetworkCard.vue shipped standalone (unmounted — place in Home.vue
when ready); shows state pill, version, FIPS npub, update button,
activate button when key is present but service is down
ISO + systemd
archipelago-fips.service: conditional on key presence, masked by
default — backend unmasks after onboarding writes the key
build-auto-installer-iso.sh: multi-stage Dockerfile builds the FIPS
.deb from jmcorgan/fips main (fail-loud), COPYs it into rootfs, apt
installs it so trixie resolves deps; unit copied + masked
Version bump: 1.3.5 → 1.4.0
Tests: 33 new/updated passing (seed, identity, transport, federation,
fips module, transport::fips).
Known gaps: fips.apply-update returns a clear stub error until
upstream publishes per-commit .deb artefacts; HomeNetworkCard is not
mounted in Home.vue by default.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 22:57:51 -04:00
echo " Using archipelago-fips.service from configs/"
fi
2026-04-08 13:15:09 +02:00
2026-04-08 15:06:27 +02:00
# Copy private Nostr relay service (native, for NostrVPN signaling)
2026-04-28 15:00:58 -04:00
if [ -f " $SCRIPT_DIR /../configs/nostr-relay.service " ] ; then
cp " $SCRIPT_DIR /../configs/nostr-relay.service " " $WORK_DIR /nostr-relay.service "
2026-04-08 15:06:27 +02:00
echo " Using nostr-relay.service from configs/"
fi
2026-04-28 15:00:58 -04:00
if [ -f " $SCRIPT_DIR /../configs/nostr-relay-config.toml " ] ; then
cp " $SCRIPT_DIR /../configs/nostr-relay-config.toml " " $WORK_DIR /nostr-relay-config.toml "
2026-04-08 15:06:27 +02:00
echo " Using nostr-relay-config.toml from configs/"
fi
2026-04-08 13:15:09 +02:00
# Copy WireGuard helper script (privileged peer management)
2026-04-28 15:00:58 -04:00
if [ -f " $SCRIPT_DIR /../../scripts/archipelago-wg " ] ; then
cp " $SCRIPT_DIR /../../scripts/archipelago-wg " " $WORK_DIR /archipelago-wg "
2026-04-08 13:15:09 +02:00
echo " Using archipelago-wg helper from scripts/"
fi
2026-02-25 18:20:50 +00:00
# Use archipelago.service from configs/ (User=root for Podman container access)
2026-04-28 15:00:58 -04:00
if [ -f " $SCRIPT_DIR /../configs/archipelago.service " ] ; then
cp " $SCRIPT_DIR /../configs/archipelago.service " " $WORK_DIR /archipelago.service "
2026-02-25 18:20:50 +00:00
echo " Using archipelago.service from configs/"
else
cat > " $WORK_DIR /archipelago.service " <<'SYS TEMDSERVICE'
2026-02-01 05:42:05 +00:00
[ Unit]
Description = Archipelago Backend
fix: production onboarding, CI tests, container security, keyboard nav
Install & Onboarding:
- Remove DEV_MODE=true from production ISO service file (auto-created
users, skipped password setup)
- Auto-install no longer overwrites rootfs service file with bad template
- Login.vue always checks auth.isSetup — shows password creation form
on fresh install without requiring dev build flag
- Deploy image-versions.sh to /opt/archipelago/scripts/ on installed nodes
- First-boot-containers sources image-versions.sh, runs podman as
archipelago user (rootless), enables linger + podman.socket
- Correct volume ownership (100000:100000 for rootless UID mapping)
Container Security:
- FileBrowser: add --cap-add=DAC_OVERRIDE for rootless podman volume access
- FileBrowser: add --read-only, /data volume for database, proper cmd args
- First-boot script matches backend config (security hardening + health check)
CI Pipeline:
- Add vue-tsc type check + vitest run to build-iso.yml (runs every push)
- Add post-install-tests.yml workflow (workflow_dispatch, SSH to target)
- Build report: set +eo pipefail, fix rootfs path, add || true guards
- Bundle run-post-install-tests.sh into ISO
E2E Test Suite (scripts/run-post-install-tests.sh):
- Phase 1: Install verification (files, services, podman, linger, DEV_MODE check)
- Phase 2: Onboarding flow (auth.isSetup, auth.setup, login, DID, complete)
- Phase 3: Container lifecycle (install 3 apps via package.install RPC,
verify running, stop, verify stopped, restart, verify running, health)
- Phase 4: Log verification (first-boot log, diagnostics, journal errors)
- Correct package.install params: {"id", "dockerImage"}
Frontend:
- Fix backdrop-filter tab-switch bug (keep animations paused during rebuild)
- Dashboard glitch animations paused during tab-hidden
- Gamepad nav: auto-focus first container on route change
- Tab roving: Left/Right on role="tab" cycles and activates sibling tabs
- ContainerApps: data-controller-launch on running app cards
- 515 tests passing (fixed 30 broken, added 19 new keyboard nav tests)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 16:16:57 +00:00
After = network-online.target archipelago-setup-tor.service
2026-02-01 05:42:05 +00:00
Wants = network-online.target
[ Service]
Type = simple
fix: production onboarding, CI tests, container security, keyboard nav
Install & Onboarding:
- Remove DEV_MODE=true from production ISO service file (auto-created
users, skipped password setup)
- Auto-install no longer overwrites rootfs service file with bad template
- Login.vue always checks auth.isSetup — shows password creation form
on fresh install without requiring dev build flag
- Deploy image-versions.sh to /opt/archipelago/scripts/ on installed nodes
- First-boot-containers sources image-versions.sh, runs podman as
archipelago user (rootless), enables linger + podman.socket
- Correct volume ownership (100000:100000 for rootless UID mapping)
Container Security:
- FileBrowser: add --cap-add=DAC_OVERRIDE for rootless podman volume access
- FileBrowser: add --read-only, /data volume for database, proper cmd args
- First-boot script matches backend config (security hardening + health check)
CI Pipeline:
- Add vue-tsc type check + vitest run to build-iso.yml (runs every push)
- Add post-install-tests.yml workflow (workflow_dispatch, SSH to target)
- Build report: set +eo pipefail, fix rootfs path, add || true guards
- Bundle run-post-install-tests.sh into ISO
E2E Test Suite (scripts/run-post-install-tests.sh):
- Phase 1: Install verification (files, services, podman, linger, DEV_MODE check)
- Phase 2: Onboarding flow (auth.isSetup, auth.setup, login, DID, complete)
- Phase 3: Container lifecycle (install 3 apps via package.install RPC,
verify running, stop, verify stopped, restart, verify running, health)
- Phase 4: Log verification (first-boot log, diagnostics, journal errors)
- Correct package.install params: {"id", "dockerImage"}
Frontend:
- Fix backdrop-filter tab-switch bug (keep animations paused during rebuild)
- Dashboard glitch animations paused during tab-hidden
- Gamepad nav: auto-focus first container on route change
- Tab roving: Left/Right on role="tab" cycles and activates sibling tabs
- ContainerApps: data-controller-launch on running app cards
- 515 tests passing (fixed 30 broken, added 19 new keyboard nav tests)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 16:16:57 +00:00
User = archipelago
2026-03-19 12:55:31 +00:00
Environment = "ARCHIPELAGO_BIND=127.0.0.1:5678"
fix: production onboarding, CI tests, container security, keyboard nav
Install & Onboarding:
- Remove DEV_MODE=true from production ISO service file (auto-created
users, skipped password setup)
- Auto-install no longer overwrites rootfs service file with bad template
- Login.vue always checks auth.isSetup — shows password creation form
on fresh install without requiring dev build flag
- Deploy image-versions.sh to /opt/archipelago/scripts/ on installed nodes
- First-boot-containers sources image-versions.sh, runs podman as
archipelago user (rootless), enables linger + podman.socket
- Correct volume ownership (100000:100000 for rootless UID mapping)
Container Security:
- FileBrowser: add --cap-add=DAC_OVERRIDE for rootless podman volume access
- FileBrowser: add --read-only, /data volume for database, proper cmd args
- First-boot script matches backend config (security hardening + health check)
CI Pipeline:
- Add vue-tsc type check + vitest run to build-iso.yml (runs every push)
- Add post-install-tests.yml workflow (workflow_dispatch, SSH to target)
- Build report: set +eo pipefail, fix rootfs path, add || true guards
- Bundle run-post-install-tests.sh into ISO
E2E Test Suite (scripts/run-post-install-tests.sh):
- Phase 1: Install verification (files, services, podman, linger, DEV_MODE check)
- Phase 2: Onboarding flow (auth.isSetup, auth.setup, login, DID, complete)
- Phase 3: Container lifecycle (install 3 apps via package.install RPC,
verify running, stop, verify stopped, restart, verify running, health)
- Phase 4: Log verification (first-boot log, diagnostics, journal errors)
- Correct package.install params: {"id", "dockerImage"}
Frontend:
- Fix backdrop-filter tab-switch bug (keep animations paused during rebuild)
- Dashboard glitch animations paused during tab-hidden
- Gamepad nav: auto-focus first container on route change
- Tab roving: Left/Right on role="tab" cycles and activates sibling tabs
- ContainerApps: data-controller-launch on running app cards
- 515 tests passing (fixed 30 broken, added 19 new keyboard nav tests)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 16:16:57 +00:00
Environment = "XDG_RUNTIME_DIR=/run/user/1000"
ExecStartPre = /bin/bash -c 'mkdir -p /run/user/1000 && chown archipelago:archipelago /run/user/1000 && chmod 700 /run/user/1000'
2026-02-01 05:42:05 +00:00
ExecStart = /usr/local/bin/archipelago
Restart = on-failure
RestartSec = 5
fix: production onboarding, CI tests, container security, keyboard nav
Install & Onboarding:
- Remove DEV_MODE=true from production ISO service file (auto-created
users, skipped password setup)
- Auto-install no longer overwrites rootfs service file with bad template
- Login.vue always checks auth.isSetup — shows password creation form
on fresh install without requiring dev build flag
- Deploy image-versions.sh to /opt/archipelago/scripts/ on installed nodes
- First-boot-containers sources image-versions.sh, runs podman as
archipelago user (rootless), enables linger + podman.socket
- Correct volume ownership (100000:100000 for rootless UID mapping)
Container Security:
- FileBrowser: add --cap-add=DAC_OVERRIDE for rootless podman volume access
- FileBrowser: add --read-only, /data volume for database, proper cmd args
- First-boot script matches backend config (security hardening + health check)
CI Pipeline:
- Add vue-tsc type check + vitest run to build-iso.yml (runs every push)
- Add post-install-tests.yml workflow (workflow_dispatch, SSH to target)
- Build report: set +eo pipefail, fix rootfs path, add || true guards
- Bundle run-post-install-tests.sh into ISO
E2E Test Suite (scripts/run-post-install-tests.sh):
- Phase 1: Install verification (files, services, podman, linger, DEV_MODE check)
- Phase 2: Onboarding flow (auth.isSetup, auth.setup, login, DID, complete)
- Phase 3: Container lifecycle (install 3 apps via package.install RPC,
verify running, stop, verify stopped, restart, verify running, health)
- Phase 4: Log verification (first-boot log, diagnostics, journal errors)
- Correct package.install params: {"id", "dockerImage"}
Frontend:
- Fix backdrop-filter tab-switch bug (keep animations paused during rebuild)
- Dashboard glitch animations paused during tab-hidden
- Gamepad nav: auto-focus first container on route change
- Tab roving: Left/Right on role="tab" cycles and activates sibling tabs
- ContainerApps: data-controller-launch on running app cards
- 515 tests passing (fixed 30 broken, added 19 new keyboard nav tests)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 16:16:57 +00:00
ProtectHome = no
2026-02-01 05:42:05 +00:00
[ Install]
WantedBy = multi-user.target
SYSTEMDSERVICE
2026-02-25 18:20:50 +00:00
fi
2026-02-01 05:42:05 +00:00
2026-02-01 18:46:35 +00:00
echo " Building $CONTAINER_CMD image (this may take a few minutes)... "
2026-03-25 21:31:51 +00:00
$CONTAINER_CMD build --no-cache --platform $CONTAINER_PLATFORM -t archipelago-rootfs -f " $WORK_DIR /Dockerfile.rootfs " " $WORK_DIR "
2026-02-01 05:42:05 +00:00
echo " Exporting filesystem..."
2026-04-02 15:16:24 +01:00
$CONTAINER_CMD rm -f archipelago-rootfs-tmp 2>/dev/null || true
2026-03-12 00:19:30 +00:00
$CONTAINER_CMD create --platform $CONTAINER_PLATFORM --name archipelago-rootfs-tmp archipelago-rootfs
2026-02-01 18:46:35 +00:00
$CONTAINER_CMD export archipelago-rootfs-tmp > " $ROOTFS_TAR "
$CONTAINER_CMD rm archipelago-rootfs-tmp
2026-02-01 05:42:05 +00:00
echo " ✅ Root filesystem created: $( du -h " $ROOTFS_TAR " | cut -f1) "
else
echo " ✅ Using cached root filesystem: $( du -h " $ROOTFS_TAR " | cut -f1) "
fi
# =============================================================================
2026-03-27 18:19:49 +00:00
# STEP 2: Build minimal installer environment (replaces Debian Live)
2026-02-01 05:42:05 +00:00
# =============================================================================
echo ""
2026-03-27 18:19:49 +00:00
echo "Step 2: Building minimal installer environment via debootstrap..."
2026-02-01 05:42:05 +00:00
2026-03-27 18:19:49 +00:00
INSTALLER_ISO = " $WORK_DIR /installer-iso "
INSTALLER_SQUASHFS = " $WORK_DIR /installer-squashfs "
rm -rf " $INSTALLER_ISO " " $INSTALLER_SQUASHFS "
mkdir -p " $INSTALLER_ISO /live " " $INSTALLER_ISO /archipelago "
mkdir -p " $INSTALLER_ISO /boot/grub " " $INSTALLER_ISO /isolinux "
mkdir -p " $INSTALLER_ISO /EFI/BOOT "
# Build the installer filesystem inside a container
# This creates: vmlinuz, initrd.img, filesystem.squashfs
fix(iso): pass installer-env script as bind-mounted file, not inline bash -c
On this host (and potentially others with a particular podman/overlay
state), passing the multi-hundred-line stage-2 script via
`debian:trixie bash -c '...'` caused debootstrap to fail at
"Extracting apt... tar failed" on the very first package — no matter
what patch, storage cleanup, or env-reset we tried.
Running the exact same script body via a bind-mounted file
(`bash /installer-env.sh`) succeeds. So: write the body to a temp
file in WORK_DIR, bind-mount it read-only, and have the container
bash execute it from the file. Same behavior, different invocation,
works.
Was blocking every ISO rebuild since ~10:57 local. First successful
build since: 14:40, sha256 41fad2ff…, 2.3GB.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 14:40:52 -04:00
# NOTE: the installer-env script is written to a file and bind-mounted into the
# container rather than passed via `bash -c '...'`. On some hosts, the inline
# form somehow interferes with debootstrap's dpkg-deb|tar extraction (repro'd
# on this box: bash -c fails at "Extracting apt...", bash /script.sh succeeds).
_INSTALLER_ENV_SCRIPT = " $WORK_DIR /_installer-env.sh "
cat > " $_INSTALLER_ENV_SCRIPT " <<'INSTALLER_ENV_EOF'
2026-03-27 18:19:49 +00:00
set -e
2026-02-14 16:44:20 +00:00
2026-03-27 18:19:49 +00:00
apt-get update -qq
apt-get install -y -qq debootstrap squashfs-tools initramfs-tools dosfstools mtools \
grub-efi-amd64-bin grub-pc-bin grub-common isolinux syslinux-common
echo " [container] Running debootstrap --variant=minbase..."
2026-04-19 09:54:12 -04:00
# ifupdown + isc-dhcp-client added because live-boot's /init writes
# /etc/network/interfaces on the target — without ifupdown, /etc/network/
# doesn't exist and the initramfs throws a non-fatal but noisy
# "can't create /root/etc/network/interfaces: nonexistent directory".
2026-03-27 18:19:49 +00:00
debootstrap --variant= minbase --arch= ${ DEB_ARCH } \
--include= systemd,systemd-sysv,udev,dbus,bash,coreutils,mount,util-linux,\
kmod,procps,iproute2,ca-certificates,gdisk,\
cryptsetup,cryptsetup-initramfs,parted,dosfstools,e2fsprogs,\
linux-image-${ DEB_ARCH } ,grub-efi-${ DEB_ARCH } ,grub-pc-bin,\
2026-04-19 09:54:12 -04:00
ifupdown,isc-dhcp-client,\
2026-03-27 18:19:49 +00:00
pciutils,usbutils,less,nano \
2026-04-09 20:42:09 +02:00
trixie /installer http://deb.debian.org/debian
2026-03-27 18:19:49 +00:00
feat: custom boot branding, MBR fix, Plymouth theme, CI smoke tests
Boot fix:
- Ship proven Debian Live MBR (4552) as branding/isohdpfx.bin — the
ISOLINUX package MBR (33ed) doesn't boot on all hardware. This was
the root cause of "machine doesn't pick up the USB".
Branding:
- Custom GRUB background: pixel-art floating island (1024x574)
- Archipelago pixel-art logo for Plymouth boot splash
- GRUB theme: dark background, orange selected item, no broken font refs
- Plymouth theme: script-based with progress bar, LUKS prompt support
- Plymouth + splash added to target rootfs packages
- GRUB theme installed on both installer ISO and target system
- Serial console (ttyS0) added to kernel params for QEMU debugging
CI improvements:
- Smoke test step: mounts ISO, verifies all critical files, checks
initrd has live-boot, confirms boot=live in grub.cfg. Fails build
before copying to Builds if any check fails.
Dev workflow:
- dev-branding.sh: extract ISO, swap branding, repackage, boot in QEMU
(~10 seconds vs 20 min full rebuild)
- generate-grub-background.py: procedural cyberpunk background generator
- generate-plymouth-logo.py: procedural logo generator
- Improved test-iso-qemu.sh: --bios/--nographic flags, serial logging
Build:
- Simplified live-boot install (clean chroot, no complex fallbacks)
- Static branding images preferred, generators as fallback
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 22:26:56 +00:00
# Install live-boot via chroot — debootstrap minbase resolver cannot handle it.
# The chroot approach works (confirmed in CI run 90) — just needs proc/sys/dev mounts.
2026-03-27 19:11:54 +00:00
echo " [container] Installing live-boot for squashfs root support..."
2026-03-27 19:45:52 +00:00
cp /etc/resolv.conf /installer/etc/resolv.conf 2>/dev/null || true
feat: custom boot branding, MBR fix, Plymouth theme, CI smoke tests
Boot fix:
- Ship proven Debian Live MBR (4552) as branding/isohdpfx.bin — the
ISOLINUX package MBR (33ed) doesn't boot on all hardware. This was
the root cause of "machine doesn't pick up the USB".
Branding:
- Custom GRUB background: pixel-art floating island (1024x574)
- Archipelago pixel-art logo for Plymouth boot splash
- GRUB theme: dark background, orange selected item, no broken font refs
- Plymouth theme: script-based with progress bar, LUKS prompt support
- Plymouth + splash added to target rootfs packages
- GRUB theme installed on both installer ISO and target system
- Serial console (ttyS0) added to kernel params for QEMU debugging
CI improvements:
- Smoke test step: mounts ISO, verifies all critical files, checks
initrd has live-boot, confirms boot=live in grub.cfg. Fails build
before copying to Builds if any check fails.
Dev workflow:
- dev-branding.sh: extract ISO, swap branding, repackage, boot in QEMU
(~10 seconds vs 20 min full rebuild)
- generate-grub-background.py: procedural cyberpunk background generator
- generate-plymouth-logo.py: procedural logo generator
- Improved test-iso-qemu.sh: --bios/--nographic flags, serial logging
Build:
- Simplified live-boot install (clean chroot, no complex fallbacks)
- Static branding images preferred, generators as fallback
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 22:26:56 +00:00
mount --bind /proc /installer/proc
mount --bind /sys /installer/sys
mount --bind /dev /installer/dev
chroot /installer apt-get update -qq
2026-05-05 11:29:18 -04:00
chroot /installer apt-get -y -qq full-upgrade
feat: custom boot branding, MBR fix, Plymouth theme, CI smoke tests
Boot fix:
- Ship proven Debian Live MBR (4552) as branding/isohdpfx.bin — the
ISOLINUX package MBR (33ed) doesn't boot on all hardware. This was
the root cause of "machine doesn't pick up the USB".
Branding:
- Custom GRUB background: pixel-art floating island (1024x574)
- Archipelago pixel-art logo for Plymouth boot splash
- GRUB theme: dark background, orange selected item, no broken font refs
- Plymouth theme: script-based with progress bar, LUKS prompt support
- Plymouth + splash added to target rootfs packages
- GRUB theme installed on both installer ISO and target system
- Serial console (ttyS0) added to kernel params for QEMU debugging
CI improvements:
- Smoke test step: mounts ISO, verifies all critical files, checks
initrd has live-boot, confirms boot=live in grub.cfg. Fails build
before copying to Builds if any check fails.
Dev workflow:
- dev-branding.sh: extract ISO, swap branding, repackage, boot in QEMU
(~10 seconds vs 20 min full rebuild)
- generate-grub-background.py: procedural cyberpunk background generator
- generate-plymouth-logo.py: procedural logo generator
- Improved test-iso-qemu.sh: --bios/--nographic flags, serial logging
Build:
- Simplified live-boot install (clean chroot, no complex fallbacks)
- Static branding images preferred, generators as fallback
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 22:26:56 +00:00
chroot /installer apt-get install -y --no-install-recommends live-boot live-boot-initramfs-tools
chroot /installer apt-get clean
umount /installer/dev 2>/dev/null || true
umount /installer/sys 2>/dev/null || true
umount /installer/proc 2>/dev/null || true
2026-03-27 19:45:52 +00:00
feat: custom boot branding, MBR fix, Plymouth theme, CI smoke tests
Boot fix:
- Ship proven Debian Live MBR (4552) as branding/isohdpfx.bin — the
ISOLINUX package MBR (33ed) doesn't boot on all hardware. This was
the root cause of "machine doesn't pick up the USB".
Branding:
- Custom GRUB background: pixel-art floating island (1024x574)
- Archipelago pixel-art logo for Plymouth boot splash
- GRUB theme: dark background, orange selected item, no broken font refs
- Plymouth theme: script-based with progress bar, LUKS prompt support
- Plymouth + splash added to target rootfs packages
- GRUB theme installed on both installer ISO and target system
- Serial console (ttyS0) added to kernel params for QEMU debugging
CI improvements:
- Smoke test step: mounts ISO, verifies all critical files, checks
initrd has live-boot, confirms boot=live in grub.cfg. Fails build
before copying to Builds if any check fails.
Dev workflow:
- dev-branding.sh: extract ISO, swap branding, repackage, boot in QEMU
(~10 seconds vs 20 min full rebuild)
- generate-grub-background.py: procedural cyberpunk background generator
- generate-plymouth-logo.py: procedural logo generator
- Improved test-iso-qemu.sh: --bios/--nographic flags, serial logging
Build:
- Simplified live-boot install (clean chroot, no complex fallbacks)
- Static branding images preferred, generators as fallback
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 22:26:56 +00:00
# Verify live-boot hooks are in place (scripts/live is a FILE not a directory)
2026-03-27 20:36:39 +00:00
if [ -e /installer/usr/share/initramfs-tools/scripts/live ] ; then
2026-03-27 19:11:54 +00:00
echo " [container] live-boot initramfs hooks: OK"
else
feat: custom boot branding, MBR fix, Plymouth theme, CI smoke tests
Boot fix:
- Ship proven Debian Live MBR (4552) as branding/isohdpfx.bin — the
ISOLINUX package MBR (33ed) doesn't boot on all hardware. This was
the root cause of "machine doesn't pick up the USB".
Branding:
- Custom GRUB background: pixel-art floating island (1024x574)
- Archipelago pixel-art logo for Plymouth boot splash
- GRUB theme: dark background, orange selected item, no broken font refs
- Plymouth theme: script-based with progress bar, LUKS prompt support
- Plymouth + splash added to target rootfs packages
- GRUB theme installed on both installer ISO and target system
- Serial console (ttyS0) added to kernel params for QEMU debugging
CI improvements:
- Smoke test step: mounts ISO, verifies all critical files, checks
initrd has live-boot, confirms boot=live in grub.cfg. Fails build
before copying to Builds if any check fails.
Dev workflow:
- dev-branding.sh: extract ISO, swap branding, repackage, boot in QEMU
(~10 seconds vs 20 min full rebuild)
- generate-grub-background.py: procedural cyberpunk background generator
- generate-plymouth-logo.py: procedural logo generator
- Improved test-iso-qemu.sh: --bios/--nographic flags, serial logging
Build:
- Simplified live-boot install (clean chroot, no complex fallbacks)
- Static branding images preferred, generators as fallback
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 22:26:56 +00:00
echo " [container] FATAL: live-boot hooks not found after install!"
ls -la /installer/usr/share/initramfs-tools/scripts/ 2>/dev/null
exit 1
2026-03-27 19:11:54 +00:00
fi
2026-03-27 18:19:49 +00:00
echo " [container] Configuring installer environment..."
# Set hostname
echo "archipelago-installer" > /installer/etc/hostname
# Set root password
echo "root:archipelago" | chroot /installer chpasswd
# Auto-login on tty1
mkdir -p /installer/etc/systemd/system/getty@tty1.service.d
cat > /installer/etc/systemd/system/getty@tty1.service.d/autologin.conf <<GETTY
[ Service]
ExecStart =
ExecStart = -/sbin/agetty --autologin root --noclear %I \$ TERM
GETTY
2026-03-28 00:35:34 +00:00
# Auto-start installer via profile.d (runs after auto-login, no getty race)
# This is the same approach the working Debian Live build used.
mkdir -p /installer/etc/profile.d
2026-03-28 18:44:36 +00:00
cat > /installer/etc/profile.d/z99-archipelago-installer.sh <<PROFILE
2026-03-27 18:19:49 +00:00
#!/bin/bash
2026-03-28 00:35:34 +00:00
# Auto-start Archipelago installer on login — only run once
2026-03-28 18:44:36 +00:00
if [ -n "\$INSTALLER_STARTED" ] ; then
2026-03-28 00:35:34 +00:00
return 0 2>/dev/null || exit 0
fi
export INSTALLER_STARTED = 1
sleep 1
2026-03-27 18:19:49 +00:00
clear
echo ""
2026-03-28 23:58:42 +00:00
echo -e "\033[38;5;208m ▄▀█ █▀▄ █▀▀ █ █ █ █▀█ █▀▀ █ ▄▀█ █▀▀ █▀█\033[0m"
echo -e "\033[38;5;208m █▀█ █▀▄ █ █▀█ █ █▀▀ ██▀ █ █▀█ █ █ █ █\033[0m"
echo -e "\033[38;5;208m ▀ ▀ ▀ ▀ ▀▀▀ ▀ ▀ ▀ ▀ ▀▀▀ ▀▀▀ ▀ ▀ ▀▀▀ ▀▀▀\033[0m"
echo -e " \033[38;5;130mbitcoin node os\033[0m"
2026-03-27 18:19:49 +00:00
echo ""
BOOT_MEDIA = ""
2026-03-27 19:08:00 +00:00
for dev in /run/live/medium /lib/live/mount/medium /run/archiso /cdrom /media/cdrom /mnt/iso; do
2026-03-28 18:44:36 +00:00
if [ -f "\$dev/archipelago/auto-install.sh" ] ; then
BOOT_MEDIA = "\$dev"
2026-03-27 18:19:49 +00:00
break
2026-02-14 16:44:20 +00:00
fi
2026-03-27 18:19:49 +00:00
done
2026-03-28 18:44:36 +00:00
# If standard mount points failed, actively find and mount the boot device
if [ -z "\$BOOT_MEDIA" ] ; then
echo -e " \033[37mSearching for boot device...\033[0m"
mkdir -p /run/archiso 2>/dev/null
for blk in /dev/sr0 /dev/sd[ a-z] /dev/sd[ a-z] [ 0-9] /dev/nvme[ 0-9] n[ 0-9] p[ 0-9] ; do
[ -b "\$blk" ] || continue
mount -o ro "\$blk" /run/archiso 2>/dev/null || continue
if [ -f /run/archiso/archipelago/auto-install.sh ] ; then
BOOT_MEDIA = "/run/archiso"
break
fi
umount /run/archiso 2>/dev/null
done
fi
if [ -n "\$BOOT_MEDIA" ] ; then
echo -e " \033[37mFound installer at: \$BOOT_MEDIA\033[0m"
2026-03-27 18:19:49 +00:00
echo ""
2026-03-28 15:15:42 +00:00
echo -e " Press Enter to install | \033[1;37mCtrl+C\033[0m for shell"
2026-03-28 11:31:48 +00:00
read -s
2026-03-28 18:44:36 +00:00
bash "\$BOOT_MEDIA/archipelago/auto-install.sh"
2026-03-27 18:19:49 +00:00
else
2026-03-28 15:15:42 +00:00
echo -e " \033[37mInstaller not found on boot media.\033[0m"
2026-03-28 00:35:34 +00:00
echo ""
2026-03-28 18:44:36 +00:00
echo -e " \033[37mDebug info:\033[0m"
ls -la /run/live/ 2>/dev/null || echo " /run/live/ does not exist"
mount | grep -E "iso9660|squashfs|overlay" 2>/dev/null
echo ""
echo -e " \033[37mTry: mount /dev/sdX /mnt/iso && bash /mnt/iso/archipelago/auto-install.sh\033[0m"
echo ""
2026-02-14 16:44:20 +00:00
fi
2026-03-28 00:35:34 +00:00
PROFILE
chmod +x /installer/etc/profile.d/z99-archipelago-installer.sh
2026-02-14 16:44:20 +00:00
2026-03-27 18:19:49 +00:00
# Custom initramfs hook: mount ISO boot media at /run/archiso
mkdir -p /installer/etc/initramfs-tools/hooks
cat > /installer/etc/initramfs-tools/hooks/archipelago <<HOOK
#!/bin/sh
set -e
PREREQ = ""
prereqs( ) { echo "\$PREREQ" ; }
case "\$1" in prereqs) prereqs; exit 0; ; esac
. /usr/share/initramfs-tools/hook-functions
# Ensure mount helpers and filesystem tools are in initramfs
copy_exec /bin/mount
copy_exec /bin/umount
copy_exec /bin/findfs 2>/dev/null || true
copy_exec /sbin/blkid
manual_add_modules iso9660 vfat squashfs overlay
HOOK
chmod +x /installer/etc/initramfs-tools/hooks/archipelago
mkdir -p /installer/etc/initramfs-tools/scripts/local-bottom
cat > /installer/etc/initramfs-tools/scripts/local-bottom/archipelago-mount <<INI TSCRIPT
#!/bin/sh
PREREQ = ""
prereqs( ) { echo "\$PREREQ" ; }
case "\$1" in prereqs) prereqs; exit 0; ; esac
. /scripts/functions
# Try to find and mount the Archipelago boot media
mkdir -p /run/archiso
log_begin_msg "Searching for Archipelago boot media..."
# Try CD-ROM first, then USB partitions
for dev in /dev/sr0 /dev/sd??* /dev/nvme*p*; do
[ -b "\$dev" ] 2>/dev/null || continue
mount -o ro "\$dev" /run/archiso 2>/dev/null || continue
if [ -d /run/archiso/archipelago ] ; then
log_end_msg 0
echo "Found Archipelago media on \$dev"
exit 0
2026-02-14 16:44:20 +00:00
fi
2026-03-27 18:19:49 +00:00
umount /run/archiso 2>/dev/null || true
done
log_end_msg 1
echo "Archipelago boot media not found (will retry from userspace)"
INITSCRIPT
chmod +x /installer/etc/initramfs-tools/scripts/local-bottom/archipelago-mount
# Strip docs and man pages from installer
rm -rf /installer/usr/share/man/* /installer/usr/share/doc/*
rm -rf /installer/var/lib/apt/lists/* /installer/var/cache/apt/*
# Extract kernel
KVER = $( ls /installer/lib/modules/ | sort -V | tail -1)
echo " [container] Kernel version: $KVER "
cp /installer/boot/vmlinuz-$KVER /output/vmlinuz
2026-03-27 19:08:00 +00:00
# Mount virtual filesystems for proper initramfs generation
mount --bind /proc /installer/proc
mount --bind /sys /installer/sys
mount --bind /dev /installer/dev
# Build initramfs with live-boot hooks + our custom hooks
2026-03-27 18:19:49 +00:00
chroot /installer update-initramfs -c -k $KVER
cp /installer/boot/initrd.img-$KVER /output/initrd.img
2026-03-27 19:08:00 +00:00
# Cleanup mounts
umount /installer/dev 2>/dev/null || true
umount /installer/sys 2>/dev/null || true
umount /installer/proc 2>/dev/null || true
2026-03-27 18:19:49 +00:00
# Create squashfs
echo " [container] Creating installer squashfs..."
mksquashfs /installer /output/filesystem.squashfs -comp xz -Xbcj x86 -noappend -quiet
2026-03-27 23:33:31 +00:00
# Build GRUB EFI image with embedded bootstrap config (grub-mkstandalone)
2026-03-27 18:19:49 +00:00
echo " [container] Building GRUB EFI image..."
2026-03-28 18:44:36 +00:00
cat > /tmp/grub-embed.cfg <<GRUBEMBED
2026-03-28 03:15:47 +00:00
insmod part_gpt
2026-03-28 23:58:42 +00:00
insmod part_msdos
2026-03-28 03:15:47 +00:00
insmod fat
insmod iso9660
2026-03-28 23:58:42 +00:00
insmod search
2026-03-28 03:15:47 +00:00
insmod search_label
2026-03-28 23:58:42 +00:00
insmod search_fs_file
2026-03-28 03:15:47 +00:00
insmod normal
insmod linux
insmod all_video
2026-03-28 23:58:42 +00:00
# Try label first (standard path)
2026-03-27 23:33:31 +00:00
search --no-floppy --set= root --label ARCHIPELAGO
2026-03-28 23:58:42 +00:00
# Fallback: search for a known file on the ISO
if [ -z "\$root" ] ; then
search --no-floppy --set= root --file /archipelago/auto-install.sh
fi
# Fallback: try configfile from the EFI partition path
if [ -z "\$root" ] ; then
set root = \$ cmdpath
fi
2026-03-28 18:44:36 +00:00
set prefix = ( \$ root) /boot/grub
2026-03-28 23:58:42 +00:00
configfile ( \$ root) /boot/grub/grub.cfg
# If configfile fails, try normal
2026-03-28 03:15:47 +00:00
normal
2026-03-27 23:33:31 +00:00
GRUBEMBED
grub-mkstandalone -O x86_64-efi \
2026-03-28 23:58:42 +00:00
--modules= "part_gpt part_msdos fat iso9660 search search_label search_fs_file normal linux all_video font gfxterm configfile echo cat ls test true loopback png" \
2026-03-27 23:33:31 +00:00
--locales= "" \
--themes= "" \
--fonts= "" \
--output= /output/BOOTX64.EFI \
"boot/grub/grub.cfg=/tmp/grub-embed.cfg"
# Create EFI FAT image (20MB — includes GRUB binary + grub.cfg)
dd if = /dev/zero of = /output/efi.img bs = 1M count = 20 2>/dev/null
2026-03-27 18:19:49 +00:00
mkfs.vfat /output/efi.img >/dev/null
mmd -i /output/efi.img ::/EFI ::/EFI/BOOT
mcopy -i /output/efi.img /output/BOOTX64.EFI ::/EFI/BOOT/BOOTX64.EFI
# Copy ISOLINUX files for legacy BIOS boot
cp /usr/lib/ISOLINUX/isolinux.bin /output/isolinux.bin
cp /usr/lib/syslinux/modules/bios/ldlinux.c32 /output/ldlinux.c32
cp /usr/lib/syslinux/modules/bios/menu.c32 /output/menu.c32 2>/dev/null || true
2026-03-28 03:15:47 +00:00
cp /usr/lib/syslinux/modules/bios/vesamenu.c32 /output/vesamenu.c32 2>/dev/null || true
2026-03-27 18:19:49 +00:00
cp /usr/lib/syslinux/modules/bios/libutil.c32 /output/libutil.c32 2>/dev/null || true
2026-03-28 03:15:47 +00:00
cp /usr/lib/syslinux/modules/bios/libcom32.c32 /output/libcom32.c32 2>/dev/null || true
2026-03-27 18:19:49 +00:00
cp /usr/lib/ISOLINUX/isohdpfx.bin /output/isohdpfx.bin
# Generate GRUB fonts for theme
echo " [container] Generating GRUB fonts..."
apt-get install -y -qq fonts-dejavu-core grub-common >/dev/null 2>& 1
mkdir -p /output/grub-fonts
fix: container security hardening, onboarding viewport scaling, boot screen cleanup
Container security:
- Add --cap-drop ALL + --security-opt no-new-privileges:true to 12 containers
missing hardening in first-boot-containers.sh (mempool-db, electrumx,
mempool-api, mempool-web, electrs-ui, btcpay-db, nbxplorer, nostr-rs-relay,
strfry, tailscale, bitcoin-ui, lnd-ui)
- Mirror same hardening in deploy-to-target.sh for consistency
- Add --read-only + tmpfs to nostr-rs-relay
- Fix filebrowser deploy to include security flags
- Remove duplicate UI image definitions in image-versions.sh
- Separate Jellyfin capabilities (needs FOWNER, exec tmpfs for CoreCLR JIT)
- Harden archy-net creation with existence check and error handling
UI fixes:
- Fix onboarding viewport scaling: all 7 screens now use h-full + max-h-full
pattern so containers never overflow viewport regardless of padding
- Remove path-option-card wrappers from seed verify inputs, left-justify labels
- Remove batteries/barbarian icons from boot screen (keep bitcoin, cloud, github, save)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 17:35:34 +01:00
grub-mkfont -s 12 -o /output/grub-fonts/dejavu_12.pf2 /usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf
grub-mkfont -s 14 -o /output/grub-fonts/dejavu_14.pf2 /usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf
grub-mkfont -s 16 -o /output/grub-fonts/dejavu_16.pf2 /usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf
2026-03-27 18:19:49 +00:00
grub-mkfont -s 24 -o /output/grub-fonts/dejavu_24.pf2 /usr/share/fonts/truetype/dejavu/DejaVuSansMono-Bold.ttf
echo " [container] Done!"
fix(iso): pass installer-env script as bind-mounted file, not inline bash -c
On this host (and potentially others with a particular podman/overlay
state), passing the multi-hundred-line stage-2 script via
`debian:trixie bash -c '...'` caused debootstrap to fail at
"Extracting apt... tar failed" on the very first package — no matter
what patch, storage cleanup, or env-reset we tried.
Running the exact same script body via a bind-mounted file
(`bash /installer-env.sh`) succeeds. So: write the body to a temp
file in WORK_DIR, bind-mount it read-only, and have the container
bash execute it from the file. Same behavior, different invocation,
works.
Was blocking every ISO rebuild since ~10:57 local. First successful
build since: 14:40, sha256 41fad2ff…, 2.3GB.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 14:40:52 -04:00
INSTALLER_ENV_EOF
$CONTAINER_CMD run --rm --privileged --platform $CONTAINER_PLATFORM \
-v " $WORK_DIR :/output " \
-v " $_INSTALLER_ENV_SCRIPT :/installer-env.sh:ro " \
-e DEB_ARCH = " $DEB_ARCH " \
-e LIB_DIR = " $LIB_DIR " \
debian:trixie bash /installer-env.sh
2026-03-27 18:19:49 +00:00
# Verify artifacts
for artifact in vmlinuz initrd.img filesystem.squashfs BOOTX64.EFI efi.img isolinux.bin isohdpfx.bin; do
if [ ! -f " $WORK_DIR / $artifact " ] ; then
echo " FATAL: Missing build artifact: $artifact "
2026-02-14 16:44:20 +00:00
exit 1
fi
2026-03-27 18:19:49 +00:00
done
2026-02-01 05:42:05 +00:00
2026-03-27 18:19:49 +00:00
# Place artifacts into ISO directory structure
cp " $WORK_DIR /vmlinuz " " $INSTALLER_ISO /live/vmlinuz "
cp " $WORK_DIR /initrd.img " " $INSTALLER_ISO /live/initrd.img "
cp " $WORK_DIR /filesystem.squashfs " " $INSTALLER_ISO /live/filesystem.squashfs "
cp " $WORK_DIR /BOOTX64.EFI " " $INSTALLER_ISO /EFI/BOOT/BOOTX64.EFI "
2026-03-28 00:35:34 +00:00
cp " $WORK_DIR /efi.img " " $INSTALLER_ISO /boot/grub/efi.img "
2026-03-27 18:19:49 +00:00
cp " $WORK_DIR /isolinux.bin " " $INSTALLER_ISO /isolinux/isolinux.bin "
cp " $WORK_DIR /ldlinux.c32 " " $INSTALLER_ISO /isolinux/ldlinux.c32 "
cp " $WORK_DIR /menu.c32 " " $INSTALLER_ISO /isolinux/menu.c32 " 2>/dev/null || true
2026-03-28 03:15:47 +00:00
cp " $WORK_DIR /vesamenu.c32 " " $INSTALLER_ISO /isolinux/vesamenu.c32 " 2>/dev/null || true
2026-03-27 18:19:49 +00:00
cp " $WORK_DIR /libutil.c32 " " $INSTALLER_ISO /isolinux/libutil.c32 " 2>/dev/null || true
2026-03-28 03:15:47 +00:00
cp " $WORK_DIR /libcom32.c32 " " $INSTALLER_ISO /isolinux/libcom32.c32 " 2>/dev/null || true
2026-03-27 18:19:49 +00:00
# Install GRUB theme
THEME_SRC = " $SCRIPT_DIR /branding/grub-theme "
THEME_DST = " $INSTALLER_ISO /boot/grub/themes/archipelago "
mkdir -p " $THEME_DST "
if [ -f " $THEME_SRC /theme.txt " ] ; then
cp " $THEME_SRC /theme.txt " " $THEME_DST / "
echo " Installed GRUB theme from branding/grub-theme/"
fi
# Install generated fonts
if [ -d " $WORK_DIR /grub-fonts " ] ; then
cp " $WORK_DIR /grub-fonts/ " *.pf2 " $THEME_DST / "
# Also copy unicode font for GRUB to load
cp " $WORK_DIR /grub-fonts/dejavu_16.pf2 " " $INSTALLER_ISO /boot/grub/font.pf2 "
fi
feat: custom boot branding, MBR fix, Plymouth theme, CI smoke tests
Boot fix:
- Ship proven Debian Live MBR (4552) as branding/isohdpfx.bin — the
ISOLINUX package MBR (33ed) doesn't boot on all hardware. This was
the root cause of "machine doesn't pick up the USB".
Branding:
- Custom GRUB background: pixel-art floating island (1024x574)
- Archipelago pixel-art logo for Plymouth boot splash
- GRUB theme: dark background, orange selected item, no broken font refs
- Plymouth theme: script-based with progress bar, LUKS prompt support
- Plymouth + splash added to target rootfs packages
- GRUB theme installed on both installer ISO and target system
- Serial console (ttyS0) added to kernel params for QEMU debugging
CI improvements:
- Smoke test step: mounts ISO, verifies all critical files, checks
initrd has live-boot, confirms boot=live in grub.cfg. Fails build
before copying to Builds if any check fails.
Dev workflow:
- dev-branding.sh: extract ISO, swap branding, repackage, boot in QEMU
(~10 seconds vs 20 min full rebuild)
- generate-grub-background.py: procedural cyberpunk background generator
- generate-plymouth-logo.py: procedural logo generator
- Improved test-iso-qemu.sh: --bios/--nographic flags, serial logging
Build:
- Simplified live-boot install (clean chroot, no complex fallbacks)
- Static branding images preferred, generators as fallback
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 22:26:56 +00:00
# Copy GRUB background image (static asset or generate if missing)
GRUB_BG = " $SCRIPT_DIR /branding/grub-theme/background.png "
if [ -f " $GRUB_BG " ] ; then
cp " $GRUB_BG " " $THEME_DST /background.png "
echo " Installed GRUB background"
elif [ -f " $SCRIPT_DIR /branding/generate-grub-background.py " ] ; then
echo " Generating GRUB background..."
python3 " $SCRIPT_DIR /branding/generate-grub-background.py " " $THEME_DST /background.png " 2>/dev/null || \
echo " WARNING: Could not generate GRUB background"
fi
2026-03-27 18:19:49 +00:00
echo " Installer squashfs: $( du -h " $INSTALLER_ISO /live/filesystem.squashfs " | cut -f1) "
echo " Kernel: $( du -h " $INSTALLER_ISO /live/vmlinuz " | cut -f1) "
echo " Initrd: $( du -h " $INSTALLER_ISO /live/initrd.img " | cut -f1) "
echo " Step 2 complete (custom minimal base, no Debian Live)"
2026-02-01 05:42:05 +00:00
# =============================================================================
# STEP 3: Add Archipelago components
# =============================================================================
echo ""
echo "📦 Step 3: Adding Archipelago components..."
ARCH_DIR = " $INSTALLER_ISO /archipelago "
mkdir -p " $ARCH_DIR "
mkdir -p " $ARCH_DIR /bin "
mkdir -p " $ARCH_DIR /scripts "
2026-04-09 21:32:08 +02:00
# netavark + aardvark-dns are installed in the rootfs via Dockerfile.rootfs (Debian 13 packages).
# Do NOT copy from the build host — the host may run a different glibc version.
echo " netavark + aardvark-dns: included in rootfs (Debian 13 packages)"
2026-03-30 20:52:01 +01:00
2026-02-01 05:42:05 +00:00
# Copy the pre-built rootfs
echo " Including root filesystem..."
cp " $ROOTFS_TAR " " $ARCH_DIR /rootfs.tar "
2026-02-03 21:43:33 +00:00
# Capture backend binary from live server
if [ " $BUILD_FROM_SOURCE " = "1" ] ; then
echo " Building backend binary from source..."
else
echo " Capturing backend binary from live server..."
fi
fix: ISO boot, container installs, VPN, nginx, companion input
- LUKS auto-unlock: initramfs hook + systemd service + nofail fstab
- Rootfs packages: add passt, aardvark-dns, netavark, nftables for Podman 5.x
- nginx: resolver + variable proxy_pass for external domains (DNS at boot)
- Boot: loglevel=0 suppresses kernel warnings, serial console for QEMU
- Container installs: write configs before chown, sudo chown for LUKS volumes
- Container installs: build UI sidecars locally (not from registry) for auth injection
- Bitcoin UI: inject RPC auth from secrets file, --no-cache rebuild
- Secrets: chown to archipelago user in first-boot (backend needs read access)
- Podman: image_copy_tmp_dir for read-only /var/tmp in user namespace
- NostrVPN: enable service in auto-install, always include public relays
- NostrVPN: read tunnel IP from nvpn status (not just config file)
- VPN invite: v2 base64 no-pad format matching phone app
- Companion input: relay always active, kiosk skips relay listener (prevents double input)
- dev-start.sh: production build includes AIUI deployment
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 03:10:49 -04:00
# Try to get backend binary: local release build → local install → remote → container build
2026-02-03 21:43:33 +00:00
BACKEND_CAPTURED = 0
fix: ISO boot, container installs, VPN, nginx, companion input
- LUKS auto-unlock: initramfs hook + systemd service + nofail fstab
- Rootfs packages: add passt, aardvark-dns, netavark, nftables for Podman 5.x
- nginx: resolver + variable proxy_pass for external domains (DNS at boot)
- Boot: loglevel=0 suppresses kernel warnings, serial console for QEMU
- Container installs: write configs before chown, sudo chown for LUKS volumes
- Container installs: build UI sidecars locally (not from registry) for auth injection
- Bitcoin UI: inject RPC auth from secrets file, --no-cache rebuild
- Secrets: chown to archipelago user in first-boot (backend needs read access)
- Podman: image_copy_tmp_dir for read-only /var/tmp in user namespace
- NostrVPN: enable service in auto-install, always include public relays
- NostrVPN: read tunnel IP from nvpn status (not just config file)
- VPN invite: v2 base64 no-pad format matching phone app
- Companion input: relay always active, kiosk skips relay listener (prevents double input)
- dev-start.sh: production build includes AIUI deployment
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 03:10:49 -04:00
2026-04-19 15:19:56 -04:00
# The captured binary MUST report the same version as the checked-out
# core/archipelago/Cargo.toml, otherwise we're shipping a stale binary
# from an earlier version bump (which is what happened with the 14:40
# ISO — it grabbed an Apr-18 1.4.0 binary and the fleet rejected the
# fips.yaml it wrote out on Activate). The expected version is the one
# compiled into this build run.
EXPECTED_VERSION = " $( grep '^version' " $( cd " $SCRIPT_DIR /.. " && pwd ) /core/archipelago/Cargo.toml " | head -1 | sed 's/.*"\(.*\)".*/\1/' ) "
echo " Expected backend version (from Cargo.toml): $EXPECTED_VERSION "
verify_backend_version( ) {
local bin = " $1 "
2026-04-19 15:41:48 -04:00
# CARGO_PKG_VERSION is compiled into the binary as a string literal.
# `strings` output concatenates adjacent printable bytes, so the
# version rarely sits on its own line — a fixed-string substring
# match is the right tool. The version is specific enough (e.g.
# "1.5.0-alpha") that accidental collisions with unrelated data
# are vanishingly unlikely.
if strings " $bin " 2>/dev/null | grep -qF " $EXPECTED_VERSION " ; then
echo " ✅ Version match: binary contains $EXPECTED_VERSION "
return 0
2026-04-19 15:19:56 -04:00
fi
2026-04-19 15:41:48 -04:00
echo " ⚠️ Captured binary does NOT contain expected version $EXPECTED_VERSION — it is stale "
return 1
2026-04-19 15:19:56 -04:00
}
fix: ISO boot, container installs, VPN, nginx, companion input
- LUKS auto-unlock: initramfs hook + systemd service + nofail fstab
- Rootfs packages: add passt, aardvark-dns, netavark, nftables for Podman 5.x
- nginx: resolver + variable proxy_pass for external domains (DNS at boot)
- Boot: loglevel=0 suppresses kernel warnings, serial console for QEMU
- Container installs: write configs before chown, sudo chown for LUKS volumes
- Container installs: build UI sidecars locally (not from registry) for auth injection
- Bitcoin UI: inject RPC auth from secrets file, --no-cache rebuild
- Secrets: chown to archipelago user in first-boot (backend needs read access)
- Podman: image_copy_tmp_dir for read-only /var/tmp in user namespace
- NostrVPN: enable service in auto-install, always include public relays
- NostrVPN: read tunnel IP from nvpn status (not just config file)
- VPN invite: v2 base64 no-pad format matching phone app
- Companion input: relay always active, kiosk skips relay listener (prevents double input)
- dev-start.sh: production build includes AIUI deployment
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 03:10:49 -04:00
# Check for local release binary first (works for both BUILD_FROM_SOURCE and normal mode)
LOCAL_RELEASE = " $( cd " $SCRIPT_DIR /.. " && pwd ) /core/target/release/archipelago "
if [ -f " $LOCAL_RELEASE " ] ; then
2026-04-19 15:19:56 -04:00
if verify_backend_version " $LOCAL_RELEASE " ; then
cp " $LOCAL_RELEASE " " $ARCH_DIR /bin/archipelago "
chmod +x " $ARCH_DIR /bin/archipelago "
echo " ✅ Backend from local release build ( $( du -h " $ARCH_DIR /bin/archipelago " | cut -f1) ) "
BACKEND_CAPTURED = 1
else
echo " Skipping stale local release binary; trying next source"
fi
fix: ISO boot, container installs, VPN, nginx, companion input
- LUKS auto-unlock: initramfs hook + systemd service + nofail fstab
- Rootfs packages: add passt, aardvark-dns, netavark, nftables for Podman 5.x
- nginx: resolver + variable proxy_pass for external domains (DNS at boot)
- Boot: loglevel=0 suppresses kernel warnings, serial console for QEMU
- Container installs: write configs before chown, sudo chown for LUKS volumes
- Container installs: build UI sidecars locally (not from registry) for auth injection
- Bitcoin UI: inject RPC auth from secrets file, --no-cache rebuild
- Secrets: chown to archipelago user in first-boot (backend needs read access)
- Podman: image_copy_tmp_dir for read-only /var/tmp in user namespace
- NostrVPN: enable service in auto-install, always include public relays
- NostrVPN: read tunnel IP from nvpn status (not just config file)
- VPN invite: v2 base64 no-pad format matching phone app
- Companion input: relay always active, kiosk skips relay listener (prevents double input)
- dev-start.sh: production build includes AIUI deployment
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 03:10:49 -04:00
fi
if [ " $BACKEND_CAPTURED " = "0" ] && [ " $BUILD_FROM_SOURCE " != "1" ] ; then
# Direct copy from ARCHIPELAGO_BIN env or local install
2026-03-26 16:23:19 +00:00
BIN = " ${ ARCHIPELAGO_BIN :- /usr/local/bin/archipelago } "
2026-04-19 15:19:56 -04:00
if [ -f " $BIN " ] && verify_backend_version " $BIN " ; then
2026-03-26 15:28:43 +00:00
cp " $BIN " " $ARCH_DIR /bin/archipelago "
2026-02-17 15:03:34 +00:00
chmod +x " $ARCH_DIR /bin/archipelago "
echo " ✅ Backend captured from local system ( $( du -h " $ARCH_DIR /bin/archipelago " | cut -f1) ) "
BACKEND_CAPTURED = 1
fi
# Remote copy via SCP if local failed
if [ " $BACKEND_CAPTURED " = "0" ] && [ " $DEV_SERVER " != "localhost" ] && [ " $DEV_SERVER " != "127.0.0.1" ] ; then
2026-04-19 15:19:56 -04:00
if scp " $DEV_SERVER :/usr/local/bin/archipelago " " $ARCH_DIR /bin/archipelago " 2>/dev/null && verify_backend_version " $ARCH_DIR /bin/archipelago " ; then
2026-02-03 21:43:33 +00:00
chmod +x " $ARCH_DIR /bin/archipelago "
echo " ✅ Backend captured from remote server ( $( du -h " $ARCH_DIR /bin/archipelago " | cut -f1) ) "
BACKEND_CAPTURED = 1
2026-04-19 15:19:56 -04:00
else
rm -f " $ARCH_DIR /bin/archipelago "
2026-02-03 21:43:33 +00:00
fi
fi
fi
if [ " $BACKEND_CAPTURED " = "0" ] ; then
if [ " $BUILD_FROM_SOURCE " != "1" ] ; then
echo " ⚠️ Could not capture from live server, building from source..."
fi
BACKEND_DOCKERFILE = " $WORK_DIR /Dockerfile.backend "
cat > " $BACKEND_DOCKERFILE " <<'BACKENDFILE'
2026-04-09 20:42:09 +02:00
FROM rust:1.93-trixie as builder
2026-02-01 13:24:03 +00:00
WORKDIR /build
COPY core ./core
2026-05-13 22:59:55 -04:00
COPY scripts ./scripts
COPY image-recipe/configs ./image-recipe/configs
2026-02-01 13:24:03 +00:00
RUN cd core && cargo build --release --bin archipelago
BACKENDFILE
2026-05-13 22:59:55 -04:00
BACKEND_IMAGE = "localhost/archipelago-backend:iso"
if $CONTAINER_CMD build --platform $CONTAINER_PLATFORM -t " $BACKEND_IMAGE " -f " $BACKEND_DOCKERFILE " " $SCRIPT_DIR /.. " ; then
2026-02-03 21:43:33 +00:00
echo " Extracting backend binary..."
2026-05-13 22:59:55 -04:00
BACKEND_CONTAINER = $( $CONTAINER_CMD create --platform $CONTAINER_PLATFORM " $BACKEND_IMAGE " )
2026-02-03 21:43:33 +00:00
$CONTAINER_CMD cp " $BACKEND_CONTAINER :/build/core/target/release/archipelago " " $ARCH_DIR /bin/ " && \
echo " ✅ Backend binary built ( $( du -h " $ARCH_DIR /bin/archipelago " | cut -f1) ) "
$CONTAINER_CMD rm " $BACKEND_CONTAINER "
else
echo " ❌ Backend build failed and server capture failed"
exit 1
fi
fi
2026-04-07 14:40:33 +01:00
# Extract NostrVPN binary from container image (native system service, not a container app)
2026-04-09 21:32:08 +02:00
# NOTE: The container image must be built against Debian 13's GLIBC (2.40).
# If built against a newer GLIBC, the binary will fail at runtime.
# Rebuild with: FROM debian:13 AS builder
2026-04-07 14:40:33 +01:00
echo " Extracting NostrVPN binary..."
2026-04-11 09:33:10 -04:00
_NVPN_IMG = " ${ NOSTR_VPN_IMAGE :- git .tx1138.com/lfg2025/nostr-vpn : v0 .3.7 } "
2026-04-09 20:42:09 +02:00
NVPN_IMAGE_ID = " $( $CONTAINER_CMD images -q " $_NVPN_IMG " 2>/dev/null) "
if [ -z " $NVPN_IMAGE_ID " ] ; then
$CONTAINER_CMD pull " $_NVPN_IMG " 2>/dev/null || true
2026-04-07 14:40:33 +01:00
fi
2026-04-09 20:42:09 +02:00
NVPN_CONTAINER = $( $CONTAINER_CMD create " $_NVPN_IMG " 2>/dev/null) || true
2026-04-07 14:40:33 +01:00
if [ -n " $NVPN_CONTAINER " ] ; then
$CONTAINER_CMD cp " $NVPN_CONTAINER :/usr/local/bin/nvpn " " $ARCH_DIR /bin/nvpn " 2>/dev/null && \
chmod +x " $ARCH_DIR /bin/nvpn " && \
echo " ✅ NostrVPN binary extracted ( $( du -h " $ARCH_DIR /bin/nvpn " | cut -f1) ) "
$CONTAINER_CMD rm " $NVPN_CONTAINER " 2>/dev/null || true
2026-04-09 21:32:08 +02:00
# Check GLIBC compatibility — Debian 13 (Trixie) has GLIBC 2.40
2026-04-09 20:42:09 +02:00
if [ -f " $ARCH_DIR /bin/nvpn " ] ; then
NVPN_GLIBC = $( objdump -T " $ARCH_DIR /bin/nvpn " 2>/dev/null | grep -oP 'GLIBC_\K[0-9.]+' | sort -V | tail -1)
if [ -n " $NVPN_GLIBC " ] ; then
2026-04-09 21:32:08 +02:00
# Compare: if required GLIBC > 2.40, warn
if printf '%s\n' "2.40" " $NVPN_GLIBC " | sort -V | tail -1 | grep -qv " ^2\.40 $" ; then
echo " ⚠ WARNING: nvpn binary requires GLIBC $NVPN_GLIBC but Debian 13 has 2.40 "
echo " ⚠ The nvpn daemon will fail at runtime. Rebuild the container against Debian 13."
2026-04-09 20:42:09 +02:00
echo " ⚠ VPN invite/status will still work via Rust backend config.toml fallback."
else
2026-04-09 21:32:08 +02:00
echo " ✅ nvpn GLIBC compatibility OK (requires $NVPN_GLIBC , target has 2.40) "
2026-04-09 20:42:09 +02:00
fi
fi
fi
2026-04-07 14:40:33 +01:00
else
echo " ⚠ NostrVPN image not available — nvpn binary will be missing"
fi
2026-04-08 15:06:27 +02:00
# Extract nostr-rs-relay binary from container image (native system service for VPN signaling)
echo " Extracting nostr-rs-relay binary..."
2026-04-11 09:33:10 -04:00
RELAY_IMAGE = " $( $CONTAINER_CMD images -q git.tx1138.com/lfg2025/nostr-rs-relay:0.9.0 2>/dev/null) "
2026-04-08 15:06:27 +02:00
if [ -z " $RELAY_IMAGE " ] ; then
2026-04-11 09:33:10 -04:00
$CONTAINER_CMD pull git.tx1138.com/lfg2025/nostr-rs-relay:0.9.0 2>/dev/null || true
2026-04-08 15:06:27 +02:00
fi
2026-04-11 09:33:10 -04:00
RELAY_CONTAINER = $( $CONTAINER_CMD create git.tx1138.com/lfg2025/nostr-rs-relay:0.9.0 2>/dev/null) || true
2026-04-08 15:06:27 +02:00
if [ -n " $RELAY_CONTAINER " ] ; then
$CONTAINER_CMD cp " $RELAY_CONTAINER :/usr/local/bin/nostr-rs-relay " " $ARCH_DIR /bin/nostr-rs-relay " 2>/dev/null && \
chmod +x " $ARCH_DIR /bin/nostr-rs-relay " && \
echo " ✅ nostr-rs-relay binary extracted ( $( du -h " $ARCH_DIR /bin/nostr-rs-relay " | cut -f1) ) "
$CONTAINER_CMD rm " $RELAY_CONTAINER " 2>/dev/null || true
else
echo " ⚠ nostr-rs-relay image not available — relay binary will be missing"
fi
2026-04-08 13:15:09 +02:00
# Copy WireGuard helper script
if [ -f " $WORK_DIR /archipelago-wg " ] ; then
cp " $WORK_DIR /archipelago-wg " " $ARCH_DIR /bin/archipelago-wg "
chmod +x " $ARCH_DIR /bin/archipelago-wg "
echo " ✅ WireGuard helper script included"
fi
2026-04-07 14:40:33 +01:00
# Copy NostrVPN UI dashboard for nginx serving
if [ -d " $SCRIPT_DIR /../docker/nostr-vpn-ui " ] ; then
mkdir -p " $ARCH_DIR /web-ui/nostr-vpn "
cp " $SCRIPT_DIR /../docker/nostr-vpn-ui/index.html " " $ARCH_DIR /web-ui/nostr-vpn/ "
echo " ✅ NostrVPN UI dashboard included"
fi
2026-02-03 21:43:33 +00:00
# Capture web UI from live server
if [ " $BUILD_FROM_SOURCE " = "1" ] ; then
echo " Building web UI from source..."
2026-02-01 13:24:03 +00:00
else
2026-02-03 21:43:33 +00:00
echo " Capturing web UI from live server..."
fi
mkdir -p " $ARCH_DIR /web-ui "
# Try to get from live server first (unless BUILD_FROM_SOURCE=1)
WEBUI_CAPTURED = 0
if [ " $BUILD_FROM_SOURCE " != "1" ] ; then
2026-02-17 15:03:34 +00:00
# Direct copy from local filesystem (when running on target with sudo)
if [ -d "/opt/archipelago/web-ui" ] && [ " $( ls -A /opt/archipelago/web-ui 2>/dev/null) " ] ; then
cp -r /opt/archipelago/web-ui/* " $ARCH_DIR /web-ui/ "
echo " ✅ Web UI captured from local system ( $( du -sh " $ARCH_DIR /web-ui " | cut -f1) ) "
WEBUI_CAPTURED = 1
fi
# Remote copy via rsync if local failed
if [ " $WEBUI_CAPTURED " = "0" ] && [ " $DEV_SERVER " != "localhost" ] && [ " $DEV_SERVER " != "127.0.0.1" ] ; then
2026-02-03 21:43:33 +00:00
if rsync -az " $DEV_SERVER :/opt/archipelago/web-ui/ " " $ARCH_DIR /web-ui/ " 2>/dev/null && [ " $( ls -A " $ARCH_DIR /web-ui " ) " ] ; then
echo " ✅ Web UI captured from remote server ( $( du -sh " $ARCH_DIR /web-ui " | cut -f1) ) "
WEBUI_CAPTURED = 1
fi
2026-02-01 13:24:03 +00:00
fi
2026-02-01 05:42:05 +00:00
fi
2026-02-03 21:43:33 +00:00
if [ " $WEBUI_CAPTURED " = "0" ] ; then
if [ " $BUILD_FROM_SOURCE " != "1" ] ; then
echo " ⚠️ Could not capture from live server, building from source..."
2026-02-01 13:24:03 +00:00
fi
2026-02-03 21:43:33 +00:00
cd " $SCRIPT_DIR /../neode-ui "
2026-04-11 13:01:10 -04:00
echo " Installing frontend dependencies..."
npm ci --prefer-offline 2>& 1 | tail -3
2026-02-03 21:43:33 +00:00
if npm run build 2>& 1 | tail -5; then
if [ -d " $SCRIPT_DIR /../web/dist/neode-ui " ] ; then
echo " Including web UI from web/dist/neode-ui..."
cp -r " $SCRIPT_DIR /../web/dist/neode-ui/ " * " $ARCH_DIR /web-ui/ "
echo " ✅ Web UI built ( $( du -sh " $ARCH_DIR /web-ui " | cut -f1) ) "
fi
2026-02-01 13:24:03 +00:00
else
2026-02-03 21:43:33 +00:00
echo " ⚠️ Web UI build failed"
# Try to use existing build
if [ -d " $SCRIPT_DIR /../web/dist/neode-ui " ] ; then
echo " Using existing web UI build..."
cp -r " $SCRIPT_DIR /../web/dist/neode-ui/ " * " $ARCH_DIR /web-ui/ "
elif [ -d " $SCRIPT_DIR /../neode-ui/dist " ] ; then
echo " Using neode-ui/dist..."
cp -r " $SCRIPT_DIR /../neode-ui/dist/ " * " $ARCH_DIR /web-ui/ "
else
echo " ❌ No web UI available"
exit 1
fi
2026-02-01 13:24:03 +00:00
fi
2026-02-03 21:43:33 +00:00
cd " $SCRIPT_DIR "
2026-02-01 05:42:05 +00:00
fi
2026-04-02 01:28:11 +01:00
# Include AIUI web app (Claude chat interface)
AIUI_INCLUDED = 0
2026-04-23 13:21:49 -04:00
# Search multiple locations for a pre-built AIUI app.
# demo/aiui is the canonical AIUI bundle checked into the repo and is
# tried first so ISO builds on a fresh clone work without needing any
# external AIUI checkout.
2026-04-02 01:28:11 +01:00
for AIUI_DIR in \
2026-04-23 13:21:49 -04:00
" $SCRIPT_DIR /../demo/aiui " \
2026-04-02 01:28:11 +01:00
" $SCRIPT_DIR /../../AIUI/packages/app/dist " \
" $HOME /AIUI/packages/app/dist " \
"/home/archipelago/AIUI/packages/app/dist" \
"/opt/archipelago/web-ui/aiui" \
"/home/archipelago/archy/AIUI/packages/app/dist" ; do
if [ -d " $AIUI_DIR " ] && [ -f " $AIUI_DIR /index.html " ] ; then
echo " Including AIUI from $AIUI_DIR ... "
mkdir -p " $ARCH_DIR /web-ui/aiui "
2026-04-02 13:04:10 +01:00
# Use rsync to handle same-file (CI workspace == /opt/archipelago) gracefully
if command -v rsync >/dev/null 2>& 1; then
rsync -a " $AIUI_DIR / " " $ARCH_DIR /web-ui/aiui/ "
else
cp -r " $AIUI_DIR / " * " $ARCH_DIR /web-ui/aiui/ " 2>/dev/null || true
fi
2026-04-02 01:28:11 +01:00
echo " ✅ AIUI included ( $( du -sh " $ARCH_DIR /web-ui/aiui " | cut -f1) ) "
AIUI_INCLUDED = 1
break
fi
done
if [ " $AIUI_INCLUDED " = "0" ] ; then
echo " ⚠️ AIUI not found — build it first:"
echo " cd ~/AIUI/packages/app && VITE_BASE_PATH=/aiui/ npx vite build"
2026-04-23 13:21:49 -04:00
echo " Searched: demo/aiui, ~/AIUI, /home/archipelago/AIUI, /opt/archipelago/web-ui/aiui"
2026-04-02 01:28:11 +01:00
fi
2026-02-01 05:42:05 +00:00
# Copy app manifests
if [ -d " $SCRIPT_DIR /../apps " ] ; then
echo " Including app manifests..."
cp -r " $SCRIPT_DIR /../apps " " $ARCH_DIR / "
fi
feat: custom boot branding, MBR fix, Plymouth theme, CI smoke tests
Boot fix:
- Ship proven Debian Live MBR (4552) as branding/isohdpfx.bin — the
ISOLINUX package MBR (33ed) doesn't boot on all hardware. This was
the root cause of "machine doesn't pick up the USB".
Branding:
- Custom GRUB background: pixel-art floating island (1024x574)
- Archipelago pixel-art logo for Plymouth boot splash
- GRUB theme: dark background, orange selected item, no broken font refs
- Plymouth theme: script-based with progress bar, LUKS prompt support
- Plymouth + splash added to target rootfs packages
- GRUB theme installed on both installer ISO and target system
- Serial console (ttyS0) added to kernel params for QEMU debugging
CI improvements:
- Smoke test step: mounts ISO, verifies all critical files, checks
initrd has live-boot, confirms boot=live in grub.cfg. Fails build
before copying to Builds if any check fails.
Dev workflow:
- dev-branding.sh: extract ISO, swap branding, repackage, boot in QEMU
(~10 seconds vs 20 min full rebuild)
- generate-grub-background.py: procedural cyberpunk background generator
- generate-plymouth-logo.py: procedural logo generator
- Improved test-iso-qemu.sh: --bios/--nographic flags, serial logging
Build:
- Simplified live-boot install (clean chroot, no complex fallbacks)
- Static branding images preferred, generators as fallback
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 22:26:56 +00:00
# Copy Plymouth theme files for installation on target
PLYMOUTH_SRC = " $SCRIPT_DIR /branding/plymouth-theme "
if [ -d " $PLYMOUTH_SRC " ] ; then
mkdir -p " $ARCH_DIR /plymouth-theme "
cp " $PLYMOUTH_SRC / " * " $ARCH_DIR /plymouth-theme/ "
echo " Included Plymouth theme"
fi
2026-02-01 05:42:05 +00:00
# =============================================================================
# STEP 3b: Bundle container images for offline installation
# =============================================================================
echo ""
2026-03-10 23:29:05 +00:00
if [ " $UNBUNDLED " = "1" ] ; then
2026-03-26 09:12:16 +00:00
echo "📦 Step 3b: Bundling core containers only (UNBUNDLED mode)"
echo " Optional apps will be downloaded on-demand from the Marketplace after install."
2026-04-10 04:01:35 -04:00
# Marker file: first-boot-containers.sh checks this to skip app creation
touch " $ARCH_DIR /.unbundled "
2026-03-10 23:29:05 +00:00
IMAGES_DIR = " $ARCH_DIR /container-images "
2026-04-11 13:01:10 -04:00
# Clean stale images from previous builds (e.g. bundled build tars leaking into unbundled)
rm -rf " $IMAGES_DIR "
2026-03-10 23:29:05 +00:00
mkdir -p " $IMAGES_DIR "
2026-03-26 09:12:16 +00:00
# FileBrowser is a core dependency (powers the Cloud file manager) — always bundle it
2026-03-26 14:06:21 +00:00
CORE_IMAGE = " ${ FILEBROWSER_IMAGE } "
2026-03-26 09:12:16 +00:00
CORE_FILE = "filebrowser.tar"
if [ -f " $IMAGES_DIR / $CORE_FILE " ] ; then
echo " ✅ Using cached: $CORE_FILE "
else
echo " Pulling $CORE_IMAGE ( $CONTAINER_PLATFORM )... "
2026-05-06 09:23:57 -04:00
if container_pull " $CORE_IMAGE " ; then
2026-03-26 09:12:16 +00:00
$CONTAINER_CMD save " $CORE_IMAGE " -o " $IMAGES_DIR / $CORE_FILE " 2>/dev/null && \
echo " ✅ Saved core: $CORE_FILE ( $( du -h " $IMAGES_DIR / $CORE_FILE " | cut -f1) ) " || \
echo " ⚠️ Failed to save $CORE_IMAGE "
else
echo " ⚠️ Failed to pull $CORE_IMAGE — Cloud will not work until installed "
fi
fi
2026-03-10 23:29:05 +00:00
else
2026-02-01 05:42:05 +00:00
echo "📦 Step 3b: Bundling container images for offline use..."
IMAGES_DIR = " $ARCH_DIR /container-images "
mkdir -p " $IMAGES_DIR "
2026-02-14 16:44:20 +00:00
# When DEV_SERVER is set (and not localhost), try to capture images from live server
# so the ISO includes the same set as the dev server (including custom UIs: bitcoin-ui, lnd-ui).
IMAGES_CAPTURED_FROM_SERVER = 0
if [ -n " $DEV_SERVER " ] && [ " $DEV_SERVER " != "localhost" ] && [ " $DEV_SERVER " != "127.0.0.1" ] ; then
echo " Capturing container images from live server ( $DEV_SERVER )... "
2026-03-09 17:09:59 +00:00
# Patterns match against `podman images` repository names (not container names)
2026-04-02 18:20:52 +01:00
CAPTURE_PATTERNS = "bitcoin-ui bitcoinknots lnd lnd-ui electrs-ui filebrowser mempool backend frontend electrs tailscale homeassistant home-assistant btcpayserver nbxplorer postgres alpine-tor nostr-rs-relay strfry fedimintd gatewayd dwn-server vaultwarden searxng mariadb valkey nginx-alpine portainer nginx-proxy-manager adguard"
2026-02-14 16:44:20 +00:00
REMOTE_TMP = " /tmp/archipelago-image-capture- $$ "
2026-03-21 01:11:05 +00:00
SAVED_LIST = $( ssh " $DEV_SERVER " " mkdir -p $REMOTE_TMP && for p in $CAPTURE_PATTERNS ; do img=\$(podman images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null | grep -i \"\$p\" | head -1); [ -n \"\$img\" ] && podman save -o \" $REMOTE_TMP /\$p.tar\" \"\$img\" 2>/dev/null && echo \"\$p\"; done " 2>/dev/null) || true
2026-02-14 16:44:20 +00:00
for p in $SAVED_LIST ; do
if [ -n " $p " ] && scp " $DEV_SERVER : $REMOTE_TMP / $p .tar " " $IMAGES_DIR / $p .tar " 2>/dev/null; then
echo " ✅ Captured from server: $p .tar "
IMAGES_CAPTURED_FROM_SERVER = 1
fi
done
ssh " $DEV_SERVER " " rm -rf $REMOTE_TMP " 2>/dev/null || true
if [ " $IMAGES_CAPTURED_FROM_SERVER " = "0" ] ; then
echo " ⚠️ No images captured from server, will use registry pull fallback"
fi
fi
# Define images to bundle for fallback (when not from server or missing). Includes filebrowser.
# bitcoin-ui and lnd-ui are custom and normally captured from server or built separately.
2026-03-09 17:09:59 +00:00
# Alpha: core Bitcoin/Lightning stack + essential apps. Others pulled on-demand from Marketplace.
2026-02-01 05:42:05 +00:00
CONTAINER_IMAGES = "
2026-03-26 14:06:21 +00:00
${ BITCOIN_KNOTS_IMAGE } bitcoin-knots.tar
${ LND_IMAGE } lnd.tar
${ HOMEASSISTANT_IMAGE } homeassistant.tar
${ BTCPAY_IMAGE } btcpayserver.tar
${ NBXPLORER_IMAGE } nbxplorer.tar
${ POSTGRES_IMAGE } postgres-btcpay.tar
${ MEMPOOL_BACKEND_IMAGE } mempool-backend.tar
${ MEMPOOL_WEB_IMAGE } mempool-frontend.tar
${ ELECTRUMX_IMAGE } electrumx.tar
${ MARIADB_IMAGE } mariadb-mempool.tar
${ FEDIMINT_IMAGE } fedimint.tar
${ FEDIMINT_GATEWAY_IMAGE } fedimint-gateway.tar
${ FILEBROWSER_IMAGE } filebrowser.tar
${ ALPINE_TOR_IMAGE } alpine-tor.tar
${ NGINX_ALPINE_IMAGE } nginx-alpine.tar
${ DWN_SERVER_IMAGE } dwn-server.tar
${ GRAFANA_IMAGE } grafana.tar
${ UPTIME_KUMA_IMAGE } uptime-kuma.tar
${ VAULTWARDEN_IMAGE } vaultwarden.tar
${ SEARXNG_IMAGE } searxng.tar
${ PORTAINER_IMAGE } portainer.tar
${ TAILSCALE_IMAGE } tailscale.tar
${ JELLYFIN_IMAGE } jellyfin.tar
${ PHOTOPRISM_IMAGE } photoprism.tar
${ NEXTCLOUD_IMAGE } nextcloud.tar
${ NPM_IMAGE } nginx-proxy-manager.tar
${ ONLYOFFICE_IMAGE } onlyoffice.tar
${ ADGUARDHOME_IMAGE } adguardhome.tar
2026-02-01 05:42:05 +00:00
"
2026-03-12 00:19:30 +00:00
# Pull and save each image (force target arch) only if not already present
2026-02-01 05:42:05 +00:00
echo " $CONTAINER_IMAGES " | while read -r image filename; do
[ -z " $image " ] && continue
tarpath = " $IMAGES_DIR / $filename "
2026-03-10 23:29:05 +00:00
2026-02-01 05:42:05 +00:00
if [ -f " $tarpath " ] ; then
echo " ✅ Using cached: $filename "
else
2026-03-12 00:19:30 +00:00
echo " Pulling $image ( $CONTAINER_PLATFORM )... "
2026-05-06 09:23:57 -04:00
if container_pull " $image " ; then
2026-02-01 05:42:05 +00:00
echo " Saving $filename ... "
2026-03-06 13:00:28 +00:00
if $CONTAINER_CMD save " $image " -o " $tarpath " 2>/dev/null; then
echo " ✅ Saved: $( du -h " $tarpath " | cut -f1) "
else
echo " ⚠️ Failed to save $image (zstd/format issue) - skipping "
rm -f " $tarpath "
fi
2026-02-01 05:42:05 +00:00
else
echo " ⚠️ Failed to pull $image - skipping "
fi
fi
done
2026-03-10 23:29:05 +00:00
fi # end UNBUNDLED check
2026-02-01 05:42:05 +00:00
# Create first-boot service to load images into Podman
echo " Creating first-boot image loader service..."
cat > " $WORK_DIR /archipelago-load-images.service " <<'LOADSERVICE'
[ Unit]
Description = Load Archipelago Container Images
After = network.target podman.service
ConditionPathExists = /opt/archipelago/container-images
ConditionPathExists = !/var/lib/archipelago/.images-loaded
[ Service]
Type = oneshot
ExecStart = /opt/archipelago/scripts/load-container-images.sh
ExecStartPost = /usr/bin/touch /var/lib/archipelago/.images-loaded
RemainAfterExit = yes
[ Install]
WantedBy = multi-user.target
LOADSERVICE
cat > " $WORK_DIR /load-container-images.sh " <<'LOADSCRIPT'
#!/bin/bash
# Load pre-bundled container images into Podman
IMAGES_DIR = "/opt/archipelago/container-images"
LOG_FILE = "/var/log/archipelago-images.log"
echo " $( date) : Starting container image load " >> " $LOG_FILE "
if [ ! -d " $IMAGES_DIR " ] ; then
echo " $( date) : No images directory found " >> " $LOG_FILE "
exit 0
fi
for tarfile in " $IMAGES_DIR " /*.tar; do
if [ -f " $tarfile " ] ; then
echo " $( date) : Loading $( basename " $tarfile " ) ... " >> " $LOG_FILE "
podman load -i " $tarfile " >> " $LOG_FILE " 2>& 1 && \
echo " $( date) : Successfully loaded $( basename " $tarfile " ) " >> " $LOG_FILE " || \
echo " $( date) : Failed to load $( basename " $tarfile " ) " >> " $LOG_FILE "
fi
done
2026-02-17 15:03:34 +00:00
# Ensure archy-net exists for mempool stack (db, api, frontend)
podman network create archy-net 2>/dev/null || true
2026-02-01 05:42:05 +00:00
echo " $( date) : Container image load complete " >> " $LOG_FILE "
echo " $( date) : Available images: " >> " $LOG_FILE "
podman images >> " $LOG_FILE " 2>& 1
LOADSCRIPT
chmod +x " $WORK_DIR /load-container-images.sh "
# Copy scripts to ISO
mkdir -p " $ARCH_DIR /scripts "
cp " $WORK_DIR /load-container-images.sh " " $ARCH_DIR /scripts/ "
cp " $WORK_DIR /archipelago-load-images.service " " $ARCH_DIR /scripts/ "
2026-02-17 15:03:34 +00:00
# Tor setup: copy torrc and create first-boot setup script
mkdir -p " $ARCH_DIR /scripts/tor "
2026-04-28 15:00:58 -04:00
if [ -f " $SCRIPT_DIR /../../scripts/tor/torrc.template " ] ; then
cp " $SCRIPT_DIR /../../scripts/tor/torrc.template " " $ARCH_DIR /scripts/tor/torrc "
2026-02-17 15:03:34 +00:00
fi
echo " Creating first-boot Tor setup service..."
cat > " $WORK_DIR /archipelago-setup-tor.service " <<'TORSERVICE'
[ Unit]
Description = Setup and start Archipelago Tor hidden services
After = archipelago-load-images.service network.target podman.service
ConditionPathExists = /opt/archipelago/scripts/setup-tor.sh
[ Service]
Type = oneshot
ExecStart = /opt/archipelago/scripts/setup-tor.sh
RemainAfterExit = yes
[ Install]
WantedBy = multi-user.target
TORSERVICE
cat > " $WORK_DIR /setup-tor.sh " <<'TORSCRIPT '
#!/bin/bash
fix: add 9 missing apps to ISO build (ISO-01)
CAPTURE_PATTERNS: added photoprism, nextcloud, nginx-proxy-manager,
immich, onlyoffice, adguard, penpot patterns.
CONTAINER_IMAGES: added jellyfin, photoprism, nextcloud,
nginx-proxy-manager, immich-server, postgres-immich, redis-immich,
onlyoffice, adguardhome with pinned versions for fallback pull.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 03:17:12 +00:00
# Setup and start Tor hidden services (autoinstaller first-boot)
# Prefers system Tor (apt package) over container
2026-02-17 15:03:34 +00:00
fix: add 9 missing apps to ISO build (ISO-01)
CAPTURE_PATTERNS: added photoprism, nextcloud, nginx-proxy-manager,
immich, onlyoffice, adguard, penpot patterns.
CONTAINER_IMAGES: added jellyfin, photoprism, nextcloud,
nginx-proxy-manager, immich-server, postgres-immich, redis-immich,
onlyoffice, adguardhome with pinned versions for fallback pull.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 03:17:12 +00:00
ARCHY_TOR_DIR = "/var/lib/archipelago/tor"
fix: overhaul container lifecycle — recovery, health, uninstall, UI state
Container recovery:
- Health monitor: MAX_RESTART_ATTEMPTS 3→10, interval 60s→120s
- Dependency-aware restarts: won't restart services before their deps
- Reset dependent counters when a dependency recovers
- Handle "created" state containers (were invisible to health monitor)
- Added IndeedHub, mempool-api, mysql to tier system
- Crash recovery: podman start timeout 30s→120s with retry
- Podman client: socket timeout 5s→30s, added restart policy
UI state representation:
- Exit code 0 shows "stopped" (gray), not "crashed" (red)
- Exit code 137 shows "killed (OOM)"
- Non-zero exit shows "crashed" (red)
- Added exit_code field to PackageDataEntry
Install/uninstall fixes:
- Install returns error when container doesn't start (was silent success)
- Post-install hooks awaited instead of fire-and-forget tokio::spawn
- Uninstall: graceful rm before force, volume prune, network cleanup
- Uninstall returns error on partial failure (was 200 OK)
Config consistency:
- DB passwords read from /var/lib/archipelago/secrets/ (was hardcoded)
- Bitcoin: added ZMQ ports 28332/28333 for LND block notifications
- IndeedHub port 7777→8190 (was conflicting with strfry)
- Marketplace versions: LND 0.17.4→0.18.4, Mempool 2.5.0→3.0.0
Performance:
- Metrics collector interval 60s→300s (was duplicating health monitor)
- Podman client: proper error propagation instead of unwrap_or_default
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 07:03:57 +01:00
TOR_CONFIG_DIR = "/var/lib/archipelago/tor-config"
fix: add 9 missing apps to ISO build (ISO-01)
CAPTURE_PATTERNS: added photoprism, nextcloud, nginx-proxy-manager,
immich, onlyoffice, adguard, penpot patterns.
CONTAINER_IMAGES: added jellyfin, photoprism, nextcloud,
nginx-proxy-manager, immich-server, postgres-immich, redis-immich,
onlyoffice, adguardhome with pinned versions for fallback pull.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 03:17:12 +00:00
TOR_DIR = "/var/lib/tor"
2026-02-17 15:03:34 +00:00
LOG = "/var/log/archipelago-tor.log"
fix: overhaul container lifecycle — recovery, health, uninstall, UI state
Container recovery:
- Health monitor: MAX_RESTART_ATTEMPTS 3→10, interval 60s→120s
- Dependency-aware restarts: won't restart services before their deps
- Reset dependent counters when a dependency recovers
- Handle "created" state containers (were invisible to health monitor)
- Added IndeedHub, mempool-api, mysql to tier system
- Crash recovery: podman start timeout 30s→120s with retry
- Podman client: socket timeout 5s→30s, added restart policy
UI state representation:
- Exit code 0 shows "stopped" (gray), not "crashed" (red)
- Exit code 137 shows "killed (OOM)"
- Non-zero exit shows "crashed" (red)
- Added exit_code field to PackageDataEntry
Install/uninstall fixes:
- Install returns error when container doesn't start (was silent success)
- Post-install hooks awaited instead of fire-and-forget tokio::spawn
- Uninstall: graceful rm before force, volume prune, network cleanup
- Uninstall returns error on partial failure (was 200 OK)
Config consistency:
- DB passwords read from /var/lib/archipelago/secrets/ (was hardcoded)
- Bitcoin: added ZMQ ports 28332/28333 for LND block notifications
- IndeedHub port 7777→8190 (was conflicting with strfry)
- Marketplace versions: LND 0.17.4→0.18.4, Mempool 2.5.0→3.0.0
Performance:
- Metrics collector interval 60s→300s (was duplicating health monitor)
- Podman client: proper error propagation instead of unwrap_or_default
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 07:03:57 +01:00
mkdir -p " $ARCHY_TOR_DIR " " $TOR_CONFIG_DIR "
fix: add 9 missing apps to ISO build (ISO-01)
CAPTURE_PATTERNS: added photoprism, nextcloud, nginx-proxy-manager,
immich, onlyoffice, adguard, penpot patterns.
CONTAINER_IMAGES: added jellyfin, photoprism, nextcloud,
nginx-proxy-manager, immich-server, postgres-immich, redis-immich,
onlyoffice, adguardhome with pinned versions for fallback pull.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 03:17:12 +00:00
# Write services.json for the backend to read
2026-03-30 16:35:06 +01:00
cat > " $ARCHY_TOR_DIR /services.json " <<TORJSON
2026-03-28 23:41:40 +00:00
{
"services" : [
{ "name" : "archipelago" , "local_port" : 80, "enabled" : true} ,
{ "name" : "bitcoin" , "local_port" : 8333, "enabled" : true} ,
{ "name" : "electrumx" , "local_port" : 50001, "enabled" : true} ,
{ "name" : "lnd" , "local_port" : 9735, "enabled" : true} ,
{ "name" : "btcpay" , "local_port" : 23000, "enabled" : true} ,
{ "name" : "mempool" , "local_port" : 4080, "enabled" : true} ,
{ "name" : "fedimint" , "local_port" : 8175, "enabled" : true}
]
}
TORJSON
echo "services.json created"
fix: overhaul container lifecycle — recovery, health, uninstall, UI state
Container recovery:
- Health monitor: MAX_RESTART_ATTEMPTS 3→10, interval 60s→120s
- Dependency-aware restarts: won't restart services before their deps
- Reset dependent counters when a dependency recovers
- Handle "created" state containers (were invisible to health monitor)
- Added IndeedHub, mempool-api, mysql to tier system
- Crash recovery: podman start timeout 30s→120s with retry
- Podman client: socket timeout 5s→30s, added restart policy
UI state representation:
- Exit code 0 shows "stopped" (gray), not "crashed" (red)
- Exit code 137 shows "killed (OOM)"
- Non-zero exit shows "crashed" (red)
- Added exit_code field to PackageDataEntry
Install/uninstall fixes:
- Install returns error when container doesn't start (was silent success)
- Post-install hooks awaited instead of fire-and-forget tokio::spawn
- Uninstall: graceful rm before force, volume prune, network cleanup
- Uninstall returns error on partial failure (was 200 OK)
Config consistency:
- DB passwords read from /var/lib/archipelago/secrets/ (was hardcoded)
- Bitcoin: added ZMQ ports 28332/28333 for LND block notifications
- IndeedHub port 7777→8190 (was conflicting with strfry)
- Marketplace versions: LND 0.17.4→0.18.4, Mempool 2.5.0→3.0.0
Performance:
- Metrics collector interval 60s→300s (was duplicating health monitor)
- Podman client: proper error propagation instead of unwrap_or_default
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 07:03:57 +01:00
# Backend reads from tor-config/, not tor/
cp " $ARCHY_TOR_DIR /services.json " " $TOR_CONFIG_DIR /services.json "
chown -R archipelago:archipelago " $TOR_CONFIG_DIR " 2>/dev/null || true
fix: add 9 missing apps to ISO build (ISO-01)
CAPTURE_PATTERNS: added photoprism, nextcloud, nginx-proxy-manager,
immich, onlyoffice, adguard, penpot patterns.
CONTAINER_IMAGES: added jellyfin, photoprism, nextcloud,
nginx-proxy-manager, immich-server, postgres-immich, redis-immich,
onlyoffice, adguardhome with pinned versions for fallback pull.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 03:17:12 +00:00
# Generate torrc — use /var/lib/tor/ for hidden services (AppArmor-safe)
cat > /etc/tor/torrc <<TORRC
fix: overhaul container lifecycle — recovery, health, uninstall, UI state
Container recovery:
- Health monitor: MAX_RESTART_ATTEMPTS 3→10, interval 60s→120s
- Dependency-aware restarts: won't restart services before their deps
- Reset dependent counters when a dependency recovers
- Handle "created" state containers (were invisible to health monitor)
- Added IndeedHub, mempool-api, mysql to tier system
- Crash recovery: podman start timeout 30s→120s with retry
- Podman client: socket timeout 5s→30s, added restart policy
UI state representation:
- Exit code 0 shows "stopped" (gray), not "crashed" (red)
- Exit code 137 shows "killed (OOM)"
- Non-zero exit shows "crashed" (red)
- Added exit_code field to PackageDataEntry
Install/uninstall fixes:
- Install returns error when container doesn't start (was silent success)
- Post-install hooks awaited instead of fire-and-forget tokio::spawn
- Uninstall: graceful rm before force, volume prune, network cleanup
- Uninstall returns error on partial failure (was 200 OK)
Config consistency:
- DB passwords read from /var/lib/archipelago/secrets/ (was hardcoded)
- Bitcoin: added ZMQ ports 28332/28333 for LND block notifications
- IndeedHub port 7777→8190 (was conflicting with strfry)
- Marketplace versions: LND 0.17.4→0.18.4, Mempool 2.5.0→3.0.0
Performance:
- Metrics collector interval 60s→300s (was duplicating health monitor)
- Podman client: proper error propagation instead of unwrap_or_default
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 07:03:57 +01:00
SocksPort 0.0.0.0:9050
SocksPolicy accept 10.89.0.0/16
SocksPolicy accept 127.0.0.0/8
SocksPolicy reject *
# ControlPort disabled for security
fix: add 9 missing apps to ISO build (ISO-01)
CAPTURE_PATTERNS: added photoprism, nextcloud, nginx-proxy-manager,
immich, onlyoffice, adguard, penpot patterns.
CONTAINER_IMAGES: added jellyfin, photoprism, nextcloud,
nginx-proxy-manager, immich-server, postgres-immich, redis-immich,
onlyoffice, adguardhome with pinned versions for fallback pull.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 03:17:12 +00:00
HiddenServiceDir $TOR_DIR /hidden_service_archipelago
HiddenServicePort 80 127.0.0.1:80
HiddenServiceDir $TOR_DIR /hidden_service_bitcoin
HiddenServicePort 8333 127.0.0.1:8333
2026-04-02 16:15:04 +01:00
HiddenServicePort 8332 127.0.0.1:8332
fix: add 9 missing apps to ISO build (ISO-01)
CAPTURE_PATTERNS: added photoprism, nextcloud, nginx-proxy-manager,
immich, onlyoffice, adguard, penpot patterns.
CONTAINER_IMAGES: added jellyfin, photoprism, nextcloud,
nginx-proxy-manager, immich-server, postgres-immich, redis-immich,
onlyoffice, adguardhome with pinned versions for fallback pull.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 03:17:12 +00:00
2026-03-16 12:58:35 +00:00
HiddenServiceDir $TOR_DIR /hidden_service_electrumx
fix: add 9 missing apps to ISO build (ISO-01)
CAPTURE_PATTERNS: added photoprism, nextcloud, nginx-proxy-manager,
immich, onlyoffice, adguard, penpot patterns.
CONTAINER_IMAGES: added jellyfin, photoprism, nextcloud,
nginx-proxy-manager, immich-server, postgres-immich, redis-immich,
onlyoffice, adguardhome with pinned versions for fallback pull.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 03:17:12 +00:00
HiddenServicePort 50001 127.0.0.1:50001
HiddenServiceDir $TOR_DIR /hidden_service_lnd
HiddenServicePort 9735 127.0.0.1:9735
security+feat: v1.3.0 — pentest remediation, container reliability, UI overhaul
Security (33 pentest findings addressed):
- CRITICAL: backend binds 127.0.0.1, path traversal in tor.rs/dwn fixed
- HIGH: federation requires signatures, XSS login redirect, RBAC viewer restricted
- HIGH: tar slip prevention, S3 SSRF validation, backup ID validation
- MEDIUM: remember-me random secret, TOTP session rotation, password re-auth
- LOW: CSP unsafe-inline removed, CORS dev-only, onion/webhook validation
Container reliability:
- Memory limits on all 37 containers (OOM prevention)
- Exited vs stopped state distinction with health-aware status badges
- Crash recovery coordination (no more restart cascade)
- User-stopped tracking survives reboots
- Tiered boot recovery (databases → core → services → apps)
UI:
- Wallet TransactionsModal, health-aware app status badges
- Restart button on containers, exited/crashed red state
- Mesh view overhaul, glass button updates, BaseModal/ToggleSwitch
- Apps sticky header removed, dev faucet, mutable mock wallet
Infrastructure:
- LND REST port 8080 exposed over Tor (LND Connect fix)
- Nginx cookie_session fix, deploy script Tor config updated
- Dev environment: podman auto-start, boot mode simulation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 12:44:31 +00:00
HiddenServicePort 8080 127.0.0.1:8080
fix: add 9 missing apps to ISO build (ISO-01)
CAPTURE_PATTERNS: added photoprism, nextcloud, nginx-proxy-manager,
immich, onlyoffice, adguard, penpot patterns.
CONTAINER_IMAGES: added jellyfin, photoprism, nextcloud,
nginx-proxy-manager, immich-server, postgres-immich, redis-immich,
onlyoffice, adguardhome with pinned versions for fallback pull.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 03:17:12 +00:00
HiddenServiceDir $TOR_DIR /hidden_service_btcpay
HiddenServicePort 23000 127.0.0.1:23000
HiddenServiceDir $TOR_DIR /hidden_service_mempool
HiddenServicePort 4080 127.0.0.1:4080
HiddenServiceDir $TOR_DIR /hidden_service_fedimint
HiddenServicePort 8175 127.0.0.1:8175
2026-04-08 20:27:38 +02:00
HiddenServiceDir $TOR_DIR /hidden_service_relay
HiddenServicePort 7777 127.0.0.1:7777
fix: add 9 missing apps to ISO build (ISO-01)
CAPTURE_PATTERNS: added photoprism, nextcloud, nginx-proxy-manager,
immich, onlyoffice, adguard, penpot patterns.
CONTAINER_IMAGES: added jellyfin, photoprism, nextcloud,
nginx-proxy-manager, immich-server, postgres-immich, redis-immich,
onlyoffice, adguardhome with pinned versions for fallback pull.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 03:17:12 +00:00
TORRC
2026-03-28 02:40:39 +00:00
# Create hidden service dirs with correct ownership and permissions (700, not 750)
# Tor refuses to start if permissions are too permissive
2026-04-08 20:27:38 +02:00
for svc in archipelago bitcoin electrumx lnd btcpay mempool fedimint relay; do
2026-03-28 02:40:39 +00:00
mkdir -p " $TOR_DIR /hidden_service_ $svc "
chown debian-tor:debian-tor " $TOR_DIR /hidden_service_ $svc "
chmod 700 " $TOR_DIR /hidden_service_ $svc "
done
fix: add 9 missing apps to ISO build (ISO-01)
CAPTURE_PATTERNS: added photoprism, nextcloud, nginx-proxy-manager,
immich, onlyoffice, adguard, penpot patterns.
CONTAINER_IMAGES: added jellyfin, photoprism, nextcloud,
nginx-proxy-manager, immich-server, postgres-immich, redis-immich,
onlyoffice, adguardhome with pinned versions for fallback pull.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 03:17:12 +00:00
# Prefer system Tor (installed via apt)
if command -v tor >/dev/null 2>& 1; then
echo " $( date) : Using system Tor daemon " >> " $LOG "
systemctl enable tor 2>/dev/null
systemctl restart tor@default 2>/dev/null
else
# Fallback: use container
echo " $( date) : System Tor not found, using container " >> " $LOG "
DOCKER = podman
command -v podman >/dev/null 2>& 1 || DOCKER = docker
for c in $( sudo $DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -E 'archy-tor|^tor$' ) ; do
[ -n " $c " ] && sudo $DOCKER stop " $c " 2>/dev/null; sudo $DOCKER rm -f " $c " 2>/dev/null
done
if ! sudo $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q archy-tor; then
sudo $DOCKER run -d --name archy-tor --restart unless-stopped --network host \
-v " $TOR_DIR : $TOR_DIR " \
--entrypoint tor \
2026-03-26 14:06:21 +00:00
${ ALPINE_TOR_IMAGE } \
fix: add 9 missing apps to ISO build (ISO-01)
CAPTURE_PATTERNS: added photoprism, nextcloud, nginx-proxy-manager,
immich, onlyoffice, adguard, penpot patterns.
CONTAINER_IMAGES: added jellyfin, photoprism, nextcloud,
nginx-proxy-manager, immich-server, postgres-immich, redis-immich,
onlyoffice, adguardhome with pinned versions for fallback pull.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 03:17:12 +00:00
-f /etc/tor/torrc >> " $LOG " 2>& 1
2026-02-17 15:03:34 +00:00
echo " $( date) : Tor container started " >> " $LOG "
fi
fi
fix: add 9 missing apps to ISO build (ISO-01)
CAPTURE_PATTERNS: added photoprism, nextcloud, nginx-proxy-manager,
immich, onlyoffice, adguard, penpot patterns.
CONTAINER_IMAGES: added jellyfin, photoprism, nextcloud,
nginx-proxy-manager, immich-server, postgres-immich, redis-immich,
onlyoffice, adguardhome with pinned versions for fallback pull.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 03:17:12 +00:00
2026-02-17 15:03:34 +00:00
# Wait for Tor to create hostname files (~30-60s), then chmod so archipelago user can read
for i in 1 2 3 4 5 6 7 8 9 10; do
sleep 6
if [ -f " $TOR_DIR /hidden_service_archipelago/hostname " ] ; then
fix: add 9 missing apps to ISO build (ISO-01)
CAPTURE_PATTERNS: added photoprism, nextcloud, nginx-proxy-manager,
immich, onlyoffice, adguard, penpot patterns.
CONTAINER_IMAGES: added jellyfin, photoprism, nextcloud,
nginx-proxy-manager, immich-server, postgres-immich, redis-immich,
onlyoffice, adguardhome with pinned versions for fallback pull.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 03:17:12 +00:00
chmod 750 " $TOR_DIR " /hidden_service_*/ 2>/dev/null || true
2026-02-17 15:03:34 +00:00
for f in " $TOR_DIR " /hidden_service_*/hostname; do
fix: add 9 missing apps to ISO build (ISO-01)
CAPTURE_PATTERNS: added photoprism, nextcloud, nginx-proxy-manager,
immich, onlyoffice, adguard, penpot patterns.
CONTAINER_IMAGES: added jellyfin, photoprism, nextcloud,
nginx-proxy-manager, immich-server, postgres-immich, redis-immich,
onlyoffice, adguardhome with pinned versions for fallback pull.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 03:17:12 +00:00
[ -f " $f " ] && chmod 640 " $f " && echo " $( date) : chmod hostname $f " >> " $LOG "
2026-02-17 15:03:34 +00:00
done
echo " $( date) : Tor hostname files readable by archipelago " >> " $LOG "
break
fi
done
fix: overhaul container lifecycle — recovery, health, uninstall, UI state
Container recovery:
- Health monitor: MAX_RESTART_ATTEMPTS 3→10, interval 60s→120s
- Dependency-aware restarts: won't restart services before their deps
- Reset dependent counters when a dependency recovers
- Handle "created" state containers (were invisible to health monitor)
- Added IndeedHub, mempool-api, mysql to tier system
- Crash recovery: podman start timeout 30s→120s with retry
- Podman client: socket timeout 5s→30s, added restart policy
UI state representation:
- Exit code 0 shows "stopped" (gray), not "crashed" (red)
- Exit code 137 shows "killed (OOM)"
- Non-zero exit shows "crashed" (red)
- Added exit_code field to PackageDataEntry
Install/uninstall fixes:
- Install returns error when container doesn't start (was silent success)
- Post-install hooks awaited instead of fire-and-forget tokio::spawn
- Uninstall: graceful rm before force, volume prune, network cleanup
- Uninstall returns error on partial failure (was 200 OK)
Config consistency:
- DB passwords read from /var/lib/archipelago/secrets/ (was hardcoded)
- Bitcoin: added ZMQ ports 28332/28333 for LND block notifications
- IndeedHub port 7777→8190 (was conflicting with strfry)
- Marketplace versions: LND 0.17.4→0.18.4, Mempool 2.5.0→3.0.0
Performance:
- Metrics collector interval 60s→300s (was duplicating health monitor)
- Podman client: proper error propagation instead of unwrap_or_default
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 07:03:57 +01:00
# Sync hostnames to backend-readable directory
HOSTNAMES_DIR = "/var/lib/archipelago/tor-hostnames"
mkdir -p " $HOSTNAMES_DIR "
2026-04-08 20:27:38 +02:00
for svc in archipelago bitcoin electrumx lnd btcpay mempool fedimint relay; do
fix: overhaul container lifecycle — recovery, health, uninstall, UI state
Container recovery:
- Health monitor: MAX_RESTART_ATTEMPTS 3→10, interval 60s→120s
- Dependency-aware restarts: won't restart services before their deps
- Reset dependent counters when a dependency recovers
- Handle "created" state containers (were invisible to health monitor)
- Added IndeedHub, mempool-api, mysql to tier system
- Crash recovery: podman start timeout 30s→120s with retry
- Podman client: socket timeout 5s→30s, added restart policy
UI state representation:
- Exit code 0 shows "stopped" (gray), not "crashed" (red)
- Exit code 137 shows "killed (OOM)"
- Non-zero exit shows "crashed" (red)
- Added exit_code field to PackageDataEntry
Install/uninstall fixes:
- Install returns error when container doesn't start (was silent success)
- Post-install hooks awaited instead of fire-and-forget tokio::spawn
- Uninstall: graceful rm before force, volume prune, network cleanup
- Uninstall returns error on partial failure (was 200 OK)
Config consistency:
- DB passwords read from /var/lib/archipelago/secrets/ (was hardcoded)
- Bitcoin: added ZMQ ports 28332/28333 for LND block notifications
- IndeedHub port 7777→8190 (was conflicting with strfry)
- Marketplace versions: LND 0.17.4→0.18.4, Mempool 2.5.0→3.0.0
Performance:
- Metrics collector interval 60s→300s (was duplicating health monitor)
- Podman client: proper error propagation instead of unwrap_or_default
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 07:03:57 +01:00
if [ -f " $TOR_DIR /hidden_service_ ${ svc } /hostname " ] ; then
cp " $TOR_DIR /hidden_service_ ${ svc } /hostname " " $HOSTNAMES_DIR / $svc "
echo " $( date) : Synced hostname: $svc " >> " $LOG "
fi
done
chown -R archipelago:archipelago " $HOSTNAMES_DIR " 2>/dev/null || true
echo " $( date) : Hostnames synced: $( ls $HOSTNAMES_DIR 2>/dev/null | tr '\n' ' ' ) " >> " $LOG "
2026-02-17 15:03:34 +00:00
TORSCRIPT
chmod +x " $WORK_DIR /setup-tor.sh "
cp " $WORK_DIR /setup-tor.sh " " $ARCH_DIR /scripts/ "
cp " $WORK_DIR /archipelago-setup-tor.service " " $ARCH_DIR /scripts/ "
2026-02-25 18:04:41 +00:00
# First-boot: create core containers (bitcoin, mempool, btcpay, lnd, fedimint, homeassistant)
2026-04-01 22:31:45 +01:00
# Both bundled and unbundled builds use the full first-boot script.
# Unbundled mode pulls images from registry; bundled mode loads from tarballs.
if false && [ " $UNBUNDLED " = "1" ] ; then
2026-03-26 09:12:16 +00:00
echo " Creating minimal first-boot service (UNBUNDLED: FileBrowser only)..."
2026-04-01 22:31:45 +01:00
# DISABLED: minimal script doesn't create UI sidecars or write app configs.
# The full first-boot-containers.sh handles both bundled and unbundled modes.
2026-03-26 09:12:16 +00:00
cat > " $WORK_DIR /first-boot-containers-unbundled.sh " <<'FBUNBUNDLED'
#!/bin/bash
# Minimal first-boot: create FileBrowser container only (unbundled ISO)
set -e
LOG = "/var/log/archipelago-first-boot.log"
echo " [ $( date) ] Starting minimal first-boot (unbundled)... " >> " $LOG "
fix: production onboarding, CI tests, container security, keyboard nav
Install & Onboarding:
- Remove DEV_MODE=true from production ISO service file (auto-created
users, skipped password setup)
- Auto-install no longer overwrites rootfs service file with bad template
- Login.vue always checks auth.isSetup — shows password creation form
on fresh install without requiring dev build flag
- Deploy image-versions.sh to /opt/archipelago/scripts/ on installed nodes
- First-boot-containers sources image-versions.sh, runs podman as
archipelago user (rootless), enables linger + podman.socket
- Correct volume ownership (100000:100000 for rootless UID mapping)
Container Security:
- FileBrowser: add --cap-add=DAC_OVERRIDE for rootless podman volume access
- FileBrowser: add --read-only, /data volume for database, proper cmd args
- First-boot script matches backend config (security hardening + health check)
CI Pipeline:
- Add vue-tsc type check + vitest run to build-iso.yml (runs every push)
- Add post-install-tests.yml workflow (workflow_dispatch, SSH to target)
- Build report: set +eo pipefail, fix rootfs path, add || true guards
- Bundle run-post-install-tests.sh into ISO
E2E Test Suite (scripts/run-post-install-tests.sh):
- Phase 1: Install verification (files, services, podman, linger, DEV_MODE check)
- Phase 2: Onboarding flow (auth.isSetup, auth.setup, login, DID, complete)
- Phase 3: Container lifecycle (install 3 apps via package.install RPC,
verify running, stop, verify stopped, restart, verify running, health)
- Phase 4: Log verification (first-boot log, diagnostics, journal errors)
- Correct package.install params: {"id", "dockerImage"}
Frontend:
- Fix backdrop-filter tab-switch bug (keep animations paused during rebuild)
- Dashboard glitch animations paused during tab-hidden
- Gamepad nav: auto-focus first container on route change
- Tab roving: Left/Right on role="tab" cycles and activates sibling tabs
- ContainerApps: data-controller-launch on running app cards
- 515 tests passing (fixed 30 broken, added 19 new keyboard nav tests)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 16:16:57 +00:00
# Source image versions (provides $FILEBROWSER_IMAGE etc.)
for f in /opt/archipelago/scripts/image-versions.sh /home/archipelago/archy/scripts/image-versions.sh; do
if [ -f " $f " ] ; then
source " $f "
echo " [ $( date) ] Sourced image versions from $f " >> " $LOG "
break
fi
done
if [ -z " $FILEBROWSER_IMAGE " ] ; then
echo " [ $( date) ] ERROR: FILEBROWSER_IMAGE not set — image-versions.sh missing or incomplete " >> " $LOG "
exit 1
fi
# Create Cloud storage directories (as root, then fix ownership for rootless podman)
2026-03-26 09:12:16 +00:00
mkdir -p /var/lib/archipelago/filebrowser
fix: production onboarding, CI tests, container security, keyboard nav
Install & Onboarding:
- Remove DEV_MODE=true from production ISO service file (auto-created
users, skipped password setup)
- Auto-install no longer overwrites rootfs service file with bad template
- Login.vue always checks auth.isSetup — shows password creation form
on fresh install without requiring dev build flag
- Deploy image-versions.sh to /opt/archipelago/scripts/ on installed nodes
- First-boot-containers sources image-versions.sh, runs podman as
archipelago user (rootless), enables linger + podman.socket
- Correct volume ownership (100000:100000 for rootless UID mapping)
Container Security:
- FileBrowser: add --cap-add=DAC_OVERRIDE for rootless podman volume access
- FileBrowser: add --read-only, /data volume for database, proper cmd args
- First-boot script matches backend config (security hardening + health check)
CI Pipeline:
- Add vue-tsc type check + vitest run to build-iso.yml (runs every push)
- Add post-install-tests.yml workflow (workflow_dispatch, SSH to target)
- Build report: set +eo pipefail, fix rootfs path, add || true guards
- Bundle run-post-install-tests.sh into ISO
E2E Test Suite (scripts/run-post-install-tests.sh):
- Phase 1: Install verification (files, services, podman, linger, DEV_MODE check)
- Phase 2: Onboarding flow (auth.isSetup, auth.setup, login, DID, complete)
- Phase 3: Container lifecycle (install 3 apps via package.install RPC,
verify running, stop, verify stopped, restart, verify running, health)
- Phase 4: Log verification (first-boot log, diagnostics, journal errors)
- Correct package.install params: {"id", "dockerImage"}
Frontend:
- Fix backdrop-filter tab-switch bug (keep animations paused during rebuild)
- Dashboard glitch animations paused during tab-hidden
- Gamepad nav: auto-focus first container on route change
- Tab roving: Left/Right on role="tab" cycles and activates sibling tabs
- ContainerApps: data-controller-launch on running app cards
- 515 tests passing (fixed 30 broken, added 19 new keyboard nav tests)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 16:16:57 +00:00
mkdir -p /var/lib/archipelago/filebrowser-data
2026-03-26 09:12:16 +00:00
mkdir -p /var/lib/archipelago/data/cloud/{ Documents,Photos,Music,Videos,Downloads}
fix: production onboarding, CI tests, container security, keyboard nav
Install & Onboarding:
- Remove DEV_MODE=true from production ISO service file (auto-created
users, skipped password setup)
- Auto-install no longer overwrites rootfs service file with bad template
- Login.vue always checks auth.isSetup — shows password creation form
on fresh install without requiring dev build flag
- Deploy image-versions.sh to /opt/archipelago/scripts/ on installed nodes
- First-boot-containers sources image-versions.sh, runs podman as
archipelago user (rootless), enables linger + podman.socket
- Correct volume ownership (100000:100000 for rootless UID mapping)
Container Security:
- FileBrowser: add --cap-add=DAC_OVERRIDE for rootless podman volume access
- FileBrowser: add --read-only, /data volume for database, proper cmd args
- First-boot script matches backend config (security hardening + health check)
CI Pipeline:
- Add vue-tsc type check + vitest run to build-iso.yml (runs every push)
- Add post-install-tests.yml workflow (workflow_dispatch, SSH to target)
- Build report: set +eo pipefail, fix rootfs path, add || true guards
- Bundle run-post-install-tests.sh into ISO
E2E Test Suite (scripts/run-post-install-tests.sh):
- Phase 1: Install verification (files, services, podman, linger, DEV_MODE check)
- Phase 2: Onboarding flow (auth.isSetup, auth.setup, login, DID, complete)
- Phase 3: Container lifecycle (install 3 apps via package.install RPC,
verify running, stop, verify stopped, restart, verify running, health)
- Phase 4: Log verification (first-boot log, diagnostics, journal errors)
- Correct package.install params: {"id", "dockerImage"}
Frontend:
- Fix backdrop-filter tab-switch bug (keep animations paused during rebuild)
- Dashboard glitch animations paused during tab-hidden
- Gamepad nav: auto-focus first container on route change
- Tab roving: Left/Right on role="tab" cycles and activates sibling tabs
- ContainerApps: data-controller-launch on running app cards
- 515 tests passing (fixed 30 broken, added 19 new keyboard nav tests)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 16:16:57 +00:00
# Container UID 0 maps to host UID 100000 under rootless podman (subuid mapping)
chown -R 100000:100000 /var/lib/archipelago/filebrowser
chown -R 100000:100000 /var/lib/archipelago/filebrowser-data
chown -R 100000:100000 /var/lib/archipelago/data
# Enable linger so rootless podman containers survive logout
loginctl enable-linger archipelago 2>/dev/null || true
2026-04-02 15:16:24 +01:00
# Enable podman-restart so containers with --restart=unless-stopped auto-start on boot
runuser -u archipelago -- bash -c 'export XDG_RUNTIME_DIR=/run/user/1000 && systemctl --user enable podman-restart.service' 2>>" $LOG " || true
fix: production onboarding, CI tests, container security, keyboard nav
Install & Onboarding:
- Remove DEV_MODE=true from production ISO service file (auto-created
users, skipped password setup)
- Auto-install no longer overwrites rootfs service file with bad template
- Login.vue always checks auth.isSetup — shows password creation form
on fresh install without requiring dev build flag
- Deploy image-versions.sh to /opt/archipelago/scripts/ on installed nodes
- First-boot-containers sources image-versions.sh, runs podman as
archipelago user (rootless), enables linger + podman.socket
- Correct volume ownership (100000:100000 for rootless UID mapping)
Container Security:
- FileBrowser: add --cap-add=DAC_OVERRIDE for rootless podman volume access
- FileBrowser: add --read-only, /data volume for database, proper cmd args
- First-boot script matches backend config (security hardening + health check)
CI Pipeline:
- Add vue-tsc type check + vitest run to build-iso.yml (runs every push)
- Add post-install-tests.yml workflow (workflow_dispatch, SSH to target)
- Build report: set +eo pipefail, fix rootfs path, add || true guards
- Bundle run-post-install-tests.sh into ISO
E2E Test Suite (scripts/run-post-install-tests.sh):
- Phase 1: Install verification (files, services, podman, linger, DEV_MODE check)
- Phase 2: Onboarding flow (auth.isSetup, auth.setup, login, DID, complete)
- Phase 3: Container lifecycle (install 3 apps via package.install RPC,
verify running, stop, verify stopped, restart, verify running, health)
- Phase 4: Log verification (first-boot log, diagnostics, journal errors)
- Correct package.install params: {"id", "dockerImage"}
Frontend:
- Fix backdrop-filter tab-switch bug (keep animations paused during rebuild)
- Dashboard glitch animations paused during tab-hidden
- Gamepad nav: auto-focus first container on route change
- Tab roving: Left/Right on role="tab" cycles and activates sibling tabs
- ContainerApps: data-controller-launch on running app cards
- 515 tests passing (fixed 30 broken, added 19 new keyboard nav tests)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 16:16:57 +00:00
# Ensure podman socket is active for archipelago user
runuser -u archipelago -- bash -c 'export XDG_RUNTIME_DIR=/run/user/1000 && systemctl --user enable --now podman.socket' 2>>" $LOG " || true
# Create FileBrowser container as archipelago user (rootless podman)
2026-03-29 21:56:38 +01:00
# Generate random FileBrowser password and store for auto-login
FB_PASS_DIR = "/var/lib/archipelago/secrets/filebrowser"
mkdir -p " $FB_PASS_DIR "
if [ ! -f " $FB_PASS_DIR /password " ] ; then
head -c 24 /dev/urandom | base64 | tr -d '/+=' | head -c 24 > " $FB_PASS_DIR /password "
chmod 600 " $FB_PASS_DIR /password "
chown 1000:1000 " $FB_PASS_DIR /password "
fi
fix: production onboarding, CI tests, container security, keyboard nav
Install & Onboarding:
- Remove DEV_MODE=true from production ISO service file (auto-created
users, skipped password setup)
- Auto-install no longer overwrites rootfs service file with bad template
- Login.vue always checks auth.isSetup — shows password creation form
on fresh install without requiring dev build flag
- Deploy image-versions.sh to /opt/archipelago/scripts/ on installed nodes
- First-boot-containers sources image-versions.sh, runs podman as
archipelago user (rootless), enables linger + podman.socket
- Correct volume ownership (100000:100000 for rootless UID mapping)
Container Security:
- FileBrowser: add --cap-add=DAC_OVERRIDE for rootless podman volume access
- FileBrowser: add --read-only, /data volume for database, proper cmd args
- First-boot script matches backend config (security hardening + health check)
CI Pipeline:
- Add vue-tsc type check + vitest run to build-iso.yml (runs every push)
- Add post-install-tests.yml workflow (workflow_dispatch, SSH to target)
- Build report: set +eo pipefail, fix rootfs path, add || true guards
- Bundle run-post-install-tests.sh into ISO
E2E Test Suite (scripts/run-post-install-tests.sh):
- Phase 1: Install verification (files, services, podman, linger, DEV_MODE check)
- Phase 2: Onboarding flow (auth.isSetup, auth.setup, login, DID, complete)
- Phase 3: Container lifecycle (install 3 apps via package.install RPC,
verify running, stop, verify stopped, restart, verify running, health)
- Phase 4: Log verification (first-boot log, diagnostics, journal errors)
- Correct package.install params: {"id", "dockerImage"}
Frontend:
- Fix backdrop-filter tab-switch bug (keep animations paused during rebuild)
- Dashboard glitch animations paused during tab-hidden
- Gamepad nav: auto-focus first container on route change
- Tab roving: Left/Right on role="tab" cycles and activates sibling tabs
- ContainerApps: data-controller-launch on running app cards
- 515 tests passing (fixed 30 broken, added 19 new keyboard nav tests)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 16:16:57 +00:00
if ! runuser -u archipelago -- bash -c 'export XDG_RUNTIME_DIR=/run/user/1000 && podman ps -a --format "{{.Names}}"' 2>/dev/null | grep -q filebrowser; then
echo " [ $( date) ] Creating FileBrowser container ( $FILEBROWSER_IMAGE )... " >> " $LOG "
runuser -u archipelago -- bash -c " export XDG_RUNTIME_DIR=/run/user/1000 && podman run -d --name filebrowser --restart unless-stopped \
--cap-drop= ALL \
--cap-add= DAC_OVERRIDE \
2026-03-27 17:17:18 +00:00
--cap-add= NET_BIND_SERVICE \
fix: production onboarding, CI tests, container security, keyboard nav
Install & Onboarding:
- Remove DEV_MODE=true from production ISO service file (auto-created
users, skipped password setup)
- Auto-install no longer overwrites rootfs service file with bad template
- Login.vue always checks auth.isSetup — shows password creation form
on fresh install without requiring dev build flag
- Deploy image-versions.sh to /opt/archipelago/scripts/ on installed nodes
- First-boot-containers sources image-versions.sh, runs podman as
archipelago user (rootless), enables linger + podman.socket
- Correct volume ownership (100000:100000 for rootless UID mapping)
Container Security:
- FileBrowser: add --cap-add=DAC_OVERRIDE for rootless podman volume access
- FileBrowser: add --read-only, /data volume for database, proper cmd args
- First-boot script matches backend config (security hardening + health check)
CI Pipeline:
- Add vue-tsc type check + vitest run to build-iso.yml (runs every push)
- Add post-install-tests.yml workflow (workflow_dispatch, SSH to target)
- Build report: set +eo pipefail, fix rootfs path, add || true guards
- Bundle run-post-install-tests.sh into ISO
E2E Test Suite (scripts/run-post-install-tests.sh):
- Phase 1: Install verification (files, services, podman, linger, DEV_MODE check)
- Phase 2: Onboarding flow (auth.isSetup, auth.setup, login, DID, complete)
- Phase 3: Container lifecycle (install 3 apps via package.install RPC,
verify running, stop, verify stopped, restart, verify running, health)
- Phase 4: Log verification (first-boot log, diagnostics, journal errors)
- Correct package.install params: {"id", "dockerImage"}
Frontend:
- Fix backdrop-filter tab-switch bug (keep animations paused during rebuild)
- Dashboard glitch animations paused during tab-hidden
- Gamepad nav: auto-focus first container on route change
- Tab roving: Left/Right on role="tab" cycles and activates sibling tabs
- ContainerApps: data-controller-launch on running app cards
- 515 tests passing (fixed 30 broken, added 19 new keyboard nav tests)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 16:16:57 +00:00
--security-opt= no-new-privileges:true \
--read-only \
--tmpfs= /tmp:rw,noexec,nosuid,size= 64m \
--health-cmd= 'curl -sf http://localhost:80/ || exit 1' \
2026-03-26 09:12:16 +00:00
--health-interval= 30s --health-timeout= 5s --health-retries= 3 \
--memory= 256m \
-p 8083:80 \
-v /var/lib/archipelago/filebrowser:/srv \
fix: production onboarding, CI tests, container security, keyboard nav
Install & Onboarding:
- Remove DEV_MODE=true from production ISO service file (auto-created
users, skipped password setup)
- Auto-install no longer overwrites rootfs service file with bad template
- Login.vue always checks auth.isSetup — shows password creation form
on fresh install without requiring dev build flag
- Deploy image-versions.sh to /opt/archipelago/scripts/ on installed nodes
- First-boot-containers sources image-versions.sh, runs podman as
archipelago user (rootless), enables linger + podman.socket
- Correct volume ownership (100000:100000 for rootless UID mapping)
Container Security:
- FileBrowser: add --cap-add=DAC_OVERRIDE for rootless podman volume access
- FileBrowser: add --read-only, /data volume for database, proper cmd args
- First-boot script matches backend config (security hardening + health check)
CI Pipeline:
- Add vue-tsc type check + vitest run to build-iso.yml (runs every push)
- Add post-install-tests.yml workflow (workflow_dispatch, SSH to target)
- Build report: set +eo pipefail, fix rootfs path, add || true guards
- Bundle run-post-install-tests.sh into ISO
E2E Test Suite (scripts/run-post-install-tests.sh):
- Phase 1: Install verification (files, services, podman, linger, DEV_MODE check)
- Phase 2: Onboarding flow (auth.isSetup, auth.setup, login, DID, complete)
- Phase 3: Container lifecycle (install 3 apps via package.install RPC,
verify running, stop, verify stopped, restart, verify running, health)
- Phase 4: Log verification (first-boot log, diagnostics, journal errors)
- Correct package.install params: {"id", "dockerImage"}
Frontend:
- Fix backdrop-filter tab-switch bug (keep animations paused during rebuild)
- Dashboard glitch animations paused during tab-hidden
- Gamepad nav: auto-focus first container on route change
- Tab roving: Left/Right on role="tab" cycles and activates sibling tabs
- ContainerApps: data-controller-launch on running app cards
- 515 tests passing (fixed 30 broken, added 19 new keyboard nav tests)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 16:16:57 +00:00
-v /var/lib/archipelago/filebrowser-data:/data \
-v /var/lib/archipelago/data/cloud:/srv/cloud \
$FILEBROWSER_IMAGE \
--database= /data/database.db --root= /srv --address= 0.0.0.0 --port= 80" 2>>" $LOG " && \
2026-03-26 09:12:16 +00:00
echo " [ $( date) ] FileBrowser created successfully " >> " $LOG " || \
echo " [ $( date) ] WARNING: FileBrowser creation failed " >> " $LOG "
2026-03-29 21:56:38 +01:00
# Set FileBrowser password to match the stored random password
sleep 5
FB_PASS = $( cat " $FB_PASS_DIR /password " 2>/dev/null || echo "admin" )
runuser -u archipelago -- bash -c " export XDG_RUNTIME_DIR=/run/user/1000 && podman exec filebrowser filebrowser users update admin --password ' $FB_PASS ' --database /data/database.db " 2>>" $LOG " && \
echo " [ $( date) ] FileBrowser admin password set " >> " $LOG " || \
echo " [ $( date) ] WARNING: Could not set FileBrowser password " >> " $LOG "
2026-03-26 09:12:16 +00:00
fi
echo " [ $( date) ] Minimal first-boot complete " >> " $LOG "
FBUNBUNDLED
chmod +x " $WORK_DIR /first-boot-containers-unbundled.sh "
cp " $WORK_DIR /first-boot-containers-unbundled.sh " " $ARCH_DIR /scripts/first-boot-containers.sh "
2026-04-01 10:38:41 +01:00
# Copy shared script library (TUI animations for installer, shared utils)
2026-04-28 15:00:58 -04:00
if [ -d " $SCRIPT_DIR /../../scripts/lib " ] ; then
2026-04-01 10:38:41 +01:00
mkdir -p " $ARCH_DIR /scripts/lib "
2026-04-28 15:00:58 -04:00
cp " $SCRIPT_DIR /../../scripts/lib/ " *.sh " $ARCH_DIR /scripts/lib/ " 2>/dev/null || true
2026-04-01 10:38:41 +01:00
echo " Copied scripts/lib/ ( $( ls " $ARCH_DIR /scripts/lib/ " 2>/dev/null | wc -l) files) "
fi
2026-03-26 09:12:16 +00:00
cat > " $WORK_DIR /archipelago-first-boot-containers.service " <<'FBCSERVICE'
[ Unit]
Description = Create core Archipelago containers on first boot
2026-03-31 18:31:00 +01:00
After = archipelago-load-images.service archipelago-setup-tor.service network-online.target podman.service
Wants = archipelago-load-images.service
2026-03-26 09:12:16 +00:00
ConditionPathExists = /opt/archipelago/scripts/first-boot-containers.sh
ConditionPathExists = !/var/lib/archipelago/.first-boot-containers-done
[ Service]
Type = oneshot
2026-03-31 18:31:00 +01:00
TimeoutStartSec = 900
2026-03-26 09:12:16 +00:00
ExecStart = /opt/archipelago/scripts/first-boot-containers.sh
ExecStartPost = /usr/bin/touch /var/lib/archipelago/.first-boot-containers-done
RemainAfterExit = yes
[ Install]
WantedBy = multi-user.target
FBCSERVICE
cp " $WORK_DIR /archipelago-first-boot-containers.service " " $ARCH_DIR /scripts/ "
2026-03-10 23:29:05 +00:00
else
echo " Creating first-boot container creation service..."
2026-03-21 03:06:29 +00:00
# Copy shared script library
2026-04-28 15:00:58 -04:00
if [ -d " $SCRIPT_DIR /../../scripts/lib " ] ; then
2026-03-21 03:06:29 +00:00
mkdir -p " $ARCH_DIR /scripts/lib "
2026-04-28 15:00:58 -04:00
cp " $SCRIPT_DIR /../../scripts/lib/ " *.sh " $ARCH_DIR /scripts/lib/ " 2>/dev/null || true
2026-03-21 03:06:29 +00:00
fi
2026-04-28 15:00:58 -04:00
if [ -f " $SCRIPT_DIR /../../scripts/first-boot-containers.sh " ] ; then
cp " $SCRIPT_DIR /../../scripts/first-boot-containers.sh " " $ARCH_DIR /scripts/ "
2026-03-10 23:29:05 +00:00
chmod +x " $ARCH_DIR /scripts/first-boot-containers.sh "
cat > " $WORK_DIR /archipelago-first-boot-containers.service " <<'FBCSERVICE'
2026-02-25 18:04:41 +00:00
[ Unit]
Description = Create core Archipelago containers on first boot
2026-03-31 18:31:00 +01:00
After = archipelago-load-images.service archipelago-setup-tor.service network-online.target podman.service
Wants = archipelago-load-images.service
2026-02-25 18:04:41 +00:00
ConditionPathExists = /opt/archipelago/scripts/first-boot-containers.sh
ConditionPathExists = !/var/lib/archipelago/.first-boot-containers-done
[ Service]
Type = oneshot
2026-03-31 18:31:00 +01:00
TimeoutStartSec = 900
2026-02-25 18:04:41 +00:00
ExecStart = /opt/archipelago/scripts/first-boot-containers.sh
ExecStartPost = /usr/bin/touch /var/lib/archipelago/.first-boot-containers-done
RemainAfterExit = yes
[ Install]
WantedBy = multi-user.target
FBCSERVICE
2026-03-10 23:29:05 +00:00
cp " $WORK_DIR /archipelago-first-boot-containers.service " " $ARCH_DIR /scripts/ "
fi
2026-02-25 18:04:41 +00:00
fi
2026-04-02 16:15:04 +01:00
# Bootstrap node config — new installs use this Bitcoin node during IBD
# so ElectrumX/LND/BTCPay/Mempool work immediately while local chain syncs
# Tries LAN first (fast), falls back to Tor (works from anywhere)
BOOTSTRAP_RPC_PASS = ""
BOOTSTRAP_ONION = ""
if [ -f /var/lib/archipelago/secrets/bitcoin-rpc-password ] ; then
BOOTSTRAP_RPC_PASS = $( cat /var/lib/archipelago/secrets/bitcoin-rpc-password 2>/dev/null)
fi
if [ -f /var/lib/archipelago/tor-hostnames/bitcoin ] ; then
BOOTSTRAP_ONION = $( cat /var/lib/archipelago/tor-hostnames/bitcoin 2>/dev/null)
fi
if [ -n " $BOOTSTRAP_RPC_PASS " ] ; then
DEV_IP = " ${ DEV_SERVER :- 192 .168.1.228 } "
cat > " $ARCH_DIR /bootstrap.conf " <<BSTRAP
# Bootstrap Bitcoin node — used during Initial Block Download
# Services connect here until the local node is fully synced
# First-boot tries LAN, then Tor (works from any network)
BOOTSTRAP_LAN_HOST = ${ DEV_IP }
BOOTSTRAP_ONION = ${ BOOTSTRAP_ONION }
BOOTSTRAP_RPC_USER = archipelago
BOOTSTRAP_RPC_PASS = ${ BOOTSTRAP_RPC_PASS }
BSTRAP
chmod 600 " $ARCH_DIR /bootstrap.conf "
echo " ✅ Bootstrap node config embedded (LAN: ${ DEV_IP } , Tor: ${ BOOTSTRAP_ONION :- none } ) "
else
echo " ⚠ No bootstrap config — no Bitcoin RPC password found on build host"
fi
# Bundle bootstrap switchover script + systemd timer
2026-04-28 15:00:58 -04:00
if [ -f " $SCRIPT_DIR /../../scripts/bootstrap-switchover.sh " ] ; then
cp " $SCRIPT_DIR /../../scripts/bootstrap-switchover.sh " " $ARCH_DIR /scripts/ "
2026-04-02 16:15:04 +01:00
chmod +x " $ARCH_DIR /scripts/bootstrap-switchover.sh "
echo " ✅ Bundled bootstrap switchover script"
fi
2026-03-09 17:09:59 +00:00
# Bundle E2E test script for post-install validation
2026-04-28 15:00:58 -04:00
if [ -f " $SCRIPT_DIR /../../scripts/run-e2e-tests.sh " ] ; then
cp " $SCRIPT_DIR /../../scripts/run-e2e-tests.sh " " $ARCH_DIR /scripts/ "
2026-03-09 17:09:59 +00:00
chmod +x " $ARCH_DIR /scripts/run-e2e-tests.sh "
echo " ✅ Bundled E2E test script for post-install validation"
fi
2026-04-28 15:00:58 -04:00
if [ -f " $SCRIPT_DIR /../../scripts/run-post-install-tests.sh " ] ; then
cp " $SCRIPT_DIR /../../scripts/run-post-install-tests.sh " " $ARCH_DIR /scripts/ "
fix: production onboarding, CI tests, container security, keyboard nav
Install & Onboarding:
- Remove DEV_MODE=true from production ISO service file (auto-created
users, skipped password setup)
- Auto-install no longer overwrites rootfs service file with bad template
- Login.vue always checks auth.isSetup — shows password creation form
on fresh install without requiring dev build flag
- Deploy image-versions.sh to /opt/archipelago/scripts/ on installed nodes
- First-boot-containers sources image-versions.sh, runs podman as
archipelago user (rootless), enables linger + podman.socket
- Correct volume ownership (100000:100000 for rootless UID mapping)
Container Security:
- FileBrowser: add --cap-add=DAC_OVERRIDE for rootless podman volume access
- FileBrowser: add --read-only, /data volume for database, proper cmd args
- First-boot script matches backend config (security hardening + health check)
CI Pipeline:
- Add vue-tsc type check + vitest run to build-iso.yml (runs every push)
- Add post-install-tests.yml workflow (workflow_dispatch, SSH to target)
- Build report: set +eo pipefail, fix rootfs path, add || true guards
- Bundle run-post-install-tests.sh into ISO
E2E Test Suite (scripts/run-post-install-tests.sh):
- Phase 1: Install verification (files, services, podman, linger, DEV_MODE check)
- Phase 2: Onboarding flow (auth.isSetup, auth.setup, login, DID, complete)
- Phase 3: Container lifecycle (install 3 apps via package.install RPC,
verify running, stop, verify stopped, restart, verify running, health)
- Phase 4: Log verification (first-boot log, diagnostics, journal errors)
- Correct package.install params: {"id", "dockerImage"}
Frontend:
- Fix backdrop-filter tab-switch bug (keep animations paused during rebuild)
- Dashboard glitch animations paused during tab-hidden
- Gamepad nav: auto-focus first container on route change
- Tab roving: Left/Right on role="tab" cycles and activates sibling tabs
- ContainerApps: data-controller-launch on running app cards
- 515 tests passing (fixed 30 broken, added 19 new keyboard nav tests)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 16:16:57 +00:00
chmod +x " $ARCH_DIR /scripts/run-post-install-tests.sh "
echo " ✅ Bundled post-install test suite"
fi
2026-03-09 17:09:59 +00:00
2026-03-25 15:52:26 +00:00
# Bundle self-update script and image-versions for update system
2026-04-28 15:00:58 -04:00
if [ -f " $SCRIPT_DIR /../../scripts/self-update.sh " ] ; then
cp " $SCRIPT_DIR /../../scripts/self-update.sh " " $ARCH_DIR /scripts/ "
2026-03-25 15:52:26 +00:00
chmod +x " $ARCH_DIR /scripts/self-update.sh "
echo " ✅ Bundled self-update script"
fi
2026-04-28 15:00:58 -04:00
if [ -f " $SCRIPT_DIR /../../scripts/image-versions.sh " ] ; then
cp " $SCRIPT_DIR /../../scripts/image-versions.sh " " $ARCH_DIR /scripts/ "
2026-03-25 15:52:26 +00:00
echo " ✅ Bundled image-versions.sh"
fi
2026-03-29 17:48:13 +01:00
# Bundle docker UI source files for building custom UIs on first boot
# Always bundle — these are tiny HTML/CSS files, not container images
if true; then
2026-03-10 23:29:05 +00:00
DOCKER_UI_DIR = " $SCRIPT_DIR /../docker "
if [ -d " $DOCKER_UI_DIR " ] ; then
echo " Bundling docker UI source files..."
mkdir -p " $ARCH_DIR /docker "
for ui_dir in bitcoin-ui lnd-ui electrs-ui; do
if [ -d " $DOCKER_UI_DIR / $ui_dir " ] ; then
cp -r " $DOCKER_UI_DIR / $ui_dir " " $ARCH_DIR /docker/ "
echo " ✅ Bundled $ui_dir source "
fi
done
fi
2026-03-08 02:27:58 +00:00
fi
2026-03-10 23:29:05 +00:00
if [ " $UNBUNDLED " = "1" ] ; then
echo " ✅ Unbundled build ready (Tor setup included, no container images)"
else
echo " ✅ Container images bundled (including Tor + first-boot)"
fi
2026-02-01 05:42:05 +00:00
# =============================================================================
# STEP 4: Create auto-installer script
# =============================================================================
echo ""
echo "📦 Step 4: Creating auto-installer..."
cat > " $ARCH_DIR /auto-install.sh " <<'INSTALLER_SCRI PT'
#!/bin/bash
#
# Archipelago Auto-Installer
# Automatically installs to internal disk (StartOS-like experience)
#
set -e
2026-03-29 12:06:19 +01:00
# Log file — verbose command output goes here, TUI stays on console
feat: add build report and first-boot diagnostics
CI build report: checks rootfs contents (nginx, SSL, keyboard, kiosk,
lid config, backend, frontend) and ISO contents after build. Reports
in the Actions log so build issues are immediately visible.
First-boot diagnostics: one-shot systemd service runs 30s after first
boot, logs service status, nginx test, SSL certs, LUKS, podman,
kiosk, console-setup, disk, network, and journal errors to
/var/log/archipelago-first-boot-diag.log. Only runs once (ConditionPathExists).
SSH in and cat the log to debug any fresh install issues.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 20:54:32 +00:00
INSTALL_LOG = "/tmp/archipelago-install.log"
2026-03-29 12:06:19 +01:00
# Run commands quietly: redirect their stdout/stderr to log
# TUI functions (p, step, ok, etc.) print directly to console
run( ) { " $@ " >> " $INSTALL_LOG " 2>& 1; }
runq( ) { " $@ " >>" $INSTALL_LOG " 2>& 1 || true; }
feat: add build report and first-boot diagnostics
CI build report: checks rootfs contents (nginx, SSL, keyboard, kiosk,
lid config, backend, frontend) and ISO contents after build. Reports
in the Actions log so build issues are immediately visible.
First-boot diagnostics: one-shot systemd service runs 30s after first
boot, logs service status, nginx test, SSL certs, LUKS, podman,
kiosk, console-setup, disk, network, and journal errors to
/var/log/archipelago-first-boot-diag.log. Only runs once (ConditionPathExists).
SSH in and cat the log to debug any fresh install issues.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 20:54:32 +00:00
2026-03-21 01:11:05 +00:00
# Detect architecture at install time
case " $( uname -m) " in
x86_64| amd64)
ARCH = "x86_64"
GRUB_TARGET = "x86_64-efi"
GRUB_BIOS_TARGET = "i386-pc"
; ;
aarch64| arm64)
ARCH = "arm64"
GRUB_TARGET = "arm64-efi"
GRUB_BIOS_TARGET = ""
; ;
esac
2026-03-28 23:58:42 +00:00
# Colors — 256-color ANSI (works on Linux fbcon console)
ORANGE = $'\033[38;5;208m'
ORANGE_DIM = $'\033[38;5;130m'
ORANGE_BRIGHT = $'\033[38;5;214m'
2026-03-28 11:31:48 +00:00
RED = $'\033[31m'
GREEN = $'\033[32m'
WHITE = $'\033[1;37m'
2026-03-28 23:58:42 +00:00
DIM = $'\033[38;5;242m'
DIMMER = $'\033[38;5;238m'
2026-03-25 17:03:21 +00:00
NC = $'\033[0m'
2026-03-28 23:58:42 +00:00
BOLD = $'\033[1m'
2026-02-01 05:42:05 +00:00
2026-03-29 15:43:57 +01:00
# Left-justified layout — 2-space indent, no centering
PADS = " "
2026-03-28 23:58:42 +00:00
p( ) { printf "%s%b\n" " $PADS " " $1 " ; }
2026-03-29 15:43:57 +01:00
hrule( ) { local hr = "" ; for i in $( seq 1 48) ; do hr = " ${ hr } ─ " ; done ; p " ${ ORANGE_DIM } ${ hr } ${ NC } " ; }
# Typewriter animation for key text
typewrite( ) {
local text = " $1 " delay = " ${ 2 :- 0 .02 } "
printf "%s" " $PADS "
local i = 0
while [ $i -lt ${# text } ] ; do
printf "%s" " ${ text : $i : 1 } "
i = $(( i + 1 ))
sleep " $delay "
done
printf "\n"
}
2026-03-28 11:31:48 +00:00
2026-03-28 23:58:42 +00:00
# Phase display
2026-03-28 13:39:10 +00:00
STEP = 0
2026-03-28 23:41:40 +00:00
TOTAL_STEPS = 8
2026-03-28 13:39:10 +00:00
step( ) {
STEP = $(( STEP + 1 ))
echo ""
2026-03-28 23:58:42 +00:00
p " ${ ORANGE } [ $STEP / $TOTAL_STEPS ] $1 ${ NC } "
2026-03-28 13:39:10 +00:00
}
2026-03-28 23:58:42 +00:00
ok( ) { p " ${ ORANGE_BRIGHT } ✓ $1 ${ NC } " ; }
warn( ) { p " ${ ORANGE } ⚠ $1 ${ NC } " ; }
fail( ) { p " ${ RED } ✗ $1 ${ NC } " ; }
2026-03-28 13:39:10 +00:00
spinner( ) {
local pid = $1 msg = $2
local frames = '⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'
local i = 0
while kill -0 " $pid " 2>/dev/null; do
2026-03-28 23:58:42 +00:00
printf "\r%s %b%s %s%b" " $PADS " " $ORANGE " " ${ frames : i %10 : 1 } " " $msg " " $NC "
2026-03-28 13:39:10 +00:00
i = $(( i + 1 ))
sleep 0.1
done
2026-03-28 23:58:42 +00:00
printf "\r%s %b✓ %s%b\n" " $PADS " " $ORANGE_BRIGHT " " $msg " " $NC "
2026-03-28 13:39:10 +00:00
}
2026-03-31 21:23:29 +01:00
# Source TUI library for install animations (graceful fallback if missing)
for _tui_path in \
" $BOOT_MEDIA /archipelago/scripts/lib/install-tui.sh " \
"/opt/archipelago/scripts/lib/install-tui.sh" \
2026-04-28 15:00:58 -04:00
" $( dirname " $0 " ) /../../scripts/lib/install-tui.sh " ; do
2026-03-31 21:23:29 +01:00
[ -f " $_tui_path " ] && { source " $_tui_path " 2>/dev/null; break; }
done
if [ " ${ TUI_AVAILABLE :- } " = "1" ] ; then
tui_welcome
tui_enable_progress_spinner
else
clear
echo ""
echo -e " ${ PADS } ${ ORANGE } ▄▀█ █▀▄ █▀▀ █ █ █ █▀█ █▀▀ █ ▄▀█ █▀▀ █▀█ ${ NC } "
echo -e " ${ PADS } ${ ORANGE } █▀█ █▀▄ █ █▀█ █ █▀▀ ██▀ █ █▀█ █ █ █ █ ${ NC } "
echo -e " ${ PADS } ${ ORANGE } ▀ ▀ ▀ ▀ ▀▀▀ ▀ ▀ ▀ ▀ ▀▀▀ ▀▀▀ ▀ ▀ ▀▀▀ ▀▀▀ ${ NC } "
typewrite " $( echo -e " ${ ORANGE_DIM } bitcoin node os ${ NC } " ) " 0.04
echo ""
fi
2026-02-01 05:42:05 +00:00
# Check required tools are present (should be bundled in ISO)
2026-03-28 13:39:10 +00:00
step "Checking tools"
2026-02-01 05:42:05 +00:00
MISSING = ""
command -v parted >/dev/null 2>& 1 || MISSING = " parted $MISSING "
command -v mkfs.vfat >/dev/null 2>& 1 || MISSING = " mkfs.vfat $MISSING "
command -v mkfs.ext4 >/dev/null 2>& 1 || MISSING = " mkfs.ext4 $MISSING "
if [ -n " $MISSING " ] ; then
2026-03-28 13:39:10 +00:00
warn " Installing missing: $MISSING "
2026-02-01 05:42:05 +00:00
if apt-get update -qq >/dev/null 2>& 1; then
2026-03-28 13:39:10 +00:00
apt-get install -y -qq parted dosfstools e2fsprogs >/dev/null 2>& 1 && ok "Tools installed" || {
fail "Failed to install required tools"
2026-02-01 05:42:05 +00:00
exit 1
}
else
2026-03-28 13:39:10 +00:00
fail "No network available and tools not bundled"
2026-02-01 05:42:05 +00:00
exit 1
fi
else
2026-03-28 13:39:10 +00:00
ok "All tools present"
2026-02-01 05:42:05 +00:00
fi
# Find boot media
BOOT_MEDIA = ""
2026-03-27 18:19:49 +00:00
for dev in /run/archiso /cdrom /media/cdrom /run/live/medium /lib/live/mount/medium /mnt/iso; do
2026-02-01 05:42:05 +00:00
if [ -d " $dev /archipelago " ] ; then
BOOT_MEDIA = " $dev "
break
fi
done
if [ -z " $BOOT_MEDIA " ] ; then
echo -e " ${ RED } ❌ Boot media not found ${ NC } "
exit 1
fi
ROOTFS_TAR = " $BOOT_MEDIA /archipelago/rootfs.tar "
if [ ! -f " $ROOTFS_TAR " ] ; then
echo -e " ${ RED } ❌ Root filesystem not found: $ROOTFS_TAR ${ NC } "
exit 1
fi
# Find the boot USB device to exclude it
BOOT_DEV = $( findmnt -n -o SOURCE " $BOOT_MEDIA " 2>/dev/null | sed 's/[0-9]*$//' | sed 's/p[0-9]*$//' )
BOOT_DEV_NAME = $( basename " $BOOT_DEV " 2>/dev/null || echo "" )
2026-03-28 13:39:10 +00:00
step "Detecting disks"
2026-02-01 05:42:05 +00:00
echo ""
# Find internal disk (prefer NVMe, then SATA, skip USB)
TARGET_DISK = ""
TARGET_SIZE = ""
# Check NVMe drives first
for disk in /dev/nvme*n1; do
if [ -b " $disk " ] ; then
disk_name = $( basename " $disk " )
if [ " $disk_name " != " $BOOT_DEV_NAME " ] ; then
size = $( lsblk -dn -o SIZE " $disk " 2>/dev/null)
model = $( cat /sys/block/$disk_name /device/model 2>/dev/null || echo "NVMe SSD" )
echo " Found: $disk ( $size ) - $model "
TARGET_DISK = " $disk "
TARGET_SIZE = " $size "
break
fi
fi
done
# If no NVMe, check SATA drives
if [ -z " $TARGET_DISK " ] ; then
for disk in /dev/sd[ a-z] ; do
if [ -b " $disk " ] ; then
disk_name = $( basename " $disk " )
if [ " $disk_name " != " $BOOT_DEV_NAME " ] ; then
# Skip USB drives (check removable flag)
removable = $( cat /sys/block/$disk_name /removable 2>/dev/null || echo "0" )
if [ " $removable " = "0" ] ; then
size = $( lsblk -dn -o SIZE " $disk " 2>/dev/null)
model = $( cat /sys/block/$disk_name /device/model 2>/dev/null || echo "SATA Drive" )
echo " Found: $disk ( $size ) - $model "
TARGET_DISK = " $disk "
TARGET_SIZE = " $size "
break
fi
fi
fi
done
fi
if [ -z " $TARGET_DISK " ] ; then
echo ""
echo -e " ${ RED } ❌ No suitable internal disk found ${ NC } "
echo " Please ensure an internal drive is connected."
exit 1
fi
2026-03-28 13:39:10 +00:00
ok " $TARGET_DISK ( $TARGET_SIZE ) "
2026-02-01 05:42:05 +00:00
echo ""
2026-03-28 13:39:10 +00:00
hrule
2026-02-01 05:42:05 +00:00
echo ""
2026-03-28 23:58:42 +00:00
p " ${ ORANGE } ⚠ all data on $TARGET_DISK will be erased ${ NC } "
2026-02-01 05:42:05 +00:00
echo ""
2026-03-28 23:58:42 +00:00
p " ${ ORANGE_DIM } press enter to install | ctrl+c to cancel ${ NC } "
2026-03-28 11:31:48 +00:00
read -s
2026-02-01 05:42:05 +00:00
echo ""
# Unmount any existing partitions
umount ${ TARGET_DISK } * 2>/dev/null || true
umount ${ TARGET_DISK } p* 2>/dev/null || true
2026-03-26 09:12:16 +00:00
# Create partition table — dual BIOS+UEFI boot + LUKS2 encrypted data
2026-03-28 13:39:10 +00:00
step "Creating partitions"
2026-02-01 05:42:05 +00:00
parted -s " $TARGET_DISK " mklabel gpt
2026-03-21 01:11:05 +00:00
# Partition 1: 1MB BIOS boot partition (for legacy BIOS GRUB on GPT disks)
parted -s " $TARGET_DISK " mkpart bios_boot 1MiB 2MiB
parted -s " $TARGET_DISK " set 1 bios_grub on
# Partition 2: 512MB EFI System Partition (for UEFI boot)
parted -s " $TARGET_DISK " mkpart efi fat32 2MiB 514MiB
parted -s " $TARGET_DISK " set 2 esp on
2026-03-26 09:12:16 +00:00
# Partition 3: Root filesystem (30GB — system, packages, container runtime)
parted -s " $TARGET_DISK " mkpart root ext4 514MiB 30GiB
# Partition 4: Encrypted data (LUKS2 — Bitcoin data, secrets, app volumes)
parted -s " $TARGET_DISK " mkpart data 30GiB 100%
2026-02-01 05:42:05 +00:00
sleep 2
# Determine partition names
if [ [ " $TARGET_DISK " = = *nvme* ] ] ; then
2026-03-21 01:11:05 +00:00
BIOS_PART = " ${ TARGET_DISK } p1 "
EFI_PART = " ${ TARGET_DISK } p2 "
ROOT_PART = " ${ TARGET_DISK } p3 "
2026-03-26 09:12:16 +00:00
DATA_PART = " ${ TARGET_DISK } p4 "
2026-02-01 05:42:05 +00:00
else
2026-03-21 01:11:05 +00:00
BIOS_PART = " ${ TARGET_DISK } 1 "
EFI_PART = " ${ TARGET_DISK } 2 "
ROOT_PART = " ${ TARGET_DISK } 3 "
2026-03-26 09:12:16 +00:00
DATA_PART = " ${ TARGET_DISK } 4 "
2026-02-01 05:42:05 +00:00
fi
# Format partitions
2026-03-28 13:39:10 +00:00
step "Formatting partitions"
2026-03-25 18:25:01 +00:00
# Zero out the BIOS boot partition to prevent FAT-fs read errors during boot
dd if = /dev/zero of = " $BIOS_PART " bs = 1M count = 1 2>/dev/null || true
2026-03-29 12:06:19 +01:00
run mkfs.vfat -F32 -n EFI " $EFI_PART "
run mkfs.ext4 -F -L archipelago " $ROOT_PART "
2026-02-01 05:42:05 +00:00
2026-03-26 09:12:16 +00:00
# Mount root + extract rootfs (need cryptsetup from rootfs for LUKS)
2026-03-28 13:39:10 +00:00
ok "Partitions created"
echo ""
2026-03-28 23:58:42 +00:00
p " ${ ORANGE_DIM } Mounting filesystems... ${ NC } "
2026-02-01 05:42:05 +00:00
mkdir -p /mnt/target
mount " $ROOT_PART " /mnt/target
mkdir -p /mnt/target/boot/efi
mount " $EFI_PART " /mnt/target/boot/efi
2026-03-28 13:39:10 +00:00
step "Installing system"
2026-03-29 12:06:19 +01:00
run tar -xf " $ROOTFS_TAR " -C /mnt/target
2026-02-01 05:42:05 +00:00
2026-03-26 09:12:16 +00:00
# LUKS2 encryption for data partition
2026-03-28 13:39:10 +00:00
step "Encrypting data partition"
2026-03-26 09:12:16 +00:00
# Generate random 4KB key file
dd if = /dev/urandom of = /mnt/target/root/.luks-archipelago.key bs = 4096 count = 1 2>/dev/null
chmod 600 /mnt/target/root/.luks-archipelago.key
2026-03-26 17:28:08 +00:00
# Load dm_mod kernel module (required for device-mapper / LUKS)
modprobe dm_mod 2>/dev/null || true
modprobe dm_crypt 2>/dev/null || true
# Bind-mount /dev, /proc, /sys so cryptsetup works in chroot
2026-03-26 09:12:16 +00:00
mount --bind /dev /mnt/target/dev
2026-03-26 17:28:08 +00:00
mount --bind /proc /mnt/target/proc
mount --bind /sys /mnt/target/sys
2026-03-26 09:12:16 +00:00
# Detect AES-NI support for cipher selection
if grep -q aes /proc/cpuinfo 2>/dev/null; then
LUKS_CIPHER = "aes-xts-plain64"
echo " AES-NI detected — using AES-256-XTS"
else
LUKS_CIPHER = "xchacha20,aes-adiantum-plain64"
echo " No AES-NI — using ChaCha20-Adiantum"
fi
# Format LUKS2 partition with key file
2026-03-29 12:06:19 +01:00
run chroot /mnt/target cryptsetup luksFormat --type luks2 \
2026-03-26 09:12:16 +00:00
--key-file /root/.luks-archipelago.key \
--cipher " $LUKS_CIPHER " --key-size 512 \
--pbkdf argon2id --batch-mode \
" $DATA_PART "
# Open the LUKS volume
2026-03-29 12:06:19 +01:00
run chroot /mnt/target cryptsetup open --type luks2 \
2026-03-26 09:12:16 +00:00
--key-file /root/.luks-archipelago.key \
" $DATA_PART " archipelago-data
2026-03-26 17:28:08 +00:00
# Unmount chroot bind mounts (will be re-mounted later for grub-install)
2026-03-29 12:06:19 +01:00
runq umount /mnt/target/sys
runq umount /mnt/target/proc
runq umount /mnt/target/dev
2026-03-26 09:12:16 +00:00
# Format the inner filesystem
2026-03-29 12:06:19 +01:00
run mkfs.ext4 -F -L archipelago-data /dev/mapper/archipelago-data
2026-03-26 09:12:16 +00:00
# Mount encrypted partition
mkdir -p /mnt/target/var/lib/archipelago
mount /dev/mapper/archipelago-data /mnt/target/var/lib/archipelago
# Recreate directory structure on encrypted partition
2026-04-08 20:27:38 +02:00
mkdir -p /mnt/target/var/lib/archipelago/{ data,config,containers,secrets,tor,identities,lnd,nostr-relay,nostr-vpn,tor-hostnames,wireguard}
2026-03-29 15:43:57 +01:00
mkdir -p /mnt/target/var/lib/archipelago/containers/storage
2026-03-26 09:12:16 +00:00
mkdir -p /mnt/target/var/lib/archipelago/data/cloud/{ Documents,Photos,Music,Videos,Downloads}
2026-04-08 17:48:38 +02:00
# Copy relay config from rootfs (LUKS mount hides what the Dockerfile put there)
if [ -f /mnt/target/etc/archipelago/nostr-relay-config.toml ] ; then
cp /mnt/target/etc/archipelago/nostr-relay-config.toml /mnt/target/var/lib/archipelago/nostr-relay/config.toml
fi
2026-03-26 09:12:16 +00:00
chown -R 1000:1000 /mnt/target/var/lib/archipelago
echo " ✅ Data partition encrypted with LUKS2 ( $LUKS_CIPHER ) "
chore: release v1.7.45-alpha
Resilience-validated release. Three full sweeps of the new resilience
harness against .228 confirm no shipstoppers.
Big user-visible:
- Bitcoin RPC auth durably correct via host-rendered nginx.conf bind-mount,
replaces fragile post-start exec that failed under restricted-cap rootless
podman ("crun: write cgroup.procs: Permission denied")
- Multi-container stack installs (indeedhub, immich, btcpay, mempool) now
emit phase events at every boundary so the progress bar advances
- Apps no longer vanish from the dashboard mid-install (absent-scanner skips
packages in transitional states)
- Indeedhub fresh installs work end-to-end (was 8500+ restart loop): five
missing env vars (DATABASE_PORT, QUEUE_HOST, QUEUE_PORT,
S3_PRIVATE_BUCKET_NAME, AES_MASTER_SECRET) added to install code
- Tailscale install fixed: --entrypoint string was being passed as a single
shell-line arg; switched to custom_args array
- Catalog cleaned of broken entries (dwn, endurain, ollama removed; nextcloud
restored on docker.io)
- Bitcoin Core update path uses correct image (was looking for nonexistent
lfg2025/bitcoin:28.4)
- ISO installs now allocate swap on the encrypted data partition
Infra:
- New resilience harness (scripts/resilience/) — black-box state-machine
tester, every app × every transition. Run before each release.
Sweep #3 final: PASS 107 / FAIL 12 / SKIP 14. The 12 fails are 1 cosmetic
(homeassistant trusted_hosts), 8 harness/timing false-positives, and 3
non-shipstopper tracked items. Down from 23 in baseline sweep #1.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 12:31:45 -04:00
# Allocate swap space on the encrypted data partition. Without swap, large
# container image builds (immich, indeedhub) and brief memory spikes can
# OOM-kill containers or trigger cgroup cascades. Sized to RAM, capped at
# 8GB (above which swap is rarely useful), floored at 2GB so even
# constrained nodes have headroom. Lives on the LUKS partition so it's
# encrypted at rest.
step "Allocating swap"
RAM_MB = $(( $( awk '/^MemTotal:/ {print $2}' /proc/meminfo) / 1024 ))
SWAP_MB = $RAM_MB
[ " $SWAP_MB " -lt 2048 ] && SWAP_MB = 2048
[ " $SWAP_MB " -gt 8192 ] && SWAP_MB = 8192
SWAPFILE = /mnt/target/var/lib/archipelago/swapfile
echo " Allocating ${ SWAP_MB } MB swap at /var/lib/archipelago/swapfile "
run dd if = /dev/zero of = " $SWAPFILE " bs = 1M count = $SWAP_MB status = none
run chmod 600 " $SWAPFILE "
run mkswap " $SWAPFILE "
echo " ✅ ${ SWAP_MB } MB swap allocated "
2026-03-26 09:12:16 +00:00
# Configure auto-unlock via crypttab (key file on root partition)
2026-03-28 13:39:10 +00:00
step "Configuring system"
2026-03-26 09:12:16 +00:00
DATA_UUID = $( blkid -s UUID -o value " $DATA_PART " )
echo "# LUKS2 encrypted data — auto-unlock with key file" > /mnt/target/etc/crypttab
echo " archipelago-data UUID= $DATA_UUID /root/.luks-archipelago.key luks,discard " >> /mnt/target/etc/crypttab
fix: ISO boot, container installs, VPN, nginx, companion input
- LUKS auto-unlock: initramfs hook + systemd service + nofail fstab
- Rootfs packages: add passt, aardvark-dns, netavark, nftables for Podman 5.x
- nginx: resolver + variable proxy_pass for external domains (DNS at boot)
- Boot: loglevel=0 suppresses kernel warnings, serial console for QEMU
- Container installs: write configs before chown, sudo chown for LUKS volumes
- Container installs: build UI sidecars locally (not from registry) for auth injection
- Bitcoin UI: inject RPC auth from secrets file, --no-cache rebuild
- Secrets: chown to archipelago user in first-boot (backend needs read access)
- Podman: image_copy_tmp_dir for read-only /var/tmp in user namespace
- NostrVPN: enable service in auto-install, always include public relays
- NostrVPN: read tunnel IP from nvpn status (not just config file)
- VPN invite: v2 base64 no-pad format matching phone app
- Companion input: relay always active, kiosk skips relay listener (prevents double input)
- dev-start.sh: production build includes AIUI deployment
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 03:10:49 -04:00
# Configure LUKS auto-unlock: three layers to ensure it works
# Layer 1: cryptsetup-initramfs config (tells update-initramfs to embed key)
mkdir -p /mnt/target/etc/cryptsetup-initramfs
cat > /mnt/target/etc/cryptsetup-initramfs/conf <<'CRYPTC ONF'
KEYFILE_PATTERN = "/root/.luks-*.key"
UMASK = 0077
CRYPTCONF
# Layer 2: initramfs hook to force-copy key file
mkdir -p /mnt/target/etc/initramfs-tools/hooks
cat > /mnt/target/etc/initramfs-tools/hooks/archipelago-luks <<'LUKSHOOK'
#!/bin/sh
PREREQ = ""
prereqs( ) { echo " $PREREQ " ; }
case $1 in prereqs) prereqs; exit 0; ; esac
. /usr/share/initramfs-tools/hook-functions
if [ -f /root/.luks-archipelago.key ] ; then
mkdir -p " ${ DESTDIR } /root "
cp /root/.luks-archipelago.key " ${ DESTDIR } /root/.luks-archipelago.key "
chmod 600 " ${ DESTDIR } /root/.luks-archipelago.key "
fi
if [ -f /etc/crypttab ] ; then
mkdir -p " ${ DESTDIR } /etc "
cp /etc/crypttab " ${ DESTDIR } /etc/crypttab "
fi
copy_exec /sbin/cryptsetup
LUKSHOOK
chmod +x /mnt/target/etc/initramfs-tools/hooks/archipelago-luks
# Layer 3: systemd service as fallback — unlocks LUKS early if initramfs missed it
cat > /mnt/target/etc/systemd/system/archipelago-luks-unlock.service <<'LUKSUNIT'
[ Unit]
Description = Unlock Archipelago LUKS data partition
DefaultDependencies = no
Before = local-fs-pre.target
After = systemd-udevd.service
Wants = systemd-udevd.service
[ Service]
Type = oneshot
RemainAfterExit = yes
ExecStart = /bin/bash -c ' \
if [ -e /dev/mapper/archipelago-data ] ; then exit 0; fi ; \
DATA_DEV = $( blkid -t TYPE = crypto_LUKS -o device 2>/dev/null | head -1) ; \
if [ -z " $DATA_DEV " ] ; then exit 0; fi ; \
cryptsetup open --type luks2 --key-file /root/.luks-archipelago.key " $DATA_DEV " archipelago-data'
[ Install]
WantedBy = local-fs-pre.target
LUKSUNIT
chroot /mnt/target systemctl enable archipelago-luks-unlock.service 2>/dev/null || \
ln -sf /etc/systemd/system/archipelago-luks-unlock.service /mnt/target/etc/systemd/system/local-fs-pre.target.wants/archipelago-luks-unlock.service
2026-02-01 05:42:05 +00:00
# Create fstab
cat > /mnt/target/etc/fstab <<EOF
# Archipelago Bitcoin Node OS
2026-03-26 09:12:16 +00:00
UUID = $( blkid -s UUID -o value " $ROOT_PART " ) / ext4 errors = remount-ro 0 1
UUID = $( blkid -s UUID -o value " $EFI_PART " ) /boot/efi vfat umask = 0077 0 1
fix: ISO boot, container installs, VPN, nginx, companion input
- LUKS auto-unlock: initramfs hook + systemd service + nofail fstab
- Rootfs packages: add passt, aardvark-dns, netavark, nftables for Podman 5.x
- nginx: resolver + variable proxy_pass for external domains (DNS at boot)
- Boot: loglevel=0 suppresses kernel warnings, serial console for QEMU
- Container installs: write configs before chown, sudo chown for LUKS volumes
- Container installs: build UI sidecars locally (not from registry) for auth injection
- Bitcoin UI: inject RPC auth from secrets file, --no-cache rebuild
- Secrets: chown to archipelago user in first-boot (backend needs read access)
- Podman: image_copy_tmp_dir for read-only /var/tmp in user namespace
- NostrVPN: enable service in auto-install, always include public relays
- NostrVPN: read tunnel IP from nvpn status (not just config file)
- VPN invite: v2 base64 no-pad format matching phone app
- Companion input: relay always active, kiosk skips relay listener (prevents double input)
- dev-start.sh: production build includes AIUI deployment
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 03:10:49 -04:00
/dev/mapper/archipelago-data /var/lib/archipelago ext4 defaults,nofail,x-systemd.device-timeout= 60 0 2
chore: release v1.7.45-alpha
Resilience-validated release. Three full sweeps of the new resilience
harness against .228 confirm no shipstoppers.
Big user-visible:
- Bitcoin RPC auth durably correct via host-rendered nginx.conf bind-mount,
replaces fragile post-start exec that failed under restricted-cap rootless
podman ("crun: write cgroup.procs: Permission denied")
- Multi-container stack installs (indeedhub, immich, btcpay, mempool) now
emit phase events at every boundary so the progress bar advances
- Apps no longer vanish from the dashboard mid-install (absent-scanner skips
packages in transitional states)
- Indeedhub fresh installs work end-to-end (was 8500+ restart loop): five
missing env vars (DATABASE_PORT, QUEUE_HOST, QUEUE_PORT,
S3_PRIVATE_BUCKET_NAME, AES_MASTER_SECRET) added to install code
- Tailscale install fixed: --entrypoint string was being passed as a single
shell-line arg; switched to custom_args array
- Catalog cleaned of broken entries (dwn, endurain, ollama removed; nextcloud
restored on docker.io)
- Bitcoin Core update path uses correct image (was looking for nonexistent
lfg2025/bitcoin:28.4)
- ISO installs now allocate swap on the encrypted data partition
Infra:
- New resilience harness (scripts/resilience/) — black-box state-machine
tester, every app × every transition. Run before each release.
Sweep #3 final: PASS 107 / FAIL 12 / SKIP 14. The 12 fails are 1 cosmetic
(homeassistant trusted_hosts), 8 harness/timing false-positives, and 3
non-shipstopper tracked items. Down from 23 in baseline sweep #1.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 12:31:45 -04:00
# Swap on encrypted data partition — activated after LUKS unlock
/var/lib/archipelago/swapfile none swap sw,nofail 0 0
2026-02-01 05:42:05 +00:00
EOF
# Configure hostname
echo "archipelago" > /mnt/target/etc/hostname
cat > /mnt/target/etc/hosts <<EOF
127.0.0.1 localhost
127.0.1.1 archipelago
::1 localhost ip6-localhost ip6-loopback
EOF
fix: container install flow, filebrowser auth, AppCard enrichment
- Fix .198-style fresh installs: systemd service ExecStartPre creates
/run/user/1000, enable podman.socket, chmod 644 /etc/hosts
- Filebrowser: add /data volume for database (fixes read-only crash),
secure auth with random password via backend RPC (no more admin/admin)
- AppCard: enrich installing state with marketplace metadata (icon,
title, description, tier badge, author, version)
- Registry: btcpayserver 1.13.5 → 1.13.7, images mirrored
- ReadWritePaths: add home container paths for rootless podman
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 13:32:54 +00:00
chmod 644 /mnt/target/etc/hosts
2026-02-01 05:42:05 +00:00
2026-03-27 17:17:18 +00:00
# Pre-create container storage dirs (ReadWritePaths needs these to exist)
mkdir -p /mnt/target/home/archipelago/.local/share/containers
2026-03-26 10:46:26 +00:00
mkdir -p /mnt/target/home/archipelago/.config/containers
2026-03-27 17:17:18 +00:00
chown -R 1000:1000 /mnt/target/home/archipelago/.local
2026-03-29 15:43:57 +01:00
# Redirect container storage to encrypted LUKS partition (not root filesystem)
# Without this, pulling images fills the 29GB root partition
cat > /mnt/target/home/archipelago/.config/containers/storage.conf <<'STORAGECONF'
[ storage]
driver = "overlay"
graphroot = "/var/lib/archipelago/containers/storage"
runroot = "/run/user/1000/containers"
STORAGECONF
# Symlink for backward compat (some tools look in ~/.local/share/containers)
ln -sf /var/lib/archipelago/containers/storage /mnt/target/home/archipelago/.local/share/containers/storage 2>/dev/null || true
2026-04-12 09:06:12 -04:00
# Configure Archipelago app registries (primary + fallback)
2026-03-26 10:46:26 +00:00
cat > /mnt/target/home/archipelago/.config/containers/registries.conf <<'REGCONF'
2026-04-12 09:06:12 -04:00
unqualified-search-registries = [ "docker.io" ]
2026-03-26 10:46:26 +00:00
[ [ registry] ]
chore: release v1.7.45-alpha
Resilience-validated release. Three full sweeps of the new resilience
harness against .228 confirm no shipstoppers.
Big user-visible:
- Bitcoin RPC auth durably correct via host-rendered nginx.conf bind-mount,
replaces fragile post-start exec that failed under restricted-cap rootless
podman ("crun: write cgroup.procs: Permission denied")
- Multi-container stack installs (indeedhub, immich, btcpay, mempool) now
emit phase events at every boundary so the progress bar advances
- Apps no longer vanish from the dashboard mid-install (absent-scanner skips
packages in transitional states)
- Indeedhub fresh installs work end-to-end (was 8500+ restart loop): five
missing env vars (DATABASE_PORT, QUEUE_HOST, QUEUE_PORT,
S3_PRIVATE_BUCKET_NAME, AES_MASTER_SECRET) added to install code
- Tailscale install fixed: --entrypoint string was being passed as a single
shell-line arg; switched to custom_args array
- Catalog cleaned of broken entries (dwn, endurain, ollama removed; nextcloud
restored on docker.io)
- Bitcoin Core update path uses correct image (was looking for nonexistent
lfg2025/bitcoin:28.4)
- ISO installs now allocate swap on the encrypted data partition
Infra:
- New resilience harness (scripts/resilience/) — black-box state-machine
tester, every app × every transition. Run before each release.
Sweep #3 final: PASS 107 / FAIL 12 / SKIP 14. The 12 fails are 1 cosmetic
(homeassistant trusted_hosts), 8 harness/timing false-positives, and 3
non-shipstopper tracked items. Down from 23 in baseline sweep #1.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 12:31:45 -04:00
location = "146.59.87.168:3000"
2026-03-26 10:46:26 +00:00
insecure = true
2026-04-12 09:06:12 -04:00
[ [ registry] ]
chore: release v1.7.45-alpha
Resilience-validated release. Three full sweeps of the new resilience
harness against .228 confirm no shipstoppers.
Big user-visible:
- Bitcoin RPC auth durably correct via host-rendered nginx.conf bind-mount,
replaces fragile post-start exec that failed under restricted-cap rootless
podman ("crun: write cgroup.procs: Permission denied")
- Multi-container stack installs (indeedhub, immich, btcpay, mempool) now
emit phase events at every boundary so the progress bar advances
- Apps no longer vanish from the dashboard mid-install (absent-scanner skips
packages in transitional states)
- Indeedhub fresh installs work end-to-end (was 8500+ restart loop): five
missing env vars (DATABASE_PORT, QUEUE_HOST, QUEUE_PORT,
S3_PRIVATE_BUCKET_NAME, AES_MASTER_SECRET) added to install code
- Tailscale install fixed: --entrypoint string was being passed as a single
shell-line arg; switched to custom_args array
- Catalog cleaned of broken entries (dwn, endurain, ollama removed; nextcloud
restored on docker.io)
- Bitcoin Core update path uses correct image (was looking for nonexistent
lfg2025/bitcoin:28.4)
- ISO installs now allocate swap on the encrypted data partition
Infra:
- New resilience harness (scripts/resilience/) — black-box state-machine
tester, every app × every transition. Run before each release.
Sweep #3 final: PASS 107 / FAIL 12 / SKIP 14. The 12 fails are 1 cosmetic
(homeassistant trusted_hosts), 8 harness/timing false-positives, and 3
non-shipstopper tracked items. Down from 23 in baseline sweep #1.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 12:31:45 -04:00
location = "git.tx1138.com"
2026-04-12 09:06:12 -04:00
insecure = true
2026-03-26 10:46:26 +00:00
REGCONF
chown -R 1000:1000 /mnt/target/home/archipelago/.config
2026-04-12 09:06:12 -04:00
# Pre-create dynamic registry config for the backend (fallback registries)
mkdir -p /mnt/target/var/lib/archipelago/config
cat > /mnt/target/var/lib/archipelago/config/registries.json <<'DYNREG'
{
"registries" : [
chore: release v1.7.45-alpha
Resilience-validated release. Three full sweeps of the new resilience
harness against .228 confirm no shipstoppers.
Big user-visible:
- Bitcoin RPC auth durably correct via host-rendered nginx.conf bind-mount,
replaces fragile post-start exec that failed under restricted-cap rootless
podman ("crun: write cgroup.procs: Permission denied")
- Multi-container stack installs (indeedhub, immich, btcpay, mempool) now
emit phase events at every boundary so the progress bar advances
- Apps no longer vanish from the dashboard mid-install (absent-scanner skips
packages in transitional states)
- Indeedhub fresh installs work end-to-end (was 8500+ restart loop): five
missing env vars (DATABASE_PORT, QUEUE_HOST, QUEUE_PORT,
S3_PRIVATE_BUCKET_NAME, AES_MASTER_SECRET) added to install code
- Tailscale install fixed: --entrypoint string was being passed as a single
shell-line arg; switched to custom_args array
- Catalog cleaned of broken entries (dwn, endurain, ollama removed; nextcloud
restored on docker.io)
- Bitcoin Core update path uses correct image (was looking for nonexistent
lfg2025/bitcoin:28.4)
- ISO installs now allocate swap on the encrypted data partition
Infra:
- New resilience harness (scripts/resilience/) — black-box state-machine
tester, every app × every transition. Run before each release.
Sweep #3 final: PASS 107 / FAIL 12 / SKIP 14. The 12 fails are 1 cosmetic
(homeassistant trusted_hosts), 8 harness/timing false-positives, and 3
non-shipstopper tracked items. Down from 23 in baseline sweep #1.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 12:31:45 -04:00
{ "url" : "146.59.87.168:3000/lfg2025" , "name" : "Archipelago Primary" , "tls_verify" : false, "enabled" : true, "priority" : 0} ,
{ "url" : "git.tx1138.com/lfg2025" , "name" : "Archipelago Fallback" , "tls_verify" : true, "enabled" : true, "priority" : 10}
2026-04-12 09:06:12 -04:00
]
}
DYNREG
chown -R 1000:1000 /mnt/target/var/lib/archipelago/config
2026-04-09 11:47:35 +02:00
# Configure podman to use netavark backend (enables container DNS on archy-net).
2026-04-09 21:32:08 +02:00
# netavark + aardvark-dns binaries come from the rootfs (Debian 13 apt packages).
2026-04-09 11:47:35 +02:00
if [ -f /mnt/target/usr/lib/podman/netavark ] ; then
2026-03-30 18:57:17 +01:00
mkdir -p /mnt/target/home/archipelago/.config/containers
cat > /mnt/target/home/archipelago/.config/containers/containers.conf <<'CONTAINERSCON F'
[ network]
network_backend = "netavark"
fix: ISO boot, container installs, VPN, nginx, companion input
- LUKS auto-unlock: initramfs hook + systemd service + nofail fstab
- Rootfs packages: add passt, aardvark-dns, netavark, nftables for Podman 5.x
- nginx: resolver + variable proxy_pass for external domains (DNS at boot)
- Boot: loglevel=0 suppresses kernel warnings, serial console for QEMU
- Container installs: write configs before chown, sudo chown for LUKS volumes
- Container installs: build UI sidecars locally (not from registry) for auth injection
- Bitcoin UI: inject RPC auth from secrets file, --no-cache rebuild
- Secrets: chown to archipelago user in first-boot (backend needs read access)
- Podman: image_copy_tmp_dir for read-only /var/tmp in user namespace
- NostrVPN: enable service in auto-install, always include public relays
- NostrVPN: read tunnel IP from nvpn status (not just config file)
- VPN invite: v2 base64 no-pad format matching phone app
- Companion input: relay always active, kiosk skips relay listener (prevents double input)
- dev-start.sh: production build includes AIUI deployment
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 03:10:49 -04:00
default_rootless_network_cmd = "pasta"
[ engine]
image_copy_tmp_dir = "/var/lib/archipelago/containers/tmp"
2026-03-30 18:57:17 +01:00
CONTAINERSCONF
fix: ISO boot, container installs, VPN, nginx, companion input
- LUKS auto-unlock: initramfs hook + systemd service + nofail fstab
- Rootfs packages: add passt, aardvark-dns, netavark, nftables for Podman 5.x
- nginx: resolver + variable proxy_pass for external domains (DNS at boot)
- Boot: loglevel=0 suppresses kernel warnings, serial console for QEMU
- Container installs: write configs before chown, sudo chown for LUKS volumes
- Container installs: build UI sidecars locally (not from registry) for auth injection
- Bitcoin UI: inject RPC auth from secrets file, --no-cache rebuild
- Secrets: chown to archipelago user in first-boot (backend needs read access)
- Podman: image_copy_tmp_dir for read-only /var/tmp in user namespace
- NostrVPN: enable service in auto-install, always include public relays
- NostrVPN: read tunnel IP from nvpn status (not just config file)
- VPN invite: v2 base64 no-pad format matching phone app
- Companion input: relay always active, kiosk skips relay listener (prevents double input)
- dev-start.sh: production build includes AIUI deployment
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 03:10:49 -04:00
mkdir -p /mnt/target/var/lib/archipelago/containers/tmp
chown -R 1000:1000 /mnt/target/var/lib/archipelago/containers/tmp
2026-03-30 18:57:17 +01:00
chown -R 1000:1000 /mnt/target/home/archipelago/.config/containers
2026-04-09 11:47:35 +02:00
echo " Configured netavark backend (container DNS enabled)"
2026-03-30 18:57:17 +01:00
else
2026-04-09 11:47:35 +02:00
echo " WARNING: netavark not found in rootfs — container DNS will not work"
2026-03-30 18:57:17 +01:00
fi
2026-03-26 15:58:16 +00:00
# Laptop support: ignore lid close so server keeps running
mkdir -p /mnt/target/etc/systemd/logind.conf.d
cat > /mnt/target/etc/systemd/logind.conf.d/lid-ignore.conf <<'LIDCONF'
[ Login]
HandleLidSwitch = ignore
HandleLidSwitchExternalPower = ignore
HandleLidSwitchDocked = ignore
LIDCONF
2026-02-01 05:42:05 +00:00
# Copy Archipelago binaries and files
if [ -d " $BOOT_MEDIA /archipelago/bin " ] ; then
cp -r " $BOOT_MEDIA /archipelago/bin/ " * /mnt/target/usr/local/bin/ 2>/dev/null || true
chmod +x /mnt/target/usr/local/bin/* 2>/dev/null || true
fi
if [ -d " $BOOT_MEDIA /archipelago/web-ui " ] ; then
cp -r " $BOOT_MEDIA /archipelago/web-ui " /mnt/target/opt/archipelago/
fi
2026-04-10 04:01:35 -04:00
# Mark unbundled mode so first-boot only creates FileBrowser (user installs apps from Marketplace)
if [ -f " $BOOT_MEDIA /archipelago/.unbundled " ] ; then
touch /mnt/target/opt/archipelago/.unbundled
echo " Unbundled mode: apps install on-demand from Marketplace"
fi
2026-02-01 05:42:05 +00:00
if [ -d " $BOOT_MEDIA /archipelago/apps " ] ; then
cp -r " $BOOT_MEDIA /archipelago/apps " /mnt/target/etc/archipelago/
fi
# Copy pre-bundled container images
if [ -d " $BOOT_MEDIA /archipelago/container-images " ] ; then
echo " Copying container images (this may take a moment)..."
mkdir -p /mnt/target/opt/archipelago/container-images
cp -r " $BOOT_MEDIA /archipelago/container-images/ " *.tar /mnt/target/opt/archipelago/container-images/ 2>/dev/null || true
# Copy first-boot loader script and service
mkdir -p /mnt/target/opt/archipelago/scripts
if [ -f " $BOOT_MEDIA /archipelago/scripts/load-container-images.sh " ] ; then
cp " $BOOT_MEDIA /archipelago/scripts/load-container-images.sh " /mnt/target/opt/archipelago/scripts/
chmod +x /mnt/target/opt/archipelago/scripts/load-container-images.sh
fi
if [ -f " $BOOT_MEDIA /archipelago/scripts/archipelago-load-images.service " ] ; then
cp " $BOOT_MEDIA /archipelago/scripts/archipelago-load-images.service " /mnt/target/etc/systemd/system/
fi
2026-02-17 15:03:34 +00:00
if [ -f " $BOOT_MEDIA /archipelago/scripts/setup-tor.sh " ] ; then
cp " $BOOT_MEDIA /archipelago/scripts/setup-tor.sh " /mnt/target/opt/archipelago/scripts/
chmod +x /mnt/target/opt/archipelago/scripts/setup-tor.sh
fi
if [ -d " $BOOT_MEDIA /archipelago/scripts/tor " ] ; then
mkdir -p /mnt/target/opt/archipelago/scripts/tor
cp -r " $BOOT_MEDIA /archipelago/scripts/tor/ " * /mnt/target/opt/archipelago/scripts/tor/ 2>/dev/null || true
fi
if [ -f " $BOOT_MEDIA /archipelago/scripts/archipelago-setup-tor.service " ] ; then
cp " $BOOT_MEDIA /archipelago/scripts/archipelago-setup-tor.service " /mnt/target/etc/systemd/system/
fi
2026-03-21 03:06:29 +00:00
# Copy shared script library
if [ -d " $BOOT_MEDIA /archipelago/scripts/lib " ] ; then
mkdir -p /mnt/target/opt/archipelago/scripts/lib
cp -r " $BOOT_MEDIA /archipelago/scripts/lib/ " * /mnt/target/opt/archipelago/scripts/lib/ 2>/dev/null || true
fi
2026-02-25 18:04:41 +00:00
if [ -f " $BOOT_MEDIA /archipelago/scripts/first-boot-containers.sh " ] ; then
cp " $BOOT_MEDIA /archipelago/scripts/first-boot-containers.sh " /mnt/target/opt/archipelago/scripts/
chmod +x /mnt/target/opt/archipelago/scripts/first-boot-containers.sh
fi
if [ -f " $BOOT_MEDIA /archipelago/scripts/archipelago-first-boot-containers.service " ] ; then
cp " $BOOT_MEDIA /archipelago/scripts/archipelago-first-boot-containers.service " /mnt/target/etc/systemd/system/
fi
2026-03-08 02:27:58 +00:00
# Copy docker UI source files for first-boot container builds
if [ -d " $BOOT_MEDIA /archipelago/docker " ] ; then
mkdir -p /mnt/target/opt/archipelago/docker
cp -r " $BOOT_MEDIA /archipelago/docker/ " * /mnt/target/opt/archipelago/docker/ 2>/dev/null || true
fi
2026-02-01 05:42:05 +00:00
echo " ✅ Container images staged for first-boot loading"
fi
2026-03-09 17:09:59 +00:00
# Initialize backend data directories for seamless first boot
mkdir -p /mnt/target/var/lib/archipelago/tor-config
mkdir -p /mnt/target/var/lib/archipelago/identities
mkdir -p /mnt/target/var/lib/archipelago/lnd
fix: production onboarding, CI tests, container security, keyboard nav
Install & Onboarding:
- Remove DEV_MODE=true from production ISO service file (auto-created
users, skipped password setup)
- Auto-install no longer overwrites rootfs service file with bad template
- Login.vue always checks auth.isSetup — shows password creation form
on fresh install without requiring dev build flag
- Deploy image-versions.sh to /opt/archipelago/scripts/ on installed nodes
- First-boot-containers sources image-versions.sh, runs podman as
archipelago user (rootless), enables linger + podman.socket
- Correct volume ownership (100000:100000 for rootless UID mapping)
Container Security:
- FileBrowser: add --cap-add=DAC_OVERRIDE for rootless podman volume access
- FileBrowser: add --read-only, /data volume for database, proper cmd args
- First-boot script matches backend config (security hardening + health check)
CI Pipeline:
- Add vue-tsc type check + vitest run to build-iso.yml (runs every push)
- Add post-install-tests.yml workflow (workflow_dispatch, SSH to target)
- Build report: set +eo pipefail, fix rootfs path, add || true guards
- Bundle run-post-install-tests.sh into ISO
E2E Test Suite (scripts/run-post-install-tests.sh):
- Phase 1: Install verification (files, services, podman, linger, DEV_MODE check)
- Phase 2: Onboarding flow (auth.isSetup, auth.setup, login, DID, complete)
- Phase 3: Container lifecycle (install 3 apps via package.install RPC,
verify running, stop, verify stopped, restart, verify running, health)
- Phase 4: Log verification (first-boot log, diagnostics, journal errors)
- Correct package.install params: {"id", "dockerImage"}
Frontend:
- Fix backdrop-filter tab-switch bug (keep animations paused during rebuild)
- Dashboard glitch animations paused during tab-hidden
- Gamepad nav: auto-focus first container on route change
- Tab roving: Left/Right on role="tab" cycles and activates sibling tabs
- ContainerApps: data-controller-launch on running app cards
- 515 tests passing (fixed 30 broken, added 19 new keyboard nav tests)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 16:16:57 +00:00
# Copy test scripts for post-install validation
for test_script in run-e2e-tests.sh run-post-install-tests.sh; do
if [ -f " $BOOT_MEDIA /archipelago/scripts/ $test_script " ] ; then
cp " $BOOT_MEDIA /archipelago/scripts/ $test_script " /mnt/target/opt/archipelago/scripts/
chmod +x /mnt/target/opt/archipelago/scripts/$test_script
fi
done
2026-03-09 17:09:59 +00:00
2026-03-25 15:52:26 +00:00
# Copy self-update script
if [ -f " $BOOT_MEDIA /archipelago/scripts/self-update.sh " ] ; then
cp " $BOOT_MEDIA /archipelago/scripts/self-update.sh " /mnt/target/opt/archipelago/scripts/
chmod +x /mnt/target/opt/archipelago/scripts/self-update.sh
# Also place in home for the update timer to find
mkdir -p /mnt/target/home/archipelago/archy/scripts
cp " $BOOT_MEDIA /archipelago/scripts/self-update.sh " /mnt/target/home/archipelago/archy/scripts/
chmod +x /mnt/target/home/archipelago/archy/scripts/self-update.sh
fi
fix: production onboarding, CI tests, container security, keyboard nav
Install & Onboarding:
- Remove DEV_MODE=true from production ISO service file (auto-created
users, skipped password setup)
- Auto-install no longer overwrites rootfs service file with bad template
- Login.vue always checks auth.isSetup — shows password creation form
on fresh install without requiring dev build flag
- Deploy image-versions.sh to /opt/archipelago/scripts/ on installed nodes
- First-boot-containers sources image-versions.sh, runs podman as
archipelago user (rootless), enables linger + podman.socket
- Correct volume ownership (100000:100000 for rootless UID mapping)
Container Security:
- FileBrowser: add --cap-add=DAC_OVERRIDE for rootless podman volume access
- FileBrowser: add --read-only, /data volume for database, proper cmd args
- First-boot script matches backend config (security hardening + health check)
CI Pipeline:
- Add vue-tsc type check + vitest run to build-iso.yml (runs every push)
- Add post-install-tests.yml workflow (workflow_dispatch, SSH to target)
- Build report: set +eo pipefail, fix rootfs path, add || true guards
- Bundle run-post-install-tests.sh into ISO
E2E Test Suite (scripts/run-post-install-tests.sh):
- Phase 1: Install verification (files, services, podman, linger, DEV_MODE check)
- Phase 2: Onboarding flow (auth.isSetup, auth.setup, login, DID, complete)
- Phase 3: Container lifecycle (install 3 apps via package.install RPC,
verify running, stop, verify stopped, restart, verify running, health)
- Phase 4: Log verification (first-boot log, diagnostics, journal errors)
- Correct package.install params: {"id", "dockerImage"}
Frontend:
- Fix backdrop-filter tab-switch bug (keep animations paused during rebuild)
- Dashboard glitch animations paused during tab-hidden
- Gamepad nav: auto-focus first container on route change
- Tab roving: Left/Right on role="tab" cycles and activates sibling tabs
- ContainerApps: data-controller-launch on running app cards
- 515 tests passing (fixed 30 broken, added 19 new keyboard nav tests)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 16:16:57 +00:00
# Copy image-versions.sh (needed by first-boot-containers and updates)
if [ -f " $BOOT_MEDIA /archipelago/scripts/image-versions.sh " ] ; then
cp " $BOOT_MEDIA /archipelago/scripts/image-versions.sh " /mnt/target/opt/archipelago/scripts/
chmod +x /mnt/target/opt/archipelago/scripts/image-versions.sh
# Also place in home for container scripts to find
mkdir -p /mnt/target/home/archipelago/archy/scripts
cp " $BOOT_MEDIA /archipelago/scripts/image-versions.sh " /mnt/target/home/archipelago/archy/scripts/
fi
2026-03-25 15:52:26 +00:00
# Clone repo for git-based updates (first-boot will have network)
# Create a script that runs on first boot to clone the repo
cat > /mnt/target/opt/archipelago/scripts/setup-git-updates.sh <<'GITSETUP'
#!/bin/bash
# Clone the Archipelago repo for git-based self-updates
REPO_DIR = "/home/archipelago/archy"
if [ -d " $REPO_DIR /.git " ] ; then
exit 0 # Already cloned
fi
echo "[update] Cloning Archipelago repo for self-updates..."
su - archipelago -c " git clone https://git.tx1138.com/lfg2025/archy $REPO_DIR " 2>/dev/null || {
echo "[update] Git clone failed (network?). Updates will retry on next boot."
exit 0
}
chown -R 1000:1000 " $REPO_DIR "
echo "[update] Repo cloned. Self-updates enabled."
GITSETUP
chmod +x /mnt/target/opt/archipelago/scripts/setup-git-updates.sh
2026-02-01 05:42:05 +00:00
# Ensure correct ownership (use numeric UID:GID 1000:1000 since we're outside chroot)
chown -R 1000:1000 /mnt/target/opt/archipelago 2>/dev/null || true
chown -R 1000:1000 /mnt/target/var/lib/archipelago 2>/dev/null || true
# Create welcome profile (nginx serves on port 80)
cat > /mnt/target/etc/profile.d/archipelago.sh <<'PROFILE'
#!/bin/bash
2026-03-21 01:11:05 +00:00
# Ensure /sbin and /usr/sbin are in PATH (needed for reboot, shutdown, etc.)
case " : $PATH : " in
*:/sbin:*) ; ; *) export PATH = " $PATH :/sbin:/usr/sbin " ; ;
esac
2026-02-01 05:42:05 +00:00
if [ -t 0 ] && [ -z " $ARCHIPELAGO_WELCOMED " ] ; then
export ARCHIPELAGO_WELCOMED = 1
2026-03-28 13:06:34 +00:00
# Wait for network (DHCP may not be ready yet on first boot)
IP = ""
for i in 1 2 3 4 5; do
IP = $( hostname -I 2>/dev/null | awk '{print $1}' )
[ -n " $IP " ] && break
sleep 2
done
2026-03-28 23:41:40 +00:00
O = '\033[38;5;208m'
2026-03-28 23:58:42 +00:00
OD = '\033[38;5;130m'
W = '\033[1;37m'
2026-03-28 23:41:40 +00:00
N = '\033[0m'
2026-03-28 13:06:34 +00:00
clear
2026-03-28 23:58:42 +00:00
echo -e " ${ O } ▄▀█ █▀▄ █▀▀ █ █ █ █▀█ █▀▀ █ ▄▀█ █▀▀ █▀█ ${ N } "
echo -e " ${ O } █▀█ █▀▄ █ █▀█ █ █▀▀ ██▀ █ █▀█ █ █ █ █ ${ N } "
echo -e " ${ O } ▀ ▀ ▀ ▀ ▀▀▀ ▀ ▀ ▀ ▀ ▀▀▀ ▀▀▀ ▀ ▀ ▀▀▀ ▀▀▀ ${ N } "
echo -e " ${ OD } bitcoin node os ${ N } "
2026-02-01 05:42:05 +00:00
if [ -n " $IP " ] ; then
2026-03-28 23:58:42 +00:00
echo -e " ${ W } web ui http:// $IP ${ N } "
echo -e " ${ W } ssh archipelago@ $IP ${ N } "
echo -e " ${ W } password archipelago (SSH) / password123 (Web) ${ N } "
2026-03-28 13:06:34 +00:00
else
2026-03-28 23:58:42 +00:00
echo -e " ${ OD } Waiting for network... ${ N } "
2026-02-01 05:42:05 +00:00
fi
2026-03-30 16:35:06 +01:00
if [ -b /dev/mapper/archipelago-data ] || [ -b /dev/mapper/archipelago_crypt ] ; then
2026-03-28 23:58:42 +00:00
echo -e " ${ OD } storage LUKS2 encrypted ${ N } "
2026-03-26 09:12:16 +00:00
fi
if systemctl is-active archipelago-kiosk.service >/dev/null 2>& 1; then
2026-03-28 23:58:42 +00:00
echo -e " ${ OD } display Kiosk active (Ctrl+Alt+F1 for terminal) ${ N } "
2026-03-26 09:12:16 +00:00
else
2026-03-28 23:58:42 +00:00
echo -e " ${ OD } display Console (Ctrl+Alt+F7 for kiosk) ${ N } "
2026-03-26 09:12:16 +00:00
fi
echo ""
2026-02-01 05:42:05 +00:00
fi
PROFILE
chmod +x /mnt/target/etc/profile.d/archipelago.sh
2026-03-30 23:47:15 +01:00
# Force UTF-8 console with Terminus font (supports Unicode block chars for ASCII logo)
cat > /mnt/target/etc/default/console-setup <<'CONSOLESETUP'
ACTIVE_CONSOLES = "/dev/tty[1-6]"
CHARMAP = "UTF-8"
CODESET = "Uni2"
FONTFACE = "Terminus"
FONTSIZE = "16"
CONSOLESETUP
2026-03-30 16:35:06 +01:00
# Suppress default Debian MOTD (our profile.d script handles the welcome)
echo -n > /mnt/target/etc/motd
rm -f /mnt/target/etc/motd.d/* 2>/dev/null || true
2026-03-29 14:25:13 +01:00
# Ensure reboot/shutdown work without sudo for the archipelago user
# profile.d only runs for login shells; .bashrc handles SSH interactive sessions
if ! grep -q '/sbin' /mnt/target/home/archipelago/.bashrc 2>/dev/null; then
echo 'export PATH="$PATH:/sbin:/usr/sbin"' >> /mnt/target/home/archipelago/.bashrc
fi
2026-04-08 17:51:21 +02:00
# Power commands need sudo on SSH sessions (polkit denies without local seat)
if ! grep -q 'alias reboot' /mnt/target/home/archipelago/.bashrc 2>/dev/null; then
cat >> /mnt/target/home/archipelago/.bashrc <<'ALIA SES'
alias reboot = 'sudo systemctl reboot'
alias shutdown = 'sudo shutdown'
alias halt = 'sudo systemctl halt'
alias poweroff = 'sudo systemctl poweroff'
ALIASES
fi
2026-03-29 14:25:13 +01:00
fix: production onboarding, CI tests, container security, keyboard nav
Install & Onboarding:
- Remove DEV_MODE=true from production ISO service file (auto-created
users, skipped password setup)
- Auto-install no longer overwrites rootfs service file with bad template
- Login.vue always checks auth.isSetup — shows password creation form
on fresh install without requiring dev build flag
- Deploy image-versions.sh to /opt/archipelago/scripts/ on installed nodes
- First-boot-containers sources image-versions.sh, runs podman as
archipelago user (rootless), enables linger + podman.socket
- Correct volume ownership (100000:100000 for rootless UID mapping)
Container Security:
- FileBrowser: add --cap-add=DAC_OVERRIDE for rootless podman volume access
- FileBrowser: add --read-only, /data volume for database, proper cmd args
- First-boot script matches backend config (security hardening + health check)
CI Pipeline:
- Add vue-tsc type check + vitest run to build-iso.yml (runs every push)
- Add post-install-tests.yml workflow (workflow_dispatch, SSH to target)
- Build report: set +eo pipefail, fix rootfs path, add || true guards
- Bundle run-post-install-tests.sh into ISO
E2E Test Suite (scripts/run-post-install-tests.sh):
- Phase 1: Install verification (files, services, podman, linger, DEV_MODE check)
- Phase 2: Onboarding flow (auth.isSetup, auth.setup, login, DID, complete)
- Phase 3: Container lifecycle (install 3 apps via package.install RPC,
verify running, stop, verify stopped, restart, verify running, health)
- Phase 4: Log verification (first-boot log, diagnostics, journal errors)
- Correct package.install params: {"id", "dockerImage"}
Frontend:
- Fix backdrop-filter tab-switch bug (keep animations paused during rebuild)
- Dashboard glitch animations paused during tab-hidden
- Gamepad nav: auto-focus first container on route change
- Tab roving: Left/Right on role="tab" cycles and activates sibling tabs
- ContainerApps: data-controller-launch on running app cards
- 515 tests passing (fixed 30 broken, added 19 new keyboard nav tests)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 16:16:57 +00:00
# Systemd service: use the production version from rootfs (configs/archipelago.service)
# Do NOT overwrite — the rootfs already has the correct User=archipelago, no DEV_MODE version
if [ ! -f /mnt/target/etc/systemd/system/archipelago.service ] ; then
echo " WARNING: archipelago.service missing from rootfs — copying from ISO"
2026-04-28 15:00:58 -04:00
cp " $BOOT_MEDIA /archipelago/../configs/archipelago.service " /mnt/target/etc/systemd/system/archipelago.service 2>/dev/null || true
fix: production onboarding, CI tests, container security, keyboard nav
Install & Onboarding:
- Remove DEV_MODE=true from production ISO service file (auto-created
users, skipped password setup)
- Auto-install no longer overwrites rootfs service file with bad template
- Login.vue always checks auth.isSetup — shows password creation form
on fresh install without requiring dev build flag
- Deploy image-versions.sh to /opt/archipelago/scripts/ on installed nodes
- First-boot-containers sources image-versions.sh, runs podman as
archipelago user (rootless), enables linger + podman.socket
- Correct volume ownership (100000:100000 for rootless UID mapping)
Container Security:
- FileBrowser: add --cap-add=DAC_OVERRIDE for rootless podman volume access
- FileBrowser: add --read-only, /data volume for database, proper cmd args
- First-boot script matches backend config (security hardening + health check)
CI Pipeline:
- Add vue-tsc type check + vitest run to build-iso.yml (runs every push)
- Add post-install-tests.yml workflow (workflow_dispatch, SSH to target)
- Build report: set +eo pipefail, fix rootfs path, add || true guards
- Bundle run-post-install-tests.sh into ISO
E2E Test Suite (scripts/run-post-install-tests.sh):
- Phase 1: Install verification (files, services, podman, linger, DEV_MODE check)
- Phase 2: Onboarding flow (auth.isSetup, auth.setup, login, DID, complete)
- Phase 3: Container lifecycle (install 3 apps via package.install RPC,
verify running, stop, verify stopped, restart, verify running, health)
- Phase 4: Log verification (first-boot log, diagnostics, journal errors)
- Correct package.install params: {"id", "dockerImage"}
Frontend:
- Fix backdrop-filter tab-switch bug (keep animations paused during rebuild)
- Dashboard glitch animations paused during tab-hidden
- Gamepad nav: auto-focus first container on route change
- Tab roving: Left/Right on role="tab" cycles and activates sibling tabs
- ContainerApps: data-controller-launch on running app cards
- 515 tests passing (fixed 30 broken, added 19 new keyboard nav tests)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 16:16:57 +00:00
fi
2026-02-01 05:42:05 +00:00
2026-03-12 22:19:04 +00:00
# Claude API proxy — middleware that injects max_tokens, strips invalid fields
# API key must be set after install via setup-aiui-server.sh or manually
cat > /mnt/target/opt/archipelago/claude-api-proxy.py <<'CLAUDEPROXY'
#!/usr/bin/env python3
import http.server, json, ssl, sys, os, urllib.request, urllib.error
API_KEY = os.environ.get( "ANTHROPIC_API_KEY" , "" )
PORT = 3142
class Handler( http.server.BaseHTTPRequestHandler) :
def do_POST( self) :
if self.path = = "/health" :
self.send_response( 200) ; self.send_header( "Content-Type" ,"application/json" ) ; self.end_headers( )
self.wfile.write( b'{"status":"ok"}' ) ; return
cl = int( self.headers.get( "Content-Length" , 0) )
body = self.rfile.read( cl)
try: data = json.loads( body)
except: data = { }
if "max_tokens" not in data: data[ "max_tokens" ] = 8096
for f in [ "webSearch" ,"web_search" ] : data.pop( f, None)
2026-04-02 11:01:58 +01:00
# Normalize model IDs — map short/dotted names to full API model IDs
MODEL_MAP = {
"claude-haiku-4.5" : "claude-haiku-4-5-20251001" ,
"claude-haiku-4-5" : "claude-haiku-4-5-20251001" ,
"claude-sonnet-4" : "claude-sonnet-4-20250514" ,
"claude-sonnet-4.5" : "claude-sonnet-4-5-20250514" ,
"claude-sonnet-4-5" : "claude-sonnet-4-5-20250514" ,
"claude-opus-4" : "claude-opus-4-20250514" ,
}
m = data.get( "model" , "" )
if m in MODEL_MAP: data[ "model" ] = MODEL_MAP[ m]
2026-03-12 22:19:04 +00:00
body = json.dumps( data) .encode( )
2026-04-02 18:30:34 +01:00
if not API_KEY:
err = json.dumps( { "type" :"error" ,"error" :{ "type" :"auth_error" ,"message" :"AIUI not configured. Set your Anthropic API key in Settings > AIUI to enable AI chat." } } ) .encode( )
self.send_response( 401) ; self.send_header( "Content-Type" ,"application/json" ) ; self.send_header( "Content-Length" ,str( len( err) ) ) ; self.end_headers( ) ; self.wfile.write( err) ; return
2026-03-12 22:19:04 +00:00
headers = { "Content-Type" :"application/json" ,"x-api-key" :API_KEY,"anthropic-version" :"2023-06-01" ,"anthropic-dangerous-direct-browser-access" :"true" }
for h in [ "anthropic-version" ,"anthropic-beta" ] :
if self.headers.get( h) : headers[ h] = self.headers[ h]
req = urllib.request.Request( "https://api.anthropic.com" +self.path, data = body, headers = headers, method = "POST" )
try:
ctx = ssl.create_default_context( )
resp = urllib.request.urlopen( req, context = ctx, timeout = 300)
self.send_response( resp.status)
is_stream = "text/event-stream" in ( resp.headers.get( "Content-Type" ,"" ) or "" )
for k,v in resp.headers.items( ) :
if k.lower( ) not in ( "transfer-encoding" ,"connection" ) : self.send_header( k,v)
if is_stream: self.send_header( "Transfer-Encoding" ,"chunked" )
self.end_headers( )
if is_stream:
while True:
chunk = resp.read( 4096)
if not chunk: break
self.wfile.write( b"%x\r\n" % len( chunk) ) ; self.wfile.write( chunk) ; self.wfile.write( b"\r\n" ) ; self.wfile.flush( )
self.wfile.write( b"0\r\n\r\n" ) ; self.wfile.flush( )
else : self.wfile.write( resp.read( ) )
except urllib.error.HTTPError as e:
self.send_response( e.code) ; self.send_header( "Content-Type" ,"application/json" ) ; self.end_headers( ) ; self.wfile.write( e.read( ) )
except Exception as e:
self.send_response( 502) ; self.send_header( "Content-Type" ,"application/json" ) ; self.end_headers( ) ; self.wfile.write( json.dumps( { "error" :str( e) } ) .encode( ) )
def do_GET( self) :
if self.path = = "/health" :
self.send_response( 200) ; self.send_header( "Content-Type" ,"application/json" ) ; self.end_headers( ) ; self.wfile.write( b'{"status":"ok"}' )
else : self.send_response( 404) ; self.end_headers( )
def log_message( self, fmt, *args) : pass
2026-04-02 18:30:34 +01:00
if not API_KEY: print( "WARNING: ANTHROPIC_API_KEY not set — AIUI will return setup instructions" )
2026-03-12 22:19:04 +00:00
server = http.server.HTTPServer( ( "127.0.0.1" , PORT) , Handler)
print( f"Claude API proxy on port {PORT}" )
server.serve_forever( )
CLAUDEPROXY
chmod +x /mnt/target/opt/archipelago/claude-api-proxy.py
# Claude API proxy systemd service (disabled by default — enabled after API key is configured)
cat > /mnt/target/etc/systemd/system/claude-api-proxy.service <<'CLAUDESVC '
[ Unit]
Description = Claude API Proxy
After = network.target
[ Service]
Type = simple
2026-03-31 05:01:13 +01:00
Environment = ANTHROPIC_API_KEY = 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
2026-03-12 22:19:04 +00:00
ExecStart = /usr/bin/python3 /opt/archipelago/claude-api-proxy.py
Restart = always
RestartSec = 5
[ Install]
WantedBy = multi-user.target
CLAUDESVC
2026-03-26 09:12:16 +00:00
# Kiosk mode — X11 + Chromium fullscreen on attached display
# Not enabled by default; toggle via: sudo archipelago-kiosk enable/disable
cat > /mnt/target/usr/local/bin/archipelago-kiosk-launcher <<'KIOSK LAUNCHER'
#!/bin/bash
2026-03-26 21:16:07 +00:00
# Start X server on VT7 (VT1 stays on MOTD/console)
/usr/bin/Xorg :0 vt7 -nolisten tcp -keeptty &
2026-03-26 09:12:16 +00:00
XPID = $!
2026-03-26 21:16:07 +00:00
sleep 3
# Switch to kiosk display
chvt 7 2>/dev/null || true
2026-03-26 09:12:16 +00:00
if ! kill -0 $XPID 2>/dev/null; then
echo 'ERROR: Xorg failed to start'
exit 1
fi
export DISPLAY = :0
export HOME = /home/archipelago
xhost +SI:localuser:archipelago 2>/dev/null
xset s off 2>/dev/null
xset -dpms 2>/dev/null
xset s noblank 2>/dev/null
unclutter -idle 3 -root &
while true; do
2026-03-26 18:30:13 +00:00
# Get screen resolution for window sizing
SCREEN_RES = $( xdpyinfo 2>/dev/null | awk '/dimensions:/{print $2}' )
SCREEN_RES = ${ SCREEN_RES :- 1920x1080 }
2026-03-26 09:12:16 +00:00
sudo -u archipelago env DISPLAY = :0 HOME = /home/archipelago chromium \
--kiosk \
2026-03-26 18:30:13 +00:00
--start-fullscreen \
--start-maximized \
--window-position= 0,0 \
--window-size= ${ SCREEN_RES /x/, } \
2026-03-26 09:12:16 +00:00
--app= http://localhost/kiosk \
--noerrdialogs \
--disable-infobars \
--disable-translate \
--no-first-run \
--check-for-update-interval= 31536000 \
2026-03-30 16:35:06 +01:00
--disable-features= TranslateUI,PasswordManagerOnboarding,AutofillServerCommunication,PasswordManagerEnabled \
2026-03-26 09:12:16 +00:00
--disable-session-crashed-bubble \
--disable-save-password-bubble \
--disable-suggestions-service \
2026-03-26 21:16:07 +00:00
--password-store= basic \
2026-03-26 09:12:16 +00:00
--disable-component-update \
2026-03-27 18:19:49 +00:00
--credentials_enable_service= false \
2026-03-30 16:35:06 +01:00
--disable-gpu \
2026-03-31 23:43:32 +01:00
--disable-breakpad \
--disable-metrics \
--disable-metrics-reporting \
--metrics-recording-only \
--disable-domain-reliability \
--disable-background-networking \
--disable-background-timer-throttling \
--disable-backgrounding-occluded-windows \
2026-04-02 11:10:08 +01:00
--user-data-dir= /var/lib/archipelago/chromium-kiosk
2026-03-26 09:12:16 +00:00
sleep 3
done
kill $XPID 2>/dev/null
KIOSKLAUNCHER
chmod +x /mnt/target/usr/local/bin/archipelago-kiosk-launcher
cat > /mnt/target/etc/systemd/system/archipelago-kiosk.service <<'KIOSK SVC'
[ Unit]
Description = Archipelago Kiosk ( X11 + Chromium)
2026-04-19 18:09:46 -04:00
After = archipelago.service systemd-user-sessions.service network-online.target
Wants = archipelago.service network-online.target
2026-03-26 09:12:16 +00:00
ConditionPathExists = /usr/local/bin/archipelago-kiosk-launcher
2026-04-19 18:09:46 -04:00
Conflicts = getty@tty1.service
2026-03-26 09:12:16 +00:00
[ Service]
Type = simple
2026-04-19 18:09:46 -04:00
# First-boot health-poll window is 300s (150 × 2s). Slow hardware
# (e.g. the atom-class box at .198) was blowing past the old 60s /
# 120s window, so Chromium launched against a not-yet-ready backend
# and showed a blank window that only recovered on reboot. At 300s
# even the unbundled-FileBrowser-pull + archipelago state sync + frontend
# settle fits with headroom. TimeoutStartSec is bumped in lockstep.
ExecStartPre = /bin/bash -c 'for i in $(seq 1 150); do curl -sf http://localhost/health >/dev/null 2>&1 && exit 0; sleep 2; done; exit 0'
2026-03-26 09:12:16 +00:00
ExecStart = /usr/local/bin/archipelago-kiosk-launcher
2026-04-19 18:09:46 -04:00
TimeoutStartSec = 360
2026-03-26 09:12:16 +00:00
Restart = always
RestartSec = 5
[ Install]
WantedBy = multi-user.target
KIOSKSVC
# Toggle script: sudo archipelago-kiosk enable|disable|status
cat > /mnt/target/usr/local/bin/archipelago-kiosk <<'KIOSK TOGGLE'
#!/bin/bash
set -e
case " ${ 1 :- status } " in
enable )
echo "Enabling kiosk mode (X11 + Chromium on display)..."
systemctl enable archipelago-kiosk.service
systemctl start archipelago-kiosk.service 2>/dev/null || true
echo "Kiosk mode ENABLED. Console login (tty1) is now disabled."
echo "To access the server, use SSH or the web UI."
; ;
disable)
echo "Disabling kiosk mode (restoring console login)..."
systemctl stop archipelago-kiosk.service 2>/dev/null || true
systemctl disable archipelago-kiosk.service
systemctl restart getty@tty1.service 2>/dev/null || true
echo "Kiosk mode DISABLED. Console login restored on tty1."
; ;
status)
if systemctl is-active archipelago-kiosk.service >/dev/null 2>& 1; then
echo "Kiosk mode: ACTIVE (display showing web UI)"
elif systemctl is-enabled archipelago-kiosk.service >/dev/null 2>& 1; then
echo "Kiosk mode: ENABLED (will start on next boot)"
else
echo "Kiosk mode: DISABLED (console login on tty1)"
fi
; ;
2026-03-26 18:30:13 +00:00
toggle)
if systemctl is-active archipelago-kiosk.service >/dev/null 2>& 1; then
systemctl stop archipelago-kiosk.service 2>/dev/null || true
systemctl restart getty@tty1.service 2>/dev/null || true
chvt 1 2>/dev/null || true
else
systemctl start archipelago-kiosk.service 2>/dev/null || true
fi
; ;
2026-03-26 09:12:16 +00:00
*)
2026-03-26 18:30:13 +00:00
echo "Usage: archipelago-kiosk [enable|disable|status|toggle]"
2026-03-26 09:12:16 +00:00
echo " enable — Start kiosk (fullscreen web UI on display)"
echo " disable — Stop kiosk, restore console login"
2026-03-26 18:30:13 +00:00
echo " toggle — Switch between kiosk and terminal"
2026-03-26 09:12:16 +00:00
echo " status — Show current mode"
2026-03-26 18:30:13 +00:00
echo ""
echo "Keyboard shortcuts (from terminal):"
echo " Ctrl+Alt+F7 — Switch to kiosk display"
echo " Ctrl+Alt+F1 — Switch to terminal"
2026-03-26 09:12:16 +00:00
exit 1
; ;
esac
KIOSKTOGGLE
chmod +x /mnt/target/usr/local/bin/archipelago-kiosk
2026-02-01 05:42:05 +00:00
# Install GRUB
2026-03-28 13:39:10 +00:00
step "Installing bootloader"
2026-02-01 05:42:05 +00:00
mount --bind /dev /mnt/target/dev
mount --bind /dev/pts /mnt/target/dev/pts
mount --bind /proc /mnt/target/proc
mount --bind /sys /mnt/target/sys
mount --bind /run /mnt/target/run
2026-03-10 23:29:05 +00:00
# Set passwords reliably by directly editing /etc/shadow
# chpasswd fails silently in chroot due to missing PAM — use sed instead
echo " Setting user passwords..."
# Pre-computed SHA-512 hash of "archipelago"
ARCHY_HASH = '$6$archipelago.salt1$QpB5VPzGHOKRVKQ5cTfd4R7PYqmMH5MUx6MxFN7MbZkxWKR3WxFp.RV4tBVbJiv.6iWXfHeq3vDph7G.XfPz0'
# Generate hash at install time if openssl is available, otherwise use pre-computed
if command -v openssl >/dev/null 2>& 1; then
ARCHY_HASH = $( openssl passwd -6 -salt "archy.install" "archipelago" )
fi
# Direct shadow file manipulation — works without PAM
sed -i " s|^archipelago:[^:]*:|archipelago: ${ ARCHY_HASH } :| " /mnt/target/etc/shadow
sed -i " s|^root:[^:]*:|root: ${ ARCHY_HASH } :| " /mnt/target/etc/shadow
# Verify the password was set (not locked/empty)
if grep -q "^archipelago:[!*]" /mnt/target/etc/shadow 2>/dev/null; then
echo " WARNING: Password still locked, trying chpasswd fallback..."
chroot /mnt/target bash -c 'echo "archipelago:archipelago" | chpasswd' 2>/dev/null || true
fi
echo " Passwords set for archipelago and root users"
2026-03-26 17:31:26 +00:00
# Remove shim-signed before grub-install to prevent hooks re-creating shim files
chroot /mnt/target dpkg --purge shim-signed shim-helpers-amd64-signed shim-helpers-arm64-signed 2>/dev/null || true
2026-03-21 01:11:05 +00:00
# UEFI boot: install to fallback path (/EFI/BOOT/BOOTX64.EFI) for maximum compatibility
echo " Installing UEFI bootloader..."
2026-03-29 12:06:19 +01:00
if run chroot /mnt/target grub-install --target= ${ GRUB_TARGET } --efi-directory= /boot/efi --bootloader-id= archipelago --removable; then
ok "UEFI bootloader installed (removable/fallback path)"
2026-03-21 01:11:05 +00:00
else
2026-03-29 12:06:19 +01:00
warn "UEFI removable install failed, trying standard..."
if run chroot /mnt/target grub-install --target= ${ GRUB_TARGET } --efi-directory= /boot/efi --bootloader-id= archipelago; then
ok "UEFI bootloader installed (standard)"
2026-03-21 01:11:05 +00:00
else
2026-03-29 12:06:19 +01:00
fail "UEFI bootloader installation failed"
2026-03-21 01:11:05 +00:00
fi
fi
2026-03-26 09:12:16 +00:00
# EFI boot: grub-install --removable places unsigned GRUB at /EFI/BOOT/BOOTX64.EFI
# No shim chain — Secure Boot must be disabled. shim-signed was removed from rootfs
# because it installs BOOTX64.CSV + shimx64.efi which cause "Failed to open \EFI\BOOT\"
# errors with garbled filenames on every boot.
2026-03-25 20:47:14 +00:00
echo " Verifying EFI boot files..."
EFI_BOOT_DIR = "/mnt/target/boot/efi/EFI/BOOT"
2026-03-21 01:11:05 +00:00
if [ " $ARCH " = "x86_64" ] ; then
EFI_BOOT_BINARY = "BOOTX64.EFI"
else
EFI_BOOT_BINARY = "BOOTAA64.EFI"
fi
2026-03-26 09:12:16 +00:00
# Remove any residual shim chain files (from grub-efi-*-signed package hooks)
# These cause firmware to try loading garbled vendor paths before falling back
for shim_file in shimx64.efi mmx64.efi fbx64.efi BOOTX64.CSV shimaa64.efi mmaa64.efi fbaa64.efi BOOTAA64.CSV; do
if [ -f " $EFI_BOOT_DIR / $shim_file " ] && [ " $shim_file " != " $EFI_BOOT_BINARY " ] ; then
rm -f " $EFI_BOOT_DIR / $shim_file "
echo " Removed shim artifact: $shim_file "
fi
done
# Also remove vendor-specific EFI directory (shim creates /EFI/archipelago/)
rm -rf "/mnt/target/boot/efi/EFI/archipelago" 2>/dev/null || true
2026-03-26 17:31:26 +00:00
# Nuclear cleanup: remove everything except the GRUB binary from EFI/BOOT
if [ -d " $EFI_BOOT_DIR " ] ; then
for f in " $EFI_BOOT_DIR " /*; do
[ " $( basename " $f " ) " = " $EFI_BOOT_BINARY " ] && continue
[ " $( basename " $f " ) " = "grub.cfg" ] && continue
rm -f " $f " 2>/dev/null && echo " Removed: $( basename " $f " ) "
done
fi
2026-03-25 20:47:14 +00:00
if [ -f " $EFI_BOOT_DIR / $EFI_BOOT_BINARY " ] ; then
echo " ✅ UEFI boot binary present: $EFI_BOOT_DIR / $EFI_BOOT_BINARY "
2026-03-25 19:25:55 +00:00
ls -la " $EFI_BOOT_DIR / "
2026-03-21 01:11:05 +00:00
else
2026-03-25 20:47:14 +00:00
echo " ❌ Missing $EFI_BOOT_DIR / $EFI_BOOT_BINARY — boot will fail! "
2026-03-21 01:11:05 +00:00
fi
# Legacy BIOS boot: only install if the installer booted in Legacy BIOS mode
# (if /sys/firmware/efi exists, the machine supports UEFI — no need for BIOS fallback)
if [ -n " ${ GRUB_BIOS_TARGET } " ] && [ ! -d /sys/firmware/efi ] ; then
echo " Installing Legacy BIOS bootloader (machine booted in BIOS mode)..."
2026-03-29 12:06:19 +01:00
if run chroot /mnt/target grub-install --target= ${ GRUB_BIOS_TARGET } " ${ TARGET_DISK } " ; then
ok "Legacy BIOS bootloader installed"
2026-03-21 01:11:05 +00:00
else
2026-03-29 12:06:19 +01:00
warn "Legacy BIOS bootloader failed (UEFI-only boot)"
2026-03-21 01:11:05 +00:00
fi
elif [ -n " ${ GRUB_BIOS_TARGET } " ] ; then
echo " Skipping Legacy BIOS bootloader (machine supports UEFI)"
fi
2026-03-27 18:19:49 +00:00
# Clean any stale live-boot artifacts (should not exist in the custom rootfs,
# but clean up defensively in case Docker base image pulled them in)
2026-03-25 16:51:14 +00:00
rm -f /mnt/target/etc/initramfs-tools/conf.d/live-boot* 2>/dev/null || true
rm -f /mnt/target/usr/share/initramfs-tools/scripts/live* 2>/dev/null || true
rm -f /mnt/target/usr/share/initramfs-tools/hooks/live* 2>/dev/null || true
2026-04-08 15:19:14 +02:00
# Brand GRUB as Archipelago (default says "Debian GNU/Linux")
sed -i 's/^GRUB_DISTRIBUTOR=.*/GRUB_DISTRIBUTOR="Archipelago"/' /mnt/target/etc/default/grub
grep -q '^GRUB_DISTRIBUTOR' /mnt/target/etc/default/grub || echo 'GRUB_DISTRIBUTOR="Archipelago"' >> /mnt/target/etc/default/grub
2026-03-25 16:56:02 +00:00
# Suppress os-prober warning in GRUB
echo "GRUB_DISABLE_OS_PROBER=true" >> /mnt/target/etc/default/grub
2026-04-02 20:28:53 +01:00
# GFX fallback for hardware without graphical GRUB support
echo 'GRUB_GFXMODE=auto' >> /mnt/target/etc/default/grub
echo 'GRUB_GFXPAYLOAD_LINUX=keep' >> /mnt/target/etc/default/grub
echo 'GRUB_TERMINAL_OUTPUT=gfxterm' >> /mnt/target/etc/default/grub
2026-03-25 16:56:02 +00:00
2026-03-27 18:19:49 +00:00
# Install Archipelago GRUB theme on target system
if [ -d " $BOOT_MEDIA /boot/grub/themes/archipelago " ] ; then
mkdir -p /mnt/target/boot/grub/themes/archipelago
cp " $BOOT_MEDIA /boot/grub/themes/archipelago/ " * /mnt/target/boot/grub/themes/archipelago/
echo 'GRUB_THEME="/boot/grub/themes/archipelago/theme.txt"' >> /mnt/target/etc/default/grub
echo " Installed Archipelago GRUB theme on target"
fi
feat: custom boot branding, MBR fix, Plymouth theme, CI smoke tests
Boot fix:
- Ship proven Debian Live MBR (4552) as branding/isohdpfx.bin — the
ISOLINUX package MBR (33ed) doesn't boot on all hardware. This was
the root cause of "machine doesn't pick up the USB".
Branding:
- Custom GRUB background: pixel-art floating island (1024x574)
- Archipelago pixel-art logo for Plymouth boot splash
- GRUB theme: dark background, orange selected item, no broken font refs
- Plymouth theme: script-based with progress bar, LUKS prompt support
- Plymouth + splash added to target rootfs packages
- GRUB theme installed on both installer ISO and target system
- Serial console (ttyS0) added to kernel params for QEMU debugging
CI improvements:
- Smoke test step: mounts ISO, verifies all critical files, checks
initrd has live-boot, confirms boot=live in grub.cfg. Fails build
before copying to Builds if any check fails.
Dev workflow:
- dev-branding.sh: extract ISO, swap branding, repackage, boot in QEMU
(~10 seconds vs 20 min full rebuild)
- generate-grub-background.py: procedural cyberpunk background generator
- generate-plymouth-logo.py: procedural logo generator
- Improved test-iso-qemu.sh: --bios/--nographic flags, serial logging
Build:
- Simplified live-boot install (clean chroot, no complex fallbacks)
- Static branding images preferred, generators as fallback
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 22:26:56 +00:00
# Install Archipelago Plymouth theme on target system
if [ -d " $BOOT_MEDIA /archipelago/plymouth-theme " ] ; then
PLYMOUTH_DIR = "/mnt/target/usr/share/plymouth/themes/archipelago"
mkdir -p " $PLYMOUTH_DIR "
cp " $BOOT_MEDIA /archipelago/plymouth-theme/ " * " $PLYMOUTH_DIR / "
# Set as default Plymouth theme
chroot /mnt/target plymouth-set-default-theme archipelago 2>/dev/null || \
ln -sf /usr/share/plymouth/themes/archipelago/archipelago.plymouth \
/mnt/target/etc/alternatives/default.plymouth 2>/dev/null || true
fix: ISO boot, container installs, VPN, nginx, companion input
- LUKS auto-unlock: initramfs hook + systemd service + nofail fstab
- Rootfs packages: add passt, aardvark-dns, netavark, nftables for Podman 5.x
- nginx: resolver + variable proxy_pass for external domains (DNS at boot)
- Boot: loglevel=0 suppresses kernel warnings, serial console for QEMU
- Container installs: write configs before chown, sudo chown for LUKS volumes
- Container installs: build UI sidecars locally (not from registry) for auth injection
- Bitcoin UI: inject RPC auth from secrets file, --no-cache rebuild
- Secrets: chown to archipelago user in first-boot (backend needs read access)
- Podman: image_copy_tmp_dir for read-only /var/tmp in user namespace
- NostrVPN: enable service in auto-install, always include public relays
- NostrVPN: read tunnel IP from nvpn status (not just config file)
- VPN invite: v2 base64 no-pad format matching phone app
- Companion input: relay always active, kiosk skips relay listener (prevents double input)
- dev-start.sh: production build includes AIUI deployment
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 03:10:49 -04:00
# Configure clean boot: splash, suppress kernel noise, hide cursor
sed -i 's/GRUB_CMDLINE_LINUX_DEFAULT=".*"/GRUB_CMDLINE_LINUX_DEFAULT="quiet splash loglevel=0 rd.systemd.show_status=false vt.global_cursor_default=0 acpi=force"/' \
/mnt/target/etc/default/grub 2>/dev/null || true
feat: custom boot branding, MBR fix, Plymouth theme, CI smoke tests
Boot fix:
- Ship proven Debian Live MBR (4552) as branding/isohdpfx.bin — the
ISOLINUX package MBR (33ed) doesn't boot on all hardware. This was
the root cause of "machine doesn't pick up the USB".
Branding:
- Custom GRUB background: pixel-art floating island (1024x574)
- Archipelago pixel-art logo for Plymouth boot splash
- GRUB theme: dark background, orange selected item, no broken font refs
- Plymouth theme: script-based with progress bar, LUKS prompt support
- Plymouth + splash added to target rootfs packages
- GRUB theme installed on both installer ISO and target system
- Serial console (ttyS0) added to kernel params for QEMU debugging
CI improvements:
- Smoke test step: mounts ISO, verifies all critical files, checks
initrd has live-boot, confirms boot=live in grub.cfg. Fails build
before copying to Builds if any check fails.
Dev workflow:
- dev-branding.sh: extract ISO, swap branding, repackage, boot in QEMU
(~10 seconds vs 20 min full rebuild)
- generate-grub-background.py: procedural cyberpunk background generator
- generate-plymouth-logo.py: procedural logo generator
- Improved test-iso-qemu.sh: --bios/--nographic flags, serial logging
Build:
- Simplified live-boot install (clean chroot, no complex fallbacks)
- Static branding images preferred, generators as fallback
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 22:26:56 +00:00
echo " Installed Archipelago Plymouth theme on target"
fi
2026-03-21 01:11:05 +00:00
# Regenerate initramfs — the one from Docker export is corrupt/incomplete
# (Docker builds have limited /proc, /sys, /dev so initramfs generation fails silently)
echo " Regenerating initramfs..."
2026-03-29 12:06:19 +01:00
run chroot /mnt/target update-initramfs -u -k all
2026-02-01 05:42:05 +00:00
2026-03-29 12:06:19 +01:00
run chroot /mnt/target update-grub
2026-02-01 05:42:05 +00:00
2026-03-28 13:39:10 +00:00
# CRITICAL: Write EFI grub.cfg that finds the root filesystem and loads the full config.
# grub-install --removable creates a BOOTX64.EFI that looks for grub.cfg on the
# EFI FAT partition (/EFI/BOOT/grub.cfg). This stub must search for the root FS
# and then load the full /boot/grub/grub.cfg from ext4.
ROOT_UUID = $( blkid -s UUID -o value " $ROOT_PART " )
if [ -n " $ROOT_UUID " ] && [ -d "/mnt/target/boot/efi/EFI/BOOT" ] ; then
cat > /mnt/target/boot/efi/EFI/BOOT/grub.cfg <<EFICFG
search.fs_uuid $ROOT_UUID root
set prefix = ( \$ root) /boot/grub
configfile \$ prefix/grub.cfg
EFICFG
echo " Wrote EFI grub.cfg (root UUID= $ROOT_UUID ) "
else
echo " WARNING: Could not write EFI grub.cfg (UUID= $ROOT_UUID ) "
fi
2026-03-18 10:50:13 +00:00
# Install udev rule for mesh radio stable naming (/dev/mesh-radio)
2026-03-27 18:19:49 +00:00
MESH_RULES = ""
2026-04-28 15:00:58 -04:00
for p in " $BOOT_MEDIA /99-mesh-radio.rules " /cdrom/99-mesh-radio.rules " $BOOT_MEDIA /archipelago/../configs/99-mesh-radio.rules " ; do
2026-03-27 18:19:49 +00:00
[ -f " $p " ] && MESH_RULES = " $p " && break
done
if [ -n " $MESH_RULES " ] ; then
cp " $MESH_RULES " /mnt/target/etc/udev/rules.d/99-mesh-radio.rules
2026-03-18 10:50:13 +00:00
echo " Installed mesh radio udev rule"
fi
2026-03-26 20:58:34 +00:00
# First-boot diagnostics — runs once, captures system state for debugging
cat > /mnt/target/usr/local/bin/archipelago-diagnostics <<'DIAG'
#!/bin/bash
LOG = "/var/log/archipelago-first-boot-diagnostics.log"
echo "=== Archipelago First Boot Diagnostics ===" > " $LOG "
echo " Date: $( date -u) " >> " $LOG "
echo " Kernel: $( uname -r) " >> " $LOG "
echo " Hostname: $( hostname) " >> " $LOG "
echo " IP: $( hostname -I 2>/dev/null) " >> " $LOG "
echo "" >> " $LOG "
echo "=== Disk ===" >> " $LOG "
lsblk -o NAME,SIZE,TYPE,MOUNTPOINT,FSTYPE >> " $LOG " 2>& 1
df -h >> " $LOG " 2>& 1
echo "" >> " $LOG "
echo "=== LUKS ===" >> " $LOG "
ls -la /dev/mapper/archipelago-data 2>& 1 >> " $LOG "
cryptsetup status archipelago-data >> " $LOG " 2>& 1 || echo "No LUKS" >> " $LOG "
echo "" >> " $LOG "
echo "=== Services ===" >> " $LOG "
for svc in nginx archipelago archipelago-kiosk archipelago-load-images \
archipelago-first-boot-containers archipelago-setup-tor \
console-setup; do
STATUS = $( systemctl is-active " $svc " 2>/dev/null || echo "inactive" )
ENABLED = $( systemctl is-enabled " $svc " 2>/dev/null || echo "disabled" )
printf " %-40s %s / %s\n" " $svc " " $STATUS " " $ENABLED " >> " $LOG "
done
echo "" >> " $LOG "
echo "=== Failed Services ===" >> " $LOG "
systemctl --failed --no-pager >> " $LOG " 2>& 1
echo "" >> " $LOG "
echo "=== Nginx ===" >> " $LOG "
nginx -t >> " $LOG " 2>& 1
echo "" >> " $LOG "
echo "=== EFI Boot ===" >> " $LOG "
ls -laR /boot/efi/EFI/ >> " $LOG " 2>& 1
echo "" >> " $LOG "
echo "=== SSL Cert ===" >> " $LOG "
ls -la /etc/archipelago/ssl/ >> " $LOG " 2>& 1
echo "" >> " $LOG "
echo "=== Podman ===" >> " $LOG "
su - archipelago -c "podman ps -a --format '{{.Names}} {{.Status}}'" >> " $LOG " 2>& 1
echo "" >> " $LOG "
echo "=== Memory ===" >> " $LOG "
free -h >> " $LOG " 2>& 1
echo "" >> " $LOG "
echo "=== Journal Errors (last 50) ===" >> " $LOG "
journalctl -p err --no-pager -n 50 >> " $LOG " 2>& 1
echo " Diagnostics saved to $LOG "
DIAG
chmod +x /mnt/target/usr/local/bin/archipelago-diagnostics
cat > /mnt/target/etc/systemd/system/archipelago-diagnostics.service <<'DIAGSVC'
[ Unit]
Description = Archipelago First Boot Diagnostics
After = multi-user.target archipelago.service nginx.service
ConditionPathExists = !/var/log/archipelago-first-boot-diagnostics.log
[ Service]
Type = oneshot
ExecStartPre = /bin/sleep 30
ExecStart = /usr/local/bin/archipelago-diagnostics
RemainAfterExit = yes
[ Install]
WantedBy = multi-user.target
DIAGSVC
2026-03-26 18:30:13 +00:00
# Ensure SSL cert exists for nginx HTTPS (safety net if rootfs build missed it)
if [ ! -f /mnt/target/etc/archipelago/ssl/archipelago.crt ] ; then
mkdir -p /mnt/target/etc/archipelago/ssl
chroot /mnt/target openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
-keyout /etc/archipelago/ssl/archipelago.key \
-out /etc/archipelago/ssl/archipelago.crt \
-subj "/C=XX/ST=Bitcoin/L=Node/O=Archipelago/CN=archipelago" 2>/dev/null
chmod 600 /mnt/target/etc/archipelago/ssl/archipelago.key
echo " Generated self-signed SSL certificate"
fi
fix: production onboarding, CI tests, container security, keyboard nav
Install & Onboarding:
- Remove DEV_MODE=true from production ISO service file (auto-created
users, skipped password setup)
- Auto-install no longer overwrites rootfs service file with bad template
- Login.vue always checks auth.isSetup — shows password creation form
on fresh install without requiring dev build flag
- Deploy image-versions.sh to /opt/archipelago/scripts/ on installed nodes
- First-boot-containers sources image-versions.sh, runs podman as
archipelago user (rootless), enables linger + podman.socket
- Correct volume ownership (100000:100000 for rootless UID mapping)
Container Security:
- FileBrowser: add --cap-add=DAC_OVERRIDE for rootless podman volume access
- FileBrowser: add --read-only, /data volume for database, proper cmd args
- First-boot script matches backend config (security hardening + health check)
CI Pipeline:
- Add vue-tsc type check + vitest run to build-iso.yml (runs every push)
- Add post-install-tests.yml workflow (workflow_dispatch, SSH to target)
- Build report: set +eo pipefail, fix rootfs path, add || true guards
- Bundle run-post-install-tests.sh into ISO
E2E Test Suite (scripts/run-post-install-tests.sh):
- Phase 1: Install verification (files, services, podman, linger, DEV_MODE check)
- Phase 2: Onboarding flow (auth.isSetup, auth.setup, login, DID, complete)
- Phase 3: Container lifecycle (install 3 apps via package.install RPC,
verify running, stop, verify stopped, restart, verify running, health)
- Phase 4: Log verification (first-boot log, diagnostics, journal errors)
- Correct package.install params: {"id", "dockerImage"}
Frontend:
- Fix backdrop-filter tab-switch bug (keep animations paused during rebuild)
- Dashboard glitch animations paused during tab-hidden
- Gamepad nav: auto-focus first container on route change
- Tab roving: Left/Right on role="tab" cycles and activates sibling tabs
- ContainerApps: data-controller-launch on running app cards
- 515 tests passing (fixed 30 broken, added 19 new keyboard nav tests)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 16:16:57 +00:00
# Enable linger for rootless podman (containers survive logout)
mkdir -p /mnt/target/var/lib/systemd/linger
touch /mnt/target/var/lib/systemd/linger/archipelago
# Enable podman socket for archipelago user (activated on first login/boot)
mkdir -p /mnt/target/home/archipelago/.config/systemd/user/sockets.target.wants
ln -sf /usr/lib/systemd/user/podman.socket /mnt/target/home/archipelago/.config/systemd/user/sockets.target.wants/podman.socket 2>/dev/null || true
chown -R 1000:1000 /mnt/target/home/archipelago/.config 2>/dev/null || true
# Ensure /run/user/1000 is created at boot for podman socket
mkdir -p /mnt/target/etc/tmpfiles.d
echo 'd /run/user/1000 0700 archipelago archipelago -' > /mnt/target/etc/tmpfiles.d/archipelago-runtime.conf
2026-04-23 12:02:46 -04:00
# Pre-create /var/log/archipelago/ and container-installs.log so the
# backend (running as `archipelago`) can append to them without needing
# root. Logrotate rotates files in this directory daily.
cat > /mnt/target/etc/tmpfiles.d/archipelago-logs.conf <<'LOGSTMPFIL ES'
d /var/log/archipelago 0755 archipelago archipelago - -
f /var/log/archipelago/container-installs.log 0644 archipelago archipelago - -
LOGSTMPFILES
2026-04-02 16:15:04 +01:00
# Bootstrap switchover — checks when local Bitcoin finishes IBD and switches services
cat > /mnt/target/etc/systemd/system/archipelago-bootstrap-switchover.service <<'BSSERVICE'
[ Unit]
Description = Switch Bitcoin-dependent services from bootstrap to local node
After = archipelago-first-boot-containers.service
[ Service]
Type = oneshot
User = archipelago
ExecStart = /opt/archipelago/scripts/bootstrap-switchover.sh
BSSERVICE
cat > /mnt/target/etc/systemd/system/archipelago-bootstrap-switchover.timer <<'BSTIMER'
[ Unit]
Description = Periodically check if local Bitcoin is synced and switch from bootstrap
[ Timer]
OnBootSec = 10min
OnUnitActiveSec = 5min
Persistent = true
[ Install]
WantedBy = timers.target
BSTIMER
# Copy bootstrap config to install target
if [ -f " $BOOT_MEDIA /archipelago/bootstrap.conf " ] ; then
cp " $BOOT_MEDIA /archipelago/bootstrap.conf " /mnt/target/opt/archipelago/bootstrap.conf
chmod 600 /mnt/target/opt/archipelago/bootstrap.conf
chown root:root /mnt/target/opt/archipelago/bootstrap.conf
fi
# Copy bootstrap switchover script
if [ -f " $BOOT_MEDIA /archipelago/scripts/bootstrap-switchover.sh " ] ; then
cp " $BOOT_MEDIA /archipelago/scripts/bootstrap-switchover.sh " /mnt/target/opt/archipelago/scripts/
chmod +x /mnt/target/opt/archipelago/scripts/bootstrap-switchover.sh
fi
2026-02-01 05:42:05 +00:00
# Enable services
2026-04-02 16:15:04 +01:00
chroot /mnt/target systemctl enable archipelago-bootstrap-switchover.timer 2>/dev/null || true
2026-02-01 05:42:05 +00:00
chroot /mnt/target systemctl enable archipelago.service 2>/dev/null || true
chroot /mnt/target systemctl enable nginx.service 2>/dev/null || true
chroot /mnt/target systemctl enable archipelago-load-images.service 2>/dev/null || true
2026-02-17 15:03:34 +00:00
chroot /mnt/target systemctl enable archipelago-setup-tor.service 2>/dev/null || true
2026-02-25 18:04:41 +00:00
chroot /mnt/target systemctl enable archipelago-first-boot-containers.service 2>/dev/null || true
2026-03-26 19:50:59 +00:00
chroot /mnt/target systemctl enable archipelago-kiosk.service 2>/dev/null || true
fix: ISO boot, container installs, VPN, nginx, companion input
- LUKS auto-unlock: initramfs hook + systemd service + nofail fstab
- Rootfs packages: add passt, aardvark-dns, netavark, nftables for Podman 5.x
- nginx: resolver + variable proxy_pass for external domains (DNS at boot)
- Boot: loglevel=0 suppresses kernel warnings, serial console for QEMU
- Container installs: write configs before chown, sudo chown for LUKS volumes
- Container installs: build UI sidecars locally (not from registry) for auth injection
- Bitcoin UI: inject RPC auth from secrets file, --no-cache rebuild
- Secrets: chown to archipelago user in first-boot (backend needs read access)
- Podman: image_copy_tmp_dir for read-only /var/tmp in user namespace
- NostrVPN: enable service in auto-install, always include public relays
- NostrVPN: read tunnel IP from nvpn status (not just config file)
- VPN invite: v2 base64 no-pad format matching phone app
- Companion input: relay always active, kiosk skips relay listener (prevents double input)
- dev-start.sh: production build includes AIUI deployment
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 03:10:49 -04:00
chroot /mnt/target systemctl enable nostr-vpn.service 2>/dev/null || true
2026-04-02 01:28:11 +01:00
# Enable claude-api-proxy (create symlink manually — chroot systemctl can fail)
chroot /mnt/target systemctl enable claude-api-proxy.service 2>/dev/null || \
ln -sf /etc/systemd/system/claude-api-proxy.service /mnt/target/etc/systemd/system/multi-user.target.wants/claude-api-proxy.service 2>/dev/null || true
2026-03-27 11:12:31 +00:00
2026-03-27 17:17:18 +00:00
# Fix console-setup: setupcon needs /tmp writable, add ordering dependency
mkdir -p /mnt/target/etc/systemd/system/console-setup.service.d
cat > /mnt/target/etc/systemd/system/console-setup.service.d/fix-tmp.conf <<'CONSOLEFIX'
[ Unit]
After = tmp.mount systemd-tmpfiles-setup.service
Wants = tmp.mount
[ Service]
ExecStartPre = /bin/mkdir -p /tmp
CONSOLEFIX
2026-03-27 11:12:31 +00:00
# Auto-login on tty1 — no password prompt on console
mkdir -p /mnt/target/etc/systemd/system/getty@tty1.service.d
cat > /mnt/target/etc/systemd/system/getty@tty1.service.d/autologin.conf <<'AUTOLOGIN'
[ Service]
ExecStart =
ExecStart = -/sbin/agetty --autologin archipelago --noclear %I $TERM
AUTOLOGIN
2026-03-26 20:58:34 +00:00
chroot /mnt/target systemctl enable archipelago-diagnostics.service 2>/dev/null || true
2026-02-01 05:42:05 +00:00
2026-03-27 17:17:18 +00:00
# Post-install smoke test — runs Phase 1 (install verification) only on first boot
# Does NOT run onboarding or create passwords — lets user do that via the UI
2026-03-27 16:19:33 +00:00
cat > /mnt/target/etc/systemd/system/archipelago-post-install-tests.service <<'PITSERVICE'
[ Unit]
2026-03-27 17:17:18 +00:00
Description = Archipelago Install Verification ( first boot)
2026-03-27 16:19:33 +00:00
After = archipelago.service archipelago-first-boot-containers.service nginx.service
Wants = archipelago.service nginx.service
ConditionPathExists = !/var/lib/archipelago/.post-install-tests-done
[ Service]
Type = oneshot
ExecStartPre = /bin/bash -c 'for i in $(seq 1 30); do curl -sf http://127.0.0.1:5678/health >/dev/null 2>&1 && exit 0; sleep 2; done; exit 0'
2026-03-27 17:17:18 +00:00
ExecStart = /bin/bash -c '/opt/archipelago/scripts/run-post-install-tests.sh --phase1-only 2>&1 | tee /var/log/archipelago-post-install-tests.log; touch /var/lib/archipelago/.post-install-tests-done'
2026-03-27 16:19:33 +00:00
RemainAfterExit = yes
StandardOutput = journal+console
StandardError = journal+console
2026-03-27 17:17:18 +00:00
TimeoutStartSec = 120
2026-03-27 16:19:33 +00:00
[ Install]
WantedBy = multi-user.target
PITSERVICE
chroot /mnt/target systemctl enable archipelago-post-install-tests.service 2>/dev/null || true
feat: add build report and first-boot diagnostics
CI build report: checks rootfs contents (nginx, SSL, keyboard, kiosk,
lid config, backend, frontend) and ISO contents after build. Reports
in the Actions log so build issues are immediately visible.
First-boot diagnostics: one-shot systemd service runs 30s after first
boot, logs service status, nginx test, SSL certs, LUKS, podman,
kiosk, console-setup, disk, network, and journal errors to
/var/log/archipelago-first-boot-diag.log. Only runs once (ConditionPathExists).
SSH in and cat the log to debug any fresh install issues.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 20:54:32 +00:00
# Install first-boot diagnostic script — runs once after first boot and logs system state
cat > /mnt/target/opt/archipelago/scripts/first-boot-diag.sh <<'DIAGSCRIPT'
#!/bin/bash
LOG = "/var/log/archipelago-first-boot-diag.log"
exec > " $LOG " 2>& 1
echo "=== Archipelago First Boot Diagnostics ==="
echo " Date: $( date -u) "
echo " Hostname: $( hostname) "
echo " Kernel: $( uname -r) "
echo " IP: $( hostname -I 2>/dev/null | awk '{print $1}' ) "
echo ""
echo "=== Build Info ==="
cat /opt/archipelago/build-info.txt 2>/dev/null || echo "No build-info.txt"
echo ""
echo "=== Services ==="
for svc in nginx archipelago archipelago-kiosk archipelago-load-images archipelago-first-boot-containers; do
STATUS = $( systemctl is-active " $svc " 2>/dev/null || echo "missing" )
ENABLED = $( systemctl is-enabled " $svc " 2>/dev/null || echo "missing" )
printf " %-45s active=%-10s enabled=%s\n" " $svc " " $STATUS " " $ENABLED "
done
echo ""
echo "=== Nginx Test ==="
nginx -t 2>& 1
echo ""
echo "=== SSL Cert ==="
ls -la /etc/archipelago/ssl/ 2>/dev/null || echo " No SSL directory"
echo ""
echo "=== EFI Boot ==="
ls -la /boot/efi/EFI/BOOT/ 2>/dev/null || echo " No EFI/BOOT directory"
echo ""
echo "=== LUKS ==="
ls -la /dev/mapper/archipelago-data 2>/dev/null && echo " LUKS volume open" || echo " No LUKS volume"
cat /etc/crypttab 2>/dev/null
echo ""
echo "=== Podman ==="
podman --version 2>/dev/null || echo " podman not found"
podman ps -a --format "{{.Names}} {{.Status}}" 2>/dev/null | head -20
echo ""
echo "=== Kiosk ==="
systemctl status archipelago-kiosk --no-pager 2>& 1 | head -10
echo ""
echo "=== Console Setup ==="
systemctl status console-setup --no-pager 2>& 1 | head -5
cat /etc/default/keyboard 2>/dev/null || echo " No keyboard config"
echo ""
echo "=== Logind (Lid) ==="
cat /etc/systemd/logind.conf.d/lid-ignore.conf 2>/dev/null || echo " No lid config"
echo ""
echo "=== Disk ==="
df -h / /boot/efi /var/lib/archipelago 2>/dev/null
echo ""
echo "=== Network ==="
ip addr show | grep -E "inet |link/" | head -10
echo ""
echo "=== Journal Errors (last boot) ==="
journalctl -b -p err --no-pager 2>/dev/null | tail -30
echo ""
echo "=== Done ==="
DIAGSCRIPT
chmod +x /mnt/target/opt/archipelago/scripts/first-boot-diag.sh
# Systemd oneshot service for first-boot diagnostics
cat > /mnt/target/etc/systemd/system/archipelago-diag.service <<'DIAGSVC'
[ Unit]
Description = Archipelago First Boot Diagnostics
After = multi-user.target archipelago.service nginx.service
ConditionPathExists = !/var/log/archipelago-first-boot-diag.log
[ Service]
Type = oneshot
ExecStartPre = /bin/sleep 30
ExecStart = /opt/archipelago/scripts/first-boot-diag.sh
RemainAfterExit = yes
[ Install]
WantedBy = multi-user.target
DIAGSVC
chroot /mnt/target systemctl enable archipelago-diag.service 2>/dev/null || true
# Write build info into the installed system
cat > /mnt/target/opt/archipelago/build-info.txt <<BUILDINFO
2026-03-31 23:43:32 +01:00
version = __BUILD_VERSION__
build = __BUILD_NUM__
commit = __GIT_SHORT__
feat: add build report and first-boot diagnostics
CI build report: checks rootfs contents (nginx, SSL, keyboard, kiosk,
lid config, backend, frontend) and ISO contents after build. Reports
in the Actions log so build issues are immediately visible.
First-boot diagnostics: one-shot systemd service runs 30s after first
boot, logs service status, nginx test, SSL certs, LUKS, podman,
kiosk, console-setup, disk, network, and journal errors to
/var/log/archipelago-first-boot-diag.log. Only runs once (ConditionPathExists).
SSH in and cat the log to debug any fresh install issues.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 20:54:32 +00:00
date = $( date -u '+%Y-%m-%d %H:%M:%S UTC' )
type = unbundled
BUILDINFO
2026-03-28 11:31:48 +00:00
# Save install log BEFORE unmounting target
cp " $INSTALL_LOG " /mnt/target/var/log/archipelago-install.log 2>/dev/null || true
2026-02-01 05:42:05 +00:00
# Cleanup
sync
umount /mnt/target/run 2>/dev/null || true
umount /mnt/target/sys 2>/dev/null || true
umount /mnt/target/proc 2>/dev/null || true
umount /mnt/target/dev/pts 2>/dev/null || true
umount /mnt/target/dev 2>/dev/null || true
umount /mnt/target/boot/efi 2>/dev/null || true
2026-03-26 09:12:16 +00:00
umount /mnt/target/var/lib/archipelago 2>/dev/null || true
cryptsetup close archipelago-data 2>/dev/null || true
2026-02-01 05:42:05 +00:00
umount /mnt/target 2>/dev/null || true
2026-02-14 16:44:20 +00:00
echo ""
2026-03-29 15:43:57 +01:00
hrule
2026-02-14 16:44:20 +00:00
echo ""
2026-03-31 21:23:29 +01:00
# Celebration animation if TUI available
[ " ${ TUI_AVAILABLE :- } " = "1" ] && tui_complete
2026-03-28 23:58:42 +00:00
p " ${ ORANGE_BRIGHT } ✓ Installation Complete ${ NC } "
2026-02-14 16:44:20 +00:00
echo ""
2026-03-28 23:58:42 +00:00
p " ${ ORANGE_DIM } After reboot, access from any device: ${ NC } "
2026-02-01 05:42:05 +00:00
echo ""
2026-03-28 23:58:42 +00:00
p " ${ ORANGE } http://<this machine's IP> ${ NC } "
2026-02-01 05:42:05 +00:00
echo ""
2026-03-28 23:58:42 +00:00
p " ${ WHITE } SSH ssh archipelago@<IP> ${ NC } "
p " ${ WHITE } Password archipelago ${ NC } "
p " ${ WHITE } Web Login password123 ${ NC } "
2026-03-25 16:56:02 +00:00
echo ""
2026-03-28 11:31:48 +00:00
hrule
2026-03-25 16:56:02 +00:00
echo ""
2026-03-28 13:14:33 +00:00
# Suppress kernel messages on console (SquashFS errors when USB is pulled)
echo 1 > /proc/sys/kernel/printk 2>/dev/null || true
feat: add build report and first-boot diagnostics
CI build report: checks rootfs contents (nginx, SSL, keyboard, kiosk,
lid config, backend, frontend) and ISO contents after build. Reports
in the Actions log so build issues are immediately visible.
First-boot diagnostics: one-shot systemd service runs 30s after first
boot, logs service status, nginx test, SSL certs, LUKS, podman,
kiosk, console-setup, disk, network, and journal errors to
/var/log/archipelago-first-boot-diag.log. Only runs once (ConditionPathExists).
SSH in and cat the log to debug any fresh install issues.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 20:54:32 +00:00
fix: CSRF 403 blocking all operations + reboot after install
CSRF fix (THE BLOCKER):
- After remember-me session restore, the browser has a stale CSRF
cookie but a new session token. ALL subsequent RPC calls return 403.
- Fix: exempt read-only polling methods (node-messages-received,
server.echo, system.stats, tor.status, etc.) from CSRF validation.
CSRF still protects state-changing operations (install, uninstall,
start, stop, restart, settings changes).
Reboot fix:
- The separate /tmp/archipelago-reboot.sh approach failed because
/bin/bash is on the squashfs which gets unmounted when USB is pulled.
- Fix: do everything inline in the installer script — show message,
unmount USB, wait for Enter, then reboot. Use sysrq-trigger first
(kernel-level, doesn't need userspace binaries).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 22:42:09 +01:00
# Show completion message, unmount USB, then reboot
# All done inline — no separate script needed (avoids /bin/bash dependency on squashfs)
2026-03-28 23:41:40 +00:00
echo ""
2026-03-31 21:23:29 +01:00
if [ " ${ TUI_AVAILABLE :- } " = "1" ] ; then
tui_flash_remove_usb
else
p " ${ ORANGE } >>> REMOVE THE USB DRIVE NOW <<< ${ NC } "
fi
2026-03-28 23:41:40 +00:00
echo ""
fix: CSRF 403 blocking all operations + reboot after install
CSRF fix (THE BLOCKER):
- After remember-me session restore, the browser has a stale CSRF
cookie but a new session token. ALL subsequent RPC calls return 403.
- Fix: exempt read-only polling methods (node-messages-received,
server.echo, system.stats, tor.status, etc.) from CSRF validation.
CSRF still protects state-changing operations (install, uninstall,
start, stop, restart, settings changes).
Reboot fix:
- The separate /tmp/archipelago-reboot.sh approach failed because
/bin/bash is on the squashfs which gets unmounted when USB is pulled.
- Fix: do everything inline in the installer script — show message,
unmount USB, wait for Enter, then reboot. Use sysrq-trigger first
(kernel-level, doesn't need userspace binaries).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 22:42:09 +01:00
p " ${ ORANGE_DIM } Press Enter to reboot (or wait 30 seconds) ${ NC } "
# Suppress kernel messages (squashfs errors when USB is pulled)
echo 1 > /proc/sys/kernel/printk 2>/dev/null || true
2026-03-28 23:41:40 +00:00
fix: CSRF 403 blocking all operations + reboot after install
CSRF fix (THE BLOCKER):
- After remember-me session restore, the browser has a stale CSRF
cookie but a new session token. ALL subsequent RPC calls return 403.
- Fix: exempt read-only polling methods (node-messages-received,
server.echo, system.stats, tor.status, etc.) from CSRF validation.
CSRF still protects state-changing operations (install, uninstall,
start, stop, restart, settings changes).
Reboot fix:
- The separate /tmp/archipelago-reboot.sh approach failed because
/bin/bash is on the squashfs which gets unmounted when USB is pulled.
- Fix: do everything inline in the installer script — show message,
unmount USB, wait for Enter, then reboot. Use sysrq-trigger first
(kernel-level, doesn't need userspace binaries).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 22:42:09 +01:00
# Lazy-unmount live filesystem
2026-03-26 18:30:13 +00:00
exec 2>/dev/null
2026-03-28 13:14:33 +00:00
umount -l /run/live/medium 2>/dev/null || true
umount -l /lib/live/mount/medium 2>/dev/null || true
umount -l /run/archiso 2>/dev/null || true
umount -l /cdrom 2>/dev/null || true
BOOT_DEV = $( findmnt -n -o SOURCE /run/live/medium 2>/dev/null || findmnt -n -o SOURCE /cdrom 2>/dev/null || echo "" )
2026-03-25 16:56:02 +00:00
if [ -n " $BOOT_DEV " ] ; then
BOOT_DISK = $( lsblk -no PKNAME " $BOOT_DEV " 2>/dev/null | head -1)
2026-03-28 13:14:33 +00:00
[ -n " $BOOT_DISK " ] && eject " /dev/ $BOOT_DISK " 2>/dev/null || true
2026-03-25 16:56:02 +00:00
fi
2026-03-28 13:06:34 +00:00
exec 2>& 1
fix: CSRF 403 blocking all operations + reboot after install
CSRF fix (THE BLOCKER):
- After remember-me session restore, the browser has a stale CSRF
cookie but a new session token. ALL subsequent RPC calls return 403.
- Fix: exempt read-only polling methods (node-messages-received,
server.echo, system.stats, tor.status, etc.) from CSRF validation.
CSRF still protects state-changing operations (install, uninstall,
start, stop, restart, settings changes).
Reboot fix:
- The separate /tmp/archipelago-reboot.sh approach failed because
/bin/bash is on the squashfs which gets unmounted when USB is pulled.
- Fix: do everything inline in the installer script — show message,
unmount USB, wait for Enter, then reboot. Use sysrq-trigger first
(kernel-level, doesn't need userspace binaries).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 22:42:09 +01:00
# Wait for Enter or timeout
read -t 30 -s 2>/dev/null || true
echo ""
p " ${ ORANGE_DIM } Rebooting... ${ NC } "
sleep 1
# Force reboot — multiple methods, first one that works wins
echo b > /proc/sysrq-trigger 2>/dev/null || \
/sbin/reboot -f 2>/dev/null || \
/usr/sbin/reboot -f 2>/dev/null || \
kill -9 1 2>/dev/null
2026-02-01 05:42:05 +00:00
INSTALLER_SCRIPT
2026-03-31 23:43:32 +01:00
# Inject build version into auto-install.sh (heredoc is single-quoted, can't expand variables)
sed -i " s|__BUILD_VERSION__| ${ BUILD_VERSION } |g " " $ARCH_DIR /auto-install.sh "
sed -i " s|__BUILD_NUM__| ${ BUILD_NUM } |g " " $ARCH_DIR /auto-install.sh "
sed -i " s|__GIT_SHORT__| ${ GIT_SHORT } |g " " $ARCH_DIR /auto-install.sh "
2026-03-10 23:29:05 +00:00
# For unbundled builds, patch the completion message to reflect no pre-loaded apps
if [ " $UNBUNDLED " = "1" ] ; then
sed -i 's/Pre-loaded apps (ready to start via Web UI):/Install apps from the Marketplace (internet required):/' " $ARCH_DIR /auto-install.sh "
sed -i 's/• Bitcoin Knots • LND • Home Assistant/ Open the Web UI → Marketplace → Install any app/' " $ARCH_DIR /auto-install.sh "
sed -i 's/• BTCPay Server • Mempool • Nostr Relays/ All apps download automatically via Podman /' " $ARCH_DIR /auto-install.sh "
fi
2026-02-01 05:42:05 +00:00
chmod +x " $ARCH_DIR /auto-install.sh "
# =============================================================================
2026-03-27 18:19:49 +00:00
# STEP 5: Configure boot loader and ISO structure
2026-02-01 05:42:05 +00:00
# =============================================================================
echo ""
2026-03-27 18:19:49 +00:00
echo "Step 5: Configuring boot loaders..."
2026-02-01 05:42:05 +00:00
2026-03-27 18:19:49 +00:00
# The installer squashfs (from Step 2) already contains:
# - systemd service for auto-starting the installer
# - auto-login on tty1
# - custom initramfs hook for mounting boot media at /run/archiso
# - all partitioning tools (parted, mkfs.*, cryptsetup)
#
# Step 5 just needs to create the GRUB and ISOLINUX boot configs.
# Create GRUB configuration
echo " Writing GRUB config..."
cat > " $INSTALLER_ISO /boot/grub/grub.cfg " <<'GRUBCFG '
2026-03-27 23:33:31 +00:00
insmod part_gpt
insmod part_msdos
insmod fat
insmod iso9660
insmod all_video
2026-03-28 23:58:42 +00:00
insmod search
insmod search_label
insmod search_fs_file
2026-03-27 23:33:31 +00:00
2026-03-28 23:58:42 +00:00
# Find boot media — try label first, then known file fallback
2026-03-27 23:33:31 +00:00
search --no-floppy --set= root --label ARCHIPELAGO
2026-03-28 23:58:42 +00:00
if [ -z " $root " ] ; then
search --no-floppy --set= root --file /archipelago/auto-install.sh
fi
2026-03-27 23:33:31 +00:00
2026-03-27 18:19:49 +00:00
set timeout = 5
set default = 0
fix: ISO boot, container installs, VPN, nginx, companion input
- LUKS auto-unlock: initramfs hook + systemd service + nofail fstab
- Rootfs packages: add passt, aardvark-dns, netavark, nftables for Podman 5.x
- nginx: resolver + variable proxy_pass for external domains (DNS at boot)
- Boot: loglevel=0 suppresses kernel warnings, serial console for QEMU
- Container installs: write configs before chown, sudo chown for LUKS volumes
- Container installs: build UI sidecars locally (not from registry) for auth injection
- Bitcoin UI: inject RPC auth from secrets file, --no-cache rebuild
- Secrets: chown to archipelago user in first-boot (backend needs read access)
- Podman: image_copy_tmp_dir for read-only /var/tmp in user namespace
- NostrVPN: enable service in auto-install, always include public relays
- NostrVPN: read tunnel IP from nvpn status (not just config file)
- VPN invite: v2 base64 no-pad format matching phone app
- Companion input: relay always active, kiosk skips relay listener (prevents double input)
- dev-start.sh: production build includes AIUI deployment
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 03:10:49 -04:00
# Serial console for QEMU/headless testing
insmod serial
serial --unit= 0 --speed= 115200
terminal_input serial console
terminal_output serial console
2026-04-02 20:28:53 +01:00
# Load font for graphical menu — fallback to text mode on hardware without gfxterm
2026-03-27 23:33:31 +00:00
if loadfont ( $root ) /boot/grub/font.pf2; then
2026-03-30 16:35:06 +01:00
set gfxmode = auto
2026-04-02 20:28:53 +01:00
set gfxpayload = keep
2026-03-27 18:19:49 +00:00
insmod gfxterm
insmod png
fix: ISO boot, container installs, VPN, nginx, companion input
- LUKS auto-unlock: initramfs hook + systemd service + nofail fstab
- Rootfs packages: add passt, aardvark-dns, netavark, nftables for Podman 5.x
- nginx: resolver + variable proxy_pass for external domains (DNS at boot)
- Boot: loglevel=0 suppresses kernel warnings, serial console for QEMU
- Container installs: write configs before chown, sudo chown for LUKS volumes
- Container installs: build UI sidecars locally (not from registry) for auth injection
- Bitcoin UI: inject RPC auth from secrets file, --no-cache rebuild
- Secrets: chown to archipelago user in first-boot (backend needs read access)
- Podman: image_copy_tmp_dir for read-only /var/tmp in user namespace
- NostrVPN: enable service in auto-install, always include public relays
- NostrVPN: read tunnel IP from nvpn status (not just config file)
- VPN invite: v2 base64 no-pad format matching phone app
- Companion input: relay always active, kiosk skips relay listener (prevents double input)
- dev-start.sh: production build includes AIUI deployment
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 03:10:49 -04:00
terminal_output gfxterm serial
2026-04-02 20:28:53 +01:00
else
fix: ISO boot, container installs, VPN, nginx, companion input
- LUKS auto-unlock: initramfs hook + systemd service + nofail fstab
- Rootfs packages: add passt, aardvark-dns, netavark, nftables for Podman 5.x
- nginx: resolver + variable proxy_pass for external domains (DNS at boot)
- Boot: loglevel=0 suppresses kernel warnings, serial console for QEMU
- Container installs: write configs before chown, sudo chown for LUKS volumes
- Container installs: build UI sidecars locally (not from registry) for auth injection
- Bitcoin UI: inject RPC auth from secrets file, --no-cache rebuild
- Secrets: chown to archipelago user in first-boot (backend needs read access)
- Podman: image_copy_tmp_dir for read-only /var/tmp in user namespace
- NostrVPN: enable service in auto-install, always include public relays
- NostrVPN: read tunnel IP from nvpn status (not just config file)
- VPN invite: v2 base64 no-pad format matching phone app
- Companion input: relay always active, kiosk skips relay listener (prevents double input)
- dev-start.sh: production build includes AIUI deployment
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 03:10:49 -04:00
terminal_output console serial
2026-03-27 18:19:49 +00:00
fi
# Archipelago GRUB theme
2026-03-27 23:33:31 +00:00
if [ -f ( $root ) /boot/grub/themes/archipelago/theme.txt ] ; then
loadfont ( $root ) /boot/grub/themes/archipelago/dejavu_12.pf2
loadfont ( $root ) /boot/grub/themes/archipelago/dejavu_14.pf2
loadfont ( $root ) /boot/grub/themes/archipelago/dejavu_16.pf2
loadfont ( $root ) /boot/grub/themes/archipelago/dejavu_24.pf2
set theme = ( $root ) /boot/grub/themes/archipelago/theme.txt
2026-02-01 05:42:05 +00:00
else
2026-03-27 18:19:49 +00:00
set menu_color_normal = light-gray/black
set menu_color_highlight = white/dark-gray
2026-02-01 05:42:05 +00:00
fi
2026-03-27 18:19:49 +00:00
menuentry "Install Archipelago" --hotkey= i {
fix: ISO boot, container installs, VPN, nginx, companion input
- LUKS auto-unlock: initramfs hook + systemd service + nofail fstab
- Rootfs packages: add passt, aardvark-dns, netavark, nftables for Podman 5.x
- nginx: resolver + variable proxy_pass for external domains (DNS at boot)
- Boot: loglevel=0 suppresses kernel warnings, serial console for QEMU
- Container installs: write configs before chown, sudo chown for LUKS volumes
- Container installs: build UI sidecars locally (not from registry) for auth injection
- Bitcoin UI: inject RPC auth from secrets file, --no-cache rebuild
- Secrets: chown to archipelago user in first-boot (backend needs read access)
- Podman: image_copy_tmp_dir for read-only /var/tmp in user namespace
- NostrVPN: enable service in auto-install, always include public relays
- NostrVPN: read tunnel IP from nvpn status (not just config file)
- VPN invite: v2 base64 no-pad format matching phone app
- Companion input: relay always active, kiosk skips relay listener (prevents double input)
- dev-start.sh: production build includes AIUI deployment
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 03:10:49 -04:00
linux ( $root ) /live/vmlinuz boot = live components quiet splash loglevel = 0 rd.systemd.show_status= false vt.global_cursor_default= 0 acpi = force console = ttyS0,115200 console = tty0
2026-03-27 23:33:31 +00:00
initrd ( $root ) /live/initrd.img
2026-03-27 18:19:49 +00:00
}
2026-02-01 05:42:05 +00:00
2026-03-27 18:19:49 +00:00
menuentry "Install Archipelago (verbose)" --hotkey= v {
2026-04-08 13:15:09 +02:00
linux ( $root ) /live/vmlinuz boot = live components loglevel = 4 console = ttyS0,115200 console = tty0 acpi = force
2026-03-27 23:33:31 +00:00
initrd ( $root ) /live/initrd.img
2026-03-27 18:19:49 +00:00
}
2026-02-01 05:42:05 +00:00
2026-03-27 18:19:49 +00:00
menuentry "Boot from local disk" --hotkey= b {
set root = ( hd0)
chainloader +1
}
GRUBCFG
2026-02-01 05:42:05 +00:00
2026-03-27 23:33:31 +00:00
# Copy grub.cfg to EFI/BOOT on ISO filesystem AND into the FAT EFI image
# The embedded grub bootstrap does configfile "${cmdpath}/grub.cfg"
cp " $INSTALLER_ISO /boot/grub/grub.cfg " " $INSTALLER_ISO /EFI/BOOT/grub.cfg "
if [ -f " $WORK_DIR /efi.img " ] ; then
mcopy -oi " $WORK_DIR /efi.img " " $INSTALLER_ISO /boot/grub/grub.cfg " ::/EFI/BOOT/grub.cfg 2>/dev/null || \
echo " WARNING: Could not copy grub.cfg into efi.img (mtools required)"
fi
2026-03-27 18:19:49 +00:00
# Create ISOLINUX configuration (legacy BIOS boot)
echo " Writing ISOLINUX config..."
2026-03-28 03:15:47 +00:00
# Copy background image for ISOLINUX graphical menu
ISOLINUX_BG = " $SCRIPT_DIR /branding/grub-theme/background.png "
if [ -f " $ISOLINUX_BG " ] ; then
cp " $ISOLINUX_BG " " $INSTALLER_ISO /isolinux/splash.png "
fi
# Copy vesamenu.c32 for graphical menu (with background support)
if [ -f " $WORK_DIR /vesamenu.c32 " ] ; then
cp " $WORK_DIR /vesamenu.c32 " " $INSTALLER_ISO /isolinux/vesamenu.c32 "
fi
2026-03-27 18:19:49 +00:00
cat > " $INSTALLER_ISO /isolinux/isolinux.cfg " <<'ISOCFG'
2026-03-28 03:15:47 +00:00
UI vesamenu.c32
2026-03-27 18:19:49 +00:00
PROMPT 0
2026-03-28 03:15:47 +00:00
TIMEOUT 0
2026-02-01 05:42:05 +00:00
fix: container security hardening, onboarding viewport scaling, boot screen cleanup
Container security:
- Add --cap-drop ALL + --security-opt no-new-privileges:true to 12 containers
missing hardening in first-boot-containers.sh (mempool-db, electrumx,
mempool-api, mempool-web, electrs-ui, btcpay-db, nbxplorer, nostr-rs-relay,
strfry, tailscale, bitcoin-ui, lnd-ui)
- Mirror same hardening in deploy-to-target.sh for consistency
- Add --read-only + tmpfs to nostr-rs-relay
- Fix filebrowser deploy to include security flags
- Remove duplicate UI image definitions in image-versions.sh
- Separate Jellyfin capabilities (needs FOWNER, exec tmpfs for CoreCLR JIT)
- Harden archy-net creation with existence check and error handling
UI fixes:
- Fix onboarding viewport scaling: all 7 screens now use h-full + max-h-full
pattern so containers never overflow viewport regardless of padding
- Remove path-option-card wrappers from seed verify inputs, left-justify labels
- Remove batteries/barbarian icons from boot screen (keep bitcoin, cloud, github, save)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 17:35:34 +01:00
MENU TITLE
2026-03-28 03:15:47 +00:00
MENU BACKGROUND splash.png
2026-03-28 13:14:33 +00:00
MENU RESOLUTION 1024 768
fix: container security hardening, onboarding viewport scaling, boot screen cleanup
Container security:
- Add --cap-drop ALL + --security-opt no-new-privileges:true to 12 containers
missing hardening in first-boot-containers.sh (mempool-db, electrumx,
mempool-api, mempool-web, electrs-ui, btcpay-db, nbxplorer, nostr-rs-relay,
strfry, tailscale, bitcoin-ui, lnd-ui)
- Mirror same hardening in deploy-to-target.sh for consistency
- Add --read-only + tmpfs to nostr-rs-relay
- Fix filebrowser deploy to include security flags
- Remove duplicate UI image definitions in image-versions.sh
- Separate Jellyfin capabilities (needs FOWNER, exec tmpfs for CoreCLR JIT)
- Harden archy-net creation with existence check and error handling
UI fixes:
- Fix onboarding viewport scaling: all 7 screens now use h-full + max-h-full
pattern so containers never overflow viewport regardless of padding
- Remove path-option-card wrappers from seed verify inputs, left-justify labels
- Remove batteries/barbarian icons from boot screen (keep bitcoin, cloud, github, save)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 17:35:34 +01:00
MENU VSHIFT 20
MENU HSHIFT 6
MENU WIDTH 68
MENU MARGIN 2
2026-03-28 11:31:48 +00:00
MENU ROWS 5
2026-03-28 13:06:34 +00:00
MENU TABMSG press tab to edit | archipelago.sh
2026-03-28 03:15:47 +00:00
MENU COLOR screen 37; 40 #00000000 #00000000 none
2026-03-28 00:35:34 +00:00
MENU COLOR border 30; 40 #00000000 #00000000 none
2026-03-28 11:31:48 +00:00
MENU COLOR title 1; 37; 40 #80888888 #00000000 none
MENU COLOR sel 7; 37; 40 #ffffffff #c0181818 std
MENU COLOR unsel 37; 40 #ffaaaaaa #00000000 none
2026-03-28 03:15:47 +00:00
MENU COLOR hotkey 1; 37; 40 #fffb923c #00000000 none
2026-03-28 11:31:48 +00:00
MENU COLOR hotsel 1; 37; 40 #fffb923c #c0181818 std
MENU COLOR timeout_msg 37; 40 #ff555555 #00000000 none
2026-03-28 03:15:47 +00:00
MENU COLOR timeout 1; 37; 40 #fffb923c #00000000 none
2026-03-28 11:31:48 +00:00
MENU COLOR tabmsg 37; 40 #ff444444 #00000000 none
2026-03-28 03:15:47 +00:00
MENU COLOR cmdmark 37; 40 #00000000 #00000000 none
MENU COLOR cmdline 37; 40 #00000000 #00000000 none
2026-02-01 05:42:05 +00:00
2026-03-27 18:19:49 +00:00
DEFAULT install
2026-02-01 05:42:05 +00:00
2026-03-27 18:19:49 +00:00
LABEL install
MENU LABEL Install Archipelago
KERNEL /live/vmlinuz
2026-04-08 13:15:09 +02:00
APPEND initrd = /live/initrd.img boot = live components quiet loglevel = 0 rd.systemd.show_status= false vt.global_cursor_default= 0 acpi = force
2026-03-27 18:19:49 +00:00
MENU DEFAULT
2026-02-01 05:42:05 +00:00
2026-03-27 18:19:49 +00:00
LABEL install-verbose
2026-03-28 11:31:48 +00:00
MENU LABEL Install ( verbose output)
2026-03-27 18:19:49 +00:00
KERNEL /live/vmlinuz
2026-04-08 13:15:09 +02:00
APPEND initrd = /live/initrd.img boot = live components loglevel = 4 acpi = force
2026-02-01 05:42:05 +00:00
2026-03-27 18:19:49 +00:00
LABEL local
MENU LABEL Boot from local disk
LOCALBOOT 0x80
ISOCFG
2026-02-01 05:42:05 +00:00
2026-03-27 18:19:49 +00:00
echo " Step 5 complete (GRUB + ISOLINUX configured)"
2026-02-01 05:42:05 +00:00
# =============================================================================
# STEP 6: Create final ISO
# =============================================================================
echo ""
2026-03-27 18:19:49 +00:00
echo "Step 6: Creating bootable ISO..."
2026-02-01 05:42:05 +00:00
2026-03-10 23:29:05 +00:00
if [ " $UNBUNDLED " = "1" ] ; then
2026-05-06 09:23:57 -04:00
OUTPUT_ISO = " $OUTPUT_DIR /archipelago-installer- ${ BUILD_VERSION } -unbundled- ${ ARCH } .iso "
2026-03-10 23:29:05 +00:00
else
2026-05-06 09:23:57 -04:00
OUTPUT_ISO = " $OUTPUT_DIR /archipelago-installer- ${ BUILD_VERSION } - ${ ARCH } .iso "
2026-03-10 23:29:05 +00:00
fi
2026-02-01 05:42:05 +00:00
feat: custom boot branding, MBR fix, Plymouth theme, CI smoke tests
Boot fix:
- Ship proven Debian Live MBR (4552) as branding/isohdpfx.bin — the
ISOLINUX package MBR (33ed) doesn't boot on all hardware. This was
the root cause of "machine doesn't pick up the USB".
Branding:
- Custom GRUB background: pixel-art floating island (1024x574)
- Archipelago pixel-art logo for Plymouth boot splash
- GRUB theme: dark background, orange selected item, no broken font refs
- Plymouth theme: script-based with progress bar, LUKS prompt support
- Plymouth + splash added to target rootfs packages
- GRUB theme installed on both installer ISO and target system
- Serial console (ttyS0) added to kernel params for QEMU debugging
CI improvements:
- Smoke test step: mounts ISO, verifies all critical files, checks
initrd has live-boot, confirms boot=live in grub.cfg. Fails build
before copying to Builds if any check fails.
Dev workflow:
- dev-branding.sh: extract ISO, swap branding, repackage, boot in QEMU
(~10 seconds vs 20 min full rebuild)
- generate-grub-background.py: procedural cyberpunk background generator
- generate-plymouth-logo.py: procedural logo generator
- Improved test-iso-qemu.sh: --bios/--nographic flags, serial logging
Build:
- Simplified live-boot install (clean chroot, no complex fallbacks)
- Static branding images preferred, generators as fallback
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 22:26:56 +00:00
# Use the proven MBR code for hybrid USB boot
# The ISOLINUX package's isohdpfx.bin (33 ed) doesn't boot on all hardware.
# We ship the Debian Live MBR (45 52) which is known to work with Balena Etcher.
ISOHDPFX = " $SCRIPT_DIR /branding/isohdpfx.bin "
if [ ! -f " $ISOHDPFX " ] ; then
ISOHDPFX = " $WORK_DIR /isohdpfx.bin "
fi
2026-03-27 18:19:49 +00:00
if [ ! -f " $ISOHDPFX " ] ; then
# Fallback to system-installed copy
2026-03-09 17:09:59 +00:00
for path in \
"/usr/lib/ISOLINUX/isohdpfx.bin" \
"/usr/share/syslinux/isohdpfx.bin" \
"/usr/local/share/syslinux/isohdpfx.bin" ; do
if [ -f " $path " ] ; then
ISOHDPFX = " $path "
2026-03-27 18:19:49 +00:00
echo " Using system isohdpfx.bin: $path "
2026-03-09 17:09:59 +00:00
break
fi
done
fi
2026-03-28 00:35:34 +00:00
# EFI boot image — embedded inside ISO (same approach as the working main ISO)
# The efi.img must be copied into the ISO directory in Step 2 artifact placement
EFI_IMG = " $INSTALLER_ISO /boot/grub/efi.img "
2026-02-01 05:42:05 +00:00
2026-03-28 00:35:34 +00:00
if [ ! -f " $EFI_IMG " ] ; then
2026-03-27 18:19:49 +00:00
echo " WARNING: No EFI boot image — ISO will only support Legacy BIOS boot"
2026-03-09 17:09:59 +00:00
xorriso -as mkisofs -o " $OUTPUT_ISO " \
-volid "ARCHIPELAGO" \
-iso-level 3 \
2026-03-28 00:35:34 +00:00
-J -joliet-long -R \
2026-03-09 17:09:59 +00:00
-isohybrid-mbr " $ISOHDPFX " \
2026-03-28 00:35:34 +00:00
-c isolinux/boot.cat \
-b isolinux/isolinux.bin \
-no-emul-boot -boot-load-size 4 -boot-info-table \
-partition_offset 16 \
2026-03-09 17:09:59 +00:00
" $INSTALLER_ISO "
else
2026-03-29 12:44:13 +01:00
# UEFI fix: append efi.img as a real EFI System Partition (ESP) in GPT
# instead of embedding it as "basic data". Strict UEFI firmware requires
# the correct ESP type GUID (C12A7328-F81F-11D2-BA4B-00A0C93EC93B).
# This is the same approach used by Arch Linux ISOs.
2026-03-09 17:09:59 +00:00
xorriso -as mkisofs -o " $OUTPUT_ISO " \
-volid "ARCHIPELAGO" \
-iso-level 3 \
2026-03-28 00:35:34 +00:00
-J -joliet-long -R \
2026-03-09 17:09:59 +00:00
-isohybrid-mbr " $ISOHDPFX " \
2026-03-28 00:35:34 +00:00
-c isolinux/boot.cat \
-b isolinux/isolinux.bin \
-no-emul-boot -boot-load-size 4 -boot-info-table \
2026-03-09 17:09:59 +00:00
-eltorito-alt-boot \
2026-03-29 12:44:13 +01:00
-e --interval:appended_partition_2:all:: \
2026-03-28 00:35:34 +00:00
-no-emul-boot \
2026-03-29 12:44:13 +01:00
-appended_part_as_gpt \
-append_partition 2 C12A7328-F81F-11D2-BA4B-00A0C93EC93B " $WORK_DIR /efi.img " \
2026-03-28 00:35:34 +00:00
-partition_offset 16 \
2026-03-09 17:09:59 +00:00
" $INSTALLER_ISO "
fi
2026-02-01 05:42:05 +00:00
echo ""
2026-03-10 23:29:05 +00:00
if [ " $UNBUNDLED " = "1" ] ; then
2026-03-27 18:19:49 +00:00
echo "UNBUNDLED AUTO-INSTALLER ISO CREATED"
2026-03-10 23:29:05 +00:00
echo ""
2026-03-27 18:19:49 +00:00
echo " Output: $OUTPUT_ISO "
echo " Size: $( du -h " $OUTPUT_ISO " | cut -f1) "
2026-03-10 23:29:05 +00:00
echo ""
2026-03-27 18:19:49 +00:00
echo " Lightweight installer -- apps downloaded on-demand from Marketplace"
2026-03-10 23:29:05 +00:00
else
2026-03-27 18:19:49 +00:00
echo "AUTO-INSTALLER ISO CREATED"
2026-03-10 23:29:05 +00:00
echo ""
2026-03-27 18:19:49 +00:00
echo " Output: $OUTPUT_ISO "
echo " Size: $( du -h " $OUTPUT_ISO " | cut -f1) "
2026-03-10 23:29:05 +00:00
echo ""
2026-03-27 18:19:49 +00:00
echo " Full installer with pre-bundled container apps"
2026-03-10 23:29:05 +00:00
fi
2026-02-01 05:42:05 +00:00
echo "To create USB:"
echo " 1. Flash with: sudo dd if= $OUTPUT_ISO of=/dev/rdiskX bs=4m "
echo " Or use Balena Etcher"
echo " 2. Boot from USB"
echo " 3. Press Enter to install"
echo " 4. Remove USB and reboot"
echo ""