From 6d3704fff51a80abafd025d3e955a3d7b08f6edf Mon Sep 17 00:00:00 2001 From: Dorian Date: Wed, 1 Apr 2026 13:06:57 +0100 Subject: [PATCH] fix: container DNS, nginx chown, onboarding guard, seed UX, install flow Backend: - Add --add-host host.containers.internal:host-gateway to LND and Bitcoin Knots containers (fixes DNS resolution failure in rootless podman) - Add --user 0:0 and DAC_OVERRIDE to nginx UI sidecar containers (fixes chown crash in rootless podman for bitcoin-ui, electrs-ui, lnd-ui) - Add hostadd to Rust Podman API client for web UI container installs - Add Chromium privacy flags to kiosk launcher (disable telemetry) Frontend: - Fix onboarding reset on raw IP visits (trust localStorage as first-class signal, skip boot screen when server is up but not onboarded) - Fix seed regression: persist challenge indices in sessionStorage so going back from Verify doesn't change which words are asked - Remove glass container from seed Generate/Verify/Restore screens - Add Back button to Restore from Seed screen - Replace Network card: Tor (purple), VPN status (orange), Bitcoin sync (orange) - Add ElectrumX to curated app list with correct .webp icon - Install flow: navigate to My Apps immediately with toast, hide installed/installing apps from marketplace and discover views Co-Authored-By: Claude Opus 4.6 (1M context) --- core/container/src/podman_client.rs | 1 + docs/container-architecture.html | 3955 ++++++++++++++++- .../configs/archipelago-kiosk-launcher.sh | 5 + neode-ui/src/composables/useOnboarding.ts | 4 +- neode-ui/src/views/Discover.vue | 7 + neode-ui/src/views/Home.vue | 33 +- neode-ui/src/views/Marketplace.vue | 17 +- neode-ui/src/views/OnboardingSeedGenerate.vue | 2 +- neode-ui/src/views/OnboardingSeedRestore.vue | 16 +- neode-ui/src/views/OnboardingSeedVerify.vue | 22 +- neode-ui/src/views/RootRedirect.vue | 6 +- neode-ui/src/views/discover/curatedApps.ts | 2 +- .../src/views/marketplace/marketplaceData.ts | 11 + scripts/first-boot-containers.sh | 26 +- 14 files changed, 3874 insertions(+), 233 deletions(-) diff --git a/core/container/src/podman_client.rs b/core/container/src/podman_client.rs index cdc97a67..3f005b95 100644 --- a/core/container/src/podman_client.rs +++ b/core/container/src/podman_client.rs @@ -310,6 +310,7 @@ impl PodmanClient { "portmappings": port_mappings, "mounts": mounts, "env": env_map, + "hostadd": ["host.containers.internal:host-gateway"], "devices": manifest.app.devices.iter().map(|d| { serde_json::json!({"path": d}) }).collect::>(), diff --git a/docs/container-architecture.html b/docs/container-architecture.html index 7ee65dd8..d7cf2ee1 100644 --- a/docs/container-architecture.html +++ b/docs/container-architecture.html @@ -3,425 +3,3992 @@ -Archipelago Container Architecture +Archipelago System Architecture - +
-

Archipelago Container Architecture

-

Interactive map of all containers, dependencies, and networks · Click any container for details

+

Archipelago System Architecture

+

Complete interactive map of every layer, protocol, container, and data path · Click anything to expand

+
+ + +
-
-
28
Total Containers
-
4
Tier 0 · DB
-
2
Tier 1 · Core
-
8
Tier 2 · Service
-
14
Tier 3 · App
+
+ + + +
-
- - - - - - - + +
+ + + + +
+ +
+
34
Containers
+
8
System Layers
+
9
Protocols
+
LUKS2
Encryption
+
Rootless
Containers
+
Tor
Privacy Layer
+
+ +
+
+
+ Kiosk Display + Layer 8 · Physical +
+
X11 + Chromium fullscreen on VT7, showing the web UI directly on connected monitor
+
The TV/monitor screen you see when the box is plugged in. No keyboard needed — it just shows the dashboard.
+
+
+
+ Web UI (Vue.js SPA) + Layer 7 · Application +
+
Vue 3 + TypeScript + Pinia frontend served by nginx, communicates via JSON-RPC and WebSocket
+
The dashboard you use in your browser to manage everything — apps, Bitcoin, settings.
+
+
+
+ Rust Backend + Layer 6 · Service +
+
Archipelago binary on 127.0.0.1:5678 — RPC server, auth, session management, container orchestration, Tor control
+
The brain of the system. Handles login, manages containers, talks to Bitcoin, and coordinates everything.
+
+
+
+ Container Layer (Podman Rootless) + Layer 5 · Isolation +
+
34 containers on archy-net (internal DNS) and bridge networks, managed by rootless Podman
+
Each app runs in its own sandbox. If one app crashes or gets hacked, the others are unaffected.
+
+
+
+ Network Layer + Layer 4 · Network +
+
Nginx reverse proxy (80/443), Tailscale mesh VPN, Tor hidden services, UFW firewall
+
Controls what traffic goes where. One front door (nginx) routes requests to the right app. Tor makes you reachable without exposing your IP.
+
+
+
+ Encryption Layer + Layer 3 · Security +
+
LUKS2 full-disk encryption on /var/lib/archipelago with auto-detected cipher (AES-XTS or ChaCha20-Adiantum)
+
All your Bitcoin data, passwords, and app data are encrypted. If someone steals the hard drive, they get nothing.
+
+
+
+ Operating System + Layer 2 · OS +
+
Debian 12 (Bookworm) minimal — systemd services, x86_64/ARM64, debootstrap custom base
+
The operating system. Debian is rock-solid Linux used by servers worldwide. We strip it down to just what's needed.
+
+
+
+ Hardware / Boot + Layer 1 · Physical +
+
UEFI + BIOS dual-boot, GPT partitions, USB flash installer, auto-detect disk + network + CPU features
+
The physical computer. Flash a USB stick, boot from it, and the installer sets everything up automatically.
+
+
+ +
+

Container Dependency Chain

+
// Startup order: Databases → Core → Services → Apps +// Health monitor restarts in this order too + +mempool-db ───┐ +btcpay-db ───┤ + + ├──→ bitcoin-knots ──→ electrumx + + ┌────┴────┬──────────┬──────────┐ + + lnd fedimint mempool-api nbxplorer + + fedi-gw mempool-web btcpay + + └──→ lnd-ui + +// IndeedHub stack (independent) +ih-postgres ──→ ih-api ──→ indeedhub +ih-redis ──→ ih-api +ih-minio ──→ ih-api + +// Penpot stack (independent) +penpot-pg ──→ penpot-be ──→ penpot-fe +penpot-vk ──→ penpot-be ──→ penpot-exp + +// Tier 3: All independent — start in any order +filebrowser grafana homeassist jellyfin photoprism +vaultwarden nextcloud searxng uptime-kuma ollama +onlyoffice nginx-pm portainer
+
-
+ + + +
+
+ +
+
+ 1. Hardware / Boot + Physical +
+
UEFI + BIOS dual-boot installer, GPT partition table, auto-detect hardware
+
You flash a USB drive, plug it in, and the computer installs itself. Works on both old and new machines.
+
+

Partition Layout

+
    +
  • 1 MB — BIOS boot (for older machines without UEFI)
  • +
  • 512 MB — EFI System Partition (UEFI boot files)
  • +
  • 30 GB — Root filesystem (Debian OS, binaries, Podman storage)
  • +
  • Remaining — LUKS2 encrypted → /var/lib/archipelago (all user data)
  • +
+

Installer Features

+
    +
  • Auto-detects target disk (largest available, prefers NVMe)
  • +
  • Auto-detects AES-NI CPU support for encryption cipher selection
  • +
  • Debootstrap minimal Debian 12 (no bloat)
  • +
  • Configures GRUB for both UEFI and legacy BIOS
  • +
  • Creates archipelago user (UID 1000) with Podman subuid/subgid mapping
  • +
+

Hardware Targets

+
    +
  • x86_64: HP ProDesk, Intel NUC, any standard PC
  • +
  • ARM64: Planned but not primary target yet
  • +
  • Minimum: 4 cores, 8GB RAM, 256GB disk
  • +
  • Recommended: 4+ cores, 16GB RAM, 1TB+ disk (for full Bitcoin node)
  • +
+
+
+ +
+
+ 2. Operating System — Debian 12 + OS +
+
Minimal Debian Bookworm with systemd, custom kernel parameters, hardened services
+
The foundation. Debian is one of the most stable and trusted Linux versions. We remove everything unnecessary.
+
+

Key Packages

+
    +
  • podman — Rootless container runtime (replaces Docker)
  • +
  • nginx — Reverse proxy (front door for all web traffic)
  • +
  • tor — Privacy network daemon
  • +
  • tailscale — Mesh VPN for remote access
  • +
  • chromium — Kiosk browser for local display
  • +
  • cryptsetup — LUKS disk encryption
  • +
  • xorg — Display server for kiosk mode
  • +
+

Kernel Tuning

+
    +
  • net.ipv4.ip_unprivileged_port_start=80 — lets rootless Podman bind ports 80+
  • +
  • vm.overcommit_memory=1 — for Redis/Valkey container requirements
  • +
  • User namespaces enabled for rootless containers
  • +
+

Users

+
    +
  • archipelago (UID 1000) — main user, owns all containers and data
  • +
  • root — only for Tor management, LUKS, and boot services
  • +
+
+
+ +
+
+ 3. Encryption — LUKS2 + Security +
+
Full-disk encryption on all user data with auto-detected hardware-accelerated ciphers
+
Your Bitcoin wallet, passwords, photos — everything is scrambled. Without the key, the data is just noise.
+
+

Cipher Selection (auto-detected at install)

+
    +
  • With AES-NI: aes-xts-plain64 (AES-256-XTS) — hardware-accelerated, fastest option
  • +
  • Without AES-NI: xchacha20,aes-adiantum-plain64 (ChaCha20-Adiantum) — fast on any CPU
  • +
+

Key Derivation

+
    +
  • PBKDF: Argon2id (memory-hard, GPU-resistant)
  • +
  • Key size: 512 bits
  • +
  • Key file: /root/.luks-archipelago.key (4KB random, auto-generated)
  • +
+

What's Encrypted

+
    +
  • Bitcoin blockchain data
  • +
  • LND wallet & Lightning channels
  • +
  • All database volumes (PostgreSQL, MariaDB)
  • +
  • All app data directories
  • +
  • Secrets (RPC passwords, macaroons, API keys)
  • +
  • Tor hidden service keys
  • +
+

What's NOT Encrypted

+
    +
  • Root filesystem (OS binaries, system config) — no sensitive data here
  • +
  • EFI/boot partitions (must be readable to start)
  • +
+
+
+ +
+
+ 4. Network Layer + Network +
+
Nginx reverse proxy, Tailscale mesh VPN, Tor hidden services, UFW firewall
+
One front door (nginx) for all traffic. Tor lets people reach you without knowing your real address. Tailscale lets YOU reach the box from anywhere.
+
+

Nginx Reverse Proxy

+
    +
  • Listens on :80 (HTTP) and :443 (HTTPS with self-signed cert)
  • +
  • Serves Vue.js SPA at /
  • +
  • Proxies backend at /rpc/v1, /ws, /health
  • +
  • Proxies each app at /app/{name}/
  • +
  • Rate limits: auth (3/s), RPC (20/s), P2P (10/s)
  • +
  • Security headers: CSP, HSTS, X-Frame-Options, Permissions-Policy
  • +
  • Injects nostr-provider.js into all app iframes
  • +
+

Tor

+
    +
  • System-level Tor daemon (not containerized)
  • +
  • SOCKS5 proxy at 127.0.0.1:9050
  • +
  • Hidden services for: web UI, LND, BTCPay, Mempool, Fedimint
  • +
  • Backend manages services via privileged helper script
  • +
  • Containers connect via host.containers.internal:9050
  • +
+

Tailscale

+
    +
  • Mesh VPN — access your node from anywhere via encrypted tunnel
  • +
  • Runs as system service or container
  • +
  • Provides stable IP (e.g., 100.x.x.x) regardless of network
  • +
+

Firewall (UFW)

+
    +
  • DEFAULT_FORWARD_POLICY=ACCEPT (required for rootless Podman)
  • +
  • Allow: 22 (SSH), 80 (HTTP), 443 (HTTPS), 8333 (Bitcoin P2P), 9735 (Lightning P2P)
  • +
+
+
+ +
+
+ 5. Rust Backend + Service +
+
Archipelago binary — JSON-RPC server, auth, RBAC, container management, Tor control, DID identity
+
The control center. Every button you click in the dashboard sends a message here, and it makes things happen.
+
+

Bind

+
    +
  • 127.0.0.1:5678 — localhost only, nginx handles external access
  • +
+

Endpoints

+
    +
  • POST /rpc/v1 — JSON-RPC 2.0 (all commands)
  • +
  • WS /ws — WebSocket (live updates, container status, logs)
  • +
  • GET /health — Health check (no auth)
  • +
  • /archipelago/ — P2P node messaging
  • +
  • /content — Content sharing (via Tor)
  • +
  • /dwn — Decentralized Web Node protocol
  • +
+

Key RPC Methods

+
    +
  • auth.* — login, TOTP, password change, onboarding
  • +
  • seed.* — generate, verify, restore wallet seeds
  • +
  • package.* — container CRUD (create, start, stop, remove)
  • +
  • node.* — DID identity, signing, backups
  • +
  • app.* — marketplace, app config
  • +
+

Systemd Service

+
    +
  • Type: notify (signals readiness to systemd)
  • +
  • Watchdog: 300s (must ping every 120s or gets killed)
  • +
  • MemoryMax: 4GB
  • +
  • Crash recovery on startup (detects unclean shutdown, restarts containers)
  • +
  • Periodic container state snapshots for recovery
  • +
+
+
+ +
+
+ 6. Container Layer — Rootless Podman + Isolation +
+
34 containers, custom bridge network (archy-net), UID mapping, security caps, memory limits
+
Apps run in sealed boxes. They can only see what we let them see, use only the memory we allow, and can't mess with each other.
+
+

Rootless Podman

+
    +
  • All containers run as user archipelago (UID 1000)
  • +
  • No root access required — even if a container is compromised, it can't escalate to root
  • +
  • Subuid/subgid: archipelago:100000:65536
  • +
  • Socket: /run/user/1000/podman/podman.sock
  • +
+

Networks

+
    +
  • archy-net (custom bridge) — Bitcoin stack + services, containers can reach each other by name (DNS)
  • +
  • bridge (default) — standalone apps, port-mapped only
  • +
  • host — Tailscale only (needs full network access)
  • +
+

Security Defaults (per container)

+
    +
  • --cap-drop=ALL then add only what's needed (least privilege)
  • +
  • --security-opt=no-new-privileges
  • +
  • Memory limits (128MB to 4GB depending on app)
  • +
  • Health checks with auto-restart on failure
  • +
  • Read-only root filesystem where possible (--read-only)
  • +
