#!/bin/bash # # First-boot container creation for Archipelago autoinstaller # Creates core containers so My Apps works out of the box after ISO install # Runs after archipelago-load-images.service and archipelago-setup-tor.service # # Based on scripts/deploy-to-target.sh (--live) container logic - do not diverge. # No set -e: each section continues even if one fails (idempotent, best-effort). # LOG="/var/log/archipelago-first-boot.log" DOCKER=podman command -v podman >/dev/null 2>&1 || DOCKER=docker # Must run as root for podman [ "$(id -u)" -eq 0 ] || { echo "Must run as root" >&2; exit 1; } TARGET_IP=$(hostname -I 2>/dev/null | awk '{print $1}') [ -z "$TARGET_IP" ] && TARGET_IP="127.0.0.1" log() { echo "$(date '+%Y-%m-%d %H:%M:%S') $*" | tee -a "$LOG"; } log "First-boot container creation starting (host=$TARGET_IP)" # Ensure network exists (matches deploy) $DOCKER network create archy-net 2>/dev/null || true # 1. Bitcoin Knots (matches deploy exactly) if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qE 'bitcoin-knots|archy-bitcoin-knots'; then log "Creating Bitcoin Knots..." mkdir -p /var/lib/archipelago/bitcoin if $DOCKER run -d --name bitcoin-knots --restart unless-stopped --network archy-net \ -p 8332:8332 -p 8333:8333 \ -v /var/lib/archipelago/bitcoin:/home/bitcoin/.bitcoin \ docker.io/bitcoinknots/bitcoin:latest \ -server=1 -txindex=1 \ -rpcallowip=0.0.0.0/0 -rpcbind=0.0.0.0:8332 \ -rpcuser=archipelago -rpcpassword=archipelago123 \ -dbcache=4096 2>>"$LOG"; then log "Bitcoin Knots started" else log "Bitcoin Knots failed (may already exist)" fi else $DOCKER network connect archy-net bitcoin-knots 2>/dev/null || true log "Bitcoin Knots already running" fi # 2. Mempool stack (matches deploy) 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 \ -v /var/lib/archipelago/mysql-mempool:/var/lib/mysql \ -e MYSQL_DATABASE=mempool -e MYSQL_USER=mempool -e MYSQL_PASSWORD=mempoolpass \ -e MYSQL_ROOT_PASSWORD=rootpass \ docker.io/mariadb:10.11 2>>"$LOG" || true sleep 3 fi MYSQL_CNT=$($DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -E 'mysql-mempool|archy-mempool-db' | head -1) MYSQL_CNT=${MYSQL_CNT:-archy-mempool-db} $DOCKER network connect archy-net "$MYSQL_CNT" 2>/dev/null || true if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q mempool-electrs; then if $DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -q mempool-electrs; then $DOCKER start mempool-electrs 2>/dev/null || true else log "Creating mempool-electrs..." mkdir -p /var/lib/archipelago/mempool-electrs $DOCKER run -d --name mempool-electrs --restart unless-stopped --network archy-net \ -p 50001:50001 -v /var/lib/archipelago/mempool-electrs:/data \ docker.io/mempool/electrs:latest \ --daemon-rpc-addr bitcoin-knots:8332 --cookie archipelago:archipelago123 \ --jsonrpc-import --electrum-rpc-addr 0.0.0.0:50001 --db-dir /data --lightmode 2>>"$LOG" || true fi 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 \ -p 8999:8999 -v /var/lib/archipelago/mempool:/data \ -e MEMPOOL_BACKEND=electrum -e ELECTRUM_HOST=mempool-electrs -e ELECTRUM_PORT=50001 \ -e ELECTRUM_TLS_ENABLED=false -e CORE_RPC_HOST="$TARGET_IP" -e CORE_RPC_PORT=8332 \ -e CORE_RPC_USERNAME=archipelago -e CORE_RPC_PASSWORD=archipelago123 \ -e DATABASE_ENABLED=true -e DATABASE_HOST="$MYSQL_CNT" -e DATABASE_DATABASE=mempool \ -e DATABASE_USERNAME=mempool -e DATABASE_PASSWORD=mempoolpass \ docker.io/mempool/backend:v2.5.0 2>>"$LOG" || true 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 \ -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 # 2b. Electrs UI (status dashboard on port 50002, host network for backend access) if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q electrs-ui; then if $DOCKER images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null | grep -q 'electrs-ui'; then log "Starting Electrs UI from pre-built image..." $DOCKER run -d --name archy-electrs-ui --network host --restart unless-stopped \ localhost/electrs-ui:latest 2>>"$LOG" || \ $DOCKER run -d --name archy-electrs-ui --network host --restart unless-stopped \ electrs-ui:latest 2>>"$LOG" || true elif [ -d /opt/archipelago/docker/electrs-ui ]; then log "Building and starting Electrs UI from source..." $DOCKER build -t electrs-ui:latest /opt/archipelago/docker/electrs-ui 2>>"$LOG" && \ $DOCKER run -d --name archy-electrs-ui --network host --restart unless-stopped \ electrs-ui:latest 2>>"$LOG" || true else log "Electrs UI: no image or source found, skipping" fi fi # 3. BTCPay stack (matches deploy) 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 \ -v /var/lib/archipelago/postgres-btcpay:/var/lib/postgresql/data \ -e POSTGRES_DB=btcpay -e POSTGRES_USER=btcpay -e POSTGRES_PASSWORD=btcpaypass \ docker.io/postgres:15-alpine 2>>"$LOG" || true sleep 3 fi # Create nbxplorer DB only if postgres is running if $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qE 'archy-btcpay-db|postgres-btcpay'; then $DOCKER exec archy-btcpay-db psql -U postgres -tc "SELECT 1 FROM pg_database WHERE datname='nbxplorer'" 2>/dev/null | grep -q 1 || \ $DOCKER exec -e PGPASSWORD=btcpaypass archy-btcpay-db psql -U postgres -c "CREATE DATABASE nbxplorer;" 2>/dev/null || true fi if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q archy-nbxplorer; then if $DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -q archy-nbxplorer; then $DOCKER start archy-nbxplorer 2>/dev/null || true else log "Creating NBXplorer..." mkdir -p /var/lib/archipelago/nbxplorer $DOCKER run -d --name archy-nbxplorer --restart unless-stopped --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 \ -e NBXPLORER_BTCRPCUSER=archipelago -e NBXPLORER_BTCRPCPASSWORD=archipelago123 \ -e NBXPLORER_POSTGRES='User ID=btcpay;Password=btcpaypass;Host=archy-btcpay-db;Port=5432;Database=nbxplorer;Include Error Detail=true' \ docker.io/nicolasdorier/nbxplorer:2.6.0 2>>"$LOG" && sleep 5 || true fi 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 \ -p 23000:49392 -v /var/lib/archipelago/btcpay:/datadir \ -e ASPNETCORE_URLS=http://0.0.0.0:49392 -e BTCPAY_PROTOCOL=http \ -e BTCPAY_HOST="$TARGET_IP:23000" -e BTCPAY_CHAINS=btc \ -e BTCPAY_BTCEXPLORERURL=http://archy-nbxplorer:32838 \ -e BTCPAY_BTCRPCURL=http://bitcoin-knots:8332 \ -e BTCPAY_BTCRPCUSER=archipelago -e BTCPAY_BTCRPCPASSWORD=archipelago123 \ -e BTCPAY_POSTGRES='User ID=btcpay;Password=btcpaypass;Host=archy-btcpay-db;Port=5432;Database=btcpay;Include Error Detail=true' \ docker.io/btcpayserver/btcpayserver:1.13.5 2>>"$LOG" || true fi # 4. LND if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qE '^lnd$'; then log "Creating LND..." mkdir -p /var/lib/archipelago/lnd $DOCKER run -d --name lnd --restart unless-stopped --network archy-net \ -p 9735:9735 -p 10009:10009 -p 8080:8080 \ -v /var/lib/archipelago/lnd:/root/.lnd \ -e BITCOIN_ACTIVE=1 \ docker.io/lightninglabs/lnd:v0.18.4-beta 2>>"$LOG" || true fi # 5. Fedimint 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 \ -p 8173:8173 -p 8174:8174 -p 8175:8175 \ -v /var/lib/archipelago/fedimint:/data \ -e FM_DATA_DIR=/data -e FM_BITCOIND_USERNAME=archipelago -e FM_BITCOIND_PASSWORD=archipelago123 \ -e FM_BITCOIN_NETWORK=bitcoin -e FM_BIND_P2P=0.0.0.0:8173 \ -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 2>>"$LOG" || true fi # 6. Home Assistant 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 \ -p 8123:8123 -v /var/lib/archipelago/home-assistant:/config \ -e TZ=UTC \ docker.io/homeassistant/home-assistant:2024.1 2>>"$LOG" || true fi # 7. Single-container apps (Grafana, Uptime Kuma, Jellyfin, PhotoPrism, Ollama, Vaultwarden) 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 \ -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 2>>"$LOG" || true 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 \ -p 3001:3001 -v /var/lib/archipelago/uptime-kuma:/app/data \ -e TZ=UTC \ docker.io/louislam/uptime-kuma:1 2>>"$LOG" || true 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 \ -p 8096:8096 \ -v /var/lib/archipelago/jellyfin/config:/config \ -v /var/lib/archipelago/jellyfin/cache:/cache \ docker.io/jellyfin/jellyfin:10.8.13 2>>"$LOG" || true 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 \ -p 2342:2342 -v /var/lib/archipelago/photoprism:/photoprism/storage \ -e PHOTOPRISM_ADMIN_PASSWORD=archipelago -e PHOTOPRISM_DEFAULT_LOCALE=en \ docker.io/photoprism/photoprism:latest 2>>"$LOG" || true 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 \ -p 11434:11434 -v /var/lib/archipelago/ollama:/root/.ollama \ docker.io/ollama/ollama:latest 2>>"$LOG" || true 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 \ -p 8082:80 -v /var/lib/archipelago/vaultwarden:/data \ docker.io/vaultwarden/server:1.30.0-alpine 2>>"$LOG" || true 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 \ -p 8085:80 -v /var/lib/archipelago/nextcloud:/var/www/html \ docker.io/library/nextcloud:28 2>>"$LOG" || true fi if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q searxng; then log "Creating SearXNG..." $DOCKER run -d --name searxng --restart unless-stopped \ --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 \ docker.io/searxng/searxng:latest 2>>"$LOG" || true fi if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q onlyoffice; then log "Creating OnlyOffice..." $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 2>>"$LOG" || true fi if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q filebrowser; then log "Creating File Browser..." mkdir -p /var/lib/archipelago/filebrowser $DOCKER run -d --name filebrowser --restart unless-stopped \ --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 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 \ --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 \ -v /var/lib/archipelago/nginx-proxy-manager/data:/data \ -v /var/lib/archipelago/nginx-proxy-manager/letsencrypt:/etc/letsencrypt \ docker.io/jc21/nginx-proxy-manager:latest 2>>"$LOG" || true 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 \ --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 \ -v /var/lib/archipelago/portainer:/data \ -v /var/run/podman/podman.sock:/var/run/docker.sock \ docker.io/portainer/portainer-ce:2.19.4 2>>"$LOG" || true fi if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q tailscale; then log "Creating Tailscale..." mkdir -p /var/lib/archipelago/tailscale $DOCKER run -d --name tailscale --restart unless-stopped \ --network host --privileged \ --cap-add NET_ADMIN --cap-add NET_RAW \ --device=/dev/net/tun \ -v /var/lib/archipelago/tailscale:/var/lib/tailscale \ -e TS_STATE_DIR=/var/lib/tailscale \ docker.io/tailscale/tailscale:stable \ sh -c 'tailscale web --listen 0.0.0.0:8240 & exec tailscaled' 2>>"$LOG" || true fi # 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 --network immich-net \ -v /var/lib/archipelago/immich-db:/var/lib/postgresql/data \ -e POSTGRES_PASSWORD=immichpass -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 --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 \ -p 2283:2283 -v /var/lib/archipelago/immich:/usr/src/app/upload \ -e DB_HOSTNAME=immich_postgres -e DB_USERNAME=postgres -e DB_PASSWORD=immichpass \ -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 # 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 --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 \ 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 \ -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 \ -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 \ -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 \ docker.io/penpotapp/backend:latest 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 --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 \ docker.io/penpotapp/exporter:latest 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 --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 \ docker.io/penpotapp/frontend:latest 2>>"$LOG" || true fi fi # 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 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 \ -p 7047:7047 -v /var/lib/archipelago/nostr-rs-relay:/data \ scsibug/nostr-rs-relay:latest 2>>"$LOG" || true fi fi if $DOCKER images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null | grep -q 'strfry'; then 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 \ -p 7777:7777 -v /var/lib/archipelago/strfry:/data \ hoytech/strfry:latest 2>>"$LOG" || true fi fi # 9. Custom UI containers (bitcoin-ui, lnd-ui) # These are built from Dockerfiles in /opt/archipelago/docker/ or loaded from pre-built images. for ui in bitcoin-ui lnd-ui; do if $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q "$ui"; then continue fi case $ui in bitcoin-ui) PORT_ARG="-p 8334:80"; NET_ARG="" ;; lnd-ui) PORT_ARG="-p 8081:80"; NET_ARG="" ;; esac CONTAINER_NAME="archy-$ui" if $DOCKER images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null | grep -q "$ui"; then log "Starting $ui from pre-built image..." IMG=$($DOCKER images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null | grep "$ui" | head -1) $DOCKER run -d --name "$CONTAINER_NAME" $PORT_ARG --restart unless-stopped $NET_ARG "$IMG" 2>>"$LOG" || true elif [ -d "/opt/archipelago/docker/$ui" ]; then log "Building $ui from source..." if $DOCKER build -t "$ui:latest" "/opt/archipelago/docker/$ui" 2>>"$LOG"; then $DOCKER run -d --name "$CONTAINER_NAME" $PORT_ARG --restart unless-stopped $NET_ARG "$ui:latest" 2>>"$LOG" || true fi fi done log "First-boot container creation complete"