Files
digiserver-v2/deploy.sh
T
ske087 46602f1933 Sanitize codebase, reorganize docs, and add missing deploy files
Remove dead code identified in docs/SANITIZATION-REVIEW.md:
- app/blueprints/content_old.py and app/blueprints/playlist.py
- app/models/group.py, app/utils/nginx_config_reader.py
- orphaned templates (content_list, edit_content, upload_content,
  player_page) and the related group/Template references

Result: 6 blueprints, 82 routes, no dead modules or orphan templates.

Add files that deploy.sh and docker-entrypoint.sh already require but
which were never tracked:
- https_manager.py       (referenced by deploy.sh, migrate_network.sh,
                          docker-entrypoint.sh)
- Caddyfile.example      (seeded by deploy.sh; its absence aborts deploy)

Relocate generated Graphify artifacts from graphify-out/ to
docs/graphify-out/ (110 files, no content change) and archive the
superseded docs under docs/.

Ignore hygiene:
- ignore ad-hoc .env backups (.env.bak*) — they contain live secrets
- keep the pre-sanitization snapshots (docs/legacy code/,
  docs/old_code_documentation/) on disk but out of the repo

Fix .env.example: drop a duplicated config block, genericize the
hardcoded host IP, and document HOSTNAME_INTERNAL.
2026-09-11 12:18:34 +03:00

344 lines
14 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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 ""