Bakes the FIPS (Free Internetworking Peering System) mesh daemon into
the node stack, supervised by archipelago alongside Tor. Runs as a
system service, identity derives from the same BIP-39 master seed, and
user-triggered updates track upstream main.
Identity
seed.rs: new HKDF label archipelago/fips/secp256k1/v1 → dedicated
secp256k1 key, distinct from the Nostr-node key for crypto isolation
but still seed-recoverable
identity.rs: writes fips_key[.pub] to /data/identity on onboarding,
chmod 0600; fips_key_exists / load_fips_keys / fips_npub accessors
Transport
TransportKind::Fips=3 inserted between LAN and Tor (Tor bumps to 4)
→ router prefers FIPS over Tor for all peer traffic
PeerRecord gains fips_npub + last_fips fields (serde(default) for
backward-compat with older nodes)
transport/fips.rs: NodeTransport stub, reports unavailable until the
daemon is live so router falls through to Tor cleanly
Federation invites
FederatedNode and FederationInvite carry optional fips_npub
create_invite / accept_invite / peer-joined callback thread it end
to end; signature domain deliberately unchanged — FIPS Noise does
its own session auth, so the unsigned hint only affects path
selection
crate::fips
config.rs: renders /etc/fips/fips.yaml and sudo-installs key material
service.rs: systemctl status/activate/restart/mask wrappers
update.rs: GitHub API check against upstream main; apply stubbed
until per-commit .deb artefact source is decided
RPC + dashboard
fips.status / fips.check-update / fips.apply-update / fips.install /
fips.restart registered in dispatcher
HomeNetworkCard.vue shipped standalone (unmounted — place in Home.vue
when ready); shows state pill, version, FIPS npub, update button,
activate button when key is present but service is down
ISO + systemd
archipelago-fips.service: conditional on key presence, masked by
default — backend unmasks after onboarding writes the key
build-auto-installer-iso.sh: multi-stage Dockerfile builds the FIPS
.deb from jmcorgan/fips main (fail-loud), COPYs it into rootfs, apt
installs it so trixie resolves deps; unit copied + masked
Version bump: 1.3.5 → 1.4.0
Tests: 33 new/updated passing (seed, identity, transport, federation,
fips module, transport::fips).
Known gaps: fips.apply-update returns a clear stub error until
upstream publishes per-commit .deb artefacts; HomeNetworkCard is not
mounted in Home.vue by default.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The .github/workflows/ci.yml Rust job runs cargo fmt --check, clippy
with -D warnings, and tests. All three were failing. This commit:
- Applies rustfmt across the tree (the bulk of the diff — untouched
since the last toolchain bump, so a wide sweep was unavoidable).
- Fixes the correctness-level clippy errors:
container/bitcoin_simulator.rs wildcard-in-or-pattern
container/manifest.rs from_str rename to parse (reserved name)
container/podman_client.rs .get(0) -> .first()
container/runtime.rs manual += collapse
archipelago/src/constants.rs doc-comment → module-doc
api/rpc/package/install.rs stray /// comment above a non-item
container/docker_packages.rs redundant field init
streaming/advertisement.rs missing Metric import in tests
tests/orchestration_tests.rs `vec!` in non-Vec contexts
mesh/listener/dispatch.rs unused store_plain_message import
api/rpc/tor/mod.rs and mesh/steganography.rs: push-after-new → vec!
- Quiets wide legacy surfaces with crate-level allows in main.rs for
stylistic lints (too_many_arguments, type_complexity, doc indent,
enum variant prefix, wildcard-in-or, assertions-on-constants,
drop_non_drop, unused_io_amount, ptr_arg) — these fired in dozens
of places with no correctness payoff and have been churning every
toolchain bump.
- Tags intentional-dead-code helpers: wallet/ and streaming/ modules
are WIP, mesh::send_chunked_payload and DM_V1_MARKER are kept for
rollback compatibility, vpn::get_nostr_vpn_status is surface-area
for a not-yet-landed RPC.
cargo fmt --check, cargo clippy --all-targets --all-features
-- -D warnings, and cargo test --all-features now all pass locally.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bundles the Phase 2b/3/4/5 work that accumulated across prior sessions
and the new attachment chunking router from this session. Everything
ships in one shot so the full mesh surface stays coherent on-wire.
Telegram primitives (variants 13–18, 20–22):
- Reply / Reaction / ReadReceipt / Forward / Edit / Delete
- Presence heartbeat + last-seen tracking
- ChannelInvite + ContactCard payload types
- MessageKey (sender_pubkey, sender_seq) as cross-transport identity
- Action menu, reply banner, edit banner, tombstones, (edited) marker
- Debounced auto-read-receipts on scroll + message arrival
Activated prototypes (Phase 4):
- PsbtHash send RPC
- Contacts CRUD (in-memory alias/notes/pinned/blocked)
- Outbox 📤 badge, rotate-prekeys button
- Chunked send fallback (MCIIXXTT framing) as auto-failover inside
send_typed_wire when a typed wire exceeds the LoRa per-frame budget
Unified inbox (Phase 1):
- conversations.list + conversations.messages RPCs (UI collapse deferred)
Attachment transport router (new this session):
- ContentInline variant 23 + ContentInlinePayload carrying file bytes
directly in the envelope for small files with no Tor path
- mesh.send-content-inline RPC — mirrors to local BlobStore, rides
send_typed_wire which auto-chunks over MCIIXXTT framing (~2.3 KB cap)
- mesh.transport-advice RPC as single source of truth for tier
decisions: auto-mesh / choose / tor-only / impossible
- Receive arm writes inline bytes to local BlobStore so the existing
content_ref card renderer handles both transports uniformly
- MeshState.blob_store field + order-independent propagation from
RpcHandler::set_blob_store / set_mesh_service
- Frontend handleAttachFile calls advice first, branches into silent
auto-send, transport-chooser modal, Tor-only path, or red error
- Transport modal with 📡 mesh / 🧅 Tor options + ETA + disabled
state when peer has no Tor reachability
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds attachment sharing over the mesh: a ContentRef envelope (variant 19)
carries the blob CID, size, mime, optional thumb/caption, and a per-peer
HMAC capability URL so the recipient fetches the full blob out-of-band via
`GET {sender_onion}/blob/{cid}?cap=..&exp=..&peer=..`. BlobStore is shared
from ApiHandler into RpcHandler so mesh.send-content and mesh.fetch-content
(reqwest via TOR_SOCKS_PROXY) hit the same store and cap_key.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cashu ecash protocol (BDHKE blind signatures, cashuA token format,
mint HTTP client) replacing the stub wallet. TollGate-inspired streaming
data payment system with step-based pricing (bytes/time/requests),
session management with incremental top-ups, usage metering, and
Nostr kind 10021 service advertisements.
13 new streaming.* RPC endpoints. Content server now verifies real
Cashu tokens. Profits tracking includes streaming revenue.
Frontend: GlobalAudioPlayer (persistent bottom bar across all pages),
video lightbox with full controls, audio in MediaLightbox, free file
previews (no blur), paid 10% audio/video previews, separated play
vs download buttons in PeerFiles.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace fragmented random key generation with a single 24-word BIP-39
mnemonic that deterministically derives all node keys: Ed25519 (DID),
secp256k1 (Nostr/Bitcoin), BIP-84 xprv (Bitcoin Core), and LND aezeed
entropy. New onboarding flow: seed generate → word verification → identity
naming. Restore path enabled via 24-word entry. Includes seed RPC handlers,
mock backend support, LND/Bitcoin Core wallet-from-seed integration, and
UI polish across settings and discover views.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- fix: login disconnect — verify session before WebSocket connect
- fix: 403 on app install — distinguish CSRF vs RBAC errors, only retry CSRF
- fix: health monitor now watches ALL containers (removed skip list for
backend services like nbxplorer, databases, UI containers)
- fix: server.get-state added to CSRF-exempt list (read-only)
- fix: ISO build includes container-specs.sh and lib/common.sh in rootfs
so reconcile actually works on fresh installs
- fix: gamepad nav — improved Server tab zone nav, focus styles, autofocus
- chore: move L484 web-only apps to Services tab
- chore: install store for cross-view install tracking
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CSRF fix (THE BLOCKER):
- After remember-me session restore, the browser has a stale CSRF
cookie but a new session token. ALL subsequent RPC calls return 403.
- Fix: exempt read-only polling methods (node-messages-received,
server.echo, system.stats, tor.status, etc.) from CSRF validation.
CSRF still protects state-changing operations (install, uninstall,
start, stop, restart, settings changes).
Reboot fix:
- The separate /tmp/archipelago-reboot.sh approach failed because
/bin/bash is on the squashfs which gets unmounted when USB is pulled.
- Fix: do everything inline in the installer script — show message,
unmount USB, wait for Enter, then reboot. Use sysrq-trigger first
(kernel-level, doesn't need userspace binaries).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Secure flag on session cookies broke HTTP LAN access — browsers refuse
to send Secure cookies over plain HTTP, causing 401 redirect loop.
Fix: check X-Forwarded-Proto header. Only set Secure when request came
over HTTPS. HTTP on LAN works, HTTPS still gets Secure cookies.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- R6: Convert 6 std::fs calls in session.rs to tokio::fs async
- R7: Convert std::fs::read_to_string in docker_packages.rs to async
- R8: Convert 3 std::fs calls in port_allocator.rs to async, switch to tokio::sync::Mutex
- R9+R10+R11: Fix blocking I/O in node_message.rs and nostr_discovery.rs
- R12: Convert electrs_status.rs from sync TCP to async tokio::net with 5s timeouts
- R4+R5: Spawn periodic cleanup tasks for endpoint and login rate limiters
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- R1: Add health RPC endpoint with crash recovery status, uptime, and version
- R2: Wrap all 5 Nostr client.connect() calls in 10s timeout
- R3: Make backup restore atomic with staging dir and rollback on failure
- I1: Add rate limiting, body size, and proxy timeouts to unauthenticated nginx endpoints
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Messages persisted to disk (messages.json) — survive restarts
- Sent messages stored on backend via node-store-sent RPC
- Message deduplication (same pubkey + message within 30s)
- Max 200 messages in circular buffer
- Direction field (sent/received) for proper UI display
- Container doctor: prefer system Tor, remove archy-tor container
- Deploy torrc generator: read from tor-config/services.json,
web apps map port 80→local port for clean .onion URLs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Major changes:
- Full Tor hidden service management via systemd path unit pattern
(tor-helper.sh + archipelago-tor-helper.path/service) — respects
NoNewPrivileges=yes, no sudo needed from backend
- Container doctor: prefer system Tor over container, remove archy-tor
- Deploy script: fix torrc generation (read correct services.json path),
web apps map port 80→local port, enable both tor and tor@default
- Federation: server rename pushes name to peers via background sync
- Server name: fix root-owned file, optimistic store update
- Mesh: local echo for sent messages, sendingArch loading state
- Web5: Message button → Mesh redirect, node name lookup in messages
- PeerFiles: show DID not onion in header
- Connected Nodes: flex-1 instead of fixed max-h
- Toast notifications route to Mesh
- Deploy script: fix single-quote syntax in SSH block
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- 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>
SameSite=Strict prevents cookies from being sent when iframe content
(like the LND UI at /app/lnd/) fetches endpoints on the parent origin
(/lnd-connect-info). Lax still protects against CSRF on POST requests
but allows same-site GET navigations and fetches from iframes.
This was the root cause of "Failed to fetch" on LND Connect.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Backend: 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>
- 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>
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>
Backend (6 new RPC endpoints):
- mesh.send-invoice: create Lightning invoice, send bolt11 to mesh peer
- mesh.send-coordinate: send GPS coordinates (integer microdegrees)
- mesh.send-alert: send signed emergency alert (with optional GPS)
- mesh.outbox: list pending store-and-forward messages
- mesh.session-status: get Double Ratchet session info per peer
- mesh.rotate-prekeys: force X3DH prekey rotation
Mock backend: matching dev mode responses for all 6 new endpoints
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- system.factory-reset RPC: wipes user data, preserves images/node_key
- Factory Reset button in Settings with confirmation modal
- backup.restore-identity RPC: decrypts and restores DID key
- Restore from Backup panel in OnboardingIntro first screen
- Auto-create default identity with Nostr key on boot if none exist
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Check user role against method permissions before dispatch.
All current users default to Admin, laying groundwork for multi-user.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New RPC endpoints:
- analytics.get-status: Check if analytics opted in
- analytics.enable/disable: Toggle opt-in
- analytics.get-snapshot: Anonymous aggregate data (version, app count,
hardware tier, CPU cores, RAM, federation peers)
No personal data: no DIDs, no IPs, no secrets. Strictly opt-in.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New RPC endpoints:
- backup.upload-s3: Upload encrypted backup to any S3-compatible endpoint
- backup.download-s3: Download backup from S3 to local storage
Supports MinIO, Backblaze B2, Wasabi via basic auth + S3 API.
Backups are AES-256-GCM encrypted before upload.
Rate-limited at 3 requests per 10 minutes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- PERF-01: Move crash recovery to background tokio task so health
endpoint is available immediately on startup
- PERF-04: Add ResponseCache with 5s TTL for system.stats and
federation.list-nodes. Reduces CPU for frequent polling.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Added node.nostr-sign RPC that uses the node-level Nostr key (matching
getPublicKey), fixing pubkey mismatch where identity.nostr-sign used a
different key. Updated appLauncher to call node.nostr-sign. Added
nostr_sign_hash() to nostr_discovery.rs. Created test-nip07.sh with
11 automated checks (INSTALL-02).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- New PeerFiles.vue view shows federated peers and their shared catalogs
- Peer Files card in Cloud.vue shows when federation peers exist
- New content.download-peer RPC fetches content from peer via Tor
- Route: /dashboard/cloud/peers
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add tor-hostnames fallback for reading onion addresses when system Tor
owns hidden_service directories (permissions 700)
- Exempt federation.peer-joined, federation.get-state, and
federation.peer-address-changed from auth/CSRF (inter-node RPC)
- Set up system Tor with AppArmor overrides on archipelago-2 and 3
- All 3 servers federated and syncing successfully
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
After rotation, spawns background task that publishes updated .onion to
Nostr relays and sends federation.peer-address-changed RPC to all peers
over Tor. Peers update their nodes.json with the new address.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
tor.rotate-service: renames hidden service dir, restarts Tor, waits
for new hostname. Old dir kept for 24h transition.
tor.cleanup-rotated: removes expired old service directories.
tor.toggle-app: enable/disable Tor access per app with service dir
management and container restart.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Backend: identity.nostr-encrypt-nip04, identity.nostr-decrypt-nip04,
identity.nostr-encrypt-nip44, identity.nostr-decrypt-nip44 endpoints
with auto-resolve to default identity. Frontend: appLauncher routes
nip04.* and nip44.* postMessage calls to backend RPC.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 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>
Webhook module with HTTP delivery, HMAC-SHA256 signing, and event
filtering. RPC handlers for get-config, configure, and test endpoints.
Settings page gains webhook configuration section with URL, secret,
event toggles, and test button.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implements monitoring/collector.rs that collects per-container CPU/RAM/network/disk,
system-wide metrics, RPC latency, and WebSocket connection count every 60 seconds.
Data stored in dual ring buffers: 1-min resolution (24h) and 15-min resolution (7d).
Three new RPC endpoints: monitoring.current, monitoring.history, monitoring.containers.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>