diff --git a/.gitignore b/.gitignore index 093f7e31..4218542e 100644 --- a/.gitignore +++ b/.gitignore @@ -79,6 +79,8 @@ web_modules/ .env.test.local .env.production.local .env.local +*.env.local +*.env.production # parcel-bundler cache (https://parceljs.org/) .cache @@ -94,16 +96,12 @@ dist # Gatsby files .cache/ -# Comment in the public line in if your project uses Gatsby and not Next.js -# https://nextjs.org/blog/next-9-1#public-directory-support -# public # vuepress build output .vuepress/dist # vuepress v2.x temp and cache directory .temp -.cache # vitepress build output **/.vitepress/dist @@ -137,9 +135,6 @@ dist .pnp.* # ---> Go -# If you prefer the allow list template instead of the deny list, see community template: -# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore -# # Binaries for programs and plugins *.exe *.exe~ @@ -153,19 +148,12 @@ dist # Output of the go coverage tool, specifically when used with LiteIDE *.out -# Dependency directories (remove the comment below to include it) -# vendor/ - # Go workspace file go.work go.work.sum -# env file -.env - # ---> Rust # Generated by Cargo -# will have compiled files and executables debug/ target/ @@ -175,12 +163,6 @@ target/ # MSVC Windows builds of rustc generate these, which store debugging information *.pdb -# RustRover -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ # ---> C++ # Prerequisites *.d @@ -195,11 +177,6 @@ target/ *.gch *.pch -# Compiled Dynamic libraries -*.so -*.dylib -*.dll - # Fortran module files *.mod *.smod @@ -211,18 +188,11 @@ target/ *.lib # Executables -*.exe -*.out *.app # ---> C -# Prerequisites -*.d - # Object files -*.o *.ko -*.obj *.elf # Linker output @@ -230,38 +200,15 @@ target/ *.map *.exp -# Precompiled Headers -*.gch -*.pch - -# Libraries -*.lib -*.a -*.la -*.lo - # Shared objects (inc. Windows DLLs) -*.dll -*.so *.so.* -*.dylib - -# Executables -*.exe -*.out -*.app -*.i*86 -*.x86_64 -*.hex # Debug files *.dSYM/ *.su *.idb -*.pdb # Kernel Module Compile Results -*.mod* *.cmd .tmp_versions/ modules.order @@ -283,3 +230,29 @@ CTestTestfile.cmake _deps CMakeUserPresets.json +# ---> Web Hosts Specific +# Data directories +**/data/ +**/postgres-data/ + +# Repo clones (managed separately) +**/repo/ + +# Runtime files +*.sock + +# OS files +.DS_Store +Thumbs.db + +# Editor files +*.swp +*.swo +*~ +.idea/ +.vscode/ + +# Temporary files +tmp/ +temp/ +*.tmp diff --git a/domains/test.coppertone.tech/README.md b/domains/test.coppertone.tech/README.md new file mode 100644 index 00000000..a39d8c5a --- /dev/null +++ b/domains/test.coppertone.tech/README.md @@ -0,0 +1,41 @@ +# test.coppertone.tech + +## Directory Structure + +``` +test.coppertone.tech/ +├── compose.yaml # Podman compose configuration +├── www/ # Static website files +├── data/ # Persistent data volumes +├── config/ # Configuration files +└── README.md # This file +``` + +## Port Assignment + +- Main site (test.coppertone.tech): `127.0.0.1:9100` +- API (api.test.coppertone.tech): `127.0.0.1:9101` (if enabled) + +## Commands + +```bash +# Start the domain +hostctl start test.coppertone.tech + +# Stop the domain +hostctl stop test.coppertone.tech + +# View logs +hostctl logs test.coppertone.tech + +# Check status +hostctl status test.coppertone.tech +``` + +## Nginx Configuration + +After starting the containers, add nginx configuration: + +```bash +sudo /docker/www/startup.sh add-domain test.coppertone.tech 9100 +``` diff --git a/domains/test.coppertone.tech/compose.yaml b/domains/test.coppertone.tech/compose.yaml new file mode 100644 index 00000000..dee87a44 --- /dev/null +++ b/domains/test.coppertone.tech/compose.yaml @@ -0,0 +1,99 @@ +# Compose file for test.coppertone.tech +# Builds from CopperTone.Tech repo (develop branch) +# All services bind to 127.0.0.1 only (routed via nginx reverse proxy) +# +# Deploy with: ./deploy.sh +# Or manually: podman-compose down && podman-compose build && podman-compose up -d + +services: + # Frontend from develop branch + frontend: + build: + context: ./repo/frontend + dockerfile: Containerfile + container_name: test-coppertone-tech-frontend + restart: unless-stopped + ports: + - "127.0.0.1:9100:80" + volumes: + - ./frontend-nginx.conf:/etc/nginx/conf.d/default.conf:ro + environment: + - VITE_API_BASE_URL=https://test.coppertone.tech/api + healthcheck: + test: ["CMD", "wget", "-q", "--spider", "http://localhost/"] + interval: 30s + timeout: 10s + retries: 3 + + # Auth service + auth-service: + build: + context: ./repo/backend/functions/auth-service + dockerfile: Containerfile + container_name: test-coppertone-tech-auth + restart: unless-stopped + ports: + - "127.0.0.1:9102:8080" + environment: + JWT_SECRET: ${JWT_SECRET:-test_jwt_secret_key_change_me_in_production_at_least_64_characters_long} + DEFAULT_USER_ROLE: ${DEFAULT_USER_ROLE:-CLIENT} + DB_HOST: db + DB_USER: ${DB_USER:-testuser} + DB_PASSWORD: ${DB_PASSWORD:-testpassword} + DB_NAME: ${DB_NAME:-testcoppertone_db} + DB_SSL_MODE: ${DB_SSL_MODE:-disable} + CORS_ALLOW_ORIGIN: https://test.coppertone.tech + depends_on: + - db + + # Work management service + work-management-service: + build: + context: ./repo/backend/functions/work-management-service + dockerfile: Containerfile + container_name: test-coppertone-tech-work-mgmt + restart: unless-stopped + ports: + - "127.0.0.1:9103:8080" + environment: + JWT_SECRET: ${JWT_SECRET:-test_jwt_secret_key_change_me_in_production_at_least_64_characters_long} + DB_HOST: db + DB_USER: ${DB_USER:-testuser} + DB_PASSWORD: ${DB_PASSWORD:-testpassword} + DB_NAME: ${DB_NAME:-testcoppertone_db} + DB_SSL_MODE: ${DB_SSL_MODE:-disable} + CORS_ALLOW_ORIGIN: https://test.coppertone.tech + depends_on: + - db + + # Blog service + blog-service: + build: + context: ./repo/backend/functions/blog-service + dockerfile: Containerfile + container_name: test-coppertone-tech-blog + restart: unless-stopped + ports: + - "127.0.0.1:9105:8080" + environment: + JWT_SECRET: ${JWT_SECRET:-test_jwt_secret_key_change_me_in_production_at_least_64_characters_long} + DB_HOST: db + DB_USER: ${DB_USER:-testuser} + DB_PASSWORD: ${DB_PASSWORD:-testpassword} + DB_NAME: ${DB_NAME:-testcoppertone_db} + DB_SSL_MODE: ${DB_SSL_MODE:-disable} + CORS_ALLOW_ORIGIN: https://test.coppertone.tech + depends_on: + - db + + # Database for test environment + db: + image: postgres:16-alpine + container_name: test-coppertone-tech-db + restart: unless-stopped + environment: + POSTGRES_DB: testcoppertone_db + POSTGRES_USER: testuser + POSTGRES_PASSWORD: testpassword + volumes: + - ./data/postgres:/var/lib/postgresql/data diff --git a/domains/test.coppertone.tech/deploy.sh b/domains/test.coppertone.tech/deploy.sh new file mode 100755 index 00000000..03a0e52a --- /dev/null +++ b/domains/test.coppertone.tech/deploy.sh @@ -0,0 +1,139 @@ +#!/bin/bash +# +# Deploy script for test.coppertone.tech +# Pulls latest from develop branch, rebuilds, and restarts containers +# +# Usage: +# ./deploy.sh # Full deploy (pull, build, restart) +# ./deploy.sh pull # Just pull latest code +# ./deploy.sh build # Just rebuild containers +# ./deploy.sh restart # Just restart containers +# ./deploy.sh status # Show status +# ./deploy.sh logs # Show logs +# +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_DIR="$SCRIPT_DIR/repo" +BRANCH="${DEPLOY_BRANCH:-testing}" + +# Colors +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"; } + +cd "$SCRIPT_DIR" + +pull_latest() { + log_info "Pulling latest from $BRANCH branch..." + cd "$REPO_DIR" + + # Ensure we're on the right branch + current_branch=$(git branch --show-current) + if [ "$current_branch" != "$BRANCH" ]; then + log_info "Switching from $current_branch to $BRANCH..." + git checkout "$BRANCH" + fi + + # Pull latest + git fetch origin "$BRANCH" + git reset --hard "origin/$BRANCH" + + # Show latest commit + log_success "Updated to: $(git log -1 --oneline)" + cd "$SCRIPT_DIR" +} + +build_containers() { + log_info "Building containers..." + podman-compose build --no-cache + log_success "Build complete" +} + +stop_containers() { + log_info "Stopping containers..." + podman-compose down 2>/dev/null || true + log_success "Containers stopped" +} + +start_containers() { + log_info "Starting containers..." + podman-compose up -d + log_success "Containers started" +} + +show_status() { + log_info "Container status:" + podman-compose ps + echo "" + log_info "Recent commits:" + cd "$REPO_DIR" + git log -3 --oneline + cd "$SCRIPT_DIR" +} + +show_logs() { + podman-compose logs -f --tail 50 +} + +full_deploy() { + echo "==========================================" + echo " Deploying test.coppertone.tech" + echo " Branch: $BRANCH" + echo "==========================================" + echo "" + + pull_latest + echo "" + stop_containers + echo "" + build_containers + echo "" + start_containers + echo "" + + log_success "==========================================" + log_success " Deployment complete!" + log_success "==========================================" + echo "" + show_status +} + +case "${1:-deploy}" in + deploy|"") + full_deploy + ;; + pull) + pull_latest + ;; + build) + build_containers + ;; + restart) + stop_containers + start_containers + ;; + stop) + stop_containers + ;; + start) + start_containers + ;; + status) + show_status + ;; + logs) + show_logs + ;; + *) + echo "Usage: $0 {deploy|pull|build|restart|stop|start|status|logs}" + exit 1 + ;; +esac diff --git a/domains/test.coppertone.tech/frontend-nginx.conf b/domains/test.coppertone.tech/frontend-nginx.conf new file mode 100644 index 00000000..db8bf421 --- /dev/null +++ b/domains/test.coppertone.tech/frontend-nginx.conf @@ -0,0 +1,44 @@ +# Simplified nginx config for test.coppertone.tech frontend +# API routing is handled by the external nginx reverse proxy + +server { + listen 80; + server_name localhost; + + root /usr/share/nginx/html; + index index.html index.htm; + + # Security Headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + + # Prevent caching of index.html + location = /index.html { + add_header Cache-Control "no-cache, no-store, must-revalidate"; + add_header Pragma "no-cache"; + add_header Expires "0"; + } + + # Cache static assets + location /assets/ { + add_header Cache-Control "public, max-age=31536000, immutable"; + } + + # Serve markdown files + location ~* \.md$ { + types { text/plain md; } + add_header Content-Type text/plain; + } + + # SPA routing - serve index.html for all non-file requests + location / { + try_files $uri $uri/ /index.html; + } + + # Health check + location /health { + return 200 "OK"; + add_header Content-Type text/plain; + } +} diff --git a/domains/test.coppertone.tech/www/index.html b/domains/test.coppertone.tech/www/index.html new file mode 100644 index 00000000..8b364186 --- /dev/null +++ b/domains/test.coppertone.tech/www/index.html @@ -0,0 +1,32 @@ + + + + + + Welcome to test.coppertone.tech + + + +
+

