Commit Graph

188 Commits

Author SHA1 Message Date
Dorian
4d17c60da7 refactor: replace blocking std::fs and TCP I/O with async tokio equivalents
- R6: Convert 6 std::fs calls in session.rs to tokio::fs async
- R7: Convert std::fs::read_to_string in docker_packages.rs to async
- R8: Convert 3 std::fs calls in port_allocator.rs to async, switch to tokio::sync::Mutex
- R9+R10+R11: Fix blocking I/O in node_message.rs and nostr_discovery.rs
- R12: Convert electrs_status.rs from sync TCP to async tokio::net with 5s timeouts
- R4+R5: Spawn periodic cleanup tasks for endpoint and login rate limiters

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 01:21:08 +00:00
Dorian
c299199d37 fix: add health RPC handler, Nostr connect timeouts, atomic backup restore, nginx rate limits
- R1: Add health RPC endpoint with crash recovery status, uptime, and version
- R2: Wrap all 5 Nostr client.connect() calls in 10s timeout
- R3: Make backup restore atomic with staging dir and rollback on failure
- I1: Add rate limiting, body size, and proxy timeouts to unauthenticated nginx endpoints

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 01:02:16 +00:00
Dorian
196682f2f2 fix: LND and ElectrumX Tor onion address resolution
- lnd.rs: check tor-hostnames readable copy, then /var/lib/tor/, then
  legacy /var/lib/archipelago/tor/ with sudo fallback for each
- electrs_status.rs: same multi-path resolution for ElectrumX onion
- Both servers: created /var/lib/archipelago/tor-hostnames/ with readable
  copies of onion addresses (avoids sudo on every API call)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 12:31:30 +00:00
Dorian
b31148a8b7 fix: rpcauth credentials, reboot survival, system Tor for all containers
- Bitcoin RPC: switch to rpcauth (salted hash in bitcoin.conf, no plaintext
  in config or CLI). Password stable across reboots/restarts/deploys.
- Remove daily-reboot-test.sh cron on both servers
- Enable podman-restart.service for container auto-start after reboot
- System Tor: SocksPort 0.0.0.0:9050 with SocksPolicy for container access
- LND: tor.socks=host.containers.internal:9050 (system Tor, not container)
- Bitcoin: -proxy=host.containers.internal:9050 for Tor outbound
- bitcoin_rpc.rs: reads from secrets file, cached, stable credentials
- package.rs: dynamic rpc_user/rpc_pass, rpcauth hash generation
- network.rs: fix missing send_to_peer args (mesh encryption update)
- first-boot-containers.sh: rpcauth generation, system Tor config
- deploy-to-target.sh: rpcauth credentials, LND config migration
- Mesh: encrypted channel message support (ChaCha20-Poly1305 updates)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 11:56:20 +00:00
Dorian
b4d204d1d6 feat: reboot button in Settings with password confirmation
- system.reboot RPC endpoint requires password re-verification
- Uses systemd path unit pattern (tor-helper.sh) for privilege escalation
- 2-second delay before reboot to allow RPC response to reach client
- Clean UI: password input modal, loading state, error feedback

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 10:48:06 +00:00
Dorian
c82158c7c8 refactor: PodmanClient uses REST API socket instead of CLI
Replace all `podman` CLI shell-outs with HTTP requests to the rootless
Podman API unix socket (/run/user/{UID}/podman/podman.sock).

Benefits:
- No process spawning overhead — direct HTTP over unix socket
- Structured JSON responses — no string parsing fragility
- Proper timeouts on all operations (5s connect, 30s default, 120s create)
- Health check method to verify socket availability
- Restart container as first-class operation

Still uses CLI for:
- Image pulls (streaming operation better suited to CLI)
- Container logs (raw text stream, not JSON)