+

UID Mapping (inside container → host)

+
    +
  • root (0) → host UID 100000
  • +
  • postgres (70) → host UID 100070
  • +
  • bitcoin (101) → host UID 100101
  • +
  • grafana (472) → host UID 100472
  • +
  • mariadb (999) → host UID 100999
  • +
+

Registry

+
    +
  • Private registry at 80.71.235.15:3000/archipelago/
  • +
  • HTTP only (insecure, self-hosted Gitea)
  • +
  • All images pre-pulled into registry; nodes pull on first boot
  • +
+
+
+ +
+
+ 7. Web UI — Vue.js SPA + Application +
+
Vue 3 + TypeScript + Pinia + Vite, served as static files by nginx
+
The website that runs on the box. Open it in any browser on your network to manage everything.
+
+

Tech Stack

+
    +
  • Framework: Vue 3 with <script setup lang="ts">
  • +
  • State: Pinia stores
  • +
  • Bundler: Vite 7
  • +
  • Styling: Global CSS with Tailwind utility classes in style.css
  • +
+

Communication

+
    +
  • JSON-RPC: All commands go through rpc-client.tsPOST /rpc/v1
  • +
  • WebSocket: Real-time container status, logs, events via /ws
  • +
  • CSRF: Token in cookie + X-CSRF-Token header
  • +
  • Session: HttpOnly cookie, SameSite=Lax
  • +
  • Retry: Auto-retry 3x with exponential backoff on 502/503
  • +
  • Timeout: 15s default (configurable per call)
  • +
+

Key Views

+
    +
  • / — Dashboard (system status, apps)
  • +
  • /kiosk — Kiosk mode (public, no auth)
  • +
  • /kiosk-recovery — Fallback with IP + QR code
  • +
  • /marketplace — App installer
  • +
  • /settings — System configuration
  • +
+

App Embedding

+
    +
  • Apps open as iframes via /app/{name}/ proxy paths
  • +
  • Each iframe gets nostr-provider.js injected for identity
  • +
+
+
+ +
+
+ 8. Kiosk Display + Physical +
+
X11 + Chromium in kiosk mode on VT7, auto-start, crash recovery
+
Plug in a monitor and the dashboard appears fullscreen. No login, no desktop, just your node. Press Ctrl+Alt+F1 for a terminal.
+
+

How It Works

+
    +
  • X11 server (Xorg) starts on Virtual Terminal 7
  • +
  • Chromium launches in --kiosk --app=http://localhost/kiosk mode
  • +
  • No address bar, no tabs, no right-click — just the dashboard
  • +
  • Cursor hidden after 3 seconds of inactivity
  • +
  • Screen blanking disabled
  • +
+

Resource Limits

+
    +
  • --disable-gpu — software rendering only
  • +
  • --renderer-process-limit=1 — single renderer process
  • +
  • --js-flags="--max-old-space-size=128" — 128MB JS heap max
  • +
  • --disable-metrics-reporting — no telemetry to Google
  • +
  • --enable-low-end-device-mode — reduce animations and compositing
  • +
+

Controls

+
    +
  • Ctrl+Alt+F7 — switch to kiosk
  • +
  • Ctrl+Alt+F1 — switch to terminal
  • +
  • sudo archipelago-kiosk enable|disable|toggle|status
  • +
