- 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>
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:
lnd.createinvoicerejected zero-amount invoices (BOLT11 "any amount") — fixed validationidentity.verifyrequired local identity lookup — now extracts pubkey fromdid:keydirectlytor.create-servicefailed with permissions error — now writes totor-config/nottor/(owned by debian-tor)
Pre-Flight Checks
- PRE-01 — Verify server is reachable:
curl -s http://192.168.1.228/healthreturns 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.loginwithpassword123, confirm session cookie set - PRE-04 — Verify WebSocket connects:
curl -s -N -H "Upgrade: websocket" http://192.168.1.228/ws/dbresponds with upgrade - PRE-05 — Verify disk space: SSH and check
df -h /has >5GB free. If not, prune old container images withpodman image prune -af - PRE-06 — Verify backend service running: SSH and check
systemctl is-active archipelagoreturnsactive
Group 1: Bitcoin Knots — Core Node
Priority: CRITICAL — everything depends on this
- BTC-01 — Verify
bitcoin-knotscontainer exists: callcontainer-listRPC, confirmbitcoin-knotsin response - BTC-02 — Verify
bitcoin-knotscontainer is running: status should be "running" in container list - BTC-03 — If not running, start it: call
package.startwith{"id":"bitcoin-knots"}. Wait up to 60s for startup - BTC-04 — Verify Bitcoin RPC responds: call
bitcoin.getinfoRPC. Should returnblock_height,sync_progress,chain - BTC-05 — Verify blockchain sync progress:
sync_progressorverification_progressshould be > 0.99 (99%+). If still syncing, log progress and continue (non-blocking) - BTC-06 — Verify Bitcoin is on mainnet:
chainshould be"main"or"mainnet" - BTC-07 — Verify mempool data:
mempool_sizeandmempool_tx_countshould 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
lndcontainer exists in container list - LND-02 — Verify
lndcontainer is running - LND-03 — If not running, start it: call
package.startwith{"id":"lnd"}. Wait up to 90s (LND needs Bitcoin synced) - LND-04 — Verify LND connects to Bitcoin: call
lnd.getinfoRPC. Should returnsynced_to_chain,block_height - LND-05 — Verify LND is synced:
synced_to_chainshould betrue. If false, log and wait up to 5 min - LND-06 — Verify LND alias is set:
aliasfield should be non-empty - LND-07 — Verify LND channel count:
num_active_channelsshould be numeric (0 is OK for fresh install) - LND-08 — Verify LND peer count:
num_peersshould be numeric - LND-09 — Verify LND on-chain balance accessible:
balance_satsshould be numeric >= 0 - LND-10 — Verify LND channel balance accessible:
channel_balance_satsshould be numeric >= 0 - LND-11 — Verify LND REST API proxied: check
/proxy/lnd/v1/getinforesponds 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.newaddressRPC. Should return{"address":"bc1..."}(bech32) - WAL-02 — Verify address format: address should start with
bc1(mainnet bech32) ortb1(testnet) - WAL-03 — Verify address is unique: call
lnd.newaddressagain, confirm different address returned - WAL-04 — Verify on-chain balance query: call
lnd.getinfo, checkbalance_satsreturns a number - WAL-05 — Test send validation (bad address): call
lnd.sendcoinswith{"addr":"invalid","amount":1000}. Should return error about invalid address - WAL-06 — Test send validation (dust amount): call
lnd.sendcoinswith{"addr":"bc1qvalidaddress","amount":100}. Should return error about minimum 546 sats - WAL-07 — Test send validation (zero amount): call
lnd.sendcoinswith{"addr":"bc1qvalidaddress","amount":0}. Should return error - WAL-08 — Verify wallet RPC endpoints exist in handler: grep
lnd.newaddressandlnd.sendcoinsin RPC router - WAL-09 — Verify Web5 view shows wallet section: check
Web5.vuerenders 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.createinvoicewith{"amount_sats":1000,"memo":"test invoice"}. Should returnpayment_requeststarting withlnbc - LN-02 — Verify invoice format:
payment_requestshould be a valid BOLT11 string (starts withlnbcon mainnet,lntbon testnet) - LN-03 — Verify invoice amount: response should include
amount_sats: 1000 - LN-04 — Create zero-amount invoice: call
lnd.createinvoicewith{"amount_sats":0}. Should succeed (any-amount invoice) - LN-05 — Test pay invoice validation (self-pay): call
lnd.payinvoicewith 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.payinvoicewith{"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.openchannelwith{"pubkey":"invalid","amount":50000}. Should return error - LN-10 — Test open channel validation (too small): call
lnd.openchannelwith{"pubkey":"validpubkey","amount":1000}. Should return error about minimum 20000 sats - LN-11 — Verify Lightning Channels view renders: check
LightningChannels.vueroute/dashboard/apps/lnd/channelsexists 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-electrscontainer exists in container list - ELX-02 — Verify
mempool-electrscontainer is running - ELX-03 — If not running, start it (requires Bitcoin running first)
- ELX-04 — Verify Electrs connects to Bitcoin: check
/electrs-statusHTTP 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(ormempool) container exists - MEM-02 — Verify
mempool-apicontainer exists - MEM-03 — Verify
mysql-mempool(orarchy-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.didRPC. 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.createwith{"name":"Personal","purpose":"personal"}. Should return identity withid,did,pubkey - DID-05 — Create identity "Business": call
identity.createwith{"name":"Business","purpose":"business"} - DID-06 — Create identity "Anonymous": call
identity.createwith{"name":"Anonymous","purpose":"anonymous"} - DID-07 — Get identity by ID: call
identity.getwith 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-keywith{"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-keywith{"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-keywith{"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-keyagain 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 includenostr_pubkeyfield
7C: DID Signing & Verification
- DID-17 — Sign message with Personal DID: call
identity.signwith{"id":"<personal_id>","message":"hello world"}. Should return{"signature":"..."} - DID-18 — Verify Personal DID signature: call
identity.verifywith the DID, message, and signature. Should return{"valid":true} - DID-19 — Verify bad signature fails: call
identity.verifywith 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-signwith{"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-signwith Business identity. Should return different signature (different key) - DID-25 — Nostr sign with Anonymous: call
identity.nostr-signwith 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-defaultwith Business identity ID. Should succeed - DID-28 — Verify default changed: call
identity.list, Business should haveis_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.deletewith Anonymous ID. Should succeed - DID-31 — Verify deletion: call
identity.getwith 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.vuedisplays 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.createwith{"name":"Issuer"}, thenidentity.create-nostr-keyfor it - VC-02 — Issue credential: call
identity.issue-credentialwith{"issuer_id":"<issuer_id>","subject_did":"did:key:z...","type":"TestCredential","claims":{"name":"Alice"}} - VC-03 — Verify credential: call
identity.verify-credentialwith 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-credentialswith{"did":"did:key:z..."} - VC-06 — Revoke credential: call
identity.revoke-credentialwith the credential ID - VC-07 — Verify revoked credential: call
identity.verify-credentialagain. Should showstatus: "revoked"orvalid: 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-namewith{"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-namesagain, confirm the test name appears with both DID and nostr_pubkey - NAME-04 — Resolve name: call
identity.resolve-namewith{"identifier":"testuser@archipelago.local"} - NAME-05 — Link name to different identity: create second identity (with Nostr key), call
identity.link-namewith 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-namewith 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
fedimintin container list - ECASH-04 — If Fedimint running, test mint: call
wallet.ecash-mintwith{"amount_sats":100}(may fail if no Lightning funding — log result) - ECASH-05 — Test mint validation (too large): call
wallet.ecash-mintwith{"amount_sats":2000000}. Should error (max 1,000,000) - ECASH-06 — Test mint validation (zero): call
wallet.ecash-mintwith{"amount_sats":0}. Should error - ECASH-07 — Test send ecash: call
wallet.ecash-sendwith{"amount_sats":50}(may fail if no balance — log result) - ECASH-08 — Test receive ecash validation (bad token): call
wallet.ecash-receivewith{"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_satsshould equalcontent_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.addwith{"filename":"test-file.txt","mime_type":"text/plain","description":"Test content"} - CNT-03 — Verify content listed: call
content.list-mineagain, confirm test file appears - CNT-04 — Set pricing to free: call
content.set-pricingwith{"id":"<id>","access":"free"} - CNT-05 — Set pricing to paid: call
content.set-pricingwith{"id":"<id>","access":"paid","price_sats":100} - CNT-06 — Set pricing to peers only: call
content.set-pricingwith{"id":"<id>","access":"peers_only"} - CNT-07 — Set availability to all peers: call
content.set-availabilitywith{"id":"<id>","availability":"all_peers"} - CNT-08 — Set availability to nobody: call
content.set-availabilitywith{"id":"<id>","availability":"nobody"} - CNT-09 — Verify content HTTP endpoint:
curl http://192.168.1.228/contentreturns JSON catalog - CNT-10 — Remove content: call
content.removewith 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-relaywith{"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-relaywith{"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-relaywith{"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-visibilitywith{"visibility":"discoverable"} - NET-03 — Verify visibility changed: get visibility again, confirm "discoverable"
- NET-04 — Set visibility back to hidden: call
network.set-visibilitywith{"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.onionaddress - 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-addresswith{"name":"archipelago"} - TOR-04 — Verify onion address format: should end in
.onion - TOR-05 — Create test service: call
tor.create-servicewith{"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-servicewith{"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
dwnin 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-peerwith 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-servercontainer exists - BTCP-02 — Verify
archy-nbxplorercontainer exists (BTCPay dependency) - BTCP-03 — Verify
archy-btcpay-dbPostgreSQL 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
fedimintcontainer exists - FED-02 — Verify
fedimint-gatewaycontainer 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.infoorserver.echo. Should succeed with valid session - SET-02 — Test password change validation: call
auth.changePasswordwith 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.beginwith{"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/passwdas 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_versionshould 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/dbwith 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
.jsassets return 200 - UI-03 — CSS bundles load: check
.cssassets return 200 - UI-04 — App icons load: check
/assets/img/app-icons/bitcoin-knots.pngreturns 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:
- Run all tests in the group via RPC calls to
http://192.168.1.228/rpc/ - 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 --livee. Wait for deploy to complete and services to restart f. Re-run the failing test g. Loop until it passes - Mark the test as passed by updating this file
- Move to the next group only when all tests in the current group pass
- At the end, run a final sweep of all tests to confirm nothing regressed
Total tests: ~195 individual checks across 25 groups