chore: release v1.7.49-alpha
This commit is contained in:
@@ -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")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user