Compare commits

...
Author SHA1 Message Date
archipelago 3ec67b8f31 chore(apps): mirror-backed bumps — vaultwarden, nbxplorer, home-assistant (PENDING)
Prepared patch-level bumps with no data migration, held here until the
three images can be mirrored to source.archipelago-foundation.org/lfg2025
(currently blocked: the only registry-push credential on record for the
lfg2025 namespace is dead, and the ai account cannot push user-namespace
packages it does not own):

  vaultwarden       1.37.1-alpine -> 1.37.2-alpine  (docker.io/vaultwarden/server)
  archy-nbxplorer   2.6.0         -> 2.6.11         (docker.io/nicolasdorier/nbxplorer)
  home-assistant    2026.8.2      -> 2026.8.3       (docker.io/homeassistant/home-assistant)

All three target tags verified present upstream. To land: mirror the
images, merge this branch into main, regenerate + re-sign the catalog.
2026-08-30 16:23:05 -04:00
archipelago 7b88ba59b2 chore(apps): bump the pins that need no mirroring; curate Cuprate's store entry
Demo images / Build & push demo images (push) Failing after 40s
Pin bumps (all verified pullable from their public registries before
editing, so none can become an image-not-found on a node):

  strfry           1.1.1 -> 1.1.2              (dockurr/strfry, direct pull)
  btcpay-server    2.4.2 -> 2.4.3             (docker.io/btcpayserver, direct pull)
  netbird (nginx)  1.31.3-alpine -> 1.31.4-alpine
  pine   (nginx)   1.31.3-alpine -> 1.31.4-alpine

image-versions.sh moved in lockstep for BTCPAY_IMAGE — it is the baseline
the update badge compares against. Held back deliberately, per the risk
policy from the Aug-17 pass: gitea (four minors of DB migrations),
portainer (six minors), filebrowser (2.27 -> 2.63), fedimint/gateway
(0.8 -> 0.12, real migrations), lnd (money-critical), netbird-server/
netbird-dashboard (0.x, must move in lockstep), and everything with a
major jump or a data migration.

Cuprate also gets its curated store entry (category money, tier optional,
icon, repo) — same shape as the Alby Hub / phoenixd entries — synced
through generate-app-catalog.py into both store catalogs and the
app-session config. The fips launch-port list is unchanged (Cuprate has
no UI port; the generated file round-trips to the committed bytes after
cargo fmt).

Three further bumps are prepared and parked on the
app-bumps-mirror-pending branch, blocked only on a registry-push token:
vaultwarden 1.37.2-alpine, archy-nbxplorer 2.6.11, home-assistant
2026.8.3 — all mirror-backed, and the push credential on record for the
lfg2025 namespace is dead.

Drift gate: check-app-catalog-drift.py --release --strict clean
(31 store entries, 0 drift, 0 missing). appSessionConfig tests 7/7.
2026-08-30 16:22:26 -04:00
archipelago b12d1d3826 feat(apps): track the last untracked apps' upstreams
Five apps had no app.upstream block, so nothing could ever tell us
when their pins fell behind upstream:

  barkd           gitlab ark-bitcoin/bark   (GitLab-only project)
  immich-postgres ghcr  immich-app/postgres (image exists only on ghcr.io)
  indeedhub-minio github minio/minio
  pine-whisper    dockerhub rhasspy/wyoming-whisper
  lightning-stack manual — no public listing exists for
                   lightninglabs/lightning-stack anywhere (docker.io,
                   ghcr.io, github.com all checked), so it is tracked by hand

This adds two fetchers to scripts/check-upstream-releases.py to reach the
first two: latest_gitlab (GitLab releases API; strips the project-name
tag prefix, e.g. bark-0.6.2 -> 0.6.2) and latest_ghcr (anonymous pull
token + tags/list, the same handshake a docker pull performs).

Live-verified after the change:
  barkd            0.3.0 -> 0.6.2   (bump gated on ark_client.rs REST compat)
  immich-postgres  14-vectorchord0.4.3-pgvectors0.2.0 -> 17-vectorchord0.4.3-pgvector0.8.0
  indeedhub-minio  RELEASE.2024-11-07T00-52-20Z -> latest (date-opaque: UNCOMPARABLE, shown for hand comparison)
  pine-whisper     3.4.1 -> 3.6.0   (tuned-args revision needs re-basing, not just a pin move)