The Podman socket is rootless (runs as archipelago user), local-only
(unix socket), and already behind our session auth in the backend.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 10:13:49 +00:00
Dorian
9b6adfc42d feat: E2E encrypted Tor channel messages (ChaCha20-Poly1305)
Messages between federated nodes are now end-to-end encrypted:
- X25519 ECDH key agreement from existing ed25519 node identities
- HKDF-SHA256 key derivation with domain separation
- ChaCha20-Poly1305 authenticated encryption per message
- Random 12-byte nonce per message via OsRng (CSPRNG)
- Graceful fallback to plaintext if encryption fails
- Receiver auto-detects encrypted vs plaintext messages

The Tor transport was already encrypted (onion routing), this adds
application-layer E2E encryption so even a compromised receiving
backend can't read messages without the node's private key.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 09:04:43 +00:00
Dorian
f0a403b224 fix: persistent Tor channel messages, bulletproof Tor after deploys
- Messages persisted to disk (messages.json) — survive restarts
- Sent messages stored on backend via node-store-sent RPC
- Message deduplication (same pubkey + message within 30s)
- Max 200 messages in circular buffer
- Direction field (sent/received) for proper UI display
- Container doctor: prefer system Tor, remove archy-tor container
- Deploy torrc generator: read from tor-config/services.json,
  web apps map port 80→local port for clean .onion URLs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 08:26:40 +00:00
Dorian
fc1120338d fix: Tor management system, bug fixes, federation name sync
Major changes:
- Full Tor hidden service management via systemd path unit pattern
  (tor-helper.sh + archipelago-tor-helper.path/service) — respects
  NoNewPrivileges=yes, no sudo needed from backend
- Container doctor: prefer system Tor over container, remove archy-tor
- Deploy script: fix torrc generation (read correct services.json path),
  web apps map port 80→local port, enable both tor and tor@default
- Federation: server rename pushes name to peers via background sync
- Server name: fix root-owned file, optimistic store update
- Mesh: local echo for sent messages, sendingArch loading state
- Web5: Message button → Mesh redirect, node name lookup in messages
- PeerFiles: show DID not onion in header
- Connected Nodes: flex-1 instead of fixed max-h
- Toast notifications route to Mesh
- Deploy script: fix single-quote syntax in SSH block

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 02:59:29 +00:00
Dorian
c5417640a2 feat: Lightning channel backup, Web5 mobile tab active, file path fix
Task 14: Lightning Channel Backup
- New lnd.export-channel-backup RPC — exports SCB (Static Channel Backup)
- Settings UI: "Lightning Channel Backup" section with export + copy
- Returns base64 backup data, channel count, timestamp

Web5 mobile tab active state
- Fixed combined tab matching for Web5: includes /web5, /federation, /mesh routes
- Previously only matched /cloud and /server (wrong branch)

Content file path fix
- Allow forward slashes in filenames for subdirectories (Music/song.mp3)
- Still block .., \, null bytes, hidden files, absolute paths

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 21:47:18 +00:00
Dorian
93aaeb4abe fix: node names not DIDs, file sharing path validation, sync results
- nodeName() shows friendly "Node-XXXX" instead of truncated DID
- nodeNameFromDid() for sync results lookup
- Map labels use node names
- Content filename validation: allow / for subdirectories (Music/song.mp3)
  but still block .., \, null bytes, hidden files, absolute paths
- Increased filename max length to 512 for paths with subdirectories

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 20:35:41 +00:00
Dorian
1a138c0409 feat: DID rotation + federation peer notification (Part 3)
- node.rotate-did: generates new Ed25519 keypair, signs rotation proof
  with old key, overwrites identity files, requires password
- federation.notify-did-change: broadcasts rotation proof to all
  trusted/observer peers over Tor
- federation.peer-did-changed: receiving side verifies rotation proof
  against known pubkey before updating peer's DID