+
+
+ +
+
+ + + + +
+ +
+ + + + + + + + + +
+ +
Tier 0 — Databases
+
-
- archy-mempool-db - archy-net -
+
archy-mempool-dbarchy-net
+
MariaDB database storing Bitcoin mempool transaction data for the Mempool block explorer.
+
A database that remembers pending Bitcoin transactions so the block explorer can show them.
mariadb:11.4.10
-
No ports exposed
+
No exposed ports (internal only)
-
UID100999:100999
-
Healthmariadb -uroot -e 'SELECT 1'
-
UI TabServices
+
UID100999:100999 (mariadb user)
+
Memory512 MB
+
Healthmariadb -uroot -e 'SELECT 1'
Data/var/lib/archipelago/mysql-mempool
+
Databasemempool (user: mempool)
DepsNone
Needed bymempool-api
+
-
- archy-btcpay-db - archy-net -
+
archy-btcpay-dbarchy-net
+
PostgreSQL database for BTCPay Server and NBXplorer, storing invoices, transactions, and merchant data.
+
Stores your payment invoices and transaction history for the Bitcoin payment processor.
postgres:15.17
-
No ports exposed
+
No exposed ports (internal only)
-
UID100070:100070
-
Healthpg_isready -U postgres
-
UI TabServices
+
UID100070:100070 (postgres user)
+
Memory512 MB
+
Healthpg_isready -U postgres
+
Data/var/lib/archipelago/postgres-btcpay
+
Databasesbtcpay, nbxplorer
Needed bynbxplorer btcpay
+ +
+
indeedhub-postgresarchy-net
+
PostgreSQL database for IndeedHub social platform, storing posts, user profiles, and relay data.
+
The database that stores all the social media posts and user data for IndeedHub.
+
postgres:16.13-alpine
+
No exposed ports (internal only)
+
+
Needed byindeedhub-api
+
+
+ +
+
indeedhub-redisarchy-net
+
Redis in-memory cache for IndeedHub, handling sessions, job queues, and real-time data.
+
A fast temporary memory store so IndeedHub pages load quickly and background tasks run smoothly.
+
redis:7.4.8-alpine
+
No exposed ports
+
+
Needed byindeedhub-api
+
+
+ +
+
penpot-postgresbridge
+
PostgreSQL database for Penpot design tool, storing projects, layers, and design assets.
+
Stores all the design projects and files for the Penpot design tool.
+
postgres:15
+
No exposed ports
+
+
Memory256 MB
+
Needed bypenpot-backend
+
+
+ +
+
penpot-valkeybridge
+
Valkey (Redis fork) cache for Penpot, handling sessions and real-time collaboration sync.
+
Fast memory cache that makes Penpot's real-time collaboration work smoothly.
+
valkey:8.1
+
No exposed ports
+
+
Memory128 MB
+
Needed bypenpot-backend
+
+
+
-
- immich_postgres - bridge +
immich_postgresbridge
+
PostgreSQL with vector extensions for Immich photo AI/search. Optional — only if Immich is installed.
+
Database for the photo manager. Has special AI search features for finding photos by what's in them.
+
immich-postgres:14-vectorchord (optional)
+
+
Memory256 MB
+
Needed byimmich
-
immich-postgres:14 (optional)
-
Needed byimmich
+
-
- immich_redis - bridge -
+
immich_redisbridge
+
Valkey cache for Immich job queue (photo processing, thumbnail generation).
+
Manages the queue of photos waiting to be processed and thumbnailed.
valkey:8.1.6 (optional)
-
Needed byimmich
+
+
Memory128 MB
+
Needed byimmich
+
+
+
-
Tier 1 — Core Infrastructure
+
Tier 1 — Core Bitcoin Infrastructure
+
-
- bitcoin-knots - archy-net -
+
bitcoin-knotsarchy-net
+
Full Bitcoin node (Knots variant). Validates every transaction and block independently. The root dependency for the entire Bitcoin stack.
+
Your own copy of the entire Bitcoin network. Nobody can lie to you about your balance because you verify everything yourself.
bitcoin-knots:latest
-
Ports: 8332 8333
+
Ports: 8332 8333 28332 28333
-
UID100101:100101
-
Healthbitcoin-cli getblockchaininfo
-
UI TabApps
-
Memory2g (1g low-mem)
-
DiskPrunes if <1TB, txindex if ≥1TB
+
Port 8332JSON-RPC API (how other apps talk to Bitcoin)
+
Port 8333P2P network (connects to other Bitcoin nodes worldwide)
+
Port 28332ZMQ block notifications (instant alert when new block arrives)
+
Port 28333ZMQ transaction notifications (instant alert for new transactions)
+
Memory2 GB (1 GB on low-memory systems)
+
Healthbitcoin-cli getblockchaininfo
+
Data/var/lib/archipelago/bitcoin (~500GB full, ~550MB pruned)
+
Disk modeAuto: prune if <1TB, full txindex if ≥1TB
+
RPC AuthHMAC-SHA256 salted hash (no plaintext password in config)
+
TorRoutes P2P through Tor SOCKS5 for privacy
+
CapsCHOWN, FOWNER, SETUID, SETGID, DAC_OVERRIDE
DepsNone — ROOT DEPENDENCY
Needed byelectrumx lnd mempool nbxplorer fedimint
+
-
- electrumx - archy-net -
+
electrumxarchy-net
+
Electrum protocol server. Indexes the blockchain by address so wallets can look up balances instantly without scanning every block.
+
An index for Bitcoin. Like a book's table of contents — instead of reading every page to find your info, you jump straight to it.
electrumx:v1.18.0
-
Ports: 50001
+
Ports: 50001 8000
-
Healthcurl localhost:8000
-
UI TabApps
-
Depsbitcoin-knots
+
Port 50001Electrum protocol (wallet connections)
+
Port 8000Health check / status API
+
Memory1 GB
+
Data/var/lib/archipelago/electrumx
+
ProtocolElectrum JSON-RPC over TCP
+
Depsbitcoin-knots (reads blockchain via RPC)
Needed bymempool-api
+
+
-
Tier 2 — Services
+
Tier 2 — Services (depend on Bitcoin core)
+
-
lndarchy-net
+
lndarchy-net
+
Lightning Network Daemon. Enables instant, low-fee Bitcoin payments through payment channels.
+
Lets you send and receive Bitcoin instantly (instead of waiting 10+ minutes for a block). Like a tab at a bar — settle up later on-chain.
lnd:v0.18.4-beta
Ports: 9735 10009 8080
+
Port 9735Lightning P2P (connects to other Lightning nodes)
+
Port 10009gRPC API (admin operations, authenticated with macaroons)
+
Port 8080REST API (simpler HTTP interface to LND)
+
Memory512 MB
+
Data/var/lib/archipelago/lnd (wallet, channels, macaroons)
+
AuthMacaroon tokens (read-only for queries, admin for mutations)
+
TorActive with stream isolation (each connection uses different circuit)
+
CapsCHOWN, FOWNER, SETUID, SETGID, DAC_OVERRIDE, NET_RAW
Depsbitcoin-knots
-
UI TabApps
-
Needed byfedi-gateway (LND mode)
+
Needed byfedi-gateway (LND mode) lnd-ui
+
-
mempool-apiarchy-net
+
mempool-apiarchy-net
+
Mempool.space backend API. Provides blockchain analytics, fee estimates, and transaction tracking.
+
The engine behind the block explorer. Shows you what's happening on the Bitcoin network in real time.
mempool-backend:v3.0.0
Ports: 8999
+
Memory512 MB
+
Data/var/lib/archipelago/mempool
Depsbitcoin-knots electrumx mempool-db
-
UI TabServices
Needed bymempool-web
+
-
archy-mempool-webarchy-net
+
archy-mempool-webarchy-net
+
Mempool.space frontend. The visual block explorer with real-time mempool visualization and fee graphs.
+
Your personal mempool.space — watch Bitcoin blocks being mined, see fee rates, track your transactions.
mempool-frontend:v3.0.0
Ports: 4080
+
Memory256 MB
Depsmempool-api
-
UI TabApps
+
Nginx path/app/mempool/
+
-
archy-nbxplorerarchy-net
+
archy-nbxplorerarchy-net
+
NBXplorer blockchain scanner. Watches Bitcoin addresses for BTCPay and notifies when payments arrive.
+
Watches Bitcoin for incoming payments and tells BTCPay Server when money arrives for your invoices.
nbxplorer:2.6.0
Ports: 32838
+
Memory512 MB
+
Data/var/lib/archipelago/nbxplorer
Depsbitcoin-knots btcpay-db
-
UI TabServices
Needed bybtcpay
+
-
btcpay-serverarchy-net
+
btcpay-serverarchy-net
+
Self-hosted Bitcoin payment processor. Accept Bitcoin payments with invoices, checkout pages, and POS.
+
Your own payment terminal for Bitcoin. Create invoices, get paid, no middleman taking a cut.
btcpayserver:1.13.7
Ports: 23000
+
Memory1 GB
+
Data/var/lib/archipelago/btcpay
Depsnbxplorer btcpay-db
-
UI TabApps
+
Nginx path/app/btcpay/
+
TorHas its own .onion address for receiving payments privately
+
-
fedimintarchy-net
+
fedimintarchy-net
+
Federated mint daemon. Enables community-run Bitcoin custody with threshold signing and e-cash tokens.
+
A way for a group of trusted people to collectively hold Bitcoin. No single person can steal the funds — you need a majority to approve.
fedimintd:v0.10.0
Ports: 8173 8174 8175
+
Port 8173P2P (federation member communication)
+
Port 8174API / WebSocket (client connections)
+
Port 8175Web UI (guardian dashboard)
+
Memory512 MB
+
Data/var/lib/archipelago/fedimint
Depsbitcoin-knots
-
UI TabApps
Needed byfedi-gateway
+
-
fedimint-gatewayarchy-net
+
fedimint-gatewayarchy-net
+
Lightning bridge for Fedimint. Connects the federation to the Lightning Network for instant payments.
+
Connects your community mint to Lightning so federation members can send/receive instant payments.
gatewayd:v0.10.0
Ports: 8176
+
Memory512 MB
+
Data/var/lib/archipelago/fedimint-gateway
+
ModeAuto-detect: uses LND if available, otherwise built-in LDK Lightning
Depsbitcoin-knots fedimint
-
UI TabApps
-
NoteUses LDK (built-in Lightning) if no LND
+
-
immich_serverbridge
+
immich_serverbridge
+
Self-hosted Google Photos replacement with AI-powered search, face detection, and automatic organization.
+
Like Google Photos but on your own hardware. Your photos never leave your box. AI finds faces and objects locally.
immich-server:release (optional)
Ports: 2283
Depsimmich_postgres immich_redis
+
Nginx path/app/immich/
+
+
-
Tier 3 — Applications (independent, no dependencies)
+
Tier 3 — Applications (independent, no cross-dependencies)
+ +
+
archy-bitcoin-uiarchy-net
+
Custom Bitcoin node dashboard showing sync status, peer connections, and blockchain info.
+
A pretty dashboard for your Bitcoin node. See how synced you are, how many peers you have, block height.
+
bitcoin-ui:latest
+
Ports: 8334
+
+
Memory128 MB
+
Nginx path/app/bitcoin-ui/
+
+
+ +
+
archy-lnd-uiarchy-net
+
Custom Lightning dashboard showing channels, balances, routing stats, and payment history.
+
Dashboard for your Lightning node. See your channels, balance, and recent payments at a glance.
+
lnd-ui:latest
+
Ports: 8081
+
+
Memory128 MB
+
Nginx path/app/lnd/
+
+
+ +
+
archy-electrs-uihost
+
ElectrumX status dashboard showing sync progress, connected clients, and index health.
+
Shows whether the Electrum index is synced and healthy. How far behind it is, how many wallets are connected.
+
electrs-ui:latest
+
Ports: 50002
+
+
Memory128 MB
+
Networkhost (needs direct access to localhost:50001)
+
Nginx path/app/electrumx/
+
+
+
-
homeassistantbridge
+
homeassistantbridge
+
Open-source home automation platform. Control lights, sensors, cameras, and IoT devices from one dashboard.
+
Smart home control center. Turn lights on, check sensors, automate your house — all locally, no cloud needed.
home-assistant:2024.1
Ports: 8123
+
+
Memory512 MB
+
Data/var/lib/archipelago/home-assistant
+
Nginx path/app/homeassistant/
+
+
-
grafanabridge
+
grafanabridge
+
Monitoring and visualization platform. Dashboards for system metrics, Bitcoin stats, and container health.
+
Beautiful graphs and charts showing how your system is doing. CPU, memory, Bitcoin sync, everything visualized.
grafana:10.2.0
Ports: 3000
-
UID100472:100472
-
-
-
jellyfinbridge
-
jellyfin:10.8.13
-
Ports: 8096
-
-
-
photoprismbridge
-
photoprism:240915
-
Ports: 2342
-
-
-
vaultwardenbridge
-
vaultwarden:1.30.0-alpine
-
Ports: 8082
-
-
-
nextcloudbridge
-
nextcloud:28
-
Ports: 8085
-
-
-
searxngbridge
-
searxng:latest
-
Ports: 8888
+
+
UID100472:100472 (grafana user)
+
Memory256 MB
+
Data/var/lib/archipelago/grafana
+
Read-onlyYes (tmpfs for /tmp, /run)
+
Nginx path/app/grafana/
+
+
-
uptime-kumabridge
+
uptime-kumabridge
+
Self-hosted uptime monitor. Pings your services and alerts you when something goes down.
+
Watches all your apps and sends alerts if anything stops working. Like a security guard for your services.
uptime-kuma:1
Ports: 3001
+
+
Memory256 MB
+
Data/var/lib/archipelago/uptime-kuma
+
-
-
filebrowserbridge
-
filebrowser:v2.27.0
-
Ports: 8083
-
NoteOnly container created by first-boot (unbundled ISO)
+ +
+
jellyfinbridge
+
Self-hosted media server. Stream your movies, TV shows, and music from your own hardware.
+
Your own Netflix. Put movies on the box, watch them on any device. No subscription, no limits.
+
jellyfin:10.8.13
+
Ports: 8096
+
+
Memory1 GB
+
Data/var/lib/archipelago/jellyfin/{config,cache}
+
Transcodetmpfs /tmp (256MB, rw,exec)
+
Nginx path/app/jellyfin/
+
+ +
+
photoprismbridge
+
AI-powered photo management. Automatic face recognition, location mapping, and smart search.
+
Photo organizer that uses AI to tag and sort your pictures. Find photos by searching "sunset" or "cat."
+
photoprism:240915
+
Ports: 2342
+
+
Memory1 GB (512 MB on low-memory)
+
Data/var/lib/archipelago/photoprism
+
+
+ +
+
vaultwardenbridge
+
Bitwarden-compatible password manager. Store all your passwords encrypted, sync across devices.
+
Your personal password safe. Store every password securely and auto-fill them on your phone and computer.
+
vaultwarden:1.30.0-alpine
+
Ports: 8082
+
+
Memory256 MB
+
Data/var/lib/archipelago/vaultwarden
+
CapsCHOWN, SETUID, SETGID, NET_BIND_SERVICE
+
+
+ +
+
nextcloudbridge
+
Self-hosted file sync and collaboration platform. Dropbox/Google Drive replacement with calendar, contacts, and office docs.
+
Your own Dropbox. Sync files, share documents, manage calendar and contacts — all on your own hardware.
+
nextcloud:29
+
Ports: 8085
+
+
Memory1 GB
+
Data/var/lib/archipelago/nextcloud
+
+
+ +
+
searxngbridge
+
Privacy-respecting metasearch engine. Searches Google, Bing, DuckDuckGo and others without tracking you.
+
Private search engine. Searches the web without anyone tracking what you look for.
+
searxng:latest
+
Ports: 8888
+
+
Memory512 MB
+
Data/var/lib/archipelago/searxng
+
Read-onlyYes (tmpfs for /tmp, /run)
+
+
+
-
onlyofficebridge
+
onlyofficebridge
+
Self-hosted document editor. Edit Word, Excel, and PowerPoint files collaboratively in the browser.
+
Like Google Docs but on your own box. Edit spreadsheets and documents with others in real time.
onlyoffice:latest
Ports: 9980
+
+
Memory2 GB (1 GB on low-memory)
+
+
-
ollamabridge
+
ollamabridge
+
Local AI model runner. Run LLMs (like Llama, Mistral) entirely on your hardware, no cloud needed.
+
ChatGPT on your own box. Talk to AI privately — nothing you say leaves your machine.
ollama:latest (optional)
Ports: 11434
+
+
Memory4 GB (1 GB on low-memory)
+
Data/var/lib/archipelago/ollama
+
Read-onlyYes (tmpfs for /tmp, /run)
+
ProtocolREST API at :11434 (OpenAI-compatible)
+
+ +
+
filebrowserbridge
+
Web-based file manager. Browse, upload, and download files through the browser.
+
A file explorer in your browser. Upload, download, and manage files on the box without SSH.
+
filebrowser:v2.27.0
+
Ports: 8083
+
+
Memory256 MB
+
Data/var/lib/archipelago/filebrowser (served), filebrowser-data (DB)
+
Read-onlyYes
+
Max upload10 GB (nginx limit)
+
+
+
-
nginx-proxy-managerbridge
+
nginx-proxy-managerbridge
+
GUI for managing nginx proxy rules and SSL certificates. Point-and-click reverse proxy configuration.
+
A visual tool for routing web traffic. Point domains to services and manage HTTPS certificates with clicks, not config files.
nginx-proxy-manager:latest
Ports: 81 8084 8443
+
+
Port 81Admin dashboard
+
Port 8084HTTP proxy
+
Port 8443HTTPS proxy
+
Memory256 MB
+
+
-
portainerbridge
+
portainerbridge
+
Container management UI. Visual dashboard for Podman containers — start, stop, inspect, view logs.
+
Visual control panel for all your containers. See what's running, restart things, read logs — no terminal needed.
portainer:latest
Ports: 9000
+
+
Memory256 MB
+
SocketPodman socket mounted as Docker socket
+
+
+ +
+
+ + +
+
IndeedHub Stack — Nostr-based Social Platform
+
+ +
+
indeedhub-minioarchy-net
+
S3-compatible object storage for IndeedHub media files (images, videos, attachments).
+
File storage for IndeedHub. When someone posts an image, it lives here.
+
minio:RELEASE.2024-11-07
+
+
Needed byindeedhub-api
+
+
+ +
+
indeedhub-apiarchy-net
+
IndeedHub backend API. Handles Nostr events, user profiles, media uploads, and relay communication.
+
The engine behind IndeedHub. Processes posts, handles user accounts, talks to Nostr relays.
+
indeedhub-api (custom build)
+
+
Depspostgres redis minio
+
Needed byindeedhub
+
+
+ +
+
indeedhub-ffmpegarchy-net
+
Video transcoding worker for IndeedHub. Converts uploaded videos to web-friendly formats.
+
Converts videos so they play smoothly in the browser. Like a video format translator.
+
indeedhub-ffmpeg (custom build)
+
+ +
+
indeedhub-relayarchy-net
+
Nostr relay for IndeedHub. Stores and distributes Nostr events (posts, follows, reactions).
+
A message board that stores Nostr posts. Other Nostr apps can connect here to read and post.
+
indeedhub-relay (custom build)
+
+ +
+
indeedhubarchy-net
+
IndeedHub web frontend. Nostr-based social media client with feeds, profiles, messaging, and media.
+
The social media app itself. Post, follow people, send messages, share media — all on the Nostr protocol.
+
indeedhub-frontend (custom build)
+
Ports: 7777
+
+
Nginx path/app/indeedhub/
+
WebSocketYes (for real-time updates)
+
Depsindeedhub-api
+
+
+ +
+
+ + +
+
Penpot Stack — Design Tool
+
+ +
+
penpot-backendbridge
+
Penpot application server. Handles design data, real-time collaboration, and file storage.
+
The engine behind the design tool. Saves your designs and lets multiple people edit at the same time.
+
penpot-backend:2.4
+
+
Memory512 MB
+
Depspenpot-postgres penpot-valkey
+
+
+ +
+
penpot-exporterbridge
+
Renders Penpot designs to PDF, SVG, and image formats for export.
+
Turns your designs into downloadable files — PDFs, images, SVGs.
+
penpot-exporter:2.4
+
+
Memory256 MB
+
Depspenpot-backend
+
+
+ +
+
penpot-frontendbridge
+
Penpot web UI. Open-source Figma alternative with vector editing, prototyping, and collaboration.
+
Your own Figma. Design interfaces, create prototypes, collaborate — completely self-hosted.
+
penpot-frontend:2.4
+
Ports: 9001
+
+
Memory256 MB
+
Nginx path/app/penpot/
+
Depspenpot-backend
+
+
+ +
+
+ +
+
archy-net (internal DNS, Bitcoin stack)
+
bridge (standalone, port-mapped)
+
host (direct network access)
+
Dimmed = optional / not always installed
+
+
+ + + + +
+ +
+ +
+
JSON-RPC 2.0
+
Primary protocol between the web UI and the Rust backend. All commands are RPC calls.
+
Like texting the backend: you send a message ("please start this app"), it texts back ("done" or "error").
+
+ Endpoint: POST /rpc/v1
+ Format: {"jsonrpc":"2.0","method":"package.start","params":{"id":"bitcoin-knots"},"id":1}
+ Auth: Session cookie + CSRF token header
+ Timeout: 15s default
+ Retry: 3 attempts with exponential backoff on 502/503
+ Rate limit: 20 req/s (burst 40)
+ Used by: Vue.js frontend → Rust backend +
+
+ +
+
WebSocket
+
Real-time bidirectional channel for live updates — container status changes, logs, events.
+
An open phone line between your browser and the server. Instead of asking "any updates?" every second, the server just tells you when something changes.
+
+ Endpoint: WS /ws (HTTP upgrade)
+ Auth: Session cookie
+ Read timeout: 86,400s (24 hours)
+ Events: Container state changes, log streams, system alerts
+ Used by: Vue.js frontend ↔ Rust backend +
+
+ +
+
Bitcoin RPC (JSON-RPC 1.0)
+
How apps talk to the Bitcoin node. Authenticated with username + HMAC-hashed password.
+
The language apps use to ask Bitcoin questions: "what's the current block?" or "send this transaction."
+
+ Endpoint: bitcoin-knots:8332 (inside archy-net)
+ Auth: HTTP Basic with rpcauth hash (HMAC-SHA256, no plaintext)
+ Methods: getblockchaininfo, getmempoolinfo, sendrawtransaction, etc.
+ Timeout: 10s default, 30s for heavy ops
+ Used by: ElectrumX, LND, Mempool, NBXplorer, Fedimint → Bitcoin Knots +
+
+ +
+
gRPC
+
High-performance RPC protocol used by LND for admin operations. Binary format, strongly typed.
+
A fast, structured way for apps to control the Lightning node. More efficient than regular HTTP for complex operations.
+
+ Endpoint: lnd:10009
+ Auth: Macaroon tokens (read-only for queries, admin for mutations)
+ TLS: Self-signed certificate (auto-generated)
+ Methods: OpenChannel, SendPayment, GetInfo, ListChannels, etc.
+ Used by: Fedimint Gateway, LND UI → LND +
+
+ +
+
Electrum Protocol
+
Lightweight protocol for wallet address lookups. JSON-RPC over raw TCP sockets.
+
How Bitcoin wallets check their balance without downloading the entire blockchain. Ask "what transactions touched this address?" and get an instant answer.
+
+ Endpoint: electrumx:50001 (TCP)
+ Format: Newline-delimited JSON-RPC
+ Methods: blockchain.scripthash.get_balance, blockchain.transaction.get, etc.
+ Used by: Wallets (Sparrow, Electrum), Mempool API → ElectrumX +
+
+ +
+
ZMQ (ZeroMQ)
+
Publish-subscribe messaging from Bitcoin node. Instant notifications for new blocks and transactions.
+
A broadcasting system. When a new Bitcoin block is found, Bitcoin instantly shouts it out and everyone listening hears immediately.
+
+ Endpoints:
+ • tcp://bitcoin-knots:28332 — New block hashes (hashblock)
+ • tcp://bitcoin-knots:28333 — New raw transactions (rawtx)
+ Pattern: PUB/SUB (publisher/subscriber)
+ Subscribers: LND, Mempool, ElectrumX +
+
+ +
+
Tor (SOCKS5 + Hidden Services)
+
Privacy layer. Routes Bitcoin P2P through onion routing, exposes services as .onion addresses.
+
Like sending a letter through 3 random post offices so nobody knows where it came from. Also lets people reach your node without knowing your real IP.
+
+ SOCKS5 proxy: 127.0.0.1:9050
+ Container access: host.containers.internal:9050
+ Hidden services: Web UI, LND, BTCPay, Mempool, Fedimint
+ Managed by: System Tor daemon + tor-helper.sh (privileged helper)
+ Used by: Bitcoin P2P, LND P2P, BTCPay invoices +
+
+ +
+
Nostr (NIP-01)
+
Decentralized social protocol. WebSocket-based relay communication for events (posts, follows, messages).
+
A social media protocol where no company controls the network. Your posts live on relays, and you own your identity with a cryptographic key.
+
+ Transport: WebSocket (WSS)
+ Format: JSON events signed with secp256k1 keys
+ Relay: IndeedHub relay (local), configurable external relays
+ Integration: nostr-provider.js injected into all app iframes
+ Identity: DID-based, linked to node Ed25519 keypair +
+
+ +
+
DWN (Decentralized Web Node)
+
W3C protocol for storing encrypted data and messages in a decentralized way. Identity-linked storage.
+
A personal data vault. Apps can store data here that only you control. Like a safety deposit box that follows you across the internet.
+
+ Endpoint: /dwn (proxied through nginx)
+ Auth: Per-record DID-based permissions
+ Reachable via: Tor hidden service
+ Used for: Encrypted backups, cross-node messaging, app data sync +
+
+ +
+
+ + + + +
+ +
+
2
DID Methods
+
26+
Identity RPCs
+
W3C 2.0
VC Spec
+
Ed25519
Primary Key
+
DWN
Data Store
+
Dual Key
Ed25519 + secp256k1
+
+ +
+ +
+
+ Applications + Layer 5 · UI +
+
Vue.js views for identity management, credential issuance, DWN dashboard, and quick actions
+
The screens where you manage your digital identity, issue credentials, and control your personal data store.
+
+

