#!/bin/sh # SecuBox MetaBlogizer - Static Site Publisher # Supports uhttpd (default) and nginx LXC runtime # Copyright (C) 2025 CyberMind.fr CONFIG="metablogizer" SITES_ROOT="/srv/metablogizer/sites" NGINX_LXC="metablogizer-nginx" LXC_PATH="/srv/lxc" PORT_BASE=8900 . /lib/functions.sh log_info() { echo "[INFO] $*"; logger -t metablogizer "$*"; } log_warn() { echo "[WARN] $*" >&2; } log_error() { echo "[ERROR] $*" >&2; } uci_get() { uci -q get ${CONFIG}.$1; } uci_set() { uci set ${CONFIG}.$1="$2" && uci commit ${CONFIG}; } fix_permissions() { local dir="$1" [ -d "$dir" ] || return 1 chmod 755 "$dir" find "$dir" -type d -exec chmod 755 {} \; find "$dir" -type f -exec chmod 644 {} \; } usage() { cat <<'EOF' MetaBlogizer - Static Site Publisher Usage: metablogizerctl [options] Site Commands: list List all sites create [repo] Create new site delete Delete site sync Sync site from git repo publish Publish site (create HAProxy vhost) gitea push Create Gitea repo and push site content gitea init-all Initialize Gitea repos for all existing sites emancipate KISS ULTIME MODE - Full exposure workflow: 1. DNS A record (Gandi/OVH) 2. Vortex DNS mesh publication 3. HAProxy vhost with SSL 4. WAF/mitmproxy integration 5. Path ACL (secubox.in/gk2/{name}) 6. SSL certificate (or wildcard) 7. Zero-downtime reload Runtime Commands: runtime Show current runtime runtime set Set runtime preference Management: status Show overall status install-nginx Install nginx LXC container (optional) Runtime Selection: auto - Auto-detect (uhttpd preferred) uhttpd - Use uhttpd instances (lightweight) nginx - Use nginx LXC container (more features) Examples: metablogizerctl create myblog blog.example.com metablogizerctl emancipate myblog # Full exposure in one command EOF } # =========================================== # Runtime Detection # =========================================== has_uhttpd() { [ -x /etc/init.d/uhttpd ]; } has_nginx_lxc() { command -v lxc-info >/dev/null 2>&1 && \ [ -d "$LXC_PATH/$NGINX_LXC/rootfs" ] } detect_runtime() { local configured=$(uci_get main.runtime) case "$configured" in uhttpd) if has_uhttpd; then echo "uhttpd" else log_error "uhttpd requested but not available" return 1 fi ;; nginx) if has_nginx_lxc; then echo "nginx" else log_error "nginx LXC requested but not installed" return 1 fi ;; auto|*) # Prefer uhttpd (lighter), fall back to nginx if has_uhttpd; then echo "uhttpd" elif has_nginx_lxc; then echo "nginx" else log_error "No runtime available" return 1 fi ;; esac } # =========================================== # Site Management # =========================================== get_next_port() { local port=$PORT_BASE while uci show uhttpd 2>/dev/null | grep -q "listen_http='0.0.0.0:$port'"; do port=$((port + 1)) done echo $port } # Convert site name to UCI section name (hyphens -> underscores) get_section() { echo "site_$(echo "$1" | tr '-' '_')" } site_exists() { local name="$1" local section=$(get_section "$name") uci -q get ${CONFIG}.${section} >/dev/null 2>&1 } cmd_list() { echo "MetaBlogizer Sites:" echo "===================" local runtime=$(detect_runtime 2>/dev/null) echo "Runtime: ${runtime:-none}" echo "" config_load "$CONFIG" local found=0 _print_site() { local section="$1" local name domain port enabled gitea_repo config_get name "$section" name config_get domain "$section" domain config_get port "$section" port config_get enabled "$section" enabled "0" config_get gitea_repo "$section" gitea_repo "" [ -z "$name" ] && return local status="disabled" [ "$enabled" = "1" ] && status="enabled" local dir_status="missing" [ -d "$SITES_ROOT/$name" ] && dir_status="exists" printf " %-15s %-25s :%-5s [%s] %s\n" "$name" "$domain" "$port" "$status" "$dir_status" found=1 } config_foreach _print_site site [ "$found" = "0" ] && echo " No sites configured" } cmd_create() { local name="$1" local domain="$2" local gitea_repo="$3" [ -z "$name" ] && { log_error "Site name required"; return 1; } [ -z "$domain" ] && { log_error "Domain required"; return 1; } # Sanitize name name=$(echo "$name" | tr -cd 'a-z0-9_-') if site_exists "$name"; then log_error "Site '$name' already exists" return 1 fi local runtime=$(detect_runtime) || return 1 local port=$(get_next_port) log_info "Creating site: $name ($domain) on port $port using $runtime" # Create site directory with proper permissions mkdir -p "$SITES_ROOT/$name" chmod 755 "$SITES_ROOT/$name" # Create placeholder index cat > "$SITES_ROOT/$name/index.html" < $name