Offline coverage check: 59 apps, 0 untracked.
2026-08-30 16:22:11 -04:00
archipelago 698e915df2 Merge PR #141: package Cuprate, an alternative Monero node
Demo images / Build & push demo images (push) Failing after 37s
2026-08-30 14:18:42 -04:00
ssmithxandarchipelago a179df66d8 docs: add app update strategy, SSH access, and app wishlist to TODO
Flags the app update policy already noted as unresolved in
app-developer-guide.md, adds a section for SSH access strategy, and
starts an app wishlist (Cashu wallet, phoenixd) for packaging.
2026-08-30 14:01:20 -04:00
ssmithxandarchipelago 771ff0d28b docs: add TODO.md backlog and link from docs index
Captures unscoped forward-looking items (peering/federation model,
distributed git & OTA, nostr integration, platform/OS, app testing,
observability, and the dev/build process) so they're tracked outside
of ROADMAP.md's curated public summary.
2026-08-30 14:01:20 -04:00
archipelago c188d9de78 fix(lifecycle): abort unsafe declarative uninstall 2026-08-23 07:59:40 -04:00
archipelago 37a82fd2f9 fix(cuprate): avoid Penpot RPC port collision 2026-08-23 01:43:09 -04:00
archipelagoandClaude Opus 5 f1b5d2d267 fix(cuprate): stop publishing the unauthenticated unrestricted RPC
The manifest bound cuprated's unrestricted RPC (full node control) to
0.0.0.0 inside the container with
i_know_what_im_doing_allow_public_unrestricted_rpc = true, relying on
ports[].bind: 127.0.0.1 to keep it private. That only restricts the HOST
side. Verified live on archi-dev-box 2026-08-22: a peer container got a
valid unauthenticated get_info off container port 18081 — and still did
after cuprate was moved to its own network, because podman bridges route
to each other unless created with --opt isolate=true, which the
orchestrator's auto-create does not pass. Every app on the node could
therefore drive full node control with no credential.

The PR justified this as the pattern bitcoin-knots already uses, but
knots writes rpcuser/rpcpassword from generated secrets, so a 0.0.0.0
bind there still is not control without credentials. cuprated has no RPC
authentication at all, so the two are not equivalent.

Unrestricted RPC is now left at cuprated's own default — container
loopback only, published nowhere, reachable by nothing — which is what
upstream intends by refusing a non-local bind without an explicit
override. Restricted RPC (the safe-for-public subset wallets use) and p2p
are unchanged, and health_check moves to 18089 since 18184 is gone.

Re-verified after the change: peer container gets connection refused on
18081 (exit 7), restricted RPC and the health endpoint still answer, the
node still syncs, validator APPROVED, 76/76 container tests pass
including the unauthenticated-port canary (still 28 — an auth: local
port was removed, not an auth: none one).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 03:10:53 -04:00
ssmithxandClaude Sonnet 5 d6b48ce095 feat(apps): package Cuprate, an alternative Monero node
Full-node daemon: P2P + Monero's own restricted RPC (the safe-for-public
subset wallets use as a "remote node") are auth:none like bitcoin/electrumx's
equivalents; unrestricted RPC (full node control) stays gated auth:local.
readonly_root works cleanly since the upstream image is FROM scratch with
ownership fixed at build time — no runtime chown/setuid needed, unlike
bitcoin-knots/core.

Verified locally end-to-end before committing: built the upstream Dockerfile,
confirmed the generated Cuprated.toml against `cuprated --generate-config`/
`--dry-run`, and ran the real image with the manifest's exact ports/volumes —
including discovering that cuprated's own 127.0.0.1-default RPC bind is
unreachable through a published host port and needs to bind 0.0.0.0
internally with ports[].bind:127.0.0.1 doing the actual restriction, the
same pattern bitcoin-knots' RPC port already uses in this repo.

