refactor: update dependencies and remove unused code

- Added new dependencies: `adler2`, `crc32fast`, `flate2`, `miniz_oxide`, and `libredox`.
- Updated existing dependencies: `tokio-rustls` to version 0.26.4 and `filetime` to version 0.2.27.
- Removed the `backup.rs` file as it is no longer needed.
- Introduced tests for configuration and credential management.
- Enhanced the `identity` module to generate W3C compliant DID documents.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-12 00:19:30 +00:00
parent 2a867b32a8
commit 6fee6befed
347 changed files with 18703 additions and 46785 deletions

View File

@@ -1,188 +1,270 @@
# CRITICAL CHANGES FOR BETA ISO BUILD
# Beta Release Checklist (v0.5.0-beta)
## ⚠️ MUST-HAVE CHANGES - Without these, the beta will NOT work!
## Pre-Build Verification
### 1. Backend: Podman Detection Fix
**File:** `core/container/src/podman_client.rs`
### Source Code
```rust
fn podman_async(&self) -> TokioCommand {
// Always use sudo podman to access system-wide containers
let mut cmd = TokioCommand::new("sudo");
cmd.arg("podman");
cmd
}
```
- [ ] All changes committed and pushed to `main`
- [ ] `cargo clippy --all-targets --all-features` passes (zero warnings)
- [ ] `cargo fmt --all` applied
- [ ] `cd neode-ui && npm run type-check` passes (zero errors)
- [ ] `cd neode-ui && npm test` passes (all tests green)
- [ ] `cargo test --all-features` passes on dev server
**System Config:** `/etc/sudoers.d/archipelago-podman`
```
archipelago ALL=(ALL) NOPASSWD: /usr/bin/podman
```
### Critical Files
### 2. Backend: Bitcoin UI Container Mapping
**File:** `core/archipelago/src/container/docker_packages.rs`
Add special case for bitcoin-knots (line ~95):
```rust
} else if app_id == "bitcoin-knots" {
// Check if bitcoin-ui exists (maps to "bitcoin" but serves bitcoin-knots)
if let Some(ui_address) = ui_containers.get("bitcoin") {
debug!("Using bitcoin-ui for bitcoin-knots: {}", ui_address);
Some(ui_address.clone())
} else {
extract_lan_address(&container.ports)
}
```
### 3. Backend: IndeedHub Metadata
**File:** `core/archipelago/src/container/docker_packages.rs`
Add to `get_app_metadata()` function (line ~327):
```rust
"indeedhub" => AppMetadata {
title: "IndeedHub".to_string(),
description: "Decentralized media streaming platform".to_string(),
icon: "/assets/img/app-icons/indeedhub.png".to_string(),
repo: "https://github.com/indeedhub/indeedhub".to_string(),
},
```
### 4. Frontend: Marketplace Bitcoin Knots
**File:** `neode-ui/src/views/Marketplace.vue`
```javascript
{
id: 'bitcoin-knots', // CHANGED from 'bitcoin'
title: 'Bitcoin Knots',
version: '28.1.0', // UPDATED version
dockerImage: 'docker.io/bitcoinknots/bitcoin:latest', // CHANGED image
// ... rest of config
}
```
### 5. Auto-Installer: Profile Script Fix
**File:** `image-recipe/build-auto-installer-iso.sh` (line ~850)
Remove `|| [ ! -t 0 ]` check:
```bash
# CORRECT:
if [ -n "$INSTALLER_STARTED" ]; then
return 0
fi
# WRONG (will break auto-login):
# if [ -n "$INSTALLER_STARTED" ] || [ ! -t 0 ]; then
```
### 6. Nginx Configuration
**File:** Captured from `/etc/nginx/sites-available/default`
MUST include:
- HTTPS on port 443
- HTTP redirect to HTTPS
- Backend proxy: `/rpc/`, `/ws/`, `/health`
- Root: `/opt/archipelago/web-ui`
- SSL certificates in `/etc/nginx/ssl/`
### 7. Bitcoin UI Files
**Files:** `docker/bitcoin-ui/index.html` and `Dockerfile`
MUST be included in ISO or downloadable, so users can deploy the web UI container.
- [ ] `core/container/src/podman_client.rs` — sudo podman
- [ ] `core/archipelago/src/container/docker_packages.rs` — app metadata + UI mapping
- [ ] `core/archipelago/src/api/rpc/package.rs` — app configs, capabilities, dependencies
- [ ] `core/archipelago/src/session.rs` — session security hardening
- [ ] `core/security/src/secrets_manager.rs` — encryption + rotation
- [ ] `neode-ui/src/views/Marketplace.vue` — all app entries with pinned image versions
- [ ] `neode-ui/src/api/websocket.ts` — heartbeat + reconnection
- [ ] `image-recipe/build-auto-installer-iso.sh` — all container images captured
- [ ] `image-recipe/configs/nginx-archipelago.conf` — all app proxies + path traversal blocks
- [ ] All app icons present in `neode-ui/public/assets/img/app-icons/`
---
## Build Verification Before Beta Release
## App Integration Matrix
Run these checks:
Every app must be tested for install, launch, and uninstall on a fresh system.
### Core Bitcoin Stack
| App | Image | Version | Install | Launch | UI Loads | Uninstall |
|-----|-------|---------|---------|--------|----------|-----------|
| Bitcoin Knots | `bitcoinknots/bitcoin` | `v28.1` | [ ] | [ ] | [ ] | [ ] |
| Electrs | `mempool/electrs` | `v0.4.1` | [ ] | [ ] | [ ] | [ ] |
| LND | `lightninglabs/lnd` | `v0.18.4` | [ ] | [ ] | [ ] | [ ] |
| BTCPay Server | `btcpayserver/btcpayserver` | `2.0.6` | [ ] | [ ] | [ ] | [ ] |
| Mempool | `mempool/frontend` | `v3.0.0` | [ ] | [ ] | [ ] | [ ] |
| Fedimint | `fedimintui/fedimint` | `0.5.0` | [ ] | [ ] | [ ] | [ ] |
| Fedimint Gateway | `fedimintui/gateway-ui` | `0.5.0` | [ ] | [ ] | [ ] | [ ] |
### Storage & Media
| App | Image | Version | Install | Launch | UI Loads | Uninstall |
|-----|-------|---------|---------|--------|----------|-----------|
| File Browser | `filebrowser/filebrowser` | `v2` | [ ] | [ ] | [ ] | [ ] |
| Immich | `ghcr.io/immich-app/immich-server` | `v1.121.0` | [ ] | [ ] | [ ] | [ ] |
| PhotoPrism | `photoprism/photoprism` | `240915` | [ ] | [ ] | [ ] | [ ] |
### Productivity & Privacy
| App | Image | Version | Install | Launch | UI Loads | Uninstall |
|-----|-------|---------|---------|--------|----------|-----------|
| Penpot | `penpotapp/frontend` | `2.4` | [ ] | [ ] | [ ] | [ ] |
| SearXNG | `searxng/searxng` | `2024.11.17-e2554de75` | [ ] | [ ] | [ ] | [ ] |
| Ollama | `ollama/ollama` | `0.5.4` | [ ] | [ ] | [ ] | [ ] |
### Network & Infrastructure
| App | Image | Version | Install | Launch | UI Loads | Uninstall |
|-----|-------|---------|---------|--------|----------|-----------|
| Nostr Relay | `scsiblade/nostr-rs-relay` | `0.9.0` | [ ] | [ ] | [ ] | [ ] |
| Nginx Proxy Manager | `jc21/nginx-proxy-manager` | `2.12.1` | [ ] | [ ] | [ ] | [ ] |
| Tailscale | `tailscale/tailscale` | pinned | [ ] | [ ] | [ ] | [ ] |
| Home Assistant | `homeassistant/home-assistant` | pinned | [ ] | [ ] | [ ] | [ ] |
### Virtual Apps (No Container)
| App | Behavior | Works |
|-----|----------|-------|
| IndeedHub | Opens external URL | [ ] |
---
## Dependency Chain Tests
These must be tested in order on a fresh install:
- [ ] Install Bitcoin Knots → starts and begins syncing
- [ ] Install Electrs while Bitcoin running → connects to Bitcoin automatically
- [ ] Install LND while Bitcoin running → connects to Bitcoin automatically
- [ ] Install BTCPay while Bitcoin running → connects; Lightning available if LND present
- [ ] Install Mempool while Bitcoin + Electrs running → shows blockchain data
- [ ] Try installing Electrs without Bitcoin → shows clear error message
- [ ] Try installing LND without Bitcoin → shows clear error message
- [ ] Try installing Mempool without Bitcoin + Electrs → shows missing deps error
- [ ] Fedimint Gateway auto-detects LND credentials when available
---
## Security Hardening Verification
### Session Security
- [ ] Sessions expire after 24 hours of inactivity
- [ ] Password change invalidates all other sessions
- [ ] Maximum 5 concurrent sessions (oldest evicted when exceeded)
- [ ] Session tokens are SHA-256 hashed in memory (never stored as plaintext)
- [ ] Login rate limiting: 5 failures per 60 seconds per IP
### Container Security
- [ ] All container images use pinned versions (no `:latest`)
- [ ] Read-only root filesystem enabled for compatible apps
- [ ] `--cap-drop=ALL` applied to all containers
- [ ] `--security-opt=no-new-privileges:true` applied to all containers
- [ ] Required capabilities added explicitly per app (e.g., CHOWN for File Browser)
### Secrets Management
- [ ] Secrets encrypted with AES-256-GCM on disk
- [ ] Secret metadata tracked (creation date, rotation count)
- [ ] Secret rotation generates new random values and re-encrypts
- [ ] `security.list-expiring` RPC returns secrets older than threshold
### Path Traversal Prevention
- [ ] Nginx blocks `..` in filebrowser API paths (403 response)
- [ ] Frontend `sanitizePath()` strips `..` and resolves paths
- [ ] File Browser token not exposed in URLs
### Authentication
- [ ] TOTP 2FA enrollment and verification works
- [ ] TOTP backup codes work for recovery
- [ ] Maximum 5 TOTP attempts before session invalidation
- [ ] Pending TOTP sessions expire after 5 minutes
- [ ] Cookie-based auth (no tokens in query strings)
---
## WebSocket & Connectivity
- [ ] WebSocket connects on login and receives initial data dump
- [ ] WebSocket reconnects after network interruption (exponential backoff, max 30s)
- [ ] Server sends ping every 30s; client responds with pong
- [ ] Client sends JSON ping every 30s; server responds with JSON pong
- [ ] Server closes inactive connections after 5 minutes
- [ ] Connection state shown in UI (connected/reconnecting/disconnected)
- [ ] Install progress updates delivered in real-time via WebSocket
---
## Fresh Install Testing Matrix
### ISO Build
- [ ] ISO builds successfully on dev server
- [ ] ISO size is reasonable (< 10 GB)
- [ ] All container images captured in ISO
### Installation
- [ ] Boot from USB on x86_64 hardware
- [ ] Auto-installer partitions disk correctly
- [ ] Debian 12 installs without errors
- [ ] Archipelago services start on first boot
- [ ] Web UI accessible at server IP within 3 minutes of first boot
### Onboarding Flow
- [ ] Welcome screen displays with intro video
- [ ] Password creation enforces minimum requirements
- [ ] Path selection shows all 6 options
- [ ] DID generation completes within 60 seconds
- [ ] Identity naming is optional and skippable
- [ ] Backup download produces valid JSON file
- [ ] Onboarding completes and reaches Dashboard
### Post-Onboarding
- [ ] Dashboard shows all overview cards
- [ ] App Store loads with all curated apps
- [ ] Settings shows server name, version, DID, Tor address
- [ ] Logout and re-login works
- [ ] Password change works and invalidates other sessions
---
## Performance Targets
- [ ] Backend startup: < 3 seconds
- [ ] Frontend initial load: < 500 KB gzipped
- [ ] WebSocket initial data: < 1 second after connection
- [ ] App install progress visible in UI within 5 seconds of starting
---
## Nginx Proxy Verification
All app proxies must work in both HTTP and HTTPS blocks:
- [ ] `/rpc/` → backend:5678
- [ ] `/ws/` → backend:5678 (WebSocket upgrade)
- [ ] `/health` → backend:5678
- [ ] `/app/filebrowser/` → filebrowser:80
- [ ] `/app/searxng/` → searxng:8080
- [ ] `/app/immich/` → immich:2283
- [ ] `/app/penpot/` → penpot-frontend:80
- [ ] `/app/ollama/` → ollama:11434
- [ ] `/app/photoprism/` → photoprism:2342
- [ ] `/app/nginx-proxy-manager/` → npm:81
- [ ] `/app/tailscale/` → tailscale:8240
- [ ] BTCPay (port 23000) opens in new tab
- [ ] Home Assistant (port 8123) opens in new tab
- [ ] Tor hidden services resolve for all configured apps
---
## Rollback Procedures
### If Backend Fails to Start
```bash
# 1. Verify all source changes are committed
cd /Users/dorian/Projects/archy
git status # Should show all critical files committed
# Check logs
sudo journalctl -u archipelago -n 50 --no-pager
# 2. Build ISO from live server
cd image-recipe
DEV_SERVER=archipelago@192.168.1.228 ./build-auto-installer-iso.sh
# 3. Test ISO on clean VM
# - Boot ISO
# - Verify auto-installer runs
# - System should boot to login
# - Access http://SERVER-IP
# - Complete onboarding
# - Install Bitcoin Knots from App Store
# - Verify "Already Installed" shows after install
# - Verify "Launch" button works
# - Verify web UI loads on port 8334
# 4. Test all critical features
# - Bitcoin node syncing
# - RPC accessible
# - Web UI functional
# - Backend detects container
# - App Store shows proper status
# Restore previous binary
sudo cp /usr/local/bin/archipelago.bak /usr/local/bin/archipelago
sudo systemctl restart archipelago
```
---
## Critical Files Checklist
Before building beta ISO, ensure these files have the latest changes:
- [ ] `core/container/src/podman_client.rs` - sudo podman
- [ ] `core/archipelago/src/container/docker_packages.rs` - app metadata + UI mapping
- [ ] `neode-ui/src/views/Marketplace.vue` - bitcoin-knots ID
- [ ] `neode-ui/src/utils/dummyApps.ts` - IndeedHub data
- [ ] `image-recipe/build-auto-installer-iso.sh` - auto-start fix
- [ ] `docker/bitcoin-ui/` - UI files present
- [ ] `scripts/deploy-bitcoin-knots.sh` - deployment script
- [ ] All assets: `neode-ui/public/assets/img/app-icons/*.png`
---
## Testing Matrix
| Feature | Expected | Status |
|---------|----------|--------|
| Bitcoin Knots container runs | Running | ✅ |
| Bitcoin UI container runs | Running | ✅ |
| Backend detects bitcoin-knots | Detected | ✅ |
| Backend maps bitcoin-ui → bitcoin-knots | Port 8334 | ✅ |
| App shows in My Apps | Listed | ✅ |
| App Store shows "Already Installed" | Badge shown | ✅ (after ID fix) |
| Launch button visible | Clickable | ✅ |
| Launch opens web UI | Port 8334 | ✅ |
| RPC accessible | Port 8332 | ✅ |
| Blockchain syncing | Active | ✅ |
---
## Roll-Back Plan
If beta ISO fails:
1. Check `/var/log/archipelago.log` on installed system
2. Verify containers with `sudo podman ps -a`
3. Check nginx status: `sudo systemctl status nginx`
4. Test backend: `curl http://localhost:5678/health`
5. Rebuild ISO with `BUILD_FROM_SOURCE=1` if server state is corrupt
---
## Support Commands for Users
### If Frontend is Broken
```bash
# Check Bitcoin status
sudo podman logs -f bitcoin-knots
# Check blockchain sync progress
curl --user archipelago:archipelago123 \
--data-binary '{"jsonrpc":"1.0","id":"test","method":"getblockchaininfo","params":[]}' \
-H 'content-type: text/plain;' http://localhost:8332/ | grep blocks
# Restart if needed
sudo podman restart bitcoin-knots bitcoin-ui
# View Archipelago backend logs
sudo journalctl -u archipelago -f
# Restore previous frontend build
sudo cp -r /opt/archipelago/web-ui.bak/* /opt/archipelago/web-ui/
sudo systemctl reload nginx
```
### If Container Won't Start
```bash
# Check container logs
sudo podman logs <container-name>
# Remove and recreate
sudo podman rm -f <container-name>
# Reinstall from App Store
```
### If ISO Install Fails
1. Boot into rescue mode from USB
2. Check `/var/log/installer.log` on target disk
3. Verify disk partitioning with `lsblk`
4. Re-run installer with `INSTALLER_STARTED= /opt/installer.sh`
### Full System Rollback
If the beta is unusable:
1. Re-flash the ISO from the last known good build
2. Restore user data from `/var/lib/archipelago/` backup
3. Re-import DID from backup JSON file
---
## Sign-Off
| Reviewer | Area | Date | Pass/Fail |
|----------|------|------|-----------|
| | Backend | | |
| | Frontend | | |
| | Security | | |
| | ISO Build | | |
| | Fresh Install | | |
| | App Integrations | | |