- Rate-limited: 3/600s for rotation, 5/60s for peer notification
- Signature verification uses ed25519_dalek (constant-time)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 19:27:16 +00:00
Dorian
f8794791f3 feat: DID persistence + federation node names in sync
Part 1 — DID Persistence:
- Deploy script creates /var/lib/archipelago/identity/ directory
- First-boot script creates identity dir with proper ownership
- Identity load now logs pubkey to confirm persistence across restarts

Part 2 — Node Names:
- NodeStateSnapshot includes node_name field
- build_local_state() passes server name to sync responses
- update_node_state() stores peer's announced name on the FederatedNode
- Names propagate automatically during federation.sync-state

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 19:19:13 +00:00
Dorian
f20f0650cf feat: Discover view, Fleet dashboard, MeshMap, type fixes
- New Discover.vue (app store redesign)
- Fleet.vue dashboard for .228
- MeshMap.vue component
- Fixed Discover.vue type errors (unused var, type predicate)
- Various UI updates (Apps, Dashboard, Marketplace, Mesh, Web5)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 16:12:01 +00:00
Dorian
e574b6dd18 fix: SameSite=Strict → Lax for session cookies (fixes iframe fetch)
SameSite=Strict prevents cookies from being sent when iframe content
(like the LND UI at /app/lnd/) fetches endpoints on the parent origin
(/lnd-connect-info). Lax still protects against CSRF on POST requests
but allows same-site GET navigations and fetches from iframes.

This was the root cause of "Failed to fetch" on LND Connect.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 15:30:58 +00:00
Dorian
6033199864 fix: remove backend auth check on /lnd-connect-info (nginx validates session)
Backend is bound to 127.0.0.1 — only nginx can reach it.
Nginx checks cookie_session presence. Adding backend auth broke
the LND UI iframe fetch because the session validation was too
strict for the cross-proxy cookie flow. The nginx layer is the
correct auth gate for this endpoint.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 15:20:44 +00:00
Dorian
aabeb2e679 security: add is_authenticated check to /lnd-connect-info backend handler (AUTH-011)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 14:58:16 +00:00
Dorian
f292ebf63e fix: ElectrumX status uses headers.subscribe (returns height correctly)
The previous blockchain.numblocks.subscribe call returned data in a
format the parser couldn't extract height from. headers.subscribe
returns {height: N, hex: "..."} which is properly parsed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 13:51:03 +00:00
Dorian
1a74a930f7 security+feat: v1.3.0 — pentest remediation, container reliability, UI overhaul
Security (33 pentest findings addressed):
- CRITICAL: backend binds 127.0.0.1, path traversal in tor.rs/dwn fixed
- HIGH: federation requires signatures, XSS login redirect, RBAC viewer restricted
- HIGH: tar slip prevention, S3 SSRF validation, backup ID validation
- MEDIUM: remember-me random secret, TOTP session rotation, password re-auth
- LOW: CSP unsafe-inline removed, CORS dev-only, onion/webhook validation

Container reliability:
- Memory limits on all 37 containers (OOM prevention)
- Exited vs stopped state distinction with health-aware status badges
- Crash recovery coordination (no more restart cascade)
- User-stopped tracking survives reboots
- Tiered boot recovery (databases → core → services → apps)

UI:
- Wallet TransactionsModal, health-aware app status badges
- Restart button on containers, exited/crashed red state
- Mesh view overhaul, glass button updates, BaseModal/ToggleSwitch
- Apps sticky header removed, dev faucet, mutable mock wallet

Infrastructure:
- LND REST port 8080 exposed over Tor (LND Connect fix)
- Nginx cookie_session fix, deploy script Tor config updated
- Dev environment: podman auto-start, boot mode simulation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 12:44:31 +00:00
Dorian
aad98dec08 fix: telemetry reporter field name cpu_percent, add type annotation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 23:53:17 +00:00
Dorian
41ab499698 feat(TASK-12): periodic telemetry reporter — 15min interval, collector POST
Background task spawned on server startup: every 15 minutes, checks opt-in
status, builds anonymous health report (node ID hash, version, uptime,
CPU/RAM/disk %, container states, recent alerts), saves to disk, and POSTs
to TELEMETRY_COLLECTOR_URL env var if configured. Non-fatal on failure.