Bumps the unauthenticated_ports_are_all_accounted_for canary (26 -> 28) for
cuprate's two auth:none ports, per that test's own review-before-updating
contract.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 13:58:06 +00:00
23 changed files with 385 additions and 27 deletions
+16 -4
View File
@@ -52,13 +52,13 @@
{ {
"id": "btcpay-server", "id": "btcpay-server",
"title": "BTCPay Server", "title": "BTCPay Server",
"version": "2.4.2", "version": "2.4.3",
"description": "Self-hosted Bitcoin payment processor. Accept Bitcoin payments without intermediaries.", "description": "Self-hosted Bitcoin payment processor. Accept Bitcoin payments without intermediaries.",
"icon": "/assets/img/app-icons/btcpay-server.png", "icon": "/assets/img/app-icons/btcpay-server.png",
"author": "BTCPay Server Foundation", "author": "BTCPay Server Foundation",
"category": "commerce", "category": "commerce",
"tier": "core", "tier": "core",
"dockerImage": "docker.io/btcpayserver/btcpayserver:2.4.2", "dockerImage": "docker.io/btcpayserver/btcpayserver:2.4.3",
"repoUrl": "https://github.com/btcpayserver/btcpayserver", "repoUrl": "https://github.com/btcpayserver/btcpayserver",
"requires": [ "requires": [
"bitcoin-knots" "bitcoin-knots"
@@ -378,7 +378,7 @@
"icon": "/assets/img/app-icons/pine.svg", "icon": "/assets/img/app-icons/pine.svg",
"author": "Archipelago", "author": "Archipelago",
"category": "home", "category": "home",
"dockerImage": "docker.io/library/nginx:1.31.3-alpine", "dockerImage": "docker.io/library/nginx:1.31.4-alpine",
"repoUrl": "https://github.com/rhasspy/wyoming" "repoUrl": "https://github.com/rhasspy/wyoming"
}, },
{ {
@@ -464,7 +464,7 @@
"author": "NetBird", "author": "NetBird",
"category": "networking", "category": "networking",
"tier": "recommended", "tier": "recommended",
"dockerImage": "docker.io/library/nginx:1.31.3-alpine", "dockerImage": "docker.io/library/nginx:1.31.4-alpine",
"repoUrl": "https://github.com/netbirdio/netbird", "repoUrl": "https://github.com/netbirdio/netbird",
"containerConfig": { "containerConfig": {
"ports": [ "ports": [
@@ -571,6 +571,18 @@
"tier": "optional", "tier": "optional",
"dockerImage": "source.archipelago-foundation.org/lfg2025/phoenixd:0.9.0", "dockerImage": "source.archipelago-foundation.org/lfg2025/phoenixd:0.9.0",
"repoUrl": "https://github.com/ACINQ/phoenixd" "repoUrl": "https://github.com/ACINQ/phoenixd"
},
{
"id": "cuprate",
"title": "Cuprate",
"version": "0.1.0-preview",
"description": "Alternative Monero node implementation in Rust. Independently validates Monero consensus rules, providing a layer of security and redundancy for the network.",
"icon": "/assets/img/app-icons/cuprate.svg",
"author": "Cuprate contributors",
"category": "money",
"tier": "optional",
"dockerImage": "source.archipelago-foundation.org/lfg2025/cuprate:0.1.0-preview-18-g618ff14",
"repoUrl": "https://github.com/Cuprate/cuprate"
} }
] ]
} }
+2 -2
View File
@@ -1,7 +1,7 @@
app: app:
id: archy-nbxplorer id: archy-nbxplorer
name: NBXplorer name: NBXplorer
version: 2.6.0 version: 2.6.11
# Where this app comes from, so scripts/check-upstream-releases.py can # Where this app comes from, so scripts/check-upstream-releases.py can
# tell us when the pin below has fallen behind. Without it nothing can: # tell us when the pin below has fallen behind. Without it nothing can:
# container.image names our mirror, not the project it was mirrored from. # container.image names our mirror, not the project it was mirrored from.
@@ -11,7 +11,7 @@ app:
description: BTCPay blockchain indexer service. description: BTCPay blockchain indexer service.
container: container:
image: source.archipelago-foundation.org/lfg2025/nbxplorer:2.6.0 image: source.archipelago-foundation.org/lfg2025/nbxplorer:2.6.11
pull_policy: if-not-present pull_policy: if-not-present
network: archy-net network: archy-net
secret_env: secret_env:
+8
View File
@@ -2,6 +2,14 @@ app:
id: barkd id: barkd
name: Ark Wallet name: Ark Wallet
version: 0.3.0 version: 0.3.0
# Where this app comes from, so scripts/check-upstream-releases.py can
# tell us when the pin below has fallen behind. bark ships on GitLab only
# (no GitHub mirror), so the gitlab fetcher is the one that can see it.
# NOTE: a version bump is code work, not a pin move — the REST shapes are
# coded in core/archipelago/src/wallet/ark_client.rs (see Dockerfile note).
upstream:
kind: gitlab
repo: ark-bitcoin/bark
description: Ark protocol wallet daemon (barkd). Lets the node hold self-custodial off-chain bitcoin via an Ark server; the wallet talks to it over a local REST API. Signet by default while Ark matures. description: Ark protocol wallet daemon (barkd). Lets the node hold self-custodial off-chain bitcoin via an Ark server; the wallet talks to it over a local REST API. Signet by default while Ark matures.
container: container:
+2 -2
View File
@@ -1,7 +1,7 @@
app: app:
id: btcpay-server id: btcpay-server
name: BTCPay Server name: BTCPay Server
version: 2.4.2 version: 2.4.3
# Where this app comes from, so scripts/check-upstream-releases.py can # Where this app comes from, so scripts/check-upstream-releases.py can
# tell us when the pin below has fallen behind. Without it nothing can: # tell us when the pin below has fallen behind. Without it nothing can:
# container.image names our mirror, not the project it was mirrored from. # container.image names our mirror, not the project it was mirrored from.
@@ -11,7 +11,7 @@ app:
description: Self-hosted Bitcoin payment processor. Accept Bitcoin payments without intermediaries. description: Self-hosted Bitcoin payment processor. Accept Bitcoin payments without intermediaries.
container: container:
image: docker.io/btcpayserver/btcpayserver:2.4.2 image: docker.io/btcpayserver/btcpayserver:2.4.3
pull_policy: if-not-present pull_policy: if-not-present
network: archy-net network: archy-net
secret_env: secret_env:
+157
View File
@@ -0,0 +1,157 @@
app:
id: cuprate
name: Cuprate
# Matches the crate's own Cargo.toml version (binaries/cuprated/Cargo.toml).
# Cuprate has no stable release yet — this is explicitly work-in-progress
# software (see upstream README). The image tag below pins the exact
# commit built, since "0.1.0-preview" alone is not reproducible.
version: 0.1.0-preview
# Where this app comes from, so scripts/check-upstream-releases.py can
# tell us when the pin below has fallen behind. Without it nothing can:
# container.image names our mirror, not the project it was mirrored from.
upstream:
kind: github
repo: Cuprate/cuprate
description: Alternative Monero node implementation in Rust. Independently validates Monero consensus rules, providing a layer of security and redundancy for the network.
category: money
metadata:
icon: /assets/img/app-icons/cuprate.svg
repo: https://github.com/Cuprate/cuprate
tier: optional
container:
# Built from the upstream Dockerfile at the tip of main, 18 commits past
# the cuprated-0.1.0-preview tag (commit 618ff14, 2026-08-19) — there is
# no newer tagged release as of this writing. Re-pin to a tagged release
# once upstream cuts one.
image: source.archipelago-foundation.org/lfg2025/cuprate:0.1.0-preview-18-g618ff14
pull_policy: if-not-present
network: archy-net
# The image's own ENTRYPOINT is ["/usr/local/bin/cuprated"]; these are
# appended as its argv, matching the project's own systemd unit
# (cuprated.service) invocation exactly.
custom_args: ["--config-file", "/home/cuprate/Cuprated.toml"]
# The image (FROM scratch) creates uid:gid 1000:1000 for the `cuprate`
# user at build time and runs as it unconditionally (USER 1000:1000,
# no shell to switch users at runtime) — same pattern as
# apps/phoenixd, apps/electrumx, apps/nostr-rs-relay, apps/portainer,
# apps/barkd. The bind-mounted data dir must be owned by that literal
# uid or cuprated dies on a permission error the first time it writes.
data_uid: "1000:1000"
dependencies:
# Monero mainnet is ~250GiB unpruned as of 2026 and growing a few GB a
# month; cuprated's pruning support is not confirmed stable yet (the
# `pruning` crate exists in the workspace but nothing in this config
# surface toggles it), so this sizes for a full unpruned chain plus
# headroom rather than assuming pruning is available.
- storage: 300Gi
resources:
cpu_limit: 0
memory_limit: 4Gi
disk_limit: 300Gi
security:
# FROM scratch, no package manager/shell, ownership fixed at build time
# — unlike bitcoin-knots this needs no runtime chown/setuid dance, so it
# can run fully read-only with an empty capability set.
capabilities: []
readonly_root: true
no_new_privileges: true
network_policy: isolated
ports:
# P2P. Cuprate's own default listen address is already 0.0.0.0
# (p2p.clear_net.listen_on), so no config override is needed — only the
# host-side port differs from Monero's canonical 18080 because that
# number is already taken on this fleet by lnd's REST port.
- host: 18183
container: 18080
protocol: tcp
auth: none
auth_rationale: >-
Monero p2p gossip. Peers are anonymous by design and speak the Monero wire protocol, not HTTP.
# Unrestricted RPC (full node control) is deliberately NOT published.
# cuprated has no RPC authentication, and for a published port to reach
# it the service would have to bind 0.0.0.0 inside the container — at
# which point every other app can reach it directly on 18081, since
# ports[].bind only restricts the HOST side and podman bridges route to
# each other (verified live 2026-08-22: a peer container on archy-net
# got an unauthenticated get_info, from a *different* network). That is
# unlike bitcoin-knots, whose 0.0.0.0 RPC still demands the rpcuser /
# rpcpassword it writes from generated secrets. So unrestricted RPC is
# left at cuprated's own default — container loopback only, reachable by
# nothing — which is also what upstream intends by refusing a non-local
# bind without an explicit i_know_what_im_doing override.
# Restricted RPC: Monero's own purpose-built safe-for-public subset —
# what wallets use when connecting to a "remote node". Disabled by
# cuprated's own default; enabled via files[] below. A dashboard login
# would break wallet clients connecting programmatically, same
# reasoning as electrumx's port. The daemon still uses its canonical
# container port 18089, but Penpot already owns host port 18089, so this
# maps the public host port to the free 18090 instead.
- host: 18090
container: 18089
protocol: tcp
auth: none
auth_rationale: >-
Monero restricted RPC — the subset upstream considers safe for public/remote-node use. Wallets (Feather, monero-wallet-rpc, GUI) connect directly over plain HTTP JSON-RPC and cannot hold a dashboard session cookie.
volumes:
- type: bind
source: /var/lib/archipelago/cuprate
target: /home/cuprate
options: [rw]
# Settings that need to differ from cuprated's own documented defaults
# (verified against `cuprated --generate-config` and `--dry-run` locally,
# 2026-08-21):
# - target_max_memory: cuprated's own default auto-detects total *host*
# RAM via sysinfo, which inside a memory-limited container would let
# it size caches far past what resources.memory_limit above actually
# grants — same class of problem bitcoin-knots' -dbcache sizing
# comment addresses. Set explicitly, comfortably under the 4Gi limit.
# - rpc.restricted.enable: cuprated ships this off by default; flip on
# so the auth:none host port above actually serves something instead
# of refusing every connection. port stays at its documented default
# (canonical 18089), and advertise stays false — this node is not
# opting in to being listed as a public remote node over the p2p
# network, just reachable if someone points a wallet at it directly.
# - rpc.unrestricted.address + the allow-public flag: cuprated's own
# default (127.0.0.1) looks like the obviously-correct choice for a
# port meant to stay loopback-only, but verified live (2026-08-21)
# that a service bound literally to 127.0.0.1 *inside* the container
# is unreachable through the host's published port — connections
# reset regardless of how long the daemon has been up. Binding
# 0.0.0.0 inside and letting ports[].bind: 127.0.0.1 below be the
# actual restriction is the same pattern apps/bitcoin-knots already
# uses for its own RPC port (-rpcbind=0.0.0.0:8332 internally, gate
# restricts it externally) — not a new risk, the same one already
# reviewed and accepted for Bitcoin's RPC.
files:
- path: /var/lib/archipelago/cuprate/Cuprated.toml
content: |
network = "Mainnet"
target_max_memory = 3000000000
[rpc.restricted]
enable = true
overwrite: false
health_check:
type: tcp
# Restricted RPC — the only RPC surface published now.
endpoint: localhost:18090
interval: 30s
timeout: 5s
retries: 3
start_period: 5m
metadata:
icon: /assets/img/app-icons/cuprate.svg
category: money
tier: optional
author: Cuprate
repo: https://github.com/Cuprate/cuprate
+2 -2
View File
@@ -1,7 +1,7 @@
app: app:
id: homeassistant id: homeassistant
name: Home Assistant name: Home Assistant
version: 2026.7.3 version: 2026.8.3
# Where this app comes from, so scripts/check-upstream-releases.py can # Where this app comes from, so scripts/check-upstream-releases.py can
# tell us when the pin below has fallen behind. Without it nothing can: # tell us when the pin below has fallen behind. Without it nothing can:
# container.image names our mirror, not the project it was mirrored from. # container.image names our mirror, not the project it was mirrored from.
@@ -11,7 +11,7 @@ app:
description: Open source home automation platform. Control and monitor your smart home devices. description: Open source home automation platform. Control and monitor your smart home devices.
container: container:
image: source.archipelago-foundation.org/lfg2025/home-assistant:2026.8.2 image: source.archipelago-foundation.org/lfg2025/home-assistant:2026.8.3
pull_policy: if-not-present pull_policy: if-not-present
network: pasta network: pasta
+6
View File
@@ -2,6 +2,12 @@ app:
id: immich-postgres id: immich-postgres
name: Immich Postgres name: Immich Postgres
version: "14-vectorchord0.4.3-pgvectors0.2.0" version: "14-vectorchord0.4.3-pgvectors0.2.0"
# Upstream is the Immich-built Postgres image, published only on ghcr.io
# (no GitHub release tags, no Docker Hub repo) — the ghcr fetcher in
# scripts/check-upstream-releases.py is the only one that can see it.
upstream:
kind: ghcr
repo: immich-app/postgres
description: Postgres (pgvecto.rs / vectorchord) backend for Immich. description: Postgres (pgvecto.rs / vectorchord) backend for Immich.
# Container named immich_postgres (underscore) to match the runtime's existing # Container named immich_postgres (underscore) to match the runtime's existing
+6
View File
@@ -2,6 +2,12 @@ app:
id: indeedhub-minio id: indeedhub-minio
name: IndeedHub MinIO name: IndeedHub MinIO
version: "RELEASE.2024-11-07T00-52-20Z" version: "RELEASE.2024-11-07T00-52-20Z"
# MinIO's release tags are date-opaque (RELEASE.YYYY-MM-DD…), so the
# checker reports them as UNCOMPARABLE rather than ordering them — the
# latest tag is still shown for hand comparison, which is the point.
upstream:
kind: github
repo: minio/minio
description: MinIO S3-compatible object storage for IndeedHub media. description: MinIO S3-compatible object storage for IndeedHub media.
category: community category: community
+6
View File
@@ -2,6 +2,12 @@ app:
id: lightning-stack id: lightning-stack
name: Lightning Stack name: Lightning Stack
version: 0.12.0 version: 0.12.0
# No public listing exists for lightninglabs/lightning-stack (checked
# docker.io, ghcr.io and github.com) — nothing can be queried automatically,
# so this one is tracked by hand.
upstream:
kind: manual
url: no public listing for lightninglabs/lightning-stack — verify by hand
description: Complete Lightning Network implementation. Includes LND, CLN, and management tools. description: Complete Lightning Network implementation. Includes LND, CLN, and management tools.
container: container:
+1 -1
View File
@@ -18,7 +18,7 @@ app:
container_name: netbird container_name: netbird
container: container:
image: docker.io/library/nginx:1.31.3-alpine image: docker.io/library/nginx:1.31.4-alpine
pull_policy: if-not-present pull_policy: if-not-present
network: netbird-net network: netbird-net
# Self-signed TLS cert materialised before create — the dashboard needs a # Self-signed TLS cert materialised before create — the dashboard needs a
+8
View File
@@ -6,6 +6,14 @@ app:
# pick up the args change; the pre-release form "3.4.1-1" would compare # pick up the args change; the pre-release form "3.4.1-1" would compare
# LOWER than 3.4.1 under semver and never roll out. # LOWER than 3.4.1 under semver and never roll out.
version: "3.4.2" version: "3.4.2"
# Tracks the rhasspy/wyoming-whisper image we pin (Docker Hub — the
# project's GitHub tags are not the image tags). NOTE: this manifest
# deliberately ships an args-tuned revision AHEAD of the image tag (see
# comment above) — BEHIND here means the image tag moved and the tuned
# revision needs re-basing onto it, not just a pin bump.
upstream:
kind: dockerhub
repo: rhasspy/wyoming-whisper
description: Wyoming-protocol faster-whisper speech-to-text engine. Internal Pine voice-assistant stack member — turns speech captured by a PineVoice satellite into text for Home Assistant Assist. description: Wyoming-protocol faster-whisper speech-to-text engine. Internal Pine voice-assistant stack member — turns speech captured by a PineVoice satellite into text for Home Assistant Assist.
category: home category: home
+1 -1
View File
@@ -19,7 +19,7 @@ app:
container_name: pine container_name: pine
container: container:
image: docker.io/library/nginx:1.31.3-alpine image: docker.io/library/nginx:1.31.4-alpine
pull_policy: if-not-present pull_policy: if-not-present
network: archy-net network: archy-net
network_aliases: [pine] network_aliases: [pine]
+2 -2
View File
@@ -1,7 +1,7 @@
app: app:
id: strfry id: strfry
name: Strfry Nostr Relay name: Strfry Nostr Relay
version: 1.1.1 version: 1.1.2
# Where this app comes from, so scripts/check-upstream-releases.py can # Where this app comes from, so scripts/check-upstream-releases.py can
# tell us when the pin below has fallen behind. Without it nothing can: # tell us when the pin below has fallen behind. Without it nothing can:
# container.image names our mirror, not the project it was mirrored from. # container.image names our mirror, not the project it was mirrored from.
@@ -11,7 +11,7 @@ app:
description: Lightweight Nostr relay written in C++. Alternative to nostr-rs-relay with lower resource usage. description: Lightweight Nostr relay written in C++. Alternative to nostr-rs-relay with lower resource usage.
container: container:
image: dockurr/strfry:1.1.1 image: dockurr/strfry:1.1.2
image_signature: cosign://... image_signature: cosign://...
pull_policy: verify-signature pull_policy: verify-signature
+2 -2
View File
@@ -1,7 +1,7 @@
app: app:
id: vaultwarden id: vaultwarden
name: Vaultwarden name: Vaultwarden
version: 1.30.0 version: 1.37.2
# Where this app comes from, so scripts/check-upstream-releases.py can # Where this app comes from, so scripts/check-upstream-releases.py can
# tell us when the pin below has fallen behind. Without it nothing can: # tell us when the pin below has fallen behind. Without it nothing can:
# container.image names our mirror, not the project it was mirrored from. # container.image names our mirror, not the project it was mirrored from.
@@ -11,7 +11,7 @@ app:
description: Self-hosted password vault with zero-knowledge encryption. description: Self-hosted password vault with zero-knowledge encryption.
container: container:
image: source.archipelago-foundation.org/lfg2025/vaultwarden:1.37.1-alpine image: source.archipelago-foundation.org/lfg2025/vaultwarden:1.37.2-alpine
pull_policy: if-not-present pull_policy: if-not-present
network: pasta network: pasta
@@ -365,8 +365,18 @@ impl RpcHandler {
// after uninstall. The reconciler owns a manifest map independent of // after uninstall. The reconciler owns a manifest map independent of
// podman state, so a raw `podman rm` alone is not enough. // podman state, so a raw `podman rm` alone is not enough.
if let Some(orchestrator) = &self.orchestrator { if let Some(orchestrator) = &self.orchestrator {
let mut teardown_errors = Vec::new();
for app_id in orchestrator_uninstall_app_ids(package_id) { for app_id in orchestrator_uninstall_app_ids(package_id) {
let _ = orchestrator.remove(&app_id, preserve_data).await; if let Err(err) = orchestrator.remove(&app_id, preserve_data).await {
teardown_errors.push(format!("{app_id}: {err:#}"));
}
}
if !teardown_errors.is_empty() {
return Err(anyhow::anyhow!(
"Uninstall {} aborted: failed to remove declarative app unit(s): {}",
package_id,
teardown_errors.join("; ")
));
} }
} }
@@ -2182,6 +2192,11 @@ mod tests {
assert!(!is_missing_container_error("Error: OCI runtime error")); assert!(!is_missing_container_error("Error: OCI runtime error"));
} }
#[test]
fn single_app_uninstall_targets_its_declarative_unit() {
assert_eq!(orchestrator_uninstall_app_ids("cuprate"), vec!["cuprate"]);
}
#[test] #[test]
fn runtime_host_ports_are_manifest_derived_for_public_apps() { fn runtime_host_ports_are_manifest_derived_for_public_apps() {
assert_eq!(runtime_host_ports("photoprism"), vec![2342]); assert_eq!(runtime_host_ports("photoprism"), vec![2342]);
+10 -1
View File
@@ -1746,6 +1746,15 @@ app:
} }
} }
exempt.sort(); exempt.sort();
// 28 as of 2026-08-23: the 26 below plus cuprate's two exemptions —
// 18183 (Monero p2p gossip, same reasoning as bitcoin's 8333) and
// 18090 (host mapping for Monero's canonical 18089 restricted RPC,
// upstream's own safe-for-public
// subset that wallets connect to directly as a "remote node" over
// plain HTTP JSON-RPC — same reasoning as electrumx's 50001).
// cuprate's unrestricted RPC (full node control) stays loopback-only
// (auth: local), not in this set.
//
// 26 as of 2026-08-16: the 25 below plus phoenixd 9740, a // 26 as of 2026-08-16: the 25 below plus phoenixd 9740, a
// loopback-only JSON API whose own generated http password // loopback-only JSON API whose own generated http password
// authenticates every request (added with the phoenixd onboarding, // authenticates every request (added with the phoenixd onboarding,
@@ -1762,7 +1771,7 @@ app:
// stage timed out that cycle, so the count here lagged at 17. // stage timed out that cycle, so the count here lagged at 17.
assert_eq!( assert_eq!(
exempt.len(), exempt.len(),
26, 28,
"unauthenticated port set changed — review before updating this count: {exempt:?}" "unauthenticated port set changed — review before updating this count: {exempt:?}"
); );
} }
+1
View File
@@ -86,4 +86,5 @@ file.
## Roadmap & history ## Roadmap & history
- [Roadmap](ROADMAP.md) — where the project is going - [Roadmap](ROADMAP.md) — where the project is going
- [TODO](TODO.md) — working backlog of unscoped forward-looking items
- [archive/](archive/README.md) — superseded design and status documents, kept for provenance - [archive/](archive/README.md) — superseded design and status documents, kept for provenance
+52
View File
@@ -0,0 +1,52 @@
# TODO
Working backlog of forward-looking items not yet scoped into a dedicated plan
doc. See [`ROADMAP.md`](ROADMAP.md) for the curated, public-facing direction.
## Dev & build process (priority)
- Formalize the contributor workflow: releases, CI, maintainers, automated
builds, PR/issue flow, branch naming, and reproducible builds.
## Federation & peering
- Peering trust model — define tiers (trusted / public / private / peered)
on top of the existing federation DID trust levels.
- Federation architecture built on the above peering model.
## Distributed git & OTA
- Nostr-hosted git for the alpha (see
[`nostr-git-source-hosting.md`](nostr-git-source-hosting.md)).
- Distributed git beyond the nostr-hosting case.
- Distributed OTA / app delivery.
## Nostr integration
- Nostr signer integration.
## Platform / OS
- Source-availability ISO — define the build/distribution story.
- HW/OS update pipeline.
- Deeper OpenWRT integration.
- GrapheneOS integration — backups, attestation, profiles.
## App ecosystem
- Full pass testing every app in the catalog; expect issues across the board.
- App update strategy — finalize the update policy referenced in
[`app-developer-guide.md`](app-developer-guide.md) (pinned vs. mutable
tags, catalog-vs-disk precedence, rollout/rollback).
- App wishlist — candidates not yet packaged: Cashu wallet, phoenixd.
(CLN is already shipped as `apps/core-lightning`.)
## Access & security
- SSH access strategy — define the access model (keys, rotation, recovery
path, remote-support access).
## Observability
- Capture error logs to troubleshoot customer issues.
- Stats & visualization for traffic, blocked attacks, VPNs, routing.
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.2 KiB