Key Views

+
    +
  • Web5Identities.vue — Create/manage identities (Personal, Business, Anonymous purposes)
  • +
  • Web5CredentialsSummary.vue — View issued/held credentials with status badges
  • +
  • Web5DWN.vue — DWN status, protocol registration, message browser
  • +
  • Web5QuickActions.vue — Copy DID, publish to DHT, trigger sync
  • +
+

Data Types (TypeScript)

+
    +
  • ManagedIdentity — id, name, purpose, did, pubkey, nostr_pubkey, profile
  • +
  • VCData — id, issuer, subject, type, claims, status (active/revoked/expired)
  • +
  • DwnStatusData — running, sync_status, message_count, registered_protocols
  • +
+
+
+ +
+
+ Verifiable Credentials (W3C VC 2.0) + Layer 4 · Trust +
+
Issue, verify, revoke, and present credentials with Ed25519Signature2020 proofs — W3C VC Data Model 2.0
+
Digital certificates that prove things about you — signed by one identity, held by another, verified by anyone. Like a digitally signed diploma.
+
+

Three-Party Model

+
    +
  • Issuer: Creates and signs the credential (any managed identity)
  • +
  • Holder: Stores credentials, creates Verifiable Presentations
  • +
  • Verifier: Checks signature + expiration + revocation status
  • +
+

Credential Structure

+
    +
  • @context: W3C Credentials v2 + Ed25519 signature suite
  • +
  • type: ["VerifiableCredential", "CustomType"]
  • +
  • issuer: did:key or did:dht
  • +
  • credentialSubject: { id: did, claims: {...} }
  • +
  • proof: Ed25519Signature2020 with verification method reference
  • +
  • credentialStatus: CredentialStatusList2021 for revocation
  • +
+

Verifiable Presentations

+
    +
  • Bundle one or more VCs with holder's own signature
  • +
  • Proof purpose: authentication (vs. assertionMethod for VCs)
  • +
  • Selective disclosure — present only relevant credentials
  • +
+

RPC Methods

+
    +
  • identity.issue-credential — Issue from any managed identity
  • +
  • identity.verify-credential — Verify by credential ID
  • +
  • identity.list-credentials — List with optional filtering
  • +
+

Storage

+
    +
  • /var/lib/archipelago/credentials/store.json
  • +
+
+
+ +
+
+ Decentralized Web Node (DWN) + Layer 3 · Storage +
+
Personal data store with protocol-governed records, peer sync over Tor, and DID-based authorization
+
Your personal database that YOU own. Apps ask permission to read/write data. Syncs with trusted peers automatically over Tor.
+
+

Records Interface

+
    +
  • Records.Write — Store a message (UUID-based record_id)
  • +
  • Records.Read — Retrieve by record_id
  • +
  • Records.Query — Filter by protocol, schema, author, date range
  • +
  • Records.Delete — Remove record
  • +
+

Protocol Definitions

+
    +
  • Declarative rule sets governing data structure and access permissions
  • +
  • types: Define allowed dataFormats and optional schema URIs
  • +
  • structure: Hierarchical — records can have child records (post → comment)
  • +
  • $actions: Who can create/read/update/delete (anyone, author, recipient)
  • +
  • Registered via dwn.register-protocol RPC, enforced automatically
  • +
+

Peer Sync

+
    +
  • Bidirectional sync with trusted peers over Tor SOCKS5 proxy (127.0.0.1:9050)
  • +
  • Deduplication by record_id, batched (200 messages per sync)
  • +
  • 30s per-peer timeout, 90s total timeout
  • +
  • State persisted to /var/lib/archipelago/dwn/sync_state.json
  • +
  • Triggered manually or via background task
  • +
+

HTTP API

+
    +
  • Endpoint: POST /dwn (proxied through nginx)
  • +
  • Reachable remotely via Tor hidden service
  • +
+

RPC Methods (8)

+
    +
  • dwn.status — Running state, sync status, message count
  • +
  • dwn.sync — Trigger background sync with trusted peers
  • +
  • dwn.register-protocol / dwn.list-protocols / dwn.remove-protocol
  • +
  • dwn.write-message / dwn.query-messages / dwn.read-message / dwn.delete-message
  • +
+

Storage

+
    +
  • Messages: /var/lib/archipelago/dwn/messages/{record_id}.json
  • +
  • Protocols: /var/lib/archipelago/dwn/protocols/{protocol_uri}.json
  • +
+
+
+ +
+
+ Decentralized Identifiers (DIDs) + Layer 2 · Identity +
+
W3C DID Core 1.0 — did:key (primary, offline-capable) and did:dht (discoverability via BitTorrent Mainline DHT)
+
Your self-sovereign digital identity. No company issues it, no platform controls it. You prove who you are with cryptographic keys.
+
+

did:key (Primary Method)

+
    +
  • Self-contained — no external resolution, works fully offline
  • +
  • Format: did:key:z6Mk... (multicodec Ed25519 in base58btc)
  • +
  • Instant, zero-cost, no network dependency
  • +
  • Used for: VCs, federation trust, backup encryption, DWN signing
  • +
  • Cannot be rotated — key is the identifier
  • +
+

did:dht (Discovery Method)

+
    +
  • Publishes DID Document to BitTorrent Mainline DHT via BEP-44 signed mutable items
  • +
  • Format: did:dht:z... (z-base-32 encoded Ed25519 pubkey)
  • +
  • Globally discoverable without any centralized registry
  • +
  • DID Document encoded as DNS Resource Records in DNS packet
  • +
  • Supports key rotation (increment sequence number, republish)
  • +
  • 1-hour TTL cache for performance
  • +
  • Replaced did:ion (Bitcoin-anchored) — simpler, no full node required
  • +
+

DID Document (W3C Core 1.0)

+
    +
  • verificationMethod: Ed25519VerificationKey2020 + derived X25519KeyAgreementKey2020
  • +
  • authentication, assertionMethod, capabilityInvocation, capabilityDelegation
  • +
  • keyAgreement: X25519 (derived from Ed25519 via Curve25519)
  • +
  • Optional EcdsaSecp256k1VerificationKey2019 for Nostr interop
  • +
  • Service endpoints: DWN URL, Nostr relay list
  • +
+

Multi-Identity Manager

+
    +
  • Users create multiple identities with purpose tags: Personal, Business, Anonymous
  • +
  • One default identity (marked with star in UI)
  • +
  • Each identity: Ed25519 key + optional Nostr secp256k1 key + optional NIP-01 profile
  • +
  • Stored as JSON in /var/lib/archipelago/identities/{id}.json
  • +
+

Identity RPC Methods (26+)

+
    +
  • identity.create / .list / .get / .delete / .set-default
  • +
  • identity.sign / .verify — Ed25519 message signing
  • +
  • identity.resolve-did / .verify-did-document
  • +
  • identity.create-dht-did / .resolve-dht-did / .refresh-dht-did
  • +
  • identity.create-nostr-key / .nostr-sign
  • +
  • identity.nostr-encrypt-nip04 / .nostr-decrypt-nip04
  • +
  • identity.nostr-encrypt-nip44 / .nostr-decrypt-nip44
  • +
  • identity.update-profile / .resolve-remote-did
  • +
+
+
+ +
+
+ Cryptographic Keys + Layer 1 · Foundation +
+
Dual key architecture — Ed25519 for Web5/DIDs + secp256k1 for Bitcoin/Nostr, both derived from BIP-39 master seed
+
Two types of cryptographic keys derived from one master seed. One for identity (Web5), one for money and social (Bitcoin/Nostr).
+
+

Ed25519 (Web5 & Identity)

+
    +
  • W3C DIDs, Verifiable Credentials (Ed25519Signature2020)
  • +
  • DWN message signing and authorization
  • +
  • Federation peer authentication and trust
  • +
  • Backup encryption (via derived X25519 key agreement)
  • +
  • Storage: /var/lib/archipelago/identity/node_key (32 bytes raw)
  • +
+

secp256k1 (Bitcoin & Nostr)

+
    +
  • Nostr event signing (NIP-01), encrypted DMs (NIP-04, NIP-44)
  • +
  • Lightning Network node identity
  • +
  • Social presence and discovery (NIP-05, kind 30078)
  • +
  • Format: hex pubkey + Nostr npub (NIP-19 bech32)
  • +
+

Key Derivation

+
    +
  • Single BIP-39 master seed (12 or 24 word mnemonic)
  • +
  • Deterministic derivation of both Ed25519 and secp256k1 keys
  • +
  • All keys recoverable from seed phrase alone
  • +
  • Seed generated at first boot, stored on LUKS-encrypted partition
  • +
+

Rust Dependencies

+
    +
  • ed25519-dalek 2.2.0 — Ed25519 signatures
  • +
  • curve25519-dalek 4.1.3 — X25519 key agreement (Ed25519 → X25519 conversion)
  • +
  • nostr-sdk 0.44 — secp256k1 signing, NIP-04/44 encryption
  • +
  • mainline 2 — BitTorrent Mainline DHT client (did:dht)
  • +
  • zbase32 0.1 — z-base-32 encoding for DID identifiers
  • +
+
+
+ +
+ +

Specification Status

+

+ Web5 was initiated by TBD (Block/Jack Dorsey) and shut down November 2024. Open-source components were donated to the Decentralized Identity Foundation (DIF). + The W3C specs (DIDs, VCs) are independent standards with broad industry adoption. Archipelago implements these W3C standards directly with a custom DWN — not dependent on TBD's SDK. +

+ + + + + + + + + +
ComponentSpecStatusArchipelago
DID Core 1.0W3C RecommendationStabledid:key + did:dht, full DID Document generation
VC Data Model 2.0W3C Recommendation (May 2025)StableIssue/verify/revoke with Ed25519Signature2020
DWNDIF DraftDraftCustom Records interface, protocol management, Tor sync
did:dhtNear v1.0ActiveMainline DHT publishing via mainline crate
did:ion (Sidetree)DIF 1.0AbandonedNot implemented — requires Bitcoin + IPFS full nodes
Presentation Exchange 2.0DIF RatifiedStableVerifiable Presentations with holder proof
+ +

Architecture Decision Records

