- Create secubox-app-vhost-manager package for unified vhost orchestration - Single CLI tool (secubox-vhost) manages HAProxy, DNS, Tor, Mesh, mitmproxy - Unified UCI config (/etc/config/vhosts) as single source of truth - Backend adapters for each component (haproxy.sh, dns.sh, tor.sh, mesh.sh, mitmproxy.sh) - Centralized backend resolution function (backends.sh) - Import tool for existing HAProxy vhosts - Validation of backend reachability before creation Also includes: - FAQ-TROUBLESHOOTING.md with LXC cgroup v1/v2 fixes - Fix mitmproxyctl cgroup v1 -> v2 syntax for container compatibility - HAProxy backend resolution bugfixes CLI commands: secubox-vhost add <domain> <service> <port> [--ssl] [--tor] [--mesh] secubox-vhost remove/list/status/enable/disable/set/sync/validate/import Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
336 lines
9.6 KiB
Bash
336 lines
9.6 KiB
Bash
#!/bin/sh
|
|
# SecuBox Subdomain Generator
|
|
# Creates DNS + HAProxy + SSL configuration for new subdomains
|
|
# Uses wildcard certificate for *.zone
|
|
|
|
VERSION="1.0.0"
|
|
CONFIG="dns-provider"
|
|
HAPROXY_VHOSTS="/etc/haproxy/conf.d"
|
|
|
|
# Colors
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
CYAN='\033[0;36m'
|
|
NC='\033[0m'
|
|
|
|
log() { echo -e "${GREEN}[SUBDOMAIN]${NC} $1"; }
|
|
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
|
error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
|
|
|
uci_get() { uci -q get ${CONFIG}.$1; }
|
|
get_zone() { uci_get main.zone; }
|
|
get_public_ip() {
|
|
curl -s --connect-timeout 5 https://ipv4.icanhazip.com 2>/dev/null | tr -d '\n'
|
|
}
|
|
|
|
# ============================================================================
|
|
# Add Subdomain
|
|
# ============================================================================
|
|
|
|
cmd_add() {
|
|
local name="$1"
|
|
local backend_port="$2"
|
|
local description="${3:-}"
|
|
|
|
if [ -z "$name" ] || [ -z "$backend_port" ]; then
|
|
echo "Usage: secubox-subdomain add <name> <backend_port> [description]"
|
|
echo ""
|
|
echo "Examples:"
|
|
echo " secubox-subdomain add api 8080 'API Gateway'"
|
|
echo " secubox-subdomain add grafana 3000 'Monitoring Dashboard'"
|
|
return 1
|
|
fi
|
|
|
|
local zone=$(get_zone)
|
|
if [ -z "$zone" ]; then
|
|
error "No DNS zone configured. Run: uci set dns-provider.main.zone='example.com'"
|
|
return 1
|
|
fi
|
|
|
|
local fqdn="${name}.${zone}"
|
|
local public_ip=$(get_public_ip)
|
|
|
|
if [ -z "$public_ip" ]; then
|
|
error "Cannot detect public IP"
|
|
return 1
|
|
fi
|
|
|
|
log "Creating subdomain: $fqdn → 127.0.0.1:$backend_port"
|
|
|
|
# Step 1: Add DNS A record
|
|
log " [1/4] Adding DNS A record..."
|
|
dnsctl add A "$name" "$public_ip" 300
|
|
if [ $? -ne 0 ]; then
|
|
error "Failed to add DNS record"
|
|
return 1
|
|
fi
|
|
|
|
# Step 2: Create HAProxy vhost config
|
|
log " [2/4] Creating HAProxy vhost..."
|
|
mkdir -p "$HAPROXY_VHOSTS"
|
|
|
|
cat > "${HAPROXY_VHOSTS}/${name}.${zone}.cfg" << EOF
|
|
# HAProxy vhost for ${fqdn}
|
|
# Generated by secubox-subdomain v${VERSION}
|
|
# Backend port: ${backend_port}
|
|
# Description: ${description:-Auto-generated subdomain}
|
|
|
|
acl host_${name//-/_}_${zone//\./_} hdr(host) -i ${fqdn}
|
|
use_backend backend_${name//-/_} if host_${name//-/_}_${zone//\./_}
|
|
|
|
backend backend_${name//-/_}
|
|
mode http
|
|
option httpchk GET /
|
|
server ${name} 127.0.0.1:${backend_port} check inter 10s
|
|
EOF
|
|
|
|
# Step 3: Register in UCI
|
|
log " [3/4] Registering in UCI..."
|
|
local idx=$(uci show haproxy 2>/dev/null | grep -c "=vhost")
|
|
uci set haproxy.vhost_${name}=vhost
|
|
uci set haproxy.vhost_${name}.enabled='1'
|
|
uci set haproxy.vhost_${name}.domain="$fqdn"
|
|
uci set haproxy.vhost_${name}.backend_port="$backend_port"
|
|
uci set haproxy.vhost_${name}.description="${description:-}"
|
|
uci set haproxy.vhost_${name}.created="$(date -Iseconds)"
|
|
uci commit haproxy
|
|
|
|
# Step 4: Reload HAProxy
|
|
log " [4/4] Reloading HAProxy..."
|
|
if command -v haproxyctl >/dev/null 2>&1; then
|
|
haproxyctl reload 2>/dev/null || true
|
|
fi
|
|
|
|
echo ""
|
|
log "Subdomain created: https://${fqdn}"
|
|
log "Using wildcard cert: *.${zone}"
|
|
log ""
|
|
log "Verify DNS: dnsctl verify ${fqdn}"
|
|
}
|
|
|
|
# ============================================================================
|
|
# List Subdomains
|
|
# ============================================================================
|
|
|
|
cmd_list() {
|
|
local zone=$(get_zone)
|
|
|
|
echo ""
|
|
echo "=========================================="
|
|
echo " SecuBox Subdomains"
|
|
echo "=========================================="
|
|
echo ""
|
|
echo "Zone: ${zone:-not configured}"
|
|
echo ""
|
|
|
|
if [ -z "$zone" ]; then
|
|
warn "No zone configured"
|
|
return 0
|
|
fi
|
|
|
|
# List from UCI
|
|
printf "%-20s %-8s %-10s %s\n" "SUBDOMAIN" "PORT" "STATUS" "DESCRIPTION"
|
|
printf "%-20s %-8s %-10s %s\n" "---------" "----" "------" "-----------"
|
|
|
|
local count=0
|
|
for section in $(uci show haproxy 2>/dev/null | grep "=vhost$" | cut -d. -f2 | cut -d= -f1); do
|
|
local enabled=$(uci -q get haproxy.${section}.enabled)
|
|
local domain=$(uci -q get haproxy.${section}.domain)
|
|
local port=$(uci -q get haproxy.${section}.backend_port)
|
|
local desc=$(uci -q get haproxy.${section}.description)
|
|
|
|
if [ -n "$domain" ]; then
|
|
local name=$(echo "$domain" | sed "s/\.${zone}$//")
|
|
local status="active"
|
|
[ "$enabled" != "1" ] && status="disabled"
|
|
|
|
printf "%-20s %-8s %-10s %s\n" "$name" "${port:-?}" "$status" "${desc:-}"
|
|
count=$((count + 1))
|
|
fi
|
|
done
|
|
|
|
if [ "$count" -eq 0 ]; then
|
|
echo " No subdomains configured"
|
|
fi
|
|
echo ""
|
|
echo "Total: $count subdomain(s)"
|
|
echo ""
|
|
}
|
|
|
|
# ============================================================================
|
|
# Remove Subdomain
|
|
# ============================================================================
|
|
|
|
cmd_remove() {
|
|
local name="$1"
|
|
|
|
if [ -z "$name" ]; then
|
|
echo "Usage: secubox-subdomain remove <name>"
|
|
return 1
|
|
fi
|
|
|
|
local zone=$(get_zone)
|
|
local fqdn="${name}.${zone}"
|
|
|
|
log "Removing subdomain: $fqdn"
|
|
|
|
# Step 1: Remove DNS record
|
|
log " [1/3] Removing DNS A record..."
|
|
dnsctl rm A "$name" 2>/dev/null || true
|
|
|
|
# Step 2: Remove HAProxy vhost config
|
|
log " [2/3] Removing HAProxy vhost..."
|
|
rm -f "${HAPROXY_VHOSTS}/${name}.${zone}.cfg"
|
|
|
|
# Step 3: Remove UCI entry
|
|
log " [3/3] Removing UCI entry..."
|
|
uci delete haproxy.vhost_${name} 2>/dev/null || true
|
|
uci commit haproxy
|
|
|
|
# Reload HAProxy
|
|
if command -v haproxyctl >/dev/null 2>&1; then
|
|
haproxyctl reload 2>/dev/null || true
|
|
fi
|
|
|
|
log "Subdomain removed: $fqdn"
|
|
}
|
|
|
|
# ============================================================================
|
|
# Enable/Disable
|
|
# ============================================================================
|
|
|
|
cmd_enable() {
|
|
local name="$1"
|
|
[ -z "$name" ] && { echo "Usage: secubox-subdomain enable <name>"; return 1; }
|
|
|
|
uci set haproxy.vhost_${name}.enabled='1'
|
|
uci commit haproxy
|
|
log "Enabled: $name"
|
|
}
|
|
|
|
cmd_disable() {
|
|
local name="$1"
|
|
[ -z "$name" ] && { echo "Usage: secubox-subdomain disable <name>"; return 1; }
|
|
|
|
uci set haproxy.vhost_${name}.enabled='0'
|
|
uci commit haproxy
|
|
log "Disabled: $name"
|
|
}
|
|
|
|
# ============================================================================
|
|
# Sync - Import existing HAProxy vhosts
|
|
# ============================================================================
|
|
|
|
cmd_sync() {
|
|
local zone=$(get_zone)
|
|
log "Syncing HAProxy vhosts from ${HAPROXY_VHOSTS}..."
|
|
|
|
local count=0
|
|
ls ${HAPROXY_VHOSTS}/*.cfg >/dev/null 2>&1 || { log "No vhost configs found"; return 0; }
|
|
for cfg in ${HAPROXY_VHOSTS}/*.cfg; do
|
|
[ -f "$cfg" ] || continue
|
|
local filename=$(basename "$cfg" .cfg)
|
|
|
|
# Check if it matches our zone
|
|
if echo "$filename" | grep -q "\.${zone}$"; then
|
|
local name=$(echo "$filename" | sed "s/\.${zone}$//")
|
|
local port=$(grep "server.*127.0.0.1:" "$cfg" 2>/dev/null | head -1 | sed 's/.*127.0.0.1:\([0-9]*\).*/\1/')
|
|
|
|
if [ -n "$name" ] && [ -n "$port" ]; then
|
|
# Register if not already in UCI
|
|
if ! uci -q get haproxy.vhost_${name} >/dev/null 2>&1; then
|
|
log " Importing: $name (port $port)"
|
|
uci set haproxy.vhost_${name}=vhost
|
|
uci set haproxy.vhost_${name}.enabled='1'
|
|
uci set haproxy.vhost_${name}.domain="${name}.${zone}"
|
|
uci set haproxy.vhost_${name}.backend_port="$port"
|
|
uci set haproxy.vhost_${name}.imported='1'
|
|
count=$((count + 1))
|
|
fi
|
|
fi
|
|
fi
|
|
done
|
|
|
|
uci commit haproxy
|
|
log "Imported $count vhost(s)"
|
|
}
|
|
|
|
# ============================================================================
|
|
# Quick Add - Shorthand for common services
|
|
# ============================================================================
|
|
|
|
cmd_quick() {
|
|
local service="$1"
|
|
|
|
case "$service" in
|
|
gitea) cmd_add gitea 3000 "Gitea Git Server" ;;
|
|
grafana) cmd_add grafana 3000 "Grafana Monitoring" ;;
|
|
portainer) cmd_add portainer 9000 "Portainer Docker UI" ;;
|
|
jellyfin) cmd_add jellyfin 8096 "Jellyfin Media Server" ;;
|
|
nextcloud) cmd_add nextcloud 8080 "Nextcloud Cloud Storage" ;;
|
|
homeassistant|ha) cmd_add ha 8123 "Home Assistant" ;;
|
|
*)
|
|
echo "Quick service shortcuts:"
|
|
echo " secubox-subdomain quick gitea"
|
|
echo " secubox-subdomain quick grafana"
|
|
echo " secubox-subdomain quick portainer"
|
|
echo " secubox-subdomain quick jellyfin"
|
|
echo " secubox-subdomain quick nextcloud"
|
|
echo " secubox-subdomain quick ha"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# ============================================================================
|
|
# Help
|
|
# ============================================================================
|
|
|
|
show_help() {
|
|
cat << EOF
|
|
SecuBox Subdomain Generator v${VERSION}
|
|
|
|
Usage: secubox-subdomain <command> [options]
|
|
|
|
Commands:
|
|
add <name> <port> [desc] Create new subdomain with DNS + HAProxy
|
|
remove <name> Remove subdomain (DNS + HAProxy + UCI)
|
|
list List all configured subdomains
|
|
enable <name> Enable a subdomain
|
|
disable <name> Disable a subdomain
|
|
sync Import existing HAProxy vhosts to UCI
|
|
quick <service> Quick-add common services
|
|
|
|
Examples:
|
|
secubox-subdomain add api 8080 "REST API Gateway"
|
|
secubox-subdomain add dashboard 3000
|
|
secubox-subdomain remove api
|
|
secubox-subdomain list
|
|
secubox-subdomain quick gitea
|
|
|
|
Notes:
|
|
- Uses wildcard certificate (*.zone) for SSL
|
|
- DNS records added via dnsctl (Gandi/OVH/Cloudflare)
|
|
- HAProxy vhosts auto-generated in ${HAPROXY_VHOSTS}
|
|
|
|
EOF
|
|
}
|
|
|
|
# ============================================================================
|
|
# Main
|
|
# ============================================================================
|
|
|
|
case "${1:-}" in
|
|
add) shift; cmd_add "$@" ;;
|
|
remove|rm) shift; cmd_remove "$@" ;;
|
|
list|ls) shift; cmd_list "$@" ;;
|
|
enable) shift; cmd_enable "$@" ;;
|
|
disable) shift; cmd_disable "$@" ;;
|
|
sync) shift; cmd_sync "$@" ;;
|
|
quick) shift; cmd_quick "$@" ;;
|
|
help|--help|-h|'') show_help ;;
|
|
*) error "Unknown command: $1"; show_help >&2; exit 1 ;;
|
|
esac
|
|
|
|
exit 0
|