+4 -4
View File
@@ -52,13 +52,13 @@
{ {
"id": "btcpay-server", "id": "btcpay-server",
"title": "BTCPay Server", "title": "BTCPay Server",
"version": "2.4.2", "version": "2.4.3",
"description": "Self-hosted Bitcoin payment processor. Accept Bitcoin payments without intermediaries.", "description": "Self-hosted Bitcoin payment processor. Accept Bitcoin payments without intermediaries.",
"icon": "/assets/img/app-icons/btcpay-server.png", "icon": "/assets/img/app-icons/btcpay-server.png",
"author": "BTCPay Server Foundation", "author": "BTCPay Server Foundation",
"category": "commerce", "category": "commerce",
"tier": "core", "tier": "core",
"dockerImage": "docker.io/btcpayserver/btcpayserver:2.4.2", "dockerImage": "docker.io/btcpayserver/btcpayserver:2.4.3",
"repoUrl": "https://github.com/btcpayserver/btcpayserver", "repoUrl": "https://github.com/btcpayserver/btcpayserver",
"requires": [ "requires": [
"bitcoin-knots" "bitcoin-knots"
@@ -378,7 +378,7 @@
"icon": "/assets/img/app-icons/pine.svg", "icon": "/assets/img/app-icons/pine.svg",
"author": "Archipelago", "author": "Archipelago",
"category": "home", "category": "home",
"dockerImage": "docker.io/library/nginx:1.31.3-alpine", "dockerImage": "docker.io/library/nginx:1.31.4-alpine",
"repoUrl": "https://github.com/rhasspy/wyoming" "repoUrl": "https://github.com/rhasspy/wyoming"
}, },
{ {
@@ -464,7 +464,7 @@
"author": "NetBird", "author": "NetBird",
"category": "networking", "category": "networking",
"tier": "recommended", "tier": "recommended",
"dockerImage": "docker.io/library/nginx:1.31.3-alpine", "dockerImage": "docker.io/library/nginx:1.31.4-alpine",
"repoUrl": "https://github.com/netbirdio/netbird", "repoUrl": "https://github.com/netbirdio/netbird",
"containerConfig": { "containerConfig": {
"ports": [ "ports": [
@@ -51,6 +51,7 @@ export const GENERATED_APP_TITLES: Record<string, string> = {
"botfights": "BotFights", "botfights": "BotFights",
"btcpay-server": "BTCPay Server", "btcpay-server": "BTCPay Server",
"core-lightning": "Core Lightning (CLN)", "core-lightning": "Core Lightning (CLN)",
"cuprate": "Cuprate",
"did-wallet": "Web5 DID Wallet", "did-wallet": "Web5 DID Wallet",
"electrs-ui": "Electrs UI", "electrs-ui": "Electrs UI",
"electrumx": "ElectrumX", "electrumx": "ElectrumX",
+45 -1
View File
@@ -39,6 +39,7 @@ import os
import re import re
import sys import sys
import urllib.error import urllib.error
import urllib.parse
import urllib.request import urllib.request
from dataclasses import dataclass, field from dataclasses import dataclass, field
from pathlib import Path from pathlib import Path
@@ -191,7 +192,50 @@ def _highest(tags: list[str], current: str = "") -> str:
return max(ranked)[1] return max(ranked)[1]
FETCHERS = {"github": latest_github, "dockerhub": latest_dockerhub} def latest_gitlab(project: str, current: str = "") -> str:
"""Newest release tag for a GitLab `group/project`.
Some projects publish releases only on GitLab with no GitHub mirror
(bark lives at ark-bitcoin/bark and nowhere else). GitLab release tags
sometimes carry the project name as a prefix (`bark-0.6.2`); strip it so
version ordering can see the number.
"""
esc = urllib.parse.quote(project, safe="")
releases = http_json(
f"https://gitlab.com/api/v4/projects/{esc}/releases?per_page=100"
)
tags = [str(r["tag_name"]) for r in releases]
prefix = project.rsplit("/", 1)[-1].lower() + "-"
tags = [t[len(prefix):] if t.lower().startswith(prefix) else t for t in tags]
return _highest(tags, current)
def latest_ghcr(repo: str, current: str = "") -> str:
"""Newest version-like tag on GitHub's container registry.
Some images exist only on ghcr.io (immich-app/postgres publishes there
and nowhere else), so neither the GitHub-release nor the Docker Hub
fetcher can see them. Anonymous pull token first, then the tag list —
the same handshake any `docker pull ghcr.io/...` performs.
"""
token = http_json(
f"https://ghcr.io/token?scope=repository:{repo}:pull&service=ghcr.io"
)["token"]
req = urllib.request.Request(
f"https://ghcr.io/v2/{repo}/tags/list",
headers={"User-Agent": USER_AGENT, "Authorization": f"Bearer {token}"},
)
with urllib.request.urlopen(req, timeout=TIMEOUT) as res: # noqa: S310
tags = [str(t) for t in json.loads(res.read().decode()).get("tags", [])]
return _highest(tags, current)
FETCHERS = {
"github": latest_github,
"dockerhub": latest_dockerhub,
"gitlab": latest_gitlab,
"ghcr": latest_ghcr,
}
# ── Manifest reading ─────────────────────────────────────────────────────── # ── Manifest reading ───────────────────────────────────────────────────────
+4 -4
View File
@@ -37,19 +37,19 @@ MEMPOOL_WEB_IMAGE="$ARCHY_REGISTRY/mempool-frontend:v3.3.1"
MARIADB_IMAGE="$ARCHY_REGISTRY/mariadb:11.4.10" MARIADB_IMAGE="$ARCHY_REGISTRY/mariadb:11.4.10"
# BTCPay # BTCPay
BTCPAY_IMAGE="docker.io/btcpayserver/btcpayserver:2.4.2" BTCPAY_IMAGE="docker.io/btcpayserver/btcpayserver:2.4.3"
NBXPLORER_IMAGE="$ARCHY_REGISTRY/nbxplorer:2.6.0" NBXPLORER_IMAGE="$ARCHY_REGISTRY/nbxplorer:2.6.11"
POSTGRES_IMAGE="$ARCHY_REGISTRY/postgres:15.17" POSTGRES_IMAGE="$ARCHY_REGISTRY/postgres:15.17"
BTCPAY_POSTGRES_IMAGE="$ARCHY_REGISTRY/postgres:15.17" BTCPAY_POSTGRES_IMAGE="$ARCHY_REGISTRY/postgres:15.17"
# Apps # Apps
HOMEASSISTANT_IMAGE="$ARCHY_REGISTRY/home-assistant:2026.8.2" HOMEASSISTANT_IMAGE="$ARCHY_REGISTRY/home-assistant:2026.8.3"
GRAFANA_IMAGE="$ARCHY_REGISTRY/grafana:10.2.0" GRAFANA_IMAGE="$ARCHY_REGISTRY/grafana:10.2.0"
UPTIME_KUMA_IMAGE="$ARCHY_REGISTRY/uptime-kuma:1" UPTIME_KUMA_IMAGE="$ARCHY_REGISTRY/uptime-kuma:1"
JELLYFIN_IMAGE="$ARCHY_REGISTRY/jellyfin:10.11.11" JELLYFIN_IMAGE="$ARCHY_REGISTRY/jellyfin:10.11.11"
PHOTOPRISM_IMAGE="$ARCHY_REGISTRY/photoprism:240915" PHOTOPRISM_IMAGE="$ARCHY_REGISTRY/photoprism:240915"
OLLAMA_IMAGE="$ARCHY_REGISTRY/ollama:latest" OLLAMA_IMAGE="$ARCHY_REGISTRY/ollama:latest"
VAULTWARDEN_IMAGE="$ARCHY_REGISTRY/vaultwarden:1.37.1-alpine" VAULTWARDEN_IMAGE="$ARCHY_REGISTRY/vaultwarden:1.37.2-alpine"
NEXTCLOUD_IMAGE="$ARCHY_REGISTRY/nextcloud:29" NEXTCLOUD_IMAGE="$ARCHY_REGISTRY/nextcloud:29"
SEARXNG_IMAGE="$ARCHY_REGISTRY/searxng:latest" SEARXNG_IMAGE="$ARCHY_REGISTRY/searxng:latest"
# OnlyOffice removed — incompatible with rootless Podman (internal postgres/rabbitmq fail) # OnlyOffice removed — incompatible with rootless Podman (internal postgres/rabbitmq fail)