View File

@@ -0,0 +1,32 @@
# ADR-001: Podman Over Docker
**Status**: Accepted
**Date**: 2026-03
## Context
Archipelago needs a container runtime for running applications. Docker and Podman are the two main options.
## Decision
Use Podman as the container runtime instead of Docker.
## Consequences
### Positive
- **Rootless by default**: Containers run without root privileges, reducing attack surface
- **Daemonless**: No persistent daemon process; containers are managed as individual processes under systemd
- **Docker-compatible**: Supports Docker images and most Docker CLI commands
- **Systemd integration**: Podman containers can be managed as systemd services natively
- **No vendor lock-in**: OCI-compliant, works with any container registry
### Negative
- **Smaller ecosystem**: Some Docker-specific tools and compose features require adaptation
- **Docker Compose differences**: Podman Compose exists but has occasional compatibility gaps
- **Documentation**: Most container documentation assumes Docker; developers need to translate
- **Networking**: Podman networking (CNI/netavark) differs from Docker's bridge networking
### Mitigation
- Use `podman` CLI wrapper that provides Docker-compatible interface
- Document Podman-specific commands in developer guide
- Use `archy-net` custom network for inter-container DNS

View File

@@ -0,0 +1,31 @@
# ADR-002: DID Key Method for Node Identity
**Status**: Accepted
**Date**: 2026-03
## Context
Each Archipelago node needs a cryptographic identity for peer authentication, federation, and verifiable credentials. Multiple DID methods exist (did:web, did:ion, did:key, did:peer).
## Decision
Use `did:key` as the primary DID method.
## Consequences
### Positive
- **Self-contained**: The DID document is derived entirely from the public key — no external resolution needed
- **Offline-capable**: Works without internet, aligning with sovereignty principles
- **Simple**: No registration, no blockchain, no web server required
- **Fast**: DID resolution is a local computation, not a network request
- **Ed25519**: Uses Ed25519 keys which are fast, compact, and well-supported
### Negative
- **No key rotation**: The DID is bound to a single key; rotating requires a new DID
- **No service endpoints in DID**: Cannot embed service URLs in the DID document itself
- **No revocation**: Cannot revoke a did:key without out-of-band mechanisms
### Mitigation
- Use federation trust lists for key management and revocation
- Store service endpoints (onion address, pubkey) separately in federation state
- Support migration to did:peer or did:web in future versions if key rotation is needed

View File

@@ -0,0 +1,35 @@
# ADR-003: Nostr Relays for Node and App Discovery
**Status**: Accepted
**Date**: 2026-03
## Context
Archipelago nodes need to discover peers and community apps without a central registry. Options: custom P2P protocol, DHT, BitTorrent tracker, Nostr relays, IPFS.
## Decision
Use Nostr relays (NIP-78, kind 30078) for both node discovery and marketplace app manifests.
## Consequences
### Positive
- **Decentralized**: Multiple independent relays; no single point of failure
- **Existing infrastructure**: Thousands of Nostr relays already running globally
- **Censorship-resistant**: If one relay censors, others still serve events
- **Simple protocol**: WebSocket + JSON — easy to implement without heavy dependencies
- **Key management**: Nostr uses secp256k1, same curve as Bitcoin — natural fit
- **NIP-33 replaceable events**: Latest event replaces previous — clean update model
- **Tor-compatible**: WebSocket over Tor SOCKS proxy works natively
### Negative
- **Relay availability varies**: Some relays may be down or rate-limited
- **No guaranteed persistence**: Relays may prune old events
- **Spam potential**: Open publishing means anyone can publish junk manifests
- **Latency**: Querying multiple relays adds latency to discovery
### Mitigation
- Query multiple relays in parallel; deduplicate results
- Cache results locally with 15-minute TTL
- Use trust scoring to rank manifests (DID verification, relay consensus, federation trust)
- Use hashtag filtering (`archipelago-marketplace`) to narrow queries

View File

@@ -0,0 +1,35 @@
# ADR-004: Tor Hidden Services for Peer Communication
**Status**: Accepted
**Date**: 2026-03
## Context
Federated nodes need to communicate directly for state sync, app deployment, and peer verification. Options: direct IP, VPN tunnel, Tor hidden services, I2P.
## Decision
Use Tor hidden services (.onion addresses) for all inter-node communication.
## Consequences
### Positive
- **NAT traversal**: Works behind any firewall or NAT without port forwarding
- **IP privacy**: Nodes never expose their real IP addresses to each other
- **End-to-end encryption**: Tor provides encryption without additional TLS setup
- **Censorship resistance**: Onion routing makes traffic analysis difficult
- **Stable addressing**: .onion addresses persist across IP changes and network migrations
- **No central infrastructure**: No VPN server, STUN/TURN server, or relay needed
### Negative
- **Latency**: Tor adds 200-500ms per hop; 3 hops per direction = noticeable delay
- **Bandwidth**: Tor network has limited bandwidth; not suitable for bulk data transfer
- **Reliability**: Tor circuits can break; connections may need retry logic
- **Setup complexity**: Requires running a Tor daemon (`archy-tor` container)
- **Blocked networks**: Some networks block Tor; bridges can help but add complexity
### Mitigation
- Use Tor only for RPC/control plane; bulk data (container images) pulled from registries
- Implement retry with backoff for Tor connections
- Container `archy-tor` runs automatically with host networking for hidden service access
- Federation sync interval (5 min) tolerates occasional connection failures

View File

@@ -0,0 +1,32 @@
# ADR-005: ChaCha20-Poly1305 for Backup Encryption
**Status**: Accepted
**Date**: 2026-03
## Context
Backups contain sensitive data (keys, credentials, app state) and must be encrypted at rest. Options: AES-256-GCM, ChaCha20-Poly1305, XChaCha20-Poly1305.
## Decision
Use ChaCha20-Poly1305 (AEAD) with Argon2id key derivation for backup encryption.
## Consequences
### Positive
- **Software performance**: ChaCha20 is faster than AES on hardware without AES-NI (common on ARM/SBCs)
- **Constant-time**: No timing side channels, unlike some AES implementations
- **AEAD**: Authenticated encryption ensures both confidentiality and integrity
- **Widely audited**: Used in TLS 1.3, WireGuard, and Signal Protocol
- **Simple implementation**: No padding, no CBC/CTR mode complexity
- **Argon2id KDF**: Memory-hard key derivation resists GPU/ASIC brute force attacks
### Negative
- **96-bit nonce**: Must ensure nonce uniqueness per encryption (random generation with collision check)
- **Not FIPS-certified**: Some enterprise environments require AES (not relevant for personal nodes)
- **Less hardware acceleration**: AES-NI on x86 can make AES faster on desktop CPUs
### Mitigation
- Generate random nonce per backup; store nonce alongside ciphertext
- Argon2id with high memory cost (64MB) and iterations (3) for password-to-key derivation
- Target hardware is mixed x86/ARM; ChaCha20's consistent performance is an advantage

397
docs/api-reference.md Normal file
View File