+ + + + + +
ADRDecisionRationale
ADR-002did:key as primary DID methodSelf-contained, offline-capable, zero-cost, aligns with sovereignty
ADR-008Dual key architecture (Ed25519 + secp256k1)Ed25519 for W3C/Web5, secp256k1 for Bitcoin/Lightning/Nostr ecosystems
ADR-011Custom DWN, not full W3C spec complianceTBD shut down, DWN spec stalled. Federation + Nostr relays prioritized for peer sync
+ +
+ + + + +
+ +
+
+
1
+
+
BIOS / UEFI → GRUB Bootloader
+
Firmware loads GRUB from EFI partition or BIOS boot sector. GRUB loads the Linux kernel.
+
The computer turns on and finds the operating system to start. Works on both old and new machines.
+
GRUB installed for both UEFI (EFI partition) and legacy BIOS (1MB boot sector) — dual-boot compatible
+
+
+ +
+
2
+
+
LUKS Unlock → Mount Encrypted Partition
+
Cryptsetup opens the LUKS2 volume using /root/.luks-archipelago.key and mounts it at /var/lib/archipelago.
+
The encrypted safe is unlocked automatically (no password prompt). All your data becomes accessible.
+
Configured in /etc/crypttab for automatic boot-time unlock
+
+
+ +
+
3
+
+
systemd Starts → Network Online
+
systemd boots Debian, starts networking, reaches network-online.target. Tor and Tailscale start.
+
The operating system finishes starting up and connects to the internet.
+
+
+ +
+
4
+
+
Archipelago Backend Starts
+
archipelago.service launches the Rust binary. Runs crash recovery, starts container state snapshots, initializes JSON-RPC server on :5678.
+
The brain of the system wakes up. If there was a crash last time, it automatically recovers.
+
Type=notify, WatchdogSec=300s, MemoryMax=4G
+
+
+ +
+
5
+
+
First Boot: Container Creation
+
first-boot-containers.sh runs once (guarded by marker file). Creates all containers in tier order: databases → Bitcoin → services → apps.
+
On first startup only: installs all the apps. Databases first, then Bitcoin, then everything else. Takes 5-15 minutes.
+
ConditionPathExists=!/var/lib/archipelago/.first-boot-containers-done · Timeout: 900s
+
+
+ +
+
6
+
+
Nginx Ready → Web UI Accessible
+
Nginx serves the Vue.js SPA on :80/:443. Backend health check at /health passes.
+
The website is now live. Open a browser and go to the machine's IP address to see the dashboard.
+
+
+ +
+
7
+
+
Kiosk Starts (if enabled)
+
archipelago-kiosk.service waits for /health endpoint (up to 30s), then starts X11 + Chromium on VT7.
+
If a monitor is plugged in, the dashboard appears fullscreen automatically. No login needed for the local screen.
+
Polls /health 15 times at 2s intervals before launching Chromium
+
+
+ +
+
8
+
+
Background Services Start
+
Timers activate: container doctor (health repair), reconciler (spec enforcement), self-update. Tor helper watches for service config changes.
+
Maintenance robots start working in the background. They check on apps, fix broken ones, and keep everything updated.
+
archipelago-doctor.timer, archipelago-reconcile.timer, archipelago-update.timer, archipelago-tor-helper.path
+
+
+
+
+ + + + +
+ +
+ +
+
LUKS2 Full-Disk Encryption
+
All user data on an encrypted partition. Auto-detects AES-NI for hardware acceleration, falls back to ChaCha20-Adiantum. Key derived with Argon2id (GPU-resistant).
+
If someone physically steals your hard drive, all they get is encrypted noise. The data is useless without the key.
+
+ +
+
Rootless Containers
+
All containers run as unprivileged user archipelago (UID 1000). Even a compromised container cannot escalate to root. UID remapping isolates container users from host users.
+
Apps run in sealed boxes without admin access. A hacked app can't take over the whole machine.
+
+ +
+
Capability Dropping
+
--cap-drop=ALL then add only specific capabilities needed. --security-opt=no-new-privileges prevents privilege escalation inside containers.
+
Each app only gets the exact permissions it needs, nothing more. Like giving a valet driver only the car key, not your house keys.
+
+ +
+
RBAC (Role-Based Access Control)
+
Backend uses explicit method allowlists per role. No prefix matching — each RPC method must be explicitly permitted. Session cookies are HttpOnly, SameSite=Lax.
+
Different users have different permissions. A viewer can't install apps, and nobody can run commands that aren't on the approved list.
+
+ +
+
Rate Limiting
+
Nginx rate limits: auth endpoints (3/s), RPC (20/s), P2P (10/s). Prevents brute-force attacks and API abuse.
+
If someone tries to guess your password by trying thousands of combinations, they get locked out after a few attempts per second.
+
+ +
+
Tor Privacy
+
Bitcoin P2P routes through Tor with stream isolation. Hidden services expose node without revealing IP. LND uses Tor for Lightning P2P.
+
Your Bitcoin node connects through the Tor privacy network. Nobody can see your real IP address or location.
+
+ +
+
Credential Management
+
Secrets auto-generated at first boot (CSPRNG). Stored in /var/lib/archipelago/secrets/ (mode 700). Bitcoin RPC uses HMAC-SHA256 auth hashes, never plaintext.
+
Passwords are randomly generated and stored securely. They never appear in config files as readable text.
+
+ +
+
Memory Limits & Health Checks
+
Every container has a memory limit (128MB–4GB). Health checks auto-restart failed containers. Backend has systemd watchdog (300s).
+
Runaway apps can't eat all the memory. Crashed apps restart automatically. The system self-heals.
+
+ +
+
Security Headers
+
CSP (Content Security Policy), HSTS, X-Frame-Options: SAMEORIGIN, X-Content-Type-Options: nosniff, strict Referrer-Policy, disabled camera/mic/geolocation.
+
The web UI tells browsers to follow strict security rules — no loading scripts from unknown sites, no accessing your camera.
+
+ +
+
Systemd Hardening
+
ProtectSystem=strict, MemoryDenyWriteExecute, RestrictRealtime, RestrictAddressFamilies. Backend can only write to approved paths.
+
The operating system restricts what the backend can do. It can only touch the files it needs to, nothing else on the system.
+
+ +
+
+ + + + +
+ +
/var/lib/archipelago/ ← LUKS2 encrypted partition + ├── bitcoin/ Bitcoin blockchain data (~500GB full, ~550MB pruned) + ├── lnd/ Lightning wallet, channels, macaroons, TLS cert + ├── electrumx/ Address index database + ├── postgres-btcpay/ BTCPay PostgreSQL data + ├── mysql-mempool/ Mempool MariaDB data + ├── mempool/ Mempool backend cache + ├── btcpay/ BTCPay server data, plugins + ├── nbxplorer/ NBXplorer blockchain scan state + ├── fedimint/ Federation data, consensus state + ├── fedimint-gateway/ Gateway keys and routing table + ├── home-assistant/ Smart home config, automations, database + ├── grafana/ Dashboards, datasources, alerting rules + ├── uptime-kuma/ Monitor definitions, status history + ├── jellyfin/ Media library metadata, transcoding cache + ├── photoprism/ Photo index, thumbnails, AI models + ├── ollama/ Downloaded LLM models (can be multi-GB) + ├── vaultwarden/ Encrypted password vault database + ├── nextcloud/ Files, calendar, contacts, config + ├── searxng/ Search engine settings + ├── filebrowser/ Served files (user uploads) + ├── filebrowser-data/ FileBrowser internal database + ├── nginx-proxy-manager/ Proxy rules, Let's Encrypt certificates + ├── portainer/ Portainer config and database + ├── tailscale/ VPN state, node identity + ├── secrets/ RPC passwords, DB passwords (mode 700) + ├── identity/ Node Ed25519 keypair (DID identity) + ├── identities/ User DIDs + ├── tor-config/ Tor service definitions (backend-managed) + ├── tor-hostnames/ .onion addresses (synced from /var/lib/tor) + └── .first-boot-containers-done Marker: first boot completed + +/opt/archipelago/ ← Unencrypted (on root partition) + ├── web-ui/ Vue.js SPA (static files served by nginx) + ├── scripts/ Deploy, container, and maintenance scripts + └── image-versions.sh Pinned container image versions + +/usr/local/bin/archipelago Rust backend binary +/etc/nginx/ Nginx config (reverse proxy rules) +/etc/tor/torrc Tor daemon configuration
+ +
+ + + + +
+ +
+ +
+
Podman
+
Daemonless container engine (OCI-compatible, Docker alternative)
+
A tool that runs apps in isolated sandboxes. Like Docker but doesn't need a background service running as root.
+
+ +
+
Rootless
+
Containers run entirely in user namespace, no root privileges required
+
The sandboxes run without admin access. Even if someone breaks into one, they can't take over the system.
+
+ +
+
LUKS2
+
Linux Unified Key Setup v2 — dm-crypt disk encryption with Argon2 KDF
+
Industry-standard disk encryption for Linux. Scrambles the entire partition so data is unreadable without the key.
+
+ +
+
Argon2id
+
Memory-hard password hashing function resistant to GPU/ASIC brute-force
+
A way to protect passwords that requires lots of memory to crack, making it extremely expensive to brute-force even with specialized hardware.
+
+ +
+
Nginx
+
High-performance HTTP reverse proxy and web server
+
The front door of the system. All web traffic goes through nginx, which directs each request to the right app.
+
+ +
+
Reverse Proxy
+
Server that forwards client requests to backend services based on URL path or hostname
+
A traffic cop for web requests. You visit one address, and the proxy routes you to the right app behind the scenes.
+
+ +
+
JSON-RPC
+
Remote procedure call protocol using JSON over HTTP/TCP
+
A simple way for one program to ask another to do something. Send a JSON message, get a JSON reply.
+
+ +
+
WebSocket
+
Full-duplex TCP communication channel over HTTP upgrade
+
A persistent connection between browser and server. Instead of repeatedly asking "anything new?", the server pushes updates instantly.
+
+ +
+
gRPC
+
Google's high-performance RPC framework using Protocol Buffers over HTTP/2
+
A fast, structured way for programs to communicate. Used by LND because it handles many Lightning operations efficiently.
+
+ +
+
ZMQ (ZeroMQ)
+
Asynchronous messaging library for pub/sub and push/pull patterns
+
A broadcasting system. Bitcoin publishes "new block!" and every subscribed app hears it instantly.
+
+ +
+
Tor
+
Onion routing network for anonymous communication via encrypted relay circuits
+
A privacy network that bounces your traffic through multiple servers so nobody can trace it back to you.
+
+ +
+
Hidden Service (.onion)
+
Tor service accessible via .onion address without revealing server IP
+
A way to make your node reachable on the internet without revealing your IP address or location.
+
+ +
+
Tailscale
+
WireGuard-based mesh VPN with NAT traversal and SSO integration
+
A private tunnel to your node from anywhere. Like a VPN but easier — install the app on your phone, and you can access the node from a coffee shop.
+
+ +
+
Macaroon
+
Bearer token with embedded caveats (permissions) used by LND for API auth
+
A special key for LND that says exactly what you're allowed to do. A "read-only" macaroon can check balance but can't send money.
+
+ +
+
CSRF Token
+
Cross-Site Request Forgery prevention token sent in cookie + header
+
A secret code that proves your browser request is genuine and not a trick from a malicious website.
+
+ +
+
DID (Decentralized Identifier)
+
W3C standard for self-sovereign identity using cryptographic keypairs
+
Your digital identity that you own completely. Like a passport that no government issued — you prove who you are with math, not authority.
+
+ +
+
Nostr
+
Notes and Other Stuff Transmitted by Relays — decentralized social protocol
+
A social media protocol where you own your identity. No company can ban you because your account is just a cryptographic key.
+
+ +
+
Lightning Network
+
Bitcoin Layer 2 payment channel network for instant, low-fee transactions
+
A way to send Bitcoin instantly (milliseconds) for tiny fees. Works by opening "tabs" between nodes and settling on-chain later.
+
+ +
+
Fedimint
+
Federated Bitcoin custody protocol with threshold signing and Chaumian e-cash
+
A community Bitcoin bank where a group of trusted guardians hold funds together. No single person can steal — you need a majority to sign.
+
+ +
+
archy-net
+
Custom Podman bridge network with DNS resolution for Bitcoin-stack containers
+
A private network inside the box where Bitcoin apps can find each other by name. Like a local phone book for containers.
+
+ +
+
Capability (CAP)
+
Fine-grained Linux privilege (e.g. CAP_NET_RAW, CAP_CHOWN) instead of full root
+
Instead of giving an app all admin powers, we give it only the specific abilities it needs. A file manager gets "change file ownership" but not "change network settings."
+
+ +
+
Systemd
+
Linux init system and service manager (PID 1)
+
The thing that starts everything when Linux boots. Manages all services, restarts crashed ones, and enforces resource limits.
+
+ +
+
RBAC
+
Role-Based Access Control — permissions assigned by user role, not individually
+
Different users get different permissions based on their role (admin, viewer, etc). Prevents regular users from doing dangerous things.
+
+ +
+
+ + + + +
+ +

Architecture analysis sourced from Start9Labs/start-os on GitHub (master branch). Click any layer to expand.

