Demo images / Build & push demo images (push) Failing after 2m22s
Replaces the registry host across 86 files: 309 references, covering all 40 app manifests, the orchestrator and container crates, the release and catalog scripts, both demo-images workflows, the ISO builder, demo-deploy, and the frontend marketplace data. Verified the domain actually serves the registry before rewriting anything, rather than assuming the web host implies the registry: - TLS verifies clean, HTTP/2 on the web root - an anonymous token grants a manifest fetch (HTTP 200) with no credentials - skopeo inspect --no-creds resolves an image and lists its tags That last check is the one that matters: an outside developer with no account can now pull, which was the functional blocker for publishing at all. Plain-HTTP references become HTTPS in the same pass, so OTA downloads stop crossing the network in the clear. Deliberately NOT rewritten: - The public FIPS anchor on port 8444. It is a functional network endpoint every node dials to bootstrap the mesh — closer to Bitcoin Core's hardcoded seeds than to leaked infrastructure. The domain does resolve to the same host, so it could become a hostname, but that adds a DNS dependency to the path used precisely when things are broken. Worth a deliberate decision, not a side effect of this change. - The companion APK on port 2100. The domain returns 404 for that path, so rewriting it would swap a working URL for a broken one. The Releases page does serve (200), which is where the plan already wants those binaries. - releases/app-catalog.json, releases/manifest.json and release-manifest.json. These carry `signature` and `signed_by`; editing their contents invalidates the signature and the fleet refuses artifacts that fail verification. They were rewritten in a first pass and reverted — they must be regenerated and re-signed through the signing ceremony instead, which needs the mnemonic. So the catalog still advertises the old host until that ceremony runs. Nodes resolve images through the signed catalog, not the on-disk manifests, so this commit alone does not change what a node pulls. Verified: archipelago-container 75/75; every manifest still parses with a top-level app block; no signed artifact modified. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
127 lines
6.0 KiB
Bash
127 lines
6.0 KiB
Bash
#!/usr/bin/env bats
|
||
# tests/lifecycle/bats/immich.bats
|
||
#
|
||
# Lifecycle tests for the manifest-driven immich stack. The user-facing package is
|
||
# "immich" (catalog title + icon); container-list reports it package-level as
|
||
# "immich". Its containers are named immich_server / immich_postgres /
|
||
# immich_redis (underscore) to match the runtime's per-app lifecycle references.
|
||
#
|
||
# Tiers:
|
||
# - Read-only (always): presence + valid state
|
||
# - Destructive (ARCHY_ALLOW_DESTRUCTIVE=1): stop → start → restart
|
||
# - Cascade (ARCHY_ALLOW_CASCADE_DESTRUCTIVE=1): uninstall → reinstall (preserve_data)
|
||
#
|
||
# RPC-based, so correct whether run on the host or against a remote ARCHY_HOST.
|
||
|
||
load '../lib/rpc.bash'
|
||
|
||
IMMICH_IMAGE="source.archipelago-foundation.org/lfg2025/immich-server:release"
|
||
|
||
setup_file() {
|
||
: "${ARCHY_PASSWORD:?Set ARCHY_PASSWORD env var to the UI password}"
|
||
export ARCHY_FORCE_LOGIN=1
|
||
rpc_login
|
||
unset ARCHY_FORCE_LOGIN
|
||
}
|
||
|
||
teardown_file() {
|
||
rpc_logout_local
|
||
}
|
||
|
||
# ────────────────────────────────────────────────────────────────────
|
||
# Read-only tier
|
||
# ────────────────────────────────────────────────────────────────────
|
||
|
||
@test "container-list includes immich" {
|
||
run rpc_result container-list
|
||
[ "$status" -eq 0 ]
|
||
echo "$output" | jq -e '.[] | select(.name == "immich")' >/dev/null
|
||
}
|
||
|
||
@test "container-list reports a valid state for immich" {
|
||
run rpc_result container-list
|
||
[ "$status" -eq 0 ]
|
||
local state
|
||
state=$(echo "$output" | jq -r '.[] | select(.name == "immich") | .state')
|
||
[[ "$state" =~ ^(running|stopped|exited|created|paused)$ ]]
|
||
}
|
||
|
||
@test "immich exposes its web UI lan-address (port 2283)" {
|
||
# Poll briefly: lan_address is derived from the published host port, which is
|
||
# momentarily absent (null) while immich_server is mid-recreate (e.g. a
|
||
# health-monitor bounce during the read-only tier). A genuinely unexposed
|
||
# immich never publishes 2283, so this still catches real port drift; it only
|
||
# absorbs the transient null seen under churn.
|
||
# 90s (not 30s): the immich stack (postgres→redis→server with DB migrations on
|
||
# boot) can take >30s to publish its host port after a churn-induced recreate,
|
||
# and the destructive-tier immich tests already allow 180–240s for the same
|
||
# stack. A genuinely unexposed immich still never publishes 2283, so this keeps
|
||
# catching real port drift while tolerating slow-but-healthy boots.
|
||
local deadline=$(( $(date +%s) + 90 ))
|
||
while (( $(date +%s) < deadline )); do
|
||
run rpc_result container-list
|
||
[ "$status" -eq 0 ]
|
||
if echo "$output" \
|
||
| jq -e '.[] | select(.name == "immich") | .lan_address // "" | test("2283")' >/dev/null; then
|
||
return 0
|
||
fi
|
||
sleep 3
|
||
done
|
||
echo "immich never reported a lan_address containing 2283 within 90s" >&2
|
||
return 1
|
||
}
|
||
|
||
# ────────────────────────────────────────────────────────────────────
|
||
# Destructive tier (stop → start → restart)
|
||
# ────────────────────────────────────────────────────────────────────
|
||
|
||
@test "package.stop transitions immich to stopped" {
|
||
[[ "${ARCHY_ALLOW_DESTRUCTIVE:-0}" == "1" ]] || skip "ARCHY_ALLOW_DESTRUCTIVE not set"
|
||
# package.stop is async ({"status":"stopping"}) and a stack stop can race a
|
||
# still-settling prior op, so the end state — not the immediate RPC return — is
|
||
# the assertion.
|
||
rpc_call package.stop '{"id":"immich"}' >/dev/null 2>&1 || true
|
||
run wait_for_container_status immich stopped 90
|
||
[ "$status" -eq 0 ]
|
||
}
|
||
|
||
@test "package.start brings immich back to running" {
|
||
[[ "${ARCHY_ALLOW_DESTRUCTIVE:-0}" == "1" ]] || skip "ARCHY_ALLOW_DESTRUCTIVE not set"
|
||
# Async start; the server comes up only after postgres is ready (~30s+), so wait.
|
||
rpc_call package.start '{"id":"immich"}' >/dev/null 2>&1 || true
|
||
run wait_for_container_status immich running 180
|
||
[ "$status" -eq 0 ]
|
||
}
|
||
|
||
@test "package.restart leaves immich in running state" {
|
||
[[ "${ARCHY_ALLOW_DESTRUCTIVE:-0}" == "1" ]] || skip "ARCHY_ALLOW_DESTRUCTIVE not set"
|
||
run rpc_result package.restart '{"id":"immich"}'
|
||
[ "$status" -eq 0 ]
|
||
# Restart = ordered stop+start of the whole 3-container stack (postgres→redis→
|
||
# server, with the server doing DB-readiness + migrations on boot), so it needs
|
||
# at least as long as `start` (180s) — more, since it stops first. The old 120s
|
||
# was inconsistent with the start test and false-failed on heavily-loaded nodes.
|
||
run wait_for_container_status immich running 240
|
||
[ "$status" -eq 0 ]
|
||
}
|
||
|
||
# ────────────────────────────────────────────────────────────────────
|
||
# Cascade tier (uninstall + reinstall the stack)
|
||
# ────────────────────────────────────────────────────────────────────
|
||
|
||
@test "package.uninstall removes immich (data preserved)" {
|
||
[[ "${ARCHY_ALLOW_CASCADE_DESTRUCTIVE:-0}" == "1" ]] || skip "ARCHY_ALLOW_CASCADE_DESTRUCTIVE not set"
|
||
run rpc_result package.uninstall '{"id":"immich","preserve_data":true}'
|
||
[ "$status" -eq 0 ]
|
||
run wait_for_container_status immich absent 120
|
||
[ "$status" -eq 0 ]
|
||
}
|
||
|
||
@test "package.install immich returns to running" {
|
||
[[ "${ARCHY_ALLOW_CASCADE_DESTRUCTIVE:-0}" == "1" ]] || skip "ARCHY_ALLOW_CASCADE_DESTRUCTIVE not set"
|
||
run rpc_result package.install "{\"id\":\"immich\",\"dockerImage\":\"${IMMICH_IMAGE}\"}"
|
||
[ "$status" -eq 0 ]
|
||
run wait_for_container_status immich running 180
|
||
[ "$status" -eq 0 ]
|
||
}
|