#!/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 [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} 192.168.255.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 " 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 "; 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 "; 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 [options] Commands: add [desc] Create new subdomain with DNS + HAProxy remove Remove subdomain (DNS + HAProxy + UCI) list List all configured subdomains enable Enable a subdomain disable Disable a subdomain sync Import existing HAProxy vhosts to UCI quick 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