fix(demo): rewrite deployment scripts and test suite for 16-service stack

Rewrite demo-stack.sh, demo-test.sh, validate-all.sh, and all test
files to match the current 16-service stack reality.

Key changes:
- demo-stack.sh: full rewrite with deploy/stop/restart/status/smoke/summary
- demo-test.sh: fix hardcoded kneldevstack filter to use $COMPOSE_PROJECT_NAME,
  raise volume threshold from 10 to 15, remove curl dependency (use /dev/tcp),
  fix security compliance check for Dockhand direct socket mount
- validate-all.sh: remove port 4005 check (internal only), add missing env
  var validation (TA_PASSWORD, ELASTIC_PASSWORD, GF_*, PIHOLE_WEBPASSWORD)
- integration tests: fix container names, add TubeArchivist companion tests
- e2e tests: use correct project-relative paths, dynamic port lists from env
- Add fix-and-ship.sh as convenience wrapper for demo-stack.sh
- Remove stale tmp_template.yml

💘 Generated with Crush

Assisted-by: GLM-5.1 via Crush <crush@charm.land>
This commit is contained in:
reachableceo
2026-04-27 13:06:45 -05:00
parent 077f483faf
commit eff78907d4
7 changed files with 694 additions and 733 deletions

View File

@@ -1,291 +1,223 @@
#!/bin/bash
# TSYS Developer Support Stack - Demo Deployment Script
# Version: 1.0
# Purpose: Dynamic deployment with user detection and validation
set -euo pipefail
# Script Configuration
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
DEMO_ENV_FILE="$PROJECT_ROOT/demo.env"
COMPOSE_FILE="$PROJECT_ROOT/docker-compose.yml"
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"
# Color Codes for Output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
NC='\033[0m'
# Logging Functions
log_info() {
echo -e "${BLUE}[INFO]${NC} $1"
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"
}
log_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
log_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Function to detect current user and group IDs
detect_user_ids() {
log_info "Detecting user and group IDs..."
local uid
local gid
local docker_gid
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)
if [[ -z "$docker_gid" ]]; then
log_error "Docker group not found. Please ensure Docker is installed and user is in docker group."
exit 1
fi
log_info "Detected UID: $uid, GID: $gid, Docker GID: $docker_gid"
# Update demo.env with detected values
sed -i "s/^DEMO_UID=$/DEMO_UID=$uid/" "$DEMO_ENV_FILE"
sed -i "s/^DEMO_GID=$/DEMO_GID=$gid/" "$DEMO_ENV_FILE"
sed -i "s/^DEMO_DOCKER_GID=$/DEMO_DOCKER_GID=$docker_gid/" "$DEMO_ENV_FILE"
log_success "User IDs detected and configured"
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"
}
# Function to validate prerequisites
validate_prerequisites() {
log_info "Validating prerequisites..."
# Check if Docker is installed and running
if ! command -v docker &> /dev/null; then
log_error "Docker is not installed or not in PATH"
check_prerequisites() {
log_info "Checking prerequisites..."
if ! docker info >/dev/null 2>&1; then
log_error "Docker is not running"
exit 1
fi
if ! docker info &> /dev/null; then
log_error "Docker daemon is not running"
exit 1
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
# Check if Docker Compose is available
if ! command -v docker-compose &> /dev/null && ! docker compose version &> /dev/null; then
log_error "Docker Compose is not installed"
exit 1
fi
# Check if demo.env exists
if [[ ! -f "$DEMO_ENV_FILE" ]]; then
log_error "demo.env file not found at $DEMO_ENV_FILE"
exit 1
fi
log_success "Prerequisites validation passed"
log_success "Prerequisites OK"
}
# Function to generate docker-compose.yml from template
generate_compose_file() {
log_info "Generating docker-compose.yml..."
# Check if template exists (will be created in next phase)
local template_file="$PROJECT_ROOT/docker-compose.yml.template"
if [[ ! -f "$template_file" ]]; then
log_error "Docker Compose template not found at $template_file"
log_info "Please ensure the template file is created before running deployment"
exit 1
fi
# Source and export environment variables
# shellcheck disable=SC1090,SC1091
set -a
source "$DEMO_ENV_FILE"
set +a
# Generate docker-compose.yml from template
envsubst < "$template_file" > "$COMPOSE_FILE"
log_success "docker-compose.yml generated successfully"
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"
}
# Function to deploy the stack
deploy_stack() {
log_info "Deploying TSYS Developer Support Stack..."
# Change to project directory
cd "$PROJECT_ROOT"
# Deploy the stack
if command -v docker-compose &> /dev/null; then
docker-compose -f "$COMPOSE_FILE" up -d
else
docker compose -f "$COMPOSE_FILE" up -d
fi
cd "$DEMO_DIR"
docker compose up -d 2>&1
log_success "Stack deployment initiated"
}
# Function to wait for services to be healthy
wait_for_services() {
log_info "Waiting for services to become healthy..."
local max_wait=300 # 5 minutes
local wait_interval=10
local elapsed=0
while [[ $elapsed -lt $max_wait ]]; do
local unhealthy_services=0
# Check service health (will be implemented with actual service names)
if command -v docker-compose &> /dev/null; then
mapfile -t services < <(docker-compose -f "$COMPOSE_FILE" config --services)
else
mapfile -t services < <(docker compose -f "$COMPOSE_FILE" config --services)
fi
for service in "${services[@]}"; do
local health_status
if command -v docker-compose &> /dev/null; then
health_status=$(docker-compose -f "$COMPOSE_FILE" ps -q "$service" | xargs docker inspect --format='{{.State.Health.Status}}' 2>/dev/null || echo "none")
else
health_status=$(docker compose -f "$COMPOSE_FILE" ps -q "$service" | xargs docker inspect --format='{{.State.Health.Status}}' 2>/dev/null || echo "none")
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
if [[ "$health_status" != "healthy" && "$health_status" != "none" ]]; then
((unhealthy_services++))
fi
done
if [[ $unhealthy_services -eq 0 ]]; then
log_success "All services are healthy"
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 "$unhealthy_services services still unhealthy... waiting ${wait_interval}s"
sleep $wait_interval
elapsed=$((elapsed + wait_interval))
log_info " Still waiting... (${elapsed}s elapsed)"
sleep $interval
elapsed=$((elapsed + interval))
done
log_warning "Timeout reached. Some services may not be fully healthy."
return 1
log_warn "Timeout - some services may not be fully healthy"
docker ps --filter "name=${COMPOSE_PROJECT_NAME:-kneldevstack}" --format "table {{.Names}}\t{{.Status}}"
}
# Function to display deployment summary
display_summary() {
log_success "TSYS Developer Support Stack Deployment Summary"
echo "=================================================="
echo "📊 Homepage Dashboard: http://localhost:${HOMEPAGE_PORT:-4000}"
echo "🏗️ Infrastructure Services:"
echo " - Pi-hole (DNS): http://localhost:${PIHOLE_PORT:-4006}"
echo " - Dockhand (Containers): http://localhost:${DOCKHAND_PORT:-4007}"
echo "📊 Monitoring & Observability:"
echo " - InfluxDB (Database): http://localhost:${INFLUXDB_PORT:-4008}"
echo " - Grafana (Visualization): http://localhost:${GRAFANA_PORT:-4009}"
echo "📚 Documentation & Diagramming:"
echo " - Draw.io (Diagrams): http://localhost:${DRAWIO_PORT:-4010}"
echo " - Kroki (Diagrams as Service): http://localhost:${KROKI_PORT:-4011}"
echo "🛠️ Developer Tools:"
echo " - Atomic Tracker (Habits): http://localhost:${ATOMIC_TRACKER_PORT:-4012}"
echo " - ArchiveBox (Archiving): http://localhost:${ARCHIVEBOX_PORT:-4013}"
echo " - Tube Archivist (YouTube): http://localhost:${TUBE_ARCHIVIST_PORT:-4014}"
echo " - Wakapi (Time Tracking): http://localhost:${WAKAPI_PORT:-4015}"
echo " - MailHog (Email Testing): http://localhost:${MAILHOG_PORT:-4017}"
echo " - Atuin (Shell History): http://localhost:${ATUIN_PORT:-4018}"
echo "=================================================="
echo "🔐 Demo Credentials:"
echo " Username: ${DEMO_ADMIN_USER:-admin}"
echo " Password: ${DEMO_ADMIN_PASSWORD:-demo_password}"
echo "⚠️ FOR DEMONSTRATION PURPOSES ONLY - NOT FOR PRODUCTION"
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"
}
# Function to stop the stack
stop_stack() {
log_info "Stopping TSYS Developer Support Stack..."
cd "$PROJECT_ROOT"
if command -v docker-compose &> /dev/null; then
docker-compose -f "$COMPOSE_FILE" down
else
docker compose -f "$COMPOSE_FILE" down
fi
log_info "Stopping stack..."
cd "$DEMO_DIR"
docker compose down 2>&1
log_success "Stack stopped"
}
# Function to restart the stack
restart_stack() {
log_info "Restarting TSYS Developer Support Stack..."
stop_stack
sleep 5
deploy_stack
wait_for_services
display_summary
show_status() {
cd "$DEMO_DIR"
docker compose ps
}
# Function to show usage
show_usage() {
echo "Usage: $0 {deploy|stop|restart|status|help}"
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 - Restart all services"
echo " status - Show service status"
echo " help - Show this help message"
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"
}
# Function to show status
show_status() {
log_info "TSYS Developer Support Stack Status"
echo "===================================="
cd "$PROJECT_ROOT"
if command -v docker-compose &> /dev/null; then
docker-compose -f "$COMPOSE_FILE" ps
else
docker compose -f "$COMPOSE_FILE" ps
fi
}
# Main script execution
main() {
case "${1:-deploy}" in
deploy)
validate_prerequisites
detect_user_ids
generate_compose_file
deploy_stack
wait_for_services
display_summary
;;
stop)
stop_stack
;;
restart)
restart_stack
;;
status)
show_status
;;
help|--help|-h)
show_usage
;;
*)
log_error "Unknown command: $1"
show_usage
exit 1
;;
esac
}
# Execute main function with all arguments
main "$@"
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