chore: release v1.7.49-alpha
This commit is contained in:
161
scripts/app-surface-smoke-test.sh
Executable file
161
scripts/app-surface-smoke-test.sh
Executable file
@@ -0,0 +1,161 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# App surface smoke test.
|
||||
#
|
||||
# Verifies that installed containers have their published host ports listening
|
||||
# and that known nginx app proxy paths return a non-5xx response. This catches
|
||||
# the common "container is running but UI disappeared" failure mode.
|
||||
#
|
||||
# Usage:
|
||||
# scripts/app-surface-smoke-test.sh --target archipelago@192.168.1.228 --ssh-key /path/key
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
TARGET=""
|
||||
SSH_KEY="${ARCHIPELAGO_SSH_KEY:-}"
|
||||
SSH_EXTRA=()
|
||||
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
--target) TARGET="${2:-}"; shift 2 ;;
|
||||
--ssh-key) SSH_KEY="${2:-}"; shift 2 ;;
|
||||
--ssh-option) SSH_EXTRA+=("-o" "${2:-}"); shift 2 ;;
|
||||
-h|--help) sed -n '1,12p' "$0"; exit 0 ;;
|
||||
*) echo "unknown argument: $1" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[ -n "$TARGET" ] || { echo "--target is required" >&2; exit 2; }
|
||||
|
||||
SSH_OPTS=(-F /dev/null -o BatchMode=yes -o PreferredAuthentications=publickey -o PasswordAuthentication=no)
|
||||
[ -n "$SSH_KEY" ] && SSH_OPTS+=(-i "$SSH_KEY")
|
||||
SSH_OPTS+=("${SSH_EXTRA[@]}")
|
||||
|
||||
ssh_run() {
|
||||
ssh "${SSH_OPTS[@]}" "$TARGET" "$@"
|
||||
}
|
||||
|
||||
ssh_run 'bash -s' <<'REMOTE'
|
||||
set -u
|
||||
|
||||
pass=0
|
||||
fail=0
|
||||
|
||||
ok() { echo " PASS $*"; pass=$((pass + 1)); }
|
||||
bad() { echo " FAIL $*"; fail=$((fail + 1)); }
|
||||
|
||||
container_exists() {
|
||||
podman ps -a --format '{{.Names}}' 2>/dev/null | grep -qx "$1"
|
||||
}
|
||||
|
||||
port_listening() {
|
||||
ss -ltn 2>/dev/null | awk '{print $4}' | grep -Eq "(^|:)$1$"
|
||||
}
|
||||
|
||||
http_code() {
|
||||
local url="$1" code
|
||||
for _ in 1 2 3; do
|
||||
code=$(curl -ksS -o /dev/null -w '%{http_code}' --max-time 12 "$url" 2>/dev/null || true)
|
||||
[ -n "$code" ] || code=000
|
||||
[ "$code" != "000" ] && { echo "$code"; return; }
|
||||
sleep 2
|
||||
done
|
||||
echo "$code"
|
||||
}
|
||||
|
||||
http_post_code() {
|
||||
local url="$1" code
|
||||
for _ in 1 2 3; do
|
||||
code=$(curl -ksS -o /dev/null -w '%{http_code}' --max-time 25 \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"jsonrpc":"2.0","id":1,"method":"getblockchaininfo","params":[]}' \
|
||||
"$url" 2>/dev/null || true)
|
||||
[ -n "$code" ] || code=000
|
||||
[ "$code" != "000" ] && { echo "$code"; return; }
|
||||
sleep 2
|
||||
done
|
||||
echo "$code"
|
||||
}
|
||||
|
||||
assert_http() {
|
||||
local label="$1" url="$2" code
|
||||
code=$(http_code "$url")
|
||||
case "$code" in
|
||||
200|204|301|302|307|308|401|403) ok "$label HTTP $code" ;;
|
||||
*) bad "$label HTTP $code ($url)" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
assert_http_post() {
|
||||
local label="$1" url="$2" code
|
||||
code=$(http_post_code "$url")
|
||||
case "$code" in
|
||||
200|204|401|403) ok "$label HTTP POST $code" ;;
|
||||
*) bad "$label HTTP POST $code ($url)" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
assert_container_ports() {
|
||||
local name="$1" ports port missing=0
|
||||
container_exists "$name" || return 0
|
||||
ports=$(podman inspect "$name" --format '{{range $p,$bindings := .NetworkSettings.Ports}}{{if $bindings}}{{range $bindings}}{{.HostPort}}{{"\n"}}{{end}}{{end}}{{end}}' 2>/dev/null | sort -u)
|
||||
[ -n "$ports" ] || return 0
|
||||
while IFS= read -r port; do
|
||||
[ -n "$port" ] || continue
|
||||
if port_listening "$port"; then
|
||||
ok "$name port $port listening"
|
||||
else
|
||||
bad "$name port $port missing listener"
|
||||
missing=1
|
||||
fi
|
||||
done <<< "$ports"
|
||||
return "$missing"
|
||||
}
|
||||
|
||||
assert_env_contains() {
|
||||
local name="$1" key="$2" needle="$3" val
|
||||
container_exists "$name" || return 0
|
||||
val=$(podman inspect "$name" --format '{{range .Config.Env}}{{println .}}{{end}}' 2>/dev/null | sed -n "s/^${key}=//p" | head -n 1)
|
||||
if [ -n "$val" ] && printf '%s' "$val" | grep -qF "$needle"; then
|
||||
ok "$name env $key"
|
||||
else
|
||||
bad "$name env $key missing $needle"
|
||||
fi
|
||||
}
|
||||
|
||||
echo "[surface] host=$(hostname) ip=$(hostname -I 2>/dev/null | awk '{print $1}')"
|
||||
|
||||
for c in $(podman ps -a --format '{{.Names}}' 2>/dev/null | sort); do
|
||||
assert_container_ports "$c" || true
|
||||
done
|
||||
|
||||
container_exists archy-bitcoin-ui && {
|
||||
assert_http "bitcoin-ui" "http://127.0.0.1/app/bitcoin-ui/"
|
||||
assert_http "bitcoin status" "http://127.0.0.1/app/bitcoin-ui/bitcoin-status"
|
||||
assert_http_post "bitcoin rpc proxy" "http://127.0.0.1/app/bitcoin-ui/bitcoin-rpc/"
|
||||
}
|
||||
|
||||
container_exists archy-electrs-ui && {
|
||||
assert_http "electrumx ui" "http://127.0.0.1/app/electrumx/"
|
||||
assert_http "electrumx status" "http://127.0.0.1/app/electrumx/electrs-status"
|
||||
assert_http "electrs legacy status" "http://127.0.0.1/app/electrs/electrs-status"
|
||||
}
|
||||
|
||||
container_exists mempool && assert_http "mempool ui" "http://127.0.0.1/app/mempool/"
|
||||
container_exists indeedhub && assert_http "indeedhub ui" "http://127.0.0.1:7778/"
|
||||
container_exists uptime-kuma && assert_http "uptime-kuma" "http://127.0.0.1/app/uptime-kuma/"
|
||||
container_exists filebrowser && assert_http "filebrowser" "http://127.0.0.1/app/filebrowser/"
|
||||
container_exists searxng && assert_http "searxng" "http://127.0.0.1/app/searxng/"
|
||||
container_exists grafana && assert_http "grafana" "http://127.0.0.1/app/grafana/"
|
||||
container_exists portainer && assert_http "portainer" "http://127.0.0.1/app/portainer/"
|
||||
container_exists vaultwarden && assert_http "vaultwarden" "http://127.0.0.1/app/vaultwarden/"
|
||||
container_exists nextcloud && assert_http "nextcloud" "http://127.0.0.1/app/nextcloud/"
|
||||
container_exists archy-nbxplorer && assert_env_contains "archy-nbxplorer" "NBXPLORER_POSTGRES" "Database=nbxplorer"
|
||||
container_exists btcpay-server && {
|
||||
assert_env_contains "btcpay-server" "BTCPAY_POSTGRES" "Database=btcpay"
|
||||
assert_http "btcpay" "http://127.0.0.1/app/btcpay/"
|
||||
}
|
||||
|
||||
echo "[surface] summary: pass=$pass fail=$fail"
|
||||
[ "$fail" -eq 0 ]
|
||||
REMOTE
|
||||
249
scripts/bitcoin-stack-lifecycle-test.sh
Executable file
249
scripts/bitcoin-stack-lifecycle-test.sh
Executable file
@@ -0,0 +1,249 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Bitcoin stack lifecycle test.
|
||||
#
|
||||
# Exercises the production Bitcoin stack under repeated stop/start and
|
||||
# remove/recreate cycles while asserting the actual user-facing surfaces:
|
||||
# Bitcoin RPC, bitcoin-ui /bitcoin-rpc, ElectrumX status, and electrs-ui.
|
||||
#
|
||||
# This intentionally removes containers but not data volumes. It is safe for
|
||||
# installed nodes, but it will briefly interrupt Bitcoin/ElectrumX service.
|
||||
#
|
||||
# Usage:
|
||||
# scripts/bitcoin-stack-lifecycle-test.sh --target archipelago@192.168.1.228
|
||||
# scripts/bitcoin-stack-lifecycle-test.sh --target archipelago@192.168.1.116 --cycles 5
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
TARGET=""
|
||||
SSH_KEY="${ARCHIPELAGO_SSH_KEY:-}"
|
||||
CYCLES=3
|
||||
SSH_EXTRA=()
|
||||
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
--target)
|
||||
TARGET="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--ssh-key)
|
||||
SSH_KEY="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--cycles)
|
||||
CYCLES="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--ssh-option)
|
||||
SSH_EXTRA+=("-o" "${2:-}")
|
||||
shift 2
|
||||
;;
|
||||
-h|--help)
|
||||
sed -n '1,22p' "$0"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "unknown argument: $1" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$TARGET" ]; then
|
||||
echo "--target is required, for example archipelago@192.168.1.228" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
SSH=(ssh -F /dev/null -o BatchMode=yes -o PreferredAuthentications=publickey -o PasswordAuthentication=no -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null)
|
||||
if [ -n "$SSH_KEY" ]; then
|
||||
SSH+=("-i" "$SSH_KEY")
|
||||
fi
|
||||
SSH+=("${SSH_EXTRA[@]}")
|
||||
|
||||
"${SSH[@]}" "$TARGET" "CYCLES='$CYCLES' bash -s" <<'REMOTE'
|
||||
set -euo pipefail
|
||||
|
||||
PODMAN="${PODMAN:-podman}"
|
||||
SCRIPTS_DIR="/opt/archipelago/scripts"
|
||||
if [ ! -x "$SCRIPTS_DIR/reconcile-containers.sh" ]; then
|
||||
SCRIPTS_DIR="$HOME/archy/scripts"
|
||||
fi
|
||||
RECONCILE="$SCRIPTS_DIR/reconcile-containers.sh"
|
||||
|
||||
pass_count=0
|
||||
fail_count=0
|
||||
|
||||
log() { printf '[%s] %s\n' "$(date +%H:%M:%S)" "$*"; }
|
||||
pass() { pass_count=$((pass_count + 1)); printf ' PASS %s\n' "$*"; }
|
||||
fail() { fail_count=$((fail_count + 1)); printf ' FAIL %s\n' "$*" >&2; }
|
||||
|
||||
retry() {
|
||||
local timeout="$1" label="$2"
|
||||
shift 2
|
||||
local end=$((SECONDS + timeout))
|
||||
local out rc
|
||||
while [ "$SECONDS" -lt "$end" ]; do
|
||||
set +e
|
||||
out=$("$@" 2>&1)
|
||||
rc=$?
|
||||
set -e
|
||||
if [ "$rc" -eq 0 ]; then
|
||||
pass "$label"
|
||||
return 0
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
fail "$label: $out"
|
||||
return 1
|
||||
}
|
||||
|
||||
rpc_pass() {
|
||||
cat /var/lib/archipelago/secrets/bitcoin-rpc-password
|
||||
}
|
||||
|
||||
json_rpc_reachable_or_warming() {
|
||||
local url="$1" auth_arg=() body rc
|
||||
if [ "${2:-}" = "auth" ]; then
|
||||
auth_arg=(--user "archipelago:$(rpc_pass)")
|
||||
fi
|
||||
set +e
|
||||
body=$(curl --connect-timeout 3 --max-time 20 -sS "${auth_arg[@]}" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data-binary '{"jsonrpc":"1.0","id":"lifecycle-test","method":"getblockchaininfo","params":[]}' \
|
||||
"$url" 2>&1)
|
||||
rc=$?
|
||||
set -e
|
||||
[ "$rc" -eq 0 ] || {
|
||||
echo "$body"
|
||||
return 1
|
||||
}
|
||||
echo "$body" | grep -q '"result"' && return 0
|
||||
echo "$body" | grep -q '"code":-28' && return 0
|
||||
echo "$body"
|
||||
return 1
|
||||
}
|
||||
|
||||
bitcoin_status_usable() {
|
||||
local url="$1"
|
||||
local body
|
||||
body=$(curl --connect-timeout 3 --max-time 20 -fsS "$url")
|
||||
echo "$body" | grep -q '"ok":\(true\|false\)' || {
|
||||
echo "$body"
|
||||
return 1
|
||||
}
|
||||
echo "$body" | grep -q '"blockchain_info"' || echo "$body" | grep -q '"error"'
|
||||
}
|
||||
|
||||
http_ok() {
|
||||
local url="$1"
|
||||
curl --connect-timeout 3 --max-time 20 -fsS -o /dev/null "$url"
|
||||
}
|
||||
|
||||
electrs_status_ok() {
|
||||
local url="${1:-http://127.0.0.1:50002/electrs-status}"
|
||||
local body
|
||||
body=$(curl --connect-timeout 3 --max-time 20 -fsS "$url")
|
||||
echo "$body" | grep -q '"network_height":[1-9]' || {
|
||||
echo "$body"
|
||||
return 1
|
||||
}
|
||||
echo "$body" | grep -q '"status":"\(indexing\|syncing\|synced\|waiting\)"'
|
||||
}
|
||||
|
||||
container_running() {
|
||||
local name="$1"
|
||||
[ "$($PODMAN inspect "$name" --format '{{.State.Status}}' 2>/dev/null || true)" = "running" ]
|
||||
}
|
||||
|
||||
container_healthy_or_starting() {
|
||||
local name="$1"
|
||||
local health
|
||||
health=$($PODMAN inspect "$name" --format '{{if .State.Health}}{{.State.Health.Status}}{{end}}' 2>/dev/null || true)
|
||||
[ "$health" = "healthy" ] || [ "$health" = "starting" ] || [ -z "$health" ]
|
||||
}
|
||||
|
||||
assert_bitcoin_stack() {
|
||||
retry 90 "bitcoin-knots running" container_running bitcoin-knots
|
||||
retry 90 "bitcoin-knots healthy/starting" container_healthy_or_starting bitcoin-knots
|
||||
retry 90 "host Bitcoin RPC reachable/ready" json_rpc_reachable_or_warming http://127.0.0.1:8332/ auth
|
||||
retry 90 "backend Bitcoin status bridge usable" bitcoin_status_usable http://127.0.0.1:5678/bitcoin-status
|
||||
retry 90 "bitcoin-ui page" http_ok http://127.0.0.1:8334/
|
||||
retry 90 "bitcoin-ui status bridge usable" bitcoin_status_usable http://127.0.0.1:8334/bitcoin-status
|
||||
retry 90 "bitcoin-ui app-session status bridge usable" bitcoin_status_usable http://127.0.0.1/app/bitcoin-ui/bitcoin-status
|
||||
retry 90 "bitcoin-ui RPC proxy reachable/ready" json_rpc_reachable_or_warming http://127.0.0.1:8334/bitcoin-rpc/
|
||||
retry 90 "bitcoin-ui app-session RPC proxy reachable/ready" json_rpc_reachable_or_warming http://127.0.0.1/app/bitcoin-ui/bitcoin-rpc/
|
||||
}
|
||||
|
||||
assert_electrum_stack() {
|
||||
retry 120 "electrumx running" container_running electrumx
|
||||
retry 120 "electrumx healthy/starting" container_healthy_or_starting electrumx
|
||||
retry 90 "electrs-ui page" http_ok http://127.0.0.1:50002/
|
||||
retry 120 "electrs status has network height" electrs_status_ok
|
||||
retry 120 "electrs app-session status has network height" electrs_status_ok http://127.0.0.1/app/electrumx/electrs-status
|
||||
retry 120 "electrs legacy app-session status has network height" electrs_status_ok http://127.0.0.1/app/electrs/electrs-status
|
||||
}
|
||||
|
||||
reconcile_one() {
|
||||
local name="$1"
|
||||
"$RECONCILE" --container="$name" --force --force-recreate --create-missing
|
||||
}
|
||||
|
||||
restart_container() {
|
||||
local name="$1"
|
||||
log "restart $name"
|
||||
$PODMAN restart "$name" >/dev/null || {
|
||||
log "podman restart failed for $name; using stop/start"
|
||||
$PODMAN stop "$name" >/dev/null 2>&1 || true
|
||||
sleep 3
|
||||
$PODMAN start "$name" >/dev/null
|
||||
}
|
||||
}
|
||||
|
||||
remove_and_reconcile() {
|
||||
local name="$1"
|
||||
log "remove/recreate $name"
|
||||
$PODMAN rm -f "$name" >/dev/null 2>&1 || true
|
||||
reconcile_one "$name"
|
||||
}
|
||||
|
||||
log "target $(hostname) cycles=$CYCLES"
|
||||
log "using reconciler: $RECONCILE"
|
||||
|
||||
assert_bitcoin_stack
|
||||
assert_electrum_stack
|
||||
|
||||
for i in $(seq 1 "$CYCLES"); do
|
||||
log "cycle $i/$CYCLES: bitcoin restart"
|
||||
restart_container bitcoin-knots
|
||||
assert_bitcoin_stack
|
||||
assert_electrum_stack
|
||||
|
||||
log "cycle $i/$CYCLES: bitcoin remove/reconcile"
|
||||
remove_and_reconcile bitcoin-knots
|
||||
assert_bitcoin_stack
|
||||
assert_electrum_stack
|
||||
|
||||
log "cycle $i/$CYCLES: bitcoin UI remove/reconcile"
|
||||
remove_and_reconcile archy-bitcoin-ui
|
||||
assert_bitcoin_stack
|
||||
|
||||
log "cycle $i/$CYCLES: electrumx restart"
|
||||
restart_container electrumx
|
||||
assert_electrum_stack
|
||||
|
||||
log "cycle $i/$CYCLES: electrumx remove/reconcile"
|
||||
remove_and_reconcile electrumx
|
||||
assert_electrum_stack
|
||||
|
||||
log "cycle $i/$CYCLES: electrs UI remove/reconcile"
|
||||
remove_and_reconcile archy-electrs-ui
|
||||
assert_electrum_stack
|
||||
done
|
||||
|
||||
log "final container state"
|
||||
$PODMAN ps -a --format 'table {{.Names}}\t{{.State}}\t{{.Status}}' \
|
||||
| grep -E 'bitcoin-knots|electrumx|archy-bitcoin-ui|archy-electrs-ui' || true
|
||||
|
||||
log "summary: pass=$pass_count fail=$fail_count"
|
||||
[ "$fail_count" -eq 0 ]
|
||||
REMOTE
|
||||
@@ -15,6 +15,7 @@
|
||||
# 6. Bitcoin Knots prune+txindex conflict
|
||||
# 7. Containers stuck with exit code 127 (binary not found)
|
||||
# 8. Stopped core containers (rootless restart policy workaround)
|
||||
# 9. Missing rootless port listeners while Podman still shows published ports
|
||||
#
|
||||
# Safe to run multiple times (idempotent). Never blocks deploy (exit 0 always).
|
||||
#
|
||||
@@ -31,6 +32,21 @@ FIX_NAMES=()
|
||||
|
||||
log() { echo "[$(date +%H:%M:%S)] DOCTOR: $*"; }
|
||||
|
||||
podman_rootless() {
|
||||
if [ "$(id -u)" = "0" ] && id archipelago >/dev/null 2>&1; then
|
||||
local archi_uid
|
||||
archi_uid=$(id -u archipelago)
|
||||
sudo -u archipelago env XDG_RUNTIME_DIR="/run/user/$archi_uid" podman "$@"
|
||||
else
|
||||
podman "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
port_is_listening() {
|
||||
local port="$1"
|
||||
ss -ltn 2>/dev/null | awk '{print $4}' | grep -Eq "(^|:)$port$"
|
||||
}
|
||||
|
||||
run_fix() {
|
||||
local name="$1"
|
||||
shift
|
||||
@@ -374,6 +390,11 @@ print(' '.join(['\"' + a + '\"' if ' ' in a else a for a in args[2:]]))
|
||||
# at 0 peers; package pulls fail. The only reliable repair is a stop-all/
|
||||
# start-all cycle so pasta + aardvark-dns rebuild the netns from scratch.
|
||||
fix_rootless_netns_egress() {
|
||||
# Needs root for nsenter. When doctor runs as the rootless container owner,
|
||||
# a failed nsenter probe is a permissions artifact, not evidence of broken
|
||||
# egress; do not cycle the fleet from that context.
|
||||
[ "$(id -u)" = "0" ] || return 1
|
||||
|
||||
local archi_uid
|
||||
archi_uid=$(id -u archipelago 2>/dev/null) || return 1
|
||||
|
||||
@@ -453,6 +474,44 @@ fix_stopped_core_containers() {
|
||||
[ ${#restarted[@]} -gt 0 ] && return 0 || return 1
|
||||
}
|
||||
|
||||
# ── Fix 10: Missing rootless port listeners ─────────────────
|
||||
# Rootless Podman can leave a container running with PortBindings still present
|
||||
# while the host-side rootlessport process has disappeared. Nginx then returns
|
||||
# 502 and direct app ports refuse connections even though `podman ps` looks OK.
|
||||
fix_missing_rootless_ports() {
|
||||
local containers
|
||||
containers=$(podman_rootless ps --format '{{.Names}}' 2>/dev/null || true)
|
||||
[ -n "$containers" ] || return 1
|
||||
|
||||
local fixed=false
|
||||
local name
|
||||
for name in $containers; do
|
||||
local ports
|
||||
ports=$(podman_rootless inspect "$name" --format '{{range $p,$bindings := .NetworkSettings.Ports}}{{if $bindings}}{{range $bindings}}{{.HostPort}}{{"\n"}}{{end}}{{end}}{{end}}' 2>/dev/null | sort -u)
|
||||
[ -n "$ports" ] || continue
|
||||
|
||||
local missing=()
|
||||
local port
|
||||
for port in $ports; do
|
||||
[ -n "$port" ] || continue
|
||||
if ! port_is_listening "$port"; then
|
||||
missing+=("$port")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ${#missing[@]} -gt 0 ]; then
|
||||
log "Restarting $name: missing rootlessport listener(s): ${missing[*]}"
|
||||
if podman_rootless restart "$name" >/dev/null 2>&1; then
|
||||
fixed=true
|
||||
else
|
||||
log "WARN: failed to restart $name for missing rootlessport listener(s)"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
$fixed && return 0 || return 1
|
||||
}
|
||||
|
||||
# ── Main ─────────────────────────────────────────────────────
|
||||
|
||||
# If remote host provided, run via SSH
|
||||
@@ -481,6 +540,7 @@ run_fix "bitcoin-txindex" fix_bitcoin_txindex
|
||||
run_fix "exit-127" fix_exit_127
|
||||
run_fix "netns-egress" fix_rootless_netns_egress
|
||||
run_fix "stopped-core" fix_stopped_core_containers
|
||||
run_fix "rootless-ports" fix_missing_rootless_ports
|
||||
|
||||
echo ""
|
||||
if [ $FIXES_APPLIED -gt 0 ]; then
|
||||
|
||||
@@ -252,7 +252,7 @@ load_spec_archy-nbxplorer() {
|
||||
SPEC_VOLUMES="/var/lib/archipelago/nbxplorer:/data"
|
||||
SPEC_MEMORY="$(mem_limit archy-nbxplorer)"
|
||||
SPEC_HEALTH_CMD="curl -sf http://localhost:32838/ || exit 1"
|
||||
SPEC_ENV="NBXPLORER_DATADIR=/data NBXPLORER_NETWORK=mainnet NBXPLORER_CHAINS=btc NBXPLORER_BIND=0.0.0.0:32838 NBXPLORER_BTCRPCURL=http://bitcoin-knots:8332 NBXPLORER_BTCRPCUSER=$BITCOIN_RPC_USER NBXPLORER_BTCRPCPASSWORD=$BITCOIN_RPC_PASS NBXPLORER_POSTGRES=User ID=btcpay;Password=$BTCPAY_DB_PASS;Host=archy-btcpay-db;Port=5432;Database=nbxplorer;Include Error Detail=true"
|
||||
SPEC_ENV="NBXPLORER_DATADIR=/data NBXPLORER_NETWORK=mainnet NBXPLORER_CHAINS=btc NBXPLORER_BIND=0.0.0.0:32838 NBXPLORER_BTCRPCURL=http://bitcoin-knots:8332 NBXPLORER_BTCRPCUSER=$BITCOIN_RPC_USER NBXPLORER_BTCRPCPASSWORD=$BITCOIN_RPC_PASS NBXPLORER_POSTGRES=Username=btcpay;Password=$BTCPAY_DB_PASS;Host=archy-btcpay-db;Port=5432;Database=nbxplorer"
|
||||
SPEC_TIER="2"
|
||||
SPEC_DATA_DIR="/var/lib/archipelago/nbxplorer"
|
||||
SPEC_DEPENDS="bitcoin-knots archy-btcpay-db"
|
||||
@@ -268,7 +268,7 @@ load_spec_btcpay-server() {
|
||||
SPEC_VOLUMES="/var/lib/archipelago/btcpay:/datadir"
|
||||
SPEC_MEMORY="$(mem_limit btcpay-server)"
|
||||
SPEC_HEALTH_CMD="curl -sf http://localhost:49392/ || exit 1"
|
||||
SPEC_ENV="ASPNETCORE_URLS=http://0.0.0.0:49392 BTCPAY_PROTOCOL=http BTCPAY_HOST=$HOST_IP:23000 BTCPAY_CHAINS=btc BTCPAY_BTCEXPLORERURL=http://archy-nbxplorer:32838 BTCPAY_BTCRPCURL=http://bitcoin-knots:8332 BTCPAY_BTCRPCUSER=$BITCOIN_RPC_USER BTCPAY_BTCRPCPASSWORD=$BITCOIN_RPC_PASS BTCPAY_POSTGRES=User ID=btcpay;Password=$BTCPAY_DB_PASS;Host=archy-btcpay-db;Port=5432;Database=btcpay;Include Error Detail=true"
|
||||
SPEC_ENV="ASPNETCORE_URLS=http://0.0.0.0:49392 BTCPAY_PROTOCOL=http BTCPAY_HOST=$HOST_IP:23000 BTCPAY_CHAINS=btc BTCPAY_BTCEXPLORERURL=http://archy-nbxplorer:32838 BTCPAY_BTCRPCURL=http://bitcoin-knots:8332 BTCPAY_BTCRPCUSER=$BITCOIN_RPC_USER BTCPAY_BTCRPCPASSWORD=$BITCOIN_RPC_PASS BTCPAY_POSTGRES=Username=btcpay;Password=$BTCPAY_DB_PASS;Host=archy-btcpay-db;Port=5432;Database=btcpay"
|
||||
SPEC_TIER="2"
|
||||
SPEC_DATA_DIR="/var/lib/archipelago/btcpay"
|
||||
SPEC_DEPENDS="archy-nbxplorer archy-btcpay-db"
|
||||
@@ -344,7 +344,7 @@ load_spec_homeassistant() {
|
||||
SPEC_ENV="TZ=UTC"
|
||||
SPEC_TIER="3"
|
||||
SPEC_DATA_DIR="/var/lib/archipelago/home-assistant"
|
||||
SPEC_CAPS="CHOWN SETUID SETGID DAC_OVERRIDE"
|
||||
SPEC_CAPS="CHOWN SETUID SETGID DAC_OVERRIDE NET_BIND_SERVICE"
|
||||
SPEC_OPTIONAL="true"
|
||||
}
|
||||
|
||||
@@ -362,7 +362,7 @@ load_spec_grafana() {
|
||||
SPEC_TIER="3"
|
||||
SPEC_DATA_DIR="/var/lib/archipelago/grafana"
|
||||
SPEC_DATA_UID="100472:100472"
|
||||
SPEC_CAPS="CHOWN SETUID SETGID DAC_OVERRIDE"
|
||||
SPEC_CAPS="CHOWN SETUID SETGID DAC_OVERRIDE NET_BIND_SERVICE"
|
||||
SPEC_OPTIONAL="true"
|
||||
}
|
||||
|
||||
@@ -370,7 +370,7 @@ load_spec_uptime-kuma() {
|
||||
reset_spec
|
||||
SPEC_NAME="uptime-kuma"
|
||||
SPEC_IMAGE="${UPTIME_KUMA_IMAGE}"
|
||||
SPEC_PORTS="3001:3001"
|
||||
SPEC_PORTS="3002:3001"
|
||||
SPEC_VOLUMES="/var/lib/archipelago/uptime-kuma:/app/data"
|
||||
SPEC_MEMORY="$(mem_limit uptime-kuma)"
|
||||
SPEC_HEALTH_CMD="curl -sf http://localhost:3001/ || exit 1"
|
||||
@@ -434,7 +434,7 @@ load_spec_nextcloud() {
|
||||
SPEC_HEALTH_CMD="curl -sf http://localhost:80/ || exit 1"
|
||||
SPEC_TIER="3"
|
||||
SPEC_DATA_DIR="/var/lib/archipelago/nextcloud"
|
||||
SPEC_CAPS="CHOWN SETUID SETGID DAC_OVERRIDE"
|
||||
SPEC_CAPS="CHOWN SETUID SETGID DAC_OVERRIDE NET_BIND_SERVICE"
|
||||
SPEC_OPTIONAL="true"
|
||||
}
|
||||
|
||||
@@ -539,6 +539,7 @@ load_spec_archy-bitcoin-ui() {
|
||||
SPEC_NAME="archy-bitcoin-ui"
|
||||
SPEC_IMAGE="localhost/bitcoin-ui:local"
|
||||
SPEC_NETWORK="host"
|
||||
SPEC_VOLUMES="/var/lib/archipelago/bitcoin-ui/nginx.conf:/etc/nginx/conf.d/default.conf:ro"
|
||||
SPEC_MEMORY="$(mem_limit archy-bitcoin-ui)"
|
||||
SPEC_TIER="4"
|
||||
SPEC_LOCAL_IMAGE="true"
|
||||
|
||||
@@ -183,6 +183,26 @@ location /app/electrs/ {
|
||||
proxy_hide_header X-Frame-Options;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
}
|
||||
location /app/electrumx/ {
|
||||
proxy_pass http://127.0.0.1:50002/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_hide_header X-Frame-Options;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
}
|
||||
location /app/electrs-ui/ {
|
||||
proxy_pass http://127.0.0.1:50002/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_hide_header X-Frame-Options;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
}
|
||||
location /app/nginx-proxy-manager/ {
|
||||
proxy_pass http://127.0.0.1:81/;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
# sudo ./reconcile-containers.sh # Fix everything
|
||||
# sudo ./reconcile-containers.sh --check-only # Audit only, no changes
|
||||
# sudo ./reconcile-containers.sh --force # Override user-stopped
|
||||
# sudo ./reconcile-containers.sh --force-recreate # Recreate matched containers
|
||||
# sudo ./reconcile-containers.sh --tier=2 # Only reconcile tier 2
|
||||
# sudo ./reconcile-containers.sh --container=lnd # Only reconcile lnd
|
||||
#
|
||||
@@ -18,6 +19,7 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
# ── Parse arguments ──────────────────────────────────────────────────
|
||||
CHECK_ONLY=false
|
||||
FORCE=false
|
||||
FORCE_RECREATE=false
|
||||
CREATE_MISSING=false
|
||||
FILTER_TIER=""
|
||||
FILTER_CONTAINER=""
|
||||
@@ -25,14 +27,18 @@ for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--check-only) CHECK_ONLY=true ;;
|
||||
--force) FORCE=true ;;
|
||||
--force-recreate) FORCE_RECREATE=true ;;
|
||||
--create-missing) CREATE_MISSING=true ;;
|
||||
--tier=*) FILTER_TIER="${arg#*=}" ;;
|
||||
--container=*) FILTER_CONTAINER="${arg#*=}" ;;
|
||||
-h|--help)
|
||||
echo "Usage: $0 [--check-only] [--force] [--create-missing] [--tier=N] [--container=NAME]"
|
||||
echo "Usage: $0 [--check-only] [--force] [--force-recreate] [--create-missing] [--tier=N] [--container=NAME]"
|
||||
echo ""
|
||||
echo " --check-only Audit only, no changes."
|
||||
echo " --force Override user-stopped state."
|
||||
echo " --force-recreate Recreate matched existing containers even if they"
|
||||
echo " otherwise match the spec. Use with --container or"
|
||||
echo " --tier for scoped image/config refreshes."
|
||||
echo " --create-missing Override SPEC_OPTIONAL for containers that have on-disk"
|
||||
echo " data but no live container (recovery from failed updates)."
|
||||
echo " --tier=N Only reconcile containers in tier N."
|
||||
@@ -110,6 +116,14 @@ container_image() {
|
||||
$PODMAN inspect "$1" --format '{{.ImageName}}' 2>/dev/null
|
||||
}
|
||||
|
||||
container_image_id() {
|
||||
$PODMAN inspect "$1" --format '{{.Image}}' 2>/dev/null
|
||||
}
|
||||
|
||||
spec_image_id() {
|
||||
$PODMAN image inspect "$SPEC_IMAGE" --format '{{.Id}}' 2>/dev/null
|
||||
}
|
||||
|
||||
container_network() {
|
||||
# Use actual Networks map — NetworkMode is unreliable (always shows 'bridge' in rootless)
|
||||
local nets
|
||||
@@ -122,6 +136,34 @@ container_memory() {
|
||||
$PODMAN inspect "$1" --format '{{.HostConfig.Memory}}' 2>/dev/null
|
||||
}
|
||||
|
||||
container_health_cmd() {
|
||||
$PODMAN inspect "$1" --format '{{with .Config.Healthcheck}}{{range .Test}}{{println .}}{{end}}{{end}}' 2>/dev/null \
|
||||
| awk 'NR > 1 { print }' \
|
||||
| paste -sd ' ' -
|
||||
}
|
||||
|
||||
normalize_health_cmd() {
|
||||
printf '%s' "$1" | sed 's/\\"/"/g; s/[[:space:]][[:space:]]*/ /g; s/^ //; s/ $//'
|
||||
}
|
||||
|
||||
host_port_listening() {
|
||||
local port="$1"
|
||||
ss -ltn 2>/dev/null | awk -v p=":$port" '
|
||||
$4 == p || $4 ~ p "$" { found=1 }
|
||||
END { exit found ? 0 : 1 }
|
||||
'
|
||||
}
|
||||
|
||||
container_has_mount() {
|
||||
local name="$1" source="$2" target="$3"
|
||||
$PODMAN inspect "$name" --format '{{range .Mounts}}{{println .Source "|" .Destination}}{{end}}' 2>/dev/null \
|
||||
| awk -F'|' -v src="$source" -v dst="$target" '
|
||||
{ gsub(/[[:space:]]+$/, "", $1); gsub(/^[[:space:]]+/, "", $2); }
|
||||
$1 == src && $2 == dst { found=1 }
|
||||
END { exit found ? 0 : 1 }
|
||||
'
|
||||
}
|
||||
|
||||
# Read one environment variable's current value from a running/stopped container.
|
||||
# Returns empty string if the var is not set.
|
||||
container_env_val() {
|
||||
@@ -153,6 +195,36 @@ image_exists() {
|
||||
echo "$images" | grep -qF "$1"
|
||||
}
|
||||
|
||||
resolve_spec_image() {
|
||||
image_exists "$SPEC_IMAGE" && return
|
||||
|
||||
local image_path image_name image_tag candidate repo
|
||||
image_path="${SPEC_IMAGE#*/}"
|
||||
image_name="${SPEC_IMAGE##*/}"
|
||||
image_tag="${image_name#*:}"
|
||||
image_name="${image_name%%:*}"
|
||||
|
||||
for candidate in \
|
||||
"${ARCHY_REGISTRY_FALLBACK:-}/${image_path}" \
|
||||
"80.71.235.15:3000/archipelago/${image_name}:${image_tag}" \
|
||||
"80.71.235.15:3000/lfg2025/${image_name}:${image_tag}"; do
|
||||
[ "$candidate" = "/" ] && continue
|
||||
if image_exists "$candidate"; then
|
||||
info "$SPEC_NAME — using local image alias $candidate"
|
||||
SPEC_IMAGE="$candidate"
|
||||
return
|
||||
fi
|
||||
done
|
||||
|
||||
repo=$($PODMAN images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null \
|
||||
| grep -E "/${image_name}:${image_tag}$" \
|
||||
| head -1 || true)
|
||||
if [ -n "$repo" ]; then
|
||||
info "$SPEC_NAME — using local image alias $repo"
|
||||
SPEC_IMAGE="$repo"
|
||||
fi
|
||||
}
|
||||
|
||||
# Convert memory string to bytes for comparison
|
||||
mem_to_bytes() {
|
||||
local m="$1"
|
||||
@@ -262,6 +334,10 @@ reconcile() {
|
||||
return
|
||||
fi
|
||||
|
||||
# Resolve registry aliases before create/recreate. ISOs and older installers
|
||||
# may seed the same image under a fallback registry tag.
|
||||
resolve_spec_image
|
||||
|
||||
# Local images: skip if image doesn't exist and container doesn't exist
|
||||
if [ "$SPEC_LOCAL_IMAGE" = "true" ]; then
|
||||
if ! image_exists "$SPEC_IMAGE" && ! container_exists "$name"; then
|
||||
@@ -284,14 +360,28 @@ reconcile() {
|
||||
local reasons=""
|
||||
|
||||
if container_exists "$name"; then
|
||||
local cur_image cur_network cur_memory
|
||||
local cur_image cur_image_id want_image_id cur_network cur_memory
|
||||
cur_image=$(container_image "$name")
|
||||
cur_image_id=$(container_image_id "$name")
|
||||
want_image_id=$(spec_image_id)
|
||||
cur_network=$(container_network "$name")
|
||||
cur_memory=$(container_memory "$name")
|
||||
local spec_memory_bytes expected_network
|
||||
|
||||
spec_memory_bytes=$(mem_to_bytes "$SPEC_MEMORY")
|
||||
|
||||
if [ "$FORCE_RECREATE" = "true" ]; then
|
||||
action="RECREATE"
|
||||
reasons+="force-recreate "
|
||||
fi
|
||||
|
||||
# Same-tag local rebuilds leave running containers on the old image ID.
|
||||
# Recreate when the currently tagged spec image points at a different ID.
|
||||
if [ "$action" = "OK" ] && [ -n "$want_image_id" ] && [ -n "$cur_image_id" ] && [ "$cur_image_id" != "$want_image_id" ]; then
|
||||
action="RECREATE"
|
||||
reasons+="image-id "
|
||||
fi
|
||||
|
||||
# Check network mismatch
|
||||
# For archy-net and host: exact match required
|
||||
# For bridge/default: accept any non-archy-net, non-host network
|
||||
@@ -319,6 +409,19 @@ reconcile() {
|
||||
reasons+="memory(none→$SPEC_MEMORY) "
|
||||
fi
|
||||
|
||||
# Healthcheck drift matters: a stale check can leave an otherwise working
|
||||
# service permanently unhealthy (for example ElectrumX images do not ship
|
||||
# curl, so the healthcheck must use python's socket module).
|
||||
if [ "$action" = "OK" ] && [ -n "$SPEC_HEALTH_CMD" ]; then
|
||||
local cur_health spec_health
|
||||
cur_health=$(normalize_health_cmd "$(container_health_cmd "$name")")
|
||||
spec_health=$(normalize_health_cmd "$SPEC_HEALTH_CMD")
|
||||
if [ "$cur_health" != "$spec_health" ]; then
|
||||
action="RECREATE"
|
||||
reasons+="healthcheck "
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check URL/HOST env drift — catches stale network topology baked into
|
||||
# container env (fedimint April-11 bug: FM_P2P_URL pointed at old IP).
|
||||
# Only checks URL-shaped keys; other env drift (passwords rotated, etc.)
|
||||
@@ -342,6 +445,40 @@ reconcile() {
|
||||
done
|
||||
fi
|
||||
|
||||
# Check bind mounts. This catches companion UIs recreated from older specs,
|
||||
# especially bitcoin-ui: its image intentionally does not bake nginx.conf,
|
||||
# so the rendered RPC proxy config must be mounted from the host.
|
||||
if [ "$action" = "OK" ] && [ -n "$SPEC_VOLUMES" ]; then
|
||||
for v in $SPEC_VOLUMES; do
|
||||
local mount_source mount_rest mount_target
|
||||
mount_source="${v%%:*}"
|
||||
mount_rest="${v#*:}"
|
||||
mount_target="${mount_rest%%:*}"
|
||||
[ -n "$mount_source" ] && [ -n "$mount_target" ] || continue
|
||||
if ! container_has_mount "$name" "$mount_source" "$mount_target"; then
|
||||
action="RECREATE"
|
||||
reasons+="mount($mount_target) "
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Rootless Podman can occasionally leave a container running while its
|
||||
# rootlessport listener is gone. The container still looks healthy in
|
||||
# `podman ps`, but host-network UIs and backend status probes fail against
|
||||
# 127.0.0.1. Treat missing host listeners as spec drift.
|
||||
if [ "$action" = "OK" ] && [ -n "$SPEC_PORTS" ]; then
|
||||
for p in $SPEC_PORTS; do
|
||||
local host_port="${p%%:*}"
|
||||
[ -n "$host_port" ] || continue
|
||||
if ! host_port_listening "$host_port"; then
|
||||
action="RECREATE"
|
||||
reasons+="port($host_port-not-listening) "
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Check if running
|
||||
if ! container_running "$name" && [ "$action" = "OK" ]; then
|
||||
action="START"
|
||||
@@ -476,7 +613,7 @@ ensure_secrets() {
|
||||
ensure_bitcoin_conf() {
|
||||
local BITCOIN_CONF="/var/lib/archipelago/bitcoin/bitcoin.conf"
|
||||
sudo mkdir -p /var/lib/archipelago/bitcoin 2>/dev/null
|
||||
if [ ! -f "$BITCOIN_CONF" ] || ! grep -q "^rpcauth=" "$BITCOIN_CONF" 2>/dev/null; then
|
||||
if [ ! -f "$BITCOIN_CONF" ] || ! sudo grep -q "^rpcauth=" "$BITCOIN_CONF" 2>/dev/null; then
|
||||
if ! $CHECK_ONLY && [ -n "$BITCOIN_RPC_PASS" ]; then
|
||||
local salt hash rpcauth
|
||||
salt=$(openssl rand -hex 16)
|
||||
@@ -491,10 +628,14 @@ BTCEOF
|
||||
info "Generated bitcoin.conf"
|
||||
fi
|
||||
fi
|
||||
# Strip duplicate server/rpc/listen lines from existing conf to avoid conflicts with custom args
|
||||
if [ -f "$BITCOIN_CONF" ]; then
|
||||
sudo sed -i '/^server=/d; /^rpcbind=/d; /^rpcallowip=/d; /^rpcport=/d; /^listen=/d' "$BITCOIN_CONF" 2>/dev/null
|
||||
fi
|
||||
# Strip duplicate server/rpc/listen lines from existing conf files to avoid
|
||||
# conflicts with custom args. Knots can persist runtime args in
|
||||
# bitcoin_rw.conf, so clean both files.
|
||||
for conf in "$BITCOIN_CONF" "/var/lib/archipelago/bitcoin/bitcoin_rw.conf"; do
|
||||
if [ -f "$conf" ]; then
|
||||
sudo sed -i '/^server=/d; /^txindex=/d; /^rpcbind=/d; /^rpcallowip=/d; /^rpcport=/d; /^listen=/d; /^bind=/d; /^dbcache=/d' "$conf" 2>/dev/null
|
||||
fi
|
||||
done
|
||||
sudo chown -R 100101:100101 /var/lib/archipelago/bitcoin 2>/dev/null
|
||||
}
|
||||
|
||||
@@ -531,6 +672,63 @@ LNDEOF
|
||||
fi
|
||||
}
|
||||
|
||||
# ── Ensure bitcoin-ui nginx.conf ────────────────────────────────────
|
||||
ensure_bitcoin_ui_nginx_conf() {
|
||||
local CONF_DIR="/var/lib/archipelago/bitcoin-ui"
|
||||
local CONF_PATH="$CONF_DIR/nginx.conf"
|
||||
[ -n "$BITCOIN_RPC_PASS" ] || return
|
||||
if $CHECK_ONLY; then
|
||||
[ -f "$CONF_PATH" ] || info "Would generate bitcoin-ui nginx.conf"
|
||||
return
|
||||
fi
|
||||
|
||||
local auth_b64 tmp
|
||||
auth_b64=$(printf '%s' "${BITCOIN_RPC_USER}:${BITCOIN_RPC_PASS}" | base64 | tr -d '\n')
|
||||
sudo mkdir -p "$CONF_DIR" 2>/dev/null
|
||||
tmp="${CONF_PATH}.tmp.$$"
|
||||
sudo tee "$tmp" >/dev/null << EOF
|
||||
server {
|
||||
listen 8334;
|
||||
server_name _;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
location /bitcoin-rpc/ {
|
||||
proxy_pass http://127.0.0.1:8332/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host \$host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header Authorization "Basic ${auth_b64}";
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
add_header Access-Control-Allow-Methods "POST, GET, OPTIONS";
|
||||
add_header Access-Control-Allow-Headers "Content-Type, Authorization";
|
||||
if (\$request_method = OPTIONS) { return 204; }
|
||||
}
|
||||
|
||||
location /bitcoin-status {
|
||||
proxy_pass http://127.0.0.1:5678/bitcoin-status;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host \$host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
add_header Cache-Control "no-store";
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files \$uri \$uri/ /index.html;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
if ! sudo cmp -s "$tmp" "$CONF_PATH" 2>/dev/null; then
|
||||
sudo mv "$tmp" "$CONF_PATH"
|
||||
sudo chmod 644 "$CONF_PATH"
|
||||
info "Generated bitcoin-ui nginx.conf"
|
||||
else
|
||||
sudo rm -f "$tmp"
|
||||
fi
|
||||
}
|
||||
|
||||
# ── Ensure BTCPay databases ─────────────────────────────────────────
|
||||
ensure_btcpay_db() {
|
||||
if container_running "archy-btcpay-db"; then
|
||||
@@ -548,8 +746,10 @@ START_TIME=$(date +%s)
|
||||
|
||||
header "Phase 0: Prerequisites"
|
||||
ensure_secrets
|
||||
detect_environment
|
||||
ensure_bitcoin_conf
|
||||
ensure_lnd_conf
|
||||
ensure_bitcoin_ui_nginx_conf
|
||||
|
||||
TIER_NAMES=("Databases" "Core Infrastructure" "Services" "Applications" "Frontend UIs")
|
||||
|
||||
|
||||
@@ -136,7 +136,7 @@ expected_containers_for() {
|
||||
ui_proxy_path_for() {
|
||||
case "$1" in
|
||||
bitcoin-knots|bitcoin-core) echo "/app/bitcoin-ui/" ;;
|
||||
electrumx|electrs) echo "/app/electrs-ui/" ;;
|
||||
electrumx|electrs) echo "/app/electrumx/" ;;
|
||||
lnd) echo "/app/lnd-ui/" ;;
|
||||
btcpay-server) echo "/app/btcpay/" ;;
|
||||
*) echo "/app/$1/" ;;
|
||||
|
||||
@@ -186,7 +186,7 @@ fi
|
||||
# for backward compatibility with older binaries that still look there.
|
||||
SCRIPTS_DEST="/opt/archipelago/scripts"
|
||||
sudo mkdir -p "$SCRIPTS_DEST"
|
||||
for script in image-versions.sh reconcile-containers.sh container-specs.sh; do
|
||||
for script in image-versions.sh reconcile-containers.sh container-specs.sh container-doctor.sh app-surface-smoke-test.sh bitcoin-stack-lifecycle-test.sh; do
|
||||
src="$REPO_DIR/scripts/$script"
|
||||
if [ -f "$src" ]; then
|
||||
sudo install -m 755 "$src" "$SCRIPTS_DEST/$script"
|
||||
@@ -299,6 +299,25 @@ if [ -f "$REPO_DIR/image-recipe/configs/archipelago.service" ]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
# Keep the doctor timer/service current too. Container uptime fixes rely on
|
||||
# these units as much as on the helper scripts themselves.
|
||||
DOCTOR_UNITS_CHANGED=false
|
||||
for unit in archipelago-doctor.service archipelago-doctor.timer; do
|
||||
src="$REPO_DIR/image-recipe/configs/$unit"
|
||||
dst="/etc/systemd/system/$unit"
|
||||
[ -f "$src" ] || continue
|
||||
if [ ! -f "$dst" ] || ! diff -q "$src" "$dst" &>/dev/null; then
|
||||
sudo install -m 644 "$src" "$dst"
|
||||
DOCTOR_UNITS_CHANGED=true
|
||||
ok "Updated $unit"
|
||||
fi
|
||||
done
|
||||
if [ "$DOCTOR_UNITS_CHANGED" = "true" ]; then
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now archipelago-doctor.timer 2>>"$LOG_FILE" || \
|
||||
warn "Failed to enable archipelago-doctor.timer"
|
||||
fi
|
||||
|
||||
# Install/refresh tmpfiles.d rules. The logs rule creates
|
||||
# /var/log/archipelago/ + container-installs.log with archipelago:archipelago
|
||||
# ownership so the non-root backend can append install audit lines.
|
||||
|
||||
Reference in New Issue
Block a user