diff --git a/package/secubox/luci-app-haproxy/root/usr/libexec/rpcd/luci.haproxy b/package/secubox/luci-app-haproxy/root/usr/libexec/rpcd/luci.haproxy index 89e85143..81bc5ba5 100755 --- a/package/secubox/luci-app-haproxy/root/usr/libexec/rpcd/luci.haproxy +++ b/package/secubox/luci-app-haproxy/root/usr/libexec/rpcd/luci.haproxy @@ -642,6 +642,13 @@ method_create_server() { return fi + # Auto-convert localhost/127.0.0.1 to 192.168.255.1 (HAProxy runs in LXC) + case "$address" in + 127.0.0.1|localhost|127.0.1.1) + address="192.168.255.1" + ;; + esac + section_id="${backend}_$(echo "$name" | sed 's/[^a-zA-Z0-9]/_/g')" uci set "$UCI_CONFIG.$section_id=server" @@ -686,6 +693,13 @@ method_update_server() { return fi + # Auto-convert localhost/127.0.0.1 to 192.168.255.1 (HAProxy runs in LXC) + case "$address" in + 127.0.0.1|localhost|127.0.1.1) + address="192.168.255.1" + ;; + esac + # Check if this is an inline server (id format: backendname_servername) # If so, we need to convert it to a proper server section if [ "$inline" = "1" ] || ! uci -q get "$UCI_CONFIG.$id" >/dev/null 2>&1; then diff --git a/package/secubox/secubox-app-dns-provider/files/usr/sbin/secubox-subdomain b/package/secubox/secubox-app-dns-provider/files/usr/sbin/secubox-subdomain index 02a0ce59..1da989e4 100644 --- a/package/secubox/secubox-app-dns-provider/files/usr/sbin/secubox-subdomain +++ b/package/secubox/secubox-app-dns-provider/files/usr/sbin/secubox-subdomain @@ -82,7 +82,7 @@ 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 + server ${name} 192.168.255.1:${backend_port} check inter 10s EOF # Step 3: Register in UCI diff --git a/package/secubox/secubox-app-exposure/files/usr/sbin/secubox-exposure b/package/secubox/secubox-app-exposure/files/usr/sbin/secubox-exposure index 1c5b47b0..810e7548 100755 --- a/package/secubox/secubox-app-exposure/files/usr/sbin/secubox-exposure +++ b/package/secubox/secubox-app-exposure/files/usr/sbin/secubox-exposure @@ -466,7 +466,7 @@ cmd_ssl_add() { uci set "haproxy.${backend_name}.mode=http" uci set "haproxy.${backend_name}.balance=roundrobin" uci set "haproxy.${backend_name}.enabled=1" - uci add_list "haproxy.${backend_name}.server=${service} 127.0.0.1:${local_port} check" + uci add_list "haproxy.${backend_name}.server=${service} 192.168.255.1:${local_port} check" fi # Check if vhost already exists diff --git a/package/secubox/secubox-app-haproxy/files/usr/lib/acme/deploy/haproxy.sh b/package/secubox/secubox-app-haproxy/files/usr/lib/acme/deploy/haproxy.sh index eba5b5b2..3c18284d 100644 --- a/package/secubox/secubox-app-haproxy/files/usr/lib/acme/deploy/haproxy.sh +++ b/package/secubox/secubox-app-haproxy/files/usr/lib/acme/deploy/haproxy.sh @@ -47,10 +47,8 @@ deploy() { echo "Certificate deployed: $HAPROXY_CERTS_DIR/$domain.pem" - # Reload HAProxy if running - if [ -x /etc/init.d/haproxy ]; then - /etc/init.d/haproxy reload 2>/dev/null || true - fi + # Regenerate certs.list for multi-certificate SNI support + /usr/sbin/haproxy-sync-certs 2>/dev/null || true return 0 } diff --git a/package/secubox/secubox-app-haproxy/files/usr/sbin/haproxy-sync-certs b/package/secubox/secubox-app-haproxy/files/usr/sbin/haproxy-sync-certs index 994321a5..ccc2a49a 100644 --- a/package/secubox/secubox-app-haproxy/files/usr/sbin/haproxy-sync-certs +++ b/package/secubox/secubox-app-haproxy/files/usr/sbin/haproxy-sync-certs @@ -40,8 +40,54 @@ done log_info "Certificate sync complete" +# Generate certs.list for multi-certificate SNI support +CERTS_LIST="$HAPROXY_CERTS_DIR/certs.list" +CONTAINER_CERTS_PATH="/opt/haproxy/certs" + +log_info "Generating certificate list file..." +> "$CERTS_LIST" + +for cert_file in "$HAPROXY_CERTS_DIR"/*.pem; do + [ -f "$cert_file" ] || continue + + filename=$(basename "$cert_file") + container_cert_path="$CONTAINER_CERTS_PATH/$filename" + + # Extract domain names from certificate SANs + domains=$(openssl x509 -in "$cert_file" -noout -text 2>/dev/null | \ + grep -A1 "Subject Alternative Name" | \ + tail -n1 | \ + sed 's/DNS://g' | \ + tr ',' '\n' | \ + tr -d ' ') + + # If no SANs, try to get CN + if [ -z "$domains" ]; then + domains=$(openssl x509 -in "$cert_file" -noout -subject 2>/dev/null | \ + sed -n 's/.*CN *= *\([^,/]*\).*/\1/p') + fi + + if [ -n "$domains" ]; then + echo "$domains" | while read -r domain; do + [ -n "$domain" ] || continue + echo "$container_cert_path $domain" >> "$CERTS_LIST" + done + else + log_info "No domain found in certificate: $filename, adding without SNI filter" + echo "$container_cert_path" >> "$CERTS_LIST" + fi +done + +# Deduplicate entries +if [ -f "$CERTS_LIST" ]; then + sort -u "$CERTS_LIST" > "$CERTS_LIST.tmp" + mv "$CERTS_LIST.tmp" "$CERTS_LIST" + count=$(wc -l < "$CERTS_LIST") + log_info "Generated certs.list with $count entries" +fi + # Reload HAProxy if running -if pgrep -x haproxy >/dev/null 2>&1 || lxc-info -n haproxy -s 2>/dev/null | grep -q RUNNING; then +if pgrep haproxy >/dev/null 2>&1 || lxc-info -n haproxy -s 2>/dev/null | grep -q RUNNING; then log_info "Reloading HAProxy..." /etc/init.d/haproxy reload 2>/dev/null || true fi diff --git a/package/secubox/secubox-app-haproxy/files/usr/sbin/haproxyctl b/package/secubox/secubox-app-haproxy/files/usr/sbin/haproxyctl index 03dd2d08..053b4752 100644 --- a/package/secubox/secubox-app-haproxy/files/usr/sbin/haproxyctl +++ b/package/secubox/secubox-app-haproxy/files/usr/sbin/haproxyctl @@ -409,6 +409,69 @@ lxc_exec() { lxc-attach -n "$LXC_NAME" -- "$@" } +# =========================================== +# Certificate List Generation (for multi-cert SNI) +# =========================================== + +# Generate crt-list file for multi-certificate SNI support +# This is more reliable than directory mode for multi-domain setups +generate_certs_list() { + local certs_list="$CERTS_PATH/certs.list" + local container_certs_path="/opt/haproxy/certs" + + log_info "Generating certificate list file..." + + # Start fresh + > "$certs_list" + + # Process each .pem file + for cert_file in "$CERTS_PATH"/*.pem; do + [ -f "$cert_file" ] || continue + + local filename=$(basename "$cert_file") + local container_cert_path="$container_certs_path/$filename" + + # Extract domain names from certificate (CN and SANs) + local domains=$(openssl x509 -in "$cert_file" -noout -text 2>/dev/null | \ + grep -A1 "Subject Alternative Name" | \ + tail -n1 | \ + sed 's/DNS://g' | \ + tr ',' '\n' | \ + tr -d ' ') + + # If no SANs, try to get CN + if [ -z "$domains" ]; then + domains=$(openssl x509 -in "$cert_file" -noout -subject 2>/dev/null | \ + sed -n 's/.*CN *= *\([^,/]*\).*/\1/p') + fi + + if [ -n "$domains" ]; then + # For each domain, add an entry with SNI filter + # Format: /path/to/cert.pem [sni_filter] + echo "$domains" | while read -r domain; do + [ -n "$domain" ] || continue + # Handle wildcard certificates - the SNI filter should match the pattern + echo "$container_cert_path $domain" >> "$certs_list" + done + else + # Fallback: add cert without SNI filter (will be default) + log_warn "No domain found in certificate: $filename" + echo "$container_cert_path" >> "$certs_list" + fi + done + + # Deduplicate entries (same cert may have multiple SANs) + if [ -f "$certs_list" ]; then + sort -u "$certs_list" > "$certs_list.tmp" + mv "$certs_list.tmp" "$certs_list" + + local count=$(wc -l < "$certs_list") + log_info "Generated certs.list with $count entries" + else + log_warn "No certificates found in $CERTS_PATH" + fi +} + # =========================================== # Configuration Generation # =========================================== @@ -420,6 +483,9 @@ generate_config() { log_info "Generating HAProxy configuration..." + # Generate certs.list for multi-certificate SNI support + generate_certs_list + # Global section cat > "$cfg_file" << EOF # HAProxy Configuration - Generated by SecuBox @@ -509,14 +575,28 @@ EOF # HTTPS Frontend (if certificates exist) # Use container path /opt/haproxy/certs/ (not host path) local CONTAINER_CERTS_PATH="/opt/haproxy/certs" + local CERTS_LIST_FILE="$CERTS_PATH/certs.list" if [ -d "$CERTS_PATH" ] && ls "$CERTS_PATH"/*.pem >/dev/null 2>&1; then - cat << EOF + # Use crt-list for proper multi-certificate SNI matching + # Falls back to directory mode if certs.list doesn't exist + if [ -f "$CERTS_LIST_FILE" ] && [ -s "$CERTS_LIST_FILE" ]; then + cat << EOF +frontend https-in + bind *:$https_port,[::]:$https_port ssl crt-list $CONTAINER_CERTS_PATH/certs.list alpn h2,http/1.1 + mode http + http-request set-header X-Forwarded-Proto https + http-request set-header X-Real-IP %[src] +EOF + else + # Fallback to directory mode if no certs.list + cat << EOF frontend https-in bind *:$https_port,[::]:$https_port ssl crt $CONTAINER_CERTS_PATH/ alpn h2,http/1.1 mode http http-request set-header X-Forwarded-Proto https http-request set-header X-Real-IP %[src] EOF + fi # Add vhost ACLs for HTTPS config_foreach _add_vhost_acl vhost "https" diff --git a/package/secubox/secubox-app-jellyfin/files/usr/sbin/jellyfinctl b/package/secubox/secubox-app-jellyfin/files/usr/sbin/jellyfinctl index 91223288..8251ff28 100644 --- a/package/secubox/secubox-app-jellyfin/files/usr/sbin/jellyfinctl +++ b/package/secubox/secubox-app-jellyfin/files/usr/sbin/jellyfinctl @@ -139,7 +139,7 @@ configure_haproxy() { uci -q add haproxy backend uci -q set haproxy.@backend[-1].name='jellyfin_web' uci -q set haproxy.@backend[-1].mode='http' - uci -q add_list haproxy.@backend[-1].server="jellyfin 127.0.0.1:$port check" + uci -q add_list haproxy.@backend[-1].server="jellyfin 192.168.255.1:$port check" # Add vhost uci -q add haproxy vhost diff --git a/package/secubox/secubox-app-jitsi/files/usr/sbin/jitsctl b/package/secubox/secubox-app-jitsi/files/usr/sbin/jitsctl index bc0b555f..24276897 100644 --- a/package/secubox/secubox-app-jitsi/files/usr/sbin/jitsctl +++ b/package/secubox/secubox-app-jitsi/files/usr/sbin/jitsctl @@ -268,7 +268,7 @@ configure_haproxy() { uci -q add haproxy backend uci -q set haproxy.@backend[-1].name='jitsi_web' uci -q set haproxy.@backend[-1].mode='http' - uci -q add_list haproxy.@backend[-1].server="jitsi 127.0.0.1:$web_port check" + uci -q add_list haproxy.@backend[-1].server="jitsi 192.168.255.1:$web_port check" # Add vhost uci -q add haproxy vhost diff --git a/package/secubox/secubox-app-simplex/files/usr/sbin/simplexctl b/package/secubox/secubox-app-simplex/files/usr/sbin/simplexctl index 6b89733f..d4b9f49a 100644 --- a/package/secubox/secubox-app-simplex/files/usr/sbin/simplexctl +++ b/package/secubox/secubox-app-simplex/files/usr/sbin/simplexctl @@ -583,7 +583,7 @@ configure_haproxy() { uci -q add haproxy backend uci -q set haproxy.@backend[-1].name='simplex_smp' uci -q set haproxy.@backend[-1].mode='tcp' - uci -q add_list haproxy.@backend[-1].server="simplex-smp 127.0.0.1:$SMP_PORT check" + uci -q add_list haproxy.@backend[-1].server="simplex-smp 192.168.255.1:$SMP_PORT check" uci commit haproxy /etc/init.d/haproxy reload 2>/dev/null diff --git a/package/secubox/secubox-app-streamlit/files/srv/streamlit/apps/fabricator/pages/04_🌐_Services.py b/package/secubox/secubox-app-streamlit/files/srv/streamlit/apps/fabricator/pages/04_🌐_Services.py index 96a647f9..dc83ec49 100644 --- a/package/secubox/secubox-app-streamlit/files/srv/streamlit/apps/fabricator/pages/04_🌐_Services.py +++ b/package/secubox/secubox-app-streamlit/files/srv/streamlit/apps/fabricator/pages/04_🌐_Services.py @@ -228,7 +228,7 @@ if service_port and domain: run_cmd(f'uci set haproxy.{backend_name}=backend') run_cmd(f'uci set haproxy.{backend_name}.name="{backend_name}"') run_cmd(f'uci set haproxy.{backend_name}.mode="http"') - run_cmd(f'uci set haproxy.{backend_name}.server="srv 127.0.0.1:{service_port} check"') + run_cmd(f'uci set haproxy.{backend_name}.server="srv 192.168.255.1:{service_port} check"') run_cmd(f'uci set haproxy.{backend_name}.enabled="1"') run_cmd(f'uci set haproxy.{backend_name}_vhost=vhost') diff --git a/package/secubox/secubox-core/root/etc/init.d/secubox-core b/package/secubox/secubox-core/root/etc/init.d/secubox-core index a2d73490..e6ab296f 100755 --- a/package/secubox/secubox-core/root/etc/init.d/secubox-core +++ b/package/secubox/secubox-core/root/etc/init.d/secubox-core @@ -62,4 +62,7 @@ service_triggers() { boot() { # Delay start on boot to allow network to initialize ( sleep 10; start "$@"; ) & + + # Regenerate landing pages after network is up + ( sleep 30; /usr/sbin/secubox-landing regenerate >/dev/null 2>&1; ) & } diff --git a/package/secubox/secubox-core/root/usr/sbin/secubox b/package/secubox/secubox-core/root/usr/sbin/secubox index 968bdd65..221bfc8c 100755 --- a/package/secubox/secubox-core/root/usr/sbin/secubox +++ b/package/secubox/secubox-core/root/usr/sbin/secubox @@ -31,6 +31,7 @@ ${BOLD}Commands:${NC} ${GREEN}device${NC} Device information and management ${GREEN}net${NC} Network management ${GREEN}diag${NC} Diagnostics and health checks + ${GREEN}landing${NC} Generate landing pages from vhosts ${GREEN}ai${NC} AI copilot (optional) ${BOLD}Examples:${NC} @@ -392,6 +393,41 @@ EOF esac } +# Landing page commands +cmd_landing() { + case "$1" in + generate|gen) + shift + /usr/sbin/secubox-landing generate "$@" + ;; + list|ls) + /usr/sbin/secubox-landing list + ;; + show) + /usr/sbin/secubox-landing show "$2" + ;; + regenerate|regen) + /usr/sbin/secubox-landing regenerate + ;; + help|*) + cat < Show services for a zone + secubox landing generate [zone] Generate landing page(s) + secubox landing regenerate Regenerate all landing pages + +${BOLD}Examples:${NC} + secubox landing list + secubox landing generate gk2.secubox.in + secubox landing regenerate +EOF + ;; + esac +} + # AI commands (optional) cmd_ai() { # Check if AI is enabled @@ -462,6 +498,10 @@ case "$1" in shift cmd_diag "$@" ;; + landing) + shift + cmd_landing "$@" + ;; ai) shift cmd_ai "$@" diff --git a/package/secubox/secubox-core/root/usr/sbin/secubox-landing b/package/secubox/secubox-core/root/usr/sbin/secubox-landing new file mode 100644 index 00000000..1e1ac79b --- /dev/null +++ b/package/secubox/secubox-core/root/usr/sbin/secubox-landing @@ -0,0 +1,735 @@ +#!/bin/sh +# +# SecuBox Landing Page Generator +# Generates landing pages from HAProxy vhosts configuration +# Includes ALL vhosts across all zones, grouped by zone +# + +VERSION="1.1.0" +SITES_DIR="/srv/metablogizer/sites" +CERTS_DIR="/etc/haproxy/certs" + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +CYAN='\033[0;36m' +NC='\033[0m' + +log() { echo -e "${GREEN}[LANDING]${NC} $1"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } +error() { echo -e "${RED}[ERROR]${NC} $1"; } + +# Get public IP +get_public_ip() { + curl -s --connect-timeout 5 https://ipv4.icanhazip.com 2>/dev/null | tr -d '\n' +} + +# Get node name from UCI or hostname +get_node_name() { + local name=$(uci -q get secubox.main.node_name) + [ -z "$name" ] && name=$(uci -q get system.@system[0].hostname) + [ -z "$name" ] && name="SecuBox" + echo "$name" +} + +# Get ALL vhosts (all domains) +get_all_vhosts() { + uci show haproxy 2>/dev/null | grep '\.domain=' | grep -v '_wildcard_' | grep -v 'cert_' | \ + sed "s/.*domain='\\([^']*\\)'/\\1/" | sort -u +} + +# Extract zone from domain (last 2 parts) +get_zone_from_domain() { + local domain="$1" + echo "$domain" | awk -F. '{if(NF>2) print $(NF-1)"."$NF; else print $0}' +} + +# Extract all unique zones from vhosts +get_zones() { + get_all_vhosts | while read domain; do + get_zone_from_domain "$domain" + done | sort -u +} + +# Get all vhosts for a specific zone +get_vhosts_for_zone() { + local zone="$1" + get_all_vhosts | grep "\.${zone}$" +} + +# Check if domain has valid certificate +has_valid_cert() { + local domain="$1" + local zone=$(get_zone_from_domain "$domain") + + # Check for exact cert or wildcard + [ -f "${CERTS_DIR}/${domain}.pem" ] && return 0 + [ -f "${CERTS_DIR}/_wildcard_.${zone}.pem" ] && return 0 + + # Check ACME certs + [ -f "/etc/acme/${domain}/fullchain.pem" ] && return 0 + [ -f "/etc/acme/_wildcard_.${zone}/fullchain.pem" ] && return 0 + + return 1 +} + +# Detect service type from backend name +detect_service_type() { + local backend="$1" + local domain="$2" + + case "$backend" in + streamlit_*|*streamlit*) echo "streamlit" ;; + metablog_*|*metablog*) echo "blog" ;; + luci|*luci*) echo "admin" ;; + *jellyfin*|*media*|*jitsi*) echo "media" ;; + *git*|*gitea*) echo "dev" ;; + *glances*|*status*|*monitor*|*uptime*) echo "infra" ;; + *) + # Try to guess from domain + case "$domain" in + console.*|admin.*|luci.*|c3box.*) echo "admin" ;; + git.*|dev.*|devel.*) echo "dev" ;; + blog.*|news.*|zine.*|gandalf.*|cyberzine.*) echo "blog" ;; + media.*|live.*|meet.*|jitsi.*) echo "media" ;; + status.*|glances.*|monitor.*) echo "infra" ;; + slides.*|sliders.*) echo "presentation" ;; + mail.*) echo "mail" ;; + *) echo "service" ;; + esac + ;; + esac +} + +# Get backend info for a vhost +get_vhost_backend() { + local domain="$1" + local backend="" + + # Find the UCI section for this domain + for s in $(uci show haproxy 2>/dev/null | grep "\.domain='${domain}'" | cut -d. -f2 | cut -d= -f1); do + backend=$(uci -q get haproxy.${s}.backend) + [ -n "$backend" ] && break + done + + echo "$backend" +} + +# Check if vhost is enabled +is_vhost_enabled() { + local domain="$1" + + for s in $(uci show haproxy 2>/dev/null | grep "\.domain='${domain}'" | cut -d. -f2 | cut -d= -f1); do + local enabled=$(uci -q get haproxy.${s}.enabled) + [ "$enabled" = "1" ] && return 0 + done + + return 1 +} + +# Generate HTML for a single service item +generate_service_html() { + local domain="$1" + local name="$2" + local desc="$3" + local type="$4" + local has_cert="$5" + local extra_class="" + local tags="" + + case "$type" in + streamlit) + extra_class=" streamlit" + tags='STREAMLIT' + ;; + admin) + extra_class=" highlight" + tags='ADMIN' + ;; + blog) + tags='BLOG' + ;; + media) + tags='MEDIA' + ;; + dev) + tags='DEV' + ;; + presentation) + tags='SLIDES' + ;; + mail) + tags='MAIL' + ;; + *) + tags="" + ;; + esac + + # Add SSL indicator + if [ "$has_cert" = "1" ]; then + tags="${tags}"'🔒' + fi + + cat < +
+ ${name} + ${desc} +
+
+ ${tags} + ${domain} +
+ +EOF +} + +# Generate full landing page for a zone (includes ALL zones) +generate_landing_page() { + local primary_zone="$1" + local include_all="${2:-1}" # Include all zones by default + local output_dir="${SITES_DIR}/${primary_zone%%.*}" + local output_file="${output_dir}/index.html" + local node_name=$(get_node_name) + local public_ip=$(get_public_ip) + + [ -z "$public_ip" ] && public_ip="N/A" + + log "Generating landing page for zone: $primary_zone" + + # Create output directory + mkdir -p "$output_dir" + + # Collect zones to include + local zones_to_include="" + if [ "$include_all" = "1" ]; then + zones_to_include=$(get_zones) + else + zones_to_include="$primary_zone" + fi + + # Count all services + local total_count=0 + local cert_count=0 + + # Generate the HTML file header + cat > "$output_file" < + + + + + ${node_name} - ${primary_zone} + + + +
+
+