@@ -0,0 +1,397 @@
# Archipelago API Reference
All endpoints use JSON-RPC over HTTP POST to `/rpc/v1`.
**Request format:**
```json
{
"method": "namespace.action",
"params": { ... }
}
```
**Response format:**
```json
{
"result": { ... }
}
```
**Error format:**
```json
{
"error": { "message": "Error description" }
}
```
**Authentication:** All endpoints require a valid session cookie (`archipelago_session`) except those marked "No Auth".
---
## Authentication
| Method | Params | Returns | Auth |
|--------|--------|---------|------|
| `auth.login` | `{ password: string }` | `{ ok: bool, totp_required?: bool }` | No Auth |
| `auth.logout` | — | `{ ok: bool }` | Yes |
| `auth.changePassword` | `{ current: string, new: string }` | `{ ok: bool }` | Yes |
| `auth.isOnboardingComplete` | — | `{ complete: bool }` | No Auth |
| `auth.onboardingComplete` | — | `{ ok: bool }` | Yes |
| `auth.resetOnboarding` | — | `{ ok: bool }` | Yes |
### TOTP 2FA
| Method | Params | Returns | Auth |
|--------|--------|---------|------|
| `auth.totp.setup.begin` | `{ password: string }` | `{ secret: string, qr_uri: string, backup_codes: string[] }` | Yes |
| `auth.totp.setup.confirm` | `{ code: string }` | `{ ok: bool }` | Yes |
| `auth.totp.disable` | `{ password: string }` | `{ ok: bool }` | Yes |
| `auth.totp.status` | — | `{ enabled: bool }` | Yes |
| `auth.login.totp` | `{ code: string }` | `{ ok: bool }` | No Auth |
| `auth.login.backup` | `{ code: string }` | `{ ok: bool }` | No Auth |
---
## Container Orchestration
| Method | Params | Returns | Auth |
|--------|--------|---------|------|
| `container-install` | `{ image: string, name?: string }` | `{ ok: bool, container_id: string }` | Yes |
| `container-start` | `{ id: string }` | `{ ok: bool }` | Yes |
| `container-stop` | `{ id: string }` | `{ ok: bool }` | Yes |
| `container-remove` | `{ id: string }` | `{ ok: bool }` | Yes |
| `container-list` | — | `{ containers: Container[] }` | Yes |
| `container-status` | `{ id: string }` | `{ status: string, ... }` | Yes |
| `container-logs` | `{ id: string, lines?: number }` | `{ logs: string }` | Yes |
| `container-health` | `{ id: string }` | `{ healthy: bool }` | Yes |
## Package Management
| Method | Params | Returns | Auth |
|--------|--------|---------|------|
| `package.install` | `{ id: string, dockerImage?: string, url?: string, version?: string }` | `{ ok: bool }` | Yes |
| `package.start` | `{ id: string }` | `{ ok: bool }` | Yes |
| `package.stop` | `{ id: string }` | `{ ok: bool }` | Yes |
| `package.restart` | `{ id: string }` | `{ ok: bool }` | Yes |
| `package.uninstall` | `{ id: string }` | `{ ok: bool }` | Yes |
## Bundled Apps
| Method | Params | Returns | Auth |
|--------|--------|---------|------|
| `bundled-app-start` | `{ id: string }` | `{ ok: bool }` | Yes |
| `bundled-app-stop` | `{ id: string }` | `{ ok: bool }` | Yes |
---
## Node Identity & P2P
| Method | Params | Returns | Auth |
|--------|--------|---------|------|
| `node.did` | — | `{ did: string }` | Yes |
| `node.signChallenge` | `{ challenge: string }` | `{ signature: string }` | Yes |
| `node.tor-address` | — | `{ address: string }` | Yes |
| `node.nostr-publish` | — | `{ ok: bool, event_id: string }` | Yes |
| `node.nostr-pubkey` | — | `{ pubkey: string }` | Yes |
| `node-nostr-verify-revoked` | — | `{ revoked: bool, nostr_pubkey: string }` | Yes |
| `node-nostr-discover` | — | `{ nodes: DiscoveredNode[] }` | Yes |
| `node-add-peer` | `{ did: string, address: string }` | `{ ok: bool }` | Yes |
| `node-list-peers` | — | `{ peers: Peer[] }` | Yes |
| `node-remove-peer` | `{ did: string }` | `{ ok: bool }` | Yes |
| `node-send-message` | `{ to: string, message: string }` | `{ ok: bool }` | Yes |
| `node-check-peer` | `{ did: string }` | `{ online: bool }` | Yes |
| `node-messages-received` | — | `{ messages: Message[] }` | Yes |
| `node.createBackup` | `{ password: string }` | `{ path: string }` | Yes |
---
## Identity Management
### Multi-Identity
| Method | Params | Returns | Auth |
|--------|--------|---------|------|
| `identity.list` | `{}` | `{ identities: Identity[] }` | Yes |
| `identity.create` | `{ label: string }` | `{ identity: Identity }` | Yes |
| `identity.get` | `{ id: string }` | `{ identity: Identity }` | Yes |
| `identity.delete` | `{ id: string }` | `{ ok: bool }` | Yes |
| `identity.set-default` | `{ id: string }` | `{ ok: bool }` | Yes |
| `identity.sign` | `{ id: string, data: string }` | `{ signature: string }` | Yes |
| `identity.verify` | `{ id: string, data: string, signature: string }` | `{ valid: bool }` | Yes |
| `identity.resolve-did` | `{ did: string }` | `{ document: DIDDocument }` | Yes |
| `identity.resolve-remote-did` | `{ did: string }` | `{ document: DIDDocument }` | Yes |
| `identity.verify-did-document` | `{ document: object }` | `{ valid: bool }` | Yes |
### Nostr Keys
| Method | Params | Returns | Auth |
|--------|--------|---------|------|
| `identity.create-nostr-key` | `{ id: string }` | `{ pubkey: string, npub: string }` | Yes |
| `identity.nostr-sign` | `{ id: string, event: object }` | `{ signed_event: object }` | Yes |
### Bitcoin Names (NIP-05)
| Method | Params | Returns | Auth |
|--------|--------|---------|------|
| `identity.register-name` | `{ name: string, pubkey: string }` | `{ ok: bool }` | Yes |
| `identity.remove-name` | `{ name: string }` | `{ ok: bool }` | Yes |
| `identity.resolve-name` | `{ name: string }` | `{ pubkey: string }` | Yes |
| `identity.list-names` | `{}` | `{ names: NameEntry[] }` | Yes |
| `identity.link-name` | `{ name: string, identity_id: string }` | `{ ok: bool }` | Yes |
### Verifiable Credentials
| Method | Params | Returns | Auth |
|--------|--------|---------|------|
| `identity.issue-credential` | `{ subject: string, claims: object }` | `{ credential: VC }` | Yes |
| `identity.verify-credential` | `{ credential: object }` | `{ valid: bool }` | Yes |
| `identity.list-credentials` | `{ id?: string }` | `{ credentials: VC[] }` | Yes |
| `identity.revoke-credential` | `{ credential_id: string }` | `{ ok: bool }` | Yes |
| `identity.create-presentation` | `{ credentials: string[] }` | `{ presentation: VP }` | Yes |
| `identity.verify-presentation` | `{ presentation: object }` | `{ valid: bool }` | Yes |
---
## Bitcoin & Lightning
| Method | Params | Returns | Auth |
|--------|--------|---------|------|
| `bitcoin.getinfo` | — | `{ blocks: number, connections: number, ... }` | Yes |
| `lnd.getinfo` | — | `{ identity_pubkey: string, num_active_channels: number, ... }` | Yes |
| `lnd.listchannels` | — | `{ channels: Channel[] }` | Yes |
| `lnd.openchannel` | `{ pubkey: string, amount: number }` | `{ funding_txid: string }` | Yes |
| `lnd.closechannel` | `{ channel_point: string }` | `{ closing_txid: string }` | Yes |
| `lnd.newaddress` | — | `{ address: string }` | Yes |
| `lnd.sendcoins` | `{ addr: string, amount: number }` | `{ txid: string }` | Yes |
| `lnd.createinvoice` | `{ amount: number, memo?: string }` | `{ payment_request: string }` | Yes |
| `lnd.payinvoice` | `{ payment_request: string }` | `{ preimage: string }` | Yes |
| `lnd.create-psbt` | `{ outputs: object, ... }` | `{ psbt: string }` | Yes |
| `lnd.finalize-psbt` | `{ psbt: string }` | `{ signed_psbt: string }` | Yes |
---
## Ecash Wallet
| Method | Params | Returns | Auth |
|--------|--------|---------|------|
| `wallet.ecash-balance` | — | `{ balance: number, mint_url: string }` | Yes |
| `wallet.ecash-mint` | `{ amount: number }` | `{ ok: bool }` | Yes |
| `wallet.ecash-melt` | `{ amount: number, invoice: string }` | `{ ok: bool }` | Yes |
| `wallet.ecash-send` | `{ amount: number }` | `{ token: string }` | Yes |
| `wallet.ecash-receive` | `{ token: string }` | `{ amount: number }` | Yes |
| `wallet.ecash-history` | — | `{ transactions: EcashTx[] }` | Yes |
| `wallet.networking-profits` | — | `{ total_sats: number, ... }` | Yes |
---
## Network
### Interfaces & WiFi
| Method | Params | Returns | Auth |
|--------|--------|---------|------|
| `network.list-interfaces` | — | `{ interfaces: Interface[] }` | Yes |
| `network.scan-wifi` | — | `{ networks: WifiNetwork[] }` | Yes |
| `network.configure-wifi` | `{ ssid: string, password: string }` | `{ ok: bool }` | Yes |
| `network.configure-ethernet` | `{ interface: string, mode: "dhcp"\|"static", ip?: string, gateway?: string, dns?: string }` | `{ ok: bool }` | Yes |
| `network.diagnostics` | — | `{ wan_ip: string, nat_type: string, upnp_available: bool, tor_connected: bool, wifi_count: number }` | Yes |
### DNS
| Method | Params | Returns | Auth |
|--------|--------|---------|------|
| `network.dns-status` | — | `{ provider: string, servers: string[], doh_enabled: bool, doh_url: string?, resolv_conf_servers: string[] }` | Yes |
| `network.configure-dns` | `{ provider: "system"\|"cloudflare"\|"google"\|"quad9"\|"mullvad"\|"custom", servers?: string[] }` | `{ ok: bool, provider: string, servers: string[], doh_enabled: bool }` | Yes |
### Network Overlay
| Method | Params | Returns | Auth |
|--------|--------|---------|------|
| `network.get-visibility` | — | `{ visibility: string }` | Yes |
| `network.set-visibility` | `{ visibility: string }` | `{ ok: bool }` | Yes |
| `network.request-connection` | `{ target_did: string }` | `{ request_id: string }` | Yes |
| `network.list-requests` | — | `{ requests: ConnectionRequest[] }` | Yes |
| `network.accept-request` | `{ request_id: string }` | `{ ok: bool }` | Yes |
| `network.reject-request` | `{ request_id: string }` | `{ ok: bool }` | Yes |
### Router / UPnP
| Method | Params | Returns | Auth |
|--------|--------|---------|------|
| `router.discover` | — | `{ router: RouterInfo }` | Yes |
| `router.list-forwards` | — | `{ forwards: PortForward[] }` | Yes |
| `router.add-forward` | `{ port: number, protocol: string, description: string }` | `{ ok: bool }` | Yes |
| `router.remove-forward` | `{ port: number, protocol: string }` | `{ ok: bool }` | Yes |
| `router.detect` | `{ ... }` | `{ detected: bool, ... }` | Yes |
| `router.info` | — | `{ ... }` | Yes |
| `router.configure` | `{ ... }` | `{ ok: bool }` | Yes |
---
## Tor
| Method | Params | Returns | Auth |
|--------|--------|---------|------|
| `tor.list-services` | — | `{ services: TorService[] }` | Yes |
| `tor.create-service` | `{ name: string, port: number }` | `{ onion_address: string }` | Yes |
| `tor.delete-service` | `{ name: string }` | `{ ok: bool }` | Yes |
| `tor.get-onion-address` | `{ name: string }` | `{ address: string }` | Yes |
## Nostr Relays
| Method | Params | Returns | Auth |
|--------|--------|---------|------|
| `nostr.list-relays` | — | `{ relays: RelayConfig[] }` | Yes |
| `nostr.add-relay` | `{ url: string }` | `{ ok: bool }` | Yes |
| `nostr.remove-relay` | `{ url: string }` | `{ ok: bool }` | Yes |
| `nostr.toggle-relay` | `{ url: string }` | `{ ok: bool }` | Yes |
| `nostr.get-stats` | — | `{ total_relays: number, connected: number, enabled: number }` | Yes |
---
## VPN
| Method | Params | Returns | Auth |
|--------|--------|---------|------|
| `vpn.status` | — | `{ connected: bool, provider?: string, ip_address?: string, hostname?: string, peers_connected: number }` | Yes |
| `vpn.configure` | `{ provider: "tailscale"\|"wireguard", auth_key?: string, address?: string, dns?: string, peer?: object }` | `{ ok: bool }` | Yes |
| `vpn.disconnect` | — | `{ disconnected: bool }` | Yes |
## Mesh Networking
| Method | Params | Returns | Auth |
|--------|--------|---------|------|
| `mesh.status` | — | `{ enabled: bool, device: string?, nodes: MeshNode[] }` | Yes |
| `mesh.discover` | `{ timeout_secs?: number }` | `{ nodes: MeshNode[] }` | Yes |
| `mesh.broadcast` | — | `{ ok: bool }` | Yes |
| `mesh.configure` | `{ enabled: bool, device?: string }` | `{ ok: bool }` | Yes |
---
## Federation
| Method | Params | Returns | Auth |
|--------|--------|---------|------|
| `federation.invite` | — | `{ code: string }` | Yes |
| `federation.join` | `{ code: string }` | `{ ok: bool, node: FederatedNode }` | Yes |
| `federation.list-nodes` | — | `{ nodes: FederatedNode[] }` | Yes |
| `federation.remove-node` | `{ did: string }` | `{ ok: bool }` | Yes |
| `federation.set-trust` | `{ did: string, trust: "trusted"\|"observer"\|"untrusted" }` | `{ ok: bool }` | Yes |
| `federation.sync-state` | — | `{ results: SyncResult[] }` | Yes |
| `federation.get-state` | — | `{ state: NodeStateSnapshot }` | Federation peer |
| `federation.peer-joined` | `{ did: string, onion: string, pubkey: string }` | `{ accepted: bool }` | Federation peer |
| `federation.deploy-app` | `{ target_did: string, app_id: string, version?: string }` | `{ ok: bool }` | Yes |
---
## Marketplace
| Method | Params | Returns | Auth |
|--------|--------|---------|------|
| `marketplace.discover` | — | `{ apps: DiscoveredApp[], relay_count: number }` | Yes |
| `marketplace.publish` | `{ app_id, name, version, description, author, container, category, ... }` | `{ ok: bool, event_id: string }` | Yes |
| `marketplace.get-manifest` | `{ app_id: string }` | `DiscoveredApp \| { error: string }` | Yes |
| `marketplace.list-published` | — | `{ manifests: AppManifest[] }` | Yes |
| `marketplace.verify` | `{ ... manifest fields ... }` | `{ valid: bool, issues: string[], trust_score: number }` | Yes |
---
## DWN (Decentralized Web Node)
| Method | Params | Returns | Auth |
|--------|--------|---------|------|
| `dwn.status` | — | `{ running: bool, message_count: number, protocol_count: number }` | Yes |
| `dwn.sync` | — | `{ synced: number }` | Yes |
| `dwn.register-protocol` | `{ uri: string, definition: object }` | `{ ok: bool }` | Yes |
| `dwn.list-protocols` | — | `{ protocols: Protocol[] }` | Yes |
| `dwn.remove-protocol` | `{ uri: string }` | `{ ok: bool }` | Yes |
| `dwn.query-messages` | `{ protocol?: string, limit?: number }` | `{ messages: DwnMessage[] }` | Yes |
| `dwn.write-message` | `{ protocol: string, data: object }` | `{ ok: bool, message_id: string }` | Yes |
---
## Content Catalog
| Method | Params | Returns | Auth |
|--------|--------|---------|------|
| `content.list-mine` | — | `{ items: ContentItem[] }` | Yes |
| `content.add` | `{ title: string, type: string, data: object }` | `{ ok: bool, id: string }` | Yes |
| `content.remove` | `{ id: string }` | `{ ok: bool }` | Yes |
| `content.set-pricing` | `{ id: string, price_sats: number }` | `{ ok: bool }` | Yes |
| `content.set-availability` | `{ id: string, available: bool }` | `{ ok: bool }` | Yes |
| `content.browse-peer` | `{ peer_did: string }` | `{ items: ContentItem[] }` | Yes |
---
## System
### Monitoring
| Method | Params | Returns | Auth |
|--------|--------|---------|------|
| `system.stats` | — | `{ cpu_percent: number, ram_used: number, ram_total: number, disk_used: number, disk_total: number, uptime_secs: number, load_avg: number[] }` | Yes |
| `system.processes` | — | `{ processes: Process[] }` | Yes |
| `system.temperature` | — | `{ celsius: number? }` | Yes |
| `system.detect-usb-devices` | — | `{ devices: UsbDevice[] }` | Yes |
### Updates
| Method | Params | Returns | Auth |
|--------|--------|---------|------|
| `update.check` | — | `{ available: bool, version?: string, changelog?: string }` | Yes |
| `update.status` | — | `{ state: string, progress?: number }` | Yes |
| `update.dismiss` | — | `{ ok: bool }` | Yes |
| `update.download` | — | `{ ok: bool }` | Yes |
| `update.apply` | — | `{ ok: bool }` | Yes |
| `update.rollback` | — | `{ ok: bool }` | Yes |
| `update.get-schedule` | — | `{ auto_check: bool, auto_install: bool, schedule: string }` | Yes |
| `update.set-schedule` | `{ auto_check?: bool, auto_install?: bool, schedule?: string }` | `{ ok: bool }` | Yes |
### Backup & Restore
| Method | Params | Returns | Auth |
|--------|--------|---------|------|
| `backup.create` | `{ password: string, include?: string[] }` | `{ path: string, size: number }` | Yes |
| `backup.list` | — | `{ backups: BackupEntry[] }` | Yes |
| `backup.verify` | `{ path: string, password: string }` | `{ valid: bool }` | Yes |
| `backup.restore` | `{ path: string, password: string }` | `{ ok: bool }` | Yes |
| `backup.delete` | `{ path: string }` | `{ ok: bool }` | Yes |
| `backup.list-drives` | — | `{ drives: UsbDrive[] }` | Yes |
| `backup.to-usb` | `{ drive: string, password: string }` | `{ ok: bool }` | Yes |
### Security
| Method | Params | Returns | Auth |
|--------|--------|---------|------|
| `security.rotate-secrets` | `{ app_id?: string }` | `{ rotated: string[] }` | Yes |
| `security.list-expiring` | `{ days?: number }` | `{ secrets: ExpiringSecret[] }` | Yes |
---
## Utility
| Method | Params | Returns | Auth |
|--------|--------|---------|------|
| `echo` | `{ message: string }` | `{ message: string }` | No Auth |
| `server.echo` | `{ message: string }` | `{ message: string }` | No Auth |
---
## Example: cURL
```bash
# Login
curl -c cookies.txt -X POST http://192.168.1.228/rpc/v1 \
-H "Content-Type: application/json" \
-d '{"method":"auth.login","params":{"password":"password123"}}'
# Get system stats (authenticated)
curl -b cookies.txt -X POST http://192.168.1.228/rpc/v1 \
-H "Content-Type: application/json" \
-d '{"method":"system.stats"}'
# Get DID
curl -b cookies.txt -X POST http://192.168.1.228/rpc/v1 \
-H "Content-Type: application/json" \
-d '{"method":"node.did"}'
```

