security+feat: v1.3.0 — pentest remediation, container reliability, UI overhaul

Security (33 pentest findings addressed):
- CRITICAL: backend binds 127.0.0.1, path traversal in tor.rs/dwn fixed
- HIGH: federation requires signatures, XSS login redirect, RBAC viewer restricted
- HIGH: tar slip prevention, S3 SSRF validation, backup ID validation
- MEDIUM: remember-me random secret, TOTP session rotation, password re-auth
- LOW: CSP unsafe-inline removed, CORS dev-only, onion/webhook validation

Container reliability:
- Memory limits on all 37 containers (OOM prevention)
- Exited vs stopped state distinction with health-aware status badges
- Crash recovery coordination (no more restart cascade)
- User-stopped tracking survives reboots
- Tiered boot recovery (databases → core → services → apps)

UI:
- Wallet TransactionsModal, health-aware app status badges
- Restart button on containers, exited/crashed red state
- Mesh view overhaul, glass button updates, BaseModal/ToggleSwitch
- Apps sticky header removed, dev faucet, mutable mock wallet

Infrastructure:
- LND REST port 8080 exposed over Tor (LND Connect fix)
- Nginx cookie_session fix, deploy script Tor config updated
- Dev environment: podman auto-start, boot mode simulation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-19 12:44:31 +00:00
parent d1b48388fb
commit 1a74a930f7
77 changed files with 2485 additions and 966 deletions

View File

