- hostctl.sh management script for starting/stopping/restarting hosts - test.coppertone.tech domain setup with compose.yaml - deploy.sh for automated deployments from testing branch - frontend-nginx.conf for static file serving 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
499 lines
12 KiB
Bash
Executable File
499 lines
12 KiB
Bash
Executable File
#!/bin/bash
|
|
#
|
|
# Web Host Management Script
|
|
# Manages podman-compose based web hosts on 127.0.0.1
|
|
# Each root domain has its own compose file in /docker/web-hosts/domains/<domain>/
|
|
#
|
|
set -e
|
|
|
|
# Colors
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
CYAN='\033[0;36m'
|
|
NC='\033[0m'
|
|
|
|
# Configuration
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
HOSTS_DIR="$(dirname "$SCRIPT_DIR")"
|
|
DOMAINS_DIR="$HOSTS_DIR/domains"
|
|
NGINX_STARTUP="/docker/www/startup.sh"
|
|
|
|
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"; }
|
|
log_header() { echo -e "${CYAN}=== $1 ===${NC}"; }
|
|
|
|
# Get domain directory
|
|
get_domain_dir() {
|
|
local domain=$1
|
|
echo "$DOMAINS_DIR/$domain"
|
|
}
|
|
|
|
# Check if domain exists
|
|
domain_exists() {
|
|
local domain=$1
|
|
local domain_dir=$(get_domain_dir "$domain")
|
|
[ -d "$domain_dir" ] && [ -f "$domain_dir/compose.yaml" ]
|
|
}
|
|
|
|
# List all domains
|
|
list_domains() {
|
|
log_header "Configured Domains"
|
|
echo ""
|
|
|
|
if [ ! -d "$DOMAINS_DIR" ] || [ -z "$(ls -A "$DOMAINS_DIR" 2>/dev/null)" ]; then
|
|
log_warn "No domains configured yet."
|
|
echo ""
|
|
echo "To create a new domain, run:"
|
|
echo " $0 create <domain>"
|
|
return
|
|
fi
|
|
|
|
for domain_dir in "$DOMAINS_DIR"/*/; do
|
|
if [ -d "$domain_dir" ]; then
|
|
domain=$(basename "$domain_dir")
|
|
compose_file="$domain_dir/compose.yaml"
|
|
|
|
if [ -f "$compose_file" ]; then
|
|
# Check running status
|
|
cd "$domain_dir"
|
|
running_count=$(podman-compose ps -q 2>/dev/null | wc -l)
|
|
|
|
if [ "$running_count" -gt 0 ]; then
|
|
status="${GREEN}RUNNING${NC} ($running_count containers)"
|
|
else
|
|
status="${YELLOW}STOPPED${NC}"
|
|
fi
|
|
|
|
echo -e " $domain: $status"
|
|
|
|
# Show services
|
|
if [ -f "$compose_file" ]; then
|
|
services=$(grep -E "^\s+[a-zA-Z0-9_-]+:\s*$" "$compose_file" | sed 's/://g' | xargs)
|
|
if [ -n "$services" ]; then
|
|
echo " Services: $services"
|
|
fi
|
|
fi
|
|
echo ""
|
|
fi
|
|
fi
|
|
done
|
|
}
|
|
|
|
# Start a domain
|
|
start_domain() {
|
|
local domain=$1
|
|
|
|
if [ -z "$domain" ]; then
|
|
log_error "Usage: $0 start <domain>"
|
|
return 1
|
|
fi
|
|
|
|
if ! domain_exists "$domain"; then
|
|
log_error "Domain '$domain' not found. Create it first with: $0 create $domain"
|
|
return 1
|
|
fi
|
|
|
|
local domain_dir=$(get_domain_dir "$domain")
|
|
|
|
log_info "Starting $domain..."
|
|
cd "$domain_dir"
|
|
podman-compose up -d
|
|
|
|
log_success "$domain started"
|
|
|
|
# Show running containers
|
|
podman-compose ps
|
|
}
|
|
|
|
# Stop a domain
|
|
stop_domain() {
|
|
local domain=$1
|
|
|
|
if [ -z "$domain" ]; then
|
|
log_error "Usage: $0 stop <domain>"
|
|
return 1
|
|
fi
|
|
|
|
if ! domain_exists "$domain"; then
|
|
log_error "Domain '$domain' not found."
|
|
return 1
|
|
fi
|
|
|
|
local domain_dir=$(get_domain_dir "$domain")
|
|
|
|
log_info "Stopping $domain..."
|
|
cd "$domain_dir"
|
|
podman-compose down
|
|
|
|
log_success "$domain stopped"
|
|
}
|
|
|
|
# Restart a domain
|
|
restart_domain() {
|
|
local domain=$1
|
|
|
|
if [ -z "$domain" ]; then
|
|
log_error "Usage: $0 restart <domain>"
|
|
return 1
|
|
fi
|
|
|
|
stop_domain "$domain"
|
|
sleep 1
|
|
start_domain "$domain"
|
|
}
|
|
|
|
# Show domain status
|
|
status_domain() {
|
|
local domain=$1
|
|
|
|
if [ -z "$domain" ]; then
|
|
# Show all domains
|
|
list_domains
|
|
return
|
|
fi
|
|
|
|
if ! domain_exists "$domain"; then
|
|
log_error "Domain '$domain' not found."
|
|
return 1
|
|
fi
|
|
|
|
local domain_dir=$(get_domain_dir "$domain")
|
|
|
|
log_header "Status: $domain"
|
|
cd "$domain_dir"
|
|
podman-compose ps
|
|
}
|
|
|
|
# Show domain logs
|
|
logs_domain() {
|
|
local domain=$1
|
|
local service=$2
|
|
|
|
if [ -z "$domain" ]; then
|
|
log_error "Usage: $0 logs <domain> [service]"
|
|
return 1
|
|
fi
|
|
|
|
if ! domain_exists "$domain"; then
|
|
log_error "Domain '$domain' not found."
|
|
return 1
|
|
fi
|
|
|
|
local domain_dir=$(get_domain_dir "$domain")
|
|
cd "$domain_dir"
|
|
|
|
if [ -n "$service" ]; then
|
|
podman-compose logs -f "$service"
|
|
else
|
|
podman-compose logs -f
|
|
fi
|
|
}
|
|
|
|
# Create a new domain
|
|
create_domain() {
|
|
local domain=$1
|
|
local port=$2
|
|
|
|
if [ -z "$domain" ]; then
|
|
log_error "Usage: $0 create <domain> [starting_port]"
|
|
log_error "Example: $0 create example.com 9000"
|
|
return 1
|
|
fi
|
|
|
|
# Default starting port
|
|
port=${port:-9000}
|
|
|
|
local domain_dir=$(get_domain_dir "$domain")
|
|
|
|
if [ -d "$domain_dir" ]; then
|
|
log_warn "Domain '$domain' already exists at $domain_dir"
|
|
return 1
|
|
fi
|
|
|
|
log_info "Creating domain: $domain"
|
|
|
|
# Create directory structure
|
|
mkdir -p "$domain_dir"/{www,data,config}
|
|
|
|
# Create compose.yaml template
|
|
cat > "$domain_dir/compose.yaml" << EOF
|
|
# Compose file for $domain
|
|
# All services bind to 127.0.0.1 only (routed via nginx reverse proxy)
|
|
#
|
|
# Port assignments for $domain:
|
|
# - Main site ($domain): $port
|
|
# - API (api.$domain): $((port + 1))
|
|
# - Add more subdomains as needed
|
|
#
|
|
|
|
services:
|
|
# Main website
|
|
www:
|
|
image: docker.io/nginx:stable-alpine
|
|
container_name: ${domain//./-}-www
|
|
restart: unless-stopped
|
|
ports:
|
|
- "127.0.0.1:${port}:80"
|
|
volumes:
|
|
- ./www:/usr/share/nginx/html:ro
|
|
# healthcheck:
|
|
# test: ["CMD", "curl", "-f", "http://localhost/"]
|
|
# interval: 30s
|
|
# timeout: 10s
|
|
# retries: 3
|
|
|
|
# Uncomment to add API subdomain
|
|
# api:
|
|
# image: your-api-image
|
|
# container_name: ${domain//./-}-api
|
|
# restart: unless-stopped
|
|
# ports:
|
|
# - "127.0.0.1:$((port + 1)):8080"
|
|
# volumes:
|
|
# - ./data:/app/data
|
|
# environment:
|
|
# - NODE_ENV=production
|
|
|
|
# Shared network (optional)
|
|
# networks:
|
|
# default:
|
|
# name: ${domain//./-}-network
|
|
EOF
|
|
|
|
# Create default index.html
|
|
cat > "$domain_dir/www/index.html" << EOF
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Welcome to $domain</title>
|
|
<style>
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
min-height: 100vh;
|
|
margin: 0;
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
color: white;
|
|
}
|
|
.container {
|
|
text-align: center;
|
|
padding: 2rem;
|
|
}
|
|
h1 { font-size: 3rem; margin-bottom: 0.5rem; }
|
|
p { font-size: 1.2rem; opacity: 0.9; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<h1>$domain</h1>
|
|
<p>Your site is ready to be configured.</p>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
EOF
|
|
|
|
# Create README
|
|
cat > "$domain_dir/README.md" << EOF
|
|
# $domain
|
|
|
|
## Directory Structure
|
|
|
|
\`\`\`
|
|
$domain/
|
|
├── compose.yaml # Podman compose configuration
|
|
├── www/ # Static website files
|
|
├── data/ # Persistent data volumes
|
|
├── config/ # Configuration files
|
|
└── README.md # This file
|
|
\`\`\`
|
|
|
|
## Port Assignment
|
|
|
|
- Main site ($domain): \`127.0.0.1:$port\`
|
|
- API (api.$domain): \`127.0.0.1:$((port + 1))\` (if enabled)
|
|
|
|
## Commands
|
|
|
|
\`\`\`bash
|
|
# Start the domain
|
|
hostctl start $domain
|
|
|
|
# Stop the domain
|
|
hostctl stop $domain
|
|
|
|
# View logs
|
|
hostctl logs $domain
|
|
|
|
# Check status
|
|
hostctl status $domain
|
|
\`\`\`
|
|
|
|
## Nginx Configuration
|
|
|
|
After starting the containers, add nginx configuration:
|
|
|
|
\`\`\`bash
|
|
sudo /docker/www/startup.sh add-domain $domain $port
|
|
\`\`\`
|
|
EOF
|
|
|
|
log_success "Domain created: $domain_dir"
|
|
echo ""
|
|
echo "Next steps:"
|
|
echo " 1. Edit $domain_dir/compose.yaml to configure services"
|
|
echo " 2. Add your website files to $domain_dir/www/"
|
|
echo " 3. Start with: $0 start $domain"
|
|
echo " 4. Add nginx config: sudo /docker/www/startup.sh add-domain $domain $port"
|
|
}
|
|
|
|
# Remove a domain
|
|
remove_domain() {
|
|
local domain=$1
|
|
local force=$2
|
|
|
|
if [ -z "$domain" ]; then
|
|
log_error "Usage: $0 remove <domain> [--force]"
|
|
return 1
|
|
fi
|
|
|
|
if ! domain_exists "$domain"; then
|
|
log_error "Domain '$domain' not found."
|
|
return 1
|
|
fi
|
|
|
|
local domain_dir=$(get_domain_dir "$domain")
|
|
|
|
# Check if running
|
|
cd "$domain_dir"
|
|
if [ "$(podman-compose ps -q 2>/dev/null | wc -l)" -gt 0 ]; then
|
|
if [ "$force" != "--force" ]; then
|
|
log_error "Domain '$domain' is running. Stop it first or use --force"
|
|
return 1
|
|
fi
|
|
log_warn "Force stopping $domain..."
|
|
podman-compose down
|
|
fi
|
|
|
|
if [ "$force" != "--force" ]; then
|
|
echo -n "Are you sure you want to remove $domain? This will delete all files! (y/N) "
|
|
read -r confirm
|
|
if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
|
|
log_info "Cancelled."
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
rm -rf "$domain_dir"
|
|
log_success "Domain '$domain' removed."
|
|
}
|
|
|
|
# Start all domains
|
|
start_all() {
|
|
log_header "Starting All Domains"
|
|
|
|
for domain_dir in "$DOMAINS_DIR"/*/; do
|
|
if [ -d "$domain_dir" ] && [ -f "$domain_dir/compose.yaml" ]; then
|
|
domain=$(basename "$domain_dir")
|
|
start_domain "$domain"
|
|
echo ""
|
|
fi
|
|
done
|
|
|
|
log_success "All domains started"
|
|
}
|
|
|
|
# Stop all domains
|
|
stop_all() {
|
|
log_header "Stopping All Domains"
|
|
|
|
for domain_dir in "$DOMAINS_DIR"/*/; do
|
|
if [ -d "$domain_dir" ] && [ -f "$domain_dir/compose.yaml" ]; then
|
|
domain=$(basename "$domain_dir")
|
|
stop_domain "$domain"
|
|
echo ""
|
|
fi
|
|
done
|
|
|
|
log_success "All domains stopped"
|
|
}
|
|
|
|
# Show usage
|
|
usage() {
|
|
echo "Web Host Management Script"
|
|
echo ""
|
|
echo "Usage: $0 <command> [options]"
|
|
echo ""
|
|
echo "Commands:"
|
|
echo " list List all configured domains"
|
|
echo " status [domain] Show status (all or specific domain)"
|
|
echo " start <domain> Start a domain"
|
|
echo " stop <domain> Stop a domain"
|
|
echo " restart <domain> Restart a domain"
|
|
echo " logs <domain> [svc] View logs for a domain"
|
|
echo ""
|
|
echo " create <domain> [port] Create a new domain (default port: 9000)"
|
|
echo " remove <domain> Remove a domain"
|
|
echo ""
|
|
echo " start-all Start all domains"
|
|
echo " stop-all Stop all domains"
|
|
echo ""
|
|
echo "Examples:"
|
|
echo " $0 create example.com 9000"
|
|
echo " $0 start example.com"
|
|
echo " $0 logs example.com www"
|
|
echo " $0 status"
|
|
echo ""
|
|
echo "Directory structure:"
|
|
echo " $DOMAINS_DIR/<domain>/compose.yaml"
|
|
}
|
|
|
|
# Main entry point
|
|
case "${1:-help}" in
|
|
list)
|
|
list_domains
|
|
;;
|
|
status)
|
|
status_domain "$2"
|
|
;;
|
|
start)
|
|
start_domain "$2"
|
|
;;
|
|
stop)
|
|
stop_domain "$2"
|
|
;;
|
|
restart)
|
|
restart_domain "$2"
|
|
;;
|
|
logs)
|
|
logs_domain "$2" "$3"
|
|
;;
|
|
create)
|
|
create_domain "$2" "$3"
|
|
;;
|
|
remove|delete)
|
|
remove_domain "$2" "$3"
|
|
;;
|
|
start-all)
|
|
start_all
|
|
;;
|
|
stop-all)
|
|
stop_all
|
|
;;
|
|
help|--help|-h|"")
|
|
usage
|
|
;;
|
|
*)
|
|
log_error "Unknown command: $1"
|
|
usage
|
|
exit 1
|
|
;;
|
|
esac
|