${node_name}

+

Services distribués sur le noeud Vortex

+
+ *.${primary_zone} + ${public_ip} + Mesh: Enabled +
+HTMLHEAD + + # Process each zone + local zone_html="" + for zone in $zones_to_include; do + local vhosts=$(get_vhosts_for_zone "$zone") + local zone_count=0 + + # Group vhosts by category + local streamlit_items="" + local blog_items="" + local admin_items="" + local media_items="" + local dev_items="" + local infra_items="" + local presentation_items="" + local mail_items="" + local service_items="" + + for domain in $vhosts; do + # Skip base zone domains + [ "$domain" = "$zone" ] && continue + + # Skip disabled vhosts + is_vhost_enabled "$domain" || continue + + local backend=$(get_vhost_backend "$domain") + local type=$(detect_service_type "$backend" "$domain") + + # Extract name from subdomain + local subdomain=$(echo "$domain" | sed "s/\\.${zone}$//") + local name=$(echo "$subdomain" | sed 's/[-_]/ /g' | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) tolower(substr($i,2))}1') + + # Generate description based on type/backend + local desc="" + case "$type" in + streamlit) desc="Streamlit App" ;; + blog) desc="Blog / Site" ;; + admin) desc="Administration" ;; + media) desc="Media / Live" ;; + dev) desc="Development" ;; + infra) desc="Monitoring" ;; + presentation) desc="Slides" ;; + mail) desc="Mail Service" ;; + *) desc="Service" ;; + esac + + # Check certificate + local has_cert=0 + has_valid_cert "$domain" && has_cert=1 + [ "$has_cert" = "1" ] && cert_count=$((cert_count + 1)) + + local item_html=$(generate_service_html "$domain" "$name" "$desc" "$type" "$has_cert") + zone_count=$((zone_count + 1)) + total_count=$((total_count + 1)) + + case "$type" in + streamlit) streamlit_items="${streamlit_items}${item_html}\n" ;; + blog) blog_items="${blog_items}${item_html}\n" ;; + admin) admin_items="${admin_items}${item_html}\n" ;; + media) media_items="${media_items}${item_html}\n" ;; + dev) dev_items="${dev_items}${item_html}\n" ;; + infra) infra_items="${infra_items}${item_html}\n" ;; + presentation) presentation_items="${presentation_items}${item_html}\n" ;; + mail) mail_items="${mail_items}${item_html}\n" ;; + *) service_items="${service_items}${item_html}\n" ;; + esac + done + + # Skip empty zones + [ "$zone_count" -eq 0 ] && continue + + # Build zone section + zone_html="${zone_html} +
+
+ *.${zone} + ${zone_count} services +
" + + # Add categories with content + [ -n "$streamlit_items" ] && zone_html="${zone_html} +

