secubox-openwrt/package/secubox/secubox-app-dns-provider/files/usr/sbin/secubox-subdomain
CyberMind-FR e13b6e4c8c feat(vhost-manager): Add centralized VHost manager
- 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>
2026-02-05 10:16:19 +01:00

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