Demo images / Build & push demo images (push) Failing after 2m22s
Replaces the registry host across 86 files: 309 references, covering all 40 app manifests, the orchestrator and container crates, the release and catalog scripts, both demo-images workflows, the ISO builder, demo-deploy, and the frontend marketplace data. Verified the domain actually serves the registry before rewriting anything, rather than assuming the web host implies the registry: - TLS verifies clean, HTTP/2 on the web root - an anonymous token grants a manifest fetch (HTTP 200) with no credentials - skopeo inspect --no-creds resolves an image and lists its tags That last check is the one that matters: an outside developer with no account can now pull, which was the functional blocker for publishing at all. Plain-HTTP references become HTTPS in the same pass, so OTA downloads stop crossing the network in the clear. Deliberately NOT rewritten: - The public FIPS anchor on port 8444. It is a functional network endpoint every node dials to bootstrap the mesh — closer to Bitcoin Core's hardcoded seeds than to leaked infrastructure. The domain does resolve to the same host, so it could become a hostname, but that adds a DNS dependency to the path used precisely when things are broken. Worth a deliberate decision, not a side effect of this change. - The companion APK on port 2100. The domain returns 404 for that path, so rewriting it would swap a working URL for a broken one. The Releases page does serve (200), which is where the plan already wants those binaries. - releases/app-catalog.json, releases/manifest.json and release-manifest.json. These carry `signature` and `signed_by`; editing their contents invalidates the signature and the fleet refuses artifacts that fail verification. They were rewritten in a first pass and reverted — they must be regenerated and re-signed through the signing ceremony instead, which needs the mnemonic. So the catalog still advertises the old host until that ceremony runs. Nodes resolve images through the signed catalog, not the on-disk manifests, so this commit alone does not change what a node pulls. Verified: archipelago-container 75/75; every manifest still parses with a top-level app block; no signed artifact modified. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
170 lines
8.1 KiB
Markdown
170 lines
8.1 KiB
Markdown
# Public Demo Deployment — Design
|
|
|
|
**Status:** design (2026-06-22)
|
|
**Goal:** a public, click-to-play demo of the Archipelago UI that **auto-tracks
|
|
the real code** yet stays **separated** from the private monorepo and its
|
|
secrets/backend. Deployed via **Portainer**, mock-data driven, with working file
|
|
storage and a testnet-flavored Bitcoin sandbox so visitors can play freely.
|
|
|
|
See also: `neode-ui/mock-backend.js` (existing mock), `docker-compose.demo.yml`
|
|
(existing demo stack), `MEMORY → reference_neode_ui_dev_testing`,
|
|
`MEMORY → reference_ovh_168_mirror` (Portainer/registry host).
|
|
|
|
---
|
|
|
|
## 1. What already exists (the 70%)
|
|
|
|
The demo is mostly built. Inventory:
|
|
|
|
| Asset | Path | State |
|
|
|-------|------|-------|
|
|
| Mock backend (Node/Express + ws) | `neode-ui/mock-backend.js` (~3,862 lines) | 95+ JSON-RPC methods: auth, package lifecycle, Bitcoin/LND wallet, mesh, federation, identity, monitoring, mock filebrowser |
|
|
| Mock data | `mockData` / `walletState` / `MOCK_FILES` in `mock-backend.js` | rich; 10 pre-installed apps, 30+ marketplace apps, wallet balances, seeded files (Music/Documents/Photos/Videos) |
|
|
| Demo compose | `docker-compose.demo.yml` | `neode-backend` (mock, `:5959`) + `neode-web` (nginx, `:4848`); header already says "Deploy via Portainer" |
|
|
| Backend image | `neode-ui/Dockerfile.backend` | Node 22 Alpine → `node mock-backend.js` |
|
|
| Web image | `neode-ui/Dockerfile.web` | multi-stage `vite build` → nginx |
|
|
| Demo nginx | `neode-ui/docker/nginx-demo.conf` | proxies `/rpc/v1`, `/ws`, `/app/*` to the mock backend |
|
|
| Precedent | `indee-demo` Portainer stack | separate stack referencing a **pre-built image** — the pattern we extend |
|
|
|
|
**Gaps for a *public* (not dev) demo:** state is global (visitors collide),
|
|
uploads are no-ops, Bitcoin block height is hardcoded, no CI image pipeline, no
|
|
separated public deploy repo.
|
|
|
|
---
|
|
|
|
## 2. Architecture: source in monorepo, demo ships as images, public repo is thin
|
|
|
|
The tension — "must update as I update the real code" **and** "sort of
|
|
separated" — is resolved by separating at the **deploy layer, not the source
|
|
layer**.
|
|
|
|
```
|
|
monorepo (private — single source of truth)
|
|
neode-ui/ + mock-backend.js
|
|
│ push to main
|
|
▼
|
|
CI: build archy-demo-web + archy-demo-backend
|
|
│ push :demo / :latest
|
|
▼
|
|
registry (source.archipelago-foundation.org / vps2)
|
|
│ Portainer webhook / re-pull
|
|
▼
|
|
archy-demo (public repo — tiny)
|
|
docker-compose.yml ──referencing pre-built images──▶ Portainer ▶ demo.<host>
|
|
.env.example
|
|
```
|
|
|
|
- **Single source of truth = the monorepo.** `neode-ui/` and `mock-backend.js`
|
|
stay where they are, so the demo tracks real code automatically — no fork to
|
|
sync, no drift.
|
|
- **Separation = the public repo never holds source.** `archy-demo` contains only
|
|
a `docker-compose.yml` (image refs) + `.env.example` + README. No Rust backend,
|
|
no secrets, no UI source. Safe to make public.
|
|
- **Auto-update flow:** edit code → push → CI rebuilds demo images → Portainer
|
|
redeploys. The public compose file is touched rarely (only when service shape
|
|
changes).
|
|
|
|
**Why not a true fork / `git subtree split`?** It works but needs a sync job
|
|
*and* re-exposes UI source publicly. The image pipeline gives stronger
|
|
separation (zero source leak) **and** zero manual sync. (Decided 2026-06-22.)
|
|
|
|
---
|
|
|
|
## 3. Work items
|
|
|
|
### 3.1 CI image pipeline
|
|
- On push to `main` (path filter: `neode-ui/**`), build:
|
|
- `archy-demo-backend` from `neode-ui/Dockerfile.backend`
|
|
- `archy-demo-web` from `neode-ui/Dockerfile.web` (`build:docker`)
|
|
- Tag `:demo` + `:<git-sha>`, push to the registry.
|
|
- Trigger Portainer redeploy (stack webhook) on success.
|
|
|
|
### 3.2 Public `archy-demo` repo
|
|
- `docker-compose.yml` mirroring `docker-compose.demo.yml` but **`image:`
|
|
references instead of `build:`** (pull `:demo`, no build context).
|
|
- `.env.example` (`ANTHROPIC_API_KEY`, `VITE_DEV_MODE=existing`, session TTL,
|
|
upload quota).
|
|
- README: one-paragraph "deploy in Portainer → web editor paste / deploy from
|
|
repo," access on `:4848`.
|
|
- No source. This is the only public surface.
|
|
|
|
### 3.3 Multi-user: per-session sandbox (reset on idle) ⟵ *decided*
|
|
The biggest code change. Today `mockData` / `walletState` / `MOCK_FILES` are
|
|
**global singletons** → visitors corrupt each other's view.
|
|
- Issue a `demo-session` cookie on first hit (the mock already sets a session on
|
|
login; extend it to anonymous visitors).
|
|
- Key state by session id: `sessions[sid] = { mockData, walletState, files }`,
|
|
each **deep-cloned from a pristine seed** on creation.
|
|
- Reap on idle (e.g. 30 min no activity) + hard cap concurrent sessions; on reap,
|
|
free memory + temp dir.
|
|
- RPC dispatch + WS patches resolve the per-session state instead of the global.
|
|
- Keeps the demo a true playground: install/uninstall/spend freely, reset by
|
|
reconnecting.
|
|
|
|
### 3.4 File storage: persisted per session ⟵ *decided*
|
|
Today filebrowser upload/delete/rename are 200-OK no-ops.
|
|
- Back each session with a temp dir (e.g. `/tmp/demo/<sid>/`), seeded from
|
|
`MOCK_FILES`.
|
|
- Make `POST/DELETE/PATCH /app/filebrowser/api/resources/*` and `GET …/raw/*`
|
|
read/write that dir. Enforce a per-session quota (e.g. 50 MB) and reject
|
|
oversize/odd MIME.
|
|
- Cleaned when the session is reaped — no standing public writable volume, no real
|
|
filebrowser container to harden.
|
|
|
|
### 3.5 Bitcoin: testnet-flavored mock ⟵ *decided*
|
|
- Relabel wallet/chain as **testnet/signet**: `tb1q…` addresses, "testnet" chain
|
|
in `bitcoin.getinfo`, scripted-but-plausible block height + confirmations.
|
|
- Keep `dev.faucet` as the in-UI "get test sats" button (instant, free).
|
|
- No real `bitcoind` → no sync, no disk, no public RPC attack surface.
|
|
- *Future upgrade path:* swap to a real signet node + LND in the stack if we ever
|
|
want movable real test sats (out of scope now).
|
|
|
|
### 3.6 Mock containers / app lifecycle
|
|
- The mock already simulates `package.install/uninstall/start/stop/restart`
|
|
asynchronously. For the demo, **force simulation mode** (never touch a real
|
|
Docker socket — rootless/safe and host-independent). Confirm no path in
|
|
`mock-backend.js` reaches for a real runtime when `DEMO=1`.
|
|
|
|
### 3.7 Mock-data refresh
|
|
- Update `mockData` static apps + marketplace to current app set/versions, refresh
|
|
wallet figures, seeded mesh messages, and files so the demo feels current. This
|
|
is ongoing and rides the same image pipeline.
|
|
|
|
---
|
|
|
|
## 4. Invariants / guardrails (public exposure)
|
|
|
|
- **No real secrets, no real backend, no real Docker socket** in the demo image or
|
|
public repo. Mock password stays a known demo credential, clearly labeled.
|
|
- **Per-session isolation** is a hard requirement before going public — without it
|
|
the demo is unusable for strangers.
|
|
- **Resource caps:** session count, per-session memory + upload quota, idle reap;
|
|
the box can't be DoS'd into OOM by upload spam or session churn.
|
|
- **`ANTHROPIC_API_KEY`** (chat) is injected via Portainer env, never committed;
|
|
rate-limit / budget-cap demo chat usage.
|
|
- **Read-only registry creds** for the Portainer host to pull `:demo`.
|
|
|
|
---
|
|
|
|
## 5. Files / seams
|
|
|
|
| Concern | Where |
|
|
|---------|-------|
|
|
| Per-session state, file persistence, testnet labels, sim-mode | `neode-ui/mock-backend.js` |
|
|
| Build contexts (reused as-is) | `neode-ui/Dockerfile.backend`, `neode-ui/Dockerfile.web`, `neode-ui/docker/nginx-demo.conf` |
|
|
| Demo stack (in-repo, dev) | `docker-compose.demo.yml` (keep `build:`) |
|
|
| Public stack (new repo) | `archy-demo/docker-compose.yml` (`image:` refs), `.env.example`, README |
|
|
| CI pipeline | new workflow (path filter `neode-ui/**` → build + push `:demo` → Portainer webhook) |
|
|
|
|
---
|
|
|
|
## 6. Open questions
|
|
|
|
1. **Demo host** — which Portainer instance (OVH `.168`? a dedicated VPS)? Public
|
|
DNS + TLS for `demo.<domain>`?
|
|
2. **Registry for `:demo` images** — `source.archipelago-foundation.org` vs vps2; public-pull or
|
|
creds baked into Portainer?
|
|
3. **Session TTL + concurrency cap** — concrete numbers (30 min / N sessions / 50 MB)?
|
|
4. **Chat in the demo** — enable Claude chat (needs key + budget cap) or stub it?
|
|
5. **Sync cadence** — rebuild `:demo` on every `neode-ui/**` push, or nightly?
|