Apps Streamlit

+
+$(printf "%b" "$streamlit_items")
" + + [ -n "$blog_items" ] && zone_html="${zone_html} +

Sites et Blogs

+
+$(printf "%b" "$blog_items")
" + + [ -n "$media_items" ] && zone_html="${zone_html} +

Médias et Live

+
+$(printf "%b" "$media_items")
" + + [ -n "$presentation_items" ] && zone_html="${zone_html} +

Présentations

+
+$(printf "%b" "$presentation_items")
" + + if [ -n "$admin_items" ] || [ -n "$infra_items" ]; then + zone_html="${zone_html} +

Infrastructure

+
" + [ -n "$admin_items" ] && zone_html="${zone_html} +$(printf "%b" "$admin_items")" + [ -n "$infra_items" ] && zone_html="${zone_html} +$(printf "%b" "$infra_items")" + zone_html="${zone_html} +
" + fi + + [ -n "$dev_items" ] && zone_html="${zone_html} +

Développement

+
+$(printf "%b" "$dev_items")
" + + [ -n "$mail_items" ] && zone_html="${zone_html} +

Services Mail

+
+$(printf "%b" "$mail_items")
" + + [ -n "$service_items" ] && zone_html="${zone_html} +

Autres Services

+
+$(printf "%b" "$service_items")
" + + zone_html="${zone_html} +
" + done + + # Add stats to header + local zone_count=$(echo "$zones_to_include" | wc -w) + cat >> "$output_file" < +
+
${total_count}
+
Services
+
+
+
${zone_count}
+
Zones
+
+
+
${cert_count}
+
SSL Certs
+
+
+ +STATS + + # Add zone sections + printf "%s" "$zone_html" >> "$output_file" + + # Footer + cat >> "$output_file" < +

