archy/docs/OPEN-SOURCE-READINESS-PLAN.md

22 KiB
Raw Permalink Blame History

Open-Source Readiness Plan — Archipelago public launch

Working plan, 2026-07-27. Source of truth for the pre-open-source cleanup. A second agent is working the same goal concurrently — before executing any phase, diff against git log since 7e8d3314 and skip/merge what's already done. (Session plan file: ~/.claude/plans/resilient-moseying-reef.md.)

Context

The repo goes public in a few days, targeting bitcoin/bitcoin-level polish. Three deep exploration passes (docs/structure, code health, secrets sweep) found the repo is fundamentally strong — README, apps/ manifest examples, ADRs, the bats lifecycle gate, 1,104 Rust tests — but has hard blockers: two live Anthropic API keys committed in tracked files, node passwords in 7 tracked files, no LICENSE (README links a 404), 5.5 GB .git (re-committed 27 MB APKs), ~290 hardcoded references to the private Gitea registry 146.59.87.168:3000 that make every app image unpullable for outsiders, and ~28 internal AI-session/tracker docs mixed into docs/.

Decisions made by the user:

  1. Fresh-history publish — new public repo with a clean initial commit; private repo keeps full history.
  2. Registry: domain + parameterize — real domain in front of the existing registry; host configurable everywhere.
  3. Deep code cleanup — orphan crates, dead_code lifts, clippy trims, legacy fallback deletion (sequenced, cut-line-friendly).
  4. Internal docs: sanitize and keep public — scrub creds/IPs/hostnames but publish plans/trackers for transparency.

Invariant throughout: the single-node production gate (tests/lifecycle/run-gate.sh) is GREEN and must stay green. Re-run after any orchestrator/lifecycle change (Phase E especially). All cargo verification uses --all-features to match CI. Stage by explicit path, never git add -A (shared tree).

Current local pass status

This branch is replayed on top of origin/main as public-prelaunch.

Completed locally in this pass:

  • Redacted the two tracked Anthropic API key literals from scripts/setup-aiui-server.sh and image-recipe/_archived/build-auto-installer-iso.sh.
  • Removed Android/app/debug.keystore and core/.env.production from the source tree; copies were preserved in ~/Desktop/archipelago-sensitive-backup-2026-07-27/.
  • Reworked scripts/audit-secrets.sh to scan tracked source more aggressively and to catch non-example env files and credential file patterns.
  • Reworked scripts/validate-app-manifest.sh so the current app: manifest schema can be audited without a Python PyYAML dependency.
  • Updated root/community docs, CI, PR template, app developer notes, and container/deployment docs toward public contributor expectations.
  • Fixed native FIPS activation fallback: nodes that have the packaged fips.service but not archipelago-fips.service now start the available unit instead of repeatedly failing activation against a missing unit. This now covers startup, supervisor self-heal, manual dashboard start/reconnect, and post-onboarding activation. The UI now labels the action as Start instead of making native FIPS look like an installable app.
  • Fixed the FIPS app-port relay design so it binds relays to the node's FIPS ULA instead of wildcard [::], avoiding collisions with Podman-published app ports such as FileBrowser 8083 and Botfights 9100.
  • Added docs/nostr-git-source-hosting.md, a NIP-34/ngit/GRASP source hosting plan using a Bitcoin Core-style maintainer model: public review and easy forks, with canonical merge rights held by a small signed maintainer set.

