archy/loop/testing.md
Dorian 0cf71c4115 fix: zero-amount invoices, identity.verify DID extraction, tor service permissions
- Allow zero-amount Lightning invoices (BOLT11 "any amount") by changing
  validation from amount_sats < 1 to amount_sats < 0
- identity.verify now extracts pubkey directly from did:key format instead
  of requiring the DID to belong to a local identity
- tor.create-service writes config to data_dir/tor-config/ instead of
  /var/lib/archipelago/tor/ (owned by debian-tor, not archipelago user)
- Add E2E test script (scripts/run-e2e-tests.sh) covering 47 RPC endpoints
- Add testing plan with results (loop/testing.md)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 09:53:36 +00:00

37 KiB

Overnight Testing Plan — Archipelago Full Feature Verification

Goal: Systematically test every functional feature of Archipelago on the live dev server (192.168.1.228). When a test fails, diagnose the issue, fix it, deploy, and re-test until it passes. Maintain a tick list of every feature verified.

Method: For each feature group, run tests against the live server via RPC. On failure: read relevant source, fix the bug, deploy with ./scripts/deploy-to-target.sh --live, and re-test. Loop until all tests pass before moving to the next group.

Server: 192.168.1.228 | Password: password123 SSH: ssh -i ~/.ssh/archipelago-deploy archipelago@192.168.1.228

Latest Run: 2026-03-09 — 46/47 PASSED (1 skipped)

Automated E2E Results (via scripts/run-e2e-tests.sh on server):

  • Auth: PASS
  • Identity (list/create/sign/verify/delete/nostr-key/nostr-sign): ALL PASS
  • Names (register/resolve/remove): ALL PASS
  • Credentials (list/issue): ALL PASS
  • Lightning (getinfo/listchannels/newaddress/createinvoice 0+1000): ALL PASS
  • Tor (list/create/delete/get-onion): ALL PASS
  • Wallet ecash (balance/history/profits): ALL PASS
  • Content (list-mine): PASS
  • Network (visibility/diagnostics/requests/peers): ALL PASS
  • Nostr relays (list/stats): ALL PASS
  • DWN (status): PASS
  • Update (status/check): ALL PASS
  • Router (info/forwards): ALL PASS
  • HTTP (/health, /electrs-status): ALL PASS
  • Containers (list): PASS; container-status: FAIL (dev-mode orchestrator issue)

Bugs Fixed:

  1. lnd.createinvoice rejected zero-amount invoices (BOLT11 "any amount") — fixed validation
  2. identity.verify required local identity lookup — now extracts pubkey from did:key directly
  3. tor.create-service failed with permissions error — now writes to tor-config/ not tor/ (owned by debian-tor)

Pre-Flight Checks

  • PRE-01 — Verify server is reachable: curl -s http://192.168.1.228/health returns 200
  • PRE-02 — Verify web UI loads: curl -s http://192.168.1.228/ returns HTML containing "Archipelago"
  • PRE-03 — Verify RPC authentication works: call auth.login with password123, confirm session cookie set
  • PRE-04 — Verify WebSocket connects: curl -s -N -H "Upgrade: websocket" http://192.168.1.228/ws/db responds with upgrade
  • PRE-05 — Verify disk space: SSH and check df -h / has >5GB free. If not, prune old container images with podman image prune -af
  • PRE-06 — Verify backend service running: SSH and check systemctl is-active archipelago returns active

Group 1: Bitcoin Knots — Core Node

Priority: CRITICAL — everything depends on this

  • BTC-01 — Verify bitcoin-knots container exists: call container-list RPC, confirm bitcoin-knots in response
  • BTC-02 — Verify bitcoin-knots container is running: status should be "running" in container list
  • BTC-03 — If not running, start it: call package.start with {"id":"bitcoin-knots"}. Wait up to 60s for startup
  • BTC-04 — Verify Bitcoin RPC responds: call bitcoin.getinfo RPC. Should return block_height, sync_progress, chain
  • BTC-05 — Verify blockchain sync progress: sync_progress or verification_progress should be > 0.99 (99%+). If still syncing, log progress and continue (non-blocking)
  • BTC-06 — Verify Bitcoin is on mainnet: chain should be "main" or "mainnet"
  • BTC-07 — Verify mempool data: mempool_size and mempool_tx_count should be numeric values >= 0
  • BTC-08 — Verify Bitcoin UI loads: curl -s http://192.168.1.228/app/bitcoin-knots/ returns HTTP 200 or redirect
  • BTC-09 — Verify Bitcoin port 8332 is proxied: check nginx proxy at /app/bitcoin-knots/ resolves
  • BTC-10 — Verify bitcoin data directory exists on server: SSH check /var/lib/archipelago/bitcoin/ exists

