#!/bin/bash set -euo pipefail DEMO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" ENV_FILE="$DEMO_DIR/demo.env" TEMPLATE_FILE="$DEMO_DIR/docker-compose.yml.template" COMPOSE_FILE="$DEMO_DIR/docker-compose.yml" RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } log_success() { echo -e "${GREEN}[OK]${NC} $1"; } log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } log_error() { echo -e "${RED}[ERROR]${NC} $1"; } fix_env() { log_info "Ensuring demo.env is complete..." grep -q '^TA_USERNAME=' "$ENV_FILE" || echo "TA_USERNAME=demo" >> "$ENV_FILE" grep -q '^TA_PASSWORD=' "$ENV_FILE" || echo "TA_PASSWORD=demo_password" >> "$ENV_FILE" grep -q '^ELASTIC_PASSWORD=' "$ENV_FILE" || echo "ELASTIC_PASSWORD=demo_password" >> "$ENV_FILE" grep -q '^ES_JAVA_OPTS=' "$ENV_FILE" || echo 'ES_JAVA_OPTS="-Xms512m -Xmx512m"' >> "$ENV_FILE" grep -q '^ARCHIVEBOX_ADMIN_USER=' "$ENV_FILE" || echo "ARCHIVEBOX_ADMIN_USER=admin" >> "$ENV_FILE" grep -q '^ARCHIVEBOX_ADMIN_PASSWORD=' "$ENV_FILE" || echo "ARCHIVEBOX_ADMIN_PASSWORD=demo_password" >> "$ENV_FILE" sed -i 's/^ATUIN_HOST=.*/ATUIN_HOST=0.0.0.0/' "$ENV_FILE" sed -i 's|^TA_HOST=.*|TA_HOST=http://localhost:4014|' "$ENV_FILE" log_success "demo.env ready" } detect_user() { log_info "Detecting user IDs..." local uid gid docker_gid uid=$(id -u) gid=$(id -g) docker_gid=$(getent group docker | cut -d: -f3) sed -i "s/^DEMO_UID=.*/DEMO_UID=$uid/" "$ENV_FILE" sed -i "s/^DEMO_GID=.*/DEMO_GID=$gid/" "$ENV_FILE" sed -i "s/^DEMO_DOCKER_GID=.*/DEMO_DOCKER_GID=$docker_gid/" "$ENV_FILE" log_success "UID=$uid GID=$gid DockerGID=$docker_gid" } check_prerequisites() { log_info "Checking prerequisites..." if ! docker info >/dev/null 2>&1; then log_error "Docker is not running" exit 1 fi local max_map_count max_map_count=$(sysctl -n vm.max_map_count 2>/dev/null || echo "0") if [[ "$max_map_count" -lt 262144 ]]; then log_warn "Setting vm.max_map_count=262144 for Elasticsearch..." if sudo sysctl -w vm.max_map_count=262144 2>/dev/null; then log_success "vm.max_map_count set" else log_warn "Could not set vm.max_map_count (TubeArchivist ES may fail)" fi fi log_success "Prerequisites OK" } generate_compose() { log_info "Generating docker-compose.yml from template..." set -a; source "$ENV_FILE"; set +a envsubst < "$TEMPLATE_FILE" > "$COMPOSE_FILE" log_success "docker-compose.yml generated" } deploy_stack() { log_info "Deploying TSYS Developer Support Stack..." cd "$DEMO_DIR" docker compose up -d 2>&1 log_success "Stack deployment initiated" } wait_healthy() { log_info "Waiting for services to become healthy (max 5 min)..." local elapsed=0 interval=15 while [[ $elapsed -lt 300 ]]; do local all_ok=true while IFS= read -r line; do local name health name=$(echo "$line" | awk '{print $1}') health=$(echo "$line" | awk '{print $2}') [[ "$name" == "NAMES" || -z "$name" ]] && continue if [[ "$health" != "healthy" && -n "$health" ]]; then all_ok=false fi done < <(docker ps --filter "name=${COMPOSE_PROJECT_NAME:-kneldevstack}" --format "{{.Names}} {{.Status}}" 2>/dev/null | sed 's/(healthy)/healthy/g; s/(unhealthy)/unhealthy/g; s/(health: starting)/starting/g') if $all_ok; then log_success "All services healthy" return 0 fi log_info " Still waiting... (${elapsed}s elapsed)" sleep $interval elapsed=$((elapsed + interval)) done log_warn "Timeout - some services may not be fully healthy" docker ps --filter "name=${COMPOSE_PROJECT_NAME:-kneldevstack}" --format "table {{.Names}}\t{{.Status}}" } display_summary() { set -a; source "$ENV_FILE"; set +a echo "" echo "========================================================" echo " TSYS Developer Support Stack - Deployment Summary" echo "========================================================" echo "" echo " Infrastructure:" echo " Homepage Dashboard http://localhost:${HOMEPAGE_PORT}" echo " Pi-hole (DNS) http://localhost:${PIHOLE_PORT}" echo " Dockhand (Docker) http://localhost:${DOCKHAND_PORT}" echo "" echo " Monitoring:" echo " InfluxDB http://localhost:${INFLUXDB_PORT}" echo " Grafana http://localhost:${GRAFANA_PORT}" echo "" echo " Documentation:" echo " Draw.io http://localhost:${DRAWIO_PORT}" echo " Kroki http://localhost:${KROKI_PORT}" echo "" echo " Developer Tools:" echo " Atomic Tracker http://localhost:${ATOMIC_TRACKER_PORT}" echo " ArchiveBox http://localhost:${ARCHIVEBOX_PORT}" echo " Tube Archivist http://localhost:${TUBE_ARCHIVIST_PORT}" echo " Wakapi http://localhost:${WAKAPI_PORT}" echo " MailHog http://localhost:${MAILHOG_PORT}" echo " Atuin http://localhost:${ATUIN_PORT}" echo "" echo " Credentials: ${DEMO_ADMIN_USER:-admin} / ${DEMO_ADMIN_PASSWORD:-demo_password}" echo " FOR DEMONSTRATION PURPOSES ONLY" echo "========================================================" } smoke_test() { log_info "Running smoke tests..." set -a; source "$ENV_FILE"; set +a local ports=(4000 4006 4007 4008 4009 4010 4011 4012 4013 4014 4015 4017 4018) local pass=0 fail=0 for port in "${ports[@]}"; do if timeout 5 bash -c "echo > /dev/tcp/localhost/$port" 2>/dev/null; then log_success "Port $port accessible" ((pass++)) else log_error "Port $port NOT accessible" ((fail++)) fi done echo "" echo "SMOKE TEST: $pass passed, $fail failed" } stop_stack() { log_info "Stopping stack..." cd "$DEMO_DIR" docker compose down 2>&1 log_success "Stack stopped" } show_status() { cd "$DEMO_DIR" docker compose ps } show_usage() { echo "TSYS Developer Support Stack" echo "" echo "Usage: $0 {deploy|stop|restart|status|smoke|summary|help}" echo "" echo "Commands:" echo " deploy Deploy the complete stack" echo " stop Stop all services" echo " restart Stop and redeploy" echo " status Show service status" echo " smoke Run port accessibility tests" echo " summary Show service URLs" echo " help Show this help" } case "${1:-deploy}" in deploy) fix_env detect_user check_prerequisites generate_compose deploy_stack wait_healthy display_summary smoke_test ;; stop) stop_stack ;; restart) stop_stack sleep 5 fix_env detect_user generate_compose deploy_stack wait_healthy display_summary ;; status) show_status ;; smoke) smoke_test ;; summary) display_summary ;; help|--help|-h) show_usage ;; *) log_error "Unknown command: $1" show_usage exit 1 ;; esac