#!/bin/bash # Automated deployment script for DigiServer on a new PC # Run this script to completely set up DigiServer with all configurations set -e # Exit on any error # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color echo -e "${BLUE}╔════════════════════════════════════════════════════════════════╗${NC}" echo -e "${BLUE}║ DigiServer Automated Deployment ║${NC}" echo -e "${BLUE}╚════════════════════════════════════════════════════════════════╝${NC}" echo "" # Check if docker compose is available. Accept either the modern plugin # (`docker compose`) or the standalone v1 binary (`docker-compose`), since not # every Docker installation ships the Compose plugin. if docker compose version &> /dev/null; then COMPOSE="docker compose" elif command -v docker-compose &> /dev/null; then COMPOSE="docker-compose" echo -e "${YELLOW}⚠️ Using legacy 'docker-compose' (v1); the 'docker compose' plugin is unavailable.${NC}" else echo -e "${RED}❌ docker compose not found!${NC}" echo "Please install the docker compose plugin or docker-compose first" exit 1 fi # Check if we're in the project directory if [ ! -f "docker-compose.yml" ]; then echo -e "${RED}❌ docker-compose.yml not found!${NC}" echo "Please run this script from the digiserver-v2 directory" exit 1 fi # ============================================================================ # INITIALIZATION: Create data directories and seed the Caddy config # ============================================================================ echo -e "${YELLOW}📁 Initializing data directories...${NC}" # Create necessary data directories mkdir -p data/instance mkdir -p data/uploads mkdir -p data/caddy-data mkdir -p data/caddy-config mkdir -p data/caddy-logs # Seed the Caddyfile. It is bind-mounted as a FILE, so it MUST exist before # `docker compose up` — otherwise Docker creates a directory in its place and # Caddy fails to start. if [ -f "data/Caddyfile" ]; then echo -e " ${GREEN}✓${NC} data/Caddyfile present" elif [ -f "Caddyfile.example" ]; then cp Caddyfile.example data/Caddyfile echo -e " ${GREEN}✓${NC} data/Caddyfile seeded from Caddyfile.example" else echo -e " ${RED}❌ Caddyfile.example not found in repo root!${NC}" exit 1 fi echo -e "${GREEN}✅ Data directories initialized${NC}" echo "" # ============================================================================ # CONFIGURATION VARIABLES # ============================================================================ # NOTE: do NOT use the name HOSTNAME here — it is a bash built-in that already # holds the machine's hostname (e.g. "development"), so `${HOSTNAME:-default}` # would silently ignore the default and leak the OS hostname into the Caddyfile. SERVER_HOSTNAME="${SERVER_HOSTNAME:-digiserver}" EMAIL="${EMAIL:-admin@example.com}" # Auto-detect the primary LAN IP unless one was supplied explicitly. if [ -z "${IP_ADDRESS:-}" ]; then IP_ADDRESS="$(ip -4 route get 1.1.1.1 2>/dev/null | grep -oP 'src \K[\d.]+' | head -1)" if [ -z "$IP_ADDRESS" ]; then IP_ADDRESS="$(hostname -I 2>/dev/null | awk '{print $1}')" fi fi if [ -z "$IP_ADDRESS" ]; then echo -e "${RED}❌ Could not determine the server IP. Set IP_ADDRESS=... and re-run.${NC}" exit 1 fi # HTTPS_MODE selects how TLS is provided: # internal : Caddy's internal CA, IP-only. Needs NO public DNS and NO ACME. # Correct for an intranet name such as digiserver.sibiusb.harting.intra. # acme : Let's Encrypt — requires DOMAIN to be publicly resolvable. # off : plain HTTP only. HTTPS_MODE="${HTTPS_MODE:-internal}" case "$HTTPS_MODE" in internal) # An empty DOMAIN is what makes CaddyConfigGenerator choose the # internal-CA path instead of Let's Encrypt. DOMAIN="" ;; acme) if [ -z "${DOMAIN:-}" ]; then echo -e "${RED}❌ HTTPS_MODE=acme requires DOMAIN to be set.${NC}" exit 1 fi ;; off) DOMAIN="" ;; *) echo -e "${RED}❌ Invalid HTTPS_MODE '$HTTPS_MODE' (use internal|acme|off).${NC}" exit 1 ;; esac # Published ports. These MUST match docker-compose.yml, which maps Caddy's # internal 80/443 to these host ports (HTTP_PORT / HTTPS_PORT). HTTP_PORT="${HTTP_PORT:-80}" HTTPS_PORT="${HTTPS_PORT:-443}" echo -e "${BLUE}Configuration:${NC}" echo " Hostname: $SERVER_HOSTNAME" echo " HTTPS mode: $HTTPS_MODE" echo " Domain: ${DOMAIN:-(none — internal CA)}" echo " IP Address: $IP_ADDRESS" echo " Email: $EMAIL" echo " HTTP port: $HTTP_PORT" echo " HTTPS port: $HTTPS_PORT" echo "" # ============================================================================ # STEP 1: Build and start containers # ============================================================================ echo -e "${YELLOW}📦 [1/6] Building and starting containers...${NC}" # Compose v1 refuses to build unless the buildx plugin is >= 0.17: # "compose build requires buildx 0.17.0 or later" # Distro Packaged Docker ships older buildx (or none). Detect that and fall back # to a plain `docker build` + `up --no-build`, which needs no buildx at all. APP_IMAGE="digiserver-v2-digiserver-app:latest" BUILDX_VER="$(docker buildx version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+' | head -1)" BUILDX_OK=0 if [ -n "$BUILDX_VER" ]; then _bx_major="${BUILDX_VER%%.*}" _bx_minor="${BUILDX_VER##*.}" if [ "$_bx_major" -gt 0 ] || { [ "$_bx_major" -eq 0 ] && [ "$_bx_minor" -ge 17 ]; }; then BUILDX_OK=1 fi fi if [ "$BUILDX_OK" -eq 1 ]; then echo -e " ${GREEN}✓${NC} buildx ${BUILDX_VER} — building via compose" $COMPOSE up -d --build else echo -e " ${YELLOW}⚠${NC} buildx ${BUILDX_VER:-not found} (< 0.17) — falling back to 'docker build'" docker build -t "$APP_IMAGE" . $COMPOSE up -d --no-build fi echo -e "${YELLOW}⏳ Waiting for containers to be healthy...${NC}" sleep 15 # Verify containers are running if ! $COMPOSE ps | grep -q "Up"; then echo -e "${RED}❌ Containers failed to start!${NC}" $COMPOSE logs exit 1 fi echo -e "${GREEN}✅ Containers started successfully${NC}" echo "" # ============================================================================ # STEP 2: Run database migrations # ============================================================================ echo -e "${YELLOW}📊 [2/6] Running database migrations...${NC}" echo -e " • Creating https_config table..." $COMPOSE exec -T digiserver-app python /app/migrations/add_https_config_table.py echo -e " • Creating player_user table..." $COMPOSE exec -T digiserver-app python /app/migrations/add_player_user_table.py echo -e " • Adding email to https_config..." $COMPOSE exec -T digiserver-app python /app/migrations/add_email_to_https_config.py echo -e " • Migrating player_user global settings..." $COMPOSE exec -T digiserver-app python /app/migrations/migrate_player_user_global.py echo -e " • Adding original_filename to content..." $COMPOSE exec -T digiserver-app python /app/migrations/add_original_filename_to_content.py echo -e "${GREEN}✅ All database migrations completed${NC}" echo "" # ============================================================================ # STEP 3: Configure HTTPS # ============================================================================ echo -e "${YELLOW}🔒 [3/6] Configuring HTTPS (mode: $HTTPS_MODE)...${NC}" # https_manager.py mirrors the Admin UI code path: persist HTTPSConfig → # regenerate the Caddyfile → hot-reload Caddy → verify the TLS listener and # automatically fall back to HTTP if it does not come up. # # `bootstrap` (rather than `enable`) is used deliberately: it records provenance # via HTTPSConfig.updated_by, so an admin's later change in the UI is not # silently overwritten on the next restart. # # The values computed above are injected with -e so this run uses them instead # of whatever happens to be in the container's .env. # # Exit code 2 means "config applied but Caddy did not reload" — not fatal, so it # must not abort the deployment. set +e if [ "$HTTPS_MODE" = "off" ]; then $COMPOSE exec -T digiserver-app python /app/https_manager.py disable else $COMPOSE exec -T \ -e HOSTNAME_INTERNAL="$SERVER_HOSTNAME" \ -e HOST_IP="$IP_ADDRESS" \ -e DOMAIN="$DOMAIN" \ -e SSL_EMAIL="$EMAIL" \ -e HTTPS_PORT="$HTTPS_PORT" \ digiserver-app python /app/https_manager.py bootstrap fi HTTPS_RC=$? set -e case "$HTTPS_RC" in 0) echo -e "${GREEN}✅ HTTPS configured${NC}" ;; 2) echo -e "${YELLOW}⚠️ HTTPS config applied but Caddy did not reload — restart the caddy container.${NC}" ;; *) echo -e "${YELLOW}⚠️ HTTPS not configured (or verification failed and it fell back to HTTP); continuing.${NC}" ;; esac echo "" # ============================================================================ # STEP 4: Verify database setup # ============================================================================ echo -e "${YELLOW}🔍 [4/6] Verifying database setup...${NC}" $COMPOSE exec -T digiserver-app python -c " from app.app import create_app from app.extensions import db from sqlalchemy import inspect app = create_app() with app.app_context(): inspector = inspect(db.engine) tables = inspector.get_table_names() print(' Database tables:') for table in sorted(tables): print(f' ✓ {table}') print(f'') print(f' ✅ Total tables: {len(tables)}') " || echo " ⚠️ Database verification skipped" echo "" # ============================================================================ # STEP 5: Verify Caddy configuration # ============================================================================ echo -e "${YELLOW}🔧 [5/6] Verifying Caddy configuration...${NC}" $COMPOSE exec -T caddy caddy validate --config /etc/caddy/Caddyfile >/dev/null 2>&1 if [ $? -eq 0 ]; then echo -e " ${GREEN}✅ Caddy configuration is valid${NC}" else echo -e " ${YELLOW}⚠️ Caddy validation skipped${NC}" fi echo "" # ============================================================================ # STEP 6: Display summary # ============================================================================ echo -e "${YELLOW}📋 [6/6] Displaying configuration summary...${NC}" echo "" $COMPOSE exec -T digiserver-app python /app/https_manager.py status echo "" echo -e "${GREEN}╔════════════════════════════════════════════════════════════════╗${NC}" echo -e "${GREEN}║ 🎉 Deployment Complete! ║${NC}" echo -e "${GREEN}╚════════════════════════════════════════════════════════════════╝${NC}" echo "" # Build host:port suffixes, omitting the default ports for readability. _http_url="http://$IP_ADDRESS" [ "$HTTP_PORT" != "80" ] && _http_url="http://$IP_ADDRESS:$HTTP_PORT" _https_url="https://$IP_ADDRESS" [ "$HTTPS_PORT" != "443" ] && _https_url="https://$IP_ADDRESS:$HTTPS_PORT" echo -e "${BLUE}📍 Access Points:${NC}" echo -e " 🌐 ${_http_url} (always available)" case "$HTTPS_MODE" in internal) echo -e " 🔒 ${_https_url} (internal CA — see note below)" echo -e " 🔒 https://$SERVER_HOSTNAME (needs DNS or an /etc/hosts entry)" ;; acme) echo -e " 🔒 https://$DOMAIN" ;; off) echo -e " ℹ️ HTTPS disabled (HTTPS_MODE=off)" ;; esac echo "" if [ "$HTTPS_MODE" = "internal" ]; then echo -e "${YELLOW}⚠️ Internal CA certificate notice:${NC}" echo " The TLS certificate is signed by Caddy's LOCAL CA, which is not in any" echo " client's trust store. Browsers will warn and players will reject it" echo " unless you either:" echo " a) install the root CA on each device, or" echo " b) use the plain HTTP endpoint above (simplest for players)." echo "" echo " Export the root CA with:" echo " $COMPOSE cp caddy:/data/caddy/pki/authorities/local/root.crt ./caddy-root.crt" echo "" fi echo -e "${BLUE}📝 Administrator Account:${NC}" if [ -f ".deployment-credentials" ]; then echo " Credentials are in .deployment-credentials (chmod 600)." else echo " Username: ${ADMIN_USERNAME:-admin}" echo " Password: see ADMIN_PASSWORD in .env (or the container's ADMIN_PASSWORD)" fi if grep -qs '^ADMIN_PASSWORD=admin123' .env 2>/dev/null; then echo -e " ${RED}⚠️ ADMIN_PASSWORD is still the default — change it now!${NC}" fi echo "" echo -e "${BLUE}📚 Documentation:${NC}" echo " • docs/07-deployment.md - Deployment + HTTPS details" echo " • docs/06-utils-services.md - Caddy / https_manager internals" echo " • Caddyfile.example - Caddy template (seeded into data/)" echo "" echo -e "${YELLOW}Next Steps:${NC}" echo "1. Access the application at one of the URLs above" echo "2. Log in with admin credentials" echo "3. Change the default password immediately" echo "4. Configure your players and content" echo "" echo -e "${BLUE}📞 Support:${NC}" echo "For troubleshooting, see docs/07-deployment.md" echo ""