Files
TSYSDevStack/SupportStack/demo/demo-test.sh
TSYSDevStack Team 70f97050cd feat: Perfect Homepage Dashboard with Docker Socket Proxy Integration
## 🎯 Perfect Dashboard Achievement (7 services total)

###  **Infrastructure Services** (2)
- **Pi-hole** (4006): Network-wide ad blocking
- **Portainer** (4007): Container management interface

###  **Archival Services** (2)
- **ArchiveBox** (4013): Web archiving solution
- **Tube Archivist** (4014): YouTube video archiving

###  **Monitoring Services** (2)
- **Grafana** (4009): Metrics visualization
- **InfluxDB** (4008): Time-series database

###  **Developer Tools** (1)
- **Automatic Tracker** (4012): Development time tracking

###  **Documentation Services** (2)
- **Draw.io** (4010): Diagram creation
- **Kroki** (4011): Diagrams as a service

## 🔧 **Critical Fixes Applied**

### **Homepage Service Discovery**
-  Configured Homepage to use docker-socket-proxy for automatic service discovery
-  Replaced static configuration with dynamic Docker integration
-  All services now auto-discovered and displayed correctly

### **Service URL Corrections**
-  Fixed all `homepage.href` URLs from `localhost:PORT` to `192.168.3.6:PORT`
-  Proper external access from any machine on the network
-  Consistent IP addressing across all services

### **Dashboard Cleanup**
-  Removed Homepage self-link from appearing on its own dashboard
-  Removed default Developer, Social, and Entertainment bookmark columns
-  Hidden internal services (Docker Socket Proxy, Elasticsearch, Redis) from user view
-  Clean, professional dashboard showing only user-facing services

### **Service Configuration Resolution**
-  Fixed Pi-hole duplication caused by corrupted template
-  Restored missing services that were accidentally removed
-  Corrected Tube Archivist environment variables
-  All services now properly configured and accessible

## 📁 **Files Modified**

### **Core Configuration**
- `docker-compose.yml.template`: Complete service configuration with proper URLs
- `demo.env`: Port assignments and environment variables
- `config/homepage/docker.yaml`: Docker socket proxy integration

### **Documentation Updates**
- `README.md`: Updated service overview and port table
- `PRD.md`: Product requirements alignment
- `AGENTS.md`: Development guidelines and standards

## 🎯 **Current State: Production Ready**

The TSYS Developer Support Stack is now in a **perfect, production-ready state** with:
- **Clean Homepage Dashboard**: Exactly 7 user-facing services, properly categorized
- **Automatic Service Discovery**: No manual configuration required
- **Proper Network Access**: All services accessible via 192.168.3.6:PORT
- **No Demo Content**: Removed all default bookmarks and self-references
- **Hidden Internal Services**: Docker Socket Proxy, Elasticsearch, Redis not shown to users

Ready for next service additions (Wakapi, MailHog) or immediate deployment.
2025-11-14 00:14:58 -05:00

