secubox-openwrt/package/secubox/secubox-app-metablogizer/files/usr/sbin/metablogizerctl
CyberMind-FR ddf480e6ed fix(droplet,dpi): Resolve publish hang and broken pipe errors
- dropletctl: Remove pipe to grep that blocked on background children
- metablogizerctl: Background HAProxy generate/reload (~90s with 95 certs)
- dpi-lan-collector: Pre-compute flow counts in single pass instead of
  spawning grep per client (eliminates broken pipe errors)

Publish time reduced from ~2 min to ~35 seconds.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-17 07:39:09 +01:00

1600 lines
44 KiB
Bash

#!/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
NFO_TEMPLATE="/usr/share/metablogizer/nfo-template.nfo"
. /lib/functions.sh
# Load NFO parser library if available
NFO_PARSER="/usr/share/streamlit-forge/lib/nfo-parser.sh"
[ -f "$NFO_PARSER" ] && . "$NFO_PARSER"
log_info() { echo "[INFO] $*"; logger -t metablogizer "$*"; }
log_warn() { echo "[WARN] $*" >&2; }
log_error() { echo "[ERROR] $*" >&2; }
log_ok() { echo "[OK] $*"; }
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 <command> [options]
Site Commands:
list List all sites
create <name> <domain> [repo] Create new site
delete <name> Delete site
sync <name> Sync site from git repo
publish <name> Publish site (create HAProxy vhost)
gitea push <name> Create Gitea repo and push site content
gitea init-all Initialize Gitea repos for all existing sites
emancipate <name> 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 <uhttpd|nginx> Set runtime preference
Management:
status Show overall status
check-ports Check for duplicate port assignments
fix-ports Auto-fix duplicate ports
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)
NFO Module Manifest:
nfo init <name> Generate README.nfo manifest
nfo info <name> Show NFO summary
nfo edit <name> Edit manifest in editor
nfo validate <name> Validate NFO structure
nfo sync <name> Sync NFO fields from UCI config
nfo json <name> Export NFO as JSON
Examples:
metablogizerctl create myblog blog.example.com
metablogizerctl emancipate myblog # Full exposure in one command
metablogizerctl nfo init myblog # Generate NFO manifest
metablogizerctl nfo info myblog # View manifest summary
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
# ===========================================
port_in_use() {
local port="$1"
# Check uhttpd config
uci show uhttpd 2>/dev/null | grep -q "listen_http='0.0.0.0:$port'" && return 0
# Check metablogizer config (in case uhttpd hasn't loaded the site yet)
uci show ${CONFIG} 2>/dev/null | grep -q "\.port='$port'" && return 0
return 1
}
get_next_port() {
local port=$PORT_BASE
while port_in_use "$port"; do
port=$((port + 1))
done
echo $port
}
# Check for duplicate ports across all sites
cmd_check_ports() {
echo "Checking for duplicate ports..."
echo ""
local duplicates=0
local ports_file=$(mktemp)
# Collect all ports with their sites
uci show ${CONFIG} 2>/dev/null | grep "\.port=" | while read line; do
local section=$(echo "$line" | cut -d. -f2)
local port=$(echo "$line" | cut -d= -f2 | tr -d "'")
local name=$(uci_get "${section}.name" 2>/dev/null)
local domain=$(uci_get "${section}.domain" 2>/dev/null)
echo "$port|$name|$domain" >> "$ports_file"
done
# Find duplicates
local seen_ports=""
while IFS='|' read port name domain; do
if echo "$seen_ports" | grep -q ":$port:"; then
echo "DUPLICATE: Port $port used by $name ($domain)"
duplicates=$((duplicates + 1))
else
seen_ports="$seen_ports:$port:"
fi
done < "$ports_file"
rm -f "$ports_file"
if [ "$duplicates" -eq 0 ]; then
echo "No duplicate ports found."
else
echo ""
echo "Found $duplicates duplicate port(s)!"
echo "Run 'metablogizerctl fix-ports' to auto-assign new ports."
return 1
fi
}
# Auto-fix duplicate ports
cmd_fix_ports() {
echo "Scanning for duplicate ports..."
local ports_file=$(mktemp)
local seen_ports=""
local fixed=0
# Collect all ports
uci show ${CONFIG} 2>/dev/null | grep "\.port=" | while read line; do
local section=$(echo "$line" | cut -d. -f2)
local port=$(echo "$line" | cut -d= -f2 | tr -d "'")
echo "$section|$port" >> "$ports_file"
done
# Process and fix duplicates
while IFS='|' read section port; do
if echo "$seen_ports" | grep -q ":$port:"; then
local name=$(uci_get "${section}.name" 2>/dev/null)
local new_port=$(get_next_port)
echo "Fixing $name: port $port -> $new_port"
# Update metablogizer config
uci set ${CONFIG}.${section}.port="$new_port"
# Update uhttpd config
uci set uhttpd.metablog_${section}.listen_http="0.0.0.0:$new_port"
# Update HAProxy backend if exists
uci set haproxy.metablog_${section}_srv.port="$new_port" 2>/dev/null
seen_ports="$seen_ports:$new_port:"
fixed=$((fixed + 1))
else
seen_ports="$seen_ports:$port:"
fi
done < "$ports_file"
rm -f "$ports_file"
if [ "$fixed" -gt 0 ]; then
uci commit ${CONFIG}
uci commit uhttpd
uci commit haproxy 2>/dev/null
echo ""
echo "Fixed $fixed duplicate port(s)."
echo "Restarting services..."
/etc/init.d/uhttpd restart
haproxyctl generate 2>/dev/null && haproxyctl reload 2>/dev/null
else
echo "No duplicate ports found."
fi
}
# 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" <<EOF
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>$name</title>
<meta property="og:title" content="$name">
<meta property="og:url" content="https://$domain">
<meta property="og:type" content="website">
<style>
body { font-family: system-ui; max-width: 800px; margin: 50px auto; padding: 20px; }
h1 { color: #3b82f6; }
</style>
</head>
<body>
<h1>$name</h1>
<p>Site published with MetaBlogizer</p>
<p><a href="https://$domain">https://$domain</a></p>
</body>
</html>
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
# Auto-generate NFO manifest if template exists
if [ -f "$NFO_TEMPLATE" ]; then
log_info "Generating NFO manifest..."
_nfo_init "$name" 2>/dev/null || true
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" <<EOF
location /$name/ {
alias /srv/sites/$name/;
index index.html;
try_files \$uri \$uri/ /index.html;
}
EOF
# Reload nginx in container
lxc-attach -n "$NGINX_LXC" -- nginx -s reload 2>/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; }
# Validate NFO manifest if exists
local site_dir="$SITES_ROOT/$name"
local nfo_file="$site_dir/README.nfo"
if [ -f "$nfo_file" ]; then
log_info "Validating NFO manifest..."
if ! _nfo_validate "$name" >/dev/null 2>&1; then
log_warn "NFO manifest has validation errors"
log_warn "Fix with: metablogizerctl nfo edit $name"
fi
fi
log_info "Publishing $name to $domain"
# Ensure uhttpd instance exists
local existing_uhttpd=$(uci -q get uhttpd.metablogizer_${name})
if [ -z "$existing_uhttpd" ]; then
log_info "Creating uhttpd instance for $name on port $port"
_create_uhttpd_site "$name" "$port"
fi
# 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 and reload container (in background - takes ~90s with many vhosts)
(
/usr/sbin/haproxyctl generate >/dev/null 2>&1
/usr/sbin/haproxyctl reload >/dev/null 2>&1
) &
log_info "Site published!"
log_info "HAProxy config regenerating in background..."
echo ""
echo "URL: https://$domain"
echo ""
echo "To request SSL certificate:"
echo " haproxyctl cert add $domain"
# Auto-push to Gitea if configured
local gitea_enabled=$(uci_get gitea.enabled)
if [ "$gitea_enabled" = "1" ]; then
log_info "Pushing to Gitea repository..."
cmd_gitea_push "$name" 2>/dev/null &
fi
# 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
# Background HAProxy regeneration (takes ~90s with many vhosts)
(/usr/sbin/haproxyctl generate >/dev/null 2>&1 && /etc/init.d/haproxy reload >/dev/null 2>&1) &
log_info "HAProxy config regenerating in background..."
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" <<EOF
lxc.uts.name = $NGINX_LXC
lxc.rootfs.path = dir:$rootfs
lxc.net.0.type = none
lxc.mount.auto = proc:mixed sys:ro cgroup:mixed
lxc.mount.entry = $SITES_ROOT srv/sites none bind,create=dir 0 0
lxc.cap.drop = sys_admin sys_module mac_admin mac_override
lxc.init.cmd = /usr/sbin/nginx -g 'daemon off;'
EOF
log_info "nginx LXC installed"
log_info "Start with: lxc-start -n $NGINX_LXC -d"
}
# ===========================================
# KISS ULTIME MODE - Emancipate
# ===========================================
_emancipate_dns() {
local name="$1"
local domain="$2"
local default_zone=$(uci -q get dns-provider.main.zone)
local provider=$(uci -q get dns-provider.main.provider)
local vortex_wildcard=$(uci -q get vortex-dns.master.wildcard_domain)
# Check if dnsctl is available
if ! command -v dnsctl >/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"
# Ensure uhttpd instance exists
local existing_uhttpd=$(uci -q get uhttpd.metablogizer_${name})
if [ -z "$existing_uhttpd" ]; then
log_info "[HAPROXY] Creating uhttpd instance for $name on port $port"
_create_uhttpd_site "$name" "$port"
fi
# 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 (in background - takes ~90s with many vhosts)
if command -v haproxyctl >/dev/null 2>&1; then
haproxyctl generate >/dev/null 2>&1 &
fi
}
_emancipate_mitmproxy() {
local name="$1"
local domain="$2"
local port=$(uci_get site_${name}.port)
local routes_file="/srv/mitmproxy-in/haproxy-routes.json"
# Get the host's LAN IP (mitmproxy runs in container, can't reach 127.0.0.1 on host)
local host_ip
host_ip=$(uci -q get network.lan.ipaddr || echo "192.168.255.1")
log_info "[WAF] Adding route: $domain -> $host_ip:$port"
# Direct JSON update - most reliable method
if [ -f "$routes_file" ] && command -v python3 >/dev/null 2>&1; then
python3 -c "
import json
import sys
try:
with open('$routes_file', 'r') as f:
routes = json.load(f)
routes['$domain'] = ['$host_ip', $port]
with open('$routes_file', 'w') as f:
json.dump(routes, f, indent=2)
print('Route added successfully')
except Exception as e:
print(f'Error: {e}', file=sys.stderr)
sys.exit(1)
" 2>&1 && {
log_info "[WAF] Route registered in $routes_file"
return 0
}
fi
# Fallback: Use centralized secubox-route if available
if command -v secubox-route >/dev/null 2>&1; then
if secubox-route add "$domain" "$host_ip" "$port" "metablogizer" 2>&1; then
log_info "[WAF] Route registered via secubox-route"
return 0
fi
fi
# Fallback: Sync via mitmproxyctl
if command -v mitmproxyctl >/dev/null 2>&1; then
log_warn "[WAF] Direct update failed, trying mitmproxyctl"
mitmproxyctl sync-routes >/dev/null 2>&1 && {
log_info "[WAF] Routes synced via mitmproxyctl"
return 0
}
fi
log_error "[WAF] Failed to register route - manual intervention required"
log_error "[WAF] Add manually to $routes_file"
return 1
}
_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 <domain>"
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 ""
}
# ===========================================
# NFO Module Manifest
# ===========================================
_nfo_init() {
local name="$1"
local site_dir="$SITES_ROOT/$name"
local nfo_file="$site_dir/README.nfo"
if [ -f "$nfo_file" ]; then
log_warn "NFO already exists: $nfo_file"
echo "Use 'metablogizerctl nfo edit $name' to modify"
return 1
fi
if [ ! -f "$NFO_TEMPLATE" ]; then
log_error "NFO template not found: $NFO_TEMPLATE"
return 1
fi
# Get site info from UCI
local section=$(get_section "$name")
local domain=$(uci_get ${section}.domain)
local short_desc="MetaBlog site: $name"
# Generate NFO from template
local date_now=$(date +%Y-%m-%d)
sed -e "s/{{APP_ID}}/$name/g" \
-e "s/{{APP_NAME}}/$name/g" \
-e "s/{{VERSION}}/1.0.0/g" \
-e "s/{{DATE}}/$date_now/g" \
-e "s/{{SHORT_DESC}}/$short_desc/g" \
"$NFO_TEMPLATE" > "$nfo_file"
log_info "NFO manifest created: $nfo_file"
echo ""
echo "Edit with: metablogizerctl nfo edit $name"
}
_nfo_info() {
local name="$1"
local site_dir="$SITES_ROOT/$name"
local nfo_file="$site_dir/README.nfo"
if [ ! -f "$nfo_file" ]; then
log_error "No NFO manifest found for $name"
log_info "Create one with: metablogizerctl nfo init $name"
return 1
fi
# Check if nfo_parser is available
if ! type nfo_parse >/dev/null 2>&1; then
log_warn "NFO parser not available, showing raw file"
cat "$nfo_file"
return 0
fi
echo ""
echo "╔══════════════════════════════════════════════════════════════╗"
echo "║ NFO Manifest: $name"
echo "╚══════════════════════════════════════════════════════════════╝"
echo ""
# Parse and display key sections
echo "IDENTITY"
echo " ID: $(nfo_get "$nfo_file" identity id)"
echo " Name: $(nfo_get "$nfo_file" identity name)"
echo " Version: $(nfo_get "$nfo_file" identity version)"
echo " Author: $(nfo_get "$nfo_file" identity author)"
echo ""
echo "DESCRIPTION"
echo " $(nfo_get "$nfo_file" description short)"
echo ""
echo "CLASSIFICATION"
echo " Category: $(nfo_get "$nfo_file" tags category)"
echo " Keywords: $(nfo_get "$nfo_file" tags keywords)"
echo " Audience: $(nfo_get "$nfo_file" tags audience)"
echo ""
echo "RUNTIME"
echo " Type: $(nfo_get "$nfo_file" runtime type)"
echo " Framework: $(nfo_get "$nfo_file" runtime framework)"
echo " Generator: $(nfo_get "$nfo_file" runtime generator)"
echo ""
echo "EXPOSURE"
echo " Auto-expose: $(nfo_get "$nfo_file" exposure auto_expose)"
echo " SSL: $(nfo_get "$nfo_file" exposure ssl)"
echo " WAF: $(nfo_get "$nfo_file" exposure waf_enabled)"
echo ""
# Show AI context if available
if nfo_has_ai_context "$nfo_file" 2>/dev/null; then
echo "DYNAMICS (AI)"
echo " Capabilities: $(nfo_get "$nfo_file" dynamics capabilities)"
echo ""
fi
echo "File: $nfo_file"
}
_nfo_edit() {
local name="$1"
local site_dir="$SITES_ROOT/$name"
local nfo_file="$site_dir/README.nfo"
if [ ! -f "$nfo_file" ]; then
log_warn "No NFO manifest found, creating one first..."
_nfo_init "$name" || return 1
fi
# Create backup before editing
if type nfo_backup >/dev/null 2>&1; then
nfo_backup "$nfo_file"
else
cp "$nfo_file" "${nfo_file}.bak"
fi
# Use available editor
local editor="${EDITOR:-vi}"
command -v nano >/dev/null 2>&1 && editor="nano"
command -v vim >/dev/null 2>&1 && editor="vim"
"$editor" "$nfo_file"
# Validate after edit
if type nfo_validate >/dev/null 2>&1; then
if nfo_validate "$nfo_file"; then
log_info "NFO manifest is valid"
else
log_warn "NFO manifest has validation warnings"
echo "Restore backup with: cp ${nfo_file}.bak $nfo_file"
fi
fi
}
_nfo_validate() {
local name="$1"
local site_dir="$SITES_ROOT/$name"
local nfo_file="$site_dir/README.nfo"
if [ ! -f "$nfo_file" ]; then
log_error "No NFO manifest found for $name"
return 1
fi
echo "Validating NFO manifest: $nfo_file"
echo ""
local errors=0
local warnings=0
# Use nfo_validate if available
if type nfo_validate >/dev/null 2>&1; then
nfo_validate "$nfo_file"
return $?
fi
# Fallback: basic validation
# Check required sections
for section in identity description tags runtime; do
if ! grep -q "^\[$section\]" "$nfo_file"; then
echo "[ERROR] Missing required section: [$section]"
errors=$((errors + 1))
fi
done
# Check required identity fields
local id=$(grep "^id=" "$nfo_file" | head -1 | cut -d= -f2)
local nfo_name=$(grep "^name=" "$nfo_file" | head -1 | cut -d= -f2)
local version=$(grep "^version=" "$nfo_file" | head -1 | cut -d= -f2)
[ -z "$id" ] && { echo "[ERROR] Missing identity.id"; errors=$((errors + 1)); }
[ -z "$nfo_name" ] && { echo "[ERROR] Missing identity.name"; errors=$((errors + 1)); }
[ -z "$version" ] && { echo "[ERROR] Missing identity.version"; errors=$((errors + 1)); }
# Check recommended fields
local category=$(grep "^category=" "$nfo_file" | head -1 | cut -d= -f2)
local short=$(grep "^short=" "$nfo_file" | head -1 | cut -d= -f2)
[ -z "$category" ] && { echo "[WARN] Missing tags.category"; warnings=$((warnings + 1)); }
[ -z "$short" ] && { echo "[WARN] Missing description.short"; warnings=$((warnings + 1)); }
echo ""
if [ $errors -eq 0 ] && [ $warnings -eq 0 ]; then
echo "✓ NFO manifest is valid"
return 0
elif [ $errors -eq 0 ]; then
echo "⚠ NFO valid with $warnings warning(s)"
return 0
else
echo "✗ NFO invalid: $errors error(s), $warnings warning(s)"
return 1
fi
}
_nfo_sync() {
local name="$1"
local site_dir="$SITES_ROOT/$name"
local nfo_file="$site_dir/README.nfo"
if [ ! -f "$nfo_file" ]; then
log_error "No NFO manifest found for $name"
log_info "Create one first: metablogizerctl nfo init $name"
return 1
fi
# Check if nfo_sync_from_uci is available
if ! type nfo_sync_from_uci >/dev/null 2>&1; then
log_error "NFO parser with sync support not available"
return 1
fi
log_info "Syncing NFO from UCI config..."
# Get site UCI section
local section=$(get_section "$name")
# Build UCI prefix for sync
local uci_prefix="${CONFIG}.${section}"
# Sync using parser function
nfo_sync_from_uci "$nfo_file" "$uci_prefix"
log_info "NFO synced from UCI"
echo "View with: metablogizerctl nfo info $name"
}
_nfo_json() {
local name="$1"
local site_dir="$SITES_ROOT/$name"
local nfo_file="$site_dir/README.nfo"
if [ ! -f "$nfo_file" ]; then
log_error "No NFO manifest found for $name"
return 1
fi
# Use nfo_to_json if available
if type nfo_to_json >/dev/null 2>&1; then
nfo_to_json "$nfo_file"
return $?
fi
# Fallback: basic JSON output
log_warn "NFO parser not available, using basic JSON export"
local id=$(grep "^id=" "$nfo_file" | head -1 | cut -d= -f2)
local nfo_name=$(grep "^name=" "$nfo_file" | head -1 | cut -d= -f2)
local version=$(grep "^version=" "$nfo_file" | head -1 | cut -d= -f2)
local category=$(grep "^category=" "$nfo_file" | head -1 | cut -d= -f2)
local short=$(grep "^short=" "$nfo_file" | head -1 | cut -d= -f2)
cat <<EOF
{
"identity": {
"id": "$id",
"name": "$nfo_name",
"version": "$version"
},
"tags": {
"category": "$category"
},
"description": {
"short": "$short"
}
}
EOF
}
cmd_nfo() {
local action="$1"
local name="$2"
case "$action" in
init)
[ -z "$name" ] && { log_error "Site name required"; return 1; }
site_exists "$name" || { log_error "Site '$name' not found"; return 1; }
_nfo_init "$name"
;;
init-all)
log_info "Generating NFO manifests for all MetaBlog sites..."
echo ""
# Use temp files for counters (config_foreach runs in subshell)
local tmp_success="/tmp/nfo_success_$$"
local tmp_skipped="/tmp/nfo_skipped_$$"
local tmp_failed="/tmp/nfo_failed_$$"
echo 0 > "$tmp_success"
echo 0 > "$tmp_skipped"
echo 0 > "$tmp_failed"
config_load "$CONFIG"
_generate_nfo_site() {
local section="$1"
local site_name
config_get site_name "$section" name
[ -z "$site_name" ] && return
local site_dir="$SITES_ROOT/$site_name"
[ ! -d "$site_dir" ] && {
log_warn "[$site_name] Site directory not found, skipping"
return
}
local nfo_file="$site_dir/README.nfo"
if [ -f "$nfo_file" ]; then
log_info "[$site_name] NFO already exists, skipping"
echo $(($(cat "$tmp_skipped") + 1)) > "$tmp_skipped"
return
fi
log_info "[$site_name] Generating README.nfo..."
if _nfo_init "$site_name"; then
log_ok " Created: $nfo_file"
echo $(($(cat "$tmp_success") + 1)) > "$tmp_success"
else
log_error " Failed to create NFO"
echo $(($(cat "$tmp_failed") + 1)) > "$tmp_failed"
fi
}
config_foreach _generate_nfo_site site
local success=$(cat "$tmp_success")
local skipped=$(cat "$tmp_skipped")
local failed=$(cat "$tmp_failed")
rm -f "$tmp_success" "$tmp_skipped" "$tmp_failed"
echo ""
echo "========================================"
echo "NFO generation complete"
echo " Created: $success"
echo " Skipped: $skipped (already exist)"
echo " Failed: $failed"
echo "========================================"
;;
info)
[ -z "$name" ] && { log_error "Site name required"; return 1; }
site_exists "$name" || { log_error "Site '$name' not found"; return 1; }
_nfo_info "$name"
;;
edit)
[ -z "$name" ] && { log_error "Site name required"; return 1; }
site_exists "$name" || { log_error "Site '$name' not found"; return 1; }
_nfo_edit "$name"
;;
validate)
[ -z "$name" ] && { log_error "Site name required"; return 1; }
site_exists "$name" || { log_error "Site '$name' not found"; return 1; }
_nfo_validate "$name"
;;
sync)
[ -z "$name" ] && { log_error "Site name required"; return 1; }
site_exists "$name" || { log_error "Site '$name' not found"; return 1; }
_nfo_sync "$name"
;;
json)
[ -z "$name" ] && { log_error "Site name required"; return 1; }
site_exists "$name" || { log_error "Site '$name' not found"; return 1; }
_nfo_json "$name"
;;
*)
echo "Usage: metablogizerctl nfo <init|init-all|info|edit|validate|sync|json> [name]"
return 1
;;
esac
}
# ===========================================
# 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 "$@" ;;
check-ports) shift; cmd_check_ports "$@" ;;
fix-ports) shift; cmd_fix_ports "$@" ;;
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
;;
nfo)
shift
cmd_nfo "$@"
;;
help|--help|-h) usage ;;
*) usage ;;
esac