Critical:
- fix: container installs fail with "statfs: no such file or directory"
Root cause: NoNewPrivileges=yes in systemd blocks sudo inside backend.
Fix: use std::fs::create_dir_all + podman unshare chown (no sudo needed)
- fix: Tor services.json never written — \$ARCHY_TOR_DIR escaping bug
- fix: kiosk white screen — increase health wait to 60s, add --disable-gpu
Improvements:
- feat: LUKS encryption badge in Server disk stats (backend detects dm-crypt)
- fix: GRUB theme text scaling on 4:3 monitors — explicit fonts, wider menu
- fix: suppress default Debian MOTD (custom profile.d welcome is enough)
- fix: install error messages now show "Failed to pull/start" instead of
generic "Operation failed" (middleware.rs allowlist expanded)
- fix: container-tests CI — source cargo env before running tests
- docs: interactive container architecture diagram (HTML)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- fix: login disconnect — verify session before WebSocket connect
- fix: 403 on app install — distinguish CSRF vs RBAC errors, only retry CSRF
- fix: health monitor now watches ALL containers (removed skip list for
backend services like nbxplorer, databases, UI containers)
- fix: server.get-state added to CSRF-exempt list (read-only)
- fix: ISO build includes container-specs.sh and lib/common.sh in rootfs
so reconcile actually works on fresh installs
- fix: gamepad nav — improved Server tab zone nav, focus styles, autofocus
- chore: move L484 web-only apps to Services tab
- chore: install store for cross-view install tracking
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Critical fixes:
- Remove ensure_default_user() — no more auto-creating user with
password123. Login page now shows "Create Password" form on first
boot. User sets their own password during onboarding flow.
- CSRF 403: increased retry delay from 300ms to 500ms for stale
cookie recovery after remember-me session restore.
- Reboot: multiple fallback methods (/sbin/reboot, sysrq, kill init)
when USB is pulled and /usr/sbin isn't available.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
Version per build:
- Health endpoint returns "1.2.0-alpha-{git_hash}" using GIT_HASH env
- CI passes git hash to cargo build
FileBrowser auto-login:
- filebrowser-client.ts: include CSRF token + credentials:include
- First-boot: generate random password, store at secrets/filebrowser/
- Set FileBrowser admin password to match after container creation
Nostr relay:
- Use docker.io/scsibug/nostr-rs-relay:0.9.0 (not in our registry)
UID mappings:
- Added electrumx (UID 1000), mysql-mempool, archy-btcpay-db, nextcloud-db
522 tests pass, Rust compiles clean.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Container name resolution:
- New all_container_names() — single source of truth for every app's
container name variants (bitcoin-knots/bitcoin/bitcoin-core, etc.)
- Covers all historical naming patterns and multi-container stacks
Start/Stop/Restart:
- No more silent failures (let _ = podman...). Every operation logs
the command, checks exit status, and returns real errors to the UI.
- Restart uses stop+start fallback when podman restart fails
(handles rootless podman loopback adapter errors)
- "No containers found" error when app doesn't exist
Tor helper:
- Install archipelago-tor-helper.path + .service in rootfs
- Enable the path unit so backend can manage Tor as non-root
- Copy tor-helper.sh to /opt/archipelago/scripts/
Verified: container with proper caps can stop/start/restart cleanly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Critical: headless services (Bitcoin, LND, Electrumx) need companion
UI containers that serve web dashboards. These were only built for
Bitcoin, and only on bundled ISO builds.
Changes:
- install.rs: auto-build UI containers for LND (port 8081) and
Electrumx (port 50002) in addition to Bitcoin (port 8334)
- build-auto-installer-iso.sh: always bundle docker UI source files
(was skipping for unbundled builds — they're tiny HTML, not images)
- Dockerfiles: fix nginx base image tag 1.29.6→1.27.4 (matches registry)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fedimintd v0.10.0 requires --data-dir and --bitcoind-url as CLI args,
not just env vars. Container was exiting with usage error.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add orchestration_tests.rs + mock_podman.rs (container unit tests)
- Add container-tests.yml CI workflow
- Add dev-container-test.sh for local testing
- MASTER_PLAN.md: add TASK-49 (P0) with 6-phase plan
- Login.vue: minor fixes from user testing
- AppCard.vue: enter key handler fix
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dashboard System card now reports disk usage for /var/lib/archipelago
(the LUKS encrypted partition) instead of / (small root partition).
This shows the actual usable storage (428GB) rather than the 29GB root.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- LND: add --bitcoin.active --bitcoin.mainnet and all bitcoind
connection args as container CMD args (was only env var before)
- SearXNG: add volume mount + auto-create settings.yml on install
(container exits immediately without it)
- Default caps: all containers get full rootless podman baseline
Tested on .198:
- Bitcoin Knots: running, syncing (942803 blocks)
- Grafana: running, migration complete
- Vaultwarden: running, keys created
- SearXNG: running, listening on 8080
- LND: needs bitcoin container named 'bitcoin-knots' on archy-net
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All containers now get CHOWN+FOWNER+SETUID+SETGID+DAC_OVERRIDE+NET_BIND_SERVICE
as the default cap set. Rootless podman needs these for:
- CHOWN/FOWNER/DAC_OVERRIDE: file ownership in mapped UID namespace
- SETUID/SETGID: internal user switching (entrypoint scripts)
- NET_BIND_SERVICE: port binding in network namespaces
Tested on .198: Grafana, Vaultwarden, Bitcoin Knots all start successfully.
Previously failed with "Permission denied" or "loopback adapter" errors.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bitcoin Knots failed to start with "failed to set loopback adapter up"
because cap-drop=ALL removed NET_BIND_SERVICE, which rootless podman
needs for network namespace setup.
- Add NET_BIND_SERVICE to Bitcoin/LND/Fedimint capabilities
- Add NET_BIND_SERVICE as default for ALL apps (rootless podman needs it)
- UID mapping fix from previous commit also included
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
create_data_dirs now chowns data directories to the correct mapped
UID for rootless podman (host_uid = 100000 + container_uid).
Previously only Grafana (UID 472) was handled. Now all containers
get the correct ownership:
- Bitcoin Knots: 100101 (container UID 101)
- Grafana: 100472 (UID 472)
- LND: 101000 (UID 1000)
- MariaDB: 100999 (UID 999)
- Postgres: 100070 (UID 70)
- All others: 100000 (UID 0, root)
Without this, containers fail with "Operation not permitted" on
chown during startup because rootless podman restricts UID operations.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Critical fixes from ISO testing on .198:
- Backend auto-creates default user (password123) on first start
so login works immediately after onboarding
- Force reboot (reboot -f) after install to avoid SquashFS errors
when live USB is removed
- Eject USB before prompting user, not after
- Add firmware-misc-nonfree for Intel i915 GPU (suppresses dozens
of "Possible missing firmware" warnings during initramfs update)
- First boot screen: wait up to 10s for DHCP before showing IP
- First boot screen: compact layout fits 80-col terminals
- ISOLINUX menu resolution dropped to 640x480 for universal
VESA compatibility (was 1024x768, caused scaling on some hardware)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Secure flag on session cookies broke HTTP LAN access — browsers refuse
to send Secure cookies over plain HTTP, causing 401 redirect loop.
Fix: check X-Forwarded-Proto header. Only set Secure when request came
over HTTPS. HTTP on LAN works, HTTPS still gets Secure cookies.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add identity.create + server.echo to UNAUTHENTICATED_METHODS
- Clear web/dist before frontend build to prevent stale artifacts
- Add autocomplete attrs to login inputs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
FileBrowser crash fix:
- Add --cap-add=NET_BIND_SERVICE (port 80 needs it with --cap-drop=ALL)
- Add --cap-add=DAC_OVERRIDE for rootless volume access
- Both in first-boot script and backend config.rs
Test script fixes:
- Extract csrf_token cookie and send as X-CSRF-Token header on RPC calls
- Add --phase1-only flag for safe install-only checks (no side effects)
- Auto-test service uses --phase1-only so it doesn't steal onboarding
Install fixes:
- Pre-create ~/.local/share/containers (ReadWritePaths mount namespace error)
- Fix console-setup.service: add After=tmp.mount + ExecStartPre mkdir /tmp
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The parmanode compatibility layer was scaffolded but never wired up —
zero imports or calls from anywhere in the codebase. Closes gitea#1.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All container image references now pull from 80.71.235.15:3000/archipelago/
instead of Docker Hub and ghcr.io. image-versions.sh is the single source
of truth; all scripts use $*_IMAGE variables instead of hardcoded refs.
Files updated:
- scripts/image-versions.sh: central ARCHY_REGISTRY variable
- core/*/config.rs: registry whitelist includes app registry
- core/*/stacks.rs: Immich + Penpot stack images
- scripts/{first-boot,deploy-to-target,container-specs}.sh: use variables
- docker/*/Dockerfile: nginx base image from registry
- image-recipe/: ISO build, podman config, menu script
- scripts/{container-doctor,deploy-bitcoin-knots,fix-indeedhub,validate-app-manifest}.sh
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- I2: Add MemoryMax=4G, LimitNOFILE=65535, TasksMax=2048 to systemd service
- I3: Tor rotation keeps old service for 1h transition before cleanup
- R14: Replace .parse().unwrap() with .unwrap_or(localhost) in rate limiter
- R15: Replace 7 unwrap/expect in mesh protocol with proper error propagation
- R27: Add 10s timeouts to mesh Bitcoin RPC calls
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- R6: Convert 6 std::fs calls in session.rs to tokio::fs async
- R7: Convert std::fs::read_to_string in docker_packages.rs to async
- R8: Convert 3 std::fs calls in port_allocator.rs to async, switch to tokio::sync::Mutex
- R9+R10+R11: Fix blocking I/O in node_message.rs and nostr_discovery.rs
- R12: Convert electrs_status.rs from sync TCP to async tokio::net with 5s timeouts
- R4+R5: Spawn periodic cleanup tasks for endpoint and login rate limiters
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- R1: Add health RPC endpoint with crash recovery status, uptime, and version
- R2: Wrap all 5 Nostr client.connect() calls in 10s timeout
- R3: Make backup restore atomic with staging dir and rollback on failure
- I1: Add rate limiting, body size, and proxy timeouts to unauthenticated nginx endpoints
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- lnd.rs: check tor-hostnames readable copy, then /var/lib/tor/, then
legacy /var/lib/archipelago/tor/ with sudo fallback for each
- electrs_status.rs: same multi-path resolution for ElectrumX onion
- Both servers: created /var/lib/archipelago/tor-hostnames/ with readable
copies of onion addresses (avoids sudo on every API call)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace all `podman` CLI shell-outs with HTTP requests to the rootless
Podman API unix socket (/run/user/{UID}/podman/podman.sock).
Benefits:
- No process spawning overhead — direct HTTP over unix socket
- Structured JSON responses — no string parsing fragility
- Proper timeouts on all operations (5s connect, 30s default, 120s create)
- Health check method to verify socket availability
- Restart container as first-class operation
Still uses CLI for:
- Image pulls (streaming operation better suited to CLI)
- Container logs (raw text stream, not JSON)
The Podman socket is rootless (runs as archipelago user), local-only
(unix socket), and already behind our session auth in the backend.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Messages between federated nodes are now end-to-end encrypted:
- X25519 ECDH key agreement from existing ed25519 node identities
- HKDF-SHA256 key derivation with domain separation
- ChaCha20-Poly1305 authenticated encryption per message
- Random 12-byte nonce per message via OsRng (CSPRNG)
- Graceful fallback to plaintext if encryption fails
- Receiver auto-detects encrypted vs plaintext messages
The Tor transport was already encrypted (onion routing), this adds
application-layer E2E encryption so even a compromised receiving
backend can't read messages without the node's private key.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Messages persisted to disk (messages.json) — survive restarts
- Sent messages stored on backend via node-store-sent RPC
- Message deduplication (same pubkey + message within 30s)
- Max 200 messages in circular buffer
- Direction field (sent/received) for proper UI display
- Container doctor: prefer system Tor, remove archy-tor container
- Deploy torrc generator: read from tor-config/services.json,
web apps map port 80→local port for clean .onion URLs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Major changes:
- Full Tor hidden service management via systemd path unit pattern
(tor-helper.sh + archipelago-tor-helper.path/service) — respects
NoNewPrivileges=yes, no sudo needed from backend
- Container doctor: prefer system Tor over container, remove archy-tor
- Deploy script: fix torrc generation (read correct services.json path),
web apps map port 80→local port, enable both tor and tor@default
- Federation: server rename pushes name to peers via background sync
- Server name: fix root-owned file, optimistic store update
- Mesh: local echo for sent messages, sendingArch loading state
- Web5: Message button → Mesh redirect, node name lookup in messages
- PeerFiles: show DID not onion in header
- Connected Nodes: flex-1 instead of fixed max-h
- Toast notifications route to Mesh
- Deploy script: fix single-quote syntax in SSH block
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- nodeName() shows friendly "Node-XXXX" instead of truncated DID
- nodeNameFromDid() for sync results lookup
- Map labels use node names
- Content filename validation: allow / for subdirectories (Music/song.mp3)
but still block .., \, null bytes, hidden files, absolute paths
- Increased filename max length to 512 for paths with subdirectories
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- node.rotate-did: generates new Ed25519 keypair, signs rotation proof
with old key, overwrites identity files, requires password
- federation.notify-did-change: broadcasts rotation proof to all
trusted/observer peers over Tor
- federation.peer-did-changed: receiving side verifies rotation proof
against known pubkey before updating peer's DID
- Rate-limited: 3/600s for rotation, 5/60s for peer notification
- Signature verification uses ed25519_dalek (constant-time)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Part 1 — DID Persistence:
- Deploy script creates /var/lib/archipelago/identity/ directory
- First-boot script creates identity dir with proper ownership
- Identity load now logs pubkey to confirm persistence across restarts
Part 2 — Node Names:
- NodeStateSnapshot includes node_name field
- build_local_state() passes server name to sync responses
- update_node_state() stores peer's announced name on the FederatedNode
- Names propagate automatically during federation.sync-state
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- New Discover.vue (app store redesign)
- Fleet.vue dashboard for .228
- MeshMap.vue component
- Fixed Discover.vue type errors (unused var, type predicate)
- Various UI updates (Apps, Dashboard, Marketplace, Mesh, Web5)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SameSite=Strict prevents cookies from being sent when iframe content
(like the LND UI at /app/lnd/) fetches endpoints on the parent origin
(/lnd-connect-info). Lax still protects against CSRF on POST requests
but allows same-site GET navigations and fetches from iframes.
This was the root cause of "Failed to fetch" on LND Connect.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Backend is bound to 127.0.0.1 — only nginx can reach it.
Nginx checks cookie_session presence. Adding backend auth broke
the LND UI iframe fetch because the session validation was too
strict for the cross-proxy cookie flow. The nginx layer is the
correct auth gate for this endpoint.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The previous blockchain.numblocks.subscribe call returned data in a
format the parser couldn't extract height from. headers.subscribe
returns {height: N, hex: "..."} which is properly parsed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>