Fix strategy: If Bitcoin container missing, check docker_packages.rs metadata and package.rs config. If RPC fails, check macaroon paths and bitcoin.conf. If container won't start, check logs with container-logs RPC.


Group 2: LND — Lightning Network Daemon

Priority: CRITICAL — wallet, channels, payments depend on this

  • LND-01 — Verify lnd container exists in container list
  • LND-02 — Verify lnd container is running
  • LND-03 — If not running, start it: call package.start with {"id":"lnd"}. Wait up to 90s (LND needs Bitcoin synced)
  • LND-04 — Verify LND connects to Bitcoin: call lnd.getinfo RPC. Should return synced_to_chain, block_height
  • LND-05 — Verify LND is synced: synced_to_chain should be true. If false, log and wait up to 5 min
  • LND-06 — Verify LND alias is set: alias field should be non-empty
  • LND-07 — Verify LND channel count: num_active_channels should be numeric (0 is OK for fresh install)
  • LND-08 — Verify LND peer count: num_peers should be numeric
  • LND-09 — Verify LND on-chain balance accessible: balance_sats should be numeric >= 0
  • LND-10 — Verify LND channel balance accessible: channel_balance_sats should be numeric >= 0
  • LND-11 — Verify LND REST API proxied: check /proxy/lnd/v1/getinfo responds through nginx
  • LND-12 — Verify LND admin macaroon exists on server: SSH check /var/lib/archipelago/lnd/data/chain/bitcoin/mainnet/admin.macaroon
  • LND-13 — Verify LND TLS cert exists: SSH check /var/lib/archipelago/lnd/tls.cert
  • LND-14 — Verify LND UI loads: check port 8081 proxy at /app/lnd/

Fix strategy: If LND can't connect to Bitcoin, verify archy-net bridge exists and both containers are on it. Check LND startup args in get_app_config(). If macaroon missing, LND wallet may need initialization.


Group 3: Bitcoin Wallet — On-Chain (via LND)

Priority: HIGH — core financial feature

  • WAL-01 — Generate new on-chain address: call lnd.newaddress RPC. Should return {"address":"bc1..."} (bech32)
  • WAL-02 — Verify address format: address should start with bc1 (mainnet bech32) or tb1 (testnet)
  • WAL-03 — Verify address is unique: call lnd.newaddress again, confirm different address returned
  • WAL-04 — Verify on-chain balance query: call lnd.getinfo, check balance_sats returns a number
  • WAL-05 — Test send validation (bad address): call lnd.sendcoins with {"addr":"invalid","amount":1000}. Should return error about invalid address
  • WAL-06 — Test send validation (dust amount): call lnd.sendcoins with {"addr":"bc1qvalidaddress","amount":100}. Should return error about minimum 546 sats
  • WAL-07 — Test send validation (zero amount): call lnd.sendcoins with {"addr":"bc1qvalidaddress","amount":0}. Should return error
  • WAL-08 — Verify wallet RPC endpoints exist in handler: grep lnd.newaddress and lnd.sendcoins in RPC router
  • WAL-09 — Verify Web5 view shows wallet section: check Web5.vue renders on-chain balance, send/receive buttons
  • WAL-10 — Verify Web5 wallet "Receive" generates address in UI (frontend check: the RPC is called and address displayed)

Fix strategy: If newaddress fails, check LND wallet status — may need lncli create or lncli unlock. If sendcoins validation wrong, check amount/address validation in lnd.rs. If Web5 view broken, check Web5.vue composables.


Group 4: Lightning Wallet — Invoices & Payments