277
docs/app-developer-guide.md Normal file
View File

@@ -0,0 +1,277 @@
# Archipelago App Developer Guide
Build and publish containerized apps for the Archipelago ecosystem.
## Overview
Apps run as Podman containers on user nodes. You publish app manifests to Nostr relays, where nodes discover and install them through the community marketplace.
## App Manifest
Every app needs a manifest (YAML for local apps, JSON for marketplace publishing).
### Template Manifest
```yaml
# apps/my-app/manifest.yml
app:
id: my-app # Unique, lowercase kebab-case
name: My App
version: 1.0.0 # Semantic versioning
container:
image: docker.io/myorg/my-app:1.0.0 # Never use :latest
ports:
- container: 8080
host: 8180
protocol: tcp
volumes:
- name: data
path: /data
env:
APP_MODE: production
capabilities: [] # Only add if absolutely necessary
readonly_root: true # Required
no_new_privileges: true # Required
run_as_user: 1000 # Must be >= 1000
metadata:
description:
short: "One-line description (max 120 chars)"
long: "Detailed description of what this app does and why."
author:
name: "Your Name"
did: "did:key:z6Mk..." # Your Archipelago node DID
category: money # money | commerce | data | networking | home | community | other
icon_url: "https://example.com/icon.png"
repo_url: "https://github.com/myorg/my-app"
license: MIT
min_archipelago_version: "0.1.0"
dependencies: [] # e.g., ["bitcoin-knots"] if this app needs Bitcoin
```
### Required Fields
| Field | Description |
|-------|-------------|
| `app.id` | Unique identifier, lowercase, kebab-case only |
| `app.name` | Human-readable name |
| `app.version` | Semantic version (major.minor.patch) |
| `container.image` | Full image reference with pinned version tag |
| `metadata.description.short` | One-line description, max 120 characters |
| `metadata.author.did` | Your node's DID (get via `node.did` RPC) |
## Security Requirements
These are enforced by the marketplace and the node. Non-compliant apps are flagged.
### Mandatory
1. **No `:latest` tag** — Pin a specific version: `myapp:1.0.0`
2. **Read-only root filesystem**`readonly_root: true` (use volumes for writable data)
3. **Non-root user**`run_as_user: 1000` or higher
4. **No privilege escalation**`no_new_privileges: true`
5. **Minimal capabilities** — Drop all caps, only add required ones
### Allowed Capabilities
Only these Linux capabilities may be requested:
| Capability | When Needed |
|-----------|-------------|
| `CHOWN` | App needs to change file ownership |
| `NET_BIND_SERVICE` | App binds to ports below 1024 |
| `DAC_OVERRIDE` | App needs to bypass file permissions |
| `SETUID`, `SETGID` | App manages user switching (e.g., nginx) |
### Forbidden
- `--network host` — Apps cannot share the host network
- Mounting system paths: `/`, `/etc`, `/var`, `/usr`, `/proc`, `/sys`
- `SYS_ADMIN`, `SYS_PTRACE`, or any privileged capability
- Hardcoded secrets in environment variables or images
## Container Best Practices
### Volumes
```yaml
volumes:
- name: data # App data persists across restarts
path: /data
- name: config # Configuration files
path: /config
```
Data is stored at `/var/lib/archipelago/{app-id}/` on the host.
### Health Checks
Define a health check endpoint in your container:
```dockerfile
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
```
### Logging
- Log to stdout/stderr (Podman captures container logs)
- Never log secrets, passwords, or keys
- Use structured logging (JSON) for machine parsing
### Networking
Apps get their own network namespace. To connect to other Archipelago apps:
```yaml
# If your app needs to talk to Bitcoin
dependencies:
- bitcoin-knots
container:
env:
BITCOIN_RPC_HOST: bitcoin-knots # Container DNS name on archy-net
BITCOIN_RPC_PORT: "8332"
```
The `archy-net` Podman network provides DNS resolution between containers.
## Publishing to the Marketplace
### 1. Build and Push Your Image
```bash
podman build -t docker.io/myorg/my-app:1.0.0 .
podman push docker.io/myorg/my-app:1.0.0
```
### 2. Get Your Node's DID
```bash
curl -b cookies.txt -X POST http://localhost/rpc/v1 \
-d '{"method":"node.did"}'
# Returns: {"result":{"did":"did:key:z6Mk..."}}
```
### 3. Publish via RPC
```bash
curl -b cookies.txt -X POST http://localhost/rpc/v1 \
-H "Content-Type: application/json" \
-d '{
"method": "marketplace.publish",
"params": {
"app_id": "my-app",
"name": "My App",
"version": "1.0.0",
"description": {"short": "A useful tool", "long": "Detailed description..."},
"author": {"name": "Dev Name", "did": "did:key:z6Mk...", "nostr_pubkey": ""},
"container": {
"image": "docker.io/myorg/my-app:1.0.0",
"ports": [{"container": 8080, "host": 8180, "protocol": "tcp"}],
"volumes": [],
"env": {},
"capabilities": [],
"readonly_root": true,
"no_new_privileges": true,
"run_as_user": 1000
},
"category": "other",
"icon_url": "",
"repo_url": "https://github.com/myorg/my-app",
"license": "MIT",
"min_archipelago_version": "0.1.0",
"dependencies": []
}
}'
```
The manifest is published to all configured Nostr relays as a NIP-78 event (kind 30078).
### 4. Verify Discovery
```bash
curl -b cookies.txt -X POST http://localhost/rpc/v1 \
-d '{"method":"marketplace.discover"}'
# Your app should appear in the results
```
## Trust Model
Published apps receive trust scores (0-100) based on:
| Factor | Points | How to Maximize |
|--------|--------|-----------------|
| Valid DID in author | 30 | Always include your node's DID |
| Found on multiple relays | 5-20 | Configure many relays in your node |
| Developer in federation | 20 | Have federated peers who trust you |
| Proper semver version | 10 | Use `major.minor.patch` format |
| Repository URL present | 5 | Include your repo URL |
| Security compliance | 15 | Meet all security requirements |
### Trust Tiers
| Score | Tier | User Experience |
|-------|------|----------------|
| 80-100 | Verified | One-click install |
| 50-79 | Community | Install with confirmation |
| 20-49 | Unverified | Install with warning |
| 0-19 | Untrusted | Requires explicit override |
## Testing Your App
### Local Testing
```bash
# Run your container locally
podman run -d --name my-app \
-p 8180:8080 \
--read-only \
--security-opt no-new-privileges \
--user 1000:1000 \
docker.io/myorg/my-app:1.0.0
# Verify it works
curl http://localhost:8180/health
# Check logs
podman logs my-app
```
### On an Archipelago Node
1. Install via the marketplace UI or RPC:
```bash
curl -b cookies.txt -X POST http://192.168.1.228/rpc/v1 \
-d '{"method":"package.install","params":{"id":"my-app","dockerImage":"docker.io/myorg/my-app:1.0.0"}}'
```
2. Verify the container is running:
```bash
curl -b cookies.txt -X POST http://192.168.1.228/rpc/v1 \
-d '{"method":"container-list"}'
```
3. Check the UI at `http://192.168.1.228/app/my-app/`
### Validate Manifest
```bash
curl -b cookies.txt -X POST http://localhost/rpc/v1 \
-H "Content-Type: application/json" \
-d '{"method":"marketplace.verify","params":{...your manifest...}}'
# Returns: {"result":{"valid":true,"issues":[],"trust_score":65,"trust_tier":"community"}}
```
## Updating Your App
1. Build and push the new version: `docker.io/myorg/my-app:1.1.0`
2. Publish an updated manifest with the new version
3. NIP-33 replaceable events: the latest publish overwrites the previous one on relays
4. Nodes running your app can see the update in their marketplace
## App Icon
- Provide a URL to your app icon (PNG, WebP, or SVG)
- Recommended size: 256x256 pixels
- Square aspect ratio
- If no icon URL, a generic placeholder is shown in the marketplace

114
docs/arm64-build.md Normal file
View File

@@ -0,0 +1,114 @@
# ARM64 (aarch64) Cross-Compilation Guide
## Overview
Archipelago supports both x86_64 and ARM64 (aarch64) platforms. The backend is compiled natively on x86_64 and cross-compiled for ARM64 targets like Raspberry Pi 5.
## Prerequisites
### On the Build Server (Debian 12)
```bash
# 1. Add the ARM64 Rust target
rustup target add aarch64-unknown-linux-gnu
# 2. Install the cross-linker and C library
sudo apt update
sudo apt install -y gcc-aarch64-linux-gnu libc6-dev-arm64-cross
# 3. Install cross-compilation OpenSSL headers (for reqwest/hyper TLS)
sudo apt install -y libssl-dev:arm64
# If the above fails (no multiarch), use vendored OpenSSL instead:
# Set OPENSSL_STATIC=1 and add openssl = { version = "0.10", features = ["vendored"] }
```
### Cargo Configuration
The cross-compilation config is at `core/.cargo/config.toml`:
```toml
[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"
```
## Building
### Native (x86_64)
```bash
cd core
cargo build --release -p archipelago
# Output: core/target/release/archipelago
```
### ARM64 Cross-Compilation
```bash
cd core
# Option A: System OpenSSL (requires libssl-dev:arm64)
PKG_CONFIG_ALLOW_CROSS=1 \
PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig \
cargo build --release --target aarch64-unknown-linux-gnu -p archipelago
# Output: core/target/aarch64-unknown-linux-gnu/release/archipelago
# Option B: Vendored OpenSSL (no system packages needed)
OPENSSL_STATIC=1 \
cargo build --release --target aarch64-unknown-linux-gnu -p archipelago
```
### Verify the Binary
```bash
file core/target/aarch64-unknown-linux-gnu/release/archipelago
# Should show: ELF 64-bit LSB pie executable, ARM aarch64, ...
```
## Using `cross` (Alternative)
The `cross` tool uses Docker containers for hermetic cross-compilation:
```bash
cargo install cross
# Build for ARM64 (downloads a Docker image with all dependencies)
cross build --release --target aarch64-unknown-linux-gnu -p archipelago
```
This is the simplest approach and avoids installing system cross-compilation packages.
## Troubleshooting
### `cannot find -lssl` / `cannot find -lcrypto`
OpenSSL headers for ARM64 are missing. Either:
- Install `libssl-dev:arm64` (requires multiarch support)
- Use vendored OpenSSL: set `OPENSSL_STATIC=1`
- Add `openssl = { version = "0.10", features = ["vendored"] }` to Cargo.toml
### `cc: error: unrecognized command-line option`
The wrong linker is being used. Verify `aarch64-linux-gnu-gcc` is installed:
```bash
which aarch64-linux-gnu-gcc
aarch64-linux-gnu-gcc --version
```
### `Exec format error` when running
You're trying to run an ARM64 binary on x86_64. Use `qemu-aarch64-static` for testing:
```bash
sudo apt install qemu-user-static
qemu-aarch64-static ./archipelago
```
## Target Hardware
| Device | Arch | Status |
|--------|------|--------|
| Raspberry Pi 5 | aarch64 | Primary ARM target |
| Raspberry Pi 4 | aarch64 | Supported |
| Rock Pi 4 | aarch64 | Untested |
| Orange Pi 5 | aarch64 | Untested |
| x86_64 NUC/Mini PC | x86_64 | Primary platform |

