From be03c9592958d3b85e30a6ade9ef9a240ca44937 Mon Sep 17 00:00:00 2001 From: reachableceo Date: Fri, 1 May 2026 09:50:40 -0500 Subject: [PATCH] fix(demo): harden deployment scripts, remove duplicate fix-and-ship.sh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit demo-stack.sh: - Add ensure_env() to create demo.env from template if missing - Add envsubst prerequisite check - Fix wait_healthy() to use docker inspect instead of fragile sed/awk parsing of docker ps output - Fix smoke_test() to use env vars instead of hardcoded ports - Remove fix_env() which overwrote TA_HOST with wrong value - Add MailHog SMTP port to display_summary() - Add service names to smoke test output demo-test.sh: - Fix security compliance test to expect only 1 socket mount (proxy only, now that Dockhand uses DOCKER_HOST) - Add Dockhand proxy routing check - Fix arithmetic increment operators for set -e compatibility - Remove scripts/fix-and-ship.sh (was identical copy of demo-stack.sh) 💘 Generated with Crush Assisted-by: GLM-5.1 via Crush --- demo/scripts/demo-stack.sh | 87 ++++++++------ demo/scripts/demo-test.sh | 28 +++-- demo/scripts/fix-and-ship.sh | 223 ----------------------------------- 3 files changed, 71 insertions(+), 267 deletions(-) delete mode 100755 demo/scripts/fix-and-ship.sh diff --git a/demo/scripts/demo-stack.sh b/demo/scripts/demo-stack.sh index 08a6013..5d8ad45 100755 --- a/demo/scripts/demo-stack.sh +++ b/demo/scripts/demo-stack.sh @@ -3,6 +3,7 @@ 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" @@ -17,17 +18,16 @@ 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" +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 } detect_user() { @@ -48,6 +48,10 @@ check_prerequisites() { 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 @@ -79,26 +83,25 @@ 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 + 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}} {{.Status}}" 2>/dev/null | sed 's/(healthy)/healthy/g; s/(unhealthy)/unhealthy/g; s/(health: starting)/starting/g') - if $all_ok; then + 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 " Still waiting... (${elapsed}s elapsed)" + 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" - docker ps --filter "name=${COMPOSE_PROJECT_NAME:-kneldevstack}" --format "table {{.Names}}\t{{.Status}}" + cd "$DEMO_DIR" && docker compose ps } display_summary() { @@ -126,10 +129,11 @@ display_summary() { 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 " MailHog (Web) http://localhost:${MAILHOG_PORT}" + echo " MailHog (SMTP) localhost:${MAILHOG_SMTP_PORT}" echo " Atuin http://localhost:${ATUIN_PORT}" echo "" - echo " Credentials: ${DEMO_ADMIN_USER:-admin} / ${DEMO_ADMIN_PASSWORD:-demo_password}" + echo " Credentials: admin / demo_password" echo " FOR DEMONSTRATION PURPOSES ONLY" echo "========================================================" } @@ -137,15 +141,31 @@ display_summary() { 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 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 port in "${ports[@]}"; do + 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 "Port $port accessible" - ((pass++)) + log_success "$svc (:$port)" + ((pass++)) || true else - log_error "Port $port NOT accessible" - ((fail++)) + log_error "$svc (:$port) NOT accessible" + ((fail++)) || true fi done echo "" @@ -179,9 +199,10 @@ show_usage() { echo " help Show this help" } +ensure_env + case "${1:-deploy}" in deploy) - fix_env detect_user check_prerequisites generate_compose @@ -196,8 +217,8 @@ case "${1:-deploy}" in restart) stop_stack sleep 5 - fix_env detect_user + check_prerequisites generate_compose deploy_stack wait_healthy diff --git a/demo/scripts/demo-test.sh b/demo/scripts/demo-test.sh index 5e00aa3..40c62f4 100755 --- a/demo/scripts/demo-test.sh +++ b/demo/scripts/demo-test.sh @@ -21,10 +21,10 @@ TESTS_FAILED=0 TESTS_TOTAL=0 log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } -log_success() { echo -e "${GREEN}[PASS]${NC} $1"; ((TESTS_PASSED++)); } +log_success() { echo -e "${GREEN}[PASS]${NC} $1"; ((TESTS_PASSED++)) || true; } log_warning() { echo -e "${YELLOW}[WARN]${NC} $1"; } -log_error() { echo -e "${RED}[FAIL]${NC} $1"; ((TESTS_FAILED++)); } -log_test() { echo -e "${BLUE}[TEST]${NC} $1"; ((TESTS_TOTAL++)); } +log_error() { echo -e "${RED}[FAIL]${NC} $1"; ((TESTS_FAILED++)) || true; } +log_test() { echo -e "${BLUE}[TEST]${NC} $1"; ((TESTS_TOTAL++)) || true; } test_file_ownership() { log_test "File ownership (no root-owned files)" @@ -83,7 +83,7 @@ test_service_health() { log_success "$name running" else log_error "$name not running: $line" - ((unhealthy++)) + ((unhealthy++)) || true fi done < <(docker ps --filter "name=${COMPOSE_PROJECT_NAME:-kneldevstack}" --format "{{.Names}} {{.Status}}" 2>/dev/null) if [[ $unhealthy -eq 0 ]]; then @@ -120,7 +120,7 @@ test_port_accessibility() { log_success "$svc (:$port)" else log_error "$svc (:$port) not accessible" - ((failed++)) + ((failed++)) || true fi done if [[ $failed -eq 0 ]]; then @@ -168,14 +168,20 @@ test_security_compliance() { log_error "Docker socket proxy not found" fi - # Count direct socket mounts - proxy + dockhand are expected + # Count direct socket mounts - only proxy should have one local socket_mounts - socket_mounts=$(grep -c "/var/run/docker.sock" "$COMPOSE_FILE" || echo "0") - local expected_mounts=2 # proxy (ro) + dockhand (rw for management) - if [[ "$socket_mounts" -le "$expected_mounts" ]]; then - log_success "Socket mounts within expected range ($socket_mounts)" + socket_mounts=$(grep -c '/var/run/docker.sock' "$COMPOSE_FILE" || echo "0") + if [[ "$socket_mounts" -le 1 ]]; then + log_success "Socket mount on proxy only ($socket_mounts)" else - log_warning "Unexpected socket mounts: $socket_mounts (expected <= $expected_mounts)" + log_error "Unexpected socket mounts: $socket_mounts (expected 1, proxy only)" + fi + + # Dockhand uses proxy, not direct socket + if grep -q 'DOCKER_HOST=tcp://docker-socket-proxy' "$COMPOSE_FILE"; then + log_success "Dockhand routes through socket proxy" + else + log_error "Dockhand not using socket proxy" fi } diff --git a/demo/scripts/fix-and-ship.sh b/demo/scripts/fix-and-ship.sh deleted file mode 100755 index 08a6013..0000000 --- a/demo/scripts/fix-and-ship.sh +++ /dev/null @@ -1,223 +0,0 @@ -#!/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