Files
TSYSDevStack-SupportStack-L…/demo/scripts/demo-stack.sh
reachableceo 25f7a6cd75 feat(demo): migrate 5 SelfStack services to demo stack (16→24 services)
Add Reactive Resume, Metrics, Kiwix, Resume Matcher, and Apple Health
from the earlier SelfStack project. Rewrite Apple Health collector to
use InfluxDB v2 with proper error handling. Update all tests, scripts,
Homepage config, env template, and documentation for the expanded stack.

New services:
- Reactive Resume (4016) + Postgres/Minio/Chrome companions
- Metrics (4021) - GitHub metrics visualization
- Kiwix (4022) - offline wiki reader
- Resume Matcher (4023) - AI resume screening
- Apple Health (4024) - health data collector → InfluxDB v2

Also adds git policy to AGENTS.md: always commit and push automatically.

💘 Generated with Crush

Assisted-by: GLM-5.1 via Crush <crush@charm.land>
2026-05-08 12:28:56 -05:00

266 lines
8.6 KiB
Bash
Executable File

#!/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"
grep -q '^HOMEPAGE_ALLOWED_HOSTS=' "$ENV_FILE" || echo "HOMEPAGE_ALLOWED_HOSTS=*" >> "$ENV_FILE"
grep -q '^REACTIVE_RESUME_PORT=' "$ENV_FILE" || echo "REACTIVE_RESUME_PORT=4016" >> "$ENV_FILE"
grep -q '^RESUME_MINIO_PORT=' "$ENV_FILE" || echo "RESUME_MINIO_PORT=4020" >> "$ENV_FILE"
grep -q '^METRICS_PORT=' "$ENV_FILE" || echo "METRICS_PORT=4021" >> "$ENV_FILE"
grep -q '^KIWIX_PORT=' "$ENV_FILE" || echo "KIWIX_PORT=4022" >> "$ENV_FILE"
grep -q '^RESUME_MATCHER_PORT=' "$ENV_FILE" || echo "RESUME_MATCHER_PORT=4023" >> "$ENV_FILE"
grep -q '^APPLEHEALTH_PORT=' "$ENV_FILE" || echo "APPLEHEALTH_PORT=4024" >> "$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 " Metrics http://localhost:${METRICS_PORT}"
echo " Apple Health http://localhost:${APPLEHEALTH_PORT}"
echo ""
echo " Documentation:"
echo " Draw.io http://localhost:${DRAWIO_PORT}"
echo " Kroki http://localhost:${KROKI_PORT}"
echo " Kiwix http://localhost:${KIWIX_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 " Productivity:"
echo " Reactive Resume http://localhost:${REACTIVE_RESUME_PORT}"
echo " Resume Matcher http://localhost:${RESUME_MATCHER_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"
"${REACTIVE_RESUME_PORT}:ReactiveResume"
"${METRICS_PORT}:Metrics"
"${KIWIX_PORT}:Kiwix"
"${RESUME_MATCHER_PORT}:ResumeMatcher"
"${APPLEHEALTH_PORT}:AppleHealth"
)
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