@@ -109,9 +109,97 @@ else
log "Swap already configured"
fi
# Rootless podman prerequisites (run as root, configures for archipelago user)
log "Setting up rootless podman prerequisites..."
# Allow binding to ports >= 80 (rootless default is 1024)
if ! grep -q "unprivileged_port_start=80" /etc/sysctl.d/99-rootless-podman.conf 2>/dev/null; then
echo "net.ipv4.ip_unprivileged_port_start=80" > /etc/sysctl.d/99-rootless-podman.conf
sysctl -p /etc/sysctl.d/99-rootless-podman.conf 2>/dev/null
log " Rootless port binding enabled (>=80)"
fi
# Linger for container persistence after logout
if [ "$(loginctl show-user archipelago 2>/dev/null | grep Linger)" != "Linger=yes" ]; then
loginctl enable-linger archipelago 2>/dev/null
log " Linger enabled for archipelago user"
fi
# Ensure subuid/subgid mappings
grep -q "^archipelago:" /etc/subuid 2>/dev/null || {
echo "archipelago:100000:65536" >> /etc/subuid
echo "archipelago:100000:65536" >> /etc/subgid
log " subuid/subgid configured"
}
# Ensure /etc/hosts is readable (rootless podman needs it)
chmod 644 /etc/hosts 2>/dev/null
# Ensure network exists (matches deploy)
$DOCKER network create archy-net 2>/dev/null || true
# Rootless podman UID mapping: fix data dir ownership so container processes
# can write. Rootless podman maps container UIDs via subuid (container UID N
# → 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 \
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
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
[ -d "/var/lib/archipelago/$dir" ] && chown -R 100070:100070 "/var/lib/archipelago/$dir" 2>/dev/null
done
# MariaDB: container UID 999 → host UID 100999
for dir in mempool mysql-mempool; do
[ -d "/var/lib/archipelago/$dir" ] && chown -R 100999:100999 "/var/lib/archipelago/$dir" 2>/dev/null
done
# Grafana: container UID 472 → host UID 100472
[ -d /var/lib/archipelago/grafana ] && chown -R 100472:100472 /var/lib/archipelago/grafana 2>/dev/null
log "UID mapping done"
# ── Memory limits per container ──────────────────────────────────────────
# Matches core/archipelago/src/api/rpc/package.rs get_memory_limit()
# Prevents a single runaway container from OOMing the whole system.
TOTAL_MEM_MB=$(($(awk '/MemTotal/{print $2}' /proc/meminfo) / 1024))
LOW_MEM=false
[ "$TOTAL_MEM_MB" -lt 12000 ] && LOW_MEM=true && log "Low-memory system (${TOTAL_MEM_MB}MB) — reducing limits"
mem_limit() {
case "$1" in
bitcoin-knots) $LOW_MEM && echo "1g" || echo "2g";;
onlyoffice) $LOW_MEM && echo "1g" || echo "2g";;
ollama) $LOW_MEM && echo "1g" || echo "4g";;
lnd) echo "512m";;
electrumx) echo "1g";;
nextcloud) echo "1g";;
immich_server) echo "1g";;
btcpay-server) echo "1g";;
homeassistant) echo "512m";;
fedimint) echo "512m";;
fedimint-gateway) echo "512m";;
photoprism) $LOW_MEM && echo "512m" || echo "1g";;
mempool-api) echo "512m";;
jellyfin) echo "1g";;
searxng) echo "512m";;
archy-btcpay-db) echo "512m";;
archy-nbxplorer) echo "512m";;
archy-mempool-db) echo "512m";;
archy-mempool-web) echo "256m";;
grafana) echo "256m";;
vaultwarden) echo "256m";;
uptime-kuma) echo "256m";;
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";;
esac
}
# ── Tier 1: Databases & Core Infrastructure ──────────────────────────────
log "=== Tier 1: Databases & Core Infrastructure ==="
@@ -130,14 +218,14 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qE 'bitcoin-knots|arch
BTC_DBCACHE=4096
log " Large disk (${DISK_GB}GB) — enabling txindex"
fi
if $DOCKER run -d --name bitcoin-knots --restart unless-stopped --network archy-net \
if $DOCKER run -d --name bitcoin-knots --restart unless-stopped --memory=$(mem_limit bitcoin-knots) --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 8332:8332 -p 8333:8333 \
-v /var/lib/archipelago/bitcoin:/home/bitcoin/.bitcoin \
docker.io/bitcoinknots/bitcoin:latest \
-server=1 $BTC_EXTRA_ARGS \
-rpcallowip=127.0.0.1/32 -rpcallowip=10.88.0.0/16 -rpcbind=0.0.0.0:8332 \
-rpcallowip=0.0.0.0/0 -rpcbind=0.0.0.0:8332 \
-rpcuser=$BITCOIN_RPC_USER -rpcpassword=$BITCOIN_RPC_PASS \
-proxy=127.0.0.1:9050 -listen=1 -bind=0.0.0.0:8333 \
-dbcache=$BTC_DBCACHE 2>>"$LOG"; then
@@ -163,7 +251,7 @@ fi
if ! $DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -qE 'archy-mempool-db|mysql-mempool'; then
log "Creating mysql-mempool..."
mkdir -p /var/lib/archipelago/mysql-mempool
$DOCKER run -d --name archy-mempool-db --restart unless-stopped --network archy-net \
$DOCKER run -d --name archy-mempool-db --restart unless-stopped --memory=$(mem_limit archy-mempool-db) --network archy-net \
-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 \
@@ -180,7 +268,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q electrumx; then
else
log "Creating electrumx..."
mkdir -p /var/lib/archipelago/electrumx
$DOCKER run -d --name electrumx --restart unless-stopped --network archy-net \
$DOCKER run -d --name electrumx --restart unless-stopped --memory=$(mem_limit electrumx) --network archy-net \
-p 50001:50001 -v /var/lib/archipelago/electrumx:/data \
-e DAEMON_URL=http://$BITCOIN_RPC_USER:$BITCOIN_RPC_PASS@bitcoin-knots:8332/ \
-e COIN=Bitcoin -e DB_DIRECTORY=/data \
@@ -192,7 +280,7 @@ fi
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q mempool-api; then
log "Creating mempool-api..."
mkdir -p /var/lib/archipelago/mempool
$DOCKER run -d --name mempool-api --restart unless-stopped --network archy-net \
$DOCKER run -d --name mempool-api --restart unless-stopped --memory=$(mem_limit mempool-api) --network archy-net \
-p 8999:8999 -v /var/lib/archipelago/mempool:/data \
-e MEMPOOL_BACKEND=electrum -e ELECTRUM_HOST=electrumx -e ELECTRUM_PORT=50001 \
-e ELECTRUM_TLS_ENABLED=false -e CORE_RPC_HOST="$TARGET_IP" -e CORE_RPC_PORT=8332 \
@@ -204,7 +292,7 @@ fi
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qE 'archy-mempool-web|mempool-web'; then
log "Creating mempool frontend..."
$DOCKER run -d --name archy-mempool-web --restart unless-stopped --network archy-net \
$DOCKER run -d --name archy-mempool-web --restart unless-stopped --memory=$(mem_limit archy-mempool-web) --network archy-net \
-p 4080:8080 -e FRONTEND_HTTP_PORT=8080 -e BACKEND_MAINNET_HTTP_HOST=mempool-api \
docker.io/mempool/frontend:v2.5.0 2>>"$LOG" || true
fi
@@ -231,7 +319,7 @@ fi
if ! $DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -qE 'archy-btcpay-db|postgres-btcpay'; then
log "Creating PostgreSQL for BTCPay..."
mkdir -p /var/lib/archipelago/postgres-btcpay
$DOCKER run -d --name archy-btcpay-db --restart unless-stopped --network archy-net \
$DOCKER run -d --name archy-btcpay-db --restart unless-stopped --memory=$(mem_limit archy-btcpay-db) --network archy-net \
-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 2>>"$LOG" || true
@@ -249,7 +337,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q archy-nbxplorer; the
else
log "Creating NBXplorer..."
mkdir -p /var/lib/archipelago/nbxplorer
$DOCKER run -d --name archy-nbxplorer --restart unless-stopped --network archy-net \
$DOCKER run -d --name archy-nbxplorer --restart unless-stopped --memory=$(mem_limit archy-nbxplorer) --network archy-net \
-p 32838:32838 -v /var/lib/archipelago/nbxplorer:/data \
-e NBXPLORER_DATADIR=/data -e NBXPLORER_NETWORK=mainnet -e NBXPLORER_CHAINS=btc \
-e NBXPLORER_BIND=0.0.0.0:32838 -e NBXPLORER_BTCRPCURL=http://bitcoin-knots:8332 \
@@ -262,7 +350,7 @@ fi
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q btcpay-server; then
log "Creating BTCPay Server..."
mkdir -p /var/lib/archipelago/btcpay
$DOCKER run -d --name btcpay-server --restart unless-stopped --network archy-net \
$DOCKER run -d --name btcpay-server --restart unless-stopped --memory=$(mem_limit btcpay-server) --network archy-net \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
--security-opt no-new-privileges:true \
-p 23000:49392 -v /var/lib/archipelago/btcpay:/datadir \
@@ -312,7 +400,7 @@ autopilot.active=false
LNDCONF
log "LND config created (archy-net → bitcoin-knots:8332, rpcpolling)"
fi
$DOCKER run -d --name lnd --restart unless-stopped --network archy-net \
$DOCKER run -d --name lnd --restart unless-stopped --memory=$(mem_limit lnd) --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 \
@@ -324,7 +412,7 @@ fi
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q fedimint; then
log "Creating Fedimint..."
mkdir -p /var/lib/archipelago/fedimint
$DOCKER run -d --name fedimint --restart unless-stopped --network archy-net \
$DOCKER run -d --name fedimint --restart unless-stopped --memory=$(mem_limit fedimint) --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 8173:8173 -p 8174:8174 -p 8175:8175 \
@@ -346,7 +434,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q fedimint-gateway; th
LND_MACAROON=/var/lib/archipelago/lnd/data/chain/bitcoin/mainnet/admin.macaroon
if $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q '^lnd$' && [ -f "$LND_CERT" ] && [ -f "$LND_MACAROON" ]; then
log " LND detected — using lnd mode"
$DOCKER run -d --name fedimint-gateway --restart unless-stopped --network archy-net \
$DOCKER run -d --name fedimint-gateway --restart unless-stopped --memory=$(mem_limit fedimint-gateway) --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 8176:8176 \
@@ -361,7 +449,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q fedimint-gateway; th
lnd --lnd-rpc-host "$TARGET_IP":10009 --lnd-tls-cert /lnd/tls.cert --lnd-macaroon /lnd/admin.macaroon 2>>"$LOG" || true
else
log " No LND found — using ldk (built-in Lightning)"
$DOCKER run -d --name fedimint-gateway --restart unless-stopped --network archy-net \
$DOCKER run -d --name fedimint-gateway --restart unless-stopped --memory=$(mem_limit fedimint-gateway) --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 8176:8176 -p 9737:9737 \
@@ -383,7 +471,7 @@ sleep 5 # Let core services stabilize
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qE 'homeassistant|home-assistant'; then
log "Creating Home Assistant..."
mkdir -p /var/lib/archipelago/home-assistant
$DOCKER run -d --name homeassistant --restart unless-stopped \
$DOCKER run -d --name homeassistant --restart unless-stopped --memory=$(mem_limit homeassistant) \
--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 \
@@ -396,7 +484,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q grafana; then
log "Creating Grafana..."
mkdir -p /var/lib/archipelago/grafana
chown 472:472 /var/lib/archipelago/grafana 2>/dev/null || true
$DOCKER run -d --name grafana --restart unless-stopped \
$DOCKER run -d --name grafana --restart unless-stopped --memory=$(mem_limit grafana) \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID \
--security-opt no-new-privileges:true \
--read-only --tmpfs /tmp:rw,noexec,nosuid,size=256m --tmpfs /run:rw,noexec,nosuid,size=64m \
@@ -407,7 +495,7 @@ fi
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q uptime-kuma; then
log "Creating Uptime Kuma..."
mkdir -p /var/lib/archipelago/uptime-kuma
$DOCKER run -d --name uptime-kuma --restart unless-stopped \
$DOCKER run -d --name uptime-kuma --restart unless-stopped --memory=$(mem_limit uptime-kuma) \
--cap-drop ALL --cap-add CHOWN --cap-add FOWNER --cap-add SETUID --cap-add SETGID \
--security-opt no-new-privileges:true \
-p 3001:3001 -v /var/lib/archipelago/uptime-kuma:/app/data \
@@ -417,7 +505,7 @@ fi
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q jellyfin; then
log "Creating Jellyfin..."
mkdir -p /var/lib/archipelago/jellyfin/config /var/lib/archipelago/jellyfin/cache
$DOCKER run -d --name jellyfin --restart unless-stopped \
$DOCKER run -d --name jellyfin --restart unless-stopped --memory=$(mem_limit jellyfin) \
--cap-drop ALL --security-opt no-new-privileges:true \
-p 8096:8096 \
-v /var/lib/archipelago/jellyfin/config:/config \
@@ -427,7 +515,7 @@ fi
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q photoprism; then
log "Creating PhotoPrism..."
mkdir -p /var/lib/archipelago/photoprism
$DOCKER run -d --name photoprism --restart unless-stopped \
$DOCKER run -d --name photoprism --restart unless-stopped --memory=$(mem_limit photoprism) \
--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 \
@@ -437,7 +525,7 @@ fi
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q ollama; then
log "Creating Ollama..."
mkdir -p /var/lib/archipelago/ollama
$DOCKER run -d --name ollama --restart unless-stopped \
$DOCKER run -d --name ollama --restart unless-stopped --memory=$(mem_limit ollama) \
--cap-drop ALL --security-opt no-new-privileges:true \
--read-only --tmpfs /tmp:rw,noexec,nosuid,size=256m --tmpfs /run:rw,noexec,nosuid,size=64m \
-p 11434:11434 -v /var/lib/archipelago/ollama:/root/.ollama \
@@ -446,7 +534,7 @@ fi
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q vaultwarden; then
log "Creating Vaultwarden..."
mkdir -p /var/lib/archipelago/vaultwarden
$DOCKER run -d --name vaultwarden --restart unless-stopped \
$DOCKER run -d --name vaultwarden --restart unless-stopped --memory=$(mem_limit vaultwarden) \
--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 \
@@ -455,7 +543,7 @@ fi
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q nextcloud; then
log "Creating Nextcloud..."
mkdir -p /var/lib/archipelago/nextcloud
$DOCKER run -d --name nextcloud --restart unless-stopped \
$DOCKER run -d --name nextcloud --restart unless-stopped --memory=$(mem_limit nextcloud) \
--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 \
@@ -463,7 +551,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q nextcloud; then
fi
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q searxng; then
log "Creating SearXNG..."
$DOCKER run -d --name searxng --restart unless-stopped \
$DOCKER run -d --name searxng --restart unless-stopped --memory=$(mem_limit searxng) \
--cap-drop ALL --security-opt no-new-privileges:true \
--read-only --tmpfs /tmp:rw,noexec,nosuid,size=256m --tmpfs /run:rw,noexec,nosuid,size=64m \
-p 8888:8080 \
@@ -471,7 +559,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q searxng; then
fi
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q onlyoffice; then
log "Creating OnlyOffice..."
$DOCKER run -d --name onlyoffice --restart unless-stopped \
$DOCKER run -d --name onlyoffice --restart unless-stopped --memory=$(mem_limit onlyoffice) \
--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 \
@@ -480,14 +568,14 @@ fi
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q filebrowser; then
log "Creating File Browser..."
mkdir -p /var/lib/archipelago/filebrowser /var/lib/archipelago/filebrowser-db
$DOCKER run -d --name filebrowser --restart unless-stopped \
$DOCKER run -d --name filebrowser --restart unless-stopped --memory=$(mem_limit filebrowser) \
-p 8083:80 -v /var/lib/archipelago/filebrowser:/srv \
docker.io/filebrowser/filebrowser:v2.27.0 2>>"$LOG" || true
fi
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q nginx-proxy-manager; then
log "Creating Nginx Proxy Manager..."
mkdir -p /var/lib/archipelago/nginx-proxy-manager/data /var/lib/archipelago/nginx-proxy-manager/letsencrypt
$DOCKER run -d --name nginx-proxy-manager --restart unless-stopped \
$DOCKER run -d --name nginx-proxy-manager --restart unless-stopped --memory=$(mem_limit nginx-proxy-manager) \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID --cap-add NET_BIND_SERVICE \
--security-opt no-new-privileges:true \
-p 81:81 -p 8084:80 -p 8443:443 \
@@ -498,7 +586,7 @@ fi
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q portainer; then
log "Creating Portainer..."
mkdir -p /var/lib/archipelago/portainer
$DOCKER run -d --name portainer --restart unless-stopped \
$DOCKER run -d --name portainer --restart unless-stopped --memory=$(mem_limit portainer) \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
--security-opt no-new-privileges:true \
-p 9000:9000 \
@@ -510,7 +598,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q tailscale; then
log "Creating Tailscale..."
mkdir -p /var/lib/archipelago/tailscale
# Tailscale needs NET_ADMIN + NET_RAW + TUN device (no --privileged)
$DOCKER run -d --name tailscale --restart unless-stopped \
$DOCKER run -d --name tailscale --restart unless-stopped --memory=$(mem_limit tailscale) \
--network host \
--cap-drop=ALL \
--cap-add=NET_ADMIN \
@@ -537,7 +625,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q immich_server; then
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 \
$DOCKER run -d --name immich_postgres --restart unless-stopped --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
@@ -548,12 +636,12 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q immich_server; then
done
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 run -d --name immich_redis --restart unless-stopped --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 --network immich-net \
$DOCKER run -d --name immich_server --restart unless-stopped --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 \
@@ -568,20 +656,20 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q penpot-frontend; the
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 \
$DOCKER run -d --name penpot-postgres --restart unless-stopped --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 --network penpot-net \
$DOCKER run -d --name penpot-valkey --restart unless-stopped --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 --network penpot-net \
$DOCKER run -d --name penpot-backend --restart unless-stopped --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 \
@@ -595,7 +683,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q penpot-frontend; the
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 \
$DOCKER run -d --name penpot-exporter --restart unless-stopped --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 \
@@ -603,7 +691,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q penpot-frontend; the
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 \
$DOCKER run -d --name penpot-frontend --restart unless-stopped --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 \
@@ -617,7 +705,7 @@ if $DOCKER images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null | grep -q 'nos
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q nostr-rs-relay; then
log "Creating nostr-rs-relay..."
mkdir -p /var/lib/archipelago/nostr-rs-relay
$DOCKER run -d --name nostr-rs-relay --restart unless-stopped \
$DOCKER run -d --name nostr-rs-relay --restart unless-stopped --memory=$(mem_limit nostr-rs-relay) \
-p 7047:7047 -v /var/lib/archipelago/nostr-rs-relay:/data \
scsibug/nostr-rs-relay:latest 2>>"$LOG" || true
fi
@@ -626,7 +714,7 @@ if $DOCKER images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null | grep -q 'str
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q strfry; then
log "Creating strfry..."
mkdir -p /var/lib/archipelago/strfry
$DOCKER run -d --name strfry --restart unless-stopped \
$DOCKER run -d --name strfry --restart unless-stopped --memory=$(mem_limit strfry) \
-p 7777:7777 -v /var/lib/archipelago/strfry:/data \
hoytech/strfry:latest 2>>"$LOG" || true
fi
@@ -644,7 +732,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q indeedhub; then
fi
if [ -n "$INDEEDHUB_IMAGE" ]; then
log "Creating Indeehub from $INDEEDHUB_IMAGE..."
$DOCKER run -d --name indeedhub --restart unless-stopped \
$DOCKER run -d --name indeedhub --restart unless-stopped --memory=$(mem_limit indeedhub) \
--cap-drop ALL --security-opt no-new-privileges:true \
--read-only --tmpfs /tmp:rw,noexec,nosuid,size=64m --tmpfs /app/.next/cache:rw,noexec,nosuid,size=128m \
-p 7777:7777 \