View File

@@ -0,0 +1,21 @@
# ARM64 Container Image Compatibility
All core Archipelago marketplace apps have multi-arch Docker images with ARM64 (linux/arm64) support.
## Core Apps
| App | Image | Tag | ARM64 | ARMv7 |
|-----|-------|-----|-------|-------|
| Bitcoin Knots | `bitcoinknots/bitcoin` | `latest` | Yes | Yes |
| Electrs | `mempool/electrs` | `latest` | Yes | No |
| BTCPay Server | `btcpayserver/btcpayserver` | `1.13.5` | Yes | Yes |
| LND | `lightninglabs/lnd` | `v0.17.4-beta` | Yes | No |
| Mempool | `mempool/frontend` | `v2.5.0` | Yes | Yes |
| FileBrowser | `filebrowser/filebrowser` | `v2.27.0` | Yes | Yes |
## Notes
- All images use multi-arch manifest lists — Podman/Docker will automatically pull the correct architecture
- No changes needed to `Marketplace.vue` image references — the same tags work on both x86_64 and ARM64
- Three apps also support ARMv7 (32-bit ARM), but Archipelago targets ARM64 only
- Verified 2026-03-11 via Docker Hub registry API manifest inspection

View File

@@ -0,0 +1,78 @@
# ARM64 Raspberry Pi 5 Testing Guide
## Prerequisites
- Raspberry Pi 5 (4GB+ RAM recommended)
- USB flash drive (16GB+) for the installer
- MicroSD card or NVMe SSD for the target install
- Monitor + keyboard (or serial console for headless setup)
- Ethernet connection (WiFi can be configured after install)
## Building the ARM64 ISO
On the build server (192.168.1.228):
```bash
cd ~/archy/image-recipe
sudo ARCH=arm64 ./build-auto-installer-iso.sh
# Output: results/archipelago-installer-arm64.iso
```
## Flashing to USB
```bash
# On macOS
diskutil list # identify USB drive
diskutil unmountDisk /dev/diskN
sudo dd if=results/archipelago-installer-arm64.iso of=/dev/rdiskN bs=4m
# On Linux
sudo dd if=results/archipelago-installer-arm64.iso of=/dev/sdX bs=4M status=progress
```
Or use Balena Etcher for a GUI approach.
## Testing Checklist
### Boot & Install
- [ ] RPi 5 boots from USB drive (may need to enable USB boot in EEPROM)
- [ ] Auto-installer detects target disk (NVMe/SD)
- [ ] Installation completes without errors
- [ ] System reboots into installed OS
### First Boot
- [ ] Archipelago service starts (`systemctl status archipelago`)
- [ ] Nginx starts and serves UI
- [ ] Web UI loads in browser at `http://<pi-ip>`
- [ ] Onboarding flow completes
- [ ] Login works with default password
### Container Stack
- [ ] Podman runs on ARM64 (`podman version`)
- [ ] Bitcoin Knots installs and syncs
- [ ] LND installs and connects to Bitcoin
- [ ] Electrs installs and indexes
- [ ] Mempool installs and shows data
- [ ] FileBrowser installs and serves files
### Performance
- [ ] Backend response time < 200ms for RPC calls
- [ ] UI renders smoothly (no jank)
- [ ] Container startup time reasonable (< 30s per app)
- [ ] Memory usage stable (no leaks over 24h)
## Known RPi 5 Considerations
1. **USB Boot**: RPi 5 needs EEPROM update for USB boot. Run `sudo rpi-eeprom-update` on a stock Raspberry Pi OS first.
2. **NVMe**: RPi 5 supports NVMe via the M.2 HAT. Recommended for performance.
3. **Power**: Use the official 27W USB-C power supply. Underpowered supplies cause throttling.
4. **Thermals**: Consider a heatsink or active cooling case for sustained Bitcoin node operation.
5. **Storage**: Bitcoin blockchain requires ~600GB+. Use NVMe or external SSD.
## Reporting Issues
Document any ARM64-specific issues found during testing:
- Architecture-specific container failures
- Performance differences vs x86_64
- Hardware compatibility problems
- Missing kernel modules or firmware

69
docs/canary-deploy.md Normal file
View File

@@ -0,0 +1,69 @@
# Canary Deploy Process
## Overview
Deploy changes to the secondary server first (192.168.1.198), verify health, then deploy to the primary server (192.168.1.228). This reduces risk by catching issues before they affect the main system.
## Steps
### 1. Deploy to Secondary (Canary)
```bash
# Deploy to secondary server only
TARGET_HOST=archipelago@192.168.1.198 ./scripts/deploy-to-target.sh --live
```
### 2. Verify Health
```bash
# Check health endpoint
curl -s http://192.168.1.198/health
# Check backend service
ssh -i ~/.ssh/archipelago-deploy archipelago@192.168.1.198 "sudo systemctl status archipelago"
# Spot-check the UI
# Open http://192.168.1.198 in browser, verify pages load
```
### 3. Deploy to Primary
Once the secondary is healthy and verified:
```bash
./scripts/deploy-to-target.sh --live
```
### 4. Verify Primary
```bash
curl -s http://192.168.1.228/health
```
## Quick Deploy to Both (Non-Canary)
If you're confident and want to deploy to both at once:
```bash
./scripts/deploy-to-target.sh --both
```
This deploys to 228 first, then copies the built artifacts to 198. Not a true canary — use the step-by-step process above for safer rollouts.
## Rollback
If the canary (198) shows issues, do NOT deploy to primary. Fix the issue first.
If primary (228) shows issues after deploy:
```bash
# Check logs
ssh -i ~/.ssh/archipelago-deploy archipelago@192.168.1.228 "sudo journalctl -u archipelago -n 50"
# Restart services
ssh -i ~/.ssh/archipelago-deploy archipelago@192.168.1.228 "sudo systemctl restart archipelago && sudo systemctl restart nginx"
```
## Post-Deploy Health Check
The deploy script automatically waits up to 60 seconds for the health endpoint to return 200 after deploying. If it fails, check the backend logs for errors.

311
docs/developer-guide.md Normal file
View File

@@ -0,0 +1,311 @@
# Archipelago Developer Guide
## Project Structure
```
archy/
├── core/ # Rust backend
│ └── archipelago/
│ ├── src/
│ │ ├── main.rs # Entry point, module declarations
│ │ ├── api/rpc/ # RPC endpoint handlers
│ │ │ ├── mod.rs # Route dispatcher
│ │ │ ├── auth.rs # Login, session, TOTP
│ │ │ ├── container.rs # Container lifecycle
│ │ │ ├── package.rs # Package install/remove
│ │ │ ├── interfaces.rs # Network interfaces, WiFi, DNS
│ │ │ ├── federation.rs # Federation management
│ │ │ ├── marketplace.rs # Community marketplace
│ │ │ └── ... # Other endpoint groups
│ │ ├── auth.rs # Password hashing, sessions
│ │ ├── config.rs # Configuration loading
│ │ ├── server.rs # HTTP/WS server (axum)
│ │ ├── container/ # Podman integration
│ │ ├── network/ # Network management
│ │ │ ├── dns.rs # DNS configuration
│ │ │ ├── router.rs # UPnP, diagnostics
│ │ │ └── dwn_*.rs # DWN protocol
│ │ ├── federation.rs # Federation protocol
│ │ ├── marketplace.rs # Marketplace discovery
│ │ ├── identity.rs # DID key management
│ │ ├── vpn.rs # VPN (Tailscale/WireGuard)
│ │ ├── mesh.rs # Meshtastic mesh networking
│ │ └── ...
│ ├── Cargo.toml
│ └── tests/ # Integration tests
├── neode-ui/ # Vue 3 frontend
│ ├── src/
│ │ ├── api/ # RPC client, WebSocket, container client
│ │ │ └── rpc-client.ts # Central RPC client (all backend calls)
│ │ ├── views/ # Page components
│ │ │ ├── Home.vue # Dashboard with system stats
│ │ │ ├── Marketplace.vue # App store (curated + community)
│ │ │ ├── Server.vue # Network, VPN, DNS management
│ │ │ ├── Federation.vue # Federation dashboard
│ │ │ ├── Settings.vue # User settings
│ │ │ ├── Web5.vue # DID, DWN, Nostr
│ │ │ └── ...
│ │ ├── stores/ # Pinia state management
│ │ ├── components/ # Reusable UI components
│ │ ├── composables/ # Vue composables
│ │ ├── router/ # Vue Router with guards
│ │ ├── types/ # TypeScript type definitions
│ │ └── style.css # Global styles + Tailwind utilities
│ ├── vite.config.ts
│ └── package.json
├── scripts/ # Deployment and utility scripts
│ ├── deploy-to-target.sh # Main deploy script
│ ├── first-boot-containers.sh # ISO first-boot setup
│ └── run-tests.sh # CI test runner
├── image-recipe/ # ISO build configuration
│ ├── build-auto-installer-iso.sh
│ └── configs/ # Nginx, systemd configs
├── docs/ # Documentation
│ ├── architecture.md
│ ├── app-manifest-spec.md
│ ├── marketplace-protocol.md
│ └── multi-node-architecture.md
├── apps/ # App manifests (YAML)
├── CLAUDE.md # AI development instructions
└── loop/plan.md # Project roadmap
```
## Development Setup
### Prerequisites
- **macOS** (development machine): Node.js 20+, npm
- **Linux server** (`192.168.1.228`): Rust toolchain, Podman, Nginx, Debian 12
- SSH key: `~/.ssh/archipelago-deploy`
### Local Frontend Development
```bash
cd neode-ui
npm install
npm start # Vite dev server on :8100, mock backend on :5959
```
The dev server at `http://localhost:8100` uses a mock backend. Login with `password123`.
### Deploying Changes
**Never build Rust on macOS.** The deploy script rsyncs source to the Linux server and builds there.
```bash
# Deploy to live server (builds backend + frontend, restarts services)
./scripts/deploy-to-target.sh --live
# Deploy to both servers
./scripts/deploy-to-target.sh --both
```
The deploy script:
1. Rsyncs source to the server
2. Builds Rust backend on the server (`cargo build --release`)
3. Builds Vue frontend (`npm run build`)
4. Copies artifacts to production paths
5. Restarts the `archipelago` systemd service
6. Runs a health check
### Running Tests
```bash
# Frontend tests
cd neode-ui && npm test
# Backend tests (on dev server via SSH)
ssh -i ~/.ssh/archipelago-deploy archipelago@192.168.1.228 \
"cd ~/archy/core && cargo test --all-features"
# Both
./scripts/run-tests.sh
```
## Adding a New RPC Endpoint
### 1. Create the Handler
Add a handler method in the appropriate file under `core/archipelago/src/api/rpc/`. If no existing file fits, create a new one.
```rust
// core/archipelago/src/api/rpc/mymodule.rs
use super::RpcHandler;
use anyhow::Result;
impl RpcHandler {
/// mymodule.action — description of what it does.
pub(super) async fn handle_mymodule_action(
&self,
params: Option<serde_json::Value>,
) -> Result<serde_json::Value> {
let params = params.ok_or_else(|| anyhow::anyhow!("Missing params"))?;
let name = params
.get("name")
.and_then(|v| v.as_str())
.ok_or_else(|| anyhow::anyhow!("Missing required parameter: name"))?;
// Your logic here
let result = do_something(name).await?;
Ok(serde_json::json!({ "ok": true, "result": result }))
}
}
```
**Key patterns:**
- Handlers are `pub(super)` — visible only to the RPC router
- Accept `Option<serde_json::Value>` for params (omit for parameterless endpoints)
- Return `Result<serde_json::Value>`
- Use `self.config.data_dir` for data persistence
- Use `anyhow::bail!()` for error responses
### 2. Register the Route
Add the module declaration and route in `core/archipelago/src/api/rpc/mod.rs`:
```rust
// At the top:
mod mymodule;
// In the match statement (handle_rpc_call):
"mymodule.action" => self.handle_mymodule_action(params).await,
```
### 3. Add Module (if new)
If your logic warrants a separate module:
```rust
// core/archipelago/src/main.rs
mod mymodule; // Add to module declarations
```
### 4. Frontend Client
Add a convenience method to `neode-ui/src/api/rpc-client.ts`:
```typescript
async myAction(params: { name: string }): Promise<{ ok: boolean; result: string }> {
return this.call({
method: 'mymodule.action',
params,
})
}
```
### 5. Deploy and Test
```bash
./scripts/deploy-to-target.sh --live
curl -X POST http://192.168.1.228/rpc/v1 \
-H "Content-Type: application/json" \
-b "archipelago_session=YOUR_SESSION" \
-d '{"method":"mymodule.action","params":{"name":"test"}}'
```
## Adding a New Vue Page
### 1. Create the Component
```vue
<!-- neode-ui/src/views/MyPage.vue -->
<template>
<div>
<h1 class="text-4xl font-bold text-white mb-2">My Page</h1>
<div class="glass-card p-6">
<!-- Content here -->
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { rpcClient } from '@/api/rpc-client'
// State and logic
</script>
```
### 2. Add the Route
In `neode-ui/src/router/index.ts`, add inside the dashboard children:
```typescript
{
path: 'my-page',
name: 'my-page',
component: () => import('@/views/MyPage.vue'),
},
```
### 3. Standards
- Always use `<script setup lang="ts">` — never Options API
- Use `glass-card` for containers, `bg-white/5 rounded-lg` for sub-rows
- Create global CSS classes in `src/style.css` instead of inline Tailwind
- Use `rpcClient` from `@/api/rpc-client.ts` for all backend calls
- Handle loading states and errors for all async operations
## Writing Tests
### Frontend (Vitest)
```typescript
// neode-ui/src/api/__tests__/my-test.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest'
describe('MyFeature', () => {
beforeEach(() => {
vi.restoreAllMocks()
})
it('should do something', async () => {
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve({ result: 'ok' }),
}))
// Test your logic
expect(true).toBe(true)
})
})
```
### Backend (Rust)
```rust
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
#[tokio::test]
async fn test_my_function() {
let dir = tempdir().unwrap();
let result = my_function(dir.path()).await.unwrap();
assert_eq!(result, expected);
}
}
```
## Code Quality Checklist
- [ ] TypeScript strict mode: no `any`, use `unknown` or proper types
- [ ] No `unwrap()` or `expect()` in production Rust code — use `?`
- [ ] No `console.log` — wrap in `if (import.meta.env.DEV)`
- [ ] No empty catch blocks — log or handle errors
- [ ] Functions under 50 lines
- [ ] `cargo clippy` and `cargo fmt` pass
- [ ] `npx vue-tsc --noEmit` passes
- [ ] Security: validate all inputs, no command injection
- [ ] Container security: readonly_root, no_new_privileges, non-root user
## Contributing
1. Create a feature branch: `git checkout -b feature/my-feature`
2. Make changes following the standards above
3. Test locally: `cd neode-ui && npm test`
4. Deploy to dev server: `./scripts/deploy-to-target.sh --live`
5. Verify at `http://192.168.1.228`
6. Commit with conventional format: `feat: add my feature`