test.coppertone.tech

+

Your site is ready to be configured.

+
+ + diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 00000000..6a9e5cef --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,124 @@ +# Web Hosts Management + +## Overview + +This system manages web hosting for multiple domains using: +- **Podman Compose** - Container orchestration (rootless) +- **Nginx** - Reverse proxy with SSL (rootful, via sudo) +- All services bind to `127.0.0.1` only + +## Directory Structure + +``` +/docker/web-hosts/ +├── scripts/ +│ └── hostctl.sh # Main management script +├── domains/ +│ └── / # One directory per root domain +│ ├── compose.yaml +│ ├── www/ # Static files +│ ├── data/ # Persistent data +│ └── config/ # Configuration +``` + +## Quick Start + +### 1. Create a new domain + +```bash +/docker/web-hosts/scripts/hostctl.sh create example.com 9000 +``` + +This creates: +- Directory structure at `/docker/web-hosts/domains/example.com/` +- Template `compose.yaml` with services bound to `127.0.0.1:9000` +- Default `index.html` + +### 2. Customize the compose file + +Edit `/docker/web-hosts/domains/example.com/compose.yaml` to add your services. + +### 3. Start the domain + +```bash +/docker/web-hosts/scripts/hostctl.sh start example.com +``` + +### 4. Add nginx reverse proxy with SSL + +```bash +sudo /docker/www/startup.sh add-domain example.com 9000 +``` + +## Commands Reference + +| Command | Description | +|---------|-------------| +| `hostctl list` | List all configured domains | +| `hostctl status [domain]` | Show status | +| `hostctl start ` | Start a domain | +| `hostctl stop ` | Stop a domain | +| `hostctl restart ` | Restart a domain | +| `hostctl logs ` | View logs | +| `hostctl create [port]` | Create new domain | +| `hostctl remove ` | Remove a domain | +| `hostctl start-all` | Start all domains | +| `hostctl stop-all` | Stop all domains | + +## Port Assignment Convention + +Each root domain gets a port range starting from the specified port: +- ``: Main website (www) +- ``: API subdomain +- ``: Additional services +- etc. + +Example for `example.com` starting at port 9000: +- `example.com` → `127.0.0.1:9000` +- `api.example.com` → `127.0.0.1:9001` +- `admin.example.com` → `127.0.0.1:9002` + +## Architecture + +``` +Internet + │ + ▼ +┌─────────────────────────────────────┐ +│ Nginx (rootful, ports 80/443) │ +│ /docker/www/ │ +│ - SSL termination │ +│ - Reverse proxy │ +└─────────────────────────────────────┘ + │ + ▼ 127.0.0.1: +┌─────────────────────────────────────┐ +│ Podman Containers (rootless) │ +│ /docker/web-hosts/domains/ │ +│ - Web apps │ +│ - APIs │ +│ - Databases │ +└─────────────────────────────────────┘ +``` + +## Adding Subdomains + +1. Add service to domain's `compose.yaml`: + +```yaml +services: + api: + image: your-api-image + ports: + - "127.0.0.1:9001:8080" +``` + +2. Restart the domain: +```bash +hostctl restart example.com +``` + +3. Add nginx config for subdomain: +```bash +sudo /docker/www/startup.sh add-domain api.example.com 9001 +``` diff --git a/scripts/hostctl b/scripts/hostctl new file mode 120000 index 00000000..497e6794 --- /dev/null +++ b/scripts/hostctl @@ -0,0 +1 @@ +/docker/web-hosts/scripts/hostctl.sh \ No newline at end of file diff --git a/scripts/hostctl.sh b/scripts/hostctl.sh new file mode 100755 index 00000000..dab887b2 --- /dev/null +++ b/scripts/hostctl.sh @@ -0,0 +1,498 @@ +#!/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// +# +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 " + 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 " + 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 " + 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 " + 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 [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 [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 + + + + + + Welcome to $domain + + + +
+

$domain

+

Your site is ready to be configured.

+
+ + +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 [--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 [options]" + echo "" + echo "Commands:" + echo " list List all configured domains" + echo " status [domain] Show status (all or specific domain)" + echo " start Start a domain" + echo " stop Stop a domain" + echo " restart Restart a domain" + echo " logs [svc] View logs for a domain" + echo "" + echo " create [port] Create a new domain (default port: 9000)" + echo " remove 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//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