Fixed FiredAlert field references (kind not rule_type, timestamp not
fired_at) in both monitoring and analytics modules.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 23:36:57 +00:00
Dorian
165972e75c feat(TASK-12): beta telemetry — report endpoint + settings toggle
Backend: telemetry.report RPC builds anonymous health report with node ID
(SHA-256 hash of pubkey, truncated), version, uptime, container states,
CPU/RAM, federation peers, and recent alerts. Saves latest report to disk.
Requires analytics opt-in (existing analytics.enable/disable flow).

Frontend: "Beta Telemetry" section in Settings with enable/disable toggle.
Shows what data is and isn't collected. Mock backend handles all analytics
and telemetry RPCs.

Privacy: No wallet data, no private keys, no DIDs, no IP addresses.
Node identified by truncated hash only.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 23:14:47 +00:00
Dorian
b7edada7fe chore: health endpoint JSON, BETA-PROGRESS updated to ~55%
Health endpoint now returns JSON with version and service status instead
of plain "OK". Updated BETA-PROGRESS.md: BUG-1 done, TASK-8 done (12/12
+ code audit), FEATURE-4 at ~80%, overall at ~55%. Added session #5 log.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 22:57:29 +00:00
Dorian
adcc3fddc7 security: migrate bcrypt→Argon2id, random Bitcoin RPC password
Password hashing migrated from bcrypt to Argon2id (m=64MiB, t=3, p=4).
Transparent upgrade: on successful bcrypt login, re-hashes with Argon2id
and persists. New signups and password changes use Argon2id directly.
Unifies crypto stack — Argon2id was already used for TOTP and backup KDF.

Bitcoin RPC password: no longer falls back to hardcoded "archipelago123".
On first boot, generates a random 32-char hex password from CSPRNG,
saves to /var/lib/archipelago/secrets/bitcoin-rpc-password with 0600
permissions. Existing installs with secrets file are unaffected.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 22:41:23 +00:00
Dorian
7bbd8f889a security: RBAC viewer role, identity label length, error sanitization
- RBAC: Viewer role changed from prefix "system." to explicit allowlist
  of safe read-only methods. Prevents Viewer access to system.factory-reset,
  system.shutdown, system.reboot, system.disk-cleanup.
- identity.create: Name/label param now enforces max 100 chars.
- sanitize_error_message: Changed from contains() to starts_with() for
  prefix matching, preventing internal errors that happen to contain
  user-facing keywords from leaking through.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 22:37:08 +00:00
Dorian
41ff1021ad fix: BUG-1 CSRF, TASK-8 H2/H3/H4, BUG-20/37/40/41 — 7 bugs fixed
BUG-1 (P0): CSRF tokens now HMAC-derived from session token instead of
random — survives backend restarts, eliminates cookie/header race conditions.
Frontend retries 403s as belt-and-suspenders.

TASK-8 H2: federation.peer-joined verifies ed25519 signature on join messages.
TASK-8 H3: federation.peer-address-changed requires signed proof from known peer.
TASK-8 H4: Rust backend default bind 0.0.0.0 → 127.0.0.1 (nginx proxies all).

BUG-20: ElectrumX index estimate string fixed from ~55GB to ~130GB.
BUG-37: App card Start/Stop buttons split into loading vs interactive states
        to prevent WebSocket state flicker during container scans.
BUG-40: Uninstall modal uses Teleport to body with z-[3000] for full overlay.
BUG-41: Uninstalling overlay on card + optimistic store removal.

Updated MASTER_PLAN.md and BETA-PROGRESS.md to reflect all completed work.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 22:05:21 +00:00
Dorian
27f205f38a security(TASK-8): fix 8 pentest findings — C1/C3/H1/M1/M2/L2
CRITICAL:
- C1: /lnd-connect-info now requires session auth, CORS wildcard removed
- C3: DEV_MODE removed from production service file (dev override only)

