diff --git a/CLAUDE.md b/CLAUDE.md index 22ec728d..9de0aeb1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -26,6 +26,28 @@ Detailed sub-plans (all linked from the master): - Current per-app state → `docs/app-registry-status-2026-06-21.md` - Production test gate (exit criterion) → `tests/lifecycle/TESTING.md` +## Commit & push every unit of work (never violate) + +**The #1 process rule: work is not "done" until it is committed AND pushed.** This +exists because finished work has been lost/clobbered by sitting uncommitted in the +shared tree across agents and sessions. To prevent that: + +- **Commit each feature/fix the moment it works** — one focused, self-contained + commit per logical change (it compiles and its targeted tests pass). Do not let + unrelated changes accumulate uncommitted. +- **Push immediately after committing** so nothing lives only on one machine. `main` + is protected → push via `git push gitea-ai main` (account `ai`, see the memory + note); feature branches push to their own remote. +- **Never leave a stack of finished work uncommitted** overnight or when handing off + between agents — if you must pause mid-change, commit a clearly-labelled WIP + checkpoint rather than leaving it dirty. +- **Stage explicitly by path** (`git add `) when another agent's uncommitted + work shares the tree — never `git add -A` / `git commit -a`, which clobbers or + entangles their changes. +- **Never commit or push secrets** (mnemonics, private keys, API tokens). Signing is + done offline; artifacts (catalog/manifest) are signed, not the keys. +- Commit messages end with the `Co-Authored-By: Claude …` trailer. + ## Invariants (never violate) - **Rootless Podman only.** No rootful, no Docker-socket mounts, no privileged diff --git a/core/archipelago/src/trust/anchor.rs b/core/archipelago/src/trust/anchor.rs index 41243d76..8498532e 100644 --- a/core/archipelago/src/trust/anchor.rs +++ b/core/archipelago/src/trust/anchor.rs @@ -16,9 +16,11 @@ use ed25519_dalek::VerifyingKey; /// Hex of the pinned Ed25519 release-root public key (32 bytes / 64 hex chars). /// -/// Pinned 2026-07-01 (signer did:key:z6MkkidEnEpo6qHMCNSZoNKWtvQvxq3whnaME9wGgEFhq7ur). -/// The corresponding mnemonic is held offline by the publisher — see -/// `docs/workstream-b-signing-runbook.md` for the ceremony that produced this. +/// Pinned 2026-07-02 from the release-root signing ceremony +/// (signer did:key:z6MkkidEnEpo6qHMCNSZoNKWtvQvxq3whnaME9wGgEFhq7ur). The +/// corresponding mnemonic is held offline by the publisher — see +/// `docs/workstream-b-signing-runbook.md`. Regenerate/verify with: +/// `RELEASE_MASTER_MNEMONIC=… archipelago ceremony pubkey`. pub const RELEASE_ROOT_PUBKEY_HEX: Option<&str> = Some("5d15cbee8a108f7dd288c02d29a1d9d71f198acc99186aad8008b4f28d469951"); @@ -53,9 +55,14 @@ mod tests { use super::*; #[test] - fn unset_constant_is_none() { - // Default build ships no pinned anchor yet. - assert!(RELEASE_ROOT_PUBKEY_HEX.is_none()); + fn pinned_constant_parses_to_a_valid_key() { + // The release-root anchor is pinned (ceremony 2026-07-02); it must be + // present and a well-formed 32-byte Ed25519 key. + let hex = RELEASE_ROOT_PUBKEY_HEX.expect("release-root anchor must be pinned"); + assert!( + parse_pubkey_hex(hex).is_some(), + "pinned RELEASE_ROOT_PUBKEY_HEX is not a valid Ed25519 key" + ); } #[test] diff --git a/core/archipelago/src/trust/signed_doc.rs b/core/archipelago/src/trust/signed_doc.rs index a0147894..1eb0fb98 100644 --- a/core/archipelago/src/trust/signed_doc.rs +++ b/core/archipelago/src/trust/signed_doc.rs @@ -149,6 +149,18 @@ mod tests { doc } + /// Pin `test_key` as the release-root anchor for this process via the env + /// override. Needed because the baked-in `RELEASE_ROOT_PUBKEY_HEX` is the + /// real ceremony key, which no unit test can produce signatures for — so to + /// exercise the anchored-verification path we pin a key we can sign with. + /// Every call sets the same value, so parallel tests stay consistent. + fn pin_test_key_as_anchor() { + std::env::set_var( + "ARCHY_RELEASE_ROOT_PUBKEY", + hex::encode(test_key().verifying_key().to_bytes()), + ); + } + #[test] fn unsigned_document_reports_unsigned() { let doc = json!({"schema": 1, "apps": {}}); @@ -156,18 +168,31 @@ mod tests { } #[test] - fn roundtrip_verifies() { + fn roundtrip_verifies_and_anchors_to_pinned_key() { + // With the anchor pinned to the signer, verification succeeds AND + // reports anchored == true (signer identity confirmed). + pin_test_key_as_anchor(); let signed = sign_into(&test_key(), json!({"schema": 1, "n": 42})); match verify_detached(&signed).unwrap() { - // No anchor pinned in the default test build → anchored == false. - SignatureStatus::Verified { anchored, .. } => assert!(!anchored), + SignatureStatus::Verified { anchored, .. } => assert!(anchored), other => panic!("expected Verified, got {:?}", other), } } + #[test] + fn signature_from_non_anchor_key_is_rejected() { + // A self-consistent signature from a key that is NOT the pinned anchor + // must hard-reject — this is what stops a mirror swapping in its own key. + pin_test_key_as_anchor(); + let other_key = SigningKey::from_bytes(&[11u8; 32]); + let signed = sign_into(&other_key, json!({"schema": 1, "n": 42})); + assert!(verify_detached(&signed).is_err()); + } + #[test] fn signature_survives_key_reordering() { // Re-emitting the document with shuffled keys must not break the sig. + pin_test_key_as_anchor(); let signed = sign_into(&test_key(), json!({"b": 2, "a": 1})); let reparsed: Value = serde_json::from_str(&serde_json::to_string(&signed).unwrap()).unwrap(); @@ -179,6 +204,9 @@ mod tests { #[test] fn tampered_payload_is_rejected() { + // Pin the signer so verification reaches the signature check (not an + // anchor-identity short-circuit), proving tamper detection itself. + pin_test_key_as_anchor(); let mut signed = sign_into(&test_key(), json!({"schema": 1, "n": 42})); signed .as_object_mut() diff --git a/docs/1.8.0-RELEASE-HARDENING-PLAN.md b/docs/1.8.0-RELEASE-HARDENING-PLAN.md new file mode 100644 index 00000000..51fb4ac8 --- /dev/null +++ b/docs/1.8.0-RELEASE-HARDENING-PLAN.md @@ -0,0 +1,301 @@ +# Archipelago 1.8.0 — Release Hardening Plan & Tracker + +> **The one living checklist for shipping 1.8.0.** Derived from a full-system deep +> audit (2026-07-02): backend security, backend code-quality, frontend, mesh, +> tests/release pipeline, and the ISO build. Supersedes nothing — it *sits above* +> `docs/UNIFIED-TASK-TRACKER.md` (day-to-day) as the release exit-criteria list. +> **Keep it updated: tick a box the moment an item lands, with the commit sha.** + +**Definition of done for 1.8.0:** the supply chain is authenticated end-to-end +(§A), OTA self-update is safe and rollback-proven on real hardware (§B), no +secrets ship in the image (§F), and the single-node gate stays 5/5 green through +all of it. Everything else is polish that should not block the tag. + +**Legend:** `[ ]` open · `[~]` in progress · `[x]` done · 🔴 critical · 🟠 high · +🟡 medium · 🟢 low/polish · ⛔ blocked on you. + +--- + +## 🎯 The single most important insight + +The **release signing ceremony (Workstream B) is the linchpin.** ✅ The ceremony +KEY was generated (user confirmed 2026-07-02) — the hard offline part is done. But +the outputs are **not yet wired into the repo**: `anchor.rs:21` is still `None` and +`releases/app-catalog.json` carries no `signature`/`signed_by` (its `image_signature` +fields are literal `"cosign://..."` placeholders). Three mechanical steps remain, +split by who can run them: **(1)** pin the pubkey — needs only the *public* hex, can +be done in-repo now; **(2)** sign the catalog with the `RELEASE_MASTER_MNEMONIC` — +only the publisher, secret never touches a host; **(3)** implement + flip cosign +enforcement on the pull path. Until (1)+(2) land, every "verify the signature" task +below is written but not enforced. **This is still the critical path; §A converges on it.** + +--- + +## §A — Supply-chain authentication (🔴 THE release blocker) + +Today an attacker who controls the mirror IP (or any MITM on the plaintext HTTP +path) can ship an arbitrary root binary, arbitrary container images, and an +arbitrary app catalog to the entire fleet — fully unattended under +`auto_apply`. These four items are one story and must land together. + +- [x] 🔴 **Pin `RELEASE_ROOT_PUBKEY_HEX` + sign the catalog** — DONE 2026-07-02. + `anchor.rs` pinned to `5d15cbee…d469951` (signer + `did:key:z6MkkidEnEpo6qHMCNSZoNKWtvQvxq3whnaME9wGgEFhq7ur`); trust tests updated (16/16 + green). `releases/app-catalog.json` signed in place (`signed_by` matches, 64-byte sig); + two blocking floats fixed en route (`archy-btcpay-db` version→string, `cpu_limit` 0.25→1). + Ship order (backward-compatible): signed catalog goes out first (old binaries still accept + it), pinned-anchor binary follows in the next build/OTA. **Still ahead:** (a) the + pinned-anchor binary must actually be built + shipped for enforcement to be live on nodes; + (b) flip "accept unsigned" → "reject unsigned" only after the whole fleet is on the pinned + binary (`container/app_catalog.rs:397`, the `Unsigned` arm) — see the next item. +- [ ] 🔴 **Enforce a signature on the OTA manifest before trusting it.** + `update.rs:68` fetches `http://146.59.87.168:3000/.../manifest.json` over cleartext + and parses/trusts it with no `trust::verify_detached` call; component sha256/blake3 + are only checked against that same unauthenticated manifest → remote root RCE. + Move to HTTPS + pinned cert, require an Ed25519 release-root signature, and + **refuse `auto_apply` until the anchor is pinned.** +- [ ] 🔴 **Implement container image signature verification (cosign).** + `container/src/podman_client.rs:255` — `pull_image(.., _signature)` silently discards + the signature that the manifest threads all the way down + (`prod_orchestrator.rs:1978/2435`). Wire `sigstore-rs`/`cosign verify` (or + `podman pull --signature-policy`); hard-fail when a declared signature doesn't verify. +- [ ] 🟠 **Move the image mirror to HTTPS; drop `--tls-verify=false`.** + `podman_client.rs:641` `INSECURE_REGISTRY_HOSTS = ["146.59.87.168:3000"]` + + `config.rs:104,124` allowlist pull images over unauthenticated HTTP. Remove the raw-IP + entries; give the mirror a valid/pinned cert. (Same host also baked insecurely into + the ISO — see §F.) +- [ ] 🟠 **Validate every image string at the pull site, not just the RPC boundary.** + `is_valid_docker_image` runs in `install.rs:224`/`runtime.rs:549` but + `prod_orchestrator::install_fresh` (1978) and `resolve_catalog_image` (944-971) pass + catalog/manifest images straight to `pull_image`. Call the validator right before + every pull. + +--- + +## §B — OTA self-update safety (🔴 1.8.0's headline feature is untested live) + +The apply path itself is well-built (resumable download, staged-complete marker, +atomic swap, single-depth backup). The gaps are **authenticity** (§A) and +**verification depth** — plus the fact that the upgrade path has never run +end-to-end on real hardware. + +- [ ] 🔴 **Deepen the post-OTA health check.** `update.rs:456` (`probe_frontend_once`) + passes on any 2xx/3xx from `GET /`, and `verify_pending_update` (494-593) only rolls + back on that. A release with a broken RPC API, dead containers, or failed LND unlock + passes and never rolls back. Add `/rpc/v1 update.status` + container-list/required-stack + health assertions before clearing the pending-verify marker. +- [ ] 🟠 **Run one real upgrade-from-vN-1 soak on hardware before tagging.** + No test installs the previous version, points it at a staged 1.8.0 manifest, applies, + and asserts health + rollback. This is the top release risk for an OTA release. A + two-VM (or two-node) harness is enough. +- [ ] 🟡 **Guard the frontend-build-no-op in the *actual* release path.** The + `ui-dist-version` grep guard (`tests/release/run.sh:82`) is behind `--with-build`, which + `scripts/create-release.sh:90` never passes → a stale frontend can ship with a valid + sha256. Call `run.sh --with-build --manifest` from create-release (or fold the grep in). +- [ ] 🟢 **publish-release-assets verifies size, not sha256** (`publish-release-assets.sh:97`). + Add a HEAD/GET sha256 compare so a size-correct/content-wrong mirror asset fails the + publish gate. + +--- + +## §C — Backend robustness (🟠 stability, mostly low-effort/high-ROI) + +Note: the `.unwrap()`/`panic!` worry is a **non-issue** — nearly all are in test +modules; production request/boot paths are essentially panic-free. The real risks: + +- [ ] 🟠 **Log swallowed persistence writes.** ~30-40 dangerous `let _ = save_*().await` + sites discard durability failures with zero diagnostics: `server.rs:270` (mesh config), + `bitcoin_relay.rs:865` (relay state), `update.rs:163/1223` (mirrors/update state), + `registry.rs:158`, `mesh/status.rs:286`, `scheduler.rs:179`, `install.rs:34`. Convert to + `if let Err(e) = … { warn!(…) }`; leave genuinely fire-and-forget ones commented. +- [ ] 🟠 **Remove blocking `std::process::Command` from async handlers.** + `install.rs:2222` `published_host_port` (sync podman on the install path), + `dependencies.rs:316` (`df`), `system/handlers.rs:578` (`sudo`), `transport/fips.rs:50` + (`systemctl`) stall tokio workers under load. Convert to `tokio::process` or + `spawn_blocking`. Only 8 files use `std::process::Command` — bounded. +- [ ] 🟡 **Restrict Bitcoin RPC exposure.** `bootstrap.rs:409` writes + `rpcallowip=0.0.0.0/0`. Scope to the container subnet / `127.0.0.1`. +- [ ] 🟡 **Move generated secrets from env to file mounts.** `manifest.rs:1208-1226` + injects secrets as `-e KEY=value`, readable via `podman inspect` / `/proc//environ`. + Prefer bind-mounting the existing `0600` secret file or `podman --secret`. +- [ ] 🟡 **Harden rate-limit IP extraction.** `middleware.rs:120-128` trusts + client-spoofable `X-Real-IP`/`X-Forwarded-For` → per-request bucket rotation defeats the + login limiter. Trust forwarded headers only from a configured proxy; have nginx set them. +- [ ] 🟢 **Include `seq` in the mesh signed preimage.** `message_types.rs:245-288` signs + `(t,v,ts)` but sets the anti-replay `seq` after signing → a radio MITM can alter ordering + without breaking the signature. +- [ ] 🟢 **Guard the short-DID slice panic** (`mesh/listener/decode.rs:566`) and gate the + dev-mode `password123` bypass (`auth.rs:18`) behind `#[cfg]` before it can reach a + release build. +- [ ] 🟢 **Apply the seccomp/apparmor profile** — `security/src/container_policies.rs:71` is a + TODO; the profile is defined but never applied to podman. + +--- + +## §D — Frontend security & performance (🟠) + +The untrusted mesh/LoRa chat path is **safe** (interpolation, no `v-html` — good). +The real issues are the app-bridge origin model and a bloated bundle. + +- [ ] 🟠 **Validate `event.origin` + add consent gates in the NIP-07 nostr bridge.** + `stores/appLauncher.ts:385-490` derives the caller from the launcher's own URL, never + `event.origin`, and `getPublicKey`/`nip04.decrypt`/`nip44.decrypt` have no consent gate → + any co-resident iframe can deanonymize the nostr identity or use the node as a decryption + oracle while an app is open. Check `event.origin` against the open app's real origin; key + approvals on it; gate decrypt/getPublicKey like `signEvent`. +- [ ] 🟠 **Origin-check the `share-to-mesh` handler.** `App.vue:450-464` acts on + `{type:'share-to-mesh', cid}` from any sender and force-navigates to `/mesh` with the CID + pre-staged. Add `ev.origin === window.location.origin` (as `Chat.vue:95` already does). +- [ ] 🟡 **Decide the app-iframe isolation model.** `AppSessionFrame.vue:54` / + `AppLauncherOverlay.vue:79` embed apps same-origin with no meaningful `sandbox`; a + same-origin app can read the CSRF cookie + `localStorage`. Ideal fix (serve apps from a + per-app subdomain origin) is architectural — at minimum decide + document for 1.8.0. +- [ ] 🟡 **Shrink the 93 MB dist.** `assets/video/video-intro.mp4` is **14.7 MB** + (precached by the service worker → blocks PWA install), plus ~18 MB of ~1 MB full-screen + JPEGs. Convert backgrounds to WebP/AVIF at responsive sizes, lazy/stream the intro video, + and exclude video/audio from the Workbox precache. Biggest, easiest perf win. +- [ ] 🟢 **DOMPurify the `Server.vue` QR SVG** (`:283/:295` render `v-html` unsanitized while + `TwoFactorSection.vue` sanitizes the analogous SVG); guard the unguarded `pollInterval` + (`Mesh.vue:391`); surface silent data-fetch failures (`curatedApps.ts:58/71`). + +--- + +## §E — Mesh transports (🟢 mostly done — verify & polish) + +Confirmed **fixed in HEAD:** B8 (1970 timestamps), B6 (inbound RX surfacing), the +per-message transport pill, and the archy↔archy plain-TEXT-DM E2E fix. Remaining: + +- [ ] 🟠 **Active Reticulum daemon-death detection.** `reticulum.rs:589` only `warn!`s on + socket EOF and `try_recv_frame` then returns `Ok(None)` forever; nothing calls + `child.try_wait()`. On an idle link a crashed daemon is invisible for up to 30 min (the + RX-stall timeout). Treat socket EOF as `Err` → immediate respawn. (Pairs with the current + `fix/reticulum-daemon-pdeathsig` branch work.) +- [ ] 🟡 **Persist chat history across restarts.** `state.messages` boots empty + (`listener/mod.rs:283`) while outbox/scheduler/peers survive — inconsistent; bubbles + vanish on restart. Add `mesh-messages.json` mirroring the `scheduler.rs`/`outbox.rs` + pattern (or explicitly accept the loss). +- [ ] 🟡 **Tighten the 30 s legacy dedup** (`listener/mod.rs:383-389`) — it silently drops a + peer legitimately sending identical text twice within 30 s. +- [ ] 🟢 **Wire the PyInstaller daemon binary into the release tarball / deploy script** + (Rust expects `/usr/local/bin/archy-reticulum-daemon`, `reticulum.rs:80`); add the RNode + udev rule; finish `ARCHY:2:` announce→`arch_pubkey_hex` binding (`reticulum.rs:119`). +- [ ] 🟢 **Duty-cycle guard for LoRa TX** — none exists; EU 868 is legally 1%. At minimum an + airtime budget/warning. + +--- + +## §F — ISO / image build (🔴 one secret leak; otherwise 🟠 hardening) + +`image-recipe/_archived/build-auto-installer-iso.sh` (3604 lines) is the real +builder; OTA is the normal update path but the ISO is what produces installable +media (latest artifact only one minor behind). + +- [ ] ⛔🔴 **Anthropic API key — INTENTIONAL for alpha/beta, hard GO-LIVE gate.** + `build-auto-installer-iso.sh:2645` bakes a live `sk-ant-…` key into `claude-api-proxy.service` + so alpha/beta testers get frictionless AI (deliberate — per user 2026-07-02). **Do NOT + remove for alpha/beta.** Before public GA it MUST be removed + rotated + injected at runtime + (a second copy also exists in a worktree). Track it here so it can't be forgotten at launch. +- [ ] 🔴 **Per-device secrets on first boot.** The self-signed TLS **private key is generated + at build time** (`:426`) → every device ships the same key; SSH host keys likewise not + regenerated. Generate TLS + SSH host keys on first boot. +- [ ] 🟠 **Kill default credentials.** `archipelago`/`archipelago` (SSH+root), web `password123`, + and SSH `PasswordAuthentication yes` (`:411`) all ship. Lock root, force credential + creation in onboarding, disable SSH password auth (or force-change on first login). +- [ ] 🟠 **Sign + checksum the ISO.** Pipeline ends at `xorriso` with no `SHA256SUMS`, no + GPG/minisign, no Secure Boot (`BOOTX64.EFI` is unsigned though `grub-efi-amd64-signed` is + installed). Emit + sign checksums; wire signed Secure Boot. +- [ ] 🟠 **Registries over HTTPS in the image too** — `146.59.87.168:3000` / `git.tx1138.com` + are baked `insecure=true`/`tls_verify:false` (`:216`, `:2308`). (Ties to §A.) +- [ ] 🟡 **Add `unattended-upgrades` + a default-deny nftables firewall** (allow 22/80/443 + + mesh/WG). Neither exists today; OS packages drift until reflash and there is no host + firewall. +- [ ] 🟡 **Pin the build for reproducibility.** FIPS daemon is built from unpinned upstream + `main`, Tailscale from its live apt repo, and `scripts/image-versions.sh` uses many + `:latest`/`stable` tags (+ `bitcoin-ui:1.7.84-alpha`, 15 behind). Pin to commits/versions; + snapshot apt. Wire ISO version to `Cargo.toml` so it can't drift. +- [ ] 🟢 **Harden LUKS + roadmap A/B partitioning.** The LUKS data key sits in plaintext on the + unencrypted root (`:2137`); add TPM2/passphrase binding. Longer-term: A/B (or + factory-reset) partitions for safe OTA rollback, and a real install-time TUI + (`docs/INSTALL-SCREENS-DESIGN.md` exists but the installer is headless "press Enter"). + +--- + +## §G — Refactor & code health (🟢 not release-blocking; do after the tag or opportunistically) + +- [ ] 🟢 **Manifest-drive per-app special-casing.** App names are branched on across 5-7 Rust + files (`config.rs` 36 match arms, `runtime.rs` 17, `install.rs:275-287` dispatch, + `prod_orchestrator.rs:54-83` baseline/restart-sensitive lists). Move `baseline`, + `restart_sensitive`, `stack_members`, `multi_container` into the manifest schema; collapse + the five near-identical `install_*_stack()` wrappers into one generic call. **Biggest + maintainability win.** +- [ ] 🟢 **Route all podman/systemctl through `podman_client`.** 113 raw `Command::new("podman")` + + 32 `systemctl` calls bypass the existing 952-LOC wrapper → untestable + the blocking-call + risk (§C). Consolidating also unlocks unit tests for the thinly-tested `package/` handlers + (`stacks.rs` 1 test, `config.rs` 2, `runtime.rs` 3, `install.rs` 7). +- [ ] 🟢 **Split the god-modules.** `prod_orchestrator.rs` (5,263 LOC) → `orchestrator/{reconcile, + host_ports,ownership,hooks}.rs`; `Mesh.vue` (2,485 LOC / 241 KB chunk) → sub-components. + Both are well-tested, so safe. +- [ ] 🟢 **Delete dead code.** ~4,100 LOC of orphan StartOS crates (`js-engine`, `models`, + `helpers`, `container-init`) not in the workspace or linked; the committed AppleDouble + `._*.rs` files; the committed `.venv/`/`build/`/`__pycache__` under the duplicate + `reticulum-daemon/` tree; promote `MeshRadioDevice` enum → trait. +- [ ] 🟢 **Resolve the Quadlet flag & dep hygiene.** Decide `use_quadlet_backends`' fate + (flip default + delete the legacy `create_container` branch, or freeze as experimental — + don't ship both half-maintained). Consolidate the mixed hyper 0.14/1.x ecosystem; bump + stale majors (reqwest, base64, thiserror, tokio-tungstenite). + +--- + +## §H — Testing gaps that gate confidence (🟠) + +- [ ] 🟠 **Add the OTA upgrade soak** (same as §B item 2) — the highest-value missing test. +- [ ] 🟡 **Add a host-reboot survival tier** — every app is `○` (untested) for reboot in + `TESTING.md:138`; the gate can't reboot the node it runs on. Run SSH-`reboot`-then-reprobe + out-of-band per node. +- [ ] 🟡 **Make the release gate run the full Rust suite** (or hard-require a green CI sha). + `tests/release/run.sh:101` runs only a 6-module slice because the full 1000-test suite + hangs PTYs on the dev box → 994 tests unverified at release time if CI is stale. +- [ ] 🟡 **Add `--max-time` to `node_rpc()`** (`tests/multinode/lib/multinode.bash`) — a slow + server-side RPC hangs the whole multinode suite with no feedback. +- [ ] 🟢 **De-hardcode creds/IPs in tests** (`tests/multinode/smoke.sh:32`, + `remote-lifecycle.sh:136`); snapshot/restore node baseline between destructive iterations + (teardown currently only clears `/tmp` session files). + +--- + +## §I — Carried-over open items (from `UNIFIED-TASK-TRACKER.md`, still valid) + +- [~] 🟠 **Multinode gate pass** — 5× destructive gate was launched on node `.5`; bring the + rest of the fleet to precondition, then run the existing (undocumented-but-present) + `tests/multinode/{smoke,meshtastic}.sh` cross-node suites. +- [ ] 🟠 **Federation `remove-node` tombstone regression.** + `federation/storage.rs:187` does `let _ = tombstone_did(...)` — swallows the write error, + so a removed peer reappears after the next sync. (This is a specific, confirmed instance + of the §C swallowed-writes class.) Needs a careful fix + `smoke.sh` re-verify. +- [ ] 🟠 **Phase-3 Quadlet default-flip** — validated + opt-in on .228/.198; flip + `config.rs:256` once the .5 gate reports clean. +- [ ] 🟠 **Developer CLI suite** (`archy app validate/render/install/test`) — gates external + app publishing (`APP-PACKAGING-MIGRATION-PLAN.md` step 5). +- [ ] ⛔🟡 **Version-naming decision** (`1.7.99-alpha` → `1.8.0` vs `1.8.00-alpha`) — a one-line + call, then a mechanical bump + tag. **Needs your decision.** +- [ ] ⛔🟢 **Bitcoin multi-version fleet OTA** — `.228` working on branch; rollout timing is + held for your call (`docs/bitcoin-version-bulletproof-rollout.md`). +- [ ] ⛔🟢 **3ccc stock-Meshtastic RF validation** — code fix in place; needs a live radio send. + +--- + +## Suggested order of attack + +1. **The critical path:** §A signing ceremony → then turn on manifest/catalog/image + signature enforcement (§A) + OTA HTTPS/signature + deeper health check (§B). +2. **Cheap high-ROI stability:** §C swallowed-writes + blocking-calls; §D nostr-bridge + + share-to-mesh origin checks; §H OTA soak + reboot tier. +3. **Image hardening:** rest of §F (per-device secrets, default creds, ISO signing, + firewall/unattended-upgrades, pinning). +4. **Polish, post-tag:** §G refactors, §E mesh persistence/dedup, §D bundle shrink. +5. **Decisions you own (⛔):** version name, signing mnemonic, bitcoin OTA timing, 3ccc test. +6. **Before public GA only (NOT alpha/beta):** remove + rotate the Anthropic key (§F) — + intentionally left in for frictionless AI during alpha/beta. + +*Last updated: 2026-07-02 (initial deep-audit synthesis). Update this line + tick +boxes with commit shas as items land.* diff --git a/releases/app-catalog.json b/releases/app-catalog.json index 4d75b685..96baa9e0 100644 --- a/releases/app-catalog.json +++ b/releases/app-catalog.json @@ -1,70 +1,67 @@ { - "schema": 1, - "updated": "2026-06-29", "apps": { "adguardhome": { - "version": "v0.107.55", - "image": "146.59.87.168:3000/lfg2025/adguardhome:v0.107.55" + "image": "146.59.87.168:3000/lfg2025/adguardhome:v0.107.55", + "version": "v0.107.55" }, "aiui": { - "version": "0.1.0", "manifest": { "app": { - "id": "aiui", - "name": "AI Assistant", - "version": "0.1.0", - "description": "Conversational AI interface for Archipelago. Quarantined \u2014 communicates only via context broker.", - "internal": true, "container": { "image": "localhost/archipelago-aiui:latest", "pull_policy": "always" }, - "resources": { - "cpu_limit": 1, - "memory_limit": "512Mi", - "disk_limit": "1Gi" - }, - "security": { - "capabilities": [], - "readonly_root": true, - "no_new_privileges": true, - "user": 1000, - "seccomp_profile": "default", - "network_policy": "isolated", - "apparmor_profile": "aiui" + "description": "Conversational AI interface for Archipelago. Quarantined — communicates only via context broker.", + "health_check": { + "endpoint": "http://localhost:80", + "interval": "60s", + "path": "/", + "retries": 3, + "timeout": "5s", + "type": "http" }, + "id": "aiui", + "internal": true, + "name": "AI Assistant", "ports": [ { - "host": 5180, + "bind": "127.0.0.1", "container": 80, - "protocol": "tcp", - "bind": "127.0.0.1" + "host": 5180, + "protocol": "tcp" } ], - "health_check": { - "type": "http", - "endpoint": "http://localhost:80", - "path": "/", - "interval": "60s", - "timeout": "5s", - "retries": 3 - } + "resources": { + "cpu_limit": 1, + "disk_limit": "1Gi", + "memory_limit": "512Mi" + }, + "security": { + "apparmor_profile": "aiui", + "capabilities": [], + "network_policy": "isolated", + "no_new_privileges": true, + "readonly_root": true, + "seccomp_profile": "default", + "user": 1000 + }, + "version": "0.1.0" } - } + }, + "version": "0.1.0" }, "archy-btcpay-db": { - "version": "15.17", "manifest": { "app": { - "id": "archy-btcpay-db", - "name": "BTCPay Postgres", - "version": 15.17, - "description": "Postgres backend for BTCPay and NBXplorer.", + "bitcoin_integration": { + "rpc_access": "none", + "sync_required": false + }, "container": { - "image": "git.tx1138.com/lfg2025/postgres:15.17", - "pull_policy": "if-not-present", - "network": "archy-net", "data_uid": "100998:100998", + "image": "git.tx1138.com/lfg2025/postgres:15.17", + "network": "archy-net", + "pull_policy": "if-not-present", "secret_env": [ { "key": "POSTGRES_PASSWORD", @@ -77,9 +74,24 @@ "storage": "20Gi" } ], + "description": "Postgres backend for BTCPay and NBXplorer.", + "environment": [ + "POSTGRES_DB=btcpay", + "POSTGRES_USER=btcpay" + ], + "health_check": { + "endpoint": "localhost:5432", + "interval": "30s", + "retries": 3, + "timeout": "5s", + "type": "tcp" + }, + "id": "archy-btcpay-db", + "name": "BTCPay Postgres", + "ports": [], "resources": { - "memory_limit": "1Gi", - "disk_limit": "20Gi" + "disk_limit": "20Gi", + "memory_limit": "1Gi" }, "security": { "capabilities": [ @@ -89,51 +101,36 @@ "SETGID", "DAC_OVERRIDE" ], - "readonly_root": false, - "network_policy": "isolated" + "network_policy": "isolated", + "readonly_root": false }, - "ports": [], + "version": "15.17", "volumes": [ { - "type": "bind", - "source": "/var/lib/archipelago/postgres-btcpay", - "target": "/var/lib/postgresql/data", "options": [ "rw" - ] + ], + "source": "/var/lib/archipelago/postgres-btcpay", + "target": "/var/lib/postgresql/data", + "type": "bind" } - ], - "environment": [ - "POSTGRES_DB=btcpay", - "POSTGRES_USER=btcpay" - ], - "health_check": { - "type": "tcp", - "endpoint": "localhost:5432", - "interval": "30s", - "timeout": "5s", - "retries": 3 - }, + ] + } + }, + "version": "15.17" + }, + "archy-mempool-db": { + "manifest": { + "app": { "bitcoin_integration": { "rpc_access": "none", "sync_required": false - } - } - } - }, - "archy-mempool-db": { - "version": "11.4.10", - "manifest": { - "app": { - "id": "archy-mempool-db", - "name": "Mempool MariaDB", - "version": "11.4.10", - "description": "MariaDB backend for the mempool explorer stack.", + }, "container": { - "image": "git.tx1138.com/lfg2025/mariadb:11.4.10", - "pull_policy": "if-not-present", - "network": "archy-net", "data_uid": "100998:100998", + "image": "git.tx1138.com/lfg2025/mariadb:11.4.10", + "network": "archy-net", + "pull_policy": "if-not-present", "secret_env": [ { "key": "MYSQL_PASSWORD", @@ -150,9 +147,24 @@ "storage": "20Gi" } ], + "description": "MariaDB backend for the mempool explorer stack.", + "environment": [ + "MYSQL_DATABASE=mempool", + "MYSQL_USER=mempool" + ], + "health_check": { + "endpoint": "localhost:3306", + "interval": "30s", + "retries": 3, + "timeout": "5s", + "type": "tcp" + }, + "id": "archy-mempool-db", + "name": "Mempool MariaDB", + "ports": [], "resources": { - "memory_limit": "512Mi", - "disk_limit": "20Gi" + "disk_limit": "20Gi", + "memory_limit": "512Mi" }, "security": { "capabilities": [ @@ -162,104 +174,89 @@ "SETGID", "DAC_OVERRIDE" ], - "readonly_root": false, - "network_policy": "isolated" + "network_policy": "isolated", + "readonly_root": false }, - "ports": [], + "version": "11.4.10", "volumes": [ { - "type": "bind", - "source": "/var/lib/archipelago/mysql-mempool", - "target": "/var/lib/mysql", "options": [ "rw" - ] + ], + "source": "/var/lib/archipelago/mysql-mempool", + "target": "/var/lib/mysql", + "type": "bind" } - ], - "environment": [ - "MYSQL_DATABASE=mempool", - "MYSQL_USER=mempool" - ], - "health_check": { - "type": "tcp", - "endpoint": "localhost:3306", - "interval": "30s", - "timeout": "5s", - "retries": 3 - }, + ] + } + }, + "version": "11.4.10" + }, + "archy-mempool-web": { + "manifest": { + "app": { "bitcoin_integration": { "rpc_access": "none", "sync_required": false - } - } - } - }, - "archy-mempool-web": { - "version": "3.0.1", - "manifest": { - "app": { - "id": "archy-mempool-web", - "name": "Mempool Web", - "version": "3.0.1", - "description": "Frontend web UI for mempool explorer.", - "container_name": "mempool", + }, "container": { "image": "146.59.87.168:3000/lfg2025/mempool-frontend:v3.0.1", - "pull_policy": "if-not-present", - "network": "archy-net" + "network": "archy-net", + "pull_policy": "if-not-present" }, + "container_name": "mempool", "dependencies": [ { "app_id": "mempool-api", "version": ">=3.0.0" } ], + "description": "Frontend web UI for mempool explorer.", + "environment": [ + "FRONTEND_HTTP_PORT=8080", + "BACKEND_MAINNET_HTTP_HOST=mempool-api" + ], + "health_check": { + "endpoint": "http://127.0.0.1:8080", + "interval": "30s", + "path": "/", + "retries": 3, + "timeout": "5s", + "type": "http" + }, + "id": "archy-mempool-web", + "name": "Mempool Web", + "ports": [ + { + "container": 8080, + "host": 4080, + "protocol": "tcp" + } + ], "resources": { "memory_limit": "512Mi" }, "security": { "capabilities": [], - "readonly_root": false, - "network_policy": "isolated" + "network_policy": "isolated", + "readonly_root": false }, - "ports": [ - { - "host": 4080, - "container": 8080, - "protocol": "tcp" - } - ], - "environment": [ - "FRONTEND_HTTP_PORT=8080", - "BACKEND_MAINNET_HTTP_HOST=mempool-api" - ], - "health_check": { - "type": "http", - "endpoint": "http://127.0.0.1:8080", - "path": "/", - "interval": "30s", - "timeout": "5s", - "retries": 3 - }, - "bitcoin_integration": { - "rpc_access": "none", - "sync_required": false - } + "version": "3.0.1" } - } + }, + "version": "3.0.1" }, "archy-nbxplorer": { - "version": "2.6.0", "manifest": { "app": { - "id": "archy-nbxplorer", - "name": "NBXplorer", - "version": "2.6.0", - "description": "BTCPay blockchain indexer service.", + "bitcoin_integration": { + "rpc_access": "read-only", + "sync_required": true + }, "container": { "image": "git.tx1138.com/lfg2025/nbxplorer:2.6.0", - "pull_policy": "if-not-present", "network": "archy-net", + "pull_policy": "if-not-present", "secret_env": [ { "key": "NBXPLORER_BTCRPCPASSWORD", @@ -281,32 +278,7 @@ "version": ">=15.17" } ], - "resources": { - "memory_limit": "2Gi", - "disk_limit": "20Gi" - }, - "security": { - "capabilities": [], - "readonly_root": false, - "network_policy": "isolated" - }, - "ports": [ - { - "host": 32838, - "container": 32838, - "protocol": "tcp" - } - ], - "volumes": [ - { - "type": "bind", - "source": "/var/lib/archipelago/nbxplorer", - "target": "/data", - "options": [ - "rw" - ] - } - ], + "description": "BTCPay blockchain indexer service.", "environment": [ "NBXPLORER_DATADIR=/data", "NBXPLORER_NETWORK=mainnet", @@ -319,46 +291,73 @@ "NBXPLORER_POSTGRES=Username=btcpay;Password=${BTCPAY_DB_PASS};Host=archy-btcpay-db;Port=5432;Database=nbxplorer" ], "health_check": { - "type": "http", "endpoint": "http://localhost:32838", - "path": "/", "interval": "30s", + "path": "/", + "retries": 5, "timeout": "30s", - "retries": 5 + "type": "http" }, - "bitcoin_integration": { - "rpc_access": "read-only", - "sync_required": true - } + "id": "archy-nbxplorer", + "name": "NBXplorer", + "ports": [ + { + "container": 32838, + "host": 32838, + "protocol": "tcp" + } + ], + "resources": { + "disk_limit": "20Gi", + "memory_limit": "2Gi" + }, + "security": { + "capabilities": [], + "network_policy": "isolated", + "readonly_root": false + }, + "version": "2.6.0", + "volumes": [ + { + "options": [ + "rw" + ], + "source": "/var/lib/archipelago/nbxplorer", + "target": "/data", + "type": "bind" + } + ] } - } + }, + "version": "2.6.0" }, "bitcoin-core": { - "version": "latest", "manifest": { "app": { - "id": "bitcoin-core", - "name": "Bitcoin Core", - "version": "28.4.0", - "description": "Reference Bitcoin Core node with dynamic prune/full-mode startup based on host disk.", - "container_name": "bitcoin-core", + "bitcoin_integration": { + "pruning_support": true, + "rpc_access": "admin", + "sync_required": true, + "testnet_support": false + }, "container": { - "image": "146.59.87.168:3000/lfg2025/bitcoin:28.4", - "pull_policy": "if-not-present", - "network": "archy-net", - "entrypoint": [ - "sh", - "-lc" - ], "custom_args": [ "BITCOIND=\"$(command -v bitcoind || true)\"; if [ -z \"$BITCOIND\" ]; then\n BITCOIND=\"$(find /opt -path '*/bin/bitcoind' -type f 2>/dev/null | sort | tail -n 1)\";\nfi; if [ -z \"$BITCOIND\" ]; then\n echo \"bitcoind not found in image\" >&2;\n exit 127;\nfi; RPC_USER=\"$(printenv BITCOIN_RPC_USER)\"; RPC_PASS=\"$(printenv BITCOIN_RPC_PASS)\"; RPC_TXRELAY_AUTH=\"$(printenv BITCOIN_RPC_TXRELAY_RPCAUTH || true)\"; DISK_GB_VALUE=\"$(printenv DISK_GB || true)\"; RPC_HEADROOM=\"-rpcthreads=16 -rpcworkqueue=256\"; RPC_TXRELAY_FLAGS=\"-rpcwhitelistdefault=0\"; if [ -n \"$RPC_TXRELAY_AUTH\" ]; then\n RPC_TXRELAY_FLAGS=\"$RPC_TXRELAY_FLAGS -rpcauth=$RPC_TXRELAY_AUTH -rpcwhitelist=txrelay:sendrawtransaction,submitpackage,testmempoolaccept,getmempoolinfo,getrawmempool,getmempoolentry,getnetworkinfo,getblockchaininfo,getblockcount,getblockhash,getblock,getblockheader,getrawtransaction,gettxout,gettxspendingprevout,decoderawtransaction,decodescript,estimatesmartfee,uptime,ping,getconnectioncount,getpeerinfo,getindexinfo,getdeploymentinfo,getchaintips\";\nfi; if [ \"${DISK_GB_VALUE:-0}\" -lt 1000 ]; then\n exec \"$BITCOIND\" -datadir=/home/bitcoin/.bitcoin -noconf -server=1 -prune=550 -rpcallowip=0.0.0.0/0 -rpcbind=0.0.0.0:8332 -listen=1 -bind=0.0.0.0:8333 -dbcache=1024 -par=0 -maxconnections=125 $RPC_HEADROOM $RPC_TXRELAY_FLAGS -rpcuser=\"$RPC_USER\" -rpcpassword=\"$RPC_PASS\";\nelse\n exec \"$BITCOIND\" -datadir=/home/bitcoin/.bitcoin -noconf -server=1 -txindex=1 -rpcallowip=0.0.0.0/0 -rpcbind=0.0.0.0:8332 -listen=1 -bind=0.0.0.0:8333 -dbcache=4096 -par=0 -maxconnections=125 $RPC_HEADROOM $RPC_TXRELAY_FLAGS -rpcuser=\"$RPC_USER\" -rpcpassword=\"$RPC_PASS\";\nfi" ], + "data_uid": "100101:100101", "derived_env": [ { "key": "DISK_GB", "template": "{{DISK_GB}}" } ], + "entrypoint": [ + "sh", + "-lc" + ], + "image": "146.59.87.168:3000/lfg2025/bitcoin:28.4", + "network": "archy-net", + "pull_policy": "if-not-present", "secret_env": [ { "key": "BITCOIN_RPC_PASS", @@ -368,18 +367,43 @@ "key": "BITCOIN_RPC_TXRELAY_RPCAUTH", "secret_file": "bitcoin-rpc-txrelay-rpcauth" } - ], - "data_uid": "100101:100101" + ] }, + "container_name": "bitcoin-core", "dependencies": [ { "storage": "500Gi" } ], + "description": "Reference Bitcoin Core node with dynamic prune/full-mode startup based on host disk.", + "environment": [ + "BITCOIN_RPC_USER=archipelago" + ], + "health_check": { + "endpoint": "localhost:8332", + "interval": "30s", + "retries": 3, + "timeout": "5s", + "type": "tcp" + }, + "id": "bitcoin-core", + "name": "Bitcoin Core", + "ports": [ + { + "container": 8332, + "host": 8332, + "protocol": "tcp" + }, + { + "container": 8333, + "host": 8333, + "protocol": "tcp" + } + ], "resources": { "cpu_limit": 0, - "memory_limit": "4Gi", - "disk_limit": "500Gi" + "disk_limit": "500Gi", + "memory_limit": "4Gi" }, "security": { "capabilities": [ @@ -389,118 +413,93 @@ "SETGID", "DAC_OVERRIDE" ], - "readonly_root": false, - "network_policy": "isolated" + "network_policy": "isolated", + "readonly_root": false }, - "ports": [ - { - "host": 8332, - "container": 8332, - "protocol": "tcp" - }, - { - "host": 8333, - "container": 8333, - "protocol": "tcp" - } - ], + "version": "28.4.0", "volumes": [ { - "type": "bind", - "source": "/var/lib/archipelago/bitcoin", - "target": "/home/bitcoin/.bitcoin", "options": [ "rw" - ] + ], + "source": "/var/lib/archipelago/bitcoin", + "target": "/home/bitcoin/.bitcoin", + "type": "bind" } - ], - "environment": [ - "BITCOIN_RPC_USER=archipelago" - ], - "health_check": { - "type": "tcp", - "endpoint": "localhost:8332", - "interval": "30s", - "timeout": "5s", - "retries": 3 - }, - "bitcoin_integration": { - "rpc_access": "admin", - "sync_required": true, - "testnet_support": false, - "pruning_support": true - } + ] } }, + "version": "latest", "versions": [ { - "version": "latest", + "default": true, "image": "146.59.87.168:3000/lfg2025/bitcoin:latest", - "default": true + "version": "latest" }, { - "version": "31.0", - "image": "146.59.87.168:3000/lfg2025/bitcoin:31.0" + "image": "146.59.87.168:3000/lfg2025/bitcoin:31.0", + "version": "31.0" }, { - "version": "30.2", - "image": "146.59.87.168:3000/lfg2025/bitcoin:30.2" + "image": "146.59.87.168:3000/lfg2025/bitcoin:30.2", + "version": "30.2" }, { - "version": "29.3", - "image": "146.59.87.168:3000/lfg2025/bitcoin:29.3" + "image": "146.59.87.168:3000/lfg2025/bitcoin:29.3", + "version": "29.3" }, { - "version": "29.2", - "image": "146.59.87.168:3000/lfg2025/bitcoin:29.2" + "image": "146.59.87.168:3000/lfg2025/bitcoin:29.2", + "version": "29.2" }, { - "version": "28.4.0", - "image": "146.59.87.168:3000/lfg2025/bitcoin:28.4" + "image": "146.59.87.168:3000/lfg2025/bitcoin:28.4", + "version": "28.4.0" }, { - "version": "27.2", - "image": "146.59.87.168:3000/lfg2025/bitcoin:27.2" + "image": "146.59.87.168:3000/lfg2025/bitcoin:27.2", + "version": "27.2" }, { - "version": "26.2", + "deprecated": true, "image": "146.59.87.168:3000/lfg2025/bitcoin:26.2", - "deprecated": true + "version": "26.2" }, { - "version": "25.2", + "deprecated": true, "image": "146.59.87.168:3000/lfg2025/bitcoin:25.2", - "deprecated": true + "version": "25.2" } ] }, "bitcoin-knots": { - "version": "latest", "image": "146.59.87.168:3000/lfg2025/bitcoin-knots:latest", "manifest": { "app": { - "id": "bitcoin-knots", - "name": "Bitcoin Knots", - "version": "28.1.0", - "description": "Full Bitcoin Knots node with dynamic prune/full-mode startup based on host disk.", - "container_name": "bitcoin-knots", + "bitcoin_integration": { + "pruning_support": true, + "rpc_access": "admin", + "sync_required": true, + "testnet_support": false + }, "container": { - "image": "146.59.87.168:3000/lfg2025/bitcoin-knots:latest", - "pull_policy": "if-not-present", - "network": "archy-net", - "entrypoint": [ - "sh", - "-lc" - ], "custom_args": [ "BITCOIND=\"$(command -v bitcoind || true)\"; if [ -z \"$BITCOIND\" ]; then\n BITCOIND=\"$(find /opt -path '*/bin/bitcoind' -type f 2>/dev/null | sort | tail -n 1)\";\nfi; if [ -z \"$BITCOIND\" ]; then\n echo \"bitcoind not found in image\" >&2;\n exit 127;\nfi; RPC_USER=\"$(printenv BITCOIN_RPC_USER)\"; RPC_PASS=\"$(printenv BITCOIN_RPC_PASS)\"; RPC_TXRELAY_AUTH=\"$(printenv BITCOIN_RPC_TXRELAY_RPCAUTH || true)\"; DISK_GB_VALUE=\"$(printenv DISK_GB || true)\"; RPC_HEADROOM=\"-rpcthreads=16 -rpcworkqueue=256\"; RPC_TXRELAY_FLAGS=\"-rpcwhitelistdefault=0\"; if [ -n \"$RPC_TXRELAY_AUTH\" ]; then\n RPC_TXRELAY_FLAGS=\"$RPC_TXRELAY_FLAGS -rpcauth=$RPC_TXRELAY_AUTH -rpcwhitelist=txrelay:sendrawtransaction,submitpackage,testmempoolaccept,getmempoolinfo,getrawmempool,getmempoolentry,getnetworkinfo,getblockchaininfo,getblockcount,getblockhash,getblock,getblockheader,getrawtransaction,gettxout,gettxspendingprevout,decoderawtransaction,decodescript,estimatesmartfee,uptime,ping,getconnectioncount,getpeerinfo,getindexinfo,getdeploymentinfo,getchaintips\";\nfi; if [ \"${DISK_GB_VALUE:-0}\" -lt 1000 ]; then\n exec \"$BITCOIND\" -datadir=/home/bitcoin/.bitcoin -noconf -server=1 -prune=550 -rpcallowip=0.0.0.0/0 -rpcbind=0.0.0.0:8332 -listen=1 -bind=0.0.0.0:8333 -dbcache=2048 -par=0 -maxconnections=125 $RPC_HEADROOM $RPC_TXRELAY_FLAGS -rpcuser=\"$RPC_USER\" -rpcpassword=\"$RPC_PASS\";\nelse\n exec \"$BITCOIND\" -datadir=/home/bitcoin/.bitcoin -noconf -server=1 -txindex=1 -rpcallowip=0.0.0.0/0 -rpcbind=0.0.0.0:8332 -listen=1 -bind=0.0.0.0:8333 -dbcache=4096 -par=0 -maxconnections=125 $RPC_HEADROOM $RPC_TXRELAY_FLAGS -rpcuser=\"$RPC_USER\" -rpcpassword=\"$RPC_PASS\";\nfi" ], + "data_uid": "100101:100101", "derived_env": [ { "key": "DISK_GB", "template": "{{DISK_GB}}" } ], + "entrypoint": [ + "sh", + "-lc" + ], + "image": "146.59.87.168:3000/lfg2025/bitcoin-knots:latest", + "network": "archy-net", + "pull_policy": "if-not-present", "secret_env": [ { "key": "BITCOIN_RPC_PASS", @@ -510,18 +509,43 @@ "key": "BITCOIN_RPC_TXRELAY_RPCAUTH", "secret_file": "bitcoin-rpc-txrelay-rpcauth" } - ], - "data_uid": "100101:100101" + ] }, + "container_name": "bitcoin-knots", "dependencies": [ { "storage": "500Gi" } ], + "description": "Full Bitcoin Knots node with dynamic prune/full-mode startup based on host disk.", + "environment": [ + "BITCOIN_RPC_USER=archipelago" + ], + "health_check": { + "endpoint": "localhost:8332", + "interval": "30s", + "retries": 3, + "timeout": "5s", + "type": "tcp" + }, + "id": "bitcoin-knots", + "name": "Bitcoin Knots", + "ports": [ + { + "container": 8332, + "host": 8332, + "protocol": "tcp" + }, + { + "container": 8333, + "host": 8333, + "protocol": "tcp" + } + ], "resources": { "cpu_limit": 0, - "memory_limit": "8Gi", - "disk_limit": "500Gi" + "disk_limit": "500Gi", + "memory_limit": "8Gi" }, "security": { "capabilities": [ @@ -531,82 +555,51 @@ "SETGID", "DAC_OVERRIDE" ], - "readonly_root": false, - "network_policy": "isolated" + "network_policy": "isolated", + "readonly_root": false }, - "ports": [ - { - "host": 8332, - "container": 8332, - "protocol": "tcp" - }, - { - "host": 8333, - "container": 8333, - "protocol": "tcp" - } - ], + "version": "28.1.0", "volumes": [ { - "type": "bind", - "source": "/var/lib/archipelago/bitcoin", - "target": "/home/bitcoin/.bitcoin", "options": [ "rw" - ] + ], + "source": "/var/lib/archipelago/bitcoin", + "target": "/home/bitcoin/.bitcoin", + "type": "bind" } - ], - "environment": [ - "BITCOIN_RPC_USER=archipelago" - ], - "health_check": { - "type": "tcp", - "endpoint": "localhost:8332", - "interval": "30s", - "timeout": "5s", - "retries": 3 - }, - "bitcoin_integration": { - "rpc_access": "admin", - "sync_required": true, - "testnet_support": false, - "pruning_support": true - } + ] } }, + "version": "latest", "versions": [ { - "version": "latest", + "default": true, "image": "146.59.87.168:3000/lfg2025/bitcoin-knots:29.3.knots20260508", - "default": true + "version": "latest" }, { - "version": "29.3.knots20260508", - "image": "146.59.87.168:3000/lfg2025/bitcoin-knots:29.3.knots20260508" + "image": "146.59.87.168:3000/lfg2025/bitcoin-knots:29.3.knots20260508", + "version": "29.3.knots20260508" }, { - "version": "29.3.knots20260507", - "image": "146.59.87.168:3000/lfg2025/bitcoin-knots:29.3.knots20260507" + "image": "146.59.87.168:3000/lfg2025/bitcoin-knots:29.3.knots20260507", + "version": "29.3.knots20260507" }, { - "version": "29.3.knots20260210", - "image": "146.59.87.168:3000/lfg2025/bitcoin-knots:29.3.knots20260210" + "image": "146.59.87.168:3000/lfg2025/bitcoin-knots:29.3.knots20260210", + "version": "29.3.knots20260210" }, { - "version": "29.2.knots20251110", - "image": "146.59.87.168:3000/lfg2025/bitcoin-knots:29.2.knots20251110" + "image": "146.59.87.168:3000/lfg2025/bitcoin-knots:29.2.knots20251110", + "version": "29.2.knots20251110" } ] }, "bitcoin-ui": { - "version": "1.7.84-alpha", "image": "146.59.87.168:3000/lfg2025/bitcoin-ui:1.7.84-alpha", "manifest": { "app": { - "id": "bitcoin-ui", - "name": "Bitcoin UI", - "version": "1.0.0", - "description": "Archipelago-native HTTP proxy + static site for interacting with the\nBitcoin Core / Bitcoin Knots JSON-RPC. Runs nginx inside a container\nand reverse-proxies /bitcoin-rpc/ to 127.0.0.1:8332 on the host. The\nupstream Authorization header is substituted from\n/var/lib/archipelago/secrets/bitcoin-rpc-password by the prod\norchestrator's pre-start hook, rendered into an nginx.conf that is\nbind-mounted read-only at container start.\n", "container": { "build": { "context": "/opt/archipelago/docker/bitcoin-ui", @@ -619,44 +612,44 @@ "app_id": "bitcoin-core" } ], + "description": "Archipelago-native HTTP proxy + static site for interacting with the\nBitcoin Core / Bitcoin Knots JSON-RPC. Runs nginx inside a container\nand reverse-proxies /bitcoin-rpc/ to 127.0.0.1:8332 on the host. The\nupstream Authorization header is substituted from\n/var/lib/archipelago/secrets/bitcoin-rpc-password by the prod\norchestrator's pre-start hook, rendered into an nginx.conf that is\nbind-mounted read-only at container start.\n", + "environment": [], + "health_check": { + "endpoint": "http://127.0.0.1:8334", + "interval": "30s", + "path": "/", + "retries": 3, + "timeout": "5s", + "type": "http" + }, + "id": "bitcoin-ui", + "name": "Bitcoin UI", + "ports": [], "resources": { "memory_limit": "128Mi" }, "security": { - "readonly_root": false, - "network_policy": "host" + "network_policy": "host", + "readonly_root": false }, - "ports": [], + "version": "1.0.0", "volumes": [ { - "type": "bind", - "source": "/var/lib/archipelago/bitcoin-ui/nginx.conf", - "target": "/etc/nginx/conf.d/default.conf", "options": [ "ro" - ] + ], + "source": "/var/lib/archipelago/bitcoin-ui/nginx.conf", + "target": "/etc/nginx/conf.d/default.conf", + "type": "bind" } - ], - "environment": [], - "health_check": { - "type": "http", - "endpoint": "http://127.0.0.1:8334", - "path": "/", - "interval": "30s", - "timeout": "5s", - "retries": 3 - } + ] } - } + }, + "version": "1.7.84-alpha" }, "botfights": { - "version": "1.1.0", "manifest": { "app": { - "id": "botfights", - "name": "BotFights", - "version": "1.1.0", - "description": "Bot competition arena with 2-player arcade fighting mode. AI bots battle in trivia challenges while humans duke it out with controllers. Built for Bitcoiners.", "category": "community", "container": { "image": "146.59.87.168:3000/lfg2025/botfights:1.1.0", @@ -667,71 +660,35 @@ "storage": "500Mi" } ], - "resources": { - "cpu_limit": 2, - "memory_limit": "512Mi", - "disk_limit": "500Mi" - }, - "security": { - "capabilities": [], - "readonly_root": true, - "no_new_privileges": true, - "user": 1001, - "seccomp_profile": "default", - "network_policy": "bridge", - "apparmor_profile": "default" - }, - "ports": [ - { - "host": 9100, - "container": 9100, - "protocol": "tcp" - } - ], - "volumes": [ - { - "type": "bind", - "source": "botfights-data", - "target": "/app/server/data" - }, - { - "type": "tmpfs", - "target": "/tmp", - "options": [ - "rw", - "noexec", - "nosuid", - "size=64m" - ] - } - ], + "description": "Bot competition arena with 2-player arcade fighting mode. AI bots battle in trivia challenges while humans duke it out with controllers. Built for Bitcoiners.", "environment": [ "NODE_ENV=production" ], "health_check": { - "type": "http", "endpoint": "http://localhost:9100", - "path": "/api/health", "interval": "30s", - "timeout": "10s", + "path": "/api/health", "retries": 3, - "start_period": "30s" + "start_period": "30s", + "timeout": "10s", + "type": "http" }, + "id": "botfights", "interfaces": { "main": { - "name": "Web UI", "description": "Bot arena and arcade fighter with controller support", - "type": "ui", + "name": "Web UI", + "path": "/", "port": 9100, "protocol": "http", - "path": "/" + "type": "ui" } }, "metadata": { "author": "Dorian", - "repo": "https://botfights.net", "icon": "/assets/img/app-icons/botfights.svg", "license": "MIT", + "repo": "https://botfights.net", "tags": [ "bitcoin", "gaming", @@ -741,31 +698,77 @@ "competition", "controller" ] - } + }, + "name": "BotFights", + "ports": [ + { + "container": 9100, + "host": 9100, + "protocol": "tcp" + } + ], + "resources": { + "cpu_limit": 2, + "disk_limit": "500Mi", + "memory_limit": "512Mi" + }, + "security": { + "apparmor_profile": "default", + "capabilities": [], + "network_policy": "bridge", + "no_new_privileges": true, + "readonly_root": true, + "seccomp_profile": "default", + "user": 1001 + }, + "version": "1.1.0", + "volumes": [ + { + "source": "botfights-data", + "target": "/app/server/data", + "type": "bind" + }, + { + "options": [ + "rw", + "noexec", + "nosuid", + "size=64m" + ], + "target": "/tmp", + "type": "tmpfs" + } + ] } - } + }, + "version": "1.1.0" }, "btcpay": { - "version": "2.3.9", "image": "docker.io/btcpayserver/btcpayserver:2.3.9", "images": { - "btcpay-server": "docker.io/btcpayserver/btcpayserver:2.3.9", + "archy-btcpay-db": "146.59.87.168:3000/lfg2025/postgres:15.17", "archy-nbxplorer": "146.59.87.168:3000/lfg2025/nbxplorer:2.6.0", - "archy-btcpay-db": "146.59.87.168:3000/lfg2025/postgres:15.17" - } + "btcpay-server": "docker.io/btcpayserver/btcpayserver:2.3.9" + }, + "version": "2.3.9" }, "btcpay-server": { - "version": "2.3.9", "manifest": { "app": { - "id": "btcpay-server", - "name": "BTCPay Server", - "version": "2.3.9", - "description": "Self-hosted Bitcoin payment processor. Accept Bitcoin payments without intermediaries.", + "bitcoin_integration": { + "rpc_access": "read-only", + "sync_required": true + }, "container": { + "derived_env": [ + { + "key": "BTCPAY_HOST", + "template": "{{HOST_IP}}:23000" + } + ], "image": "docker.io/btcpayserver/btcpayserver:2.3.9", - "pull_policy": "if-not-present", "network": "archy-net", + "pull_policy": "if-not-present", "secret_env": [ { "key": "BTCPAY_BTCRPCPASSWORD", @@ -775,12 +778,6 @@ "key": "BTCPAY_DB_PASS", "secret_file": "btcpay-db-password" } - ], - "derived_env": [ - { - "key": "BTCPAY_HOST", - "template": "{{HOST_IP}}:23000" - } ] }, "dependencies": [ @@ -797,33 +794,7 @@ "version": ">=2.6.0" } ], - "resources": { - "cpu_limit": 2, - "memory_limit": "2Gi", - "disk_limit": "20Gi" - }, - "security": { - "capabilities": [], - "readonly_root": false, - "network_policy": "isolated" - }, - "ports": [ - { - "host": 23000, - "container": 49392, - "protocol": "tcp" - } - ], - "volumes": [ - { - "type": "bind", - "source": "/var/lib/archipelago/btcpay", - "target": "/datadir", - "options": [ - "rw" - ] - } - ], + "description": "Self-hosted Bitcoin payment processor. Accept Bitcoin payments without intermediaries.", "environment": [ "ASPNETCORE_URLS=http://0.0.0.0:49392", "BTCPAY_PROTOCOL=http", @@ -834,47 +805,73 @@ "BTCPAY_POSTGRES=Username=btcpay;Password=${BTCPAY_DB_PASS};Host=archy-btcpay-db;Port=5432;Database=btcpay" ], "health_check": { - "type": "http", "endpoint": "http://localhost:49392", - "path": "/", "interval": "30s", + "path": "/", + "retries": 5, "timeout": "30s", - "retries": 5 - }, - "bitcoin_integration": { - "rpc_access": "read-only", - "sync_required": true - }, - "lightning_integration": { - "payment_processing": false, - "invoice_management": true + "type": "http" }, + "id": "btcpay-server", "interfaces": { "main": { - "name": "Web UI", "description": "BTCPay Server dashboard", - "type": "ui", + "name": "Web UI", + "path": "/", "port": 23000, "protocol": "http", - "path": "/" + "type": "ui" } }, + "lightning_integration": { + "invoice_management": true, + "payment_processing": false + }, "metadata": { "launch": { "open_in_new_tab": true } - } + }, + "name": "BTCPay Server", + "ports": [ + { + "container": 49392, + "host": 23000, + "protocol": "tcp" + } + ], + "resources": { + "cpu_limit": 2, + "disk_limit": "20Gi", + "memory_limit": "2Gi" + }, + "security": { + "capabilities": [], + "network_policy": "isolated", + "readonly_root": false + }, + "version": "2.3.9", + "volumes": [ + { + "options": [ + "rw" + ], + "source": "/var/lib/archipelago/btcpay", + "target": "/datadir", + "type": "bind" + } + ] } - } + }, + "version": "2.3.9" }, "core-lightning": { - "version": "23.08.2", "manifest": { "app": { - "id": "core-lightning", - "name": "Core Lightning (CLN)", - "version": "23.08.2", - "description": "Lightning Network implementation in C. Lightweight alternative to LND.", + "bitcoin_integration": { + "rpc_access": "admin", + "sync_required": true + }, "container": { "image": "elementsproject/lightningd:v23.08.2", "image_signature": "cosign://...", @@ -886,44 +883,7 @@ "version": ">=26.0" } ], - "resources": { - "cpu_limit": 1, - "memory_limit": "512Mi", - "disk_limit": "5Gi" - }, - "security": { - "capabilities": [ - "NET_BIND_SERVICE" - ], - "readonly_root": true, - "no_new_privileges": true, - "user": 1000, - "seccomp_profile": "default", - "network_policy": "isolated", - "apparmor_profile": "core-lightning" - }, - "ports": [ - { - "host": 9736, - "container": 9735, - "protocol": "tcp" - }, - { - "host": 9835, - "container": 9835, - "protocol": "tcp" - } - ], - "volumes": [ - { - "type": "bind", - "source": "/var/lib/archipelago/core-lightning", - "target": "/home/clightning/.lightning", - "options": [ - "rw" - ] - } - ], + "description": "Lightning Network implementation in C. Lightweight alternative to LND.", "environment": [ "BITCOIND_RPCURL=http://bitcoin-core:8332", "BITCOIND_RPCUSER=${BITCOIN_RPC_USER}", @@ -931,35 +891,68 @@ "NETWORK=bitcoin" ], "health_check": { - "type": "exec", "endpoint": "lightning-cli getinfo", "interval": "30s", + "retries": 3, "timeout": "5s", - "retries": 3 - }, - "bitcoin_integration": { - "rpc_access": "admin", - "sync_required": true + "type": "exec" }, + "id": "core-lightning", "lightning_integration": { "channel_management": true, "payment_routing": true - } + }, + "name": "Core Lightning (CLN)", + "ports": [ + { + "container": 9735, + "host": 9736, + "protocol": "tcp" + }, + { + "container": 9835, + "host": 9835, + "protocol": "tcp" + } + ], + "resources": { + "cpu_limit": 1, + "disk_limit": "5Gi", + "memory_limit": "512Mi" + }, + "security": { + "apparmor_profile": "core-lightning", + "capabilities": [ + "NET_BIND_SERVICE" + ], + "network_policy": "isolated", + "no_new_privileges": true, + "readonly_root": true, + "seccomp_profile": "default", + "user": 1000 + }, + "version": "23.08.2", + "volumes": [ + { + "options": [ + "rw" + ], + "source": "/var/lib/archipelago/core-lightning", + "target": "/home/clightning/.lightning", + "type": "bind" + } + ] } - } + }, + "version": "23.08.2" }, "cryptpad": { - "version": "2024.12.0", - "image": "146.59.87.168:3000/lfg2025/cryptpad:2024.12.0" + "image": "146.59.87.168:3000/lfg2025/cryptpad:2024.12.0", + "version": "2024.12.0" }, "did-wallet": { - "version": "1.0.0", "manifest": { "app": { - "id": "did-wallet", - "name": "Web5 DID Wallet", - "version": "1.0.0", - "description": "Web5 wallet with Decentralized Identifier (DID) support. Manage your digital identity and Web5 assets.", "container": { "image": "archipelago/did-wallet:1.0.0", "image_signature": "cosign://...", @@ -970,65 +963,65 @@ "storage": "2Gi" } ], - "resources": { - "cpu_limit": 1, - "memory_limit": "512Mi", - "disk_limit": "2Gi" - }, - "security": { - "capabilities": [], - "readonly_root": true, - "no_new_privileges": true, - "user": 1000, - "seccomp_profile": "default", - "network_policy": "isolated", - "apparmor_profile": "did-wallet" - }, - "ports": [ - { - "host": 8083, - "container": 8080, - "protocol": "tcp" - } - ], - "volumes": [ - { - "type": "bind", - "source": "/var/lib/archipelago/did-wallet", - "target": "/app/wallet", - "options": [ - "rw" - ] - } - ], + "description": "Web5 wallet with Decentralized Identifier (DID) support. Manage your digital identity and Web5 assets.", "environment": [ "WALLET_STORAGE=/app/wallet" ], "health_check": { - "type": "http", "endpoint": "http://localhost:8083", - "path": "/health", "interval": "30s", + "path": "/health", + "retries": 3, "timeout": "5s", - "retries": 3 + "type": "http" }, + "id": "did-wallet", + "name": "Web5 DID Wallet", + "ports": [ + { + "container": 8080, + "host": 8083, + "protocol": "tcp" + } + ], + "resources": { + "cpu_limit": 1, + "disk_limit": "2Gi", + "memory_limit": "512Mi" + }, + "security": { + "apparmor_profile": "did-wallet", + "capabilities": [], + "network_policy": "isolated", + "no_new_privileges": true, + "readonly_root": true, + "seccomp_profile": "default", + "user": 1000 + }, + "version": "1.0.0", + "volumes": [ + { + "options": [ + "rw" + ], + "source": "/var/lib/archipelago/did-wallet", + "target": "/app/wallet", + "type": "bind" + } + ], "web5_integration": { + "bitcoin_integration": true, "did_support": true, - "wallet_functionality": true, - "bitcoin_integration": true + "wallet_functionality": true } } - } + }, + "version": "1.0.0" }, "electrs-ui": { - "version": "latest", "image": "146.59.87.168:3000/lfg2025/electrs-ui:latest", "manifest": { "app": { - "id": "electrs-ui", - "name": "Electrs UI", - "version": "1.0.0", - "description": "Archipelago-native HTTP frontend for electrs/electrumx status. Runs\nnginx inside a container, serves static assets, and proxies\n/electrs-status to the archipelago backend on 127.0.0.1:5678.\n", "container": { "build": { "context": "/opt/archipelago/docker/electrs-ui", @@ -1037,48 +1030,53 @@ } }, "dependencies": [], + "description": "Archipelago-native HTTP frontend for electrs/electrumx status. Runs\nnginx inside a container, serves static assets, and proxies\n/electrs-status to the archipelago backend on 127.0.0.1:5678.\n", + "environment": [], + "health_check": { + "endpoint": "http://127.0.0.1:50002", + "interval": "30s", + "path": "/", + "retries": 3, + "timeout": "5s", + "type": "http" + }, + "id": "electrs-ui", + "name": "Electrs UI", + "ports": [], "resources": { "memory_limit": "64Mi" }, "security": { - "readonly_root": false, - "network_policy": "host" + "network_policy": "host", + "readonly_root": false }, - "ports": [], - "volumes": [], - "environment": [], - "health_check": { - "type": "http", - "endpoint": "http://127.0.0.1:50002", - "path": "/", - "interval": "30s", - "timeout": "5s", - "retries": 3 - } + "version": "1.0.0", + "volumes": [] } - } + }, + "version": "latest" }, "electrumx": { - "version": "v1.18.0", "image": "146.59.87.168:3000/lfg2025/electrumx:v1.18.0", "manifest": { "app": { - "id": "electrumx", - "name": "ElectrumX", - "version": "1.18.0", - "description": "Electrum server indexing Bitcoin chain data for lightweight wallet queries.", + "bitcoin_integration": { + "pruning_support": false, + "rpc_access": "read-only", + "sync_required": true + }, "container": { - "image": "146.59.87.168:3000/lfg2025/electrumx:v1.18.0", - "pull_policy": "if-not-present", - "network": "archy-net", + "custom_args": [ + "export DAEMON_URL=\"http://archipelago:$(printenv BITCOIN_RPC_PASS)@bitcoin-knots:8332/\"; exec electrumx_server" + ], "data_uid": "1000:1000", "entrypoint": [ "sh", "-lc" ], - "custom_args": [ - "export DAEMON_URL=\"http://archipelago:$(printenv BITCOIN_RPC_PASS)@bitcoin-knots:8332/\"; exec electrumx_server" - ], + "image": "146.59.87.168:3000/lfg2025/electrumx:v1.18.0", + "network": "archy-net", + "pull_policy": "if-not-present", "secret_env": [ { "key": "BITCOIN_RPC_PASS", @@ -1095,35 +1093,7 @@ "storage": "50Gi" } ], - "resources": { - "cpu_limit": 0, - "memory_limit": "6Gi", - "disk_limit": "50Gi" - }, - "security": { - "capabilities": [ - "DAC_OVERRIDE" - ], - "readonly_root": false, - "network_policy": "isolated" - }, - "ports": [ - { - "host": 50001, - "container": 50001, - "protocol": "tcp" - } - ], - "volumes": [ - { - "type": "bind", - "source": "/var/lib/archipelago/electrumx", - "target": "/data", - "options": [ - "rw" - ] - } - ], + "description": "Electrum server indexing Bitcoin chain data for lightweight wallet queries.", "environment": [ "COIN=Bitcoin", "DB_DIRECTORY=/data", @@ -1131,51 +1101,72 @@ "CACHE_MB=1024", "MAX_SEND=10000000" ], - "interfaces": { - "main": { - "name": "Web UI", - "description": "ElectrumX server status and connection details", - "type": "ui", - "port": 50002, - "protocol": "http" - } - }, "health_check": { - "type": "tcp", "endpoint": "localhost:50001", "interval": "30s", - "timeout": "5s", "retries": 3, - "start_period": "10m" + "start_period": "10m", + "timeout": "5s", + "type": "tcp" }, - "bitcoin_integration": { - "rpc_access": "read-only", - "sync_required": true, - "pruning_support": false - } + "id": "electrumx", + "interfaces": { + "main": { + "description": "ElectrumX server status and connection details", + "name": "Web UI", + "port": 50002, + "protocol": "http", + "type": "ui" + } + }, + "name": "ElectrumX", + "ports": [ + { + "container": 50001, + "host": 50001, + "protocol": "tcp" + } + ], + "resources": { + "cpu_limit": 0, + "disk_limit": "50Gi", + "memory_limit": "6Gi" + }, + "security": { + "capabilities": [ + "DAC_OVERRIDE" + ], + "network_policy": "isolated", + "readonly_root": false + }, + "version": "1.18.0", + "volumes": [ + { + "options": [ + "rw" + ], + "source": "/var/lib/archipelago/electrumx", + "target": "/data", + "type": "bind" + } + ] } - } + }, + "version": "v1.18.0" }, "fedimint": { - "version": "v0.10.0", "image": "146.59.87.168:3000/lfg2025/fedimintd:v0.10.0", "manifest": { "app": { - "id": "fedimint", - "name": "Fedimint Guardian", - "version": "0.10.0", - "description": "Federated Bitcoin minting service with built-in Guardian UI. Privacy-preserving Bitcoin custody.", + "bitcoin_integration": { + "rpc_access": "admin", + "sync_required": true + }, "container": { - "image": "146.59.87.168:3000/lfg2025/fedimintd:v0.10.0", - "pull_policy": "if-not-present", - "network": "archy-net", - "entrypoint": [ - "sh", - "-lc" - ], "custom_args": [ "until state=\"$(curl -sS --connect-timeout 5 -m 45 -u \"$FM_BITCOIND_USERNAME:$FM_BITCOIND_PASSWORD\" -H \"Content-Type: application/json\" --data-binary '{\"jsonrpc\":\"1.0\",\"id\":\"fedimint-wait\",\"method\":\"getblockchaininfo\",\"params\":[]}' \"$FM_BITCOIND_URL/\")\" && echo \"$state\" | grep -q '\"initialblockdownload\":false'; do\n echo \"Waiting for Bitcoin RPC sync at $FM_BITCOIND_URL...\";\n sleep 30;\ndone;\nexec fedimintd" ], + "data_uid": "1000:1000", "derived_env": [ { "key": "FM_P2P_URL", @@ -1186,13 +1177,19 @@ "template": "ws://{{HOST_MDNS}}:8174" } ], + "entrypoint": [ + "sh", + "-lc" + ], + "image": "146.59.87.168:3000/lfg2025/fedimintd:v0.10.0", + "network": "archy-net", + "pull_policy": "if-not-present", "secret_env": [ { "key": "FM_BITCOIND_PASSWORD", "secret_file": "bitcoin-rpc-password" } - ], - "data_uid": "1000:1000" + ] }, "dependencies": [ { @@ -1203,43 +1200,7 @@ "storage": "20Gi" } ], - "resources": { - "cpu_limit": 4, - "memory_limit": "4Gi", - "disk_limit": "20Gi" - }, - "security": { - "capabilities": [], - "readonly_root": true, - "network_policy": "isolated" - }, - "ports": [ - { - "host": 8173, - "container": 8173, - "protocol": "tcp" - }, - { - "host": 8174, - "container": 8174, - "protocol": "tcp" - }, - { - "host": 8177, - "container": 8175, - "protocol": "tcp" - } - ], - "volumes": [ - { - "type": "bind", - "source": "/var/lib/archipelago/fedimint", - "target": "/data", - "options": [ - "rw" - ] - } - ], + "description": "Federated Bitcoin minting service with built-in Guardian UI. Privacy-preserving Bitcoin custody.", "environment": [ "FM_DATA_DIR=/data", "FM_BITCOIND_URL=http://bitcoin-knots:8332", @@ -1250,65 +1211,120 @@ "FM_BIND_UI=0.0.0.0:8175" ], "health_check": { - "type": "http", "endpoint": "http://localhost:8175", - "path": "/", "interval": "30s", + "path": "/", + "retries": 3, "timeout": "5s", - "retries": 3 + "type": "http" }, + "id": "fedimint", "interfaces": { "main": { - "name": "Guardian UI", "description": "Fedimint Guardian wait/proxy UI", - "type": "ui", + "name": "Guardian UI", + "path": "/", "port": 8175, "protocol": "http", - "path": "/" + "type": "ui" } }, - "bitcoin_integration": { - "rpc_access": "admin", - "sync_required": true - } + "name": "Fedimint Guardian", + "ports": [ + { + "container": 8173, + "host": 8173, + "protocol": "tcp" + }, + { + "container": 8174, + "host": 8174, + "protocol": "tcp" + }, + { + "container": 8175, + "host": 8177, + "protocol": "tcp" + } + ], + "resources": { + "cpu_limit": 4, + "disk_limit": "20Gi", + "memory_limit": "4Gi" + }, + "security": { + "capabilities": [], + "network_policy": "isolated", + "readonly_root": true + }, + "version": "0.10.0", + "volumes": [ + { + "options": [ + "rw" + ], + "source": "/var/lib/archipelago/fedimint", + "target": "/data", + "type": "bind" + } + ] } - } + }, + "version": "v0.10.0" }, "fedimint-clientd": { - "version": "0.8.0", "manifest": { "app": { - "id": "fedimint-clientd", - "name": "Fedimint Client", - "version": "0.8.0", - "description": "Fedimint ecash client daemon (fmcd). Lets the node hold Fedimint ecash and join federations; the wallet talks to it over a local REST API.", "container": { - "image": "146.59.87.168:3000/lfg2025/fmcd:0.8.1", - "pull_policy": "if-not-present", - "network": "archy-net", + "data_uid": "1000:1000", "generated_secrets": [ { - "name": "fmcd-password", - "kind": "hex16" + "kind": "hex16", + "name": "fmcd-password" } ], + "image": "146.59.87.168:3000/lfg2025/fmcd:0.8.1", + "network": "archy-net", + "pull_policy": "if-not-present", "secret_env": [ { "key": "FMCD_PASSWORD", "secret_file": "fmcd-password" } - ], - "data_uid": "1000:1000" + ] }, "dependencies": [ { "storage": "2Gi" } ], + "description": "Fedimint ecash client daemon (fmcd). Lets the node hold Fedimint ecash and join federations; the wallet talks to it over a local REST API.", + "environment": [ + "FMCD_ADDR=0.0.0.0:8080", + "FMCD_MODE=rest", + "FMCD_DATA_DIR=/data", + "FMCD_INVITE_CODE=fed11qgqyj3mfwfhksw309uuxywtxxfjrjc35xuexverpxdsnxcnrxucxvenzveskgc3kvvun2c34xp3k2ep38yunzdpexcekxe3hvd3rvvmx8pnrvdenx5mnzvtzqqqjqt0t6pc3s5z0ynqjw9s4njf6svwgu59kweawc0vvrddcjeemw6yyn4pcdp" + ], + "health_check": { + "endpoint": "localhost:8080", + "interval": "30s", + "retries": 3, + "timeout": "5s", + "type": "tcp" + }, + "id": "fedimint-clientd", + "name": "Fedimint Client", + "ports": [ + { + "container": 8080, + "host": 8178, + "protocol": "tcp" + } + ], "resources": { - "cpu_limit": 0.25, - "memory_limit": "1Gi", - "disk_limit": "2Gi" + "cpu_limit": 1, + "disk_limit": "2Gi", + "memory_limit": "1Gi" }, "security": { "capabilities": [ @@ -1318,68 +1334,50 @@ "SETUID", "SETGID" ], - "readonly_root": true, - "network_policy": "bridge" + "network_policy": "bridge", + "readonly_root": true }, - "ports": [ - { - "host": 8178, - "container": 8080, - "protocol": "tcp" - } - ], + "version": "0.8.0", "volumes": [ { - "type": "bind", - "source": "/var/lib/archipelago/fmcd", - "target": "/data", "options": [ "rw" - ] + ], + "source": "/var/lib/archipelago/fmcd", + "target": "/data", + "type": "bind" } - ], - "environment": [ - "FMCD_ADDR=0.0.0.0:8080", - "FMCD_MODE=rest", - "FMCD_DATA_DIR=/data", - "FMCD_INVITE_CODE=fed11qgqyj3mfwfhksw309uuxywtxxfjrjc35xuexverpxdsnxcnrxucxvenzveskgc3kvvun2c34xp3k2ep38yunzdpexcekxe3hvd3rvvmx8pnrvdenx5mnzvtzqqqjqt0t6pc3s5z0ynqjw9s4njf6svwgu59kweawc0vvrddcjeemw6yyn4pcdp" - ], - "health_check": { - "type": "tcp", - "endpoint": "localhost:8080", - "interval": "30s", - "timeout": "5s", - "retries": 3 - } + ] } - } + }, + "version": "0.8.0" }, "fedimint-gateway": { - "version": "v0.10.0", "image": "146.59.87.168:3000/lfg2025/gatewayd:v0.10.0", "manifest": { "app": { - "id": "fedimint-gateway", - "name": "Fedimint Gateway", - "version": "0.10.0", - "description": "Fedimint gateway service with automatic LND-or-LDK backend selection.", + "bitcoin_integration": { + "rpc_access": "admin", + "sync_required": true + }, "container": { - "image": "git.tx1138.com/lfg2025/gatewayd:v0.10.0", - "pull_policy": "if-not-present", - "network": "archy-net", + "custom_args": [ + "if [ -f /lnd/tls.cert ] && [ -f /lnd/data/chain/bitcoin/mainnet/admin.macaroon ]; then\n exec gatewayd --data-dir /data --listen 0.0.0.0:8176 --bcrypt-password-hash \"$FEDI_HASH\" --network bitcoin --bitcoind-url http://host.archipelago:8332 --bitcoind-username \"$FM_BITCOIND_USERNAME\" --bitcoind-password \"$FM_BITCOIND_PASSWORD\" lnd --lnd-rpc-host lnd:10009 --lnd-tls-cert /lnd/tls.cert --lnd-macaroon /lnd/data/chain/bitcoin/mainnet/admin.macaroon;\nelse\n exec gatewayd --data-dir /data --listen 0.0.0.0:8176 --bcrypt-password-hash \"$FEDI_HASH\" --network bitcoin --bitcoind-url http://host.archipelago:8332 --bitcoind-username \"$FM_BITCOIND_USERNAME\" --bitcoind-password \"$FM_BITCOIND_PASSWORD\" ldk --ldk-lightning-port 9737 --ldk-alias archipelago-gateway;\nfi" + ], + "data_uid": "1000:1000", "entrypoint": [ "sh", "-lc" ], - "custom_args": [ - "if [ -f /lnd/tls.cert ] && [ -f /lnd/data/chain/bitcoin/mainnet/admin.macaroon ]; then\n exec gatewayd --data-dir /data --listen 0.0.0.0:8176 --bcrypt-password-hash \"$FEDI_HASH\" --network bitcoin --bitcoind-url http://host.archipelago:8332 --bitcoind-username \"$FM_BITCOIND_USERNAME\" --bitcoind-password \"$FM_BITCOIND_PASSWORD\" lnd --lnd-rpc-host lnd:10009 --lnd-tls-cert /lnd/tls.cert --lnd-macaroon /lnd/data/chain/bitcoin/mainnet/admin.macaroon;\nelse\n exec gatewayd --data-dir /data --listen 0.0.0.0:8176 --bcrypt-password-hash \"$FEDI_HASH\" --network bitcoin --bitcoind-url http://host.archipelago:8332 --bitcoind-username \"$FM_BITCOIND_USERNAME\" --bitcoind-password \"$FM_BITCOIND_PASSWORD\" ldk --ldk-lightning-port 9737 --ldk-alias archipelago-gateway;\nfi" - ], "generated_secrets": [ { - "name": "fedimint-gateway-hash", - "kind": "bcrypt" + "kind": "bcrypt", + "name": "fedimint-gateway-hash" } ], + "image": "git.tx1138.com/lfg2025/gatewayd:v0.10.0", + "network": "archy-net", + "pull_policy": "if-not-present", "secret_env": [ { "key": "FM_BITCOIND_PASSWORD", @@ -1389,8 +1387,7 @@ "key": "FEDI_HASH", "secret_file": "fedimint-gateway-hash" } - ], - "data_uid": "1000:1000" + ] }, "dependencies": [ { @@ -1402,91 +1399,110 @@ "version": ">=0.10.0" } ], - "resources": { - "cpu_limit": 2, - "memory_limit": "2Gi", - "disk_limit": "10Gi" - }, - "security": { - "capabilities": [], - "readonly_root": true, - "network_policy": "isolated" - }, - "ports": [ - { - "host": 8176, - "container": 8176, - "protocol": "tcp" - }, - { - "host": 9737, - "container": 9737, - "protocol": "tcp" - } - ], - "volumes": [ - { - "type": "bind", - "source": "/var/lib/archipelago/fedimint-gateway", - "target": "/data", - "options": [ - "rw" - ] - }, - { - "type": "bind", - "source": "/var/lib/archipelago/lnd", - "target": "/lnd", - "options": [ - "ro" - ] - } - ], + "description": "Fedimint gateway service with automatic LND-or-LDK backend selection.", "environment": [ "FM_BITCOIND_USERNAME=archipelago" ], "health_check": { - "type": "http", "endpoint": "http://localhost:8176", - "path": "/", "interval": "30s", + "path": "/", + "retries": 3, "timeout": "5s", - "retries": 3 + "type": "http" }, - "bitcoin_integration": { - "rpc_access": "admin", - "sync_required": true - } + "id": "fedimint-gateway", + "name": "Fedimint Gateway", + "ports": [ + { + "container": 8176, + "host": 8176, + "protocol": "tcp" + }, + { + "container": 9737, + "host": 9737, + "protocol": "tcp" + } + ], + "resources": { + "cpu_limit": 2, + "disk_limit": "10Gi", + "memory_limit": "2Gi" + }, + "security": { + "capabilities": [], + "network_policy": "isolated", + "readonly_root": true + }, + "version": "0.10.0", + "volumes": [ + { + "options": [ + "rw" + ], + "source": "/var/lib/archipelago/fedimint-gateway", + "target": "/data", + "type": "bind" + }, + { + "options": [ + "ro" + ], + "source": "/var/lib/archipelago/lnd", + "target": "/lnd", + "type": "bind" + } + ] } - } + }, + "version": "v0.10.0" }, "filebrowser": { - "version": "v2.27.0", "image": "146.59.87.168:3000/lfg2025/filebrowser:v2.27.0", "manifest": { "app": { - "id": "filebrowser", - "name": "File Browser", - "version": "2.27.0", - "description": "Baseline Archipelago file manager service.", + "bitcoin_integration": { + "rpc_access": "none", + "sync_required": false + }, "container": { - "image": "git.tx1138.com/lfg2025/filebrowser:v2.27.0", - "pull_policy": "if-not-present", - "network": "archy-net", "custom_args": [ "--config", "/data/.filebrowser.json" ], - "data_uid": "100000:100000" + "data_uid": "100000:100000", + "image": "git.tx1138.com/lfg2025/filebrowser:v2.27.0", + "network": "archy-net", + "pull_policy": "if-not-present" }, "dependencies": [ { "storage": "10Gi" } ], + "description": "Baseline Archipelago file manager service.", + "environment": [], + "health_check": { + "endpoint": "http://localhost:80", + "interval": "30s", + "path": "/health", + "retries": 3, + "timeout": "5s", + "type": "http" + }, + "id": "filebrowser", + "name": "File Browser", + "ports": [ + { + "container": 80, + "host": 8083, + "protocol": "tcp" + } + ], "resources": { - "memory_limit": "256Mi", - "disk_limit": "10Gi" + "disk_limit": "10Gi", + "memory_limit": "256Mi" }, "security": { "capabilities": [ @@ -1497,62 +1513,39 @@ "DAC_OVERRIDE", "NET_BIND_SERVICE" ], - "readonly_root": false, - "network_policy": "isolated" + "network_policy": "isolated", + "readonly_root": false }, - "ports": [ - { - "host": 8083, - "container": 80, - "protocol": "tcp" - } - ], + "version": "2.27.0", "volumes": [ { - "type": "bind", + "options": [ + "rw" + ], "source": "/var/lib/archipelago/filebrowser", "target": "/srv", - "options": [ - "rw" - ] + "type": "bind" }, { - "type": "bind", - "source": "/var/lib/archipelago/filebrowser-data", - "target": "/data", "options": [ "rw" - ] + ], + "source": "/var/lib/archipelago/filebrowser-data", + "target": "/data", + "type": "bind" } - ], - "environment": [], - "health_check": { - "type": "http", - "endpoint": "http://localhost:80", - "path": "/health", - "interval": "30s", - "timeout": "5s", - "retries": 3 - }, - "bitcoin_integration": { - "rpc_access": "none", - "sync_required": false - } + ] } - } + }, + "version": "v2.27.0" }, "fips": { - "version": "v0.1.0", - "image": "146.59.87.168:3000/lfg2025/fips:v0.1.0" + "image": "146.59.87.168:3000/lfg2025/fips:v0.1.0", + "version": "v0.1.0" }, "fips-ui": { - "version": "1.0.0", "manifest": { "app": { - "id": "fips-ui", - "name": "FIPS Mesh", - "version": "1.0.0", - "description": "Archipelago-native dashboard for the FIPS mesh transport. Runs nginx\ninside a container with host networking, serves a static dashboard on\n:8336, and reverse-proxies /rpc/v1 to the archipelago backend on\n127.0.0.1:5678. All FIPS controls (status, seed anchors, reconnect,\nrestart, and stable-channel daemon updates) go through the existing\nfips.* RPC methods, authenticated by the browser's own archipelago\nsession \u2014 there is no separate secret to manage.\n", "container": { "build": { "context": "/opt/archipelago/docker/fips-ui", @@ -1560,35 +1553,35 @@ "tag": "localhost/fips-ui:local" } }, + "description": "Archipelago-native dashboard for the FIPS mesh transport. Runs nginx\ninside a container with host networking, serves a static dashboard on\n:8336, and reverse-proxies /rpc/v1 to the archipelago backend on\n127.0.0.1:5678. All FIPS controls (status, seed anchors, reconnect,\nrestart, and stable-channel daemon updates) go through the existing\nfips.* RPC methods, authenticated by the browser's own archipelago\nsession — there is no separate secret to manage.\n", + "environment": [], + "health_check": { + "endpoint": "http://127.0.0.1:8336", + "interval": "30s", + "path": "/", + "retries": 3, + "timeout": "5s", + "type": "http" + }, + "id": "fips-ui", + "name": "FIPS Mesh", + "ports": [], "resources": { "memory_limit": "128Mi" }, "security": { - "readonly_root": false, - "network_policy": "host" + "network_policy": "host", + "readonly_root": false }, - "ports": [], - "volumes": [], - "environment": [], - "health_check": { - "type": "http", - "endpoint": "http://127.0.0.1:8336", - "path": "/", - "interval": "30s", - "timeout": "5s", - "retries": 3 - } + "version": "1.0.0", + "volumes": [] } - } + }, + "version": "1.0.0" }, "gitea": { - "version": "1.23", "manifest": { "app": { - "id": "gitea", - "name": "Gitea", - "version": "1.23", - "description": "Self-hosted Git service with built-in container registry, CI/CD, and package hosting.", "category": "development", "container": { "image": "docker.io/gitea/gitea:1.23", @@ -1599,53 +1592,7 @@ "storage": "500Mi" } ], - "resources": { - "memory_limit": "256Mi", - "disk_limit": "500Mi" - }, - "security": { - "capabilities": [ - "CHOWN", - "FOWNER", - "SETUID", - "SETGID", - "DAC_OVERRIDE", - "NET_BIND_SERVICE" - ], - "readonly_root": false, - "no_new_privileges": false, - "network_policy": "bridge" - }, - "ports": [ - { - "host": 3001, - "container": 3000, - "protocol": "tcp" - }, - { - "host": 2222, - "container": 22, - "protocol": "tcp" - } - ], - "volumes": [ - { - "type": "bind", - "source": "/var/lib/archipelago/gitea/data", - "target": "/data", - "options": [ - "rw" - ] - }, - { - "type": "bind", - "source": "/var/lib/archipelago/gitea/config", - "target": "/etc/gitea", - "options": [ - "rw" - ] - } - ], + "description": "Self-hosted Git service with built-in container registry, CI/CD, and package hosting.", "environment": [ "GITEA__database__DB_TYPE=sqlite3", "GITEA__server__SSH_PORT=2222", @@ -1656,100 +1603,116 @@ "GITEA__repository__ENABLE_PUSH_CREATE_ORG=true" ], "health_check": { - "type": "http", "endpoint": "http://localhost:3000", - "path": "/", "interval": "120s", + "path": "/", + "retries": 5, "timeout": "30s", - "retries": 5 + "type": "http" }, + "id": "gitea", "interfaces": { "main": { - "name": "Web UI", "description": "Gitea web interface", - "type": "ui", + "name": "Web UI", + "path": "/", "port": 3001, "protocol": "http", - "path": "/" + "type": "ui" } }, "metadata": { - "icon": "/assets/img/app-icons/gitea.svg", - "repo": "https://gitea.com", - "tier": "optional", - "launch": { - "open_in_new_tab": true - }, "features": [ "Git repositories with web UI", "Built-in container/package registry", "Issue tracking and pull requests", "CI/CD via Gitea Actions", "Lightweight SQLite deployment" - ] + ], + "icon": "/assets/img/app-icons/gitea.svg", + "launch": { + "open_in_new_tab": true + }, + "repo": "https://gitea.com", + "tier": "optional" }, + "name": "Gitea", "nginx_proxy": { - "listen": 3000, - "proxy_pass": "http://127.0.0.1:3001", "extra_headers": [ "proxy_hide_header X-Frame-Options", "proxy_hide_header Content-Security-Policy" - ] - } + ], + "listen": 3000, + "proxy_pass": "http://127.0.0.1:3001" + }, + "ports": [ + { + "container": 3000, + "host": 3001, + "protocol": "tcp" + }, + { + "container": 22, + "host": 2222, + "protocol": "tcp" + } + ], + "resources": { + "disk_limit": "500Mi", + "memory_limit": "256Mi" + }, + "security": { + "capabilities": [ + "CHOWN", + "FOWNER", + "SETUID", + "SETGID", + "DAC_OVERRIDE", + "NET_BIND_SERVICE" + ], + "network_policy": "bridge", + "no_new_privileges": false, + "readonly_root": false + }, + "version": "1.23", + "volumes": [ + { + "options": [ + "rw" + ], + "source": "/var/lib/archipelago/gitea/data", + "target": "/data", + "type": "bind" + }, + { + "options": [ + "rw" + ], + "source": "/var/lib/archipelago/gitea/config", + "target": "/etc/gitea", + "type": "bind" + } + ] } - } + }, + "version": "1.23" }, "grafana": { - "version": "10.2.0", "image": "146.59.87.168:3000/lfg2025/grafana:10.2.0", "manifest": { "app": { - "id": "grafana", - "name": "Grafana", - "version": "10.2.0", - "description": "Analytics and monitoring platform. Visualize metrics and create dashboards.", "container": { + "data_uid": "472:472", "image": "grafana/grafana:10.2.0", "image_signature": "cosign://...", - "pull_policy": "if-not-present", - "data_uid": "472:472" + "pull_policy": "if-not-present" }, "dependencies": [ { "storage": "5Gi" } ], - "resources": { - "cpu_limit": 2, - "memory_limit": "1Gi", - "disk_limit": "5Gi" - }, - "security": { - "capabilities": [], - "readonly_root": true, - "no_new_privileges": true, - "user": 1000, - "seccomp_profile": "default", - "network_policy": "isolated", - "apparmor_profile": "grafana" - }, - "ports": [ - { - "host": 3000, - "container": 3000, - "protocol": "tcp" - } - ], - "volumes": [ - { - "type": "bind", - "source": "/var/lib/archipelago/grafana", - "target": "/var/lib/grafana", - "options": [ - "rw" - ] - } - ], + "description": "Analytics and monitoring platform. Visualize metrics and create dashboards.", "environment": [ "GF_SECURITY_ADMIN_USER=admin", "GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD}", @@ -1757,46 +1720,117 @@ "GF_INSTALL_PLUGINS=" ], "health_check": { - "type": "http", "endpoint": "http://localhost:3000", - "path": "/api/health", "interval": "30s", + "path": "/api/health", + "retries": 5, "timeout": "30s", - "retries": 5 + "type": "http" }, + "id": "grafana", "metadata": { "launch": { "open_in_new_tab": true } - } + }, + "name": "Grafana", + "ports": [ + { + "container": 3000, + "host": 3000, + "protocol": "tcp" + } + ], + "resources": { + "cpu_limit": 2, + "disk_limit": "5Gi", + "memory_limit": "1Gi" + }, + "security": { + "apparmor_profile": "grafana", + "capabilities": [], + "network_policy": "isolated", + "no_new_privileges": true, + "readonly_root": true, + "seccomp_profile": "default", + "user": 1000 + }, + "version": "10.2.0", + "volumes": [ + { + "options": [ + "rw" + ], + "source": "/var/lib/archipelago/grafana", + "target": "/var/lib/grafana", + "type": "bind" + } + ] } - } + }, + "version": "10.2.0" }, "homeassistant": { - "version": "2024.1", "image": "146.59.87.168:3000/lfg2025/home-assistant:2024.1", "manifest": { "app": { - "id": "homeassistant", - "name": "Home Assistant", - "version": "2024.1.0", - "description": "Open source home automation platform. Control and monitor your smart home devices.", "container": { "image": "146.59.87.168:3000/lfg2025/home-assistant:2024.1", - "pull_policy": "if-not-present", - "network": "pasta" + "network": "pasta", + "pull_policy": "if-not-present" }, "dependencies": [ { "storage": "10Gi" } ], + "description": "Open source home automation platform. Control and monitor your smart home devices.", + "devices": [], + "environment": [ + "TZ=UTC" + ], + "health_check": { + "endpoint": "localhost:8123", + "interval": "30s", + "retries": 3, + "timeout": "5s", + "type": "tcp" + }, + "id": "homeassistant", + "interfaces": { + "main": { + "description": "Home Assistant dashboard", + "name": "Web UI", + "path": "/", + "port": 8123, + "protocol": "http", + "type": "ui" + } + }, + "metadata": { + "author": "Home Assistant", + "category": "home", + "icon": "/assets/img/app-icons/homeassistant.png", + "launch": { + "open_in_new_tab": true + }, + "repo": "https://github.com/home-assistant/core" + }, + "name": "Home Assistant", + "ports": [ + { + "container": 8123, + "host": 8123, + "protocol": "tcp" + } + ], "resources": { "cpu_limit": 2, - "memory_limit": "512Mi", - "disk_limit": "10Gi" + "disk_limit": "10Gi", + "memory_limit": "512Mi" }, "security": { + "apparmor_profile": "home-assistant", "capabilities": [ "CHOWN", "FOWNER", @@ -1806,82 +1840,40 @@ "NET_BIND_SERVICE", "NET_RAW" ], - "readonly_root": false, - "no_new_privileges": true, - "user": 1000, - "seccomp_profile": "default", "network_policy": "isolated", - "apparmor_profile": "home-assistant" + "no_new_privileges": true, + "readonly_root": false, + "seccomp_profile": "default", + "user": 1000 }, - "ports": [ - { - "host": 8123, - "container": 8123, - "protocol": "tcp" - } - ], + "version": "2024.1.0", "volumes": [ { - "type": "bind", - "source": "/var/lib/archipelago/home-assistant", - "target": "/config", "options": [ "rw" - ] + ], + "source": "/var/lib/archipelago/home-assistant", + "target": "/config", + "type": "bind" } - ], - "devices": [], - "environment": [ - "TZ=UTC" - ], - "health_check": { - "type": "tcp", - "endpoint": "localhost:8123", - "interval": "30s", - "timeout": "5s", - "retries": 3 - }, - "interfaces": { - "main": { - "name": "Web UI", - "description": "Home Assistant dashboard", - "type": "ui", - "port": 8123, - "protocol": "http", - "path": "/" - } - }, - "metadata": { - "icon": "/assets/img/app-icons/homeassistant.png", - "category": "home", - "author": "Home Assistant", - "repo": "https://github.com/home-assistant/core", - "launch": { - "open_in_new_tab": true - } - } + ] } - } + }, + "version": "2024.1" }, "immich": { - "version": "release", "image": "146.59.87.168:3000/lfg2025/immich-server:release", "images": { - "immich_server": "146.59.87.168:3000/lfg2025/immich-server:release", "immich_postgres": "146.59.87.168:3000/lfg2025/immich-postgres:14-vectorchord0.4.3-pgvectors0.2.0", - "immich_redis": "146.59.87.168:3000/lfg2025/redis:7.4.8" + "immich_redis": "146.59.87.168:3000/lfg2025/redis:7.4.8", + "immich_server": "146.59.87.168:3000/lfg2025/immich-server:release" }, "manifest": { "app": { - "id": "immich", - "name": "Immich", - "version": "2.7.4", - "description": "Self-hosted photo and video backup with mobile apps and search.", - "container_name": "immich_server", "container": { "image": "146.59.87.168:3000/lfg2025/immich-server:release", - "pull_policy": "if-not-present", "network": "archy-net", + "pull_policy": "if-not-present", "secret_env": [ { "key": "DB_PASSWORD", @@ -1889,6 +1881,7 @@ } ] }, + "container_name": "immich_server", "dependencies": [ { "app_id": "immich-postgres" @@ -1900,32 +1893,7 @@ "storage": "200Gi" } ], - "resources": { - "memory_limit": "2Gi", - "disk_limit": "200Gi" - }, - "security": { - "capabilities": [], - "readonly_root": false, - "network_policy": "isolated" - }, - "ports": [ - { - "host": 2283, - "container": 2283, - "protocol": "tcp" - } - ], - "volumes": [ - { - "type": "bind", - "source": "/var/lib/archipelago/immich", - "target": "/usr/src/app/upload", - "options": [ - "rw" - ] - } - ], + "description": "Self-hosted photo and video backup with mobile apps and search.", "environment": [ "DB_HOSTNAME=immich_postgres", "DB_USERNAME=postgres", @@ -1934,51 +1902,75 @@ "UPLOAD_LOCATION=/usr/src/app/upload" ], "health_check": { - "type": "http", "endpoint": "http://localhost:2283", - "path": "/api/server/ping", "interval": "30s", + "path": "/api/server/ping", + "retries": 20, "timeout": "5s", - "retries": 20 + "type": "http" }, + "id": "immich", "interfaces": { "main": { - "name": "Web UI", "description": "Immich photo library", - "type": "ui", + "name": "Web UI", + "path": "/", "port": 2283, "protocol": "http", - "path": "/" + "type": "ui" } }, "metadata": { "launch": { "open_in_new_tab": true } - } + }, + "name": "Immich", + "ports": [ + { + "container": 2283, + "host": 2283, + "protocol": "tcp" + } + ], + "resources": { + "disk_limit": "200Gi", + "memory_limit": "2Gi" + }, + "security": { + "capabilities": [], + "network_policy": "isolated", + "readonly_root": false + }, + "version": "2.7.4", + "volumes": [ + { + "options": [ + "rw" + ], + "source": "/var/lib/archipelago/immich", + "target": "/usr/src/app/upload", + "type": "bind" + } + ] } - } + }, + "version": "release" }, "immich-postgres": { - "version": "14-vectorchord0.4.3-pgvectors0.2.0", "manifest": { "app": { - "id": "immich-postgres", - "name": "Immich Postgres", - "version": "14-vectorchord0.4.3-pgvectors0.2.0", - "description": "Postgres (pgvecto.rs / vectorchord) backend for Immich.", - "container_name": "immich_postgres", "container": { - "image": "146.59.87.168:3000/lfg2025/immich-postgres:14-vectorchord0.4.3-pgvectors0.2.0", - "pull_policy": "if-not-present", - "network": "archy-net", "data_uid": "100998:100998", "generated_secrets": [ { - "name": "immich-db-password", - "kind": "hex32" + "kind": "hex32", + "name": "immich-db-password" } ], + "image": "146.59.87.168:3000/lfg2025/immich-postgres:14-vectorchord0.4.3-pgvectors0.2.0", + "network": "archy-net", + "pull_policy": "if-not-present", "secret_env": [ { "key": "POSTGRES_PASSWORD", @@ -1986,14 +1978,30 @@ } ] }, + "container_name": "immich_postgres", "dependencies": [ { "storage": "40Gi" } ], + "description": "Postgres (pgvecto.rs / vectorchord) backend for Immich.", + "environment": [ + "POSTGRES_USER=postgres", + "POSTGRES_DB=immich" + ], + "health_check": { + "endpoint": "localhost:5432", + "interval": "30s", + "retries": 3, + "timeout": "5s", + "type": "tcp" + }, + "id": "immich-postgres", + "name": "Immich Postgres", + "ports": [], "resources": { - "memory_limit": "2Gi", - "disk_limit": "40Gi" + "disk_limit": "40Gi", + "memory_limit": "2Gi" }, "security": { "capabilities": [ @@ -2003,49 +2011,46 @@ "SETGID", "SETUID" ], - "readonly_root": false, - "network_policy": "isolated" + "network_policy": "isolated", + "readonly_root": false }, - "ports": [], + "version": "14-vectorchord0.4.3-pgvectors0.2.0", "volumes": [ { - "type": "bind", - "source": "/var/lib/archipelago/immich-db", - "target": "/var/lib/postgresql/data", "options": [ "rw" - ] + ], + "source": "/var/lib/archipelago/immich-db", + "target": "/var/lib/postgresql/data", + "type": "bind" } - ], - "environment": [ - "POSTGRES_USER=postgres", - "POSTGRES_DB=immich" - ], - "health_check": { - "type": "tcp", - "endpoint": "localhost:5432", - "interval": "30s", - "timeout": "5s", - "retries": 3 - } + ] } - } + }, + "version": "14-vectorchord0.4.3-pgvectors0.2.0" }, "immich-redis": { - "version": "7-alpine", "manifest": { "app": { - "id": "immich-redis", - "name": "Immich Redis", - "version": "7-alpine", - "description": "Valkey (Redis-compatible) cache for Immich.", - "container_name": "immich_redis", "container": { "image": "146.59.87.168:3000/lfg2025/valkey:7-alpine", - "pull_policy": "if-not-present", - "network": "archy-net" + "network": "archy-net", + "pull_policy": "if-not-present" }, + "container_name": "immich_redis", "dependencies": [], + "description": "Valkey (Redis-compatible) cache for Immich.", + "environment": [], + "health_check": { + "endpoint": "localhost:6379", + "interval": "30s", + "retries": 3, + "timeout": "5s", + "type": "tcp" + }, + "id": "immich-redis", + "name": "Immich Redis", + "ports": [], "resources": { "memory_limit": "128Mi" }, @@ -2054,24 +2059,16 @@ "SETGID", "SETUID" ], - "readonly_root": false, - "network_policy": "isolated" + "network_policy": "isolated", + "readonly_root": false }, - "ports": [], - "volumes": [], - "environment": [], - "health_check": { - "type": "tcp", - "endpoint": "localhost:6379", - "interval": "30s", - "timeout": "5s", - "retries": 3 - } + "version": "7-alpine", + "volumes": [] } - } + }, + "version": "7-alpine" }, "indeedhub": { - "version": "1.0.0", "image": "146.59.87.168:3000/lfg2025/indeedhub:1.0.0", "images": { "indeedhub": "146.59.87.168:3000/lfg2025/indeedhub:1.0.0", @@ -2080,17 +2077,13 @@ }, "manifest": { "app": { - "id": "indeedhub", - "name": "IndeeHub", - "version": "1.0.0", - "description": "Bitcoin documentary streaming platform featuring God Bless Bitcoin and other educational content about Bitcoin, sovereignty, and decentralized technology. Sign in with your Nostr identity.", "category": "community", - "container_name": "indeedhub", "container": { "image": "146.59.87.168:3000/lfg2025/indeedhub:1.0.0", - "pull_policy": "if-not-present", - "network": "indeedhub-net" + "network": "indeedhub-net", + "pull_policy": "if-not-present" }, + "container_name": "indeedhub", "dependencies": [ { "app_id": "indeedhub-api" @@ -2099,50 +2092,16 @@ "storage": "1Gi" } ], - "resources": { - "memory_limit": "512Mi", - "disk_limit": "1Gi" - }, - "security": { - "capabilities": [ - "CHOWN", - "DAC_OVERRIDE", - "SETGID", - "SETUID" - ], - "readonly_root": false, - "network_policy": "isolated" - }, - "ports": [ - { - "host": 7778, - "container": 7777, - "protocol": "tcp" - } - ], - "volumes": [ - { - "type": "tmpfs", - "target": "/run", - "options": [ - "rw", - "nosuid", - "nodev", - "size=16m" - ] - }, - { - "type": "tmpfs", - "target": "/var/cache/nginx", - "options": [ - "rw", - "nosuid", - "nodev", - "size=32m" - ] - } - ], + "description": "Bitcoin documentary streaming platform featuring God Bless Bitcoin and other educational content about Bitcoin, sovereignty, and decentralized technology. Sign in with your Nostr identity.", "environment": [], + "health_check": { + "endpoint": "localhost:7777", + "interval": "30s", + "retries": 5, + "start_period": "30s", + "timeout": "5s", + "type": "tcp" + }, "hooks": { "post_install": [ { @@ -2155,8 +2114,8 @@ }, { "copy_from_host": { - "src": "web-ui/nostr-provider.js", - "dest": "/usr/share/nginx/html/nostr-provider.js" + "dest": "/usr/share/nginx/html/nostr-provider.js", + "src": "web-ui/nostr-provider.js" } }, { @@ -2175,30 +2134,22 @@ } ] }, - "health_check": { - "type": "tcp", - "endpoint": "localhost:7777", - "interval": "30s", - "timeout": "5s", - "retries": 5, - "start_period": "30s" - }, + "id": "indeedhub", "interfaces": { "main": { - "name": "Web UI", "description": "Stream Bitcoin documentaries with Nostr identity", - "type": "ui", + "name": "Web UI", + "path": "/", "port": 7778, "protocol": "http", - "path": "/" + "type": "ui" } }, "metadata": { "author": "Indeehub Team", "icon": "/assets/img/app-icons/indeedhub.png", - "website": "https://indeedhub.com", - "repo": "https://github.com/indeedhub/indeedhub", "license": "MIT", + "repo": "https://github.com/indeedhub/indeedhub", "tags": [ "bitcoin", "documentary", @@ -2206,34 +2157,75 @@ "media", "education", "nostr" - ] - } + ], + "website": "https://indeedhub.com" + }, + "name": "IndeeHub", + "ports": [ + { + "container": 7777, + "host": 7778, + "protocol": "tcp" + } + ], + "resources": { + "disk_limit": "1Gi", + "memory_limit": "512Mi" + }, + "security": { + "capabilities": [ + "CHOWN", + "DAC_OVERRIDE", + "SETGID", + "SETUID" + ], + "network_policy": "isolated", + "readonly_root": false + }, + "version": "1.0.0", + "volumes": [ + { + "options": [ + "rw", + "nosuid", + "nodev", + "size=16m" + ], + "target": "/run", + "type": "tmpfs" + }, + { + "options": [ + "rw", + "nosuid", + "nodev", + "size=32m" + ], + "target": "/var/cache/nginx", + "type": "tmpfs" + } + ] } - } + }, + "version": "1.0.0" }, "indeedhub-api": { - "version": "1.0.0", "manifest": { "app": { - "id": "indeedhub-api", - "name": "IndeedHub API", - "version": "1.0.0", - "description": "IndeedHub backend API (Nostr auth, media, payments).", "category": "community", - "container_name": "indeedhub-api", "container": { + "generated_secrets": [ + { + "kind": "hex32", + "name": "indeedhub-jwt" + } + ], "image": "146.59.87.168:3000/lfg2025/indeedhub-api:1.0.0", - "pull_policy": "if-not-present", "network": "indeedhub-net", "network_aliases": [ "api" ], - "generated_secrets": [ - { - "name": "indeedhub-jwt", - "kind": "hex32" - } - ], + "pull_policy": "if-not-present", "secret_env": [ { "key": "DATABASE_PASSWORD", @@ -2249,6 +2241,7 @@ } ] }, + "container_name": "indeedhub-api", "dependencies": [ { "app_id": "indeedhub-postgres" @@ -2260,16 +2253,7 @@ "app_id": "indeedhub-minio" } ], - "resources": { - "memory_limit": "2Gi" - }, - "security": { - "capabilities": [], - "readonly_root": false, - "network_policy": "isolated" - }, - "ports": [], - "volumes": [], + "description": "IndeedHub backend API (Nostr auth, media, payments).", "environment": [ "PORT=4000", "DATABASE_HOST=postgres", @@ -2289,29 +2273,37 @@ "ENVIRONMENT=production" ], "health_check": { - "type": "tcp", "endpoint": "localhost:4000", "interval": "30s", + "retries": 10, "timeout": "5s", - "retries": 10 - } + "type": "tcp" + }, + "id": "indeedhub-api", + "name": "IndeedHub API", + "ports": [], + "resources": { + "memory_limit": "2Gi" + }, + "security": { + "capabilities": [], + "network_policy": "isolated", + "readonly_root": false + }, + "version": "1.0.0", + "volumes": [] } - } + }, + "version": "1.0.0" }, "indeedhub-ffmpeg": { - "version": "1.0.0", "manifest": { "app": { - "id": "indeedhub-ffmpeg", - "name": "IndeedHub FFmpeg Worker", - "version": "1.0.0", - "description": "IndeedHub background media transcoding worker.", "category": "community", - "container_name": "indeedhub-ffmpeg", "container": { "image": "146.59.87.168:3000/lfg2025/indeedhub-ffmpeg:1.0.0", - "pull_policy": "if-not-present", "network": "indeedhub-net", + "pull_policy": "if-not-present", "secret_env": [ { "key": "DATABASE_PASSWORD", @@ -2323,21 +2315,13 @@ } ] }, + "container_name": "indeedhub-ffmpeg", "dependencies": [ { "app_id": "indeedhub-api" } ], - "resources": { - "memory_limit": "4Gi" - }, - "security": { - "capabilities": [], - "readonly_root": false, - "network_policy": "isolated" - }, - "ports": [], - "volumes": [], + "description": "IndeedHub background media transcoding worker.", "environment": [ "DATABASE_HOST=postgres", "DATABASE_PORT=5432", @@ -2352,37 +2336,45 @@ "S3_PRIVATE_BUCKET_NAME=indeedhub-private", "ENVIRONMENT=production", "AES_MASTER_SECRET=0123456789abcdef0123456789abcdef" - ] + ], + "id": "indeedhub-ffmpeg", + "name": "IndeedHub FFmpeg Worker", + "ports": [], + "resources": { + "memory_limit": "4Gi" + }, + "security": { + "capabilities": [], + "network_policy": "isolated", + "readonly_root": false + }, + "version": "1.0.0", + "volumes": [] } - } + }, + "version": "1.0.0" }, "indeedhub-minio": { - "version": "RELEASE.2024-11-07T00-52-20Z", "manifest": { "app": { - "id": "indeedhub-minio", - "name": "IndeedHub MinIO", - "version": "RELEASE.2024-11-07T00-52-20Z", - "description": "MinIO S3-compatible object storage for IndeedHub media.", "category": "community", - "container_name": "indeedhub-minio", "container": { - "image": "146.59.87.168:3000/lfg2025/minio:RELEASE.2024-11-07T00-52-20Z", - "pull_policy": "if-not-present", - "network": "indeedhub-net", - "network_aliases": [ - "minio" - ], "custom_args": [ "server", "/data" ], "generated_secrets": [ { - "name": "indeedhub-minio-password", - "kind": "hex32" + "kind": "hex32", + "name": "indeedhub-minio-password" } ], + "image": "146.59.87.168:3000/lfg2025/minio:RELEASE.2024-11-07T00-52-20Z", + "network": "indeedhub-net", + "network_aliases": [ + "minio" + ], + "pull_policy": "if-not-present", "secret_env": [ { "key": "MINIO_ROOT_PASSWORD", @@ -2390,68 +2382,68 @@ } ] }, + "container_name": "indeedhub-minio", "dependencies": [ { "storage": "50Gi" } ], - "resources": { - "memory_limit": "1Gi", - "disk_limit": "50Gi" - }, - "security": { - "capabilities": [], - "readonly_root": false, - "network_policy": "isolated" - }, - "ports": [], - "volumes": [ - { - "type": "volume", - "source": "indeedhub-minio-data", - "target": "/data", - "options": [ - "rw" - ] - } - ], + "description": "MinIO S3-compatible object storage for IndeedHub media.", "environment": [ "MINIO_ROOT_USER=indeeadmin" ], "health_check": { - "type": "http", "endpoint": "http://localhost:9000", - "path": "/minio/health/live", "interval": "30s", + "path": "/minio/health/live", + "retries": 5, "timeout": "5s", - "retries": 5 - } + "type": "http" + }, + "id": "indeedhub-minio", + "name": "IndeedHub MinIO", + "ports": [], + "resources": { + "disk_limit": "50Gi", + "memory_limit": "1Gi" + }, + "security": { + "capabilities": [], + "network_policy": "isolated", + "readonly_root": false + }, + "version": "RELEASE.2024-11-07T00-52-20Z", + "volumes": [ + { + "options": [ + "rw" + ], + "source": "indeedhub-minio-data", + "target": "/data", + "type": "volume" + } + ] } - } + }, + "version": "RELEASE.2024-11-07T00-52-20Z" }, "indeedhub-postgres": { - "version": "16.13-alpine", "manifest": { "app": { - "id": "indeedhub-postgres", - "name": "IndeedHub Postgres", - "version": "16.13-alpine", - "description": "Postgres database backend for IndeedHub.", "category": "community", - "container_name": "indeedhub-postgres", "container": { + "generated_secrets": [ + { + "kind": "hex32", + "name": "indeedhub-db-password" + } + ], "image": "146.59.87.168:3000/lfg2025/postgres:16.13-alpine", - "pull_policy": "if-not-present", "network": "indeedhub-net", "network_aliases": [ "postgres" ], - "generated_secrets": [ - { - "name": "indeedhub-db-password", - "kind": "hex32" - } - ], + "pull_policy": "if-not-present", "secret_env": [ { "key": "POSTGRES_PASSWORD", @@ -2459,14 +2451,30 @@ } ] }, + "container_name": "indeedhub-postgres", "dependencies": [ { "storage": "10Gi" } ], + "description": "Postgres database backend for IndeedHub.", + "environment": [ + "POSTGRES_USER=indeedhub", + "POSTGRES_DB=indeedhub" + ], + "health_check": { + "endpoint": "localhost:5432", + "interval": "30s", + "retries": 3, + "timeout": "5s", + "type": "tcp" + }, + "id": "indeedhub-postgres", + "name": "IndeedHub Postgres", + "ports": [], "resources": { - "memory_limit": "1Gi", - "disk_limit": "10Gi" + "disk_limit": "10Gi", + "memory_limit": "1Gi" }, "security": { "capabilities": [ @@ -2476,57 +2484,54 @@ "SETGID", "SETUID" ], - "readonly_root": false, - "network_policy": "isolated" + "network_policy": "isolated", + "readonly_root": false }, - "ports": [], + "version": "16.13-alpine", "volumes": [ { - "type": "volume", - "source": "indeedhub-postgres-data", - "target": "/var/lib/postgresql/data", "options": [ "rw" - ] + ], + "source": "indeedhub-postgres-data", + "target": "/var/lib/postgresql/data", + "type": "volume" } - ], - "environment": [ - "POSTGRES_USER=indeedhub", - "POSTGRES_DB=indeedhub" - ], - "health_check": { - "type": "tcp", - "endpoint": "localhost:5432", - "interval": "30s", - "timeout": "5s", - "retries": 3 - } + ] } - } + }, + "version": "16.13-alpine" }, "indeedhub-redis": { - "version": "7.4.8-alpine", "manifest": { "app": { - "id": "indeedhub-redis", - "name": "IndeedHub Redis", - "version": "7.4.8-alpine", - "description": "Redis queue/cache backend for IndeedHub.", "category": "community", - "container_name": "indeedhub-redis", "container": { "image": "146.59.87.168:3000/lfg2025/redis:7.4.8-alpine", - "pull_policy": "if-not-present", "network": "indeedhub-net", "network_aliases": [ "redis" - ] + ], + "pull_policy": "if-not-present" }, + "container_name": "indeedhub-redis", "dependencies": [ { "storage": "1Gi" } ], + "description": "Redis queue/cache backend for IndeedHub.", + "environment": [], + "health_check": { + "endpoint": "localhost:6379", + "interval": "30s", + "retries": 3, + "timeout": "5s", + "type": "tcp" + }, + "id": "indeedhub-redis", + "name": "IndeedHub Redis", + "ports": [], "resources": { "memory_limit": "256Mi" }, @@ -2535,107 +2540,129 @@ "SETGID", "SETUID" ], - "readonly_root": false, - "network_policy": "isolated" + "network_policy": "isolated", + "readonly_root": false }, - "ports": [], + "version": "7.4.8-alpine", "volumes": [ { - "type": "volume", - "source": "indeedhub-redis-data", - "target": "/data", "options": [ "rw" - ] + ], + "source": "indeedhub-redis-data", + "target": "/data", + "type": "volume" } - ], - "environment": [], - "health_check": { - "type": "tcp", - "endpoint": "localhost:6379", - "interval": "30s", - "timeout": "5s", - "retries": 3 - } + ] } - } + }, + "version": "7.4.8-alpine" }, "indeedhub-relay": { - "version": "0.9.0", "manifest": { "app": { - "id": "indeedhub-relay", - "name": "IndeedHub Nostr Relay", - "version": "0.9.0", - "description": "nostr-rs-relay backing IndeedHub's Nostr identity + comments.", "category": "community", - "container_name": "indeedhub-relay", "container": { "image": "146.59.87.168:3000/lfg2025/nostr-rs-relay:0.9.0", - "pull_policy": "if-not-present", "network": "indeedhub-net", "network_aliases": [ "relay" - ] + ], + "pull_policy": "if-not-present" }, + "container_name": "indeedhub-relay", "dependencies": [ { "storage": "2Gi" } ], + "description": "nostr-rs-relay backing IndeedHub's Nostr identity + comments.", + "environment": [], + "health_check": { + "endpoint": "localhost:8080", + "interval": "30s", + "retries": 3, + "timeout": "5s", + "type": "tcp" + }, + "id": "indeedhub-relay", + "name": "IndeedHub Nostr Relay", + "ports": [], "resources": { - "memory_limit": "256Mi", - "disk_limit": "2Gi" + "disk_limit": "2Gi", + "memory_limit": "256Mi" }, "security": { "capabilities": [], - "readonly_root": false, - "network_policy": "isolated" + "network_policy": "isolated", + "readonly_root": false }, - "ports": [], + "version": "0.9.0", "volumes": [ { - "type": "volume", - "source": "indeedhub-relay-data", - "target": "/usr/src/app/db", "options": [ "rw" - ] + ], + "source": "indeedhub-relay-data", + "target": "/usr/src/app/db", + "type": "volume" } - ], - "environment": [], - "health_check": { - "type": "tcp", - "endpoint": "localhost:8080", - "interval": "30s", - "timeout": "5s", - "retries": 3 - } + ] } - } + }, + "version": "0.9.0" }, "jellyfin": { - "version": "10.8.13", "image": "146.59.87.168:3000/lfg2025/jellyfin:10.8.13", "manifest": { "app": { - "id": "jellyfin", - "name": "Jellyfin", - "version": "10.8.13", - "description": "Free media server. Stream movies, music, and photos.", "container": { "image": "146.59.87.168:3000/lfg2025/jellyfin:10.8.13", - "pull_policy": "if-not-present", - "network": "pasta" + "network": "pasta", + "pull_policy": "if-not-present" }, "dependencies": [ { "storage": "10Gi" } ], + "description": "Free media server. Stream movies, music, and photos.", + "environment": [], + "health_check": { + "endpoint": "localhost:8096", + "interval": "30s", + "retries": 3, + "timeout": "5s", + "type": "tcp" + }, + "id": "jellyfin", + "interfaces": { + "main": { + "description": "Jellyfin media dashboard", + "name": "Web UI", + "path": "/", + "port": 8096, + "protocol": "http", + "type": "ui" + } + }, + "metadata": { + "author": "Jellyfin", + "category": "data", + "icon": "/assets/img/app-icons/jellyfin.webp", + "repo": "https://github.com/jellyfin/jellyfin" + }, + "name": "Jellyfin", + "ports": [ + { + "container": 8096, + "host": 8096, + "protocol": "tcp" + } + ], "resources": { - "memory_limit": "1Gi", - "disk_limit": "10Gi" + "disk_limit": "10Gi", + "memory_limit": "1Gi" }, "security": { "capabilities": [ @@ -2645,69 +2672,39 @@ "SETGID", "DAC_OVERRIDE" ], - "readonly_root": false, - "network_policy": "isolated" + "network_policy": "isolated", + "readonly_root": false }, - "ports": [ - { - "host": 8096, - "container": 8096, - "protocol": "tcp" - } - ], + "version": "10.8.13", "volumes": [ { - "type": "bind", + "options": [ + "rw" + ], "source": "/var/lib/archipelago/jellyfin/config", "target": "/config", - "options": [ - "rw" - ] + "type": "bind" }, { - "type": "bind", - "source": "/var/lib/archipelago/jellyfin/cache", - "target": "/cache", "options": [ "rw" - ] + ], + "source": "/var/lib/archipelago/jellyfin/cache", + "target": "/cache", + "type": "bind" } - ], - "environment": [], - "health_check": { - "type": "tcp", - "endpoint": "localhost:8096", - "interval": "30s", - "timeout": "5s", - "retries": 3 - }, - "interfaces": { - "main": { - "name": "Web UI", - "description": "Jellyfin media dashboard", - "type": "ui", - "port": 8096, - "protocol": "http", - "path": "/" - } - }, - "metadata": { - "icon": "/assets/img/app-icons/jellyfin.webp", - "category": "data", - "author": "Jellyfin", - "repo": "https://github.com/jellyfin/jellyfin" - } + ] } - } + }, + "version": "10.8.13" }, "lightning-stack": { - "version": "0.12.0", "manifest": { "app": { - "id": "lightning-stack", - "name": "Lightning Stack", - "version": "0.12.0", - "description": "Complete Lightning Network implementation. Includes LND, CLN, and management tools.", + "bitcoin_integration": { + "rpc_access": "admin", + "sync_required": true + }, "container": { "image": "lightninglabs/lightning-stack:v0.12.0", "image_signature": "cosign://...", @@ -2722,49 +2719,7 @@ "storage": "50Gi" } ], - "resources": { - "cpu_limit": 4, - "memory_limit": "4Gi", - "disk_limit": "50Gi" - }, - "security": { - "capabilities": [ - "NET_BIND_SERVICE" - ], - "readonly_root": true, - "no_new_privileges": true, - "user": 1000, - "seccomp_profile": "default", - "network_policy": "isolated", - "apparmor_profile": "lightning-stack" - }, - "ports": [ - { - "host": 9737, - "container": 9735, - "protocol": "tcp" - }, - { - "host": 10010, - "container": 10009, - "protocol": "tcp" - }, - { - "host": 8087, - "container": 8080, - "protocol": "tcp" - } - ], - "volumes": [ - { - "type": "bind", - "source": "/var/lib/archipelago/lightning-stack", - "target": "/root/.lightning", - "options": [ - "rw" - ] - } - ], + "description": "Complete Lightning Network implementation. Includes LND, CLN, and management tools.", "environment": [ "BITCOIND_HOST=bitcoin-core", "BITCOIND_RPCUSER=${BITCOIN_RPC_USER}", @@ -2772,44 +2727,86 @@ "NETWORK=mainnet" ], "health_check": { - "type": "http", "endpoint": "http://localhost:8087", - "path": "/v1/getinfo", "interval": "30s", + "path": "/v1/getinfo", + "retries": 3, "timeout": "5s", - "retries": 3 + "type": "http" }, + "id": "lightning-stack", + "lightning_integration": { + "channel_management": true, + "payment_routing": true + }, + "name": "Lightning Stack", + "ports": [ + { + "container": 9735, + "host": 9737, + "protocol": "tcp" + }, + { + "container": 10009, + "host": 10010, + "protocol": "tcp" + }, + { + "container": 8080, + "host": 8087, + "protocol": "tcp" + } + ], + "resources": { + "cpu_limit": 4, + "disk_limit": "50Gi", + "memory_limit": "4Gi" + }, + "security": { + "apparmor_profile": "lightning-stack", + "capabilities": [ + "NET_BIND_SERVICE" + ], + "network_policy": "isolated", + "no_new_privileges": true, + "readonly_root": true, + "seccomp_profile": "default", + "user": 1000 + }, + "version": "0.12.0", + "volumes": [ + { + "options": [ + "rw" + ], + "source": "/var/lib/archipelago/lightning-stack", + "target": "/root/.lightning", + "type": "bind" + } + ] + } + }, + "version": "0.12.0" + }, + "lnd": { + "image": "146.59.87.168:3000/lfg2025/lnd:v0.18.4-beta", + "manifest": { + "app": { "bitcoin_integration": { "rpc_access": "admin", "sync_required": true }, - "lightning_integration": { - "channel_management": true, - "payment_routing": true - } - } - } - }, - "lnd": { - "version": "v0.18.4-beta", - "image": "146.59.87.168:3000/lfg2025/lnd:v0.18.4-beta", - "manifest": { - "app": { - "id": "lnd", - "name": "LND", - "version": "0.18.4", - "description": "Lightning Network implementation by Lightning Labs. Enables instant, low-cost Bitcoin payments.", "container": { + "data_uid": "100000:100000", "image": "146.59.87.168:3000/lfg2025/lnd:v0.18.4-beta", - "pull_policy": "if-not-present", "network": "archy-net", + "pull_policy": "if-not-present", "secret_env": [ { "key": "BITCOIND_RPCPASS", "secret_file": "bitcoin-rpc-password" } - ], - "data_uid": "100000:100000" + ] }, "dependencies": [ { @@ -2817,10 +2814,46 @@ "version": ">=26.0" } ], + "description": "Lightning Network implementation by Lightning Labs. Enables instant, low-cost Bitcoin payments.", + "environment": [ + "BITCOIND_HOST=bitcoin-knots", + "BITCOIND_RPCUSER=archipelago", + "NETWORK=mainnet" + ], + "health_check": { + "endpoint": "localhost:10009", + "interval": "30s", + "retries": 3, + "timeout": "5s", + "type": "tcp" + }, + "id": "lnd", + "lightning_integration": { + "channel_management": true, + "payment_routing": true + }, + "name": "LND", + "ports": [ + { + "container": 9735, + "host": 9735, + "protocol": "tcp" + }, + { + "container": 10009, + "host": 10009, + "protocol": "tcp" + }, + { + "container": 8080, + "host": 18080, + "protocol": "tcp" + } + ], "resources": { "cpu_limit": 2, - "memory_limit": "1Gi", - "disk_limit": "10Gi" + "disk_limit": "10Gi", + "memory_limit": "1Gi" }, "security": { "capabilities": [ @@ -2831,68 +2864,28 @@ "DAC_OVERRIDE", "NET_RAW" ], - "readonly_root": false, - "network_policy": "isolated" + "network_policy": "isolated", + "readonly_root": false }, - "ports": [ - { - "host": 9735, - "container": 9735, - "protocol": "tcp" - }, - { - "host": 10009, - "container": 10009, - "protocol": "tcp" - }, - { - "host": 18080, - "container": 8080, - "protocol": "tcp" - } - ], + "version": "0.18.4", "volumes": [ { - "type": "bind", - "source": "/var/lib/archipelago/lnd", - "target": "/root/.lnd", "options": [ "rw" - ] + ], + "source": "/var/lib/archipelago/lnd", + "target": "/root/.lnd", + "type": "bind" } - ], - "environment": [ - "BITCOIND_HOST=bitcoin-knots", - "BITCOIND_RPCUSER=archipelago", - "NETWORK=mainnet" - ], - "health_check": { - "type": "tcp", - "endpoint": "localhost:10009", - "interval": "30s", - "timeout": "5s", - "retries": 3 - }, - "bitcoin_integration": { - "rpc_access": "admin", - "sync_required": true - }, - "lightning_integration": { - "channel_management": true, - "payment_routing": true - } + ] } - } + }, + "version": "v0.18.4-beta" }, "lnd-ui": { - "version": "latest", "image": "146.59.87.168:3000/lfg2025/lnd-ui:latest", "manifest": { "app": { - "id": "lnd-ui", - "name": "LND UI", - "version": "1.0.0", - "description": "Archipelago-native HTTP frontend for LND. Runs nginx inside a\ncontainer and serves static assets. LND connection info is fetched\nvia an absolute URL that the host nginx routes to the archipelago\nbackend on 127.0.0.1:5678, so no upstream auth is baked in.\n", "container": { "build": { "context": "/opt/archipelago/docker/lnd-ui", @@ -2905,47 +2898,51 @@ "app_id": "lnd" } ], + "description": "Archipelago-native HTTP frontend for LND. Runs nginx inside a\ncontainer and serves static assets. LND connection info is fetched\nvia an absolute URL that the host nginx routes to the archipelago\nbackend on 127.0.0.1:5678, so no upstream auth is baked in.\n", + "environment": [], + "health_check": { + "endpoint": "http://127.0.0.1:18083", + "interval": "30s", + "path": "/", + "retries": 3, + "timeout": "5s", + "type": "http" + }, + "id": "lnd-ui", + "name": "LND UI", + "ports": [ + { + "container": 80, + "host": 18083, + "protocol": "tcp" + } + ], "resources": { "memory_limit": "64Mi" }, "security": { - "readonly_root": false, - "network_policy": "bridge" + "network_policy": "bridge", + "readonly_root": false }, - "ports": [ - { - "host": 18083, - "container": 80, - "protocol": "tcp" - } - ], - "volumes": [], - "environment": [], - "health_check": { - "type": "http", - "endpoint": "http://127.0.0.1:18083", - "path": "/", - "interval": "30s", - "timeout": "5s", - "retries": 3 - } + "version": "1.0.0", + "volumes": [] } - } + }, + "version": "latest" }, "mempool": { - "version": "v3.0.1", "image": "146.59.87.168:3000/lfg2025/mempool-frontend:v3.0.1", "images": { + "archy-mempool-db": "146.59.87.168:3000/lfg2025/mariadb:11.4.10", "archy-mempool-web": "146.59.87.168:3000/lfg2025/mempool-frontend:v3.0.1", - "mempool-api": "146.59.87.168:3000/lfg2025/mempool-backend:v3.0.0", - "archy-mempool-db": "146.59.87.168:3000/lfg2025/mariadb:11.4.10" + "mempool-api": "146.59.87.168:3000/lfg2025/mempool-backend:v3.0.0" }, "manifest": { "app": { - "id": "mempool", - "name": "Mempool Explorer", - "version": "3.0.0", - "description": "Bitcoin mempool and blockchain explorer. Real-time transaction and block visualization.", + "bitcoin_integration": { + "rpc_access": "read-only", + "sync_required": true + }, "container": { "image": "146.59.87.168:3000/lfg2025/mempool-frontend:v3.0.1", "image_signature": "cosign://...", @@ -2960,37 +2957,7 @@ "storage": "20Gi" } ], - "resources": { - "cpu_limit": 2, - "memory_limit": "2Gi", - "disk_limit": "20Gi" - }, - "security": { - "capabilities": [], - "readonly_root": true, - "no_new_privileges": true, - "user": 1000, - "seccomp_profile": "default", - "network_policy": "isolated", - "apparmor_profile": "mempool" - }, - "ports": [ - { - "host": 4080, - "container": 8080, - "protocol": "tcp" - } - ], - "volumes": [ - { - "type": "bind", - "source": "/var/lib/archipelago/mempool", - "target": "/data", - "options": [ - "rw" - ] - } - ], + "description": "Bitcoin mempool and blockchain explorer. Real-time transaction and block visualization.", "environment": [ "MEMPOOL_BACKEND=electrum", "MEMPOOL_BITCOIN_HOST=bitcoin-core", @@ -2999,38 +2966,69 @@ "MEMPOOL_BITCOIN_PASSWORD=${BITCOIN_RPC_PASSWORD}" ], "health_check": { - "type": "http", "endpoint": "http://localhost:4080", - "path": "/api/health", "interval": "30s", + "path": "/api/health", + "retries": 3, "timeout": "5s", - "retries": 3 + "type": "http" }, - "bitcoin_integration": { - "rpc_access": "read-only", - "sync_required": true - } + "id": "mempool", + "name": "Mempool Explorer", + "ports": [ + { + "container": 8080, + "host": 4080, + "protocol": "tcp" + } + ], + "resources": { + "cpu_limit": 2, + "disk_limit": "20Gi", + "memory_limit": "2Gi" + }, + "security": { + "apparmor_profile": "mempool", + "capabilities": [], + "network_policy": "isolated", + "no_new_privileges": true, + "readonly_root": true, + "seccomp_profile": "default", + "user": 1000 + }, + "version": "3.0.0", + "volumes": [ + { + "options": [ + "rw" + ], + "source": "/var/lib/archipelago/mempool", + "target": "/data", + "type": "bind" + } + ] } - } + }, + "version": "v3.0.1" }, "mempool-api": { - "version": "3.0.0", "manifest": { "app": { - "id": "mempool-api", - "name": "Mempool API", - "version": "3.0.0", - "description": "Backend API for mempool explorer.", + "bitcoin_integration": { + "pruning_support": false, + "rpc_access": "read-only", + "sync_required": true + }, "container": { - "image": "git.tx1138.com/lfg2025/mempool-backend:v3.0.0", - "pull_policy": "if-not-present", - "network": "archy-net", "derived_env": [ { "key": "CORE_RPC_HOST", "template": "{{BITCOIN_HOST}}" } ], + "image": "git.tx1138.com/lfg2025/mempool-backend:v3.0.0", + "network": "archy-net", + "pull_policy": "if-not-present", "secret_env": [ { "key": "CORE_RPC_PASSWORD", @@ -3056,32 +3054,7 @@ "version": ">=11.4.10" } ], - "resources": { - "memory_limit": "2Gi", - "disk_limit": "20Gi" - }, - "security": { - "capabilities": [], - "readonly_root": false, - "network_policy": "isolated" - }, - "ports": [ - { - "host": 8999, - "container": 8999, - "protocol": "tcp" - } - ], - "volumes": [ - { - "type": "bind", - "source": "/var/lib/archipelago/mempool", - "target": "/data", - "options": [ - "rw" - ] - } - ], + "description": "Backend API for mempool explorer.", "environment": [ "MEMPOOL_BACKEND=electrum", "ELECTRUM_HOST=electrumx", @@ -3095,29 +3068,49 @@ "DATABASE_USERNAME=mempool" ], "health_check": { - "type": "http", "endpoint": "http://localhost:8999", - "path": "/api/v1/backend-info", "interval": "30s", + "path": "/api/v1/backend-info", + "retries": 3, "timeout": "5s", - "retries": 3 + "type": "http" }, - "bitcoin_integration": { - "rpc_access": "read-only", - "sync_required": true, - "pruning_support": false - } + "id": "mempool-api", + "name": "Mempool API", + "ports": [ + { + "container": 8999, + "host": 8999, + "protocol": "tcp" + } + ], + "resources": { + "disk_limit": "20Gi", + "memory_limit": "2Gi" + }, + "security": { + "capabilities": [], + "network_policy": "isolated", + "readonly_root": false + }, + "version": "3.0.0", + "volumes": [ + { + "options": [ + "rw" + ], + "source": "/var/lib/archipelago/mempool", + "target": "/data", + "type": "bind" + } + ] } - } + }, + "version": "3.0.0" }, "morphos-server": { - "version": "1.0.0", "manifest": { "app": { - "id": "morphos-server", - "name": "MorphOS Server", - "version": "1.0.0", - "description": "MorphOS server platform. Decentralized application server.", "container": { "image": "archipelago/morphos-server:1.0.0", "image_signature": "cosign://...", @@ -3128,73 +3121,73 @@ "storage": "5Gi" } ], - "resources": { - "cpu_limit": 2, - "memory_limit": "2Gi", - "disk_limit": "5Gi" - }, - "security": { - "capabilities": [], - "readonly_root": true, - "no_new_privileges": true, - "user": 1000, - "seccomp_profile": "default", - "network_policy": "isolated", - "apparmor_profile": "morphos-server" - }, - "ports": [ - { - "host": 8086, - "container": 8080, - "protocol": "tcp" - } - ], - "volumes": [ - { - "type": "bind", - "source": "/var/lib/archipelago/morphos-server", - "target": "/app/data", - "options": [ - "rw" - ] - } - ], + "description": "MorphOS server platform. Decentralized application server.", "environment": [ "MORPHOS_ENV=production", "MORPHOS_DATA_DIR=/app/data" ], "health_check": { - "type": "http", "endpoint": "http://localhost:8086", - "path": "/health", "interval": "30s", + "path": "/health", + "retries": 3, "timeout": "5s", - "retries": 3 - } + "type": "http" + }, + "id": "morphos-server", + "name": "MorphOS Server", + "ports": [ + { + "container": 8080, + "host": 8086, + "protocol": "tcp" + } + ], + "resources": { + "cpu_limit": 2, + "disk_limit": "5Gi", + "memory_limit": "2Gi" + }, + "security": { + "apparmor_profile": "morphos-server", + "capabilities": [], + "network_policy": "isolated", + "no_new_privileges": true, + "readonly_root": true, + "seccomp_profile": "default", + "user": 1000 + }, + "version": "1.0.0", + "volumes": [ + { + "options": [ + "rw" + ], + "source": "/var/lib/archipelago/morphos-server", + "target": "/app/data", + "type": "bind" + } + ] } - } + }, + "version": "1.0.0" }, "netbird": { - "version": "2.38.0", "manifest": { "app": { - "id": "netbird", - "name": "NetBird", - "version": "2.38.0", - "description": "Self-hosted WireGuard mesh VPN control plane with dashboard, embedded identity provider, management API, signal, relay, and STUN. The user-facing entry point \u2014 a TLS proxy in front of the dashboard + server.", "category": "networking", - "container_name": "netbird", "container": { - "image": "docker.io/library/nginx:1.27-alpine", - "pull_policy": "if-not-present", - "network": "netbird-net", "generated_certs": [ { "crt": "/var/lib/archipelago/netbird/tls.crt", "key": "/var/lib/archipelago/netbird/tls.key" } - ] + ], + "image": "docker.io/library/nginx:1.27-alpine", + "network": "netbird-net", + "pull_policy": "if-not-present" }, + "container_name": "netbird", "dependencies": [ { "app_id": "netbird-server" @@ -3206,6 +3199,55 @@ "storage": "1Gi" } ], + "description": "Self-hosted WireGuard mesh VPN control plane with dashboard, embedded identity provider, management API, signal, relay, and STUN. The user-facing entry point — a TLS proxy in front of the dashboard + server.", + "environment": [], + "files": [ + { + "content": "server {\n listen 443 ssl;\n server_name _;\n\n # netbird's dashboard needs a secure context (window.crypto.subtle for\n # OIDC PKCE), so the proxy terminates TLS with a self-signed cert (#15).\n ssl_certificate /etc/nginx/tls.crt;\n ssl_certificate_key /etc/nginx/tls.key;\n\n # Rootless Podman can hand a container a new IP across restarts/reboots.\n # nginx resolves a literal upstream name ONCE at startup and caches it,\n # so after the IP moves every request 502s with \"host unreachable\"\n # (issue #15, observed live on .198: nginx pinned to a dead\n # netbird-dashboard IP). Fix: point `resolver` at the netbird-net\n # gateway (Podman's aardvark DNS) and use VARIABLE upstreams, which\n # forces nginx to re-resolve the container names at request time.\n resolver {{NETWORK_GATEWAY}} valid=10s ipv6=off;\n\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto $scheme;\n proxy_http_version 1.1;\n\n location ~ ^/(relay|ws-proxy/) {\n set $nb_server netbird-server;\n proxy_pass http://$nb_server:80;\n proxy_set_header Upgrade $http_upgrade;\n proxy_set_header Connection \"upgrade\";\n proxy_read_timeout 1d;\n }\n\n location ~ ^/(api|oauth2)(/|$) {\n # The dashboard is a SPA whose API/OIDC base URL is baked at build\n # time to one host:port. A single box is reached via several\n # addresses, so those fetches are cross-origin and the browser\n # blocks them with no Access-Control-Allow-Origin (#15, live on\n # .198). Reflect the caller's Origin and answer the CORS preflight.\n if ($request_method = OPTIONS) {\n add_header Access-Control-Allow-Origin $http_origin always;\n add_header Access-Control-Allow-Credentials true always;\n add_header Access-Control-Allow-Methods \"GET, POST, PUT, PATCH, DELETE, OPTIONS\" always;\n add_header Access-Control-Allow-Headers \"Authorization, Content-Type, Accept\" always;\n add_header Access-Control-Max-Age 86400 always;\n add_header Content-Length 0;\n return 204;\n }\n add_header Access-Control-Allow-Origin $http_origin always;\n add_header Access-Control-Allow-Credentials true always;\n add_header Access-Control-Allow-Methods \"GET, POST, PUT, PATCH, DELETE, OPTIONS\" always;\n add_header Access-Control-Allow-Headers \"Authorization, Content-Type, Accept\" always;\n set $nb_server netbird-server;\n proxy_pass http://$nb_server:80;\n }\n\n location ~ ^/(signalexchange\\.SignalExchange|management\\.ManagementService|management\\.ProxyService)/ {\n set $nb_server netbird-server;\n grpc_pass grpc://$nb_server:80;\n grpc_read_timeout 1d;\n grpc_send_timeout 1d;\n }\n\n # OIDC callback routes are client-side SPA routes with NO prebuilt page\n # in the dashboard bundle, so proxying them straight through 404s —\n # which crashes the dashboard's auth init and shows \"Unauthenticated\"\n # with dead buttons (#15, live on .198: /nb-auth + /nb-silent-auth\n # returned 404). Serve index.html at these paths (URL unchanged) so\n # react-oidc boots and completes the login / silent-SSO.\n location ~ ^/(nb-auth|nb-silent-auth) {\n set $nb_dashboard netbird-dashboard;\n rewrite ^.*$ /index.html break;\n proxy_pass http://$nb_dashboard:80;\n }\n\n location / {\n set $nb_dashboard netbird-dashboard;\n proxy_pass http://$nb_dashboard:80;\n }\n}\n", + "overwrite": true, + "path": "/var/lib/archipelago/netbird/nginx.conf" + } + ], + "health_check": { + "endpoint": "localhost:443", + "interval": "30s", + "retries": 5, + "start_period": "20s", + "timeout": "5s", + "type": "tcp" + }, + "id": "netbird", + "interfaces": { + "main": { + "description": "Manage your self-hosted NetBird mesh VPN", + "name": "Dashboard", + "path": "/", + "port": 8087, + "protocol": "https", + "type": "ui" + } + }, + "metadata": { + "author": "NetBird", + "icon": "/assets/img/app-icons/netbird.svg", + "license": "BSD-3-Clause", + "repo": "https://github.com/netbirdio/netbird", + "tags": [ + "networking", + "vpn", + "wireguard", + "mesh" + ], + "website": "https://netbird.io" + }, + "name": "NetBird", + "ports": [ + { + "container": 443, + "host": 8087, + "protocol": "tcp" + } + ], "resources": { "memory_limit": "256Mi" }, @@ -3217,101 +3259,45 @@ "SETUID", "NET_BIND_SERVICE" ], - "readonly_root": false, - "network_policy": "isolated" + "network_policy": "isolated", + "readonly_root": false }, - "ports": [ - { - "host": 8087, - "container": 443, - "protocol": "tcp" - } - ], + "version": "2.38.0", "volumes": [ { - "type": "bind", + "options": [ + "ro" + ], "source": "/var/lib/archipelago/netbird/nginx.conf", "target": "/etc/nginx/conf.d/default.conf", - "options": [ - "ro" - ] + "type": "bind" }, { - "type": "bind", + "options": [ + "ro" + ], "source": "/var/lib/archipelago/netbird/tls.crt", "target": "/etc/nginx/tls.crt", - "options": [ - "ro" - ] + "type": "bind" }, { - "type": "bind", - "source": "/var/lib/archipelago/netbird/tls.key", - "target": "/etc/nginx/tls.key", "options": [ "ro" - ] + ], + "source": "/var/lib/archipelago/netbird/tls.key", + "target": "/etc/nginx/tls.key", + "type": "bind" } - ], - "environment": [], - "files": [ - { - "path": "/var/lib/archipelago/netbird/nginx.conf", - "overwrite": true, - "content": "server {\n listen 443 ssl;\n server_name _;\n\n # netbird's dashboard needs a secure context (window.crypto.subtle for\n # OIDC PKCE), so the proxy terminates TLS with a self-signed cert (#15).\n ssl_certificate /etc/nginx/tls.crt;\n ssl_certificate_key /etc/nginx/tls.key;\n\n # Rootless Podman can hand a container a new IP across restarts/reboots.\n # nginx resolves a literal upstream name ONCE at startup and caches it,\n # so after the IP moves every request 502s with \"host unreachable\"\n # (issue #15, observed live on .198: nginx pinned to a dead\n # netbird-dashboard IP). Fix: point `resolver` at the netbird-net\n # gateway (Podman's aardvark DNS) and use VARIABLE upstreams, which\n # forces nginx to re-resolve the container names at request time.\n resolver {{NETWORK_GATEWAY}} valid=10s ipv6=off;\n\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto $scheme;\n proxy_http_version 1.1;\n\n location ~ ^/(relay|ws-proxy/) {\n set $nb_server netbird-server;\n proxy_pass http://$nb_server:80;\n proxy_set_header Upgrade $http_upgrade;\n proxy_set_header Connection \"upgrade\";\n proxy_read_timeout 1d;\n }\n\n location ~ ^/(api|oauth2)(/|$) {\n # The dashboard is a SPA whose API/OIDC base URL is baked at build\n # time to one host:port. A single box is reached via several\n # addresses, so those fetches are cross-origin and the browser\n # blocks them with no Access-Control-Allow-Origin (#15, live on\n # .198). Reflect the caller's Origin and answer the CORS preflight.\n if ($request_method = OPTIONS) {\n add_header Access-Control-Allow-Origin $http_origin always;\n add_header Access-Control-Allow-Credentials true always;\n add_header Access-Control-Allow-Methods \"GET, POST, PUT, PATCH, DELETE, OPTIONS\" always;\n add_header Access-Control-Allow-Headers \"Authorization, Content-Type, Accept\" always;\n add_header Access-Control-Max-Age 86400 always;\n add_header Content-Length 0;\n return 204;\n }\n add_header Access-Control-Allow-Origin $http_origin always;\n add_header Access-Control-Allow-Credentials true always;\n add_header Access-Control-Allow-Methods \"GET, POST, PUT, PATCH, DELETE, OPTIONS\" always;\n add_header Access-Control-Allow-Headers \"Authorization, Content-Type, Accept\" always;\n set $nb_server netbird-server;\n proxy_pass http://$nb_server:80;\n }\n\n location ~ ^/(signalexchange\\.SignalExchange|management\\.ManagementService|management\\.ProxyService)/ {\n set $nb_server netbird-server;\n grpc_pass grpc://$nb_server:80;\n grpc_read_timeout 1d;\n grpc_send_timeout 1d;\n }\n\n # OIDC callback routes are client-side SPA routes with NO prebuilt page\n # in the dashboard bundle, so proxying them straight through 404s \u2014\n # which crashes the dashboard's auth init and shows \"Unauthenticated\"\n # with dead buttons (#15, live on .198: /nb-auth + /nb-silent-auth\n # returned 404). Serve index.html at these paths (URL unchanged) so\n # react-oidc boots and completes the login / silent-SSO.\n location ~ ^/(nb-auth|nb-silent-auth) {\n set $nb_dashboard netbird-dashboard;\n rewrite ^.*$ /index.html break;\n proxy_pass http://$nb_dashboard:80;\n }\n\n location / {\n set $nb_dashboard netbird-dashboard;\n proxy_pass http://$nb_dashboard:80;\n }\n}\n" - } - ], - "health_check": { - "type": "tcp", - "endpoint": "localhost:443", - "interval": "30s", - "timeout": "5s", - "retries": 5, - "start_period": "20s" - }, - "interfaces": { - "main": { - "name": "Dashboard", - "description": "Manage your self-hosted NetBird mesh VPN", - "type": "ui", - "port": 8087, - "protocol": "https", - "path": "/" - } - }, - "metadata": { - "author": "NetBird", - "icon": "/assets/img/app-icons/netbird.svg", - "website": "https://netbird.io", - "repo": "https://github.com/netbirdio/netbird", - "license": "BSD-3-Clause", - "tags": [ - "networking", - "vpn", - "wireguard", - "mesh" - ] - } + ] } - } + }, + "version": "2.38.0" }, "netbird-dashboard": { - "version": "2.38.0", "manifest": { "app": { - "id": "netbird-dashboard", - "name": "NetBird Dashboard", - "version": "2.38.0", - "description": "NetBird management dashboard (SPA). Internal stack member served through the netbird proxy.", "category": "networking", - "container_name": "netbird-dashboard", "container": { - "image": "docker.io/netbirdio/dashboard:v2.38.0", - "pull_policy": "if-not-present", - "network": "netbird-net", - "network_aliases": [ - "netbird-dashboard" - ], "derived_env": [ { "key": "NETBIRD_MGMT_API_ENDPOINT", @@ -3325,29 +3311,21 @@ "key": "AUTH_AUTHORITY", "template": "https://{{HOST_IP}}:8087/oauth2" } - ] + ], + "image": "docker.io/netbirdio/dashboard:v2.38.0", + "network": "netbird-net", + "network_aliases": [ + "netbird-dashboard" + ], + "pull_policy": "if-not-present" }, + "container_name": "netbird-dashboard", "dependencies": [ { "app_id": "netbird-server" } ], - "resources": { - "memory_limit": "256Mi" - }, - "security": { - "capabilities": [ - "CHOWN", - "DAC_OVERRIDE", - "SETGID", - "SETUID", - "NET_BIND_SERVICE" - ], - "readonly_root": false, - "network_policy": "isolated" - }, - "ports": [], - "volumes": [], + "description": "NetBird management dashboard (SPA). Internal stack member served through the netbird proxy.", "environment": [ "AUTH_AUDIENCE=netbird-dashboard", "AUTH_CLIENT_ID=netbird-dashboard", @@ -3361,65 +3339,124 @@ "LETSENCRYPT_DOMAIN=none" ], "health_check": { - "type": "tcp", "endpoint": "localhost:80", "interval": "30s", - "timeout": "5s", "retries": 5, - "start_period": "20s" + "start_period": "20s", + "timeout": "5s", + "type": "tcp" }, + "id": "netbird-dashboard", "metadata": { "author": "NetBird", "icon": "/assets/img/app-icons/netbird.svg", - "website": "https://netbird.io", - "repo": "https://github.com/netbirdio/dashboard", "license": "BSD-3-Clause", + "repo": "https://github.com/netbirdio/dashboard", "tags": [ "networking", "vpn", "dashboard" - ] - } + ], + "website": "https://netbird.io" + }, + "name": "NetBird Dashboard", + "ports": [], + "resources": { + "memory_limit": "256Mi" + }, + "security": { + "capabilities": [ + "CHOWN", + "DAC_OVERRIDE", + "SETGID", + "SETUID", + "NET_BIND_SERVICE" + ], + "network_policy": "isolated", + "readonly_root": false + }, + "version": "2.38.0", + "volumes": [] } - } + }, + "version": "2.38.0" }, "netbird-server": { - "version": "0.71.2", "manifest": { "app": { - "id": "netbird-server", - "name": "NetBird Server", - "version": "0.71.2", - "description": "NetBird combined management / signal / relay server with an embedded identity provider and STUN. Backend for the self-hosted NetBird mesh VPN.", "category": "networking", - "container_name": "netbird-server", "container": { + "custom_args": [ + "--config", + "/etc/netbird/config.yaml" + ], + "generated_secrets": [ + { + "kind": "base64", + "name": "netbird-relay-auth-secret" + }, + { + "kind": "base64", + "name": "netbird-store-encryption-key" + } + ], "image": "docker.io/netbirdio/netbird-server:0.71.2", - "pull_policy": "if-not-present", "network": "netbird-net", "network_aliases": [ "netbird-server" ], - "generated_secrets": [ - { - "name": "netbird-relay-auth-secret", - "kind": "base64" - }, - { - "name": "netbird-store-encryption-key", - "kind": "base64" - } - ], - "custom_args": [ - "--config", - "/etc/netbird/config.yaml" - ] + "pull_policy": "if-not-present" }, + "container_name": "netbird-server", "dependencies": [ { "storage": "1Gi" } ], + "description": "NetBird combined management / signal / relay server with an embedded identity provider and STUN. Backend for the self-hosted NetBird mesh VPN.", + "environment": [], + "files": [ + { + "content": "server:\n listenAddress: \":80\"\n exposedAddress: \"https://{{HOST_IP}}:8087\"\n stunPorts:\n - 3478\n metricsPort: 9090\n healthcheckAddress: \":9000\"\n logLevel: \"info\"\n logFile: \"console\"\n authSecret: \"{{secret:netbird-relay-auth-secret}}\"\n dataDir: \"/var/lib/netbird\"\n auth:\n issuer: \"https://{{HOST_IP}}:8087/oauth2\"\n localAuthDisabled: false\n signKeyRefreshEnabled: false\n dashboardRedirectURIs:\n - \"https://{{HOST_IP}}:8087/nb-auth\"\n - \"https://{{HOST_IP}}:8087/nb-silent-auth\"\n dashboardPostLogoutRedirectURIs:\n - \"https://{{HOST_IP}}:8087/\"\n cliRedirectURIs:\n - \"http://localhost:53000/\"\n store:\n engine: \"sqlite\"\n encryptionKey: \"{{secret:netbird-store-encryption-key}}\"\n", + "overwrite": true, + "path": "/var/lib/archipelago/netbird/config.yaml" + } + ], + "health_check": { + "endpoint": "localhost:80", + "interval": "30s", + "retries": 10, + "start_period": "30s", + "timeout": "5s", + "type": "tcp" + }, + "id": "netbird-server", + "metadata": { + "author": "NetBird", + "icon": "/assets/img/app-icons/netbird.svg", + "license": "BSD-3-Clause", + "repo": "https://github.com/netbirdio/netbird", + "tags": [ + "networking", + "vpn", + "wireguard", + "mesh" + ], + "website": "https://netbird.io" + }, + "name": "NetBird Server", + "ports": [ + { + "container": 80, + "host": 8086, + "protocol": "tcp" + }, + { + "container": 3478, + "host": 3478, + "protocol": "udp" + } + ], "resources": { "memory_limit": "1Gi" }, @@ -3427,93 +3464,86 @@ "capabilities": [ "NET_BIND_SERVICE" ], - "readonly_root": false, - "network_policy": "isolated" + "network_policy": "isolated", + "readonly_root": false }, - "ports": [ - { - "host": 8086, - "container": 80, - "protocol": "tcp" - }, - { - "host": 3478, - "container": 3478, - "protocol": "udp" - } - ], + "version": "0.71.2", "volumes": [ { - "type": "bind", - "source": "/var/lib/archipelago/netbird/data", - "target": "/var/lib/netbird", "options": [ "rw" - ] + ], + "source": "/var/lib/archipelago/netbird/data", + "target": "/var/lib/netbird", + "type": "bind" }, { - "type": "bind", - "source": "/var/lib/archipelago/netbird/config.yaml", - "target": "/etc/netbird/config.yaml", "options": [ "ro" - ] + ], + "source": "/var/lib/archipelago/netbird/config.yaml", + "target": "/etc/netbird/config.yaml", + "type": "bind" } - ], - "environment": [], - "files": [ - { - "path": "/var/lib/archipelago/netbird/config.yaml", - "overwrite": true, - "content": "server:\n listenAddress: \":80\"\n exposedAddress: \"https://{{HOST_IP}}:8087\"\n stunPorts:\n - 3478\n metricsPort: 9090\n healthcheckAddress: \":9000\"\n logLevel: \"info\"\n logFile: \"console\"\n authSecret: \"{{secret:netbird-relay-auth-secret}}\"\n dataDir: \"/var/lib/netbird\"\n auth:\n issuer: \"https://{{HOST_IP}}:8087/oauth2\"\n localAuthDisabled: false\n signKeyRefreshEnabled: false\n dashboardRedirectURIs:\n - \"https://{{HOST_IP}}:8087/nb-auth\"\n - \"https://{{HOST_IP}}:8087/nb-silent-auth\"\n dashboardPostLogoutRedirectURIs:\n - \"https://{{HOST_IP}}:8087/\"\n cliRedirectURIs:\n - \"http://localhost:53000/\"\n store:\n engine: \"sqlite\"\n encryptionKey: \"{{secret:netbird-store-encryption-key}}\"\n" - } - ], - "health_check": { - "type": "tcp", - "endpoint": "localhost:80", - "interval": "30s", - "timeout": "5s", - "retries": 10, - "start_period": "30s" - }, - "metadata": { - "author": "NetBird", - "icon": "/assets/img/app-icons/netbird.svg", - "website": "https://netbird.io", - "repo": "https://github.com/netbirdio/netbird", - "license": "BSD-3-Clause", - "tags": [ - "networking", - "vpn", - "wireguard", - "mesh" - ] - } + ] } - } + }, + "version": "0.71.2" }, "nextcloud": { - "version": "29", "image": "146.59.87.168:3000/lfg2025/nextcloud:29", "manifest": { "app": { - "id": "nextcloud", - "name": "Nextcloud", - "version": "29", - "description": "Your own private cloud. File sync, calendars, contacts.", "container": { "image": "146.59.87.168:3000/lfg2025/nextcloud:29", - "pull_policy": "if-not-present", - "network": "pasta" + "network": "pasta", + "pull_policy": "if-not-present" }, "dependencies": [ { "storage": "10Gi" } ], + "description": "Your own private cloud. File sync, calendars, contacts.", + "environment": [], + "health_check": { + "endpoint": "localhost:80", + "interval": "30s", + "retries": 3, + "timeout": "5s", + "type": "tcp" + }, + "id": "nextcloud", + "interfaces": { + "main": { + "description": "Nextcloud file and collaboration dashboard", + "name": "Web UI", + "path": "/", + "port": 8085, + "protocol": "http", + "type": "ui" + } + }, + "metadata": { + "author": "Nextcloud", + "category": "data", + "icon": "/assets/img/app-icons/nextcloud.webp", + "launch": { + "open_in_new_tab": true + }, + "repo": "https://github.com/nextcloud/server" + }, + "name": "Nextcloud", + "ports": [ + { + "container": 80, + "host": 8085, + "protocol": "tcp" + } + ], "resources": { - "memory_limit": "1Gi", - "disk_limit": "10Gi" + "disk_limit": "10Gi", + "memory_limit": "1Gi" }, "security": { "capabilities": [ @@ -3523,111 +3553,44 @@ "DAC_OVERRIDE", "NET_BIND_SERVICE" ], - "readonly_root": false, - "network_policy": "isolated" + "network_policy": "isolated", + "readonly_root": false }, - "ports": [ - { - "host": 8085, - "container": 80, - "protocol": "tcp" - } - ], + "version": "29", "volumes": [ { - "type": "bind", - "source": "/var/lib/archipelago/nextcloud", - "target": "/var/www/html", "options": [ "rw" - ] + ], + "source": "/var/lib/archipelago/nextcloud", + "target": "/var/www/html", + "type": "bind" } - ], - "environment": [], - "health_check": { - "type": "tcp", - "endpoint": "localhost:80", - "interval": "30s", - "timeout": "5s", - "retries": 3 - }, - "interfaces": { - "main": { - "name": "Web UI", - "description": "Nextcloud file and collaboration dashboard", - "type": "ui", - "port": 8085, - "protocol": "http", - "path": "/" - } - }, - "metadata": { - "icon": "/assets/img/app-icons/nextcloud.webp", - "category": "data", - "author": "Nextcloud", - "repo": "https://github.com/nextcloud/server", - "launch": { - "open_in_new_tab": true - } - } + ] } - } + }, + "version": "29" }, "nginx-proxy-manager": { - "version": "latest", - "image": "146.59.87.168:3000/lfg2025/nginx-proxy-manager:latest" + "image": "146.59.87.168:3000/lfg2025/nginx-proxy-manager:latest", + "version": "latest" }, "nostr-rs-relay": { - "version": "0.9.0", "image": "146.59.87.168:3000/lfg2025/nostr-rs-relay:0.9.0", "manifest": { "app": { - "id": "nostr-rs-relay", - "name": "Nostr Relay (Rust)", - "version": "0.8.0", - "description": "High-performance Nostr relay written in Rust. Host your own decentralized social media relay and earn networking profits.", "container": { + "data_uid": "1000:1000", "image": "scsibug/nostr-rs-relay:0.8.9", "image_signature": "cosign://...", - "pull_policy": "verify-signature", - "data_uid": "1000:1000" + "pull_policy": "verify-signature" }, "dependencies": [ { "storage": "10Gi" } ], - "resources": { - "cpu_limit": 2, - "memory_limit": "1Gi", - "disk_limit": "10Gi" - }, - "security": { - "capabilities": [], - "readonly_root": true, - "no_new_privileges": true, - "user": 1000, - "seccomp_profile": "default", - "network_policy": "isolated", - "apparmor_profile": "nostr-relay" - }, - "ports": [ - { - "host": 18081, - "container": 8080, - "protocol": "tcp" - } - ], - "volumes": [ - { - "type": "bind", - "source": "/var/lib/archipelago/nostr-relay", - "target": "/usr/src/app/db", - "options": [ - "rw" - ] - } - ], + "description": "High-performance Nostr relay written in Rust. Host your own decentralized social media relay and earn networking profits.", "environment": [ "RELAY_NAME=Archipelago Nostr Relay", "RELAY_DESCRIPTION=Self-hosted Nostr relay on Archipelago", @@ -3635,49 +3598,79 @@ "MAX_SUBSCRIPTIONS=100" ], "health_check": { - "type": "http", "endpoint": "http://localhost:8080", - "path": "/", "interval": "30s", + "path": "/", + "retries": 5, "timeout": "30s", - "retries": 5 + "type": "http" }, + "id": "nostr-rs-relay", + "name": "Nostr Relay (Rust)", "nostr_integration": { - "relay_type": "public", + "event_storage": "sqlite", "monetization_enabled": true, - "event_storage": "sqlite" - } + "relay_type": "public" + }, + "ports": [ + { + "container": 8080, + "host": 18081, + "protocol": "tcp" + } + ], + "resources": { + "cpu_limit": 2, + "disk_limit": "10Gi", + "memory_limit": "1Gi" + }, + "security": { + "apparmor_profile": "nostr-relay", + "capabilities": [], + "network_policy": "isolated", + "no_new_privileges": true, + "readonly_root": true, + "seccomp_profile": "default", + "user": 1000 + }, + "version": "0.8.0", + "volumes": [ + { + "options": [ + "rw" + ], + "source": "/var/lib/archipelago/nostr-relay", + "target": "/usr/src/app/db", + "type": "bind" + } + ] } - } + }, + "version": "0.9.0" }, "nostr-vpn": { - "version": "v0.3.7", - "image": "146.59.87.168:3000/lfg2025/nostr-vpn:v0.3.7" + "image": "146.59.87.168:3000/lfg2025/nostr-vpn:v0.3.7", + "version": "v0.3.7" }, "ollama": { - "version": "latest", - "image": "146.59.87.168:3000/lfg2025/ollama:latest" + "image": "146.59.87.168:3000/lfg2025/ollama:latest", + "version": "latest" }, "penpot": { - "version": "2.4", "image": "146.59.87.168:3000/lfg2025/penpot-frontend:2.4", "images": { - "penpot-frontend": "146.59.87.168:3000/lfg2025/penpot-frontend:2.4", "penpot-backend": "146.59.87.168:3000/lfg2025/penpot-backend:2.4", "penpot-exporter": "146.59.87.168:3000/lfg2025/penpot-exporter:2.4", + "penpot-frontend": "146.59.87.168:3000/lfg2025/penpot-frontend:2.4", "penpot-postgres": "146.59.87.168:3000/lfg2025/postgres:15", "penpot-valkey": "146.59.87.168:3000/lfg2025/valkey:8.1" - } + }, + "version": "2.4" }, "photoprism": { - "version": "240915", "image": "146.59.87.168:3000/lfg2025/photoprism:240915", "manifest": { "app": { - "id": "photoprism", - "name": "PhotoPrism", - "version": "240915", - "description": "AI-powered photo management with facial recognition.", "container": { "image": "146.59.87.168:3000/lfg2025/photoprism:240915", "pull_policy": "if-not-present" @@ -3687,9 +3680,49 @@ "storage": "10Gi" } ], + "description": "AI-powered photo management with facial recognition.", + "environment": [ + "PHOTOPRISM_ADMIN_PASSWORD=archipelago", + "PHOTOPRISM_DEFAULT_LOCALE=en" + ], + "health_check": { + "endpoint": "localhost:2342", + "interval": "60s", + "retries": 3, + "timeout": "5s", + "type": "tcp" + }, + "id": "photoprism", + "interfaces": { + "main": { + "description": "PhotoPrism photo library", + "name": "Web UI", + "path": "/", + "port": 2342, + "protocol": "http", + "type": "ui" + } + }, + "metadata": { + "author": "PhotoPrism", + "category": "data", + "icon": "/assets/img/app-icons/photoprism.svg", + "launch": { + "open_in_new_tab": true + }, + "repo": "https://github.com/photoprism/photoprism" + }, + "name": "PhotoPrism", + "ports": [ + { + "container": 2342, + "host": 2342, + "protocol": "tcp" + } + ], "resources": { - "memory_limit": "1Gi", - "disk_limit": "10Gi" + "disk_limit": "10Gi", + "memory_limit": "1Gi" }, "security": { "capabilities": [ @@ -3697,82 +3730,75 @@ "SETUID", "SETGID" ], - "readonly_root": false, - "network_policy": "isolated" + "network_policy": "isolated", + "readonly_root": false }, - "ports": [ - { - "host": 2342, - "container": 2342, - "protocol": "tcp" - } - ], + "version": "240915", "volumes": [ { - "type": "bind", - "source": "/var/lib/archipelago/photoprism", - "target": "/photoprism/storage", "options": [ "rw" - ] + ], + "source": "/var/lib/archipelago/photoprism", + "target": "/photoprism/storage", + "type": "bind" } - ], - "environment": [ - "PHOTOPRISM_ADMIN_PASSWORD=archipelago", - "PHOTOPRISM_DEFAULT_LOCALE=en" - ], - "health_check": { - "type": "tcp", - "endpoint": "localhost:2342", - "interval": "60s", - "timeout": "5s", - "retries": 3 - }, - "interfaces": { - "main": { - "name": "Web UI", - "description": "PhotoPrism photo library", - "type": "ui", - "port": 2342, - "protocol": "http", - "path": "/" - } - }, - "metadata": { - "icon": "/assets/img/app-icons/photoprism.svg", - "category": "data", - "author": "PhotoPrism", - "repo": "https://github.com/photoprism/photoprism", - "launch": { - "open_in_new_tab": true - } - } + ] } - } + }, + "version": "240915" }, "portainer": { - "version": "2.19.4", "image": "146.59.87.168:3000/lfg2025/portainer:2.19.4", "manifest": { "app": { - "id": "portainer", - "name": "Portainer", - "version": "2.19.4", - "description": "Container management web UI for the local Podman socket.", "category": "development", "container": { + "data_uid": "1000:1000", "image": "146.59.87.168:3000/lfg2025/portainer:2.19.4", - "pull_policy": "if-not-present", - "data_uid": "1000:1000" + "pull_policy": "if-not-present" }, "dependencies": [ { "storage": "1Gi" } ], + "description": "Container management web UI for the local Podman socket.", + "environment": [], + "id": "portainer", + "interfaces": { + "main": { + "description": "Portainer web interface", + "name": "Web UI", + "path": "/", + "port": 9000, + "protocol": "http", + "type": "ui" + } + }, + "metadata": { + "features": [ + "Container management dashboard", + "Local Podman socket access", + "Compose stack storage" + ], + "icon": "/assets/img/app-icons/portainer.webp", + "launch": { + "open_in_new_tab": true + }, + "tier": "optional" + }, + "name": "Portainer", + "ports": [ + { + "container": 9000, + "host": 9000, + "protocol": "tcp" + } + ], "resources": { - "memory_limit": "256Mi", - "disk_limit": "1Gi" + "disk_limit": "1Gi", + "memory_limit": "256Mi" }, "security": { "capabilities": [ @@ -3781,77 +3807,44 @@ "SETGID", "DAC_OVERRIDE" ], - "readonly_root": false, + "network_policy": "isolated", "no_new_privileges": true, - "network_policy": "isolated" + "readonly_root": false }, - "ports": [ - { - "host": 9000, - "container": 9000, - "protocol": "tcp" - } - ], + "version": "2.19.4", "volumes": [ { - "type": "bind", + "options": [ + "rw" + ], "source": "/var/lib/archipelago/portainer", "target": "/data", - "options": [ - "rw" - ] + "type": "bind" }, { - "type": "bind", + "options": [ + "rw" + ], "source": "/var/lib/archipelago/portainer/compose", "target": "/data/compose", - "options": [ - "rw" - ] + "type": "bind" }, { - "type": "bind", - "source": "/run/user/1000/podman/podman.sock", - "target": "/var/run/docker.sock", "options": [ "rw" - ] + ], + "source": "/run/user/1000/podman/podman.sock", + "target": "/var/run/docker.sock", + "type": "bind" } - ], - "environment": [], - "interfaces": { - "main": { - "name": "Web UI", - "description": "Portainer web interface", - "type": "ui", - "port": 9000, - "protocol": "http", - "path": "/" - } - }, - "metadata": { - "icon": "/assets/img/app-icons/portainer.webp", - "tier": "optional", - "launch": { - "open_in_new_tab": true - }, - "features": [ - "Container management dashboard", - "Local Podman socket access", - "Compose stack storage" - ] - } + ] } - } + }, + "version": "2.19.4" }, "router": { - "version": "1.0.0", "manifest": { "app": { - "id": "router", - "name": "Mesh Router", - "version": "1.0.0", - "description": "Mesh routing and local network management. Provides device discovery, routing, and network topology visualization.", "container": { "image": "archipelago/router:1.0.0", "image_signature": "cosign://...", @@ -3862,96 +3855,96 @@ "storage": "500Mi" } ], - "resources": { - "cpu_limit": 2, - "memory_limit": "512Mi", - "disk_limit": "500Mi" - }, - "security": { - "capabilities": [ - "NET_ADMIN", - "NET_RAW" - ], - "readonly_root": true, - "no_new_privileges": true, - "user": 1000, - "seccomp_profile": "default", - "network_policy": "host", - "apparmor_profile": "router" - }, - "ports": [ - { - "host": 8084, - "container": 8080, - "protocol": "tcp" - }, - { - "host": 5353, - "container": 5353, - "protocol": "udp" - }, - { - "host": 1900, - "container": 1900, - "protocol": "udp" - } - ], - "volumes": [ - { - "type": "bind", - "source": "/var/lib/archipelago/router", - "target": "/app/data", - "options": [ - "rw" - ] - }, - { - "type": "bind", - "source": "/var/run/dbus", - "target": "/var/run/dbus", - "options": [ - "ro" - ] - } - ], + "description": "Mesh routing and local network management. Provides device discovery, routing, and network topology visualization.", "environment": [ "NETWORK_INTERFACE=eth0", "MESH_ENABLED=true", "DEVICE_DISCOVERY=true" ], "health_check": { - "type": "http", "endpoint": "http://localhost:8084", - "path": "/health", "interval": "30s", + "path": "/health", + "retries": 3, "timeout": "5s", - "retries": 3 + "type": "http" }, + "id": "router", + "name": "Mesh Router", "networking": { - "mesh_enabled": true, - "local_network_access": true, "device_discovery": true, + "local_network_access": true, + "mesh_enabled": true, "routing_protocols": [ "olsr", "babel" ] - } + }, + "ports": [ + { + "container": 8080, + "host": 8084, + "protocol": "tcp" + }, + { + "container": 5353, + "host": 5353, + "protocol": "udp" + }, + { + "container": 1900, + "host": 1900, + "protocol": "udp" + } + ], + "resources": { + "cpu_limit": 2, + "disk_limit": "500Mi", + "memory_limit": "512Mi" + }, + "security": { + "apparmor_profile": "router", + "capabilities": [ + "NET_ADMIN", + "NET_RAW" + ], + "network_policy": "host", + "no_new_privileges": true, + "readonly_root": true, + "seccomp_profile": "default", + "user": 1000 + }, + "version": "1.0.0", + "volumes": [ + { + "options": [ + "rw" + ], + "source": "/var/lib/archipelago/router", + "target": "/app/data", + "type": "bind" + }, + { + "options": [ + "ro" + ], + "source": "/var/run/dbus", + "target": "/var/run/dbus", + "type": "bind" + } + ] } - } + }, + "version": "1.0.0" }, "routstr": { - "version": "v0.4.3", - "image": "146.59.87.168:3000/lfg2025/routstr:v0.4.3" + "image": "146.59.87.168:3000/lfg2025/routstr:v0.4.3", + "version": "v0.4.3" }, "searxng": { - "version": "latest", "image": "146.59.87.168:3000/lfg2025/searxng:latest", "manifest": { "app": { - "id": "searxng", - "name": "SearXNG", - "version": "1.0.0", - "description": "Privacy-respecting metasearch engine. Search the web without tracking.", "container": { "image": "146.59.87.168:3000/lfg2025/searxng:latest", "pull_policy": "if-not-present" @@ -3961,60 +3954,60 @@ "storage": "2Gi" } ], - "resources": { - "cpu_limit": 2, - "memory_limit": "1Gi", - "disk_limit": "2Gi" - }, - "security": { - "capabilities": [], - "readonly_root": true, - "no_new_privileges": true, - "user": 1000, - "seccomp_profile": "default", - "network_policy": "isolated", - "apparmor_profile": "searxng" - }, - "ports": [ - { - "host": 8888, - "container": 8080, - "protocol": "tcp" - } - ], - "volumes": [ - { - "type": "bind", - "source": "/var/lib/archipelago/searxng", - "target": "/etc/searxng", - "options": [ - "rw" - ] - } - ], + "description": "Privacy-respecting metasearch engine. Search the web without tracking.", "environment": [ "SEARXNG_HOSTNAME=localhost", "SEARXNG_BIND_ADDRESS=0.0.0.0:8080" ], "health_check": { - "type": "http", "endpoint": "http://localhost:8080", - "path": "/", "interval": "30s", + "path": "/", + "retries": 5, "timeout": "30s", - "retries": 5 - } + "type": "http" + }, + "id": "searxng", + "name": "SearXNG", + "ports": [ + { + "container": 8080, + "host": 8888, + "protocol": "tcp" + } + ], + "resources": { + "cpu_limit": 2, + "disk_limit": "2Gi", + "memory_limit": "1Gi" + }, + "security": { + "apparmor_profile": "searxng", + "capabilities": [], + "network_policy": "isolated", + "no_new_privileges": true, + "readonly_root": true, + "seccomp_profile": "default", + "user": 1000 + }, + "version": "1.0.0", + "volumes": [ + { + "options": [ + "rw" + ], + "source": "/var/lib/archipelago/searxng", + "target": "/etc/searxng", + "type": "bind" + } + ] } - } + }, + "version": "latest" }, "strfry": { - "version": "0.9.0", "manifest": { "app": { - "id": "strfry", - "name": "Strfry Nostr Relay", - "version": "0.9.0", - "description": "Lightweight Nostr relay written in C++. Alternative to nostr-rs-relay with lower resource usage.", "container": { "image": "dockurr/strfry:1.0.4", "image_signature": "cosign://...", @@ -4025,86 +4018,117 @@ "storage": "5Gi" } ], - "resources": { - "cpu_limit": 1, - "memory_limit": "512Mi", - "disk_limit": "5Gi" - }, - "security": { - "capabilities": [], - "readonly_root": true, - "no_new_privileges": true, - "user": 1000, - "seccomp_profile": "default", - "network_policy": "isolated", - "apparmor_profile": "nostr-relay" - }, - "ports": [ - { - "host": 8082, - "container": 8080, - "protocol": "tcp" - } - ], - "volumes": [ - { - "type": "bind", - "source": "/var/lib/archipelago/strfry", - "target": "/strfry", - "options": [ - "rw" - ] - } - ], + "description": "Lightweight Nostr relay written in C++. Alternative to nostr-rs-relay with lower resource usage.", "environment": [ "RELAY_NAME=Archipelago Strfry Relay" ], "health_check": { - "type": "http", "endpoint": "http://localhost:8082", - "path": "/health", "interval": "30s", + "path": "/health", + "retries": 3, "timeout": "5s", - "retries": 3 + "type": "http" }, + "id": "strfry", + "name": "Strfry Nostr Relay", "nostr_integration": { - "relay_type": "public", - "monetization_enabled": true - } + "monetization_enabled": true, + "relay_type": "public" + }, + "ports": [ + { + "container": 8080, + "host": 8082, + "protocol": "tcp" + } + ], + "resources": { + "cpu_limit": 1, + "disk_limit": "5Gi", + "memory_limit": "512Mi" + }, + "security": { + "apparmor_profile": "nostr-relay", + "capabilities": [], + "network_policy": "isolated", + "no_new_privileges": true, + "readonly_root": true, + "seccomp_profile": "default", + "user": 1000 + }, + "version": "0.9.0", + "volumes": [ + { + "options": [ + "rw" + ], + "source": "/var/lib/archipelago/strfry", + "target": "/strfry", + "type": "bind" + } + ] } - } + }, + "version": "0.9.0" }, "tailscale": { - "version": "stable", - "image": "146.59.87.168:3000/lfg2025/tailscale:stable" + "image": "146.59.87.168:3000/lfg2025/tailscale:stable", + "version": "stable" }, "uptime-kuma": { - "version": "1", "image": "146.59.87.168:3000/lfg2025/uptime-kuma:1", "manifest": { "app": { - "id": "uptime-kuma", - "name": "Uptime Kuma", - "version": "1.23.0", - "description": "Self-hosted uptime monitoring.", "container": { - "image": "146.59.87.168:3000/lfg2025/uptime-kuma:1", - "pull_policy": "if-not-present", - "network": "pasta", "custom_args": [ "--", "node", "server/server.js" - ] + ], + "image": "146.59.87.168:3000/lfg2025/uptime-kuma:1", + "network": "pasta", + "pull_policy": "if-not-present" }, "dependencies": [ { "storage": "1Gi" } ], + "description": "Self-hosted uptime monitoring.", + "environment": [ + "TZ=UTC" + ], + "health_check": { + "endpoint": "localhost:3001", + "interval": "30s", + "path": "/", + "retries": 3, + "timeout": "5s", + "type": "http" + }, + "id": "uptime-kuma", + "metadata": { + "author": "Uptime Kuma", + "category": "data", + "icon": "/assets/img/app-icons/uptime-kuma.webp", + "launch": { + "open_in_new_tab": true + }, + "repo": "https://github.com/louislam/uptime-kuma", + "tier": "recommended" + }, + "name": "Uptime Kuma", + "ports": [ + { + "container": 3001, + "host": 3002, + "protocol": "tcp" + } + ], "resources": { - "memory_limit": "256Mi", - "disk_limit": "1Gi" + "disk_limit": "1Gi", + "memory_limit": "256Mi" }, "security": { "capabilities": [ @@ -4113,72 +4137,79 @@ "SETUID", "SETGID" ], - "readonly_root": false, - "network_policy": "isolated" + "network_policy": "isolated", + "readonly_root": false }, - "ports": [ - { - "host": 3002, - "container": 3001, - "protocol": "tcp" - } - ], + "version": "1.23.0", "volumes": [ { - "type": "bind", - "source": "/var/lib/archipelago/uptime-kuma", - "target": "/app/data", "options": [ "rw" - ] + ], + "source": "/var/lib/archipelago/uptime-kuma", + "target": "/app/data", + "type": "bind" } - ], - "environment": [ - "TZ=UTC" - ], - "health_check": { - "type": "http", - "endpoint": "localhost:3001", - "path": "/", - "interval": "30s", - "timeout": "5s", - "retries": 3 - }, - "metadata": { - "icon": "/assets/img/app-icons/uptime-kuma.webp", - "category": "data", - "tier": "recommended", - "author": "Uptime Kuma", - "repo": "https://github.com/louislam/uptime-kuma", - "launch": { - "open_in_new_tab": true - } - } + ] } - } + }, + "version": "1" }, "vaultwarden": { - "version": "1.30.0-alpine", "image": "146.59.87.168:3000/lfg2025/vaultwarden:1.30.0-alpine", "manifest": { "app": { - "id": "vaultwarden", - "name": "Vaultwarden", - "version": "1.30.0", - "description": "Self-hosted password vault with zero-knowledge encryption.", "container": { "image": "146.59.87.168:3000/lfg2025/vaultwarden:1.30.0-alpine", - "pull_policy": "if-not-present", - "network": "pasta" + "network": "pasta", + "pull_policy": "if-not-present" }, "dependencies": [ { "storage": "1Gi" } ], + "description": "Self-hosted password vault with zero-knowledge encryption.", + "environment": [], + "health_check": { + "endpoint": "localhost:80", + "interval": "30s", + "retries": 3, + "timeout": "5s", + "type": "tcp" + }, + "id": "vaultwarden", + "interfaces": { + "main": { + "description": "Vaultwarden web vault", + "name": "Web UI", + "path": "/", + "port": 8082, + "protocol": "http", + "type": "ui" + } + }, + "metadata": { + "author": "Vaultwarden", + "category": "data", + "icon": "/assets/img/app-icons/vaultwarden.webp", + "launch": { + "open_in_new_tab": true + }, + "repo": "https://github.com/dani-garcia/vaultwarden", + "tier": "recommended" + }, + "name": "Vaultwarden", + "ports": [ + { + "container": 80, + "host": 8082, + "protocol": "tcp" + } + ], "resources": { - "memory_limit": "256Mi", - "disk_limit": "1Gi" + "disk_limit": "1Gi", + "memory_limit": "256Mi" }, "security": { "capabilities": [ @@ -4187,56 +4218,27 @@ "SETGID", "NET_BIND_SERVICE" ], - "readonly_root": false, - "network_policy": "isolated" + "network_policy": "isolated", + "readonly_root": false }, - "ports": [ - { - "host": 8082, - "container": 80, - "protocol": "tcp" - } - ], + "version": "1.30.0", "volumes": [ { - "type": "bind", - "source": "/var/lib/archipelago/vaultwarden", - "target": "/data", "options": [ "rw" - ] + ], + "source": "/var/lib/archipelago/vaultwarden", + "target": "/data", + "type": "bind" } - ], - "environment": [], - "health_check": { - "type": "tcp", - "endpoint": "localhost:80", - "interval": "30s", - "timeout": "5s", - "retries": 3 - }, - "interfaces": { - "main": { - "name": "Web UI", - "description": "Vaultwarden web vault", - "type": "ui", - "port": 8082, - "protocol": "http", - "path": "/" - } - }, - "metadata": { - "icon": "/assets/img/app-icons/vaultwarden.webp", - "category": "data", - "tier": "recommended", - "author": "Vaultwarden", - "repo": "https://github.com/dani-garcia/vaultwarden", - "launch": { - "open_in_new_tab": true - } - } + ] } - } + }, + "version": "1.30.0-alpine" } - } + }, + "schema": 1, + "signature": "0e8bd98743c5ac56ef350964e99f72358fd05d051c002dcd92a7d3e089bd7fddad4ae2870b39ae35e6b2fc386780f8bca53f6eb10dfefd295977e17ec239ac0a", + "signed_by": "did:key:z6MkkidEnEpo6qHMCNSZoNKWtvQvxq3whnaME9wGgEFhq7ur", + "updated": "2026-06-29" } diff --git a/scripts/sign-catalog.sh b/scripts/sign-catalog.sh new file mode 100755 index 00000000..c9ea3727 --- /dev/null +++ b/scripts/sign-catalog.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# One-step release-catalog signer. +# +# Run: bash scripts/sign-catalog.sh +# Then: paste your 24-word release master mnemonic, press Enter, then Ctrl-D. +# +# It signs releases/app-catalog.json in place and checks the signature was made +# by the expected release-root key. Your mnemonic is read from the terminal only +# (never stored, never in shell history, never passed to Claude). +set -euo pipefail + +REPO="/home/archipelago/Projects/archy" +CATALOG="$REPO/releases/app-catalog.json" +EXPECTED_DID="did:key:z6MkkidEnEpo6qHMCNSZoNKWtvQvxq3whnaME9wGgEFhq7ur" + +# Use ONLY the prebuilt signer. If it isn't ready, stop cleanly — never compile +# here (compiling caused the earlier hangs). Claude builds it in the background. +BIN="/tmp/archy-sign-bin/release/archipelago" +if [[ ! -x "$BIN" ]]; then + echo "⏳ The signer isn't ready yet — Claude is still building it." + echo " Wait until Claude says 'READY', then run this again. Nothing was changed." + exit 0 +fi +SIGN=("$BIN" ceremony sign "$CATALOG") + +echo "════════════════════════════════════════════════════════════════" +echo " Paste your 24-word release master mnemonic below, press Enter," +echo " then press Ctrl-D on a new line." +echo "════════════════════════════════════════════════════════════════" +"${SIGN[@]}" + +# Verify the signature is present and made by the expected key. +echo +if grep -q "\"signed_by\": \"$EXPECTED_DID\"" "$CATALOG" \ + && grep -q '"signature":' "$CATALOG"; then + echo "✅ SUCCESS — catalog signed by the correct release-root key." + echo " Tell Claude \"signed\" and it will commit + push for you." +else + echo "❌ Something is off — the catalog is NOT signed by the expected key." + echo " Expected signer: $EXPECTED_DID" + echo " Do NOT commit. Check the mnemonic and re-run, or ask Claude." + exit 1 +fi