#!/bin/bash set -euo pipefail DEMO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" ENV_FILE="$DEMO_DIR/demo.env" ENV_TEMPLATE="$DEMO_DIR/demo.env.template" 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"; } ensure_env() { if [[ ! -f "$ENV_FILE" ]]; then if [[ -f "$ENV_TEMPLATE" ]]; then log_info "Creating demo.env from template..." cp "$ENV_TEMPLATE" "$ENV_FILE" else log_error "No demo.env or demo.env.template found" exit 1 fi fi # Ensure new variables exist in older env files grep -q '^MAILHOG_SMTP_PORT=' "$ENV_FILE" || echo "MAILHOG_SMTP_PORT=4019" >> "$ENV_FILE" } 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 if ! command -v envsubst >/dev/null 2>&1; then log_error "envsubst not found (install gettext package)" 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 unhealthy=0 while IFS= read -r name; do local health health=$(docker inspect --format='{{.State.Health.Status}}' "$name" 2>/dev/null || echo "unknown") if [[ "$health" != "healthy" ]]; then unhealthy=$((unhealthy + 1)) fi done < <(docker ps --filter "name=${COMPOSE_PROJECT_NAME:-kneldevstack}" --format '{{.Names}}' 2>/dev/null) if [[ $unhealthy -eq 0 ]]; then log_success "All services healthy" return 0 fi log_info " $unhealthy services not yet healthy (${elapsed}s elapsed)" sleep $interval elapsed=$((elapsed + interval)) done log_warn "Timeout - some services may not be fully healthy" cd "$DEMO_DIR" && docker compose ps } 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 (Web) http://localhost:${MAILHOG_PORT}" echo " MailHog (SMTP) localhost:${MAILHOG_SMTP_PORT}" echo " Atuin http://localhost:${ATUIN_PORT}" echo "" echo " Credentials: admin / demo_password" echo " FOR DEMONSTRATION PURPOSES ONLY" echo "========================================================" } smoke_test() { log_info "Running smoke tests..." set -a; source "$ENV_FILE"; set +a local ports=( "${HOMEPAGE_PORT}:Homepage" "${PIHOLE_PORT}:Pi-hole" "${DOCKHAND_PORT}:Dockhand" "${INFLUXDB_PORT}:InfluxDB" "${GRAFANA_PORT}:Grafana" "${DRAWIO_PORT}:Draw.io" "${KROKI_PORT}:Kroki" "${ATOMIC_TRACKER_PORT}:AtomicTracker" "${ARCHIVEBOX_PORT}:ArchiveBox" "${TUBE_ARCHIVIST_PORT}:TubeArchivist" "${WAKAPI_PORT}:Wakapi" "${MAILHOG_PORT}:MailHog" "${ATUIN_PORT}:Atuin" ) local pass=0 fail=0 for pt in "${ports[@]}"; do local port="${pt%:*}" local svc="${pt#*:}" if timeout 5 bash -c "echo > /dev/tcp/localhost/$port" 2>/dev/null; then log_success "$svc (:$port)" ((pass++)) || true else log_error "$svc (:$port) NOT accessible" ((fail++)) || true 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" } ensure_env case "${1:-deploy}" in deploy) detect_user check_prerequisites generate_compose deploy_stack wait_healthy display_summary smoke_test ;; stop) stop_stack ;; restart) stop_stack sleep 5 detect_user check_prerequisites 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