View File

@@ -0,0 +1,179 @@
# Hardware Wallet Integration Architecture
## Overview
Archipelago supports hardware wallets for secure Bitcoin transaction signing via PSBT (Partially Signed Bitcoin Transactions). This document covers integration with ColdCard, Trezor, and Ledger hardware wallets.
## Supported Devices
| Device | Connection | PSBT Support | DID Signing | Detection |
|--------|-----------|-------------|-------------|-----------|
| ColdCard Mk4 | USB / MicroSD / NFC | Native PSBT | No (Bitcoin-only) | USB VID `0xd13e` |
| Trezor Model T/Safe 3 | USB / WebUSB | Via trezorctl | No | USB VID `0x534c` (SatoshiLabs) |
| Ledger Nano S/X/Plus | USB / Bluetooth | Via HWI | No | USB VID `0x2c97` |
## Architecture
```
┌─────────────────────────────────────────────────────────────────┐
│ Archipelago Node │
│ │
│ ┌──────────┐ ┌────────────┐ ┌──────────────────────────┐ │
│ │ Web UI │───▸│ RPC Server │───▸│ LND (gRPC) │ │
│ │ │ │ │ │ - FundPsbt │ │
│ │ QR code │ │ lnd.create │ │ - SignPsbt (partial) │ │
│ │ display │ │ -psbt │ │ - FinalizePsbt │ │
│ │ │ │ │ │ - PublishTransaction │ │
│ │ File │ │ lnd.final │ └──────────────────────────┘ │
│ │ upload │ │ ize-psbt │ │
│ └──────────┘ └────────────┘ │
│ ▲ │
│ │ PSBT (base64) │
│ ▼ │
│ ┌──────────────────────┐ │
│ │ Hardware Wallet │ │
│ │ - USB direct │ │
│ │ - QR code scan │ │
│ │ - MicroSD (CC) │ │
│ └──────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
```
## PSBT Signing Flow
### 1. Create Unsigned PSBT
The user initiates a transaction (send coins, open channel, close channel). Instead of LND signing automatically, we create an unsigned PSBT.
**RPC endpoint**: `lnd.create-psbt`
```json
{
"method": "lnd.create-psbt",
"params": {
"outputs": [{"address": "bc1q...", "amount_sats": 50000}],
"fee_rate_sat_per_vbyte": 10,
"change_address": "bc1q..."
}
}
```
**Response**:
```json
{
"psbt_base64": "cHNidP8BAH...",
"psbt_hex": "70736274ff...",
"estimated_fee_sats": 1420,
"inputs": [{"txid": "abc...", "vout": 0, "amount_sats": 100000}],
"outputs": [{"address": "bc1q...", "amount_sats": 50000}, {"address": "bc1q...", "amount_sats": 48580}]
}
```
**LND gRPC mapping**: Uses `WalletKit.FundPsbt` to select UTXOs and create the PSBT template.
### 2. Sign with Hardware Wallet
Three transfer methods supported:
#### QR Code (ColdCard NFC, Trezor via companion app)
- Display PSBT as animated QR code (BBQr format for large PSBTs)
- User scans with hardware wallet
- Hardware wallet displays transaction details for verification
- User confirms on device
- Signed PSBT returned as QR code — user scans with camera or uploads screenshot
#### USB Direct (Trezor, Ledger)
- Detect hardware wallet USB device
- Pass PSBT via USB HID protocol
- User confirms on device
- Signed PSBT returned via USB
#### MicroSD (ColdCard)
- Export PSBT file for download
- User transfers to ColdCard via MicroSD
- ColdCard signs and saves signed PSBT to MicroSD
- User uploads signed PSBT file back to Archipelago
### 3. Finalize and Broadcast
**RPC endpoint**: `lnd.finalize-psbt`
```json
{
"method": "lnd.finalize-psbt",
"params": {
"signed_psbt_base64": "cHNidP8BAH..."
}
}
```
**Response**:
```json
{
"txid": "abc123...",
"raw_tx_hex": "0200000001...",
"broadcast": true
}
```
**LND gRPC mapping**: Uses `WalletKit.FinalizePsbt` then `WalletKit.PublishTransaction`.
## USB Device Detection
**RPC endpoint**: `system.detect-usb-devices`
Scans `/sys/bus/usb/devices/` or uses `lsusb` to detect known hardware wallet vendor IDs:
| Vendor | VID | Product IDs |
|--------|-----|-------------|
| ColdCard (Coinkite) | `0xd13e` | `0xcc10` (Mk4), `0xcc20` (Q) |
| Trezor (SatoshiLabs) | `0x534c` | `0x0001` (One), `0x0002` (T) |
| Ledger | `0x2c97` | `0x0001` (Nano S), `0x0004` (Nano X), `0x0005` (Nano S+) |
Implementation approach:
```rust
// Read from /sys/bus/usb/devices/*/idVendor and idProduct
async fn detect_usb_devices(known_vids: &[(u16, &str)]) -> Vec<DetectedDevice> {
// Parse /sys/bus/usb/devices/X-Y/idVendor
// Match against known VIDs
// Return device list with type identification
}
```
The detection runs server-side since the hardware wallet is plugged into the Archipelago node (not the browser).
## UI Integration Points
### Send Coins View
- Add "Sign with Hardware Wallet" toggle/option
- When enabled: create unsigned PSBT → show QR/download → accept signed PSBT → finalize
### Channel Management
- Open Channel: PSBT funding option
- Close Channel: Cooperative close via PSBT
### Hardware Wallet Status
- Show notification banner when USB device detected
- Display device type and connection status
- Auto-detect on the Server/Dashboard page
## Security Considerations
1. **PSBT verification**: Display transaction details (amounts, addresses, fees) before and after hardware signing — user should verify they match
2. **No private keys on node**: When using hardware wallet flow, LND's internal wallet creates watch-only inputs; the hardware wallet holds the actual signing keys
3. **PSBT size limits**: QR codes can handle ~2KB; larger PSBTs need animated QR (BBQr) or file transfer
4. **USB permissions**: The `archipelago` user needs access to USB HID devices (`udev` rules)
## Implementation Priority
1. **Phase 1** (HW-02): PSBT create/finalize RPC endpoints via LND gRPC
2. **Phase 2** (HW-03): QR code display + file upload UI
3. **Phase 3** (HW-04): USB device detection and notification
4. **Future**: Direct USB HID communication (trezorlib, ledger-transport)
## Dependencies
- LND v0.18+ (PSBT API via WalletKit)
- `qrcode` npm package (QR generation in UI)
- `lsusb` or `/sys/bus/usb/` access (device detection)
- No external hardware wallet libraries needed for Phase 1-3 (PSBT is a standard format)

View File

@@ -0,0 +1,331 @@
# Decentralized App Marketplace Protocol
## Overview
Archipelago's community marketplace enables developers to publish app manifests to Nostr relays, where nodes discover and install them without a central app store. Trust is established through DID-signed manifests and community reputation.
## Architecture
```
Developer Node Nostr Relays User Node
│ │ │
│── Publish signed manifest ──► │ │
│ (NIP-78, kind 30078) │ │
│ │ ◄── Query app manifests ── │
│ │ (filter by d-tag) │
│ │ │
│ │── Return signed manifests ──► │
│ │ │
│ │ [Verify DID signature] │
│ │ [Check trust score] │
│ │ [Display in marketplace] │
│ │ │
│ │ [User clicks Install] │
│ │ [Pull container image] │
│ │ [Start container] │
```
## Manifest Schema
App manifests published to Nostr relays follow the existing `apps/{app-id}/manifest.yml` schema (see `docs/app-manifest-spec.md`), serialized as JSON within a Nostr event.
### Marketplace Manifest Fields
```json
{
"app_id": "my-bitcoin-tool",
"name": "My Bitcoin Tool",
"version": "1.2.0",
"description": {
"short": "A useful Bitcoin utility",
"long": "Detailed description of what this app does..."
},
"author": {
"name": "Developer Name",
"did": "did:key:z6Mkh...",
"nostr_pubkey": "npub1..."
},
"container": {
"image": "docker.io/developer/my-bitcoin-tool:1.2.0",
"ports": [{ "container": 8080, "host": 8180, "protocol": "tcp" }],
"volumes": [{ "name": "data", "path": "/data" }],
"env": {
"NETWORK": "mainnet"
},
"capabilities": [],
"readonly_root": true,
"no_new_privileges": true,
"run_as_user": 1000
},
"category": "money",
"icon_url": "https://example.com/icon.png",
"repo_url": "https://github.com/developer/my-bitcoin-tool",
"license": "MIT",
"min_archipelago_version": "0.1.0",
"dependencies": [],
"signatures": {
"manifest_hash": "sha256:abc123...",
"did_signature": "base64-encoded-signature"
}
}
```
### Required Fields
| Field | Type | Description |
|-------|------|-------------|
| `app_id` | string | Unique identifier, lowercase kebab-case |
| `name` | string | Human-readable display name |
| `version` | string | Semantic version (major.minor.patch) |
| `description.short` | string | One-line description (max 120 chars) |
| `author.did` | string | Developer's DID (did:key method) |
| `container.image` | string | Full container image reference with tag (never `latest`) |
| `category` | string | One of: money, commerce, data, networking, home, community, other |
### Security-Required Fields
| Field | Default | Description |
|-------|---------|-------------|
| `container.readonly_root` | true | Container root filesystem is read-only |
| `container.no_new_privileges` | true | Prevent privilege escalation |
| `container.run_as_user` | 1000 | UID to run as (must be > 1000) |
| `container.capabilities` | [] | Required Linux capabilities (drop all, add only needed) |
## Nostr Event Format
### Event Kind
App manifests use **NIP-78 application-specific data** with event kind **30078** (replaceable parameterized). This matches the existing node discovery pattern in `nostr_discovery.rs`.
### Event Structure
```json
{
"kind": 30078,
"tags": [
["d", "archipelago-app:<app_id>"],
["t", "archipelago-marketplace"],
["t", "category:<category>"],
["version", "<semver>"],
["image", "<container_image>"],
["L", "archipelago"],
["l", "app-manifest", "archipelago"]
],
"content": "<JSON-serialized manifest>",
"created_at": 1710000000,
"pubkey": "<developer's secp256k1 pubkey hex>",
"sig": "<schnorr signature>"
}
```
### Tag Semantics
| Tag | Purpose |
|-----|---------|
| `d` | Unique identifier for NIP-33 replaceable events. Format: `archipelago-app:<app_id>` |
| `t` | Searchable topic tags for relay filtering |
| `version` | Allows version-specific queries |
| `image` | Container image for quick display without parsing content |
| `L`/`l` | NIP-32 labeling namespace for structured queries |
### Publishing a Manifest
1. Developer creates/updates their app manifest
2. Serialize manifest as JSON
3. Compute SHA-256 hash of the serialized manifest
4. Sign the hash with the developer's DID key
5. Embed manifest + signature in Nostr event content
6. Sign the Nostr event with the node's secp256k1 key
7. Publish to all configured Nostr relays
### Discovering Manifests
1. Node queries configured relays with filter:
```json
{
"kinds": [30078],
"limit": 100,
"#t": ["archipelago-marketplace"]
}
```
2. For each returned event:
a. Verify Nostr event signature (standard NIP-01)
b. Parse manifest JSON from content
c. Verify DID signature on manifest hash
d. Check manifest against security requirements
e. Calculate trust score
3. Return manifests sorted by trust score
## Trust Model
### Trust Score Calculation
Each discovered app receives a trust score (0-100) based on:
| Factor | Weight | Description |
|--------|--------|-------------|
| **DID Verification** | 30 | Manifest is signed by a valid DID key |
| **Relay Consensus** | 20 | Manifest found on multiple independent relays |
| **Federation Trust** | 20 | Developer's DID is in the user's federation network |
| **Version History** | 15 | App has multiple published versions (shows maintenance) |
| **Security Compliance** | 15 | Manifest follows all security requirements |
### Trust Tiers
| Score | Tier | UI Treatment |
|-------|------|--------------|
| 80-100 | Verified | Green badge, install with one click |
| 50-79 | Community | Yellow badge, install with confirmation |
| 20-49 | Unverified | Orange badge, install with warning dialog |
| 0-19 | Untrusted | Red badge, requires explicit security override |
### Federation-Based Trust
When a developer's DID appears in the user's federation network (trusted peer), the app automatically receives +20 trust points. This creates organic trust propagation: if you trust a node operator, you're more likely to trust their published apps.
### ADR: Nostr Relays over Centralized Registry
**Decision**: Use Nostr relays as the app discovery layer instead of a centralized registry.
**Context**: A centralized app store contradicts Archipelago's sovereignty principles. Nostr relays provide censorship-resistant, decentralized event distribution.
**Consequences**:
- (+) No single point of failure for app discovery
- (+) Developers publish without permission or review gates
- (+) Multiple relay sources increase availability
- (+) Leverages existing Nostr infrastructure and key management
- (-) No global content moderation (each node decides trust locally)
- (-) Spam is possible (mitigated by DID verification and trust scoring)
- (-) Relay availability varies (mitigated by querying multiple relays)
## Signing Protocol
### Manifest Signing (DID Layer)
```
1. Serialize manifest to canonical JSON (sorted keys, no whitespace)
2. Compute: manifest_hash = SHA-256(canonical_json)
3. Sign: did_signature = Ed25519_Sign(did_private_key, manifest_hash)
4. Attach to manifest:
{
"signatures": {
"manifest_hash": "sha256:<hex>",
"did_signature": "<base64>"
}
}
```
### Event Signing (Nostr Layer)
Standard NIP-01 Schnorr signature over the event ID (hash of serialized event fields). This is handled by the Nostr client library.
### Verification Flow
```
Receiving Node:
1. Verify Nostr event signature (NIP-01) → Proves event authenticity
2. Extract manifest JSON from event content
3. Compute SHA-256 of manifest content
4. Compare with manifest.signatures.manifest_hash → Proves content integrity
5. Resolve DID document for manifest.author.did
6. Verify did_signature with DID public key → Proves developer identity
7. Check container.image tag is pinned (not :latest)
8. Validate security fields meet minimums
```
## RPC Endpoints
### Marketplace Discovery
| Method | Description | Auth |
|--------|-------------|------|
| `marketplace.discover` | Query relays for app manifests, verify, score, return sorted | Local |
| `marketplace.publish` | Publish an app manifest to configured relays | Local |
| `marketplace.get-manifest` | Get full manifest for a specific app by ID | Local |
| `marketplace.verify` | Verify a manifest's signatures and security compliance | Local |
### Manifest Management
| Method | Description | Auth |
|--------|-------------|------|
| `marketplace.list-published` | List manifests published by this node | Local |
| `marketplace.unpublish` | Remove a published manifest from relays | Local |
## Security Requirements
### Container Security Enforcement
Before installing a community app, the node validates:
1. **No `latest` tag**: Image must use a specific version tag
2. **Read-only root**: `readonly_root` must be true (or explicitly overridden by user)
3. **No root**: `run_as_user` must be > 1000
4. **No new privileges**: `no_new_privileges` must be true
5. **Minimal capabilities**: Only allowed capabilities are accepted (CHOWN, NET_BIND_SERVICE, etc.)
6. **No host networking**: Apps cannot use `--network host`
7. **Volume restrictions**: Apps cannot mount system paths (/, /etc, /var, /usr)
### Image Verification
- Container images are pulled from registries, never transferred between nodes
- Future: Cosign signature verification for container images (leverages `core/security/`)
- Image digest pinning recommended for production apps
## UI: Community Marketplace Tab
### Route
Extends existing `/dashboard/marketplace` page.
### Layout
Two tabs at the top of Marketplace.vue:
1. **Curated** (existing): Built-in apps maintained by Archipelago team
2. **Community** (new): Apps discovered from Nostr relays
### Community Tab Components
1. **App Grid**: Same card layout as curated tab, with trust score badge
2. **Search & Filter**: Category filter + text search across community apps
3. **Trust Indicators**: Color-coded badges (Verified/Community/Unverified/Untrusted)
4. **App Detail**: Shows full manifest, developer DID, relay sources, version history
5. **Install Flow**: Trust-level-dependent confirmation (one-click for Verified, warning for Untrusted)
### Publishing UI
Accessible from Settings or a "Developer" section:
1. Select a local app container to publish
2. Fill in manifest metadata (description, category, icon)
3. Review security compliance
4. Sign and publish to relays
5. View published manifests and their discovery status
## Data Storage
```
/var/lib/archipelago/marketplace/
├── cache/
│ ├── manifests.json # Cached discovered manifests
│ └── trust-scores.json # Cached trust scores
├── published/
│ └── <app-id>.json # Manifests published by this node
└── config.json # Marketplace preferences (auto-refresh interval, etc.)
```
## Implementation Notes
### Relay Query Strategy
1. Query all enabled relays in parallel (from `nostr_relays.rs` config)
2. Deduplicate manifests by `app_id` + `version`
3. If same manifest found on multiple relays, boost trust score
4. Cache results with 15-minute TTL
5. Background refresh every 30 minutes
### Version Comparison
- Use semantic versioning for all version comparisons
- When multiple versions exist for the same `app_id`, show the latest
- Keep version history available in app detail view
- Flag apps with versions older than 6 months as potentially unmaintained