Propulsé par SecuBox | Vortex DNS Mesh

+

Generated: $(date "+%Y-%m-%d %H:%M:%S")

+ + + + +HTMLFOOTER + + log " Generated: $output_file (${total_count} services, ${zone_count} zones, ${cert_count} certs)" +} + +# ============================================================================ +# Commands +# ============================================================================ + +cmd_generate() { + local zone="$1" + local single_zone="${2:-}" # --single to only include that zone + + if [ -n "$zone" ]; then + if [ "$single_zone" = "--single" ]; then + generate_landing_page "$zone" 0 + else + generate_landing_page "$zone" 1 + fi + else + # Generate for all zones + log "Generating landing pages for all zones..." + for z in $(get_zones); do + generate_landing_page "$z" 1 + done + fi + + log "Done!" +} + +cmd_list() { + echo "" + echo "Available Zones:" + echo "================" + local total=0 + for z in $(get_zones); do + local count=$(get_vhosts_for_zone "$z" | wc -l) + printf " %-30s %3d services\n" "$z" "$count" + total=$((total + count)) + done + echo "" + echo "Total: $total services across $(get_zones | wc -w) zones" + echo "" +} + +cmd_show() { + local zone="$1" + + if [ -z "$zone" ]; then + # Show all vhosts + echo "" + echo "All Vhosts:" + echo "===========" + for domain in $(get_all_vhosts); do + local backend=$(get_vhost_backend "$domain") + local type=$(detect_service_type "$backend" "$domain") + local enabled="✓" + is_vhost_enabled "$domain" || enabled="✗" + local cert="🔒" + has_valid_cert "$domain" || cert=" " + printf " %s %s %-40s [%-10s] → %s\n" "$enabled" "$cert" "$domain" "$type" "${backend:-inline}" + done + echo "" + else + echo "" + echo "Services for zone: $zone" + echo "=========================" + for domain in $(get_vhosts_for_zone "$zone"); do + local backend=$(get_vhost_backend "$domain") + local type=$(detect_service_type "$backend" "$domain") + local enabled="✓" + is_vhost_enabled "$domain" || enabled="✗" + local cert="🔒" + has_valid_cert "$domain" || cert=" " + printf " %s %s %-40s [%-10s] → %s\n" "$enabled" "$cert" "$domain" "$type" "${backend:-inline}" + done + echo "" + fi +} + +cmd_regenerate_all() { + log "Regenerating all landing pages..." + for z in $(get_zones); do + generate_landing_page "$z" 1 + done + log "All landing pages regenerated!" +} + +show_help() { + cat < [options] + +Commands: + generate [zone] [--single] + Generate landing page(s) + If zone specified, generate for that zone + --single: only include services from that zone + Default: includes ALL zones on each landing + + list List all detected zones and service counts + + show [zone] Show vhosts (optionally filtered by zone) + Displays: enabled, SSL, domain, type, backend + + regenerate Regenerate all landing pages + +Examples: + secubox-landing list + secubox-landing show + secubox-landing show gk2.secubox.in + secubox-landing generate gk2.secubox.in + secubox-landing generate gk2.secubox.in --single + secubox-landing regenerate + +Environment: + SITES_DIR Output directory (default: /srv/metablogizer/sites) + +EOF +} + +# ============================================================================ +# Main +# ============================================================================ + +case "${1:-}" in + generate|gen) + shift + cmd_generate "$@" + ;; + list|ls) + cmd_list + ;; + show) + shift + cmd_show "$@" + ;; + regenerate|regen) + cmd_regenerate_all + ;; + help|--help|-h|'') + show_help + ;; + *) + error "Unknown command: $1" + show_help >&2 + exit 1 + ;; +esac + +exit 0