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 <crush@charm.land>
256 lines
7.5 KiB
Bash
Executable File
256 lines
7.5 KiB
Bash
Executable File
#!/bin/bash
|
|
# TSYS Developer Support Stack - Demo Testing Script
|
|
# Version: 2.0
|
|
# Purpose: Comprehensive QA and validation
|
|
|
|
set -euo pipefail
|
|
|
|
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"
|
|
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m'
|
|
|
|
TESTS_PASSED=0
|
|
TESTS_FAILED=0
|
|
TESTS_TOTAL=0
|
|
|
|
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
|
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++)) || true; }
|
|
log_test() { echo -e "${BLUE}[TEST]${NC} $1"; ((TESTS_TOTAL++)) || true; }
|
|
|
|
test_file_ownership() {
|
|
log_test "File ownership (no root-owned files)"
|
|
local root_files
|
|
root_files=$(find "$PROJECT_ROOT" -type f -user root 2>/dev/null || true)
|
|
if [[ -z "$root_files" ]]; then
|
|
log_success "No root-owned files"
|
|
else
|
|
log_error "Root-owned files found: $root_files"
|
|
fi
|
|
}
|
|
|
|
test_user_mapping() {
|
|
log_test "UID/GID detection"
|
|
source "$DEMO_ENV_FILE"
|
|
if [[ -z "${DEMO_UID:-}" || -z "${DEMO_GID:-}" ]]; then
|
|
log_error "DEMO_UID or DEMO_GID not set"
|
|
return
|
|
fi
|
|
local cur_uid cur_gid
|
|
cur_uid=$(id -u)
|
|
cur_gid=$(id -g)
|
|
if [[ "$DEMO_UID" -eq "$cur_uid" && "$DEMO_GID" -eq "$cur_gid" ]]; then
|
|
log_success "UID/GID correct ($DEMO_UID/$DEMO_GID)"
|
|
else
|
|
log_error "UID/GID mismatch: env=$DEMO_UID/$DEMO_GID actual=$cur_uid/$cur_gid"
|
|
fi
|
|
}
|
|
|
|
test_docker_group() {
|
|
log_test "Docker group access"
|
|
source "$DEMO_ENV_FILE"
|
|
if [[ -z "${DEMO_DOCKER_GID:-}" ]]; then
|
|
log_error "DEMO_DOCKER_GID not set"
|
|
return
|
|
fi
|
|
local actual_gid
|
|
actual_gid=$(getent group docker | cut -d: -f3)
|
|
if [[ "$DEMO_DOCKER_GID" -eq "$actual_gid" ]]; then
|
|
log_success "Docker GID correct ($DEMO_DOCKER_GID)"
|
|
else
|
|
log_error "Docker GID mismatch: env=$DEMO_DOCKER_GID actual=$actual_gid"
|
|
fi
|
|
}
|
|
|
|
test_service_health() {
|
|
log_test "Service health"
|
|
local unhealthy=0
|
|
while IFS= read -r line; do
|
|
local name status
|
|
name=$(echo "$line" | awk '{print $1}')
|
|
[[ "$name" == "NAMES" || -z "$name" ]] && continue
|
|
if echo "$line" | grep -q "(healthy)"; then
|
|
log_success "$name healthy"
|
|
elif echo "$line" | grep -q "Up"; then
|
|
log_success "$name running"
|
|
else
|
|
log_error "$name not running: $line"
|
|
((unhealthy++)) || true
|
|
fi
|
|
done < <(docker ps --filter "name=${COMPOSE_PROJECT_NAME:-kneldevstack}" --format "{{.Names}} {{.Status}}" 2>/dev/null)
|
|
if [[ $unhealthy -eq 0 ]]; then
|
|
log_success "All services running"
|
|
fi
|
|
}
|
|
|
|
test_port_accessibility() {
|
|
log_test "Port accessibility"
|
|
source "$DEMO_ENV_FILE"
|
|
|
|
# These are exposed to host
|
|
local port_tests=(
|
|
"$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 failed=0
|
|
for pt in "${port_tests[@]}"; 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)"
|
|
else
|
|
log_error "$svc (:$port) not accessible"
|
|
((failed++)) || true
|
|
fi
|
|
done
|
|
if [[ $failed -eq 0 ]]; then
|
|
log_success "All exposed ports accessible"
|
|
fi
|
|
}
|
|
|
|
test_network_isolation() {
|
|
log_test "Network isolation"
|
|
source "$DEMO_ENV_FILE"
|
|
if docker network ls --format '{{.Name}}' | grep -q "$COMPOSE_NETWORK_NAME"; then
|
|
log_success "Network $COMPOSE_NETWORK_NAME exists"
|
|
local driver
|
|
driver=$(docker network inspect "$COMPOSE_NETWORK_NAME" --format '{{.Driver}}' 2>/dev/null || echo "")
|
|
if [[ "$driver" == "bridge" ]]; then
|
|
log_success "Bridge driver confirmed"
|
|
else
|
|
log_warning "Driver: $driver"
|
|
fi
|
|
else
|
|
log_error "Network $COMPOSE_NETWORK_NAME not found"
|
|
fi
|
|
}
|
|
|
|
test_volume_permissions() {
|
|
log_test "Docker volumes exist"
|
|
source "$DEMO_ENV_FILE"
|
|
local vol_count
|
|
vol_count=$(docker volume ls --filter "name=${COMPOSE_PROJECT_NAME}" -q 2>/dev/null | wc -l)
|
|
if [[ $vol_count -ge 15 ]]; then
|
|
log_success "$vol_count volumes created"
|
|
else
|
|
log_error "Only $vol_count volumes found"
|
|
fi
|
|
}
|
|
|
|
test_security_compliance() {
|
|
log_test "Security compliance"
|
|
source "$DEMO_ENV_FILE"
|
|
|
|
# Docker socket proxy present
|
|
if grep -q "docker-socket-proxy" "$COMPOSE_FILE"; then
|
|
log_success "Docker socket proxy configured"
|
|
else
|
|
log_error "Docker socket proxy not found"
|
|
fi
|
|
|
|
# Count direct socket mounts - only proxy should have one
|
|
local 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_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
|
|
}
|
|
|
|
run_full_tests() {
|
|
log_info "Running comprehensive test suite..."
|
|
test_file_ownership || true
|
|
test_user_mapping || true
|
|
test_docker_group || true
|
|
test_service_health || true
|
|
test_port_accessibility || true
|
|
test_network_isolation || true
|
|
test_volume_permissions || true
|
|
test_security_compliance || true
|
|
display_test_results
|
|
}
|
|
|
|
run_security_tests() {
|
|
log_info "Running security tests..."
|
|
test_file_ownership || true
|
|
test_network_isolation || true
|
|
test_security_compliance || true
|
|
display_test_results
|
|
}
|
|
|
|
run_permission_tests() {
|
|
log_info "Running permission tests..."
|
|
test_file_ownership || true
|
|
test_user_mapping || true
|
|
test_docker_group || true
|
|
test_volume_permissions || true
|
|
display_test_results
|
|
}
|
|
|
|
run_network_tests() {
|
|
log_info "Running network tests..."
|
|
test_network_isolation || true
|
|
test_port_accessibility || true
|
|
display_test_results
|
|
}
|
|
|
|
display_test_results() {
|
|
echo ""
|
|
echo "===================================="
|
|
echo "TEST RESULTS"
|
|
echo "===================================="
|
|
echo "Total: $TESTS_TOTAL"
|
|
echo -e "Passed: ${GREEN}$TESTS_PASSED${NC}"
|
|
echo -e "Failed: ${RED}$TESTS_FAILED${NC}"
|
|
if [[ $TESTS_FAILED -eq 0 ]]; then
|
|
echo -e "\n${GREEN}ALL TESTS PASSED${NC}"
|
|
return 0
|
|
else
|
|
echo -e "\n${RED}SOME TESTS FAILED${NC}"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
main() {
|
|
case "${1:-full}" in
|
|
full) run_full_tests ;;
|
|
security) run_security_tests ;;
|
|
permissions) run_permission_tests ;;
|
|
network) run_network_tests ;;
|
|
help|--help|-h)
|
|
echo "Usage: $0 {full|security|permissions|network|help}"
|
|
;;
|
|
*) log_error "Unknown: $1"; exit 1 ;;
|
|
esac
|
|
}
|
|
|
|
main "$@"
|