View File

@@ -0,0 +1,188 @@
# Multi-Node Architecture
## Overview
Archipelago supports federation — multiple nodes can form a trusted cluster to share status, deploy apps remotely, and coordinate services. This document describes the architecture for multi-node orchestration.
## Discovery & Trust Model
### Node Discovery
Nodes discover each other through two complementary channels:
1. **Nostr Relay Discovery**: Each node publishes its identity (DID, onion address, pubkey) to configured Nostr relays as a NIP-78 application-specific event. Other nodes query relays to find peers.
2. **Direct Invite**: A node generates an invite code containing its DID, onion address, and a one-time authentication token. The recipient node uses this code to establish a direct connection.
3. **Tor Hidden Services**: All inter-node communication uses Tor hidden services (.onion addresses) for privacy and NAT traversal.
### Trust Establishment
Federation uses a mutual DID verification model:
```
Node A Node B
│ │
│── federation.invite (generates invite code) ──► │
│ │
│ ◄── federation.join (presents invite + DID) ── │
│ │
│── Verify Node B's DID Document over Tor ──────► │
│ ◄── Verify Node A's DID Document over Tor ── │
│ │
│── Exchange signed challenge/response ─────────► │
│ ◄── Exchange signed challenge/response ────── │
│ │
│ [Mutual trust established] │
│ [Both nodes add each other to federation] │
```
**Trust Levels**:
- `trusted`: Full federation — can deploy apps, sync state, see all container statuses
- `observer`: Read-only — can see status but cannot deploy or modify
- `untrusted`: Discovered but not yet verified — pending invite acceptance
### ADR: Decentralized Trust over Centralized Authority
**Decision**: Use DID-based mutual verification instead of a central authority or PKI.
**Context**: Archipelago nodes are sovereign — no central server should control trust. Each node maintains its own trust list.
**Consequences**:
- (+) No single point of failure for trust
- (+) Nodes can federate without internet (direct Tor connection)
- (+) Consistent with the DID identity model already in use
- (-) No global revocation mechanism (each node manages its own trust)
- (-) Trust is bilateral — A trusting B doesn't imply C trusts B
## Shared State Protocol
### State Sync
Federated nodes periodically sync their state. Each node exposes a state summary via its RPC endpoint, accessible only to trusted federation peers.
**Synced data**:
- Container/app statuses (installed, running, stopped, version)
- Node health (CPU, memory, disk, uptime)
- Available storage capacity
- Tor hidden service status
- Lightning Network status (channels, capacity)
**Not synced** (privacy):
- Credentials and secrets
- Private keys
- Session data
- User passwords
### Sync Protocol
```
Every 5 minutes (configurable):
For each federated node:
1. POST to peer's /rpc/ endpoint: federation.get-state
2. Authenticate with signed challenge (DID key)
3. Receive state snapshot
4. Store in local federation cache
5. Broadcast changes via WebSocket to local UI
```
### State Storage
```
/var/lib/archipelago/federation/
├── nodes.json # List of federated nodes with trust levels
├── state-cache/
│ ├── <node-did>.json # Latest state snapshot from each peer
│ └── ...
└── invites/
├── pending.json # Outgoing invites awaiting acceptance
└── received.json # Incoming invites awaiting approval
```
## RPC Endpoints
### Federation Management
| Method | Description | Auth |
|--------|-------------|------|
| `federation.invite` | Generate invite code for a new peer | Local |
| `federation.join` | Accept an invite and establish federation | Local |
| `federation.list-nodes` | List all federated nodes with status | Local |
| `federation.remove-node` | Remove a node from federation | Local |
| `federation.set-trust` | Change trust level for a federated node | Local |
### Federation Data Exchange
| Method | Description | Auth |
|--------|-------------|------|
| `federation.get-state` | Return node's state snapshot | Federation peer |
| `federation.deploy-app` | Request remote app installation | Trusted peer |
| `federation.sync-state` | Trigger manual state sync | Local |
### Authentication for Inter-Node RPC
Federation RPC calls between nodes use DID-based authentication:
1. Caller includes `X-Federation-DID` header with their DID
2. Caller includes `X-Federation-Sig` header with a signed timestamp
3. Receiver verifies the DID is in their trusted federation list
4. Receiver verifies the signature using the DID's public key
5. Timestamp must be within 5 minutes to prevent replay attacks
## Federated App Deployment
### Flow
```
Local Node Remote Node
│ │
│── federation.deploy-app ──────► │
│ {app_id, version, config} │
│ │
│ [Remote verifies trust level] │
│ [Remote checks if app exists] │
│ [Remote pulls container image] │
│ [Remote starts container] │
│ │
│ ◄── Status update via sync ── │
│ {app_id: "running"} │
```
### Constraints
- Only `trusted` peers can deploy apps to each other
- Remote node can reject deployment (insufficient resources, policy)
- Container images are pulled from registry, not transferred between nodes
- App configuration is sent with the deploy command
- Remote node applies its own security policies (AppArmor, capabilities)
## UI: Federation Dashboard
**Route**: `/dashboard/server/federation`
**Components**:
1. **Node List**: Table of federated nodes showing:
- Node name (DID-derived or custom alias)
- Status: online/offline (based on last successful sync)
- Trust level badge (trusted/observer)
- App count, resource usage summary
- Last seen timestamp
2. **Add Node**: Form with invite code input or QR code scanner
3. **Node Detail Modal**: Clicking a node shows:
- Full DID and onion address
- Container/app list with statuses
- Resource usage (CPU, memory, disk)
- Deploy app button (if trusted)
- Change trust level / remove node
## Security Considerations
1. **All federation traffic over Tor**: Prevents IP address leakage between nodes
2. **DID-based auth**: No shared secrets; each node proves identity with its key
3. **Replay protection**: Signed timestamps prevent replay attacks
4. **Trust is bilateral**: Both nodes must agree to federate
5. **App deployment is opt-in**: Remote node can refuse deployment requests
6. **State snapshots are read-only**: A compromised peer cannot modify another node's state
7. **Invite codes are single-use**: Once accepted, the invite token is invalidated

111
docs/release-process.md Normal file
View File

