46602f1933
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.
344 lines
14 KiB
Bash
Executable File
344 lines
14 KiB
Bash
Executable File
#!/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 ""
|