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