@@ -0,0 +1,111 @@
# Archipelago Release Process
## Overview
Archipelago uses a JSON-based release manifest for the auto-update system. The backend checks `UPDATE_MANIFEST_URL` periodically (based on user's schedule setting) and compares versions.
## Manifest Format
The manifest is a single JSON file at:
```
https://raw.githubusercontent.com/archipelago-os/releases/main/manifest.json
```
```json
{
"version": "0.2.0",
"release_date": "2026-04-01",
"changelog": [
"Added automatic update scheduling",
"Improved backup encryption"
],
"components": [
{
"name": "archipelago",
"current_version": "0.1.0",
"new_version": "0.2.0",
"download_url": "https://github.com/archipelago-os/releases/releases/download/v0.2.0/archipelago",
"sha256": "abc123...",
"size_bytes": 15000000
}
]
}
```
## Release Steps
### 1. Build Release Artifacts
On the build server (192.168.1.228):
```bash
# Build backend (release mode)
cd ~/archy/core
cargo build --release -p archipelago
# Build frontend
cd ~/archy/neode-ui
npm run build
```
### 2. Generate Manifest
```bash
./scripts/create-release-manifest.sh \
--version 0.2.0 \
--date 2026-04-01
```
This auto-detects the backend binary and frontend archive, computes SHA256 hashes, and writes `manifest.json`.
### 3. Upload Artifacts
Upload the backend binary and frontend archive to GitHub Releases:
```bash
gh release create v0.2.0 \
core/target/release/archipelago \
/tmp/archipelago-frontend-0.2.0.tar.gz \
--title "v0.2.0" \
--notes "See CHANGELOG.md for details"
```
### 4. Publish Manifest
Push the generated `manifest.json` to the releases repo:
```bash
# In the archipelago-os/releases repo
cp manifest.json .
git add manifest.json
git commit -m "Release v0.2.0"
git push
```
### 5. Tag the Source
```bash
git tag v0.2.0
git push --tags
```
## Update Schedules
Users can configure how updates are handled:
| Schedule | Behavior |
|----------|----------|
| **Manual** | Never checks automatically. User must click "Check for Updates" |
| **Daily Check** (default) | Checks once per day. Notifies user, who decides when to install |
| **Auto-Apply** | Checks daily. Downloads and applies at 3 AM, restarts service |
## Rollback
If an update causes issues, users can rollback from the System Update page. The previous binary is backed up to `{data_dir}/update-backup/` before applying.
## Security
- All downloads are verified against SHA256 hashes in the manifest
- The manifest itself is fetched over HTTPS from a known URL
- Binary replacement requires service restart (handled by systemd)
- Rollback is always available after an update

View File

@@ -0,0 +1,28 @@
# StartOS Dependency Audit — 2026-03-10
## Summary
**`core/archipelago/` has ZERO dependencies on `core/startos/`.** The startos directory is dead code — not compiled, not imported, not referenced by any active module.
## Findings
### Workspace
- `core/Cargo.toml` workspace members: `archipelago`, `container`, `parmanode`, `performance`, `security`
- `startos` is NOT a workspace member
### Dependencies
- `core/archipelago/Cargo.toml`: no startos dependency
- `core/container/Cargo.toml`: no startos dependency
- All other core modules: no startos dependency
- `cargo tree -p archipelago`: startos does not appear
### Source Code
- Zero `use startos::*` imports in `core/archipelago/src/`
- Zero references to startos in any active Rust code
- No git submodule reference
### Status
`core/startos/` contains a StartOS fork (`start-os` v0.3.5-rev.1) that is present on disk but completely inert. It can be safely removed.
## Action
Remove `core/startos/` directory. No migration needed — there are no dependencies to migrate.

421
docs/user-guide.md Normal file
View File

@@ -0,0 +1,421 @@
# Archipelago User Guide
Welcome to Archipelago — your personal server for a sovereign digital life. This guide walks you through everything from first boot to daily usage.
## Table of Contents
1. [First-Time Setup](#first-time-setup)
2. [Onboarding Walkthrough](#onboarding-walkthrough)
3. [Dashboard Overview](#dashboard-overview)
4. [Installing Apps](#installing-apps)
5. [Managing Apps](#managing-apps)
6. [Bitcoin Node](#bitcoin-node)
7. [Lightning Network (LND)](#lightning-network-lnd)
8. [Cloud Storage](#cloud-storage)
9. [Identity & Web5](#identity--web5)
10. [Settings](#settings)
11. [Backup & Restore](#backup--restore)
12. [Remote Access](#remote-access)
13. [Troubleshooting](#troubleshooting)
---
## First-Time Setup
### What You Need
- A dedicated computer (Intel/AMD x86_64 or ARM64)
- 16 GB RAM minimum (32 GB recommended)
- 500 GB+ SSD/NVMe storage
- Ethernet connection to your home router
- A USB drive (8 GB+) for the installer
### Flashing the Installer
1. Download the latest Archipelago ISO from the releases page
2. Flash the ISO to a USB drive using [balenaEtcher](https://etcher.balena.io/) or `dd`
3. Insert the USB into your target machine and boot from it
4. The auto-installer partitions your disk, installs Debian 12, and sets up all Archipelago services
5. When complete, the installer prompts you to remove the USB and reboot
### Finding Your Server
After reboot, Archipelago starts automatically. Find your server on your local network:
- **Default address**: `http://archipelago.local` (if mDNS works on your network)
- **IP address**: Check your router's DHCP client list for the new device
- **Direct**: Connect a monitor — the IP is displayed on the console login screen
Open your browser and navigate to the server address. You should see the Archipelago welcome screen.
---
## Onboarding Walkthrough
On first visit, Archipelago guides you through a 7-step onboarding process.
### Step 1: Welcome Screen
A cinematic intro video plays. Click **"Begin"** to start setup.
### Step 2: Create Admin Password
Set your admin password. This password protects:
- The web interface login
- SSH access to your server
- Encrypted secrets on disk
**Requirements**: Minimum 8 characters. Choose something strong — this protects your entire server.
### Step 3: Choose Your Path
Select the sovereign use case that interests you most:
- **Self Sovereignty** — Own your data, identity, and digital life
- **Community Commerce** — Peer-to-peer commerce on Bitcoin
- **Sovereign Projects** — Collaborative workspace without third parties
- **Data Transmitter** — Run relays and network services
- **Hoster** — Monetize hosting capacity
- **Sovereign AI** — Run AI models locally, no surveillance
This is informational — all features are available regardless of your choice.
### Step 4: Setup Type
Choose **Fresh Start** for a new installation. (Restore from backup and connect to existing server are coming in future releases.)
### Step 5: Generate Your Identity (DID)
Archipelago generates a Decentralized Identifier (DID) for you. This is your sovereign digital identity — it proves you are you without any company in the middle.
- Your DID is displayed on screen — copy it if you like
- This identity is stored locally on your server
- It's used for passwordless authentication and Web5 features
Wait for the server health check to complete (13 minutes on first boot as services start up).
### Step 6: Name Your Identity
Give your identity a name (e.g., "Personal", "Business") and choose its purpose. This is optional and can be changed later.
### Step 7: Create a Backup
**Important**: Set a passphrase and download your identity backup file (`archipelago-did-backup.json`). Store this file securely — it's the only way to recover your identity if your server is lost.
### Done
After completing onboarding, you're taken to the Dashboard.
---
## Dashboard Overview
The Dashboard is your home screen with two tabs:
### Dashboard Tab
Quick overview cards showing:
- **My Apps** — How many apps are installed and running. Quick links to browse the store or manage apps.
- **Cloud** — Storage usage and folder count. Access your files.
- **Server** — Connection status and system health.
- **Web5** — DID status, wallet connection, and Nostr relay count.
### Setup Tab
Goal-based guided setup cards for first-time users. These walk you through installing and configuring recommended apps step by step.
---
## Installing Apps
### From the App Store
1. Navigate to **App Store** from the sidebar (desktop) or bottom bar (mobile)
2. Browse by category (Finance, Storage, Communication, Network, etc.) or search by name
3. Click an app to see its details
4. Click **Install**
### Installation Progress
When you install an app, a progress banner appears at the top of the App Store showing:
- Download progress (percentage and MB downloaded)
- Current status (Downloading, Installing, Starting)
Most apps take 15 minutes to install depending on image size and network speed.
### Dependency Resolution
Some apps require others to be running first:
- **Electrs** requires Bitcoin Knots
- **LND** requires Bitcoin Knots
- **Mempool** requires Bitcoin Knots + Electrs
- **BTCPay Server** requires Bitcoin Knots
The App Store shows dependency requirements and offers to install them automatically.
### Available Apps
| App | Category | Description |
|-----|----------|-------------|
| Bitcoin Knots | Finance | Full Bitcoin node with enhanced features |
| Electrs | Finance | Electrum server for wallet connectivity |
| LND | Finance | Lightning Network node for instant payments |
| BTCPay Server | Finance | Self-hosted payment processor |
| Mempool | Finance | Bitcoin blockchain explorer |
| Fedimint | Finance | Federated e-cash and community banking |
| File Browser | Storage | Web-based file manager |
| Immich | Storage | Photo and video management (Google Photos alternative) |
| PhotoPrism | Storage | AI-powered photo organizer |
| Penpot | Productivity | Open-source design tool (Figma alternative) |
| SearXNG | Privacy | Privacy-respecting metasearch engine |
| Ollama | AI | Run large language models locally |
| Nostr Relay | Network | Decentralized social protocol relay |
| Nginx Proxy Manager | Network | Reverse proxy with SSL management |
| Home Assistant | IoT | Smart home automation |
| Tailscale | Network | Zero-config VPN for remote access |
---
## Managing Apps
### My Apps View
Navigate to **My Apps** to see all installed applications in a grid view. Each card shows:
- App icon and name
- Status badge (Running, Stopped, Installing)
- Version number
### App Actions
Click an app to open its details page. Available actions:
- **Launch** — Open the app's web interface
- **Start** — Start a stopped app
- **Stop** — Stop a running app
- **Restart** — Restart the app
- **Uninstall** — Remove the app and its container (data volumes are preserved)
### App Interfaces
Most apps open in an embedded view within Archipelago. Some apps (BTCPay Server, Home Assistant) open in a new browser tab due to security restrictions.
---
## Bitcoin Node
### First-Time Bitcoin Setup
1. Install **Bitcoin Knots** from the App Store
2. The node begins syncing the blockchain automatically
3. Initial sync takes 17 days depending on your hardware and connection
4. Monitor sync progress by launching the Bitcoin Knots interface
### What Bitcoin Knots Provides
- Full validation of all Bitcoin transactions
- Privacy — no third party sees your wallet queries
- Foundation for Lightning (LND), Electrs, Mempool, and BTCPay
### Bitcoin Data Location
Bitcoin blockchain data is stored at `/var/lib/archipelago/bitcoin-knots/` on the server. Plan for 600+ GB of storage.
---
## Lightning Network (LND)
### Setup
1. Ensure Bitcoin Knots is installed and running
2. Install **LND** from the App Store
3. LND connects to your Bitcoin node automatically
### Managing Channels
Navigate to **My Apps → LND → Channels** to:
- View open channels and their balances
- Open new channels to peers
- Close existing channels
### Integration with BTCPay
When both LND and BTCPay Server are installed, BTCPay automatically detects LND and enables Lightning payments. No manual configuration needed.
---
## Cloud Storage
### File Browser
The built-in File Browser gives you web-based access to your server's files.
1. Navigate to **Cloud** from the sidebar
2. Click **File Browser** to open it
3. Upload, download, create folders, and manage files
### Photo Management (Immich)
For photo and video management similar to Google Photos:
1. Install **Immich** from the App Store
2. Access it from the Cloud section
3. Upload photos/videos or use the Immich mobile app to auto-backup your phone
### Storage Location
All cloud data is stored under `/var/lib/archipelago/` on your server's disk.
---
## Identity & Web5
### Your Decentralized Identifier (DID)
Your DID is a globally unique identifier that you control. Navigate to **Web5** to see:
- Your DID string (copy it to share)
- Wallet connection status
- Connected Nostr relays
### Nostr Relay
If you've installed the Nostr Relay, it runs locally on your server. You can use it with any Nostr client by adding your server's relay URL.
### DID Features (Coming Soon)
- Verifiable credentials
- Passwordless authentication to other services
- Data portability between servers
---
## Settings
Navigate to **Settings** from the sidebar.
### Account Information
- **Server Name** — Your server's display name
- **Version** — Current Archipelago version
- **DID** — Your Decentralized Identifier (with copy button)
- **Tor Address** — Your .onion address for Tor access (with copy button)
### Change Password
1. Click **Change Password**
2. Enter your current password
3. Enter and confirm your new password (12+ characters, must include uppercase, lowercase, digit, and special character)
4. All other active sessions are invalidated after password change
### Two-Factor Authentication (2FA)
1. Click **Enable 2FA** in Settings
2. Scan the QR code with your authenticator app (Google Authenticator, Authy, etc.)
3. Enter the 6-digit code to verify
4. Save your backup codes securely — they're the only way to log in if you lose your authenticator
### Logout
Click **Logout** to end your session. You'll be redirected to the login screen.
---
## Backup & Restore
### Identity Backup
During onboarding, you created an identity backup file. To create a new one:
1. Go to **Settings**
2. Look for backup options (identity backup is tied to your DID)
### App Data
App data is stored in `/var/lib/archipelago/{app-id}/` on the server. To back up:
1. Use File Browser to download important files
2. Or SSH into the server and use standard backup tools (rsync, tar)
### Full System Recovery
If your server fails:
1. Flash a new USB installer and install on replacement hardware
2. During onboarding, choose **Restore from Backup** (when available)
3. Upload your `archipelago-did-backup.json` file
4. Re-install your apps from the App Store
---
## Remote Access
### Local Network
Your Archipelago server is accessible on your home network at its IP address or `http://archipelago.local`.
### Tor (Built-in)
Every Archipelago server has a Tor hidden service. Your `.onion` address is shown in Settings. Access it via Tor Browser from anywhere in the world — no port forwarding required.
### Tailscale (Recommended)
For easy, secure remote access without Tor:
1. Install **Tailscale** from the App Store
2. Launch it and sign in with your Tailscale account
3. Install Tailscale on your phone/laptop
4. Access your server from anywhere via its Tailscale IP
See [Tailscale Setup Guide](USER-GUIDE-TAILSCALE.md) for detailed instructions.
---
## Troubleshooting
### Can't Find Server on Network
- Ensure the server is powered on and connected to your router via Ethernet
- Check your router's DHCP client list for the server's IP
- Try `http://archipelago.local` (requires mDNS support)
- Connect a monitor to see the IP displayed on the console
### Login Issues
- **Forgot password**: Connect a monitor and keyboard. Log in at the console and run the password reset tool
- **2FA locked out**: Use your backup codes on the login screen (click "Use backup code")
- **Session expired**: Sessions expire after 24 hours of inactivity. Simply log in again.
### App Won't Start
- Check if dependent apps are running (e.g., Bitcoin Knots must run before Electrs)
- Try stopping and starting the app again
- Check app logs in **My Apps → [App] → Logs**
- Restart the Archipelago service: SSH in and run `sudo systemctl restart archipelago`
### Bitcoin Node Sync Issues
- Initial sync can take several days — this is normal
- Ensure your server has a reliable internet connection
- Check available disk space: Bitcoin needs 600+ GB
### WebSocket Disconnected
The UI shows a "Reconnecting..." banner if the WebSocket connection drops.
- This auto-recovers within 30 seconds
- If persistent, check that the backend service is running: `sudo systemctl status archipelago`
- Hard refresh the browser (Ctrl+Shift+R)
### Server Unresponsive
If the web UI is unreachable:
1. SSH into the server: `ssh archipelago@<your-ip>`
2. Check service status: `sudo systemctl status archipelago`
3. Restart if needed: `sudo systemctl restart archipelago`
4. Check system resources: `free -h` (RAM), `df -h` (disk)
### Getting Help
- Check the [Architecture Guide](architecture.md) for technical details
- File issues at the project repository
- Join the community for support