Verified locally:

  • ./scripts/audit-secrets.sh passes.
  • Full apps/*/manifest.yml repository audit passes with warnings only.
  • bash -n passes for the edited shell scripts.
  • Targeted FIPS dashboard vitest passes.
  • Targeted Rust tests for FIPS service unit detection and FIPS app relay address selection pass.

Verified on a Linux Archipelago verification node:

  • Native FIPS was restored by starting the already-installed packaged fips.service; the daemon became active and joined the FIPS tree.
  • Correct local lifecycle API endpoint is HTTP, not HTTPS (ARCHY_HOST=127.0.0.1 ARCHY_SCHEME=http).
  • Read-only lifecycle run progressed past login and confirmed required containers, Bitcoin RPC, ElectrumX TCP, and manifest port-drift checks, but did not complete cleanly: botfights and filebrowser remained in restarting longer than the matrix window, and the LND lncli getinfo probe hung. Do not run the destructive gate until those live-node issues are understood.
  • After the node updated to 1.7.116-alpha, botfights, filebrowser, and lnd were active/running and ports 8083/9100 were held by Podman's rootlessport as expected. The packaged fips.service remained installed and enabled but inactive, so the native FIPS service fallback should still ship before the public launch.

Still required before public publish:

  • Rotate/revoke compromised credentials listed in Phase 0.
  • Finish Phase 1 password/node/token sanitization beyond the two API keys.
  • Publish from fresh history after the sanitized tree is final.
  • Run full Rust, frontend, Android, and lifecycle gate verification.
  • Resolve the live-node lifecycle blockers above, then rerun the read-only suite followed by the destructive gate only on an approved verification node.
  • Decide the canonical Archipelago maintainer npub and merge-maintainer npub list before publishing the Nostr Git source-hosting workflow.

Phase 0 — Credential rotation (immediate, independent of the repo)

Treat all of these as already compromised; rotate even though we're doing fresh-history:

  • Anthropic API key #1: image-recipe/_archived/build-auto-installer-iso.sh:2837 (the "intentional alpha" ISO key). Revoke + reissue; move the live key OUT of source into a build-time secret/env (ISO_ANTHROPIC_API_KEY), keep the alpha-baking behavior if desired but never the literal in git.
  • Anthropic API key #2: scripts/setup-aiui-server.sh:28 — a different live key, not covered by the documented alpha exception. Revoke; parameterize the script.
  • The shared node SSH/sudo/UI password (two variants) — in 7 tracked files + 24+ commits. Rotate fleet-wide (user task).
  • Gitea ai account password + 2 Gitea tokens — embedded in .git/config remote URLs (not tracked, but leaks in any directory copy/tarball). Rotate; switch remotes to credential-helper storage instead of URL-embedded creds.

Phase 1 — Secrets & sanitization of tracked files

  1. Strip the password/credential lines from the 7 files: docs/PRODUCTION-MASTER-PLAN.md (lines ~428429, 454457, 483, 521528, 886 — the fleet cred table), docs/archive/SESSION-1.8.0-OTA-PROGRESS.md, docs/archive/HANDOVER-2026-07-02-iso-feedback.md, docs/bitcoin-version-bulletproof-rollout.md, tests/production-quality/TRACKER.md, tests/multinode/meshtastic.sh:26, neode-ui/test-openwrt.mjs:4 (→ env var).
  2. .gitea/workflows/post-install-tests.yml — remove sshpass -p '…' + default target IP; use secrets/vars.
  3. Sanitize infra identifiers repo-wide (in the sanitize-and-keep docs and scripts): replace Tailscale IPs (17 unique, 14 files), LAN IPs (192.168.1.x, 93 files), hostnames (tx1138, shorty-s, archy-x250, archy-dev-pa) with placeholders like <node-a> / NODE_IP. Key script targets: scripts/deploy-config-defaults.sh, scripts/deploy-tailscale.sh, docs/operations-runbook.md (opens with real node IPs), docs/developer-guide.md, docs/api-reference.md, docs/hotfix-process.md.
  4. Fix the audit tool that let this happen: scripts/audit-secrets.sh:28 — remove \.md$ and bare test from ALLOW_PATTERNS; add sk-ant- and password-table patterns; scan all tracked files not just *.env. Run it clean as a Phase-1 exit check.
  5. .gitignore additions: .claude/, *.key, *.pem, id_rsa*, *.sqlite, *.db (.claude/settings.local.json with creds is currently only ignored by a machine-global rule).
  6. Product-security note to raise (not fix now): password123 is a shipped default (auth.rs, en.json, user-walkthrough) — file a public issue for forced first-run password change if not already enforced.

Phase 2 — Repo restructure: deletions, binaries, layout

Delete (each its own commit):

  • loop/ (AI overnight harness w/ node SSH lines), .agents/, .codex, .githooks/pre-push (the hook that re-commits the 27 MB APK — root cause of the 5.5 GB history).
  • indeedhub/ submodule + .gitmodules entry (points at private HTTP Gitea, breaks --recursive clones); indeedhub-demo/ (single Dockerfile — merge or drop).
  • RELEASE-NOTES-v1.0.0.md (superseded by CHANGELOG), neode-ui/docs/GAMEPAD-NAV-MAP.md (duplicate of docs/GAMEPAD-NAV.md).
  • Stray generated HTML: docs/container-architecture.html (311 KB), docs/archive/architecture-review.html, docs/archive/lora-functionality.html.
  • Android/local.properties from tracking (local absolute path); remove Android/app/debug.keystore (standard practice).

Move out of git (→ release assets on the Releases page, referenced by URL):

  • neode-ui/public/packages/archipelago-companion.apk (27 MB), wireguard.apk (17 MB), atob.s9pk (23 MB).
  • Android/archipelago-0.3.0-debug.apk.zip (16 MB, stale).
  • demo/content/music/* + heavy demo/aiui/assets (~261 MB, third-party/unclear-licence media — MUST not ship publicly regardless of size).
  • neode-ui/dev-dist/ (generated Workbox output) → gitignore.

Rename/fix the naming lie: image-recipe/_archived/ contains the production ISO builder (build-auto-installer-iso.sh, referenced by .gitea/workflows/build-iso.yml). Move live files up into image-recipe/, delete the genuinely archived rest.

Phase 3 — Registry domain + parameterization (functional blocker)

Infra (user assists: DNS + TLS):

  • Put a domain (e.g. registry.archipelago-os.org / git.archipelago-os.org) with HTTPS in front of the existing Gitea on vps2. OTA download URLs move from plain HTTP to HTTPS.

Repo changes:

  • Introduce a single source of truth for the registry host (e.g. REGISTRY_HOST in scripts/lib/ + a default in the orchestrator config). Replace 146.59.87.168:3000 in: all 56 apps/*/manifest.yml, app-catalog/catalog.json, releases/manifest.json, release-manifest.json, the 11 scripts (self-update.sh, create-release.sh, generate-app-catalog.sh, validate-app-manifest.sh, first-boot-containers.sh, …), both demo-images.yml workflows, demo-deploy/.env.example, and the Android sources (FipsPreferences.kt, PartyScreen.kt).
  • Because the catalog is signed: regenerate + re-sign + republish the app catalog after the manifest host change (catalog-overlay supremacy — disk edits don't apply otherwise). Signing needs the user's mnemonic → schedule one ceremony after manifests are final.
  • Verify: fresh machine with no LAN/tailnet access can podman pull one app image via the domain and the gate node still installs apps after the re-signed catalog lands.

Phase 4 — Documentation overhaul

4a. Community/legal files (missing today)

  • LICENSE — MIT (matches existing README badge). Add [workspace.package] license + license.workspace = true in the 5 member Cargo.tomls (also see Phase A4).
  • SECURITY.md — disclosure address, PGP key, supported-versions; cite the March 2026 audit (docs/archive/security-code-audit-2026-03.md).
  • CODE_OF_CONDUCT.md — Contributor Covenant (CONTRIBUTING.md already links to it, 404 today).
  • CONTRIBUTING.md edits: Gitea→GitHub fork flow, remove private deploy instructions, absorb the public-worthy CLAUDE.md invariants (rootless podman, manifest-driven, secrets model, non-destructive migrations), versioning policy note for the -alpha scheme.
  • CLAUDE.md — rewrite: keep invariants/build-verify (public-worthy), remove status banner, node numbers, gitea-ai push mechanics, MEMORY references (those move to private notes).

4b. New developer docs (the three real gaps for app developers)

  1. docs/quadlet-compilation.md — how a manifest becomes a Quadlet/systemd unit: naming, systemctl --user lifecycle, where units land, how to inspect/debug one. (Source: core/archipelago/src/container/quadlet*.rs, prod_orchestrator.)
  2. docs/container-lifecycle.md — the 30 s level-triggered reconciler, install/adopt/ restart/uninstall state machine, health checks, crash recovery. (Replaces the plan-shaped docs/bulletproof-containers.md as the current description; salvage its content.)
  3. docs/secrets.mdgenerated_secrets declaration → materialisation by container::secrets (0600, rootless) → injection; what developers must never do.
  • Also: make every example in docs/app-developer-guide.md + apps/*/manifest.yml copy-paste work against the new public registry host; add an end-to-end "write your first app" walkthrough that a stranger can follow with only the public repo + an Archipelago node.

4c. Sanitize-and-keep internal docs (user's transparency choice)

  • Keep, after Phase-1 scrubbing: docs/PRODUCTION-MASTER-PLAN.md, docs/UNIFIED-TASK-TRACKER.md, docs/1.8.0-RELEASE-HARDENING-PLAN.md, docs/RETICULUM-TRANSPORT-PROGRESS.md, HANDOFF-*, test plans, docs/archive/* — but move all session/handoff/tracker material under docs/history/ (extending the existing honest docs/archive/README.md pattern) so the top-level docs/ reads as current reference only. Add a banner to each: "historical working document, sanitized; not maintained."
  • Remove dangling agent-memory references in tracked docs (docs/bulletproof-containers.md, docs/RETICULUM-TRANSPORT-PROGRESS.md, docs/registry-manifest-design.md, docs/bitcoin-multi-version-design.md progress block).
  • De-status the 14 design docs (strip "Status/RESUME POINT" headers into a one-line status field; e.g. docs/APP-PACKAGING-MIGRATION-PLAN.md → public app-platform design doc).
  • Extract North-Star narrative from PRODUCTION-MASTER-PLAN into docs/ROADMAP.md; extract the "run the gate ON the node" philosophy from docs/multinode-testing-plan.md into tests/lifecycle/TESTING.md.
  • Add docs/README.md index (bitcoin/bitcoin doc/ style): Getting started / Architecture / App development / Operations / Design docs (ADRs) / History.
  • README fixes: LICENSE link becomes real, Documentation table repointed at the reorganized docs, remove "Deploy to a Test Node" private-LAN section, point Contributing at CONTRIBUTING.md only.

Phase 5 — Deep code cleanup (ordered zero-risk → highest-risk; cut-line after any commit)

A. Zero-risk deletions & metadata (S each, own commits)

  • A1 Delete orphan non-compiling StartOS crates: core/models, core/helpers, core/js-engine (incl. 2 committed JS_SNAPSHOT.*.bin), core/container-init (~4,100 LOC, zero references). Verify: cargo build --workspace && cargo test --all-features.
  • A2 Delete unreferenced Vue components: neode-ui/src/components/{AppSwitcher,EmptyState,SkeletonCard}.vue. Verify: npm run type-check && npm run build.
  • A3 Fix .gitignore lockfile lines (7: Cargo.lock, 15: package-lock.json) — lockfiles are intentionally tracked; the rules are misleading and swallow future lockfiles.
  • A4 LICENSE + Cargo license fields (see 4a). Verify with cargo metadata.
  • A5 core/rust-toolchain.toml pinning 1.95.0; align .github/workflows/ci.yml (remove explicit toolchain: stable input so the file wins). Upgrades become deliberate PRs.
  • A6 core/rustfmt.toml codifying defaults only (edition = "2021" + comment) — do NOT add style options days before launch (whole-tree reformat churn). Verify cargo fmt --all -- --check yields no diff.

B. CI guards (zero runtime risk)

  • B1 Enable vitest in CI: run cd neode-ui && npm run test locally; fix trivial failures, .skip+issue flaky ones; add step to the frontend job. Playwright → tracked issue only (needs browsers + mock backend orchestration).
  • B2 Raw podman/systemctl ratchet, not migration: the 132 raw Command::new("podman"/"systemctl") sites use subcommands the core/container/src/podman_client.rs wrapper doesn't expose (network/inspect/ps/port), 43 sites are in gate-critical install.rs, and the prod path intentionally uses Quadlet+systemctl. Add scripts/ci/raw-podman-ratchet.sh (count vs committed baseline, fail on increase) as a CI step + tracked issue for wrapper API design.

C. Clippy suppression trim (core/archipelago/src/main.rs:8-18, per-lint commits)

  • Remove cheaply: assertions_on_constants, drop_non_drop, wildcard_in_or_patterns, doc_lazy_continuation, enum_variant_names (targeted allows on serde enums — never rename wire variants).
  • Own careful commit: unused_io_amount — a correctness lint; fix sites with read_exact/write_all or documented targeted allows (mesh/serial.rs:456,496 has raw partial reads; serial framing may be intentional). Full test suite + gate after.
  • Keep crate-wide with justifying comment: too_many_arguments, type_complexity; attempt ptr_arg (&Vec<T>&[T], mechanical) if time allows — first to cut.
  • Verify each: cargo clippy --all-targets --all-features -- -D warnings && cargo test --all-features.

D. dead_code lift — Tiers 12 pre-launch, Tier 3 → commented allows + issues

Per-module procedure (one file per commit): remove #![allow(dead_code)]cargo check --all-targets --all-features → triage each warning: (a) genuinely dead → delete; (b) future-feature/protocol-mandated → targeted #[allow(dead_code)] // TODO(#NNN): …; (c) missing wiring → keep + targeted allow + issue (don't fix wiring in this workstream) → clippy -D warnings + tests → commit.

  • Tier 1 (small/leaf, S each): swarm/seed_advert.rs, transport/{mesh_transport,lan,chunking,delta}.rs, mesh/{crypto,alerts,types,outbox}.rs, streaming/mod.rs, wallet/mod.rs.
  • Tier 2 (M each): fips/{mod,iface,dial}.rs (41 external refs → little residual deadness), mesh/{x3dh,ratchet,steganography,message_types}.rs — for crypto files bias to (b) with roadmap comments (unused crypto attracts auditor noise; every kept item needs its why).
  • Tier 3 (defer, riskiest): mesh/{mod,reticulum,protocol,serial,bitcoin_relay}.rs, transport/mod.rs — change each blanket allow to #![allow(dead_code)] // Hardware-mesh surface partially wired; triage tracked in #NNN.
  • Optional S/M win: move prod_orchestrator.rs's 5,034-line #[cfg(test)] module to a sibling file via #[path] (pure move, halves the 6,291-line file).

E. stacks.rs legacy fallbacks (highest risk — LAST, evidence-gated)

Legacy installers for immich/btcpay/mempool/indeedhub (core/archipelago/src/api/rpc/package/stacks.rs:838/1047/1267/1498, ~1,000 LOC with hardcoded registry IPs) fire only on "unknown app_id, zero members installed", logging INSTALL ORCH SKIP (stacks.rs:673). Netbird already uses the hard-error replacement (stacks.rs:1898-1920).

  1. Run the full gate on the node; grep install logs for INSTALL ORCH SKIP.
  2. Zero SKIPs → replace each legacy body with the netbird-style hard error (keep orchestrator call + adopt_stack_if_exists; satisfies migrations-never-destroy-data). Re-run gate; any red → revert + issue.
  3. Any SKIP → don't delete; issue: "deploy manifests fleet-wide, then delete legacy installers".

Explicitly deferred → public tracked issues at launch

PodmanClient API extension + call-site migration; god-module splits (install.rs, update.rs, mesh/mod.rs); Playwright in CI; Tier-3 dead_code triage; password123 default hardening.

Phase 6 — Fresh-history publish

  1. Freeze: all phases merged on internal main, gate green, catalog re-signed.
  2. Build the public tree: git archive-style export of HEAD (never copy .git/ — it holds credentialed remotes) → new repo, single initial commit ("Initial public release, vX.Y.Z"), optionally preserving CHANGELOG.md as the human-readable history.
  3. Pre-publish gate on the export: scripts/audit-secrets.sh (fixed version) clean; grep-zero for sk-ant-, rotated-password strings, 146.59.87.168, tailnet 100. IPs, 192.168.1., internal hostnames; du -sh .git sanity (< ~100 MB); fresh git clone + cd core && cargo build
    • cd neode-ui && npm ci && npm run build on a clean machine/container; one app image pull from the public domain.
  4. Publish to GitHub; enable issue templates (already present in .github/); file the deferred-work issues (from Phase 5's issue list) as the initial public issue set — honest and gives contributors entry points.
  5. Internal repo remains the private full-history remote; decide sync direction post-launch (recommend: public repo becomes canonical, private keeps only ops/infra notes).

Verification (end-to-end)

  • tests/lifecycle/run-gate.sh green on the node after Phases 3 + 5E (and after any lifecycle-touching commit).
  • CI green on every phase commit: cargo fmt --check, clippy -D warnings, cargo test --all-features, frontend type-check + build + (new) vitest.
  • Phase-6 clean-machine clone/build/pull test is the final acceptance test — it simulates the first outside developer.
  • Docs acceptance: a reader following docs/app-developer-guide.md + the new quadlet/lifecycle/secrets docs can build and install an app manifest without any private infra.

Sequencing / cut-line

Order: 0 → 1 → 2 → (3 ∥ 4) → 5 (A→E) → 6. Phases 02 are non-negotiable security; Phase 3 is the functional blocker; Phase 4 is the developer-experience payload; Phase 5 can be cut after any commit (minimum viable: A1A6, B1B2, unused_io_amount fix); Phase 6 last. If the timeline compresses, Tier-2 dead_code and Phase E move to public issues — everything else holds.