HIGH:
- H1: node-message endpoint now verifies ed25519 signatures when
  provided, logs warning for unsigned messages

MEDIUM:
- M1: content.add rejects filenames containing ".." (path traversal)
- M2: NIP-07 postMessage responses use specific origin instead of '*'

LOW:
- L2: Onion validation now enforces strict v3 format (56 base32 chars
  + ".onion", exactly 62 chars, no colons)

Previously fixed: C2 (RPC creds generated per-install from secrets)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 19:45:10 +00:00
Dorian
25ad68ac4c fix: BUG-33 CPU threshold, TASK-27 tab icons, TASK-36 iframe errors
- BUG-33: CPU load alert threshold increased from 2x to 4x core count
  (8→16 on 4-core machine) to reduce false alerts during container ops
- TASK-27: Launch buttons for new-tab apps now show external link icon
  (BTCPay, Grafana, PhotoPrism, Portainer, OnlyOffice, etc.)
- TASK-36: Iframe error screen now distinguishes between X-Frame-Options
  blocked vs container not reachable, with appropriate messaging

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 19:24:52 +00:00
Dorian
dd5ab6b10a fix(TASK-26): Rename fedimintd to "Fedimint Guardian"
Added fedimintd to the metadata map with title "Fedimint Guardian"
and description clarifying it's the federation consensus node.
Shares the fedimint.png icon.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 17:56:45 +00:00
Dorian
f54206d231 fix(BUG-20): ElectrumX shows index size instead of "Building..."
When ElectrumX is indexing and can't accept TCP connections, the UI
now shows the actual index size (e.g. "126.9 GB") in the Indexed
Height field instead of a generic "Building..." label. Also shows
the size in the status message for better progress visibility.

Updated estimated full index size from 55GB to 130GB (2026 mainnet).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 17:50:33 +00:00
Dorian
9f90c2cc91 fix: Fedimint Guardian UI on port 8175 (not 8174 API)
Fedimintd serves JSON-RPC API on 8174 and Guardian web UI on 8175.
Updated all port mappings: frontend AppSession, nginx HTTP/HTTPS
proxies, PodmanClient static map.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 17:31:07 +00:00
Dorian
00eebfbb3d fix: import PodmanClient for lan_address_for fallback
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 16:35:12 +00:00
Dorian
a6f2e6743f fix: use PodmanClient::lan_address_for as static fallback for port mapping
Dynamic port extraction from container bindings, falling back to the
static PodmanClient address map for apps without port bindings (e.g.
host-network containers).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 16:32:39 +00:00
Dorian
0c5b7db4a2 fix: dynamic port detection + electrumx sync + rootless infra
Backend:
- Remove most hardcoded port overrides from docker_packages.rs, use
  dynamic port extraction from actual container bindings with fallback
  to static map in PodmanClient
- Fix OnlyOffice (8044), NginxPM (8181), Fedimint (8174) port mappings
- Remove Tailscale fake web UI port (no web UI)
- ElectrumX: detect "Connection reset" as syncing state (not error)

Deploy script:
- Auto-configure sysctl unprivileged_port_start=80 for rootless
- Auto-enable loginctl linger for container persistence
- Auto-enable podman.socket for Portainer

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 16:29:03 +00:00
Dorian
fef7e8cb24 fix: ElectrumX sync detection + rootless podman infrastructure
- ElectrumX status: detect "Connection reset" as syncing (not error)
  by using case-insensitive check on connect/reset/refused
- Deploy script: auto-configure rootless podman prerequisites
  (sysctl unprivileged ports >= 80, loginctl linger, podman socket)
