bug fixing and deploy and build diagnostics

This commit is contained in:
Dorian
2026-03-22 03:30:21 +00:00
parent 01942cea95
commit 13e4a738be
198 changed files with 21703 additions and 19587 deletions

View File

@@ -38,7 +38,7 @@ BUILD_DIR="/home/archipelago/archy"
# Node registry
TAILSCALE_NODES=(
"archipelago@${TAILSCALE_ARCH1:-100.82.97.63}"
"archipelago@archipelago-2.tail2b6225.ts.net"
"archipelago@${TAILSCALE_ARCH2:-100.122.84.60}"
"archipelago@${TAILSCALE_ARCH3:-100.124.105.113}"
)
TAILSCALE_NAMES=("Arch 1" "Arch 2" "Arch 3")
@@ -107,18 +107,27 @@ deploy_node() {
echo " Copy-only (cargo=$HAS_CARGO, npm=$HAS_NPM) — will copy from $BUILD_SOURCE"
fi
# ── Step 4: Rootful→rootless migration ───────────────────────────
# ── Step 4: Rootful→rootless migration (one-time) ────────────────
step "Checking for rootful containers (migration)"
ssh $SSH_OPTS "$TARGET" '
# Count rootful containers (run as sudo = rootful podman)
ROOTFUL=$(podman ps -a --format "{{.Names}}" 2>/dev/null | grep -v "^$" | wc -l)
ROOTLESS=$(podman ps -a --format "{{.Names}}" 2>/dev/null | grep -v "^$" | wc -l)
echo " Rootful: $ROOTFUL containers, Rootless: $ROOTLESS containers"
if [ "$ROOTFUL" -gt 0 ]; then
echo " MIGRATING: Stopping $ROOTFUL rootful containers..."
podman stop --all --timeout 30 2>/dev/null || true
podman rm --all --force 2>/dev/null || true
echo " Rootful containers removed (data preserved in /var/lib/archipelago/)"
MIGRATION_FLAG="/var/lib/archipelago/.rootless-migrated"
if [ -f "$MIGRATION_FLAG" ]; then
ROOTLESS=$(podman ps -a --format "{{.Names}}" 2>/dev/null | grep -v "^$" | wc -l)
echo " Already migrated ($ROOTLESS rootless containers)"
else
# Check if rootful podman has any containers (sudo = rootful context)
ROOTFUL=$(sudo podman ps -a --format "{{.Names}}" 2>/dev/null | grep -v "^$" | wc -l)
ROOTLESS=$(podman ps -a --format "{{.Names}}" 2>/dev/null | grep -v "^$" | wc -l)
echo " Rootful: $ROOTFUL, Rootless: $ROOTLESS"
if [ "$ROOTFUL" -gt 0 ] && [ "$ROOTFUL" != "$ROOTLESS" ]; then
echo " MIGRATING: Stopping $ROOTFUL rootful containers..."
sudo podman stop --all --timeout 30 2>/dev/null || true
sudo podman rm --all --force 2>/dev/null || true
echo " Rootful containers removed (data preserved in /var/lib/archipelago/)"
else
echo " No rootful containers to migrate"
fi
sudo touch "$MIGRATION_FLAG"
fi
' 2>&1
@@ -166,12 +175,17 @@ deploy_node() {
# Transfer custom UI images (individual tarballs — never combined)
echo " Transferring custom UI images..."
for ui_img in bitcoin-ui lnd-ui electrs-ui; do
HAS_IMG=$(ssh $SSH_OPTS "$BUILD_SOURCE" "podman images --format '{{.Repository}}' 2>/dev/null | grep -q '$ui_img' && echo yes || echo no" 2>/dev/null)
HAS_IMG=$(ssh $SSH_OPTS "$BUILD_SOURCE" "podman images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null | grep -q '${ui_img}:' && echo yes || echo no" 2>/dev/null)
if [ "$HAS_IMG" = "yes" ]; then
echo " $ui_img..."
ssh $SSH_OPTS "$BUILD_SOURCE" "podman save 'localhost/${ui_img}:local'" > "/tmp/${ui_img}.tar"
ssh $SSH_OPTS "$TARGET" "podman load" < "/tmp/${ui_img}.tar" 2>&1 | tail -1
if ssh $SSH_OPTS "$BUILD_SOURCE" "podman save 'localhost/${ui_img}:local' 2>/dev/null" > "/tmp/${ui_img}.tar" 2>/dev/null && [ -s "/tmp/${ui_img}.tar" ]; then
ssh $SSH_OPTS "$TARGET" "podman load" < "/tmp/${ui_img}.tar" 2>&1 | tail -1
else
echo " $ui_img: not available on build server, skipping"
fi
rm -f "/tmp/${ui_img}.tar"
else
echo " $ui_img: not found on build server, skipping"
fi
done
@@ -221,7 +235,7 @@ deploy_node() {
AIUI_DIST="$PROJECT_DIR/../AIUI/packages/app/dist"
if [ -d "$AIUI_DIST" ] && [ -f "$AIUI_DIST/index.html" ]; then
ssh $SSH_OPTS "$TARGET" "sudo mkdir -p /opt/archipelago/web-ui/aiui && sudo rm -rf /opt/archipelago/web-ui/aiui/*"
(cd "$AIUI_DIST" && tar cf - .) | ssh $SSH_OPTS "$TARGET" "sudo tar xf - -C /opt/archipelago/web-ui/aiui/"
(cd "$AIUI_DIST" && tar --no-xattrs -cf - .) | ssh $SSH_OPTS "$TARGET" "sudo tar xf - -C /opt/archipelago/web-ui/aiui/ 2>/dev/null"
ssh $SSH_OPTS "$TARGET" "sudo chown -R 1000:1000 /opt/archipelago/web-ui/aiui"
echo " AIUI deployed."
else
@@ -304,20 +318,23 @@ deploy_node() {
sudo mkdir -p /var/lib/archipelago/dwn/messages /var/lib/archipelago/dwn/protocols
sudo mkdir -p /var/lib/archipelago/content/files /var/lib/archipelago/federation
sudo mkdir -p /var/lib/archipelago/identities /var/lib/archipelago/tor-config
sudo mkdir -p /var/lib/archipelago/searxng /var/lib/archipelago/vaultwarden
sudo mkdir -p /var/lib/archipelago/photoprism /var/lib/archipelago/filebrowser
sudo mkdir -p /var/lib/archipelago/nextcloud
sudo chown -R archipelago:archipelago /var/lib/archipelago/dwn /var/lib/archipelago/content \
/var/lib/archipelago/federation /var/lib/archipelago/identities /var/lib/archipelago/tor-config 2>/dev/null || true
echo " Fixing rootless podman UID mapping..."
# Containers running as root (UID 0 → host UID 100000)
for dir in lnd electrumx btcpay nbxplorer immich jellyfin vaultwarden \
for dir in lnd electrumx btcpay nbxplorer jellyfin vaultwarden \
home-assistant fedimint fedimint-gateway photoprism ollama filebrowser \
nextcloud uptime-kuma onlyoffice nginx-proxy-manager portainer nostr-rs-relay; do
nextcloud uptime-kuma onlyoffice nginx-proxy-manager portainer nostr-rs-relay searxng; do
[ -d "/var/lib/archipelago/$dir" ] && sudo chown -R 100000:100000 "/var/lib/archipelago/$dir" 2>/dev/null
done
# Bitcoin Knots: container UID 101 → host UID 100101
[ -d /var/lib/archipelago/bitcoin ] && sudo chown -R 100101:100101 /var/lib/archipelago/bitcoin 2>/dev/null
# Postgres: container UID 70 → host UID 100070
for dir in postgres-btcpay immich-db penpot-postgres; do
for dir in postgres-btcpay; do
[ -d "/var/lib/archipelago/$dir" ] && sudo chown -R 100070:100070 "/var/lib/archipelago/$dir" 2>/dev/null
done
# MariaDB: container UID 999 → host UID 100999
@@ -413,7 +430,7 @@ deploy_node() {
DB_PASSWORDS=$(ssh $SSH_OPTS "$TARGET" '
SECRETS_DIR="/var/lib/archipelago/secrets"
for svc in mempool btcpay immich penpot mysql-root; do
for svc in mempool btcpay mysql-root; do
if [ ! -f "$SECRETS_DIR/${svc}-db-password" ]; then
openssl rand -base64 24 | sudo tee "$SECRETS_DIR/${svc}-db-password" > /dev/null
sudo chmod 600 "$SECRETS_DIR/${svc}-db-password"
@@ -421,8 +438,6 @@ deploy_node() {
done
echo "MEMPOOL_DB_PASS=$(sudo cat "$SECRETS_DIR/mempool-db-password")"
echo "BTCPAY_DB_PASS=$(sudo cat "$SECRETS_DIR/btcpay-db-password")"
echo "IMMICH_DB_PASS=$(sudo cat "$SECRETS_DIR/immich-db-password")"
echo "PENPOT_DB_PASS=$(sudo cat "$SECRETS_DIR/penpot-db-password")"
echo "MYSQL_ROOT_PASS=$(sudo cat "$SECRETS_DIR/mysql-root-db-password")"
# Fedimint gateway
if [ ! -f "$SECRETS_DIR/fedimint-gateway-password" ]; then
@@ -485,7 +500,7 @@ deploy_node() {
--security-opt no-new-privileges:true \
-p 8332:8332 -p 8333:8333 \
-v /var/lib/archipelago/bitcoin:/home/bitcoin/.bitcoin \
${BITCOIN_KNOTS_IMAGE:-docker.io/bitcoinknots/bitcoin:v28.1} \
$BITCOIN_KNOTS_IMAGE \
-server=1 \$BTC_EXTRA_ARGS \
-rpcallowip=0.0.0.0/0 -rpcbind=0.0.0.0:8332 \
-rpcuser=$BITCOIN_RPC_USER -rpcpassword=$BITCOIN_RPC_PASS \
@@ -503,7 +518,7 @@ deploy_node() {
-v /var/lib/archipelago/mysql-mempool:/var/lib/mysql \
-e MYSQL_DATABASE=mempool -e MYSQL_USER=mempool \
-e MYSQL_PASSWORD=$MEMPOOL_DB_PASS -e MYSQL_ROOT_PASSWORD=$MYSQL_ROOT_PASS \
docker.io/mariadb:10.11
$MARIADB_IMAGE
sleep 3
fi
MYSQL_CNT=\$(\$DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -E 'mysql-mempool|archy-mempool-db' | head -1)
@@ -521,7 +536,7 @@ deploy_node() {
-e DAEMON_URL=http://$BITCOIN_RPC_USER:$BITCOIN_RPC_PASS@bitcoin-knots:8332/ \
-e COIN=Bitcoin -e DB_DIRECTORY=/data \
-e SERVICES=tcp://:50001,rpc://0.0.0.0:8000 \
docker.io/lukechilds/electrumx:v1.18.0
$ELECTRUMX_IMAGE
fi
fi
@@ -535,24 +550,32 @@ deploy_node() {
-e CORE_RPC_USERNAME=archipelago -e CORE_RPC_PASSWORD=$BITCOIN_RPC_PASS \
-e DATABASE_ENABLED=true -e DATABASE_HOST=\$MYSQL_CNT -e DATABASE_DATABASE=mempool \
-e DATABASE_USERNAME=mempool -e DATABASE_PASSWORD=$MEMPOOL_DB_PASS \
docker.io/mempool/backend:v2.5.0
$MEMPOOL_BACKEND_IMAGE
fi
if ! \$DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q archy-mempool-web; then
echo ' Creating mempool frontend...'
\$DOCKER run -d --name archy-mempool-web --restart unless-stopped \$NET_OPT \
-p 4080:8080 -e FRONTEND_HTTP_PORT=8080 -e BACKEND_MAINNET_HTTP_HOST=mempool-api \
docker.io/mempool/frontend:v2.5.0
$MEMPOOL_WEB_IMAGE
fi
echo ' === BTCPay Stack ==='
# Recreate btcpay-db if postgres version mismatch (15→16 incompatible)
if \$DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -qE 'archy-btcpay-db|postgres-btcpay'; then
if ! \$DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qE 'archy-btcpay-db|postgres-btcpay'; then
echo ' Recreating archy-btcpay-db (was stopped/broken)...'
\$DOCKER rm -f archy-btcpay-db 2>/dev/null
\$DOCKER rm -f postgres-btcpay 2>/dev/null
fi
fi
if ! \$DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -qE 'archy-btcpay-db|postgres-btcpay'; then
echo ' Creating archy-btcpay-db...'
sudo mkdir -p /var/lib/archipelago/postgres-btcpay
\$DOCKER run -d --name archy-btcpay-db --restart unless-stopped \$NET_OPT \
-v /var/lib/archipelago/postgres-btcpay:/var/lib/postgresql/data \
-e POSTGRES_DB=btcpay -e POSTGRES_USER=btcpay -e POSTGRES_PASSWORD=$BTCPAY_DB_PASS \
docker.io/postgres:15-alpine
$BTCPAY_POSTGRES_IMAGE
sleep 3
fi
\$DOCKER exec archy-btcpay-db psql -U postgres -tc \"SELECT 1 FROM pg_database WHERE datname='nbxplorer'\" 2>/dev/null | grep -q 1 || \
@@ -570,7 +593,7 @@ deploy_node() {
-e NBXPLORER_BIND=0.0.0.0:32838 -e NBXPLORER_BTCRPCURL=http://bitcoin-knots:8332 \
-e NBXPLORER_BTCRPCUSER=archipelago -e NBXPLORER_BTCRPCPASSWORD=$BITCOIN_RPC_PASS \
-e NBXPLORER_POSTGRES='User ID=btcpay;Password=$BTCPAY_DB_PASS;Host=archy-btcpay-db;Port=5432;Database=nbxplorer;Include Error Detail=true' \
docker.io/nicolasdorier/nbxplorer:2.6.0
$NBXPLORER_IMAGE
sleep 5
fi
fi
@@ -588,18 +611,20 @@ deploy_node() {
-e BTCPAY_BTCRPCURL=http://bitcoin-knots:8332 \
-e BTCPAY_BTCRPCUSER=archipelago -e BTCPAY_BTCRPCPASSWORD=$BITCOIN_RPC_PASS \
-e BTCPAY_POSTGRES='User ID=btcpay;Password=$BTCPAY_DB_PASS;Host=archy-btcpay-db;Port=5432;Database=btcpay;Include Error Detail=true' \
docker.io/btcpayserver/btcpayserver:1.13.5
$BTCPAY_IMAGE
fi
echo ' === LND ==='
# Always update LND config with current RPC credentials
sudo mkdir -p /var/lib/archipelago/lnd
if ! \$DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qx lnd; then
if \$DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -qx lnd; then
\$DOCKER start lnd 2>/dev/null || true
else
echo ' Creating LND...'
sudo mkdir -p /var/lib/archipelago/lnd
if [ ! -f /var/lib/archipelago/lnd/lnd.conf ]; then
cat > /tmp/lnd.conf <<'LNDCONF'
# Always write/update lnd.conf with current RPC credentials
RPC_PASS=\$(sudo cat /var/lib/archipelago/secrets/bitcoin-rpc-password 2>/dev/null)
cat > /tmp/lnd.conf <<LNDCONF
[Application Options]
listen=0.0.0.0:9735
rpclisten=0.0.0.0:10009
@@ -615,27 +640,36 @@ bitcoin.node=bitcoind
[Bitcoind]
bitcoind.rpchost=bitcoin-knots:8332
bitcoind.rpcuser=archipelago
bitcoind.rpcpass=$BITCOIN_RPC_PASS
bitcoind.rpcpass=\$RPC_PASS
bitcoind.rpcpolling=true
bitcoind.estimatemode=ECONOMICAL
[autopilot]
autopilot.active=false
LNDCONF
sudo cp /tmp/lnd.conf /var/lib/archipelago/lnd/lnd.conf
rm -f /tmp/lnd.conf
fi
sudo cp /tmp/lnd.conf /var/lib/archipelago/lnd/lnd.conf
sudo chown 100000:100000 /var/lib/archipelago/lnd/lnd.conf 2>/dev/null
rm -f /tmp/lnd.conf
\$DOCKER run -d --name lnd --restart unless-stopped --network archy-net \
--cap-drop ALL --cap-add CHOWN --cap-add FOWNER --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
--security-opt no-new-privileges:true \
-p 9735:9735 -p 10009:10009 -p 8080:8080 \
-v /var/lib/archipelago/lnd:/root/.lnd \
docker.io/lightninglabs/lnd:v0.18.4-beta
$LND_IMAGE
fi
fi
echo ' === Fedimint ==='
if ! \$DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qx fedimint; then
# Recreate fedimint if it exists but is broken (wrong env vars)
if \$DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -qx fedimint; then
if ! \$DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qx fedimint; then
echo ' Recreating fedimint (was stopped/broken)...'
\$DOCKER rm -f fedimint 2>/dev/null
else
echo ' Fedimint already running'
fi
fi
if ! \$DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -qx fedimint; then
echo ' Creating Fedimint...'
sudo mkdir -p /var/lib/archipelago/fedimint
\$DOCKER run -d --name fedimint --restart unless-stopped \$NET_OPT \
@@ -648,12 +682,21 @@ LNDCONF
-e FM_BIND_API=0.0.0.0:8174 -e FM_BIND_UI=0.0.0.0:8175 \
-e FM_P2P_URL=fedimint://\$TARGET_IP:8173 -e FM_API_URL=ws://\$TARGET_IP:8174 \
-e FM_BITCOIND_URL=http://\$TARGET_IP:8332 \
docker.io/fedimint/fedimintd:v0.10.0
-e FM_REQ_RELEASE_NOTES_ACK_V0_4=true \
$FEDIMINT_IMAGE
fi
if ! \$DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q fedimint-gateway; then
# Recreate fedimint-gateway if broken
if \$DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -q fedimint-gateway; then
if ! \$DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q fedimint-gateway; then
echo ' Recreating fedimint-gateway (was stopped/broken)...'
\$DOCKER rm -f fedimint-gateway 2>/dev/null
fi
fi
if ! \$DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -q fedimint-gateway; then
echo ' Creating fedimint-gateway...'
sudo mkdir -p /var/lib/archipelago/fedimint-gateway
FEDI_PASS=\$(sudo cat /var/lib/archipelago/secrets/fedimint-gateway-password 2>/dev/null || echo 'archipelago')
LND_CERT=/var/lib/archipelago/lnd/tls.cert
LND_MACAROON=/var/lib/archipelago/lnd/data/chain/bitcoin/mainnet/admin.macaroon
if \$DOCKER ps --format '{{.Names}}' | grep -q '^lnd\$' && sudo test -f \$LND_CERT && sudo test -f \$LND_MACAROON; then
@@ -663,9 +706,9 @@ LNDCONF
-p 8176:8176 -v /var/lib/archipelago/fedimint-gateway:/data \
-v /var/lib/archipelago/lnd/tls.cert:/lnd/tls.cert:ro \
-v /var/lib/archipelago/lnd/data/chain/bitcoin/mainnet/admin.macaroon:/lnd/admin.macaroon:ro \
docker.io/fedimint/gatewayd:v0.10.0 \
$FEDIMINT_GATEWAY_IMAGE \
gatewayd --data-dir /data --listen 0.0.0.0:8176 \
--bcrypt-password-hash '$FEDI_HASH' \
--password \"\$FEDI_PASS\" \
--network bitcoin --bitcoind-url http://\$TARGET_IP:8332 \
--bitcoind-username $BITCOIN_RPC_USER --bitcoind-password $BITCOIN_RPC_PASS \
lnd --lnd-rpc-host \$TARGET_IP:10009 --lnd-tls-cert /lnd/tls.cert --lnd-macaroon /lnd/admin.macaroon
@@ -674,42 +717,15 @@ LNDCONF
--cap-drop ALL --cap-add CHOWN --cap-add FOWNER --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
--security-opt no-new-privileges:true \
-p 8176:8176 -p 9737:9737 -v /var/lib/archipelago/fedimint-gateway:/data \
docker.io/fedimint/gatewayd:v0.10.0 \
$FEDIMINT_GATEWAY_IMAGE \
gatewayd --data-dir /data --listen 0.0.0.0:8176 \
--bcrypt-password-hash '$FEDI_HASH' \
--password \"\$FEDI_PASS\" \
--network bitcoin --bitcoind-url http://\$TARGET_IP:8332 \
--bitcoind-username $BITCOIN_RPC_USER --bitcoind-password $BITCOIN_RPC_PASS \
ldk --ldk-lightning-port 9737 --ldk-alias archipelago-gateway
fi
fi
echo ' === Immich ==='
if ! \$DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q immich_server; then
echo ' Creating Immich stack...'
sudo mkdir -p /var/lib/archipelago/immich /var/lib/archipelago/immich-db
\$DOCKER network create immich-net 2>/dev/null || true
if ! \$DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -q immich_postgres; then
\$DOCKER run -d --name immich_postgres --restart unless-stopped --network immich-net \
-v /var/lib/archipelago/immich-db:/var/lib/postgresql/data \
-e POSTGRES_PASSWORD=$IMMICH_DB_PASS -e POSTGRES_USER=postgres -e POSTGRES_DB=immich \
ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0 2>/dev/null || true
sleep 5
fi
if ! \$DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q immich_redis; then
\$DOCKER run -d --name immich_redis --restart unless-stopped --network immich-net \
docker.io/valkey/valkey:7-alpine 2>/dev/null || true
sleep 2
fi
if ! \$DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q immich_server; then
\$DOCKER run -d --name immich_server --restart unless-stopped --network immich-net \
-p 2283:2283 -v /var/lib/archipelago/immich:/usr/src/app/upload \
-e DB_HOSTNAME=immich_postgres -e DB_USERNAME=postgres -e DB_PASSWORD=$IMMICH_DB_PASS \
-e DB_DATABASE_NAME=immich -e REDIS_HOSTNAME=immich_redis \
-e UPLOAD_LOCATION=/usr/src/app/upload \
ghcr.io/immich-app/immich-server:release 2>/dev/null || true
fi
fi
echo ' === Simple apps ==='
# Home Assistant
if ! \$DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qx homeassistant; then
@@ -721,7 +737,7 @@ LNDCONF
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
--security-opt no-new-privileges:true \
-p 8123:8123 -v /var/lib/archipelago/home-assistant:/config -e TZ=UTC \
docker.io/homeassistant/home-assistant:2024.1
$HOMEASSISTANT_IMAGE
fi
fi
# Grafana
@@ -741,7 +757,7 @@ LNDCONF
--user 0:0 \
-p 3000:3000 -v /var/lib/archipelago/grafana:/var/lib/grafana \
-e GF_PATHS_DATA=/var/lib/grafana -e GF_USERS_ALLOW_SIGN_UP=false \
docker.io/grafana/grafana:10.2.0
$GRAFANA_IMAGE
fi
fi
# Jellyfin
@@ -755,68 +771,84 @@ LNDCONF
-p 8096:8096 \
-v /var/lib/archipelago/jellyfin/config:/config \
-v /var/lib/archipelago/jellyfin/cache:/cache \
docker.io/jellyfin/jellyfin:10.8.13
$JELLYFIN_IMAGE
fi
fi
# Vaultwarden
if ! \$DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qx vaultwarden; then
if \$DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -qx vaultwarden; then
\$DOCKER start vaultwarden 2>/dev/null || true
else
sudo mkdir -p /var/lib/archipelago/vaultwarden
\$DOCKER run -d --name vaultwarden --restart unless-stopped \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID --cap-add NET_BIND_SERVICE \
--security-opt no-new-privileges:true \
-p 8082:80 -v /var/lib/archipelago/vaultwarden:/data \
docker.io/vaultwarden/server:1.30.0-alpine
# Vaultwarden — recreate if broken (permissions/DB)
if \$DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -qx vaultwarden; then
if ! \$DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qx vaultwarden; then
\$DOCKER rm -f vaultwarden 2>/dev/null
fi
fi
# SearXNG
if ! \$DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qx searxng; then
if \$DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -qx searxng; then
\$DOCKER start searxng 2>/dev/null || true
else
\$DOCKER run -d --name searxng --restart unless-stopped \
--cap-drop ALL --security-opt no-new-privileges:true \
-p 8888:8080 ${SEARXNG_IMAGE:-docker.io/searxng/searxng:2024.11.17}
if ! \$DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -qx vaultwarden; then
sudo mkdir -p /var/lib/archipelago/vaultwarden
sudo chown -R 100000:100000 /var/lib/archipelago/vaultwarden 2>/dev/null
\$DOCKER run -d --name vaultwarden --restart unless-stopped \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID --cap-add NET_BIND_SERVICE \
--security-opt no-new-privileges:true \
-p 8082:80 -v /var/lib/archipelago/vaultwarden:/data \
$VAULTWARDEN_IMAGE
fi
# SearXNG — recreate if broken (permission denied on settings.yml)
if \$DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -qx searxng; then
if ! \$DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qx searxng; then
\$DOCKER rm -f searxng 2>/dev/null
fi
fi
# FileBrowser
if ! \$DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qx filebrowser; then
if ! \$DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -qx searxng; then
sudo mkdir -p /var/lib/archipelago/searxng
\$DOCKER run -d --name searxng --restart unless-stopped \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
--security-opt no-new-privileges:true \
-v /var/lib/archipelago/searxng:/etc/searxng \
-p 8888:8080 $SEARXNG_IMAGE
fi
# FileBrowser — recreate if broken (permission denied on :80)
if \$DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -qx filebrowser; then
if ! \$DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qx filebrowser; then
\$DOCKER rm -f filebrowser 2>/dev/null
fi
fi
if ! \$DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -qx filebrowser; then
sudo mkdir -p /var/lib/archipelago/filebrowser
\$DOCKER run -d --name filebrowser --restart=always \
--cap-add NET_BIND_SERVICE \
-p 8083:80 -v /var/lib/archipelago/filebrowser:/srv \
docker.io/filebrowser/filebrowser:v2.27.0
$FILEBROWSER_IMAGE
fi
echo ' === Additional apps ==='
# Nextcloud
if ! \$DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qx nextcloud; then
if \$DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -qx nextcloud; then
\$DOCKER start nextcloud 2>/dev/null || true
else
sudo mkdir -p /var/lib/archipelago/nextcloud
\$DOCKER run -d --name nextcloud --restart unless-stopped \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
--security-opt no-new-privileges:true \
-p 8085:80 -v /var/lib/archipelago/nextcloud:/var/www/html \
docker.io/library/nextcloud:28
# Nextcloud — recreate if wrong image version (28→30 not supported, need 29)
if \$DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -qx nextcloud; then
if ! \$DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qx nextcloud; then
echo ' Recreating nextcloud (was stopped/broken)...'
\$DOCKER rm -f nextcloud 2>/dev/null
fi
fi
# PhotoPrism
if ! \$DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qx photoprism; then
if \$DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -qx photoprism; then
\$DOCKER start photoprism 2>/dev/null || true
else
sudo mkdir -p /var/lib/archipelago/photoprism
\$DOCKER run -d --name photoprism --restart unless-stopped \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID \
--security-opt no-new-privileges:true \
-p 2342:2342 -v /var/lib/archipelago/photoprism:/photoprism/storage \
-e PHOTOPRISM_ADMIN_PASSWORD=archipelago -e PHOTOPRISM_DEFAULT_LOCALE=en \
${PHOTOPRISM_IMAGE:-docker.io/photoprism/photoprism:240915}
if ! \$DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -qx nextcloud; then
sudo mkdir -p /var/lib/archipelago/nextcloud
\$DOCKER run -d --name nextcloud --restart unless-stopped \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
--security-opt no-new-privileges:true \
-p 8085:80 -v /var/lib/archipelago/nextcloud:/var/www/html \
$NEXTCLOUD_IMAGE
fi
# PhotoPrism — recreate if broken (permissions)
if \$DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -qx photoprism; then
if ! \$DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qx photoprism; then
\$DOCKER rm -f photoprism 2>/dev/null
fi
fi
if ! \$DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -qx photoprism; then
sudo mkdir -p /var/lib/archipelago/photoprism
sudo chown -R 100000:100000 /var/lib/archipelago/photoprism 2>/dev/null
\$DOCKER run -d --name photoprism --restart unless-stopped \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID \
--security-opt no-new-privileges:true \
-p 2342:2342 -v /var/lib/archipelago/photoprism:/photoprism/storage \
-e PHOTOPRISM_ADMIN_PASSWORD=archipelago -e PHOTOPRISM_DEFAULT_LOCALE=en \
$PHOTOPRISM_IMAGE
fi
# OnlyOffice
if ! \$DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qx onlyoffice; then
if \$DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -qx onlyoffice; then
@@ -825,7 +857,7 @@ LNDCONF
\$DOCKER run -d --name onlyoffice --restart unless-stopped \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
--security-opt no-new-privileges:true \
-p 9980:80 docker.io/onlyoffice/documentserver:7.5.1
-p 9980:80 $ONLYOFFICE_IMAGE
fi
fi
# Nginx Proxy Manager
@@ -840,7 +872,7 @@ LNDCONF
-p 81:81 -p 8084:80 -p 8443:443 \
-v /var/lib/archipelago/nginx-proxy-manager/data:/data \
-v /var/lib/archipelago/nginx-proxy-manager/letsencrypt:/etc/letsencrypt \
${NPM_IMAGE:-docker.io/jc21/nginx-proxy-manager:2}
$NPM_IMAGE
fi
fi
# Portainer
@@ -854,58 +886,9 @@ LNDCONF
--security-opt no-new-privileges:true \
-p 9000:9000 -v /var/lib/archipelago/portainer:/data \
-v /run/user/1000/podman/podman.sock:/var/run/docker.sock \
docker.io/portainer/portainer-ce:2.19.4
$PORTAINER_IMAGE
fi
fi
# Penpot stack (postgres + valkey + backend + exporter + frontend)
if ! \$DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q penpot-frontend; then
echo ' Creating Penpot stack...'
sudo mkdir -p /var/lib/archipelago/penpot-assets /var/lib/archipelago/penpot-postgres
\$DOCKER network create penpot-net 2>/dev/null || true
if ! \$DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -q penpot-postgres; then
\$DOCKER run -d --name penpot-postgres --restart unless-stopped --network penpot-net \
-v /var/lib/archipelago/penpot-postgres:/var/lib/postgresql/data \
-e POSTGRES_DB=penpot -e POSTGRES_USER=penpot -e POSTGRES_PASSWORD=$PENPOT_DB_PASS \
docker.io/postgres:15
sleep 5
fi
if ! \$DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q penpot-valkey; then
\$DOCKER run -d --name penpot-valkey --restart unless-stopped --network penpot-net \
-e VALKEY_EXTRA_FLAGS='--maxmemory 128mb --maxmemory-policy volatile-lfu' \
docker.io/valkey/valkey:8.1
sleep 3
fi
if ! \$DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q penpot-backend; then
\$DOCKER run -d --name penpot-backend --restart unless-stopped --network penpot-net \
-v /var/lib/archipelago/penpot-assets:/opt/data/assets \
-e PENPOT_PUBLIC_URI=http://\${TARGET_IP}:9001 \
-e PENPOT_SECRET_KEY=archipelago-penpot-secret-key-change-in-production \
-e PENPOT_DATABASE_URI=postgresql://penpot-postgres/penpot \
-e PENPOT_DATABASE_USERNAME=penpot -e PENPOT_DATABASE_PASSWORD=$PENPOT_DB_PASS \
-e PENPOT_REDIS_URI=redis://penpot-valkey/0 \
-e PENPOT_OBJECTS_STORAGE_BACKEND=fs \
-e PENPOT_OBJECTS_STORAGE_FS_DIRECTORY=/opt/data/assets \
-e PENPOT_FLAGS='disable-email-verification enable-smtp enable-prepl-server disable-secure-session-cookies' \
${PENPOT_BACKEND_IMAGE:-docker.io/penpotapp/backend:2.4.2}
sleep 5
fi
if ! \$DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q penpot-exporter; then
\$DOCKER run -d --name penpot-exporter --restart unless-stopped --network penpot-net \
-e PENPOT_SECRET_KEY=archipelago-penpot-secret-key-change-in-production \
-e PENPOT_PUBLIC_URI=http://penpot-frontend:8080 \
-e PENPOT_REDIS_URI=redis://penpot-valkey/0 \
${PENPOT_EXPORTER_IMAGE:-docker.io/penpotapp/exporter:2.4.2}
sleep 2
fi
if ! \$DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q penpot-frontend; then
\$DOCKER run -d --name penpot-frontend --restart unless-stopped --network penpot-net \
-p 9001:8080 -v /var/lib/archipelago/penpot-assets:/opt/data/assets \
-e PENPOT_PUBLIC_URI=http://\${TARGET_IP}:9001 \
-e PENPOT_FLAGS='disable-email-verification enable-smtp enable-prepl-server disable-secure-session-cookies' \
${PENPOT_FRONTEND_IMAGE:-docker.io/penpotapp/frontend:2.4.2}
fi
fi
echo ' === Custom UI containers ==='
# Build custom UI containers if source exists
for ui in bitcoin-ui lnd-ui electrs-ui; do
@@ -955,13 +938,26 @@ LNDCONF
echo \" Total containers running: \$TOTAL\"
" 2>&1 | sed 's/^/ /'
# ── Step 23: Tor ─────────────────────────────────────────────
# ── Step 23: Tor (robust setup) ──────────────────────────────
step "Setting up Tor"
ssh $SSH_OPTS "$TARGET" "
ssh $SSH_OPTS "$TARGET" '
sudo mkdir -p /var/lib/archipelago/tor
# Install Tor if missing
if ! command -v tor >/dev/null 2>&1; then
echo " Installing Tor..."
sudo apt-get update -qq && sudo apt-get install -y -qq tor 2>/dev/null
fi
if ! command -v tor >/dev/null 2>&1; then
echo " ERROR: Tor installation failed"
exit 0
fi
# Write services.json
SERVICES_JSON=/var/lib/archipelago/tor/services.json
if [ ! -f \"\$SERVICES_JSON\" ]; then
sudo python3 -c '
if [ ! -f "$SERVICES_JSON" ]; then
sudo python3 -c "
import json
services = [
{\"name\": \"archipelago\", \"local_port\": 80, \"enabled\": True},
@@ -974,21 +970,29 @@ services = [
]
with open(\"/var/lib/archipelago/tor/services.json\", \"w\") as f:
json.dump({\"services\": services}, f, indent=2)
'
"
fi
if command -v tor >/dev/null 2>&1; then
sudo systemctl enable tor 2>/dev/null
sudo systemctl restart tor@default 2>/dev/null
echo ' System Tor running'
# Enable + start Tor service (try both unit names)
sudo systemctl enable tor 2>/dev/null || true
sudo systemctl enable tor@default 2>/dev/null || true
# Restart Tor — try tor@default first (Debian pattern), fallback to tor
if sudo systemctl restart tor@default 2>/dev/null; then
echo " Tor running (tor@default)"
elif sudo systemctl restart tor 2>/dev/null; then
echo " Tor running (tor)"
else
sudo apt-get update -qq && sudo apt-get install -y -qq tor 2>/dev/null || true
if command -v tor >/dev/null 2>&1; then
sudo systemctl enable tor 2>/dev/null
sudo systemctl restart tor@default 2>/dev/null
echo ' Tor installed and started'
fi
echo " WARNING: Tor failed to start — check journalctl -u tor"
fi
" 2>&1 | tail -5 | sed 's/^/ /' || true
# Verify Tor is actually running
if systemctl is-active tor@default >/dev/null 2>&1 || systemctl is-active tor >/dev/null 2>&1; then
echo " Tor verified active"
else
echo " WARNING: Tor not active after restart attempt"
fi
' 2>&1 | sed 's/^/ /'
fi
# ── Step 24: UFW forward policy ──────────────────────────────────
@@ -1041,6 +1045,40 @@ with open(\"/var/lib/archipelago/tor/services.json\", \"w\") as f:
step "Running container doctor"
"$SCRIPT_DIR/container-doctor.sh" "$TARGET" 2>&1 | tail -10 | sed 's/^/ /' || true
# ── Step 26b: Restart stopped containers + verify health ──────
step "Verifying all containers running"
ssh $SSH_OPTS "$TARGET" '
DOCKER=podman; command -v podman >/dev/null 2>&1 || DOCKER=docker
# Fix permissions before restart attempts (rootless UID mapping)
for dir in vaultwarden photoprism nextcloud filebrowser searxng; do
[ -d "/var/lib/archipelago/$dir" ] && sudo chown -R 100000:100000 "/var/lib/archipelago/$dir" 2>/dev/null
done
# Restart any exited containers (unless user-stopped)
USER_STOPPED="/var/lib/archipelago/user-stopped.json"
for ctr in $($DOCKER ps -a --filter "status=exited" --format "{{.Names}}" 2>/dev/null); do
if [ -f "$USER_STOPPED" ] && grep -q "\"$ctr\"" "$USER_STOPPED" 2>/dev/null; then
continue
fi
echo " Restarting exited container: $ctr"
$DOCKER start "$ctr" 2>/dev/null || echo " WARNING: Failed to start $ctr"
done
# Summary
RUNNING=$($DOCKER ps --format "{{.Names}}" 2>/dev/null | wc -l)
EXITED=$($DOCKER ps -a --filter "status=exited" --format "{{.Names}}" 2>/dev/null | wc -l)
echo " Containers: $RUNNING running, $EXITED exited"
# Verify Tor is still active
if systemctl is-active tor@default >/dev/null 2>&1 || systemctl is-active tor >/dev/null 2>&1; then
echo " Tor: active"
else
echo " Tor: NOT RUNNING — attempting restart..."
sudo systemctl restart tor@default 2>/dev/null || sudo systemctl restart tor 2>/dev/null || echo " Tor restart failed"
fi
' 2>&1 | sed 's/^/ /'
# ── Step 27: Deploy manifest ─────────────────────────────────────
step "Writing deploy manifest"
DEPLOY_TS=$(date -u +%Y-%m-%dT%H:%M:%SZ)

View File

@@ -54,6 +54,7 @@ DRY_RUN=false
CANARY=false
TAILSCALE=false
TAILSCALE_NODE=""
FLEET=false
for arg in "$@"; do
case $arg in
--quick) QUICK=true ;;
@@ -65,9 +66,30 @@ for arg in "$@"; do
--canary) CANARY=true ;;
--tailscale) TAILSCALE=true ;;
--tailscale-node=*) TAILSCALE_NODE="${arg#*=}" ;;
--fleet) FLEET=true ;;
esac
done
# Fleet deploy: .228 → .198 → all 3 Tailscale nodes (all 5 servers)
if [ "$FLEET" = true ]; then
echo "╔════════════════════════════════════════════════════════════════╗"
echo "║ FLEET DEPLOY — All 5 nodes (.228, .198, Arch 1/2/3) ║"
echo "╚════════════════════════════════════════════════════════════════╝"
echo ""
echo "Phase 1: Build + deploy to .228 (primary build server)"
"$0" --live || { echo "FAILED: .228 deploy"; exit 1; }
echo ""
echo "Phase 2: Copy to .198 (LAN secondary)"
"$0" --both || { echo "WARNING: .198 deploy failed (continuing)"; }
echo ""
echo "Phase 3: Deploy to all Tailscale nodes (Arch 1/2/3)"
"$SCRIPT_DIR/deploy-tailscale.sh" --all || { echo "WARNING: Some Tailscale nodes failed"; }
echo ""
echo "════════════════════════════════════════════════════════════════"
echo "Fleet deploy complete."
exit 0
fi
# Tailscale deploy: delegate to deploy-tailscale.sh
if [ "$TAILSCALE" = true ]; then
echo "Deploying to all Tailscale nodes..."
@@ -300,6 +322,8 @@ fi
# When --both: deploy to 228 first, then copy to 198
if [ "$BOTH" = true ]; then
echo "Deploying to both servers (228, then 198)..."
# Release lock so the recursive --live call can acquire it
rm -rf "$LOCK_DIR" 2>/dev/null; trap - EXIT
"$0" --live
echo ""
echo "📤 Copying to 192.168.1.198 (no rsync/cargo on that node)..."

View File

@@ -125,7 +125,7 @@ BTCCONF
fi
# Generate per-installation database passwords if not already saved
for svc in mempool btcpay immich penpot mysql-root; do
for svc in mempool btcpay mysql-root; do
if [ ! -f "$SECRETS_DIR/${svc}-db-password" ]; then
openssl rand -base64 24 > "$SECRETS_DIR/${svc}-db-password"
chmod 600 "$SECRETS_DIR/${svc}-db-password"
@@ -133,8 +133,6 @@ for svc in mempool btcpay immich penpot mysql-root; do
done
MEMPOOL_DB_PASS=$(cat "$SECRETS_DIR/mempool-db-password")
BTCPAY_DB_PASS=$(cat "$SECRETS_DIR/btcpay-db-password")
IMMICH_DB_PASS=$(cat "$SECRETS_DIR/immich-db-password")
PENPOT_DB_PASS=$(cat "$SECRETS_DIR/penpot-db-password")
MYSQL_ROOT_PASS=$(cat "$SECRETS_DIR/mysql-root-db-password")
# Generate Fedimint gateway password and bcrypt hash
@@ -236,7 +234,7 @@ $DOCKER network create archy-net 2>/dev/null || true
# → host UID 100000+N). Must run BEFORE container creation.
log "Fixing rootless podman UID mapping..."
# Containers running as root (UID 0 → host UID 100000)
for dir in lnd electrumx btcpay nbxplorer immich jellyfin vaultwarden \
for dir in lnd electrumx btcpay nbxplorer jellyfin vaultwarden \
home-assistant fedimint fedimint-gateway photoprism ollama filebrowser \
nextcloud uptime-kuma onlyoffice nginx-proxy-manager portainer nostr-rs-relay; do
[ -d "/var/lib/archipelago/$dir" ] && chown -R 100000:100000 "/var/lib/archipelago/$dir" 2>/dev/null
@@ -244,7 +242,7 @@ done
# Bitcoin Knots: container UID 101 → host UID 100101
[ -d /var/lib/archipelago/bitcoin ] && chown -R 100101:100101 /var/lib/archipelago/bitcoin 2>/dev/null
# Postgres: container UID 70 → host UID 100070
for dir in postgres-btcpay immich-db penpot-postgres; do
for dir in postgres-btcpay; do
[ -d "/var/lib/archipelago/$dir" ] && chown -R 100070:100070 "/var/lib/archipelago/$dir" 2>/dev/null
done
# MariaDB: container UID 999 → host UID 100999
@@ -270,7 +268,6 @@ mem_limit() {
lnd) echo "512m";;
electrumx) echo "1g";;
nextcloud) echo "1g";;
immich_server) echo "1g";;
btcpay-server) echo "1g";;
homeassistant) echo "512m";;
fedimint) echo "512m";;
@@ -289,8 +286,6 @@ mem_limit() {
filebrowser) echo "256m";;
portainer) echo "256m";;
nginx-proxy-manager) echo "256m";;
immich_postgres) echo "256m";;
immich_redis) echo "128m";;
tailscale) echo "256m";;
indeedhub|archy-bitcoin-ui|archy-lnd-ui|archy-electrs-ui) echo "128m";;
*) echo "512m";;
@@ -322,7 +317,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qE 'bitcoin-knots|arch
--security-opt no-new-privileges:true \
-p 8332:8332 -p 8333:8333 \
-v /var/lib/archipelago/bitcoin:/home/bitcoin/.bitcoin \
"${BITCOIN_KNOTS_IMAGE:-docker.io/bitcoinknots/bitcoin:v28.1}" \
"${BITCOIN_KNOTS_IMAGE:-docker.io/bitcoinknots/bitcoin:28.1}" \
-server=1 $BTC_EXTRA_ARGS \
-rpcallowip=0.0.0.0/0 -rpcbind=0.0.0.0:8332 \
-proxy=host.containers.internal:9050 -listen=1 -bind=0.0.0.0:8333 \
@@ -800,110 +795,6 @@ fi
track_container "tailscale"
# Immich stack (postgres + redis + server - ML optional)
# Remove old single-container 'immich' if present (wrong port 2283:3001, conflicts with immich_server)
if $DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -qx immich; then
log "Removing old immich container (use immich_server stack)..."
$DOCKER stop immich 2>/dev/null || true
$DOCKER rm -f immich 2>/dev/null || true
$DOCKER start immich_server 2>/dev/null || true
fi
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q immich_server; then
log "Creating Immich stack..."
mkdir -p /var/lib/archipelago/immich /var/lib/archipelago/immich-db
$DOCKER network create immich-net 2>/dev/null || true
if ! $DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -q immich_postgres; then
$DOCKER run -d --name immich_postgres --restart unless-stopped \
--health-cmd="pg_isready -U postgres || exit 1" --health-interval=30s --health-timeout=5s --health-retries=3 \
--memory=$(mem_limit immich_postgres) --network immich-net \
-v /var/lib/archipelago/immich-db:/var/lib/postgresql/data \
-e "POSTGRES_PASSWORD=$IMMICH_DB_PASS" -e POSTGRES_USER=postgres -e POSTGRES_DB=immich \
ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0 2>>"$LOG" || true
sleep 3
for i in 1 2 3 4 5 6 7 8 9 10; do
$DOCKER exec immich_postgres pg_isready -U postgres 2>/dev/null && break
sleep 2
done
fi
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q immich_redis; then
$DOCKER run -d --name immich_redis --restart unless-stopped \
--health-cmd="redis-cli ping || exit 1" --health-interval=30s --health-timeout=5s --health-retries=3 \
--memory=$(mem_limit immich_redis) --network immich-net \
docker.io/valkey/valkey:7-alpine 2>>"$LOG" || true
sleep 2
fi
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q immich_server; then
$DOCKER run -d --name immich_server --restart unless-stopped \
--health-cmd="curl -sf http://localhost:2283/ || exit 1" --health-interval=30s --health-timeout=5s --health-retries=3 \
--memory=$(mem_limit immich_server) --network immich-net \
-p 2283:2283 -v /var/lib/archipelago/immich:/usr/src/app/upload \
-e DB_HOSTNAME=immich_postgres -e DB_USERNAME=postgres -e "DB_PASSWORD=$IMMICH_DB_PASS" \
-e DB_DATABASE_NAME=immich -e REDIS_HOSTNAME=immich_redis \
-e UPLOAD_LOCATION=/usr/src/app/upload \
ghcr.io/immich-app/immich-server:release 2>>"$LOG" || true
fi
fi
track_container "immich_server"
# Penpot stack (postgres + valkey + backend + exporter + frontend)
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q penpot-frontend; then
log "Creating Penpot stack..."
mkdir -p /var/lib/archipelago/penpot-assets /var/lib/archipelago/penpot-postgres
$DOCKER network create penpot-net 2>/dev/null || true
if ! $DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -q penpot-postgres; then
$DOCKER run -d --name penpot-postgres --restart unless-stopped \
--health-cmd="pg_isready -U penpot || exit 1" --health-interval=30s --health-timeout=5s --health-retries=3 \
--memory=$(mem_limit penpot-postgres) --network penpot-net \
-v /var/lib/archipelago/penpot-postgres:/var/lib/postgresql/data \
-e POSTGRES_DB=penpot -e POSTGRES_USER=penpot -e "POSTGRES_PASSWORD=$PENPOT_DB_PASS" \
docker.io/postgres:15 2>>"$LOG" || true
sleep 5
fi
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q penpot-valkey; then
$DOCKER run -d --name penpot-valkey --restart unless-stopped \
--health-cmd="redis-cli ping || exit 1" --health-interval=30s --health-timeout=5s --health-retries=3 \
--memory=$(mem_limit penpot-valkey) --network penpot-net \
-e VALKEY_EXTRA_FLAGS="--maxmemory 128mb --maxmemory-policy volatile-lfu" \
docker.io/valkey/valkey:8.1 2>>"$LOG" || true
sleep 3
fi
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q penpot-backend; then
$DOCKER run -d --name penpot-backend --restart unless-stopped \
--health-cmd="curl -sf http://localhost:6060/ || exit 1" --health-interval=30s --health-timeout=5s --health-retries=3 \
--memory=$(mem_limit penpot-backend) --network penpot-net \
-v /var/lib/archipelago/penpot-assets:/opt/data/assets \
-e PENPOT_PUBLIC_URI="http://${TARGET_IP}:9001" \
-e PENPOT_SECRET_KEY=archipelago-penpot-secret-key-change-in-production \
-e PENPOT_DATABASE_URI=postgresql://penpot-postgres/penpot \
-e PENPOT_DATABASE_USERNAME=penpot -e "PENPOT_DATABASE_PASSWORD=$PENPOT_DB_PASS" \
-e PENPOT_REDIS_URI=redis://penpot-valkey/0 \
-e PENPOT_OBJECTS_STORAGE_BACKEND=fs \
-e PENPOT_OBJECTS_STORAGE_FS_DIRECTORY=/opt/data/assets \
-e PENPOT_FLAGS=disable-email-verification enable-smtp enable-prepl-server disable-secure-session-cookies \
"${PENPOT_BACKEND_IMAGE:-docker.io/penpotapp/backend:2.4.2}" 2>>"$LOG" || true
sleep 5
fi
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q penpot-exporter; then
$DOCKER run -d --name penpot-exporter --restart unless-stopped \
--health-cmd="curl -sf http://localhost:6061/ || exit 1" --health-interval=30s --health-timeout=5s --health-retries=3 \
--memory=$(mem_limit penpot-exporter) --network penpot-net \
-e PENPOT_SECRET_KEY=archipelago-penpot-secret-key-change-in-production \
-e PENPOT_PUBLIC_URI=http://penpot-frontend:8080 \
-e PENPOT_REDIS_URI=redis://penpot-valkey/0 \
"${PENPOT_EXPORTER_IMAGE:-docker.io/penpotapp/exporter:2.4.2}" 2>>"$LOG" || true
sleep 2
fi
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q penpot-frontend; then
$DOCKER run -d --name penpot-frontend --restart unless-stopped \
--health-cmd="curl -sf http://localhost:80/ || exit 1" --health-interval=30s --health-timeout=5s --health-retries=3 \
--memory=$(mem_limit penpot-frontend) --network penpot-net \
-p 9001:8080 -v /var/lib/archipelago/penpot-assets:/opt/data/assets \
-e PENPOT_PUBLIC_URI="http://${TARGET_IP}:9001" \
-e PENPOT_FLAGS=disable-email-verification enable-smtp enable-prepl-server disable-secure-session-cookies \
"${PENPOT_FRONTEND_IMAGE:-docker.io/penpotapp/frontend:2.4.2}" 2>>"$LOG" || true
fi
fi
track_container "penpot-frontend"
# 8. Nostr relays (optional - only if images were loaded; deploy does not create these on first boot)
# nostr-rs-relay and strfry are in ISO image bundle; create if image exists
if $DOCKER images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null | grep -q 'nostr-rs-relay'; then
@@ -925,7 +816,7 @@ if $DOCKER images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null | grep -q 'str
--health-cmd="curl -sf http://localhost:7777/ || exit 1" --health-interval=30s --health-timeout=5s --health-retries=3 \
--memory=$(mem_limit strfry) \
-p 7777:7777 -v /var/lib/archipelago/strfry:/data \
"${STRFRY_IMAGE:-docker.io/pluja/strfry:latest}" 2>>"$LOG" || true
"${STRFRY_IMAGE:-docker.io/pluja/strfry:1.0.4}" 2>>"$LOG" || true
fi
fi

View File

@@ -6,19 +6,20 @@
# source "$(dirname "$0")/image-versions.sh" 2>/dev/null || true
# Bitcoin stack
BITCOIN_KNOTS_IMAGE="docker.io/bitcoinknots/bitcoin:v28.1"
BITCOIN_KNOTS_IMAGE="docker.io/bitcoinknots/bitcoin:28.1"
LND_IMAGE="docker.io/lightninglabs/lnd:v0.18.5-beta"
ELECTRUMX_IMAGE="docker.io/lukechilds/electrumx:v1.16.0"
# Mempool stack
MEMPOOL_API_IMAGE="docker.io/mempool/frontend:v3.0.0"
MEMPOOL_BACKEND_IMAGE="docker.io/mempool/backend:v3.0.0"
MEMPOOL_WEB_IMAGE="docker.io/mempool/frontend:v3.0.0"
MARIADB_IMAGE="docker.io/library/mariadb:11.4"
# BTCPay
BTCPAY_IMAGE="docker.io/btcpayserver/btcpayserver:1.14.5"
BTCPAY_IMAGE="docker.io/btcpayserver/btcpayserver:1.13.7"
NBXPLORER_IMAGE="docker.io/nicolasdorier/nbxplorer:2.5.13"
POSTGRES_IMAGE="docker.io/library/postgres:16"
POSTGRES_IMAGE="docker.io/library/postgres:15"
BTCPAY_POSTGRES_IMAGE="docker.io/library/postgres:15"
# Apps
HOMEASSISTANT_IMAGE="ghcr.io/home-assistant/home-assistant:2024.12"
@@ -28,8 +29,8 @@ JELLYFIN_IMAGE="docker.io/jellyfin/jellyfin:10.10.3"
PHOTOPRISM_IMAGE="docker.io/photoprism/photoprism:240915"
OLLAMA_IMAGE="docker.io/ollama/ollama:0.5.4"
VAULTWARDEN_IMAGE="docker.io/vaultwarden/server:1.32.5"
NEXTCLOUD_IMAGE="docker.io/library/nextcloud:30"
SEARXNG_IMAGE="docker.io/searxng/searxng:2024.11.17"
NEXTCLOUD_IMAGE="docker.io/library/nextcloud:29"
SEARXNG_IMAGE="docker.io/searxng/searxng:2026.3.20-6c7e9c197"
ONLYOFFICE_IMAGE="docker.io/onlyoffice/documentserver:8.2"
FILEBROWSER_IMAGE="docker.io/filebrowser/filebrowser:v2"
NPM_IMAGE="docker.io/jc21/nginx-proxy-manager:2"
@@ -45,20 +46,14 @@ FEDIMINT_IMAGE="docker.io/fedimint/fedimintd:v0.5.1"
FEDIMINT_GATEWAY_IMAGE="docker.io/fedimint/gatewayd:v0.5.1"
# Media
IMMICH_IMAGE="ghcr.io/immich-app/immich-server:v1.123.0"
IMMICH_POSTGRES_IMAGE="ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0"
IMMICH_VALKEY_IMAGE="docker.io/valkey/valkey:7-alpine"
REDIS_IMAGE="docker.io/library/redis:7"
# Penpot
PENPOT_BACKEND_IMAGE="docker.io/penpotapp/backend:2.4.2"
PENPOT_FRONTEND_IMAGE="docker.io/penpotapp/frontend:2.4.2"
PENPOT_EXPORTER_IMAGE="docker.io/penpotapp/exporter:2.4.2"
# Valkey (general purpose)
VALKEY_IMAGE="docker.io/valkey/valkey:8"
# Nostr
NOSTR_RS_RELAY_IMAGE="docker.io/scsibug/nostr-rs-relay:0.9.0"
STRFRY_IMAGE="docker.io/pluja/strfry:latest" # No stable tag available yet
STRFRY_IMAGE="docker.io/pluja/strfry:1.0.4"
# IndeedHub stack (local builds use :local tag, not :latest)
MINIO_IMAGE="docker.io/minio/minio:RELEASE.2024-11-07T00-52-20Z"
@@ -68,8 +63,6 @@ INDEEDHUB_REDIS_IMAGE="docker.io/library/redis:7-alpine"
# DWN (Decentralized Web Node)
DWN_SERVER_IMAGE="ghcr.io/tbd54566975/dwn-server:main"
# Penpot postgres (separate from BTCPay postgres — different version)
PENPOT_POSTGRES_IMAGE="docker.io/library/postgres:15"
# Base images
NGINX_ALPINE_IMAGE="docker.io/library/nginx:alpine"

View File

@@ -7,7 +7,7 @@
# Provides: logging, SSH helpers, health checks, disk checks, memory limits
# Guard against double-sourcing
[ -n "$_ARCHY_COMMON_LOADED" ] && return 0
[ -n "${_ARCHY_COMMON_LOADED:-}" ] && return 0
_ARCHY_COMMON_LOADED=1
# ── Colored logging ─────────────────────────────────────────────────────