Archipelago — open-source initial import

This commit is contained in:
Archipelago
2026-08-12 10:55:49 +00:00
commit 6d567c8517
1877 changed files with 416132 additions and 0 deletions
@@ -0,0 +1,23 @@
---
created: 2026-07-30T22:19:31.770Z
title: Connected-nodes list must scroll at row-matched height, not grow to fit
area: ui
severity: blocker
files:
- neode-ui/src/views/web5/Web5ConnectedNodes.vue
- neode-ui/src/views/web5/Web5.vue
---
## Problem
Dorian reports (2026-07-30): the connected-nodes container's inner list was still meant to scroll, and currently it doesn't — it grows to fit all rows without a scrollbar. The intended behavior for the row layout:
- The connected-nodes container sits in a row next to another container (on the right).
- If the right-hand container is TALLER, the connected-nodes inner list should extend/scale to match the right container's height.
- Within that matched height the list must remain SCROLLABLE when it has more rows than fit — it must NOT expand to show all entries scroll-free (which stretches the row).
So: height is driven by the sibling (equal-height row), overflow inside is `scroll`, never grow-to-fit. Likely component: `Web5ConnectedNodes.vue` inside `Web5.vue`'s row layout (verify — "connected nodes" also appears in settings AccountInfoSection). This sounds like a regression/misimplementation of an earlier request ("was still meant to scroll").
## Solution
Classic equal-height-row + inner-scroll pattern: the row uses flex with stretch alignment; the connected-nodes card gets `min-height: 0` (or `h-full` with a min-h-0 flex column) and the inner list `flex-1 overflow-y-auto`, so its height tracks the taller right-hand sibling while the list scrolls internally. Confirm exact container with Dorian's screenshot/preview if ambiguous. Standing rule: change only this behavior — no other visual changes.
@@ -0,0 +1,21 @@
---
created: 2026-07-30T22:19:31.770Z
title: Fedimint gateway must not install with a pre-set password
area: security
severity: blocker
files:
- core/archipelago/src (app orchestrator / secrets materialisation)
---
## Problem
Dorian reports (2026-07-30): the Fedimint gateway app installs with a password already set up — i.e. a default/pre-baked credential rather than one generated at install time or set by the user. Default credentials on a Lightning/fedimint gateway are a security hole: anyone knowing the shipped default can access the gateway. Dorian rated this a blocker.
This also violates the repo's standing secrets invariant (CLAUDE.md): secrets must be manifest-declared (`generated_secrets`, materialised by `container::secrets`, 0600/rootless) — never hardcoded or per-app baked in.
## Solution
Tracked as requirement **FED-07** (Phase 1, ROADMAP success criterion 7 — needs a gap plan since phase 1's 10 plans predate it):
- Find where the fedimint gateway app's manifest/install path sets its password (check `/opt/archipelago/apps/*/manifest.yml` for the gateway, and the orchestrator's secrets materialisation).
- Move it to `generated_secrets` so each install gets a unique credential, or require explicit user setup.
- Migration path for existing installs still carrying the default password (migrations must never destroy data).
@@ -0,0 +1,19 @@
---
created: 2026-07-30T22:19:31.770Z
title: Keep FIPS/Tor pills on cloud files and show them on mobile
area: ui
severity: blocker
files:
- neode-ui/src/views/Cloud.vue
- neode-ui/src/views/CloudFolder.vue
---
## Problem
Dorian loves the FIPS/Tor pills shown on cloud files ("really helpful") — they must be kept and never removed in any future cleanup or refactor. However, they do not (or may not) display on mobile widths, where they are hidden or cramped. Dorian rated mobile visibility a blocker: on mobile, users genuinely can't see the security/transport state of their files.
Context: captured 2026-07-30 during phase 02 (ui-performance) execution. The pills live in the Cloud file listing UI (Cloud.vue / CloudFolder.vue area — verify exact component when picking this up).
## Solution
Ensure the FIPS/Tor pills render at mobile breakpoints in the cloud file views — likely responsive-layout work (wrap/stack/compact-pill variant) rather than new functionality. Must follow the standing rule: no changes to existing desktop visuals/animations — mobile-only additions. TBD exact approach after inspecting the component at mobile widths.
@@ -0,0 +1,54 @@
---
created: 2026-07-30T22:19:31.770Z
title: Migrate all VPS2 references to https://source.archipelago-foundation.org (stop exposing IP; TLS everywhere)
area: security
severity: blocker
files:
- apps/*/manifest.yml
- app-catalog/catalog.json
- neode-ui/public/catalog.json
- core/archipelago/src (update.rs, container/registry.rs, image_versions.rs, image_policy.rs, quadlet.rs, prod_orchestrator.rs, app_catalog.rs, companion.rs, api/rpc/package/*)
- core/container/src/podman_client.rs
- core/archipelago/src/fips/anchors.rs
- scripts/self-update.sh
- scripts/first-boot-containers.sh
- scripts/create-release.sh
- scripts/create-release-manifest.sh
- scripts/image-versions.sh
- scripts/generate-app-catalog.sh
- scripts/bootstrap-switchover.sh
- release-manifest.json
- releases/manifest.json
- tests/lifecycle/remote-lifecycle.sh
- .gitea/workflows/demo-images.yml
- neode-ui/src/components/CompanionIntroOverlay.vue
---
## Problem
Dorian (2026-07-30, CRITICAL): every VPS2 service is referenced by raw IP `146.59.87.168` — plain HTTP for most — exposing the IP in shipped artifacts and requiring insecure-registry config on every node. Everything must move to `https://source.archipelago-foundation.org` "perfectly so all the stuff we use it for works".
**Verified inventory (2026-07-30):** ~196 references.
- `:3000` (Gitea — git/raw/releases AND the OCI container registry): **194 refs** — every app manifest's `image:` field, both catalogs (app-catalog/catalog.json + neode-ui/public/catalog.json, 23 each), compiled Rust core (self-update, registry allow-list, image policy/versions, quadlet gen, package RPCs, podman client), OTA manifests (release-manifest.json, releases/manifest.json), lifecycle tests, release/first-boot/self-update scripts, CI workflows, docs, UI marketplace data.
- `:2100` (companion APK package server): 1 ref (`CompanionIntroOverlay.vue`).
- `:8444` (FIPS trust anchor, raw TLS — `ARCHY_ANCHOR_ADDR` in `fips/anchors.rs`): 1 ref.
**Domain coverage verified live (2026-07-30):**
- git clone/ls-remote over HTTPS: ✓ (works; `gitea-ai` remote already switched)
- Gitea raw + releases: ✓ (HTTP 200)
- Container registry `/v2/` through the domain: ✓ (proper 401 auth-challenge; TLS pulls will work and the insecure-registry hack becomes removable)
- `:2100` APK server: ✗ NOT behind the domain — needs an openresty route/subdomain on VPS2 (Dorian/VPS2-side)
- `:8444` FIPS anchor: ✗ raw TLS service, can't share 443 — at minimum switch the constant to a DNS name (e.g. anchor host + :8444); check whether anchors pin certs before touching (security-sensitive)
## Solution
Execute as a dedicated quick task IMMEDIATELY AFTER phase 2's 02-08 (don't disturb the dev-pair perf measurements; requires core Rust rebuild + node verification per CLAUDE.md discipline):
1. Mechanical rewrite of all `:3000` refs → `https://source.archipelago-foundation.org` (registry image refs become `source.archipelago-foundation.org/lfg2025/<img>:<tag>` — no port, implies 443/TLS).
2. **Image-ref transition safety:** running containers/quadlets store the OLD image name; migration must not orphan or re-pull-break existing apps (migrations never destroy data). Plan the transition: catalog+manifests flip, orchestrator treats old/new registry names as equivalent during adoption, or staged re-tag.
3. Rebuild core, run the lifecycle gate on a dev node (NOT the fleet), verify: self-update fetch, catalog fetch, image pull via domain, app install end-to-end.
4. Keep the IP endpoints alive on VPS2 during transition — nodes updating FROM old releases still hit IP URLs (fallback window until fleet rolls past).
5. `:2100` + `:8444`: Dorian is setting these up (2026-07-30) as:
- `companion.archipelago-foundation.org` → replaces `http://146.59.87.168:2100` (companion APK downloads; update `CompanionIntroOverlay.vue` + any packaging docs). HTTPS assumed — verify before flipping.
- `fips.archipelago-foundation.org` → replaces the raw IP in `ARCHY_ANCHOR_ADDR` (`fips/anchors.rs`), becoming `fips.archipelago-foundation.org:8444` (or whatever port Dorian lands on). SECURITY-SENSITIVE: check whether the FIPS anchor validation pins certificates/identity to the address before changing; verify anchor handshake against the new name from a dev node before fleet rollout.
- As of 2026-07-30 22:50 UTC neither subdomain resolved yet — re-verify DNS + TLS at execution time; do not flip these two refs until both probes pass.
6. Remove insecure-registry config from node provisioning once pulls verify over TLS.
@@ -0,0 +1,16 @@
---
created: 2026-07-30T22:19:31.770Z
title: Add loader states to surfaces that take time to open
area: ui
severity: major
files:
- neode-ui/src/views (inventory needed — which opens are slow with no loading affordance)
---
## Problem
Dorian (2026-07-30): "some stuff doesn't have loader states when it's gonna take time to open" — certain screens/actions with genuinely slow opens give no loading feedback, so the UI feels dead/broken while working.
## Solution
Inventory slow-open surfaces (02-08's PERF-AFTER timings + the flagged timing regressions are a starting list: Discover, Server, Web5, Fleet, AppDetails, OpenWrtGateway); add house-style skeletons/spinners where an open can exceed ~300ms, consistent with the phase 2 rule that cached revisits show no spinner. Clearest confirmed instance (02-08 classification, pre-existing since 2026-07-22): `Cloud.vue` `viewPaidItem()`'s `content.owned-get` call has a 60s timeout and zero loading indicator. `CloudFolder.vue`'s native file grid already has a skeleton (fine). Tracked as UIFIX-06 (phase 1).
@@ -0,0 +1,22 @@
---
created: 2026-07-30T22:19:31.770Z
title: Onboarding tickbox hidden below fold on short screens — make it beautifully obvious
area: ui
severity: blocker
files:
- neode-ui/src/views/OnboardingSeedGenerate.vue
- neode-ui/src/views/OnboardingWrapper.vue
---
## Problem
Dorian reports (2026-07-30): on shorter screens, the onboarding screen's tickbox (the seed-phrase confirmation checkbox in `OnboardingSeedGenerate.vue` — verify it's this step) sits below the fold inside a scrollable area, and it's not obvious it exists — users don't realize they need to scroll to find it, so onboarding appears stuck. Blocker: this stalls new-user onboarding on small displays.
## Solution
Make its presence obvious "in a beautiful way" (Dorian's words — design discretion, but it must feel intentional and match the house glass/dark style, not a bolted-on arrow). Candidate approaches to evaluate:
- A soft bottom scroll-affordance: gradient fade at the container's bottom edge plus a subtle animated chevron/pulse when unscrolled content remains, disappearing once the tickbox is in view.
- Or make the tickbox + continue button a sticky footer of the card on short viewports so it's always visible, with the seed content scrolling behind it.
- Or auto-scroll/nudge the container after the seed is revealed.
Constraints: follow the standing rule — don't alter the existing onboarding look beyond this affordance; keep desktop/tall-screen appearance unchanged; whatever ships must be verified on an actual short viewport (e.g. small laptop / mobile landscape).
@@ -0,0 +1,16 @@
---
created: 2026-07-30T22:19:31.770Z
title: Paid Files pictures open in a browser tab instead of the app lightbox
area: ui
severity: major
files:
- neode-ui/src/views/Cloud.vue (Paid Files sub-tab — locate the picture click handler)
---
## Problem
Dorian (2026-07-30, on archi-dev-box; initially said "peer files", corrected to **Paid Files**): in the Cloud tab's Paid Files section, clicking a picture opens it in a new browser tab rather than the in-app lightbox that exists elsewhere ("the lightbox we've made"). Inconsistent media UX.
## Solution
Route Paid Files image clicks through the existing lightbox component. Classified by 02-08 (pre-existing, introduced f3393581 2026-07-22): `Cloud.vue`'s `viewPaidItem()` (~line 470) calls `window.open(...)` instead of routing into `MediaLightbox.vue`. Fix: invoke MediaLightbox the way My Files does. Tracked as UIFIX-04 (phase 1).
@@ -0,0 +1,23 @@
---
created: 2026-07-30T22:19:31.770Z
title: PiP robustness — close lightbox fluidly, survive tab changes and buffering
area: ui
severity: major
files:
- neode-ui/src (lightbox/video player components — locate exact files)
---
## Problem
Dorian (2026-07-30), two reports:
1. Entering picture-in-picture on a video does not close the lightbox — it should close with a fluid on-brand animation as PiP begins.
2. "Picture in picture must survive tab changes or video buffering pauses, doesn't seem to, please fix." — PiP currently dies when switching main tabs and/or when the video pauses to buffer.
Classified by 02-08 (all pre-existing, NOT phase 2): `src/utils/pip.ts`'s `togglePip()` (introduced f72d4b92 2026-07-23) never touches `MediaLightbox.vue`'s visibility state; no `onDeactivated` teardown exists in Cloud/CloudFolder/MediaLightbox (phase 2 added none there); pre-phase-2 tab switches fully unmounted the view, so PiP tab-survival never existed. The `<video>` has zero pause/waiting/stalled listeners — buffering behavior is pure native. KeepAlive actually helps now: the element persists off-screen, so survival is achievable by not letting anything pause/unmount it.
## Solution
- PiP enter → close the lightbox with a house-style fluid animation (handoff feel).
- While PiP is active: the owning video element must not be paused/reset/unmounted by tab deactivation — exempt media playback from deactivate teardown when `document.pictureInPictureElement` is the view's video (or reparent the video to a persistent host).
- Buffering (`waiting` events / `pause` from buffering) must not close PiP or the session — only explicit user stop should.
Tracked as UIFIX-05 (phase 1 gap plan).
@@ -0,0 +1,32 @@
---
created: 2026-07-31T19:00:00.000Z
title: Build the installer ISO for v1.7.119-alpha
area: tooling
severity: minor
files:
- scripts/build-iso-release.sh
---
## Problem
v1.7.119-alpha is released and its OTA assets are published and checksum-verified, but the **installer ISO was never built**. Three attempts were blocked by a dirty working tree — `build-iso-release.sh`'s preflight hard-fails on any uncommitted change, and this checkout is shared by phase 1 executors and a separate BotFights session, so the tree is rarely clean for long. Not urgent: the ISO only matters for fresh installations; everyone updating is served by the working OTA.
## Solution
Run during a genuine pause in phase 1 execution (between waves), not racing against it:
```
TMPDIR=/home/archipelago/tmp/iso-scratch bash scripts/build-iso-release.sh
```
Keep the QEMU boot test (no `--no-qemu`).
**Preconditions:** `git status --porcelain` empty on `main`.
**Launch it as an explicit background job.** The build runs well past 10 minutes; twice it hit the tool's foreground timeout and once that killed an in-progress `cargo test` outright.
**Build from current `main` HEAD, not a detached tag checkout** — decision already made, don't re-derive. Preflight requires `git rev-parse --abbrev-ref HEAD == "main"` (a detached checkout returns `HEAD` and fails), and the archipelago version identity (Cargo.toml / package.json / CHANGELOG / manifest, all `1.7.119-alpha`) is untouched by commits made after the tag. BotFights is versioned and shipped independently via the signed catalog, so building at HEAD ships the security-fixed 1.2.11 rather than reverting to the vulnerable 1.2.9 current at tag time.
**Gates already seen passing** (mostly cached, expect them to pass fast): git-diff-check, cargo-fmt, catalog-drift, whats-new-sync, ui-type-check, ui-unit-tests (788), cargo-check. **Not yet reached:** cargo-test-weekly (~1500s), verify-artifacts, build-iso, iso-smoke, qemu-boot.
`TMPDIR` matters while `/tmp` (12G tmpfs) is tight — honored natively by mktemp-based scripts and by `test-iso-qemu.sh` since commit `6c2b6668` fixed its hardcoded `/tmp` paths.
@@ -0,0 +1,60 @@
---
created: 2026-08-01T04:05:00.000Z
title: Make archi-dev-box double as a fresh test node (both shapes, no ISO flash)
area: testing-infra
severity: major
files:
- core/archipelago/src/config.rs (the env seams: ARCHIPELAGO_DATA_DIR:125, ARCHIPELAGO_BIND:129, ARCHIPELAGO_PORT_OFFSET:155, ARCHIPELAGO_APPS_DIR)
- core/container/src/port_manager.rs (port_offset applied at :44)
- core/archipelago/src/auth.rs (:182 is_onboarding_complete — "fresh" is decided purely from state inside data_dir)
- tests/lifecycle/ (existing gate harness — candidate host for the new node profile)
---
## Problem
Dorian (2026-08-01): wants archi-dev-box to serve as a testing node that can be exercised
"as if it's a new node", running alongside the existing Linux desktop app install, **without
flashing the ISO**. Both shapes are wanted — this is a testing node, so it needs to cover
first-run UX *and* real app lifecycle.
Today the box runs one real production-ish node: `archipelago.service` (systemd, enabled,
`/usr/local/bin/archipelago`) against a heavily-populated `/var/lib/archipelago` (bitcoin,
btcpay, botfights, blobs, live LND/mesh state). That node must not be disturbed — it is the
dev-pair deploy target gated before every OTA.
## Solution
Two shapes, both ISO-free. Ship A first, then B.
**(A) Lightweight second instance, same Linux user.** Own empty `ARCHIPELAGO_DATA_DIR`, own
`ARCHIPELAGO_BIND` port, `ARCHIPELAGO_PORT_OFFSET` set, mesh/Reticulum disabled. Boots
un-onboarded, so it exercises the true first-run path: seed generation, password/setup,
identity keygen, node naming, onboarding UI. Cheap to create and destroy — the natural
regression harness for onboarding changes.
**(B) Second Linux user** (`useradd` + `loginctl enable-linger` + its own rootless podman
namespace + own data dir). Gives a genuinely independent node where app install / uninstall /
reinstall lifecycle is real, not shared. This is what makes it a *testing node* rather than a
first-run mock.
## Hazards (verified by grep 2026-08-01, must be designed around)
1. **Hardcoded paths defeat `ARCHIPELAGO_DATA_DIR`.** Several constants point at
`/var/lib/archipelago` literally and ignore the override: `bitcoin_rpc.rs:10`
(`SECRETS_PATH`), `container/lnd.rs:131` (`ARCHY_DATA_DIR`), `electrs_status.rs:15`,
`api/rpc/package/pine_ha.rs:34-36`, `bootstrap.rs:242` (secrets dir), `disk_monitor.rs:41`.
Under shape (A) a test instance that installs **Bitcoin, LND, electrumx, or Pine/HA would
read and write the LIVE node's files.** Those four are off-limits in (A); shape (B) fixes
this properly via a different user's paths — or the constants get plumbed through config,
which is arguably the real fix and a candidate follow-up.
2. **Rootless podman is per-Linux-user.** In (A) both instances share one container
namespace: `PORT_OFFSET` resolves port collisions, container *name* collisions it does not.
3. **Reticulum/mesh contention.** The live daemon holds `/dev/mesh-radio` and
`identity/node_key`; a second instance would fight it for the radio. Mesh must be off for
the test node (or the radio explicitly assigned to one of them).
## Notes
Raised while resuming quick task 260731-upz (entropy/seed audit) — shape (A) is also the
natural on-node harness for that audit's UNVERIFIED checklist, since fresh-seed generation is
exactly the `[ARCHY-1]` path under review. Sequence this after 260731-upz lands.
@@ -0,0 +1,80 @@
---
created: 2026-08-01T09:45:00.000Z
title: Verify FED-07 gateway credential rotation on a real node (01-16 Task 2)
area: security
severity: major
files:
- core/archipelago/src/container/secrets.rs
- core/archipelago/src/container/prod_orchestrator.rs
- .planning/phases/01-federation-mesh-hardening/01-16-PLAN.md
---
## Problem
FED-07's code is complete and pushed (`42652547` removed every shipped credential,
`9e2d2ef2` added detection + rotation for nodes already carrying one), but **plan 01-16's
blocking Task 2 checkpoint has never been run**, so the requirement is not closed. Two
things are unproven:
1. **That the recreate actually preserves the gateway's data.** Rotation deliberately
avoids any teardown: it changes the credential, which changes `secret_env_hash`, which
the drift check reads as a container-label mismatch, which fires the platform's own
recreate around the unchanged data directory, ports, volumes and container name. That
chain is confirmed by reading the code (`prod_orchestrator.rs:3309` computes the hash,
`:3374` compares the label) but has **never been observed running**.
2. **That the rotated credential actually authenticates** and the old shipped one is
rejected.
## Why it was deferred (2026-08-01)
Checkpoint step 1 was run read-only on archi-dev-box. Findings:
- **The node is CLEAN** — the hash file is present, `600`, `archipelago:archipelago`, and
is *not* the shipped default. The legacy `fedimint-gateway-password` file also exists.
- **There is no `fedimint-gateway` container.** The app is installed at
`/opt/archipelago/apps/fedimint-gateway`, but nothing is running and
`/var/lib/archipelago/fedimint-gateway` is empty. `archy-fedimint-ui` and
`fedimint-clientd` are running, but they are not the gateway.
So the rotation path cannot fire naturally here, and steps 56 (data survived, gateway
authenticates) have nothing to exercise. Proving it on this box means installing and
starting the gateway first, then deliberately seeding the old value — which the plan's
Planner Assumption already anticipates and requires be recorded.
The deploy itself is the blocker: **30 containers are running with 48 days uptime**
(IndeeHub relay/minio/postgres/ffmpeg, Immich, BTCPay + nbxplorer, netbird, portainer,
strfry, searxng, the archy UI set), the `archipelago` system service is active, and
CLAUDE.md's standing rule is that restarting it SIGKILLs containers until Quadlet is the
default. Step 8 then wants `tests/lifecycle/run-gate.sh` on the same box, which cycles
install/stop/start/reinstall/reboot-survive. Dorian chose to hold rather than take that
blast radius unattended.
## Solution
Run 01-16 Task 2's eight steps in a window where restarting `archipelago` on the target is
acceptable. Two viable routes:
- **On archi-dev-box:** install + start `fedimint-gateway`, put some state in its data
directory, seed the old hash into
`/var/lib/archipelago/secrets/fedimint-gateway-hash`, deploy this build, restart, then
run steps 38. Record that the affected state was seeded deliberately.
- **On a node that runs the gateway for real** (.228 is the heavy 14-app node): the
rotation path may exist naturally there, which is stronger evidence than seeding.
Either way, record in `01-16-SUMMARY.md`: the affected-or-clean verdict, the exact deploy
command, whether the `secret_env_hash` recreate was observed firing, the gate result, and
the fact that archy-x250-dev was offline (single-node verification with the second-node
gap recorded honestly is the expected pattern).
## Related, found while doing 01-16
- **Operator recovery gap:** `handle_package_credentials`
(`core/archipelago/src/api/rpc/package/install.rs:2093`) is a hardcoded if-chain covering
only `filebrowser` and `photoprism`. `fedimint-gateway` falls through to an empty list,
so after a rotation the operator has no in-UI way to get the new password — only the
`0600` file the log line names. Adding one arm closes it; the UI already renders whatever
the RPC returns.
- **`apps/photoprism/manifest.yml:35` ships `PHOTOPRISM_ADMIN_PASSWORD=archipelago`**, and
that same literal is handed to the UI. Same defect class as FED-07 on a different app —
wants its own requirement.
</content>