+ +
+
LXC
Container Runtime
+
Rust
Backend (startd)
+
Angular 21
Frontend
+
S9PK v2
Package Format
+
Optional
LUKS Encryption
+
btrfs
Filesystem
+
+ +
+
Angular 21 + Taiga UI 5UI
Three Angular apps: admin UI, setup wizard, VPN management. Patch-DB reactive sync via CBOR diffs over WebSocket.
+
Rust Backend (startd / startbox)Service
Single binary with 5 personalities (symlinks). Built-in reverse proxy (Axum), DNS (hickory-server), ACME, WireGuard, SOCKS5.
+
LXC ContainersIsolation
Two-layer model: outer LXC per service (SquashFS + OverlayFS), inner subcontainers from S9PK images. JSON-RPC over Unix sockets.
+
Network (built-in)Network
VHostController reverse proxy, hickory-server DNS, ACME TLS, WireGuard tunnels, SOCKS5 at 10.0.3.1:1080. No nginx/caddy.
+
Optional LUKS on btrfsSecurity
User chooses encrypted or unencrypted during setup. LVM with btrfs for COW snapshots enabling safe app installs.
+
Debian BookwormOS
Same Debian 12 base. Targets x86_64, ARM64 (aarch64), and RISC-V (riscv64).
+
+ +
+ + + + +
+
+ +
+
+ Web UI — Angular 21 + Application +
+
Angular 21 + TypeScript + Taiga UI 5 components, served directly by the Rust backend (Axum)
+
The dashboard you use in your browser. Built with Angular (Google's web framework), not served by a separate web server.
+
+

Three Separate Angular Apps

+
    +
  • projects/ui/ — Main admin interface
  • +
  • projects/setup-wizard/ — Initial setup flow
  • +
  • projects/start-tunnel/ — VPN management UI
  • +
+

State Management

+
    +
  • Patch-DB: Backend pushes CBOR diffs over WebSocket
  • +
  • Frontend applies diffs and notifies observers via PatchDB.watch$()
  • +
  • Converted to Angular signals via toSignal()
  • +
  • Reactive — UI updates automatically when backend state changes
  • +
+

Communication

+
    +
  • JSON-RPC exclusively (not REST)
  • +
  • ApiService abstract class with 100+ methods
  • +
  • i18n: 5 languages (en, es, de, fr, pl)
  • +
+
+
+ +
+
+ Rust Backend — startd (startbox) + Service +
+
Single Rust binary (multi-personality via symlinks: startd, start-cli, start-container, registrybox, tunnelbox)
+
The brain of the system. One binary that does everything — serves the UI, manages containers, handles networking, runs the built-in reverse proxy.
+
+

Key Components

+
    +
  • Axum web server: Serves UI + JSON-RPC API (no separate web server)
  • +
  • VHostController: Built-in reverse proxy with TLS termination (no nginx/caddy)
  • +
  • Patch-DB: Custom CBOR-encoded reactive database with diff-based WebSocket sync
  • +
  • LxcManager: Container lifecycle (create, destroy, garbage collection)
  • +
  • NetController: DNS (hickory-server), SOCKS5, ACME, WiFi, WireGuard, port forwarding
  • +
  • Service Actors: Per-service state machines managing lifecycle
  • +
+

Binary Personalities (symlinks)

+
    +
  • startd — Main daemon
  • +
  • start-cli — CLI interface
  • +
  • start-container — Runs inside LXC containers, communicates with host
  • +
  • registrybox — Package registry daemon
  • +
  • tunnelbox — WireGuard VPN tunnel daemon
  • +
+

Key Dependencies

+
    +
  • Async: Tokio · Web: Axum 0.8 + Hyper 1.5 · TLS: tokio-rustls 0.26 + OpenSSL (vendored)
  • +
  • DNS: hickory-server · Crypto: blake3, ed25519, x25519-dalek, aes
  • +
  • TypeScript bindings: ts-rs (auto-generates TS types from Rust structs)
  • +
+

Systemd

+
    +
  • startd.service: Type=simple, Restart=always, RestartSec=3
  • +
  • LimitNOFILE=65536
  • +
+
+
+ +
+
+ Container Layer — LXC + Isolation +
+
Linux Containers (LXC) with two-layer model: outer LXC per service + inner subcontainers from S9PK images
+
Each app gets its own sealed Linux environment. Unlike Docker, these are full system containers with their own init process.
+
+

Two-Layer Container Model

+
    +
  • Outer LXC container: One per service. Created by Rust backend via lxc-create/destroy
  • +
  • Base rootfs: SquashFS image (/usr/lib/startos/container-runtime/rootfs.squashfs) mounted as OverlayFS
  • +
  • Inner subcontainers: Node.js container runtime inside each LXC can launch additional containers from S9PK-bundled images
  • +
  • Timeout: 30-second container creation timeout
  • +
+

LXC Configuration

+
    +
  • User namespaces: lxc.idmap = u 0 100000 65536
  • +
  • AppArmor profile: generated with nesting allowed
  • +
  • Network: veth bridge on lxcbr0 (10.0.3.x subnet, host at 10.0.3.1)
  • +
  • OverlayFS rootfs (base read-only squashfs, writes to overlay)
  • +
  • GPU passthrough support: /dev/dri, /dev/nvidia*, /dev/kfd
  • +
+

Communication

+
    +
  • JSON-RPC over Unix domain sockets
  • +
  • /media/startos/rpc/service.sock — Inbound (runtime listens)
  • +
  • /media/startos/rpc/host.sock — Host callbacks (effects)
  • +
+
+
+ +
+
+ Network Layer + Network +
+
Built-in reverse proxy (Axum/Hyper), DNS (hickory-server), SOCKS5, ACME (Let's Encrypt), WireGuard tunnels
+
No nginx or caddy — the Rust backend IS the web server, proxy, and DNS. Also manages VPN tunnels and encryption certificates.
+
+

Built-in Reverse Proxy

+
    +
  • VHostController in core/src/net/vhost.rs handles all HTTP routing
  • +
  • TLS termination via tokio-rustls with SNI-based routing
  • +
  • No external proxy software (no nginx, no caddy, no traefik)
  • +
  • Virtual hosting with per-service domain assignment
  • +
+

DNS

+
    +
  • Built-in DNS server using hickory-server (formerly trust-dns)
  • +
  • Service discovery and resolution for containers
  • +
  • mDNS via avahi-resolve-host-name for .local domains
  • +
+

TLS / Certificates

+
    +
  • Self-signed root CA per server (NIST P-256 via OpenSSL)
  • +
  • Built-in ACME client (async-acme) for Let's Encrypt with TLS-ALPN-01 challenge
  • +
  • Certificate store managed in Patch-DB
  • +
+

Connectivity

+
    +
  • SOCKS5 proxy: Built-in at 10.0.3.1:1080 for container outbound traffic
  • +
  • WireGuard: First-class support via wg-quick + x25519-dalek
  • +
  • Multi-gateway: Supports multiple interfaces (Ethernet, WiFi, WireGuard) with separate domain configs
  • +
  • Port forwarding: iptables-based via InterfacePortForwardController
  • +
+

Tor (Status: Removed in v0.4)

+
    +
  • Architecture doc mentions "Tor via Arti" but Arti is absent from current Cargo.toml
  • +
  • Previous versions (0.3.x) used the C Tor daemon for hidden services
  • +
  • Likely planned for re-integration but not yet implemented in the 0.4 rewrite
  • +
+
+
+ +
+
+ Encryption — Optional LUKS on btrfs + Security +
+
Optional LUKS encryption on LVM volumes, btrfs filesystem with COW snapshots for safe installs
+
Disk encryption is optional (you choose during setup). Uses btrfs which can make instant copies of data for safe app updates.
+
+

Encryption (Optional)

+
    +
  • User chooses encrypted or unencrypted during setup
  • +
  • LVM volume groups: STARTOS_<random> (encrypted) or STARTOS_<random>_UNENC
  • +
  • LUKS via cryptsetup luksFormat/luksOpen with password-based key
  • +
  • Default password: "password" (changed during setup)
  • +
+

Filesystem: btrfs

+
    +
  • Copy-on-Write (COW) snapshots for safe service installs
  • +
  • cp --reflink=always for instant volume snapshots before upgrades
  • +
  • If install fails, volumes restored from snapshot automatically
  • +
+

Volume Layout (LVM)

+
    +
  • main (8 GB) — System data, Patch-DB
  • +
  • package-data (100% remaining) — All service/app data
  • +
+
+
+ +
+
+ Operating System — Debian Bookworm + OS +
+
Debian 12 (same base as Archipelago), systemd services, x86_64 + ARM64 + RISC-V targets
+
Same stable Debian foundation as Archipelago. Supports more CPU architectures including RISC-V.
+
+

Platform Targets

+
    +
  • x86_64: Standard PCs and servers
  • +
  • aarch64: ARM64 (Raspberry Pi 4/5, etc.)
  • +
  • riscv64: RISC-V (emerging architecture)
  • +
+
+
+ +
+ +
+ + + + +
+ +

LXC Container Model

+
+
+
Two-Layer Architecture
+
Outer: One LXC container per service, created by the Rust backend. Base rootfs is a read-only SquashFS image mounted as OverlayFS. Inner: Node.js container runtime inside each LXC can launch subcontainers from S9PK-bundled images.
+
Each app gets its own sealed Linux environment with a read-only base. Any changes go to a separate overlay layer.
+
+
+
Communication
+
JSON-RPC over Unix domain sockets. /media/startos/rpc/service.sock (inbound) and host.sock (host callbacks). Services export init(), uninit(), main() via JavaScript ABI.
+
Apps talk to the system through socket files, not network ports. Each app implements 3 required JavaScript functions.
+
+
+
Isolation
+
User namespaces (container UID 0 → host UID 100000, range 65536). AppArmor profiles with nesting. veth bridge on lxcbr0 (10.0.3.x subnet). GPU passthrough support via manifest flag.
+
Container root maps to an unprivileged host user. Each container gets its own virtual network interface.
+
+
+
Lifecycle
+
LxcManager handles creation (30s timeout), garbage collection, and cleanup. Service actors manage per-service state machines. btrfs reflink snapshots before install/upgrade for atomic rollback.
+
+
+ +

S9PK Package Format (v2)

+
+
+
Signed Merkle Archive
+
Ed25519 signatures with prehashed content (SHA-512 over blake3 merkle root). Magic bytes: 0x3b 0x3b 0x02. Enables partial downloads, integrity verification of subsets, and efficient delta updates.
+
App packages are cryptographically signed and structured so you can verify integrity without downloading the entire thing.
+
+
+
Archive Contents
+
manifest.json (metadata) + javascript.squashfs (service logic, Node.js) + images/<arch>/*.squashfs (container filesystems per CPU architecture) + assets.squashfs (optional static assets) + icon + LICENSE.md
+
Each package bundles its own containers, logic code, icon, and license in one downloadable file.
+
+
+
Service ABI (JavaScript/Node.js)
+
Services implement init(), uninit(), and main() in JavaScript. The container runtime provides an Effects interface for host callbacks (dependency queries, config, health reporting).
+
App developers write their service logic in JavaScript. The system provides a standard API for the app to interact with the host.
+
+
+ +
+ + + + +
+ +
+
+
JSON-RPC (Host ↔ Service)
+
All communication between the Rust backend and services uses JSON-RPC over Unix domain sockets.
+
Apps and the system talk through a structured messaging format over local socket files — fast and secure.
+
+ Transport: Unix domain sockets
+ Inbound: /media/startos/rpc/service.sock
+ Host callbacks: /media/startos/rpc/host.sock (Effects interface)
+ Library: rpc-toolkit (custom Rust crate)
+ Used by: All service containers ↔ startd +
+
+ +
+
JSON-RPC (UI ↔ Backend)
+
The Angular frontend communicates with startd via JSON-RPC over HTTP. 100+ API methods. State sync via Patch-DB WebSocket.
+
The dashboard sends commands and gets responses in JSON format. Live updates stream automatically through a WebSocket.
+
+ Transport: HTTP POST (commands) + WebSocket (state sync)
+ State sync: Patch-DB pushes CBOR-encoded diffs over WebSocket
+ Frontend applies: PatchDB.watch$() → Angular signals via toSignal()
+ Methods: 100+ via ApiService abstract class +
+
+ +
+
Patch-DB (CBOR Reactive Sync)
+
Custom reactive database using CBOR encoding. Backend pushes diffs over WebSocket — UI updates automatically without polling.
+
Instead of the UI constantly asking "what changed?", the backend pushes only what changed, in a compact binary format.
+
+ Encoding: CBOR (Concise Binary Object Representation, RFC 8949)
+ Sync model: Server-push diffs, not request-response
+ Storage: /media/startos/data/main/
+ Advantage: Much smaller than JSON, real-time without polling +
+
+ +
+
HTTPS / TLS (Built-in)
+
TLS termination handled directly by the Rust backend (tokio-rustls). Self-signed root CA per server + ACME for public domains.
+
The backend IS the web server — no nginx or caddy needed. It handles encryption directly.
+
+ TLS library: tokio-rustls 0.26 + OpenSSL (vendored, for cert generation)
+ Local certs: Self-signed root CA (NIST P-256 keys)
+ Public certs: ACME client (async-acme) with TLS-ALPN-01 challenge
+ Routing: SNI-based virtual hosting via VHostController +
+
+ +
+
WireGuard (VPN Tunnels)
+
First-class WireGuard support for remote access. Users add WireGuard configs as "gateways." Managed by tunnelbox daemon.
+
Built-in VPN for accessing your node from anywhere. Add a WireGuard config and get a secure tunnel.
+
+ Implementation: wg-quick + x25519-dalek (Rust)
+ Daemon: tunnelbox (symlink of startbox binary)
+ Multi-gateway: Supports multiple interfaces with separate domain configs +
+
+ +
+
DNS (hickory-server)
+
Built-in DNS server for service discovery and resolution. Also uses mDNS (avahi) for .local domain access on LAN.
+
The system runs its own DNS so containers can find each other by name. Your phone finds the node via .local address.
+
+ Library: hickory-server (formerly trust-dns)
+ mDNS: avahi-resolve-host-name for .local domains
+ Container network: lxcbr0 bridge, host at 10.0.3.1 +
+
+
+
+ + + + +
+
+

Not Implemented in StartOS

+

StartOS does not implement Web5 (DIDs, DWNs, or Verifiable Credentials).
Authentication uses password-based sessions and public/private key signatures.

+
+
+ + + + +
+
+
+
LXC + User Namespaces
+
Each service in its own LXC container with UID/GID mapping (container 0 → host 100000, range 65536). AppArmor profiles with nesting. OverlayFS rootfs (base read-only).
+
Apps run in isolated Linux environments with their own user systems. Container root is mapped to an unprivileged host user.
+
+
+
Package Signing (Ed25519)
+
All S9PK packages signed with Ed25519 over blake3 merkle roots. Signature verified before installation. Prevents supply chain attacks.
+
Every app package is cryptographically signed. If someone tampers with it, the signature check fails and installation is blocked.
+
+
+
btrfs Snapshots
+
COW filesystem snapshots before every install/upgrade. If an install fails, data is atomically restored to the pre-install state.
+
The system takes a snapshot before every app update. If the update fails, your data is automatically rolled back.
+
+
+
Authentication
+
Password-based + session cookies. Local authcookie for CLI. Public/private key signatures for remote admin. Encrypted wire protocol during setup (public key exchange + encrypted password).
+
+
+ +
+ + + + +
+
+
+
1
+
+
Preinit Script
+
Optional /media/startos/config/preinit.sh runs before anything else. Enables local auth cookie.
+
+
+
+
2
+
+
Load Database + SSH Keys
+
Patch-DB loaded from disk (CBOR format). SSH developer keys written. MOK enrollment for Secure Boot if applicable.
+
+
+
+
3
+
+
Network Controller
+
DNS server (hickory-server), SOCKS5 proxy, VHost reverse proxy, port forwarding, ACME client, WiFi configuration — all start together.
+
+
+
+
4
+
+
System Initialization
+
Mount logs to data drive, load CA certificate, set CPU governor to performance, NTP clock sync, enable zram, hardware inventory via lshw.
+
+
+
+
5
+
+
Launch Service Intranet + Services
+
LXC bridge network (lxcbr0) created. Database validated. Service actors start all installed services. Postinit script runs.
-
-

Dependency Chain

-
// Startup order: Tier 0 → 1 → 2 → 3 -// Health monitor restarts in this order too + + + +
+
/media/startos/data/ ← Root data directory (optionally LUKS encrypted) + ├── main/ System data, Patch-DB (8 GB LVM volume) + └── package-data/ All service data (remaining disk space) + ├── volumes/{pkg-id}/data/{vol}/ Per-service volume data + ├── volumes/{pkg-id}/assets/{ver}/ Per-service read-only assets + └── logs/{pkg-id}/ Per-service log output -mempool-db ───┐ -btcpay-db ───┤ - - ├──→ bitcoin-knots ──→ electrumx - - ┌────┴────┬──────────┬──────────┐ - - lnd fedimint mempool-api nbxplorer - - fedi-gw mempool-web btcpay +/usr/lib/startos/ ← System binaries and base images + ├── container-runtime/rootfs.squashfs Base LXC container image + └── package/ Mounted JS from S9PK inside containers -// Tier 3: All independent — start in any order -filebrowser grafana homeassist jellyfin photoprism -vaultwarden nextcloud searxng uptime-kuma ollama
+/var/lib/lxc/ LXC container storage +/media/startos/config/ System config (preinit.sh, postinit.sh, standby) +/media/startos/backups/ Backup mount points per service
-
-
archy-net (Bitcoin stack, inter-container DNS)
-
bridge (standalone apps, port-mapped)
-
Apps tab (user-facing)
-
Services tab (infrastructure)
+ + + + + + + + + + + + + +
+ +

Architecture analysis sourced from getumbrel/umbrel on GitHub (master branch). Click any layer to expand.

+ +
+
Docker
Container Runtime
+
Node.js
Backend (umbreld)
+
React 19
Frontend
+
Compose
App Format
+
None
Disk Encryption
+
A/B Boot
Rugix Partitions
+
+ +
+
React 19 + Tailwind 4 + Radix UIUI
Static SPA served by umbreld's Express server. Zustand + TanStack React Query for state. tRPC for typed API. 8+ languages.
+
umbreld (Node.js 22 / TypeScript)Service
Single daemon on port 80: Express + tRPC API, app lifecycle via Docker Compose, file manager, backups (Kopia), terminal (node-pty).
+
Docker 28.5 (rootful)Containers
Each app is a Docker Compose project. Flat bridge network (10.21.0.0/16). Per-app auth proxy containers. All containers destroyed on boot.
+
NetworkingNetwork
No reverse proxy. Express serves on :80 directly. Optional Tor (containerized). mDNS via avahi. No TLS by default.
+
Debian Trixie (testing) + RugixOS
Date-pinned Debian testing. Rugix A/B root partitions for atomic OS updates with automatic rollback. /data partition persists.
+
+ +
+ +
+
+ +
+
+ Web UI — React 19 + Application +
+
React 19 + TypeScript + Vite 6 + Tailwind 4 + Radix UI, served as static SPA by umbreld's Express server
+
The dashboard you use in your browser. Built with React (Meta's web framework), styled with Tailwind.
+
+

Tech Stack

+
    +
  • Framework: React 19 + TypeScript (strict)
  • +
  • Build: Vite 6
  • +
  • Styling: Tailwind CSS 4 + Radix UI primitives + shadcn/ui patterns
  • +
  • State: Zustand (client) + TanStack React Query v5 (server)
  • +
  • API: tRPC React Query v11 for typed RPC
  • +
  • i18n: i18next (8+ languages)
  • +
  • Animations: Framer Motion (as motion package)
  • +
  • Terminal: xterm.js for in-browser terminal
  • +
  • Charts: Recharts for data visualization
  • +
+
+
+ +
+
+ Backend — umbreld (Node.js/TypeScript) + Service +
+
Single Node.js 22 daemon handling web server, tRPC API, app lifecycle, Tor, backups, file management, and OS updates
+
The brain of the system. A TypeScript process that does everything — web server, app manager, backup handler.
+
+

Key Modules

+
    +
  • Server: Express 4 + tRPC v11 over HTTP and WebSocket on port 80
  • +
  • Apps: Docker Compose lifecycle (install, start, stop, update, uninstall)
  • +
  • AppStore: Git-based — clones getumbrel/umbrel-apps, pulls every 5 minutes
  • +
  • User: Single-user JWT auth (bcrypt $2b$, 12 rounds) + optional TOTP 2FA
  • +
  • Files: File browser with Samba sharing, thumbnails, external storage
  • +
  • Hardware: RAID (ZFS) for Umbrel Home Pro, internal/external storage detection
  • +
  • Backups: Kopia v0.19.0 encrypted backups to external drives
  • +
  • Notifications: In-app notification system + widgets
  • +
  • Terminal: WebSocket-based terminal (node-pty + xterm.js)
  • +
  • Dbus: D-Bus interface to systemd for reboot/shutdown/hostname
  • +
+

State Storage

+
    +
  • YAML file: umbrel.yaml — no database, just a YAML file
  • +
  • Validation: Zod schemas
  • +
  • Docker: dockerode library + execa shell calls
  • +
  • Git: isomorphic-git for app store management
  • +
+

Legacy Compat Layer

+
    +
  • App lifecycle handled by a large bash script (app-script)
  • +
  • Shells out to: docker compose, yq, envsubst, openssl
  • +
  • Explicitly labeled "legacy" in the codebase
  • +
+

Systemd

+
    +
  • umbrel.service: After=network-online.target docker.service
  • +
  • Restart=always, 15-minute stop timeout, StartLimitInterval=0
  • +
+
+
+ +
+
+ Container Layer — Docker (rootful) + Isolation +
+
Docker 28.5.0 (rootful, not rootless) + Docker Compose v2. Each app is a separate Compose project.
+
Apps run in Docker containers managed by Docker Compose. Unlike Podman, Docker runs as root — simpler but less isolated.
+
+

Docker Setup

+
    +
  • Installed via official Docker install script, pinned to v28.5.0
  • +
  • Rootful (runs as root) — not rootless
  • +
  • Each app: separate Docker Compose project (--project-name <app-id>)
  • +
  • Legacy container naming: <app-id>_<service>_1 for DNS compat
  • +
+

Network: Flat Bridge

+
    +
  • Single shared network: umbrel_main_network (10.21.0.0/16)
  • +
  • All apps share one flat network — any container can talk to any other
  • +
  • Static IPs assigned per service (defined in exports.sh)
  • +
  • No per-app network isolation
  • +
+

Per-App Proxy

+
    +
  • Each app gets an app_proxy container (Node.js Express, getumbrel/app-proxy)
  • +
  • Handles JWT authentication for iframe embedding
  • +
  • Proxies to the actual app container on its internal port
  • +
  • UI renders apps in iframes pointing to proxy port
  • +
+

Boot Cleanup

+
    +
  • On every startup: stops ALL containers, prunes ALL networks
  • +
  • Prevents stale state from previous versions
  • +
  • Pre-loads images from /images/ (tor, auth-server baked into ISO)
  • +
+
+
+ +
+
+ Network Layer + Network +
+
No traditional reverse proxy. umbreld serves on port 80. Per-app proxy containers. Optional Tor via container.
+
No nginx, no caddy — the backend itself serves on port 80. Each app has its own mini proxy container for authentication.
+
+

HTTP

+
    +
  • umbreld's Express server listens directly on port 80
  • +
  • Serves UI static files + tRPC API
  • +
  • No port 443/TLS by default — HTTP only on LAN
  • +
  • mDNS via avahi for HOSTNAME.local access
  • +
+

Tor (Optional)

+
    +
  • Toggle per-system (not per-app)
  • +
  • tor_proxy container on 10.21.21.11 (SOCKS5)
  • +
  • Each app gets a tor_server container creating hidden services
  • +
  • Dashboard also gets its own hidden service
  • +
  • Provides end-to-end encryption for remote access
  • +
+

Inter-App Communication

+
    +
  • Via static IPs on the flat 10.21.0.0/16 bridge network
  • +
  • No DNS-based service discovery — IPs hardcoded in exports.sh
  • +
+
+
+ +
+
+ Operating System — Debian Trixie (testing) + OS +
+
Debian Trixie (testing branch, not stable), built from date-pinned snapshot for reproducibility, Rugix A/B partitions
+
Uses Debian's "testing" branch (less stable than Bookworm). Has a clever A/B partition system for safe OS updates.
+
+

OS Build

+
    +
  • Built inside Docker via multi-stage Dockerfile (umbrelos.Dockerfile)
  • +
  • Date-pinned Debian snapshot (e.g., 20251229) for reproducibility
  • +
  • Includes: NetworkManager, avahi, systemd-timesyncd, Bluetooth, SSH
  • +
  • Node.js 22.13.0 baked in
  • +
+

Rugix A/B Partitions

+
    +
  • Two root partitions — active and standby
  • +
  • OS updates write to inactive partition, then swap on reboot
  • +
  • If boot fails, automatic rollback to previous partition
  • +
  • Root filesystem committed after successful boot
  • +
+

Persistent Bind Mounts

+
    +
  • /var/log/data/umbrel-os/var/log
  • +
  • /var/lib/docker/data/umbrel-os/var/lib/docker
  • +
  • /home/data/umbrel-os/home
  • +
  • Separate /data partition persists across OS updates
  • +
+

User

+
    +
  • umbrel (UID 1000), default password umbrel
  • +
  • Synced to web UI password after onboarding
  • +
  • Has sudo access
  • +
+
+
+ +
+ +
+ +
+ +

Docker Container Model

+
+
+
Docker 28.5 (Rootful)
+
Docker daemon runs as root (not rootless). Each app is a separate Docker Compose v2 project (--project-name <app-id>). Legacy container naming: <app-id>_<service>_1 for DNS compatibility.
+
Standard Docker running with root permissions. Each app is managed as a Compose project with its own containers.
+
+
+
Flat Network (No Isolation)
+
Single shared bridge: umbrel_main_network (10.21.0.0/16). All apps share one network — any container can communicate with any other. Static IPs assigned per service via exports.sh.
+
All apps are on the same network. A compromised app could potentially reach other apps' services directly.
+
+
+
Per-App Auth Proxy
+
Each app gets an app_proxy container (getumbrel/app-proxy, Node.js Express) that handles JWT authentication for iframe embedding. Proxies to the actual app on its internal port.
+
Each app has a mini web server in front of it that checks your login before letting you in.
+
+
+
Boot Cleanup
+
On every startup: stops ALL containers, prunes ALL networks to prevent stale state. Pre-loads images from /images/ (tor, auth-server baked into ISO).
+
Every reboot starts fresh by destroying all containers and recreating them. Clean but adds startup time.
+
+
+ +

App Packaging

+
+
+
umbrel-app.yml
+
App manifest with: id, name, version, port, category, dependencies, permissions (GPU), gallery images, release notes, widgets, torOnly flag, installSize. Validated by Zod schema.
+
A YAML file describing the app — what it does, what it needs, what ports it uses, and how to display it in the store.
+
+
+
docker-compose.yml
+
Standard Docker Compose v3.7. Services reference umbrel_main_network. Images pinned by SHA256 digest. Apps define their own security constraints (no enforced capability dropping).
+
Standard Docker Compose file that defines the app's containers, networks, and volumes.
+
+
+
exports.sh + hooks/
+
exports.sh exports environment variables (IPs, ports, credentials) for dependency resolution. hooks/ directory with lifecycle scripts: pre/post-install, pre/post-start, pre/post-stop, pre/post-update, pre-uninstall.
+
Shell scripts that set up environment variables so apps can find each other, plus hooks that run at key lifecycle moments.
+
+
+
App Store (Git repo)
+
Apps distributed via Git repository (getumbrel/umbrel-apps). Cloned locally, pulled every 5 minutes. Community app stores supported. implements field enables alternative implementations (e.g., Bitcoin Knots for Bitcoin Core).
+
The app store is just a Git repository. Umbrel checks for updates every 5 minutes by pulling the latest commits.
+
+
+ +
+ +
+
+
+
tRPC (UI ↔ Backend)
+
TypeScript-first RPC framework with end-to-end type safety. Runs over both HTTP and WebSocket on port 80.
+
A typed communication channel between the dashboard and the backend. If the API changes, TypeScript catches errors automatically.
+
+ Version: tRPC v11
+ Transport: HTTP + WebSocket (via Express 4)
+ Port: 80 (Express serves both UI and API)
+ Type safety: Server types flow directly to client (TanStack React Query v5)
+ Used by: React 19 frontend ↔ umbreld +
+
+ +
+
Docker Compose (App Lifecycle)
+
Each app managed via Docker Compose v2. Install/start/stop/update handled by a bash script (app-script) calling docker compose.
+
Apps are defined as Docker Compose projects. A bash script handles the lifecycle by calling docker compose commands.
+
+ Compose version: v2 (docker compose plugin, not docker-compose binary)
+ Lifecycle script: app-script (bash, labeled "legacy")
+ Tools used: docker compose, yq, envsubst, openssl
+ Hooks: pre/post-install, pre/post-start, pre/post-stop, pre/post-update, pre-uninstall +
+
+ +
+
exports.sh (Dependency Resolution)
+
Shell scripts that export environment variables (IPs, ports, RPC credentials). When app B depends on app A, A's exports.sh is sourced first.
+
Apps share their connection details through environment variables set by shell scripts.
+
+ Variables exported: IP addresses (static), ports, RPC passwords, hidden service hostnames
+ Resolution: Transitive deps resolved in post-order (depth-first)
+ Alternative implementations: settings.yml can map dependency (e.g., bitcoin → bitcoin-knots)
+ Deps NOT auto-installed: UI warns users to install dependencies first +
+
+ +
+
Tor (Optional, Containerized)
+
Toggle per-system. tor_proxy container provides SOCKS5 at 10.21.21.11. Per-app tor_server containers create hidden services.
+
Tor is optional and runs in its own container. When enabled, each app gets its own .onion address for remote access.
+
+ SOCKS5: 10.21.21.11 (tor_proxy container)
+ Per-app: tor_server container creates hidden service pointing to app_proxy
+ Dashboard: Also gets its own hidden service
+ Provides: End-to-end encryption for remote access (since no TLS by default) +
+
+ +
+
JWT + Proxy Tokens (Auth)
+
JWT for API authentication. Separate "proxy tokens" validate iframe requests to app_proxy containers. bcrypt password hashing.
+
Login tokens that prove who you are. Separate tokens for the dashboard API and for accessing individual apps.
+
+ API auth: JWT (jsonwebtoken library)
+ Password: bcrypt ($2b$, 12 rounds)
+ App auth: UMBREL_PROXY_TOKEN cookie validated by app_proxy containers
+ 2FA: Optional TOTP +
+
+ +
+
Git (App Store)
+
App store is a Git repository cloned locally via isomorphic-git. Pulled every 5 minutes for updates.
+
The app catalog is just a Git repo. Umbrel checks for new apps and updates by pulling the latest commits every 5 minutes.
+
+ Default repo: getumbrel/umbrel-apps (GitHub)
+ Library: isomorphic-git (pure JS Git implementation)
+ Pull interval: Every 5 minutes
+ Community stores: Supported (add custom Git URLs) +
+
+
+
+ +
+
+

Not Implemented in umbrelOS

+

umbrelOS does not implement Web5 (DIDs, DWNs, or Verifiable Credentials).
Authentication uses a single-user JWT model. Per-app passwords are derived from a deterministic seed via HMAC-SHA256.

+
+
+ +
+
+
+
No Disk Encryption
+
No LUKS, no dm-crypt. All data stored unencrypted on disk. Backups use Kopia with per-repository passwords. A deterministic seed (256-byte random token) derives per-app passwords via HMAC-SHA256.
+
If someone physically steals the drive, all data is readable. No encryption at rest.
+
+
+
Flat Network (No App Isolation)
+
All apps share one Docker bridge (10.21.0.0/16). Any container can communicate with any other container. The app_proxy adds authentication but not network isolation.
+
All apps are on the same network. A compromised app could potentially access other apps' services.
+
+
+
Authentication
+
Single user model. Password hashed with bcrypt ($2b$, 12 rounds). JWT tokens for API auth. Separate "proxy tokens" for app iframe auth. Optional TOTP 2FA. Session cookie: UMBREL_PROXY_TOKEN.
+
+
+
Rootful Docker
+
Docker daemon runs as root. Containers run as UID 1000 where possible, but no enforced capability dropping or security profiles. No --cap-drop=ALL, no no-new-privileges by default.
+
Docker has root access to the machine. Individual containers may or may not restrict their own privileges.
+
+
+ +
+ +
+
+
+
1
+
+
GRUB / Rugix → Select Active Partition
+
GRUB (amd64) or tryboot (RPi) loads the kernel from the active A/B partition. Rugix commits to current partition on successful boot.
+
+
+
+
2
+
+
systemd → Docker → umbreld
+
systemd starts, brings up networking (NetworkManager) and Docker daemon. umbrel.service starts umbreld --data-directory=/home/umbrel/umbrel.
+
+
+
+
3
+
+
umbreld Initialization
+
Runs startup migrations, syncs system password, restores WiFi, waits for NTP sync (10s, important for RPi with no RTC).
+
+
+
+
4
+
+
Docker Clean Slate
+
Stops and removes ALL containers, prunes ALL networks. Pre-loads images from /images/. Prevents stale state from previous versions.
+
Destructive reset on every boot — ensures clean state but adds startup time
+
+
+
+
5
+
+
Start App Environment + All Apps
+
Starts tor_proxy + auth containers first, then all installed apps in parallel. Express HTTP server starts on port 80. App store update loop begins (every 5 min).
+
+
+
+ +
+ +
+
/home/umbrel/umbrel/ ← Main data directory (NO encryption) + ├── umbrel.yaml Main config/state (YAML file, not a database) + ├── app-data/{app-id}/ Per-app data, compose files, manifests + ├── app-stores/ Git clones of app store repositories + ├── tor/data/app-{id}/hostname Per-app .onion addresses + ├── db/umbrel-seed/seed Deterministic seed (256-byte) for per-app passwords + ├── secrets/jwt JWT signing secret + └── home/ User files, backups + +/opt/umbreld/ umbreld daemon (npm-linked) +/opt/umbreld/ui/ React SPA static files +/images/ Pre-loaded Docker images (tor, auth-server)
+
+ + + + + + + +
+ +
+
3
Systems Compared
+
Rust
2/3 Backends
+
Debian
3/3 Base OS
+
3
Container Runtimes
+
3
Frontend Frameworks
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AspectArchipelagoStartOSumbrelOSNotes / Trade-offs
Core Architecture
Backend LanguageRustRustTypeScript / Node.js 22Rust: memory safety, performance, no GC pauses. Node.js: faster prototyping, larger ecosystem, but runtime overhead.
FrontendVue 3 + Vite 7 + TailwindAngular 21 + Taiga UI 5React 19 + Vite 6 + Tailwind 4All modern choices. Angular is heaviest (TypeScript-only). Vue/React are lighter. Tailwind enables rapid UI iteration.
API ProtocolJSON-RPC 2.0JSON-RPC (rpc-toolkit)tRPC v11JSON-RPC is standard and language-agnostic. tRPC gives end-to-end TypeScript type safety but couples frontend/backend.
State SyncPinia + JSON Patch over WebSocketPatch-DB (CBOR diffs over WebSocket)Zustand + TanStack React QueryPatch-DB is most efficient (binary diffs). Archipelago and StartOS push updates; Umbrel polls via React Query.
Reverse ProxyNginx (external, battle-tested)Built into Rust backend (Axum/Hyper)None (Express on :80 + per-app proxy containers)Nginx: proven, configurable, rate limiting. Built-in: fewer moving parts. Umbrel: no central proxy means no rate limiting or security headers.
Container Isolation
Container RuntimePodman (rootless, OCI)LXC (system containers, AppArmor)Docker 28.5 (rootful)Podman: no daemon, rootless by design. LXC: heavier isolation (full system containers). Docker: rootful daemon is a larger attack surface.
Rootless ContainersYes (all containers as UID 1000)User namespaces (UID 0 → host 100000)No (Docker daemon runs as root)Rootless Podman: container escape = unprivileged user. LXC: namespace mapping mitigates. Docker rootful: escape = root on host.
Capability Dropping--cap-drop=ALL + whitelistAppArmor profiles (generated)Not enforced by defaultArchipelago: explicit least-privilege. StartOS: AppArmor provides MAC. Umbrel: apps define their own security (inconsistent).
Network IsolationPer-tier networks (archy-net + bridge)Per-service veth on lxcbr0Flat bridge (10.21.0.0/16, all apps share)Archipelago/StartOS: apps can't see each other unless connected. Umbrel: any container can reach any other.
Memory LimitsPer-container (128MB–4GB)Configurable via manifestNot enforced by defaultMemory limits prevent a single app from consuming all RAM and crashing the system.
Security
Disk EncryptionMandatory LUKS2 (AES-XTS or ChaCha20-Adiantum)Optional LUKS on LVM/btrfsNonePhysical theft risk: Archipelago data is unreadable. StartOS depends on user choice. Umbrel data is fully exposed.
Security HeadersCSP, HSTS, X-Frame-Options, Permissions-PolicyPartial (built-in proxy)None (no central proxy)Headers prevent XSS, clickjacking, and protocol downgrade attacks. Critical for browser-based management.
Rate LimitingAuth 3/s, RPC 20/s, P2P 10/sRBAC via method metadataNoneRate limiting prevents brute-force password attacks and API abuse.
TLSSelf-signed cert on :443 + HSTSSelf-signed CA + ACME (Let's Encrypt)None (HTTP only on LAN)Without TLS, any device on the LAN can intercept credentials. Tor provides encryption for remote but not LAN access.
Auth ModelRBAC (Admin/Viewer/AppUser) + CSRF + session cookiesPassword + session cookies + key signaturesSingle-user JWT + optional TOTPArchipelago supports multiple roles. Others are single-user only.
App Ecosystem
App FormatContainer images from private registryS9PK v2 (signed merkle archive)docker-compose.yml + umbrel-app.ymlS9PK: most sophisticated (signed, partial downloads, delta updates). Compose: simplest for developers. Registry: fast deployment.
Package SigningRegistry-based trustEd25519 over blake3 merkle rootsDocker image digests onlyStartOS has the strongest supply chain security. Archipelago trusts its private registry. Umbrel relies on Docker content trust.
App StoreBuilt-in marketplace (curated)Registry-based (marketplace)Git repository (pulled every 5 min)Git-based: easy for devs to contribute. Registry: more control. Curated: quality gate but slower additions.
Update MechanismISO reflash / manual upgradeRegistry-based OTARugix A/B partitions (atomic, rollback)Umbrel has the smoothest update path with automatic rollback. Archipelago's ISO approach is most disruptive.
Networking & Privacy
TorSystem daemon + hidden services (always available)Removed in v0.4 (planned re-integration)Optional (containerized)Archipelago: Tor is first-class. StartOS temporarily lost Tor in 0.4 rewrite. Umbrel: toggle on/off.
VPNTailscale (WireGuard mesh)WireGuard (first-class, tunnelbox)None built-inBoth Archipelago and StartOS offer remote access without port forwarding. Umbrel relies on Tor or manual setup.
DNSSystem DNS + container NetAvark DNSBuilt-in (hickory-server)Docker DNS + static IPs in exports.shStartOS has the most integrated DNS. Archipelago uses standard tools. Umbrel hardcodes IPs.
Identity & Web5
DID Supportdid:key + did:dht + W3C DID DocumentsNoneNoneArchipelago is the only node OS with decentralized identity support. Enables credential issuance and cross-node trust.
Verifiable CredentialsW3C VC 2.0 (Ed25519Signature2020)NoneNoneArchipelago can issue and verify digital certificates without any central authority.
DWN (Data Store)Custom implementation + peer sync via TorNoneNonePersonal data store that syncs across nodes. Unique to Archipelago.
Nostr IntegrationNIP-01/04/44, nostr-provider.js in iframesNoneNoneArchipelago injects Nostr identity into every app iframe for seamless decentralized social integration.
Infrastructure
Base OSDebian 12 Bookworm (stable)Debian BookwormDebian Trixie (testing)Stable: proven, security patches. Testing: newer packages but less battle-tested, potential for regressions.
Filesystemext4btrfs (COW snapshots)ext4 (A/B partitions)btrfs snapshots enable instant rollback on failed installs. ext4 is simpler and more mature. A/B adds OS-level rollback.
Kiosk DisplayX11 + Chromium on VT7NoneNonePlug in a monitor and the dashboard appears fullscreen. Unique physical UX for dedicated hardware.
Boot RecoveryCrash recovery + container state snapshotsbtrfs snapshots + preinit/postinit hooksDestroys all containers on every bootArchipelago/StartOS: resume from last known state. Umbrel: clean slate every boot (slower but deterministic).
+
+ +

Summary

+
+
+
Archipelago
+
Strengths: Security (rootless, LUKS, caps, rate limiting, CSP), identity (DIDs, VCs, DWN, Nostr), kiosk display, Tor first-class.
Trade-offs: No OTA updates (ISO reflash), ext4 lacks snapshot rollback, smaller app ecosystem.
+
+
+
StartOS
+
Strengths: Package signing (S9PK), btrfs snapshots, built-in reverse proxy (fewer moving parts), WireGuard VPN, multi-arch (x86/ARM/RISC-V).
Trade-offs: Tor removed in v0.4, no identity system, Angular is heavier, LXC is less container-ecosystem-compatible.
+
+
+
umbrelOS
+
Strengths: Easiest setup, A/B OTA updates with rollback, largest app ecosystem, Git-based app store (easy contributions), React UI polish.
Trade-offs: No disk encryption, flat network (no isolation), rootful Docker, no TLS, no rate limiting, no security headers, Node.js backend.
+
+
+
diff --git a/image-recipe/configs/archipelago-kiosk-launcher.sh b/image-recipe/configs/archipelago-kiosk-launcher.sh index 5b53ce8e..e860ea01 100644 --- a/image-recipe/configs/archipelago-kiosk-launcher.sh +++ b/image-recipe/configs/archipelago-kiosk-launcher.sh @@ -47,6 +47,11 @@ while true; do --disable-background-networking \ --disable-background-timer-throttling \ --disable-backgrounding-occluded-windows \ + --disable-breakpad \ + --disable-metrics \ + --disable-metrics-reporting \ + --metrics-recording-only \ + --disable-domain-reliability \ --js-flags="--max-old-space-size=128" \ --user-data-dir=/home/archipelago/.config/chromium-kiosk sleep 3 diff --git a/neode-ui/src/composables/useOnboarding.ts b/neode-ui/src/composables/useOnboarding.ts index 2f2c061a..a2648452 100644 --- a/neode-ui/src/composables/useOnboarding.ts +++ b/neode-ui/src/composables/useOnboarding.ts @@ -19,9 +19,11 @@ async function callWithRetry(fn: () => Promise, maxRetries = 3): Promise { + // localStorage is set on completion and survives backend restarts/resets + if (localStorage.getItem('neode_onboarding_complete') === '1') return true const result = await callWithRetry(() => rpcClient.isOnboardingComplete(), 2) if (result !== null) return result - return localStorage.getItem('neode_onboarding_complete') === '1' + return false } export async function completeOnboarding(): Promise { diff --git a/neode-ui/src/views/Discover.vue b/neode-ui/src/views/Discover.vue index e7cb6701..aeef8fbe 100644 --- a/neode-ui/src/views/Discover.vue +++ b/neode-ui/src/views/Discover.vue @@ -144,6 +144,7 @@ import { useServerStore } from '@/stores/server' import { rpcClient } from '@/api/rpc-client' import { useMarketplaceApp } from '@/composables/useMarketplaceApp' import { useAppLauncherStore } from '@/stores/appLauncher' +import { useToast } from '@/composables/useToast' import DiscoverHero from './discover/DiscoverHero.vue' import FeaturedApps from './discover/FeaturedApps.vue' import AppGrid from './discover/AppGrid.vue' @@ -427,9 +428,13 @@ function startInstallPolling(appId: string, statusMessage: string) { }, 1000) } +const toast = useToast() + async function installApp(app: MarketplaceApp) { if (installingApps.has(app.id) || isInstalled(app.id)) return installingApps.set(app.id, { id: app.id, title: app.title ?? app.id, status: 'downloading', progress: 10, message: 'Preparing installation...', attempt: 0 }) + toast.info(`Installing ${app.title ?? app.id} — check My Apps`) + router.push('/dashboard/apps').catch(() => {}) try { const installUrl = app.url || app.manifestUrl || app.s9pkUrl installingApps.set(app.id, { ...installingApps.get(app.id)!, status: 'downloading', progress: 30, message: 'Downloading package...' }) @@ -446,6 +451,8 @@ async function installApp(app: MarketplaceApp) { async function installCommunityApp(app: MarketplaceApp) { if (installingApps.has(app.id) || isInstalled(app.id) || !app.dockerImage) return installingApps.set(app.id, { id: app.id, title: app.title ?? app.id, status: 'downloading', progress: 10, message: 'Pulling Docker image...', attempt: 0 }) + toast.info(`Installing ${app.title ?? app.id} — check My Apps`) + router.push('/dashboard/apps').catch(() => {}) try { installingApps.set(app.id, { ...installingApps.get(app.id)!, status: 'downloading', progress: 20, message: 'Downloading container image...' }) await rpcClient.call({ method: 'package.install', params: { id: app.id, dockerImage: app.dockerImage, version: app.version }, timeout: 180000 }) diff --git a/neode-ui/src/views/Home.vue b/neode-ui/src/views/Home.vue index 0c02b647..87cf23c9 100644 --- a/neode-ui/src/views/Home.vue +++ b/neode-ui/src/views/Home.vue @@ -140,16 +140,16 @@
-
{{ t('home.servicesStatus') }}
- {{ servicesStatusText }} +
Tor
+ {{ torConnected ? 'Connected' : 'Offline' }}
-
{{ t('home.connectivity') }}
- {{ connectivityText }} +
VPN
+ {{ vpnConnected ? 'Connected' : 'Not configured' }}
-
{{ t('home.runningApps') }}
- {{ runningCount }} +
Bitcoin
+ {{ bitcoinSyncDisplay }}
@@ -306,13 +306,20 @@ const quickLaunchApps = [ { id: '484-kitchen', name: '484 Kitchen', icon: '/assets/img/app-icons/484-kitchen.png', bg: '', padded: false }, ] -const servicesAllRunning = computed(() => appCount.value > 0 && runningCount.value === appCount.value) -const servicesStatusText = computed(() => appCount.value === 0 ? t('home.noApps') : servicesAllRunning.value ? t('home.allRunning') : `${runningCount.value}/${appCount.value} ${t('home.runningLabel')}`) -const servicesStatusColor = computed(() => appCount.value === 0 ? 'text-white/60' : servicesAllRunning.value ? 'text-green-400' : 'text-yellow-400') -const servicesDotColor = computed(() => appCount.value === 0 ? 'bg-white/40' : servicesAllRunning.value ? 'bg-green-400' : 'bg-yellow-400') -const connectivityText = computed(() => store.isConnected ? t('common.connected') : t('common.disconnected')) -const connectivityColor = computed(() => store.isConnected ? 'text-green-400' : 'text-red-400') -const connectivityDotColor = computed(() => store.isConnected ? 'bg-green-400' : 'bg-red-400') +// Network card data +const torConnected = computed(() => { + const torAddr = store.data?.['server-info']?.['tor-address'] + return !!torAddr && torAddr.length > 0 +}) +const vpnConnected = computed(() => { + const pkg = packages.value['tailscale'] + return !!pkg && pkg.state === PackageState.Running +}) +const bitcoinSyncDisplay = computed(() => { + if (!systemStats.bitcoinAvailable) return 'Not running' + if (systemStats.bitcoinSyncPercent >= 99.9) return 'Synced' + return `${systemStats.bitcoinSyncPercent.toFixed(1)}%` +}) // Quick Start const quickStartDismissed = ref(false) diff --git a/neode-ui/src/views/Marketplace.vue b/neode-ui/src/views/Marketplace.vue index 0ffa77b6..437750ca 100644 --- a/neode-ui/src/views/Marketplace.vue +++ b/neode-ui/src/views/Marketplace.vue @@ -116,6 +116,7 @@ import { useServerStore } from '@/stores/server' import { rpcClient } from '@/api/rpc-client' import { useMarketplaceApp } from '@/composables/useMarketplaceApp' import { useAppLauncherStore } from '@/stores/appLauncher' +import { useToast } from '@/composables/useToast' import MarketplaceAppCard from './marketplace/MarketplaceAppCard.vue' import MarketplaceFilterModal from './marketplace/MarketplaceFilterModal.vue' import { @@ -135,6 +136,7 @@ const { t } = useI18n() const showStagger = !marketplaceAnimationDone const { setCurrentApp } = useMarketplaceApp() const appLauncher = useAppLauncherStore() +const toast = useToast() // Category state — read initial value from query param (set by Discover page navigation) const selectedCategory = ref((route.query.category as string) || 'all') @@ -300,11 +302,8 @@ const filteredApps = computed(() => { ) } - apps.sort((a, b) => { - const aInstalled = isInstalled(a.id) ? 1 : 0 - const bInstalled = isInstalled(b.id) ? 1 : 0 - return aInstalled - bInstalled - }) + // Hide installed and installing apps from marketplace — they belong in My Apps + apps = apps.filter(app => !isInstalled(app.id) && !installingApps.has(app.id)) return apps }) @@ -433,6 +432,10 @@ async function installApp(app: MarketplaceApp) { id: app.id, title: app.title ?? app.id, status: 'downloading', progress: 10, message: 'Preparing installation...', attempt: 0 }) + // Navigate to My Apps immediately and show toast + toast.info(`Installing ${app.title ?? app.id} — check My Apps`) + router.push('/dashboard/apps').catch(() => {}) + try { const installUrl = app.url || app.manifestUrl || app.s9pkUrl @@ -457,6 +460,10 @@ async function installCommunityApp(app: MarketplaceApp) { id: app.id, title: app.title ?? app.id, status: 'downloading', progress: 10, message: 'Pulling Docker image...', attempt: 0 }) + // Navigate to My Apps immediately and show toast + toast.info(`Installing ${app.title ?? app.id} — check My Apps`) + router.push('/dashboard/apps').catch(() => {}) + try { installingApps.set(app.id, { ...installingApps.get(app.id)!, status: 'downloading', progress: 20, message: 'Downloading container image...' }) diff --git a/neode-ui/src/views/OnboardingSeedGenerate.vue b/neode-ui/src/views/OnboardingSeedGenerate.vue index 28f898a4..0722496a 100644 --- a/neode-ui/src/views/OnboardingSeedGenerate.vue +++ b/neode-ui/src/views/OnboardingSeedGenerate.vue @@ -1,6 +1,6 @@