- Marketplace: sort installed apps to bottom of list

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 16:07:09 +00:00
Dorian
cc8a6fd4d8 fix: container state mapping + marketplace install aliases
- Created containers now show as "stopped" not "starting" (fixes
  ollama/tailscale perpetual "starting" state)
- Comprehensive INSTALLED_ALIASES map: fedimint, electrumx, grafana,
  jellyfin, vaultwarden, searxng, homeassistant, photoprism, lnd,
  filebrowser, tailscale, ollama — prevents marketplace showing
  "Install" for already-installed containers

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 15:18:43 +00:00
Dorian
0c8dd582fa fix: rootless podman scanning — relax namespace/syscall restrictions
RestrictNamespaces and SystemCallFilter block rootless podman from
creating user namespaces needed for container isolation. Removed these
along with RestrictSUIDSGID (implied by NoNewPrivileges). ProtectHome
set to no (rootless podman needs ~/.local/share/containers writable).

Remaining active protections: NoNewPrivileges, ProtectSystem=strict,
ReadWritePaths, RestrictAddressFamilies, MemoryDenyWriteExecute,
RestrictRealtime, SystemCallArchitectures=native.

Also reduced initial scan delay from 15s to 3s for faster container
visibility after boot, and removed Ollama from auto-deploy.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 14:22:00 +00:00
Dorian
870ff095d8 feat: rootless podman, session hardening, boot stability, sidebar fix
Rootless podman migration (TASK-11):
- Remove sudo from all podman calls in PodmanClient + 8 backend files
- Remove sudo from all podman/docker calls in deploy script
- Restore full systemd security hardening: NoNewPrivileges,
  RestrictAddressFamilies, MemoryDenyWriteExecute, RestrictRealtime,
  RestrictNamespaces, RestrictSUIDSGID, SystemCallFilter, ProtectSystem=strict
- Enable loginctl linger for rootless container persistence
- Remove Ollama from auto-deploy (marketplace-only)

Session & auth hardening:
- Increase MAX_CONCURRENT_SESSIONS 20→50 (prevents eviction storms)
- Debounced 401 redirect in rpc-client.ts (prevents redirect storms)

Boot stability:
- optimize-debian.sh: adds chrony, swap, removes policy-rc.d
- deploy script: pre-restart chrony + swap setup
- ISO build: chrony package, swap file creation
- BootScreen: no longer clears localStorage (prevents splash replay)
- RootRedirect: sole owner of localStorage clearing on server ready

UI fixes:
- Sidebar opacity default changed from 0→visible (fixes missing sidebar
  after page-persistence login without entrance animation)
- Console.log/error wrapped in import.meta.env.DEV guards
- Remove unused route import from RootRedirect

Beta tracking:
- CLAUDE.md: beta freeze protocol added
- MASTER_PLAN.md: TASK-11, TASK-17, phase structure
- BETA-PROGRESS.md: initial tracking doc
- Tagged v1.2.0-alpha.1 as pre-rootless baseline

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 13:53:27 +00:00
Dorian
6a56d4972d fix: prevent install buttons showing before first container scan
Added containers_scanned flag to StatusInfo in the data model. Starts
false, set to true after the first Podman scan completes (~15s after
boot). Marketplace now shows a shimmer "Checking..." indicator on app
buttons until the scan finishes, preventing users from accidentally
re-installing apps that are already present but not yet enumerated.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 11:46:38 +00:00
Dorian
3187d1ad28 fix: remove doubled -alpha-alpha version suffix
CARGO_PKG_VERSION already contains -alpha from Cargo.toml, so the
format!("{}-alpha", ...) was producing 1.2.0-alpha-alpha. Use the
Cargo version directly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 11:28:28 +00:00
Dorian
7278397209 fix: bulletproof mesh serial connection — PrivateDevices, auto-detect fallback, backoff
Root cause: systemd PrivateDevices=yes hid /dev/ttyUSB* from the service,
preventing .198 from connecting to its Heltec V3 after the security hardening.