Priority: HIGH — Lightning is the primary payment rail

  • LN-01 — Create Lightning invoice: call lnd.createinvoice with {"amount_sats":1000,"memo":"test invoice"}. Should return payment_request starting with lnbc
  • LN-02 — Verify invoice format: payment_request should be a valid BOLT11 string (starts with lnbc on mainnet, lntb on testnet)
  • LN-03 — Verify invoice amount: response should include amount_sats: 1000
  • LN-04 — Create zero-amount invoice: call lnd.createinvoice with {"amount_sats":0}. Should succeed (any-amount invoice)
  • LN-05 — Test pay invoice validation (self-pay): call lnd.payinvoice with the invoice from LN-01. Should fail (can't pay own invoice) or succeed if channels exist — either way should not crash
  • LN-06 — Test pay invoice validation (invalid): call lnd.payinvoice with {"payment_request":"invalid"}. Should return error
  • LN-07 — List channels: call lnd.listchannels. Should return {"channels":[],"total_inbound":0,"total_outbound":0} or actual channel data
  • LN-08 — Verify channel data structure: each channel should have chan_id, remote_pubkey, capacity, local_balance, remote_balance, active
  • LN-09 — Test open channel validation (bad pubkey): call lnd.openchannel with {"pubkey":"invalid","amount":50000}. Should return error
  • LN-10 — Test open channel validation (too small): call lnd.openchannel with {"pubkey":"validpubkey","amount":1000}. Should return error about minimum 20000 sats
  • LN-11 — Verify Lightning Channels view renders: check LightningChannels.vue route /dashboard/apps/lnd/channels exists in router
  • LN-12 — Verify Web5 wallet shows Lightning balance: check Web5.vue renders channel_balance_sats

Fix strategy: If createinvoice fails, check LND wallet is unlocked and synced. If listchannels returns wrong format, fix response mapping in lnd.rs. If LightningChannels.vue broken, check the Vue component and its RPC calls.


Group 5: Electrs — Bitcoin Indexer

Priority: HIGH — Mempool depends on this

  • ELX-01 — Verify mempool-electrs container exists in container list
  • ELX-02 — Verify mempool-electrs container is running
  • ELX-03 — If not running, start it (requires Bitcoin running first)
  • ELX-04 — Verify Electrs connects to Bitcoin: check /electrs-status HTTP endpoint returns JSON with sync status
  • ELX-05 — Verify Electrs port 50001 is listening: SSH curl -s http://localhost:50001/ or check via container inspect
  • ELX-06 — Verify Electrs dashboard: check port 50002 responds
  • ELX-07 — Verify dependency enforcement: if Bitcoin is stopped, installing Electrs should fail or warn

Fix strategy: If Electrs can't find Bitcoin, check archy-net connectivity. Check startup args in get_app_config() — should point to bitcoin-knots:8332.


Group 6: Mempool Explorer

Priority: MEDIUM — visualization tool, not critical path

  • MEM-01 — Verify mempool-web (or mempool) container exists
  • MEM-02 — Verify mempool-api container exists
  • MEM-03 — Verify mysql-mempool (or archy-mempool-db) container exists
  • MEM-04 — Verify all three Mempool containers are running
  • MEM-05 — If not running, start in order: mysql → mempool-api → mempool-web
  • MEM-06 — Verify Mempool UI loads: curl -s http://192.168.1.228/app/mempool/ returns HTML
  • MEM-07 — Verify Mempool API responds: check port 8999 via proxy
  • MEM-08 — Verify Mempool connects to Electrs: API should return block data

Fix strategy: If Mempool fails, check all 3 containers are on archy-net. Check environment variables in get_app_config() for database credentials and Electrs connection.


Group 7: Identity System (DIDs + Nostr Dual Identity)

Priority: HIGH — Web5 foundation. Every identity MUST have both a DID and a Nostr keypair.

The identity system creates ed25519 DIDs. Each identity must also get a Nostr keypair (secp256k1) so users can operate in both DID-based (Web5/VC) and Nostr-based (social/relay) ecosystems from every identity.

7A: Core Identity CRUD

  • DID-01 — Get node DID: call node.did RPC. Should return {"did":"did:key:z...","pubkey":"..."}
  • DID-02 — Verify DID format: should start with did:key:z (ed25519 multicodec)
  • DID-03 — List identities: call identity.list. Should return {"identities":[...]}
  • DID-04 — Create identity "Personal": call identity.create with {"name":"Personal","purpose":"personal"}. Should return identity with id, did, pubkey
  • DID-05 — Create identity "Business": call identity.create with {"name":"Business","purpose":"business"}
  • DID-06 — Create identity "Anonymous": call identity.create with {"name":"Anonymous","purpose":"anonymous"}
  • DID-07 — Get identity by ID: call identity.get with the Personal identity ID. Should return full identity object
  • DID-08 — Verify all 3 identities listed: call identity.list, confirm all 3 appear with correct names and purposes

7B: Nostr Keypair for Every Identity

Every DID must also have a Nostr identity so users can sign Nostr events, publish to relays, and interact with the Nostr ecosystem from any of their identities.

  • DID-09 — Create Nostr key for Personal: call identity.create-nostr-key with {"id":"<personal_id>"}. Should return {"nostr_pubkey":"<hex>"}
  • DID-10 — Verify Personal Nostr pubkey format: should be 64-char hex string (secp256k1 x-only pubkey)
  • DID-11 — Create Nostr key for Business: call identity.create-nostr-key with {"id":"<business_id>"}. Should return different Nostr pubkey
  • DID-12 — Verify Business Nostr pubkey is unique: must differ from Personal's Nostr pubkey
  • DID-13 — Create Nostr key for Anonymous: call identity.create-nostr-key with {"id":"<anonymous_id>"}. Should return yet another unique Nostr pubkey
  • DID-14 — Verify all 3 Nostr pubkeys are unique: no two identities share the same Nostr pubkey
  • DID-15 — Verify idempotency: call identity.create-nostr-key again for Personal. Should return the same pubkey (not create a second one) or error gracefully
  • DID-16 — List identities and verify Nostr keys present: call identity.list, each identity should now include nostr_pubkey field

7C: DID Signing & Verification

  • DID-17 — Sign message with Personal DID: call identity.sign with {"id":"<personal_id>","message":"hello world"}. Should return {"signature":"..."}
  • DID-18 — Verify Personal DID signature: call identity.verify with the DID, message, and signature. Should return {"valid":true}
  • DID-19 — Verify bad signature fails: call identity.verify with wrong message. Should return {"valid":false}
  • DID-20 — Sign with Business DID: sign same message with Business identity, verify signature is different from Personal's
  • DID-21 — Cross-identity verification: verify Business signature fails against Personal's DID (different keys)

7D: Nostr Signing & Verification

  • DID-22 — Nostr sign with Personal: call identity.nostr-sign with {"id":"<personal_id>","event_hash":"0000000000000000000000000000000000000000000000000000000000000001"}. Should return Schnorr signature
  • DID-23 — Verify Nostr signature format: should be 128-char hex string (64-byte Schnorr signature)
  • DID-24 — Nostr sign with Business: call identity.nostr-sign with Business identity. Should return different signature (different key)
  • DID-25 — Nostr sign with Anonymous: call identity.nostr-sign with Anonymous identity. Should succeed
  • DID-26 — Verify all 3 Nostr signatures are different: same event hash, 3 different keys = 3 different signatures

7E: Identity Management

  • DID-27 — Set default identity: call identity.set-default with Business identity ID. Should succeed
  • DID-28 — Verify default changed: call identity.list, Business should have is_default: true
  • DID-29 — Switch default to Anonymous: set-default with Anonymous ID, verify it's now default
  • DID-30 — Delete Anonymous identity: call identity.delete with Anonymous ID. Should succeed
  • DID-31 — Verify deletion: call identity.get with deleted ID. Should return error
  • DID-32 — Verify default falls back: after deleting the default identity, another identity should become default
  • DID-33 — Cleanup: delete Business and Personal test identities (only if they're test-created, not the node's original identity)

7F: Frontend Integration

  • DID-34 — Verify Web5 view shows DID: check Web5.vue displays the node's DID with copy button
  • DID-35 — Verify Web5 view shows identity list with Nostr pubkeys alongside DIDs
  • DID-36 — Verify identity picker component exists and shows both DID and Nostr pubkey for each identity
  • DID-37 — Verify onboarding identity step creates both DID and Nostr key for the first identity

Fix strategy: If identity endpoints fail, check identity_manager.rs and identity.rs RPC module. If Nostr key creation fails, check secp256k1 key generation in identity_manager.rs. If identity.list doesn't include nostr_pubkey, the serialization needs updating. If onboarding doesn't create Nostr key, add identity.create-nostr-key call after identity creation in the onboarding flow.


Group 8: Verifiable Credentials

Priority: MEDIUM — depends on Identity system

  • VC-01 — Create a test identity (issuer): call identity.create with {"name":"Issuer"}, then identity.create-nostr-key for it
  • VC-02 — Issue credential: call identity.issue-credential with {"issuer_id":"<issuer_id>","subject_did":"did:key:z...","type":"TestCredential","claims":{"name":"Alice"}}
  • VC-03 — Verify credential: call identity.verify-credential with the credential ID. Should return {"valid":true}
  • VC-04 — List credentials: call identity.list-credentials. Should include the credential from VC-02
  • VC-05 — Filter credentials by DID: call identity.list-credentials with {"did":"did:key:z..."}
  • VC-06 — Revoke credential: call identity.revoke-credential with the credential ID
  • VC-07 — Verify revoked credential: call identity.verify-credential again. Should show status: "revoked" or valid: false
  • VC-08 — Cleanup: delete the test issuer identity

Fix strategy: If credential issuance fails, check credentials.rs module. Verify JSON serialization of claims.


Group 9: Bitcoin Domain Names (NIP-05)

Priority: MEDIUM — depends on Identity + Nostr

  • NAME-01 — List names: call identity.list-names. Should return {"names":[...]}
  • NAME-02 — Register a test name: call identity.register-name with {"name":"testuser","domain":"archipelago.local","identity_id":"<id>","did":"did:key:z...","nostr_pubkey":"<hex>"} — include the Nostr pubkey so the name resolves in both DID and Nostr contexts
  • NAME-03 — Verify name registered: call identity.list-names again, confirm the test name appears with both DID and nostr_pubkey
  • NAME-04 — Resolve name: call identity.resolve-name with {"identifier":"testuser@archipelago.local"}
  • NAME-05 — Link name to different identity: create second identity (with Nostr key), call identity.link-name with new identity ID
  • NAME-06 — Verify name now has new identity's Nostr pubkey after re-link
  • NAME-07 — Remove test name: call identity.remove-name with the name ID
  • NAME-08 — Verify removal: list names again, confirm test name is gone
  • NAME-09 — Cleanup: delete any test identities created

Fix strategy: If name registration fails, check names.rs module. If resolve fails, check NIP-05 HTTP resolution logic. Ensure nostr_pubkey is carried through the name registration.


Group 10: Ecash Wallet (Cashu/Fedimint)

Priority: MEDIUM — depends on Fedimint running

  • ECASH-01 — Check ecash balance: call wallet.ecash-balance. Should return {"balance_sats":0,"token_count":0} or existing balance
  • ECASH-02 — Check ecash history: call wallet.ecash-history. Should return {"transactions":[...]}
  • ECASH-03 — Verify Fedimint container running: check fedimint in container list
  • ECASH-04 — If Fedimint running, test mint: call wallet.ecash-mint with {"amount_sats":100} (may fail if no Lightning funding — log result)
  • ECASH-05 — Test mint validation (too large): call wallet.ecash-mint with {"amount_sats":2000000}. Should error (max 1,000,000)
  • ECASH-06 — Test mint validation (zero): call wallet.ecash-mint with {"amount_sats":0}. Should error
  • ECASH-07 — Test send ecash: call wallet.ecash-send with {"amount_sats":50} (may fail if no balance — log result)
  • ECASH-08 — Test receive ecash validation (bad token): call wallet.ecash-receive with {"token":"invalid"}. Should error
  • ECASH-09 — Verify Web5 view shows ecash balance section

Fix strategy: If ecash endpoints fail, check wallet/ecash.rs. If Fedimint connection fails, check container is on archy-net and port 8174 is reachable internally.


Group 11: Networking Profits

Priority: LOW — display feature

  • PROF-01 — Get networking profits: call wallet.networking-profits. Should return {"total_sats":...,"content_sales_sats":...,"routing_fees_sats":...,"recent":[...]}
  • PROF-02 — Verify profit structure: total_sats should equal content_sales_sats + routing_fees_sats
  • PROF-03 — Verify recent transactions: each item should have source, amount_sats, timestamp, description
  • PROF-04 — Verify Web5 view displays profits card

Fix strategy: If profits endpoint fails, check wallet/profits.rs. It aggregates from ecash history and LND forwarding events.


Group 12: Content Sharing & Monetization

Priority: MEDIUM — core Web5 feature

  • CNT-01 — List my content: call content.list-mine. Should return {"items":[...]}
  • CNT-02 — Add content: call content.add with {"filename":"test-file.txt","mime_type":"text/plain","description":"Test content"}
  • CNT-03 — Verify content listed: call content.list-mine again, confirm test file appears
  • CNT-04 — Set pricing to free: call content.set-pricing with {"id":"<id>","access":"free"}
  • CNT-05 — Set pricing to paid: call content.set-pricing with {"id":"<id>","access":"paid","price_sats":100}
  • CNT-06 — Set pricing to peers only: call content.set-pricing with {"id":"<id>","access":"peers_only"}
  • CNT-07 — Set availability to all peers: call content.set-availability with {"id":"<id>","availability":"all_peers"}
  • CNT-08 — Set availability to nobody: call content.set-availability with {"id":"<id>","availability":"nobody"}
  • CNT-09 — Verify content HTTP endpoint: curl http://192.168.1.228/content returns JSON catalog
  • CNT-10 — Remove content: call content.remove with the content ID
  • CNT-11 — Verify removal: list content again, confirm item gone

Fix strategy: If content endpoints fail, check content_server.rs and content.rs RPC module. Verify content data directory exists on server.


Group 13: Nostr Relay Management

Priority: MEDIUM — used for discovery and names

  • NOSTR-01 — List relays: call nostr.list-relays. Should return {"relays":[...]}
  • NOSTR-02 — Verify default relays seeded: should have relay.damus.io, nos.lol, etc.
  • NOSTR-03 — Add relay: call nostr.add-relay with {"url":"wss://relay.test.example"}
  • NOSTR-04 — Verify relay added: list relays again, confirm new relay present
  • NOSTR-05 — Toggle relay off: call nostr.toggle-relay with {"url":"wss://relay.test.example","enabled":false}
  • NOSTR-06 — Get relay stats: call nostr.get-stats. Should return {"total_relays":...,"connected_count":...,"enabled_count":...}
  • NOSTR-07 — Remove test relay: call nostr.remove-relay with {"url":"wss://relay.test.example"}
  • NOSTR-08 — Verify removal: list relays, confirm test relay gone
  • NOSTR-09 — Get node Nostr pubkey: call node.nostr-pubkey. Should return hex pubkey
  • NOSTR-10 — Verify local nostr-rs-relay container (if installed): check container list for nostr-rs-relay

Fix strategy: If relay endpoints fail, check nostr_relays.rs and nostr.rs RPC module. Default relays are seeded in NostrRelayManager::new().


Group 14: Network Visibility & Peer Discovery

Priority: MEDIUM — social networking feature

  • NET-01 — Get visibility: call network.get-visibility. Should return {"visibility":"hidden"|"discoverable"|"public","tor_address":"..."}
  • NET-02 — Set visibility to discoverable: call network.set-visibility with {"visibility":"discoverable"}
  • NET-03 — Verify visibility changed: get visibility again, confirm "discoverable"
  • NET-04 — Set visibility back to hidden: call network.set-visibility with {"visibility":"hidden"}
  • NET-05 — List connection requests: call network.list-requests. Should return {"requests":[...]}
  • NET-06 — Run network diagnostics: call network.diagnostics. Should return WAN IP, NAT type, UPnP status, Tor status
  • NET-07 — Verify Tor address available: call node.tor-address. Should return .onion address
  • NET-08 — Discover nodes via Nostr: call node-nostr-discover. Should return {"nodes":[...]}

Fix strategy: If visibility fails, check network.rs RPC module. If Tor address missing, check Tor service on server. If diagnostics fail, check network/router.rs.


Group 15: Tor Hidden Services

Priority: MEDIUM — privacy feature

  • TOR-01 — List Tor services: call tor.list-services. Should return services for archipelago, lnd, etc.
  • TOR-02 — Verify archipelago service exists: should have name "archipelago" on port 80
  • TOR-03 — Get onion address: call tor.get-onion-address with {"name":"archipelago"}
  • TOR-04 — Verify onion address format: should end in .onion
  • TOR-05 — Create test service: call tor.create-service with {"name":"test-service","local_port":9999}
  • TOR-06 — Verify test service listed: list services, confirm "test-service" present
  • TOR-07 — Delete test service: call tor.delete-service with {"name":"test-service"}
  • TOR-08 — Verify deletion: list services, confirm test service gone

Fix strategy: If Tor services fail, check tor.rs RPC module. Verify Tor is running on server with systemctl status tor.


Group 16: Router & UPnP

Priority: LOW — optional networking

  • RTR-01 — Discover router: call router.discover. Should return {"discovered":...,"upnp_available":...}
  • RTR-02 — List port forwards: call router.list-forwards. Should return {"forwards":[...]}
  • RTR-03 — Detect router type: call router.detect. Should return gateway and router type
  • RTR-04 — Run network diagnostics: call network.diagnostics. Verify WAN IP detection works

Fix strategy: If UPnP fails, this is expected on some networks. Log and skip. Check network/router.rs.


Group 17: DWN (Decentralized Web Node)

Priority: MEDIUM — Web5 data sync

  • DWN-01 — Check DWN status: call dwn.status. Should return running status, sync info
  • DWN-02 — If DWN container not running, check if installed: look for dwn in container list
  • DWN-03 — Trigger sync: call dwn.sync. Should return sync status
  • DWN-04 — Verify DWN port 3100: SSH check curl -s http://localhost:3100/ from server

Fix strategy: If DWN fails, check container is running and port 3100 is exposed. Check network/dwn_sync.rs.


Group 18: Peer Messaging

Priority: LOW — social feature (needs 2 nodes)

  • MSG-01 — List peers: call node-list-peers. Should return {"peers":[...]}
  • MSG-02 — List received messages: call node-messages-received. Should return {"messages":[...]}
  • MSG-03 — Check peer (if any peers exist): call node-check-peer with a peer's onion address
  • MSG-04 — Verify Web5 view has "Send Message" button and modal

Fix strategy: If peer endpoints fail, check peers.rs in the RPC module. Full P2P messaging requires 2 nodes.


Group 19: BTCPay Server

Priority: MEDIUM — payment processing

  • BTCP-01 — Verify btcpay-server container exists
  • BTCP-02 — Verify archy-nbxplorer container exists (BTCPay dependency)
  • BTCP-03 — Verify archy-btcpay-db PostgreSQL container exists
  • BTCP-04 — All three containers running
  • BTCP-05 — BTCPay UI loads: curl -s http://192.168.1.228:23000/ returns HTML (or via proxy)
  • BTCP-06 — BTCPay opens in new tab (not iframe): verify mustOpenInNewTab() includes port 23000

Fix strategy: BTCPay needs NBXplorer + PostgreSQL. Check all containers are on archy-net. Verify DB credentials in env vars.


Group 20: Fedimint

Priority: MEDIUM — federated Bitcoin custody

  • FED-01 — Verify fedimint container exists
  • FED-02 — Verify fedimint-gateway container exists
  • FED-03 — Both containers running
  • FED-04 — Fedimint Guardian UI loads: check port 8175
  • FED-05 — Fedimint Gateway API responds: check port 8176
  • FED-06 — Verify Fedimint connects to Bitcoin: check env vars point to bitcoin RPC

Fix strategy: If Fedimint containers missing, check first-boot-containers.sh and deploy-to-target.sh. Verify archy-net membership.


Group 21: All Marketplace Apps — Install & Launch

Priority: MEDIUM — verify every app can be installed and started

For each app, verify: (1) appears in marketplace, (2) container exists or can be installed, (3) container starts, (4) UI/port responds:

  • APP-01 — Bitcoin Knots (verified in Group 1)
  • APP-02 — LND (verified in Group 2)
  • APP-03 — Electrs (verified in Group 5)
  • APP-04 — Mempool (verified in Group 6)
  • APP-05 — BTCPay Server (verified in Group 19)
  • APP-06 — Fedimint (verified in Group 20)
  • APP-07 — Vaultwarden — container exists, running, port 8082 responds, proxy at /app/vaultwarden/ works
  • APP-08 — File Browser — container exists, running, port 8083 responds
  • APP-09 — Nextcloud — container exists, running, port 8085 responds, opens in new tab
  • APP-10 — Jellyfin — container exists, running, port 8096 responds
  • APP-11 — Immich — container exists, running, port 2283 responds (multi-container: server, postgres, redis)
  • APP-12 — PhotoPrism — container exists, running, port 2342 responds
  • APP-13 — Penpot — container exists, running, port 9001 responds (multi-container: frontend, backend, exporter, postgres, valkey)
  • APP-14 — Grafana — container exists, running, port 3000 responds
  • APP-15 — SearXNG — container exists, running, port 8888 responds
  • APP-16 — Ollama — container exists, running, port 11434 responds
  • APP-17 — OnlyOffice — container exists, running, port 9980 responds
  • APP-18 — Nginx Proxy Manager — container exists, running, port 81 responds
  • APP-19 — Portainer — container exists, running, port 9000 responds
  • APP-20 — Uptime Kuma — container exists, running, port 3001 responds
  • APP-21 — Home Assistant — container exists, running, port 8123 responds, opens in new tab
  • APP-22 — Tailscale — container exists, running, port 8240 responds
  • APP-23 — Endurain — container exists, running, port 8080 responds
  • APP-24 — Nostr Relay (nostr-rs-relay) — container exists, running, port 18081 responds

Fix strategy: For any app that fails, check get_app_config() in package.rs, get_app_metadata() in docker_packages.rs, nginx proxy config, and container logs.


Group 22: Settings & Security

Priority: HIGH — core security features

  • SET-01 — Verify authenticated session: call system.info or server.echo. Should succeed with valid session
  • SET-02 — Test password change validation: call auth.changePassword with wrong current password. Should fail
  • SET-03 — Verify TOTP status: call auth.totp.status. Should return {"enabled":false} (unless already enabled)
  • SET-04 — Test TOTP setup flow: call auth.totp.setup.begin with {"password":"password123"}. Should return QR SVG and secret
  • SET-05 — Verify TOTP setup returns backup codes: the setup.confirm step should return 10 backup codes (skip actual confirmation to avoid locking out)
  • SET-06 — Test rate limiting: send 5+ rapid login failures. Should get rate-limited response
  • SET-07 — Test auth bypass: call a protected endpoint without session cookie. Should return auth error
  • SET-08 — Test input validation: send SQL injection payload '; DROP TABLE-- as password. Should fail safely
  • SET-09 — Test path traversal: send ../../etc/passwd as app_id. Should fail with validation error
  • SET-10 — Verify onboarding status: call auth.isOnboardingComplete. Should return boolean

Fix strategy: If auth endpoints fail, check auth.rs and totp.rs. If security validation fails, review input sanitization in handler.


Group 23: System Updates

Priority: LOW — maintenance feature

  • UPD-01 — Check for updates: call update.check. Should return current version, update status
  • UPD-02 — Get update status: call update.status. Should return version info without hitting remote
  • UPD-03 — Dismiss update: call update.dismiss. Should return success
  • UPD-04 — Verify version format: current_version should match semver pattern

Fix strategy: If update check fails, check update.rs. The remote manifest URL may not exist yet — handle gracefully.


Group 24: WebSocket Real-Time Updates

Priority: HIGH — UI depends on this for live state

  • WS-01 — WebSocket connects: establish connection to ws://192.168.1.228/ws/db with session cookie
  • WS-02 — Initial state received: first message should contain full state dump with revision
  • WS-03 — Heartbeat works: connection stays alive for 60+ seconds
  • WS-04 — State updates broadcast: start/stop an app and verify WebSocket receives state change

Fix strategy: If WebSocket fails, check server.rs WebSocket handler. Verify nginx is proxying WebSocket upgrade headers.


Group 25: Frontend Views — Render & Function

Priority: HIGH — user-facing

  • UI-01 — Dashboard Home loads: curl http://192.168.1.228/ returns full HTML with assets
  • UI-02 — JavaScript bundles load: check .js assets return 200
  • UI-03 — CSS bundles load: check .css assets return 200
  • UI-04 — App icons load: check /assets/img/app-icons/bitcoin-knots.png returns 200
  • UI-05 — Marketplace page functional: has app cards, install buttons
  • UI-06 — My Apps page functional: shows installed apps with status
  • UI-07 — Web5 page functional: shows DID, wallet, identity list with Nostr pubkeys, networking sections
  • UI-08 — Settings page functional: shows account info, password change, 2FA
  • UI-09 — Server/Network page functional: shows connectivity, services
  • UI-10 — Cloud page functional: shows file sections (if File Browser installed)
  • UI-11 — Lightning Channels page functional: accessible from LND app detail
  • UI-12 — Onboarding pages render: intro, DID, identity steps load (identity step creates both DID + Nostr key)
  • UI-13 — App launcher overlay works: opening an app shows iframe or new tab
  • UI-14 — Mobile responsive: UI loads at 375px width without horizontal scroll

Fix strategy: If frontend fails, check Vite build output. Deploy with ./scripts/deploy-to-target.sh --live to rebuild and push.


Completion Criteria

All groups must have every test passing. The final state should be:

  • All 25 Groups Passing — Every checkbox above ticked
  • Zero Broken Features — No RPC endpoint returns unexpected errors
  • Zero Container Crashes — All running containers healthy
  • Frontend Renders — All views load without JS errors
  • Bitcoin Stack Connected — Bitcoin Knots ↔ LND ↔ Electrs ↔ Mempool chain works
  • Web5 Stack Working — DID ↔ Nostr Keys ↔ Identities ↔ Credentials ↔ Names ↔ Wallet integrated
  • Every Identity Has Dual Keys — All DIDs also have Nostr keypairs for full ecosystem interop
  • Networking Stack Working — Tor ↔ Nostr ↔ Peers ↔ Content sharing functional

Execution Instructions

For each group in order:

  1. Run all tests in the group via RPC calls to http://192.168.1.228/rpc/
  2. If a test fails: a. Read the relevant source file to understand the expected behavior b. Identify the bug (wrong response format, missing handler, bad config, etc.) c. Fix the code d. Deploy: ./scripts/deploy-to-target.sh --live e. Wait for deploy to complete and services to restart f. Re-run the failing test g. Loop until it passes
  3. Mark the test as passed by updating this file
  4. Move to the next group only when all tests in the current group pass
  5. At the end, run a final sweep of all tests to confirm nothing regressed

Total tests: ~195 individual checks across 25 groups