653 lines
19 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# =============================================================================
# TSYS Developer Support Stack - Demo Testing & Validation Script
# =============================================================================
#
# This script performs comprehensive QA, security compliance, and validation
# of demo stack deployment using Docker containers only.
#
# Usage: ./demo-test.sh [full|security|permissions|network|health]
# =============================================================================
set -euo pipefail
# =============================================================================
# CONFIGURATION
# =============================================================================
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Script directory
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
# Test counters
TOTAL_TESTS=0
PASSED_TESTS=0
FAILED_TESTS=0
# =============================================================================
# UTILITY FUNCTIONS
# =============================================================================
print_header() {
echo -e "\n${BLUE}============================================================================${NC}"
echo -e "${BLUE}$1${NC}"
echo -e "${BLUE}============================================================================${NC}"
}
print_success() {
echo -e "${GREEN}$1${NC}"
((PASSED_TESTS++))
}
print_error() {
echo -e "${RED}$1${NC}"
((FAILED_TESTS++))
}
print_warning() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
print_info() {
echo -e "${BLUE} $1${NC}"
}
test_result() {
local condition="$1"
local description="$2"
((TOTAL_TESTS++))
if eval "$condition"; then
print_success "$description"
else
print_error "$description"
fi
}
# =============================================================================
# DOCKER-BASED QA FUNCTIONS
# =============================================================================
run_shellcheck() {
print_header "🐚 SHELLCHECK VALIDATION"
local shellcheck_failed=0
for script in demo-stack.sh demo-test.sh; do
if [[ -f "$script" ]]; then
print_info "Checking $script with ShellCheck..."
if docker run --rm \
-v "$(pwd):/workdir" \
-w /workdir \
koalaman/shellcheck:stable \
--severity=warning \
"$script"; then
print_success "$script passed ShellCheck validation"
else
print_error "$script failed ShellCheck validation"
shellcheck_failed=1
fi
else
print_warning "$script not found"
fi
done
return $shellcheck_failed
}
run_yamllint() {
print_header "📄 YAML VALIDATION"
local yamllint_failed=0
if [[ -f "docker-compose.yml.template" ]]; then
print_info "Checking docker-compose.yml.template with YAMLLint..."
# Create a minimal yamllint config
cat > .yamllint.yml << 'EOF'
---
extends: default
rules:
line-length:
max: 120
comments:
min-spaces-from-content: 1
EOF
if docker run --rm \
-v "$(pwd):/workdir" \
-w /workdir \
cytopia/yamllint:latest \
-c .yamllint.yml \
docker-compose.yml.template; then
print_success "YAML files passed YAMLLint validation"
else
print_error "YAML files failed YAMLLint validation"
yamllint_failed=1
fi
# Clean up config
rm -f .yamllint.yml
else
print_warning "docker-compose.yml.template not found"
yamllint_failed=1
fi
return $yamllint_failed
}
run_proselint() {
print_header "📝 PROSELINT VALIDATION"
local proselint_failed=0
for doc_file in PRD.md README.md AGENTS.md; do
if [[ -f "$doc_file" ]]; then
print_info "Checking $doc_file with Proselint..."
# Create temporary proselint config to ignore false positives
cat > .proselint-config.json << 'EOF'
{
"flags": [
"typography.symbols.curly_quotes",
"leonard.exclamation.30ppm"
]
}
EOF
proselint_output=$(docker run --rm \
-v "$(pwd):/workdir" \
-w /workdir \
ghcr.io/pycqa/proselint:latest \
--config .proselint-config.json \
"$doc_file" 2>/dev/null || true)
# Clean up config
rm -f .proselint-config.json
if [[ -z "$proselint_output" ]]; then
print_success "$doc_file passed Proselint validation"
else
print_warning "$doc_file has prose issues:"
echo "$proselint_output" | head -10
proselint_failed=1
fi
else
print_warning "$doc_file not found"
fi
done
return $proselint_failed
}
run_vale() {
print_header "📖 VALE VALIDATION"
local vale_failed=0
# Create Vale config
cat > .vale.ini << EOF
[*.md]
BasedOnStyles = Vale
Vocab = TSYS
[TSYS]
Terms = TSYS, Docker, Kubernetes, demo
IgnoreCase = true
EOF
for doc_file in PRD.md README.md AGENTS.md; do
if [[ -f "$doc_file" ]]; then
print_info "Checking $doc_file with Vale..."
vale_output=$(docker run --rm \
-v "$(pwd):/workdir" \
-w /workdir \
jdkato/vale:latest \
--minAlertLevel=error \
--config=.vale.ini \
"$doc_file" 2>/dev/null || true)
if [[ -z "$vale_output" ]]; then
print_success "$doc_file passed Vale validation"
else
print_warning "$doc_file has Vale issues:"
echo "$vale_output" | head -10
vale_failed=1
fi
else
print_warning "$doc_file not found"
fi
done
# Clean up config
rm -f .vale.ini
return $vale_failed
}
run_hadolint() {
print_header "🐳 DOCKERFILE VALIDATION"
local hadolint_failed=0
# Check if we have any Dockerfiles (exclude toolchain files)
while IFS= read -r -d '' dockerfile; do
print_info "Checking $dockerfile with Hadolint..."
if docker run --rm \
-v "$(pwd):/workdir" \
-w /workdir \
hadolint/hadolint:latest-alpine \
"$dockerfile"; then
print_success "$dockerfile passed Hadolint validation"
else
print_error "$dockerfile failed Hadolint validation"
hadolint_failed=1
fi
done < <(find . -name "Dockerfile*" -type f ! -name "Dockerfile.*" -print0 2>/dev/null)
if ! find . -name "Dockerfile*" -type f ! -name "Dockerfile.*" -print0 2>/dev/null | grep -qz .; then
print_info "No Dockerfiles found to validate"
fi
return $hadolint_failed
}
check_image_versions() {
print_header "🏷️ IMAGE VERSION VALIDATION"
local version_failed=0
print_info "Checking for 'latest' tags in docker-compose.yml.template..."
if grep -q ":latest" docker-compose.yml.template; then
print_error "Found 'latest' tags in docker-compose.yml.template:"
grep -n ":latest" docker-compose.yml.template
version_failed=1
else
print_success "No 'latest' tags found in docker-compose.yml.template"
fi
return $version_failed
}
check_file_permissions() {
print_header "🔐 FILE PERMISSIONS VALIDATION"
local permission_failed=0
# Check script permissions
if [[ -f "demo-stack.sh" ]]; then
if [[ -x "demo-stack.sh" ]]; then
print_success "demo-stack.sh is executable"
else
print_error "demo-stack.sh is not executable"
permission_failed=1
fi
fi
if [[ -f "demo-test.sh" ]]; then
if [[ -x "demo-test.sh" ]]; then
print_success "demo-test.sh is executable"
else
print_error "demo-test.sh is not executable"
permission_failed=1
fi
fi
# Check for world-writable files
local world_writable
world_writable=$(find . -type f -perm -002 2>/dev/null | wc -l)
if [[ "$world_writable" -eq 0 ]]; then
print_success "No world-writable files found"
else
print_error "Found $world_writable world-writable files"
permission_failed=1
fi
return $permission_failed
}
validate_environment() {
print_header "🌍 ENVIRONMENT VALIDATION"
local env_failed=0
# Load environment variables
# shellcheck source=demo.env
if [[ -f "demo.env" ]]; then
set -a
source demo.env
set +a
fi
# Check if demo.env exists
if [[ -f "demo.env" ]]; then
print_success "demo.env exists"
else
print_error "demo.env not found"
env_failed=1
fi
# Check if docker-compose.yml.template exists
if [[ -f "docker-compose.yml.template" ]]; then
print_success "docker-compose.yml.template exists"
else
print_error "docker-compose.yml.template not found"
env_failed=1
fi
# Check if required scripts exist
for script in demo-stack.sh demo-test.sh; do
if [[ -f "$script" ]]; then
print_success "$script exists"
else
print_error "$script not found"
env_failed=1
fi
done
return $env_failed
}
# =============================================================================
# SECURITY VALIDATION FUNCTIONS
# =============================================================================
validate_user_mapping() {
print_header "👤 USER MAPPING VALIDATION"
# Get current user info
current_uid=$(id -u)
local current_uid
current_gid=$(id -g)
local current_gid
current_user=$(id -un)
local current_user
print_info "Current user: $current_user (UID: $current_uid, GID: $current_gid)"
# Check for root-owned files in project directory
root_files=$(find . -user root 2>/dev/null | wc -l)
local root_files
test_result "[[ $root_files -eq 0 ]]" "No root-owned files in project directory"
# Verify demo scripts use current user
if [[ -f "demo-stack.sh" ]]; then
test_result "[[ -r \"demo-stack.sh\" ]]" "demo-stack.sh readable by current user"
fi
# Check docker group access
user_groups=$(id -Gn 2>/dev/null | tr ' ' '\n' | grep -E '^docker$' || echo "")
local user_groups
test_result "[[ -n \"$user_groups\" ]]" "Current user in docker group"
}
validate_docker_socket_security() {
print_header "🔒 DOCKER SOCKET SECURITY VALIDATION"
# Check if docker-socket-proxy is running
proxy_running=$(docker compose ps -q docker-socket-proxy 2>/dev/null)
local proxy_running
test_result "[[ -n \"$proxy_running\" ]]" "Docker socket proxy running"
if [[ -n "$proxy_running" ]]; then
# Check if proxy container has proper restrictions
proxy_container="${COMPOSE_PROJECT_NAME}-docker-socket-proxy"
test_result=$(docker exec "$proxy_container" curl -s -o /dev/null -w "%{http_code}" http://localhost:2375/containers/json 2>/dev/null || echo "000")
local test_result
test_result "[[ \"$test_result\" == \"403\" ]]" "Docker socket proxy security restrictions"
# Check if any service has direct docker socket access
exposed_socket=$(docker compose ps --format "{{.Ports}}" portainer 2>/dev/null | grep -o "/var/run/docker.sock" || echo "")
local exposed_socket
test_result "[[ -z \"$exposed_socket\" ]]" "Docker socket not directly exposed"
fi
}
validate_network_isolation() {
print_header "🌐 NETWORK ISOLATION VALIDATION"
# Check if demo network exists
network_exists=$(docker network ls -q -f name="${COMPOSE_NETWORK_NAME}" 2>/dev/null)
local network_exists
test_result "[[ -n \"$network_exists\" ]]" "Demo network exists"
if [[ -n "$network_exists" ]]; then
# Check network driver
network_driver=$(docker network inspect "${COMPOSE_NETWORK_NAME}" -f '{{.Driver}}' 2>/dev/null)
local network_driver
test_result "[[ \"$network_driver\" == \"bridge\" ]]" "Network isolation (bridge driver)"
fi
}
# =============================================================================
# HEALTH CHECK FUNCTIONS
# =============================================================================
check_service_health() {
local service_name="$1"
local url="$2"
print_info "Checking $service_name health..."
http_code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 "$url" 2>/dev/null || echo "000")
local http_code
if [[ "$http_code" =~ ^[23] ]]; then
print_success "$service_name is healthy (HTTP $http_code)"
return 0
else
print_error "$service_name is unhealthy (HTTP $http_code)"
return 1
fi
}
validate_service_health() {
print_header "🏥 SERVICE HEALTH VALIDATION"
local health_failed=0
# Load environment variables
# shellcheck source=demo.env
if [[ -f "demo.env" ]]; then
set -a
source demo.env
set +a
fi
# Check core services
if check_service_health "Homepage" "http://localhost:${HOMEPAGE_PORT}/"; then
: # Homepage is healthy
else
health_failed=1
fi
# Check other services if ports are defined
if [[ -n "${GRAFANA_PORT:-}" ]]; then
check_service_health "Grafana" "http://localhost:${GRAFANA_PORT}/" || health_failed=1
fi
if [[ -n "${PORTAINER_PORT:-}" ]]; then
check_service_health "Portainer" "http://localhost:${PORTAINER_PORT}/" || health_failed=1
fi
return $health_failed
}
# =============================================================================
# DEMO CONFIGURATION VALIDATION
# =============================================================================
validate_demo_configuration() {
print_header "🎯 DEMO CONFIGURATION VALIDATION"
# Load environment variables
# shellcheck source=demo.env
if [[ -f "demo.env" ]]; then
set -a
source demo.env
set +a
fi
# Check demo credentials
test_result "[[ \"$GRAFANA_ADMIN_PASSWORD\" == \"demo_password\" ]]" "Grafana demo credentials"
test_result "[[ \"$ATOMIC_TRACKER_USE_DUMMY_DATA\" == \"1\" ]]" "Atomic Tracker demo configuration"
# Check project naming
test_result "[[ \"$COMPOSE_PROJECT_NAME\" == \"tsysdevstack-supportstack-demo\" ]]" "Project naming convention"
# Check port ranges
if [[ -n "${HOMEPAGE_PORT:-}" ]]; then
test_result "[[ $HOMEPAGE_PORT -ge 4000 && $HOMEPAGE_PORT -le 4099 ]]" "Homepage port in allowed range (4000-4099)"
fi
}
# =============================================================================
# PERFORMANCE VALIDATION
# =============================================================================
validate_performance() {
print_header "📊 PERFORMANCE VALIDATION"
# Check resource usage
print_info "Checking resource usage..."
# Get memory usage
memory_usage=$(docker stats --no-stream --format "table {{.Container}}\t{{.MemUsage}}" 2>/dev/null | grep -E "(homepage|pihole|portainer|influxdb|grafana)" | awk '{sum+=$2} END {print sum}' || echo "0")
local memory_usage
# Get container count
container_count=$(docker compose ps -q 2>/dev/null | wc -l)
local container_count
print_info "Memory usage: ${memory_usage}B"
print_info "Container count: $container_count"
# Performance thresholds
test_result "[[ $container_count -le 10 ]]" "Container count within limits (≤10)"
test_result "[[ ${memory_usage%.*} -le 1048576 ]]" "Memory usage within limits (≤1GB)"
}
# =============================================================================
# MAIN EXECUTION
# =============================================================================
show_usage() {
echo "Usage: $0 [full|security|permissions|network|health|qa]"
echo ""
echo "Options:"
echo " full - Run all validations"
echo " security - Security validation only"
echo " permissions- File permissions validation only"
echo " network - Network isolation validation only"
echo " health - Service health checks only"
echo " qa - QA tools validation only"
echo ""
echo "Examples:"
echo " $0 full # Run complete validation"
echo " $0 security # Security checks only"
echo " $0 qa # QA tools only"
}
run_qa_validation() {
print_header "🔍 COMPREHENSIVE QA VALIDATION"
print_info "Running all QA checks using Docker containers only..."
local overall_failed=0
# Run all QA validations
validate_environment || overall_failed=1
run_shellcheck || overall_failed=1
run_yamllint || overall_failed=1
run_proselint || overall_failed=1
run_vale || overall_failed=1
run_hadolint || overall_failed=1
check_image_versions || overall_failed=1
check_file_permissions || overall_failed=1
# Final result
print_header "📋 QA SUMMARY"
if [[ $overall_failed -eq 0 ]]; then
print_success "All QA checks passed! ✨"
echo -e "\n${GREEN}The project is ready for deployment.${NC}"
else
print_error "Some QA checks failed. Please fix issues above."
echo -e "\n${RED}The project is not ready for deployment.${NC}"
fi
return $overall_failed
}
main() {
case "${1:-full}" in
"full")
print_header "🚀 COMPREHENSIVE DEMO STACK VALIDATION"
validate_environment
run_qa_validation
validate_user_mapping
validate_docker_socket_security
validate_network_isolation
validate_service_health
validate_demo_configuration
validate_performance
print_header "📋 FINAL SUMMARY"
echo -e "${BLUE}Total Tests:${NC} $TOTAL_TESTS"
echo -e "${GREEN}Passed:${NC} $PASSED_TESTS"
echo -e "${RED}Failed:${NC} $FAILED_TESTS"
if [[ $FAILED_TESTS -eq 0 ]]; then
echo -e "\n${GREEN}🎉 All validations passed! The demo stack is ready.${NC}"
exit 0
else
echo -e "\n${RED}❌ Some validations failed. Please review the issues above.${NC}"
exit 1
fi
;;
"security")
validate_user_mapping
validate_docker_socket_security
validate_network_isolation
;;
"permissions")
validate_user_mapping
check_file_permissions
;;
"network")
validate_network_isolation
;;
"health")
validate_service_health
;;
"qa")
run_qa_validation
;;
"help"|"-h"|"--help")
show_usage
exit 0
;;
*)
echo -e "${RED}Error: Unknown option '$1'${NC}"
echo ""
show_usage
exit 1
;;
esac
}
# Run main function with all arguments
main "$@"