Changes:
- Set PrivateDevices=no in systemd service (serial access needs physical devices;
  other hardening layers remain: NoNewPrivileges, ProtectSystem, RestrictNamespaces)
- Add SupplementaryGroups=dialout for explicit serial permissions
- Add fallback auto-detect when configured serial path fails to open
- Add exponential backoff on reconnect (5s→60s cap) to reduce log spam
- Add pre-open device existence check with actionable error messages
- Add udev rule (99-mesh-radio.rules) for stable /dev/mesh-radio symlink
- Add /dev/mesh-radio to serial candidate list (checked first)
- Add Connect button per detected device in Mesh UI
- Deploy udev rule to both servers and ISO build
- Fix FEDI_HASH unbound variable in deploy script
- Fix deploy binary step to handle hung service stop gracefully

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 10:50:13 +00:00
Dorian
d341585bed fix: Phase 8 — mesh hardening: atomic writes, unwrap elimination, GPS opt-out
- Ratchet state: atomic write via tmp + rename to prevent corruption on crash
- Block header decode: replaced .unwrap() with proper error handling on
  untrusted network data (was a crash vector from malicious peers)
- Shutdown channel: replaced .unwrap() with .ok_or_else() error propagation
- Dead man's switch GPS: default changed to opt-out (auto_include_gps=false)
- Alert signature verification: already covered by Phase 4 envelope checks

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 01:04:19 +00:00
Dorian
36a33f3575 fix: Phase 7 — key zeroization, OsRng, checked arithmetic, TOTP rate limits
- SecretsManager: raw key stored in Zeroizing<[u8; 32]>, auto-zeroed on drop
- SecretsManager: replaced thread_rng with OsRng (CSPRNG) for nonces
- Remember-me secret: derived from machine-id via SHA-256 (deterministic, no
  plaintext key storage)
- Bitcoin ecash balance: uses checked_add with u64::MAX saturation on overflow
- TOTP setup/confirm: added to EndpointRateLimiter (3 and 5 per 5min)
- AppId validation and Tor service name validation already existed

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 01:00:57 +00:00
Dorian
5853b6a065 feat: Phase 4 — mesh authentication, envelope signature verification, TX validation
- Identity announcements: verify Ed25519 key validity and X25519 consistency
- Envelope signatures: verify Ed25519 signatures on signed messages, drop invalid
- Block header validation: height range, hash length, timestamp sanity checks
- TX relay validation: hex validity, size bounds, version check before broadcast
- Rate limiter struct for per-peer relay operations
- Message sequence number field (seq) added to TypedEnvelope for ordering

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 00:49:38 +00:00
Dorian
dd8e8e9e4f fix: Phase 3 — command injection, unwrap/expect panics, unsigned image acceptance
- VPN key gen: replaced sh -c with format string (command injection) with
  safe stdin piping to wg pubkey
- Secrets manager: replaced .unwrap() on path.parent() with proper error
- Tor proxy: replaced .expect("valid proxy") with continue on error
- Image verifier: added require_signatures flag, strict mode rejects
  unsigned images and missing cosign binary

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 00:45:15 +00:00
Dorian
c005dc9a22 feat: Phase 2 — systemd sandboxing, Bitcoin RPC localhost binding, Tailscale deprivilege
- Service runs as unprivileged `archipelago` user instead of root
- Added systemd sandboxing: ProtectSystem=strict, NoNewPrivileges, PrivateTmp,
  MemoryDenyWriteExecute, RestrictNamespaces, SystemCallFilter
- Bitcoin RPC rpcallowip restricted to localhost + Podman subnet (10.88.0.0/16)
- Tailscale container: removed --privileged, uses cap-drop ALL + cap-add NET_ADMIN/NET_RAW

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 00:42:29 +00:00
Dorian
809a976960 feat: Phase 1 — per-installation credential generation, eliminate hardcoded passwords
Generate unique random passwords at first boot for Bitcoin RPC, all database
services (mempool, btcpay, immich, penpot, mysql-root), and Fedimint gateway.
Credentials stored in /var/lib/archipelago/secrets/ with 600 permissions.