$name

Site published with MetaBlogizer

https://$domain

EOF chmod 644 "$SITES_ROOT/$name/index.html" # Clone from Gitea if repo specified if [ -n "$gitea_repo" ]; then local gitea_url=$(uci_get main.gitea_url) [ -z "$gitea_url" ] && gitea_url="http://localhost:3000" log_info "Cloning from $gitea_url/$gitea_repo..." rm -rf "$SITES_ROOT/$name" git clone "$gitea_url/$gitea_repo.git" "$SITES_ROOT/$name" 2>/dev/null || { log_warn "Git clone failed, using placeholder" mkdir -p "$SITES_ROOT/$name" } fi # Always fix permissions for web serving fix_permissions "$SITES_ROOT/$name" # Configure runtime case "$runtime" in uhttpd) _create_uhttpd_site "$name" "$port" ;; nginx) _create_nginx_site "$name" ;; esac # Save site config uci set ${CONFIG}.site_${name}=site uci set ${CONFIG}.site_${name}.name="$name" uci set ${CONFIG}.site_${name}.domain="$domain" uci set ${CONFIG}.site_${name}.port="$port" uci set ${CONFIG}.site_${name}.runtime="$runtime" [ -n "$gitea_repo" ] && uci set ${CONFIG}.site_${name}.gitea_repo="$gitea_repo" uci set ${CONFIG}.site_${name}.enabled="1" uci commit ${CONFIG} log_info "Site created: $name" log_info "Directory: $SITES_ROOT/$name" log_info "Local URL: http://localhost:$port" # Auto-push to Gitea if enabled local gitea_token_cfg=$(uci_get main.gitea_token) if [ -n "$gitea_token_cfg" ]; then log_info "Auto-pushing to Gitea..." cmd_gitea_push "$name" fi echo "" echo "Next: Run 'metablogizerctl publish $name' to create HAProxy vhost" } _create_uhttpd_site() { local name="$1" local port="$2" log_info "Creating uhttpd instance for $name on port $port" uci set uhttpd.metablog_${name}=uhttpd uci set uhttpd.metablog_${name}.listen_http="0.0.0.0:$port" uci set uhttpd.metablog_${name}.home="$SITES_ROOT/$name" uci set uhttpd.metablog_${name}.index_page="index.html" uci set uhttpd.metablog_${name}.error_page="/index.html" uci commit uhttpd /etc/init.d/uhttpd reload 2>/dev/null || /etc/init.d/uhttpd restart } _create_nginx_site() { local name="$1" if ! has_nginx_lxc; then log_error "nginx LXC not installed. Run: metablogizerctl install-nginx" return 1 fi log_info "Creating nginx config for $name" local nginx_conf="$LXC_PATH/$NGINX_LXC/rootfs/etc/nginx/sites.d" mkdir -p "$nginx_conf" cat > "$nginx_conf/metablog-$name.conf" </dev/null || true } cmd_publish() { local name="$1" [ -z "$name" ] && { log_error "Site name required"; return 1; } if ! site_exists "$name"; then log_error "Site '$name' not found" return 1 fi local domain=$(uci_get site_${name}.domain) local port=$(uci_get site_${name}.port) [ -z "$domain" ] && { log_error "Site domain not configured"; return 1; } log_info "Publishing $name to $domain" # Create HAProxy backend local backend_name="metablog_${name}" uci set haproxy.${backend_name}=backend uci set haproxy.${backend_name}.name="$backend_name" uci set haproxy.${backend_name}.mode="http" uci set haproxy.${backend_name}.balance="roundrobin" uci set haproxy.${backend_name}.enabled="1" # Create HAProxy server local server_name="${backend_name}_srv" uci set haproxy.${server_name}=server uci set haproxy.${server_name}.backend="$backend_name" uci set haproxy.${server_name}.name="uhttpd" uci set haproxy.${server_name}.address="192.168.255.1" uci set haproxy.${server_name}.port="$port" uci set haproxy.${server_name}.weight="100" uci set haproxy.${server_name}.check="1" uci set haproxy.${server_name}.enabled="1" # Create HAProxy vhost local vhost_name=$(echo "$domain" | tr '.-' '_') uci set haproxy.${vhost_name}=vhost uci set haproxy.${vhost_name}.domain="$domain" uci set haproxy.${vhost_name}.backend="$backend_name" uci set haproxy.${vhost_name}.ssl="1" uci set haproxy.${vhost_name}.ssl_redirect="1" uci set haproxy.${vhost_name}.acme="1" uci set haproxy.${vhost_name}.enabled="1" uci commit haproxy # Regenerate HAProxy config /usr/sbin/haproxyctl generate 2>/dev/null /etc/init.d/haproxy reload 2>/dev/null log_info "Site published!" echo "" echo "URL: https://$domain" echo "" echo "To request SSL certificate:" echo " haproxyctl cert add $domain" # Auto-package for P2P distribution if [ -x /usr/sbin/secubox-content-pkg ]; then log_info "Packaging site for P2P distribution..." /usr/sbin/secubox-content-pkg site "$name" "$domain" 2>/dev/null && \ log_info "Site packaged for mesh distribution" fi # Regenerate GK2 Hub landing page if generator exists [ -x /usr/bin/gk2hub-generate ] && /usr/bin/gk2hub-generate >/dev/null 2>&1 & } cmd_delete() { local name="$1" [ -z "$name" ] && { log_error "Site name required"; return 1; } log_info "Deleting site: $name" # Remove uhttpd instance uci delete uhttpd.metablog_${name} 2>/dev/null uci commit uhttpd /etc/init.d/uhttpd reload 2>/dev/null # Remove HAProxy config local domain=$(uci_get site_${name}.domain) if [ -n "$domain" ]; then local vhost_name=$(echo "$domain" | tr '.-' '_') uci delete haproxy.${vhost_name} 2>/dev/null uci delete haproxy.metablog_${name} 2>/dev/null uci delete haproxy.metablog_${name}_srv 2>/dev/null uci commit haproxy /usr/sbin/haproxyctl generate 2>/dev/null /etc/init.d/haproxy reload 2>/dev/null fi # Remove site config uci delete ${CONFIG}.site_${name} 2>/dev/null uci commit ${CONFIG} # Optionally remove files if [ -d "$SITES_ROOT/$name" ]; then echo "Site directory: $SITES_ROOT/$name" echo "Remove manually if desired: rm -rf $SITES_ROOT/$name" fi log_info "Site deleted" # Regenerate GK2 Hub landing page if generator exists [ -x /usr/bin/gk2hub-generate ] && /usr/bin/gk2hub-generate >/dev/null 2>&1 & } cmd_sync() { local name="$1" [ -z "$name" ] && { log_error "Site name required"; return 1; } local section=$(get_section "$name") local gitea_repo=$(uci_get ${section}.gitea_repo) [ -z "$gitea_repo" ] && { log_error "No git repo configured for $name"; return 1; } local site_dir="$SITES_ROOT/$name" [ ! -d "$site_dir" ] && { log_error "Site directory not found"; return 1; } log_info "Syncing $name from git..." cd "$site_dir" if [ -d ".git" ]; then git pull origin main 2>/dev/null || git pull origin master 2>/dev/null || git pull else local gitea_url=$(uci_get main.gitea_url) [ -z "$gitea_url" ] && gitea_url="http://localhost:3000" git clone "$gitea_url/$gitea_repo.git" /tmp/metablog-sync-$$ cp -r /tmp/metablog-sync-$$/* "$site_dir/" rm -rf /tmp/metablog-sync-$$ fi # Fix permissions for web serving fix_permissions "$site_dir" log_info "Sync complete" } # Create Gitea repo via API and push local site content cmd_gitea_push() { local name="$1" [ -z "$name" ] && { log_error "Site name required"; return 1; } if ! site_exists "$name"; then log_error "Site '$name' not found" return 1 fi # Load Gitea config from dedicated gitea section local gitea_enabled=$(uci_get gitea.enabled) local gitea_url=$(uci_get gitea.url) local gitea_user=$(uci_get gitea.user) local gitea_token=$(uci_get gitea.token) [ -z "$gitea_url" ] && gitea_url="http://localhost:3000" if [ -z "$gitea_token" ]; then log_error "Gitea token not configured" log_info "Configure with:" log_info " uci set metablogizer.gitea=gitea" log_info " uci set metablogizer.gitea.enabled=1" log_info " uci set metablogizer.gitea.url='http://192.168.255.1:3001'" log_info " uci set metablogizer.gitea.user='admin'" log_info " uci set metablogizer.gitea.token='your-token'" log_info " uci commit metablogizer" return 1 fi local site_dir="$SITES_ROOT/$name" if [ ! -d "$site_dir" ]; then log_error "Site '$name' not found at $site_dir" return 1 fi local gitea_host=$(echo "$gitea_url" | sed 's|^https\?://||' | sed 's|/.*||') local gitea_proto=$(echo "$gitea_url" | grep -q '^https' && echo "https" || echo "http") local repo_name="metablog-$name" log_info "Creating Gitea repository: $repo_name" # Check if repo exists, create if not local repo_check=$(curl -s -o /dev/null -w "%{http_code}" \ -H "Authorization: token $gitea_token" \ "${gitea_url}/api/v1/repos/${gitea_user}/${repo_name}" 2>/dev/null) if [ "$repo_check" != "200" ]; then log_info "Repository doesn't exist, creating..." local create_result=$(curl -s -X POST \ -H "Authorization: token $gitea_token" \ -H "Content-Type: application/json" \ -d "{\"name\":\"${repo_name}\",\"description\":\"MetaBlogizer site: ${name}\",\"private\":true,\"auto_init\":false}" \ "${gitea_url}/api/v1/user/repos" 2>/dev/null) if ! echo "$create_result" | grep -q "\"name\":"; then log_error "Failed to create repository" log_error "Response: $create_result" return 1 fi log_info "Repository created: ${gitea_user}/${repo_name}" else log_info "Repository exists: ${gitea_user}/${repo_name}" fi # Initialize git in site directory if needed cd "$site_dir" if [ ! -d ".git" ]; then log_info "Initializing git repository..." git init git config user.name "$gitea_user" git config user.email "${gitea_user}@localhost" fi # Set remote local remote_url="${gitea_proto}://${gitea_user}:${gitea_token}@${gitea_host}/${gitea_user}/${repo_name}.git" git remote remove origin 2>/dev/null git remote add origin "$remote_url" # Add, commit and push log_info "Adding files and committing..." git add -A git commit -m "Auto-push from SecuBox MetaBlogizer at $(date -Iseconds)" 2>/dev/null || \ log_info "No changes to commit" log_info "Pushing to Gitea..." git push -u origin HEAD:main --force 2>&1 || { # Try master branch as fallback git push -u origin HEAD:master --force 2>&1 || { log_error "Failed to push to Gitea" return 1 } } # Save repo reference in UCI local section=$(get_section "$name") uci set "${CONFIG}.${section}.gitea_repo=${gitea_user}/${repo_name}" uci set "${CONFIG}.${section}.gitea_synced=$(date -Iseconds)" uci commit "$CONFIG" log_info "Push complete: ${gitea_url}/${gitea_user}/${repo_name}" } # Initialize Gitea for all existing sites cmd_gitea_init_all() { local gitea_token=$(uci_get main.gitea_token) if [ -z "$gitea_token" ]; then log_error "Gitea token not configured" log_info "Configure with:" log_info " uci set metablogizer.main.gitea_url='http://192.168.255.1:3000'" log_info " uci set metablogizer.main.gitea_user='admin'" log_info " uci set metablogizer.main.gitea_token='your-token'" log_info " uci commit metablogizer" return 1 fi log_info "Initializing Gitea repositories for all sites..." echo "" local success=0 local failed=0 # Load config and iterate over sites config_load "$CONFIG" _init_site_gitea() { local section="$1" local name config_get name "$section" name [ -z "$name" ] && return # Check if site directory exists if [ ! -d "$SITES_ROOT/$name" ]; then log_warn "[$name] Site directory not found, skipping" return fi # Check if already has a repo configured local existing_repo config_get existing_repo "$section" gitea_repo if [ -n "$existing_repo" ]; then log_info "[$name] Already linked to $existing_repo, syncing..." else log_info "[$name] Creating Gitea repository..." fi if cmd_gitea_push "$name"; then success=$((success + 1)) else failed=$((failed + 1)) fi echo "" } config_foreach _init_site_gitea site echo "========================================" echo "Gitea initialization complete" echo " Success: $success" echo " Failed: $failed" echo "========================================" } cmd_runtime() { local action="$1" local value="$2" if [ "$action" = "set" ]; then case "$value" in uhttpd|nginx|auto) uci_set main.runtime "$value" log_info "Runtime set to: $value" ;; *) log_error "Invalid runtime: $value (use uhttpd, nginx, or auto)" return 1 ;; esac else local configured=$(uci_get main.runtime) local detected=$(detect_runtime 2>/dev/null) echo "Configured: ${configured:-auto}" echo "Detected: ${detected:-none}" echo "" echo "Available:" has_uhttpd && echo " - uhttpd (installed)" || echo " - uhttpd (not available)" has_nginx_lxc && echo " - nginx LXC (installed)" || echo " - nginx LXC (not installed)" fi } cmd_status() { echo "MetaBlogizer Status" echo "===================" local enabled=$(uci_get main.enabled) local runtime=$(detect_runtime 2>/dev/null) local sites_count=$(uci show $CONFIG 2>/dev/null | grep -c "=site") echo "Enabled: $([ "$enabled" = "1" ] && echo "yes" || echo "no")" echo "Runtime: ${runtime:-none}" echo "Sites: $sites_count" echo "Sites Root: $SITES_ROOT" echo "" cmd_list } cmd_install_nginx() { log_info "Installing nginx LXC container..." command -v lxc-start >/dev/null 2>&1 || { log_error "LXC not installed. Install with: opkg install lxc lxc-common" return 1 } local rootfs="$LXC_PATH/$NGINX_LXC/rootfs" mkdir -p "$LXC_PATH/$NGINX_LXC" # Download Alpine local arch="aarch64" case "$(uname -m)" in x86_64) arch="x86_64" ;; armv7l) arch="armv7" ;; esac log_info "Downloading Alpine Linux..." wget -q -O /tmp/alpine-nginx.tar.gz \ "https://dl-cdn.alpinelinux.org/alpine/v3.19/releases/$arch/alpine-minirootfs-3.19.0-$arch.tar.gz" || { log_error "Failed to download Alpine" return 1 } mkdir -p "$rootfs" tar xzf /tmp/alpine-nginx.tar.gz -C "$rootfs" rm -f /tmp/alpine-nginx.tar.gz # Configure echo "nameserver 8.8.8.8" > "$rootfs/etc/resolv.conf" # Install nginx chroot "$rootfs" /bin/sh -c "apk update && apk add --no-cache nginx" # Create LXC config cat > "$LXC_PATH/$NGINX_LXC/config" </dev/null 2>&1; then log_warn "[DNS] dnsctl not found, skipping external DNS" return 1 fi # Get public IP local public_ip=$(curl -s --connect-timeout 5 https://ipv4.icanhazip.com 2>/dev/null | tr -d '\n') [ -z "$public_ip" ] && { log_warn "[DNS] Cannot detect public IP, skipping DNS"; return 1; } # Detect zone from domain suffix (try known zones) local zone="" local subdomain="" for z in "secubox.in" "maegia.tv" "cybermind.fr"; do if echo "$domain" | grep -q "\.${z}$"; then zone="$z" subdomain=$(echo "$domain" | sed "s/\.${z}$//") break elif [ "$domain" = "$z" ]; then zone="$z" subdomain="@" break fi done # Fallback to default zone if no match if [ -z "$zone" ]; then zone="$default_zone" subdomain=$(echo "$domain" | sed "s/\.${zone}$//") fi [ -z "$zone" ] && { log_warn "[DNS] No zone detected, skipping external DNS"; return 1; } log_info "[DNS] Registering $subdomain.$zone -> $public_ip via $provider" # Register on the published domain's zone dnsctl -z "$zone" add A "$subdomain" "$public_ip" 3600 # Also register on vortex node subdomain (e.g., bday.gk2.secubox.in) if [ -n "$vortex_wildcard" ]; then local vortex_zone=$(echo "$vortex_wildcard" | sed 's/^[^.]*\.//') local vortex_node=$(echo "$vortex_wildcard" | cut -d. -f1) local vortex_subdomain="${name}.${vortex_node}" log_info "[DNS] Registering $vortex_subdomain.$vortex_zone -> $public_ip (vortex node)" dnsctl -z "$vortex_zone" add A "$vortex_subdomain" "$public_ip" 3600 fi log_info "[DNS] Verify with: dnsctl verify $domain" } _emancipate_vortex() { local name="$1" local domain="$2" # Check if vortexctl is available if ! command -v vortexctl >/dev/null 2>&1; then log_info "[VORTEX] vortexctl not found, skipping mesh publication" return 0 fi # Check if vortex-dns is enabled local vortex_enabled=$(uci -q get vortex-dns.main.enabled) if [ "$vortex_enabled" = "1" ]; then log_info "[VORTEX] Publishing $name as $domain to mesh" vortexctl mesh publish "$name" "$domain" 2>/dev/null else log_info "[VORTEX] Vortex DNS disabled, skipping mesh publication" fi } _emancipate_haproxy() { local name="$1" local domain="$2" local port=$(uci_get site_${name}.port) log_info "[HAPROXY] Creating vhost for $domain" # Create backend local backend_name="metablog_${name}" uci set haproxy.${backend_name}=backend uci set haproxy.${backend_name}.name="$backend_name" uci set haproxy.${backend_name}.mode="http" uci set haproxy.${backend_name}.balance="roundrobin" uci set haproxy.${backend_name}.enabled="1" # Create server local server_name="${backend_name}_srv" uci set haproxy.${server_name}=server uci set haproxy.${server_name}.backend="$backend_name" uci set haproxy.${server_name}.name="uhttpd" uci set haproxy.${server_name}.address="192.168.255.1" uci set haproxy.${server_name}.port="$port" uci set haproxy.${server_name}.weight="100" uci set haproxy.${server_name}.check="1" uci set haproxy.${server_name}.enabled="1" # Create vhost with SSL local vhost_name=$(echo "$domain" | tr '.-' '_') uci set haproxy.${vhost_name}=vhost uci set haproxy.${vhost_name}.domain="$domain" uci set haproxy.${vhost_name}.backend="$backend_name" uci set haproxy.${vhost_name}.ssl="1" uci set haproxy.${vhost_name}.ssl_redirect="1" uci set haproxy.${vhost_name}.acme="1" uci set haproxy.${vhost_name}.enabled="1" uci commit haproxy # Generate HAProxy config if command -v haproxyctl >/dev/null 2>&1; then haproxyctl generate 2>/dev/null fi } _emancipate_mitmproxy() { local name="$1" local domain="$2" log_info "[WAF] Syncing mitmproxy routes from HAProxy config" # Use mitmproxyctl to sync routes from HAProxy UCI config # This ensures all routes are in sync and mitmproxy will auto-reload if command -v mitmproxyctl >/dev/null 2>&1; then mitmproxyctl sync-routes 2>&1 | while read -r line; do log_info "[WAF] $line" done log_info "[WAF] Routes synced - mitmproxy will auto-reload on next request" else log_warn "[WAF] mitmproxyctl not found, manually adding route" # Fallback: directly add route to the routes file local port=$(uci_get site_${name}.port) local routes_file="/srv/mitmproxy-in/haproxy-routes.json" if [ -f "$routes_file" ]; then python3 -c " import json try: with open('$routes_file') as f: data = json.load(f) data['$domain'] = ['192.168.255.1', $port] with open('$routes_file', 'w') as f: json.dump(data, f, indent=2) print('[WAF] Route added: $domain -> 192.168.255.1:$port') except Exception as e: print(f'[WAF] Error: {e}') " 2>/dev/null fi fi } _emancipate_path_acl() { local name="$1" local backend_name="metablog_${name}" log_info "[PATH] Adding /gk2/$name path ACL to secubox.in" # Create path ACL for secubox.in/gk2/{name} local acl_name="path_gk2_${name}" uci set haproxy.${acl_name}=acl uci set haproxy.${acl_name}.type="path_beg" uci set haproxy.${acl_name}.pattern="/gk2/${name}" uci set haproxy.${acl_name}.backend="$backend_name" uci set haproxy.${acl_name}.host="secubox.in" uci set haproxy.${acl_name}.enabled="1" uci set haproxy.${acl_name}.waf_bypass="1" uci commit haproxy log_info "[PATH] Path ACL created: secubox.in/gk2/$name -> $backend_name" } _emancipate_ssl() { local domain="$1" log_info "[SSL] Requesting certificate for $domain" # Check if haproxyctl is available if ! command -v haproxyctl >/dev/null 2>&1; then log_warn "[SSL] haproxyctl not found, skipping SSL" return 1 fi # haproxyctl cert add handles ACME webroot mode (no HAProxy restart needed) haproxyctl cert add "$domain" 2>&1 | while read line; do echo " $line" done if [ -f "/srv/haproxy/certs/$domain.pem" ]; then log_info "[SSL] Certificate obtained successfully" else log_warn "[SSL] Certificate request may still be pending" log_warn "[SSL] Check with: haproxyctl cert verify $domain" fi } _emancipate_reload() { log_info "[RELOAD] Applying HAProxy configuration" # Generate fresh config haproxyctl generate 2>/dev/null # Always restart for clean state with new vhosts/certs log_info "[RELOAD] Restarting HAProxy for clean state..." /etc/init.d/haproxy restart 2>/dev/null sleep 1 # Verify HAProxy is running if pgrep haproxy >/dev/null 2>&1; then log_info "[RELOAD] HAProxy restarted successfully" else log_warn "[RELOAD] HAProxy may not have started properly" fi # Regenerate GK2 Hub landing page if generator exists [ -x /usr/bin/gk2hub-generate ] && /usr/bin/gk2hub-generate >/dev/null 2>&1 & } cmd_emancipate() { local name="$1" [ -z "$name" ] && { log_error "Site name required"; usage; return 1; } if ! site_exists "$name"; then log_error "Site '$name' not found" log_error "Create first: metablogizerctl create $name " return 1 fi local section=$(get_section "$name") local domain=$(uci_get ${section}.domain) [ -z "$domain" ] && { log_error "Site domain not configured"; return 1; } echo "" echo "==============================================" echo " KISS ULTIME MODE: Emancipating $name" echo "==============================================" echo "" # Step 1: DNS Registration (external provider) _emancipate_dns "$name" "$domain" # Step 2: Vortex DNS (mesh registration) _emancipate_vortex "$name" "$domain" # Step 3: HAProxy vhost + backend _emancipate_haproxy "$name" "$domain" # Step 4: WAF/mitmproxy integration _emancipate_mitmproxy "$name" "$domain" # Step 5: Path ACL for secubox.in/gk2/{name} _emancipate_path_acl "$name" # Step 6: SSL Certificate (wildcard covers *.gk2.secubox.in) # Only request if not covered by wildcard case "$domain" in *.gk2.secubox.in) log_info "[SSL] Using wildcard certificate *.gk2.secubox.in" ;; *) _emancipate_ssl "$domain" ;; esac # Step 7: Reload HAProxy _emancipate_reload # Mark site as emancipated uci set ${CONFIG}.${section}.emancipated="1" uci set ${CONFIG}.${section}.emancipated_at="$(date -Iseconds)" uci commit ${CONFIG} echo "" echo "==============================================" echo " EMANCIPATION COMPLETE" echo "==============================================" echo "" echo " Site: https://$domain" echo " Status: Published and SSL-protected" echo " Mesh: $(uci -q get vortex-dns.main.enabled | grep -q 1 && echo 'Published' || echo 'Disabled')" echo "" echo " Verify:" echo " curl -v https://$domain" echo " dnsctl verify $domain" echo " haproxyctl cert verify $domain" echo "" } # =========================================== # Main # =========================================== case "${1:-}" in list) shift; cmd_list "$@" ;; create) shift; cmd_create "$@" ;; delete) shift; cmd_delete "$@" ;; sync) shift; cmd_sync "$@" ;; publish) shift; cmd_publish "$@" ;; emancipate) shift; cmd_emancipate "$@" ;; runtime) shift; cmd_runtime "$@" ;; status) shift; cmd_status "$@" ;; install-nginx) shift; cmd_install_nginx "$@" ;; gitea) shift case "${1:-}" in push) shift; cmd_gitea_push "$@" ;; init-all) shift; cmd_gitea_init_all "$@" ;; *) echo "Usage: metablogizerctl gitea {push|init-all} [name]"; exit 1 ;; esac ;; help|--help|-h) usage ;; *) usage ;; esac