fix: production onboarding, CI tests, container security, keyboard nav
Some checks failed
Build Archipelago ISO / build-iso (push) Has been cancelled
Some checks failed
Build Archipelago ISO / build-iso (push) Has been cancelled
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>
This commit is contained in:
@@ -385,17 +385,19 @@ NGINXCONF
|
||||
cat > "$WORK_DIR/archipelago.service" <<'SYSTEMDSERVICE'
|
||||
[Unit]
|
||||
Description=Archipelago Backend
|
||||
After=network-online.target
|
||||
After=network-online.target archipelago-setup-tor.service
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
User=archipelago
|
||||
Environment="ARCHIPELAGO_BIND=127.0.0.1:5678"
|
||||
Environment="ARCHIPELAGO_DEV_MODE=true"
|
||||
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'
|
||||
ExecStart=/usr/local/bin/archipelago
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
ProtectHome=no
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -921,25 +923,56 @@ if [ "$UNBUNDLED" = "1" ]; then
|
||||
#!/bin/bash
|
||||
# Minimal first-boot: create FileBrowser container only (unbundled ISO)
|
||||
set -e
|
||||
DOCKER="podman"
|
||||
LOG="/var/log/archipelago-first-boot.log"
|
||||
echo "[$(date)] Starting minimal first-boot (unbundled)..." >> "$LOG"
|
||||
|
||||
# Create Cloud storage directories
|
||||
mkdir -p /var/lib/archipelago/filebrowser
|
||||
mkdir -p /var/lib/archipelago/data/cloud/{Documents,Photos,Music,Videos,Downloads}
|
||||
chown -R 1000:1000 /var/lib/archipelago/filebrowser
|
||||
chown -R 1000:1000 /var/lib/archipelago/data
|
||||
# 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 ! $DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -q filebrowser; then
|
||||
echo "[$(date)] Creating FileBrowser container..." >> "$LOG"
|
||||
$DOCKER run -d --name filebrowser --restart unless-stopped \
|
||||
--health-cmd="curl -sf http://localhost:80/ || exit 1" \
|
||||
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)
|
||||
mkdir -p /var/lib/archipelago/filebrowser
|
||||
mkdir -p /var/lib/archipelago/filebrowser-data
|
||||
mkdir -p /var/lib/archipelago/data/cloud/{Documents,Photos,Music,Videos,Downloads}
|
||||
# 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
|
||||
|
||||
# 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)
|
||||
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 \
|
||||
--security-opt=no-new-privileges:true \
|
||||
--read-only \
|
||||
--tmpfs=/tmp:rw,noexec,nosuid,size=64m \
|
||||
--health-cmd='curl -sf http://localhost:80/ || exit 1' \
|
||||
--health-interval=30s --health-timeout=5s --health-retries=3 \
|
||||
--memory=256m \
|
||||
-p 8083:80 \
|
||||
-v /var/lib/archipelago/filebrowser:/srv \
|
||||
"$FILEBROWSER_IMAGE" 2>>"$LOG" && \
|
||||
-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" && \
|
||||
echo "[$(date)] FileBrowser created successfully" >> "$LOG" || \
|
||||
echo "[$(date)] WARNING: FileBrowser creation failed" >> "$LOG"
|
||||
fi
|
||||
@@ -1001,6 +1034,11 @@ if [ -f "$SCRIPT_DIR/../scripts/run-e2e-tests.sh" ]; then
|
||||
chmod +x "$ARCH_DIR/scripts/run-e2e-tests.sh"
|
||||
echo " ✅ Bundled E2E test script for post-install validation"
|
||||
fi
|
||||
if [ -f "$SCRIPT_DIR/../scripts/run-post-install-tests.sh" ]; then
|
||||
cp "$SCRIPT_DIR/../scripts/run-post-install-tests.sh" "$ARCH_DIR/scripts/"
|
||||
chmod +x "$ARCH_DIR/scripts/run-post-install-tests.sh"
|
||||
echo " ✅ Bundled post-install test suite"
|
||||
fi
|
||||
|
||||
# Bundle self-update script and image-versions for update system
|
||||
if [ -f "$SCRIPT_DIR/../scripts/self-update.sh" ]; then
|
||||
@@ -1411,11 +1449,13 @@ 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
|
||||
|
||||
# Copy E2E test script for post-install validation
|
||||
if [ -f "$BOOT_MEDIA/archipelago/scripts/run-e2e-tests.sh" ]; then
|
||||
cp "$BOOT_MEDIA/archipelago/scripts/run-e2e-tests.sh" /mnt/target/opt/archipelago/scripts/
|
||||
chmod +x /mnt/target/opt/archipelago/scripts/run-e2e-tests.sh
|
||||
fi
|
||||
# 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
|
||||
|
||||
# Copy self-update script
|
||||
if [ -f "$BOOT_MEDIA/archipelago/scripts/self-update.sh" ]; then
|
||||
@@ -1427,6 +1467,15 @@ if [ -f "$BOOT_MEDIA/archipelago/scripts/self-update.sh" ]; then
|
||||
chmod +x /mnt/target/home/archipelago/archy/scripts/self-update.sh
|
||||
fi
|
||||
|
||||
# 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
|
||||
|
||||
# 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'
|
||||
@@ -1513,26 +1562,12 @@ fi
|
||||
PROFILE
|
||||
chmod +x /mnt/target/etc/profile.d/archipelago.sh
|
||||
|
||||
# Systemd service: User=root required for Podman container access
|
||||
cat > /mnt/target/etc/systemd/system/archipelago.service <<'SERVICE'
|
||||
[Unit]
|
||||
Description=Archipelago Backend
|
||||
After=network-online.target archipelago-setup-tor.service
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
Environment="ARCHIPELAGO_BIND=127.0.0.1:5678"
|
||||
Environment="ARCHIPELAGO_DEV_MODE=true"
|
||||
ExecStartPre=/bin/bash -c 'mkdir -p /etc/archipelago && echo "ARCHIPELAGO_HOST_IP=$(hostname -I 2>/dev/null | awk \"{print \$1}\")" > /etc/archipelago/host-ip.env'
|
||||
ExecStart=/usr/local/bin/archipelago
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
SERVICE
|
||||
# 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"
|
||||
cp "$BOOT_MEDIA/archipelago/configs/archipelago.service" /mnt/target/etc/systemd/system/archipelago.service 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# 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
|
||||
@@ -1942,6 +1977,19 @@ if [ ! -f /mnt/target/etc/archipelago/ssl/archipelago.crt ]; then
|
||||
echo " Generated self-signed SSL certificate"
|
||||
fi
|
||||
|
||||
# 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
|
||||
|
||||
# Enable services
|
||||
chroot /mnt/target systemctl enable archipelago.service 2>/dev/null || true
|
||||
chroot /mnt/target systemctl enable nginx.service 2>/dev/null || true
|
||||
|
||||
Reference in New Issue
Block a user