Scripts: first-boot-containers.sh, deploy-to-target.sh, deploy-bitcoin-knots.sh,
container-doctor.sh all read from secrets files instead of hardcoded values.

Rust backend: new bitcoin_rpc module reads password from secrets file, env var,
or dev fallback. All .basic_auth() calls and container config strings now use
the shared credential reader instead of hardcoded "archipelago123".

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 00:39:52 +00:00
Dorian
f273816405 feat: v1.2.0-alpha — E2E encrypted mesh relay, steganography, relay status polling
Phase 5 mesh networking:
- E2E encrypted TX relay (X25519 + ChaCha20-Poly1305) — non-Archy nodes
  relay encrypted blobs transparently via Meshcore native routing
- Steganographic encoding modes (WeatherStation, SensorNetwork) — traffic
  looks like sensor data on the wire, 0xAA marker, configurable per-node
- Pre-flight Bitcoin Core health check on relay node — specific error codes
  (bitcoin_unreachable, bitcoin_syncing, tx_rejected) instead of generic fails
- mesh.relay-status RPC endpoint — frontend polls for relay result every 3s
- On-Chain / Lightning tabs in Off-Grid Bitcoin panel
- Archy Peers vs Mesh Broadcast relay mode selector
- Mesh view fills viewport (no page scroll), internal panel scrolling
- Version bump to 1.2.0-alpha

Also includes: deploy hardening, container fixes, IndeedHub updates,
boot screen, dashboard improvements, MASTER_PLAN task tracking

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 23:56:37 +00:00
Dorian
d1ac098edb feat: Phase 4 — off-grid Bitcoin relay, block headers, dead man's switch
- Typed message dispatch in listener (BlockHeader, TxRelay, LightningRelay, Alert, TxConfirmation)
- Base64 encoding for binary payloads over LoRa (fixes NUL byte truncation)
- Compact block header announcements (88 bytes, fits 160-byte LoRa limit)
- Block header announcer: internet nodes auto-announce new blocks to Archy peers
- TX relay: mesh-only nodes can broadcast transactions via internet-connected peers
- Confirmation tracking: relay node monitors 1/3, 2/3, 3/3 confirmations, sends updates back
- Dead man's switch background task with configurable interval and signed alert broadcast
- 6 new RPC endpoints: relay-tx, block-headers, relay-lightning, deadman-status/configure/checkin
- lnd.create-raw-tx: create signed TX without broadcasting (for mesh relay)
- Web5 wallet: offline detection + "Send via mesh?" prompt with auto relay + confirmation polling
- Mesh.vue: Off-Grid Bitcoin tab, Dead Man tab, Send Bitcoin/Lightning buttons
- TX/Lightning relay sends only to Archy peers (not broadcast to all devices)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 15:51:56 +00:00
Dorian
c6f1894e10 feat: Phase 3-4 Weeks 5+6 — off-grid Bitcoin ops + emergency alert system
Bitcoin relay (mesh/bitcoin_relay.rs):
- BlockHeaderCache: stores latest block headers from internet peers for SPV
- RelayTracker: tracks in-flight TX and Lightning relay requests
- Builder functions: block header announcements (Ed25519 signed),
  TX relay request/response, Lightning invoice relay/response
- All amounts as u64 sats, never float
- 4 unit tests

Emergency alerts (mesh/alerts.rs):
- AlertConfig: dead man switch settings, GPS, emergency contacts
- DeadManSwitch: background timer, auto-trigger after configurable interval
  (default 6h), signed alert broadcast with GPS coordinates
- check_in() resets timer, is_triggered() checks elapsed time
- GPS as integer microdegrees (Coordinate type from message_types)
- Disk persistence for config
- 4 unit tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 02:26:07 +00:00