feat(haproxy): Add dynamic path ACL management commands

New haproxyctl path commands:
- path list: Show all path ACLs with patterns and backends
- path sync <prefix> <host>: Auto-generate ACLs from all backends
  Extracts short name from backend (metablog_X -> X, streamlit_Y -> Y)
  Skips existing ACLs, only adds new ones
- path add: Manually add single path ACL
- path remove: Remove specific path ACL
- path clear: Remove all ACLs matching prefix

This enables dynamic route updates when backends change.
Example: haproxyctl path sync /gk2 secubox.in

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-02-09 06:56:54 +01:00
parent b6235df631
commit d0d060add1

View File

@ -173,6 +173,14 @@ Certificates:
cert renew [domain] Renew certificate(s)
cert remove <domain> Remove certificate
Path ACLs:
path list List all path ACLs
path sync <prefix> <host> Auto-generate path ACLs from backends
(e.g., path sync /gk2 secubox.in)
path add <pattern> <backend> <host> Add path ACL
path remove <name> Remove path ACL
path clear <prefix> Remove all path ACLs with prefix
Service Commands:
service-run Run in foreground (for init)
service-stop Stop service
@ -1833,6 +1841,174 @@ cmd_service_stop() {
lxc_stop
}
# ===========================================
# Path ACL Management
# ===========================================
cmd_path_list() {
config_load haproxy
echo "Path ACLs:"
echo "=========================================="
printf "%-20s %-25s %-25s %s\n" "NAME" "PATTERN" "BACKEND" "HOST"
echo "------------------------------------------"
config_foreach _print_path_acl acl
}
_print_path_acl() {
local section="$1"
local enabled pattern backend host type
config_get enabled "$section" enabled "1"
[ "$enabled" = "1" ] || return
config_get type "$section" type
config_get pattern "$section" pattern
config_get backend "$section" backend
config_get host "$section" host
[ -n "$pattern" ] || return
printf "%-20s %-25s %-25s %s\n" "$section" "$pattern" "$backend" "$host"
}
cmd_path_sync() {
local prefix="${1:-/gk2}"
local host="${2:-secubox.in}"
log_info "Syncing path ACLs: prefix=$prefix host=$host"
# Collect backend names
local backends=""
config_load haproxy
_collect_backend_name() {
local section="$1"
local name enabled
config_get name "$section" name "$section"
config_get enabled "$section" enabled "1"
[ "$enabled" = "1" ] || return
# Skip system backends
case "$name" in
fallback|acme_challenge|end_of_internet|vortex_*|luci_default) return ;;
esac
backends="$backends $name"
}
config_foreach _collect_backend_name backend
log_info "Found backends: $backends"
# Generate path ACL for each backend
local added=0
for backend in $backends; do
# Extract short name from backend (e.g., metablog_gk2 -> gk2, streamlit_evolution -> evolution)
local short_name=""
case "$backend" in
metablog_*) short_name="${backend#metablog_}" ;;
streamlit_*) short_name="${backend#streamlit_}" ;;
jellyfin_*) short_name="${backend#jellyfin_}" ;;
*) short_name="$backend" ;;
esac
local acl_name="path_$(echo "${prefix#/}" | tr '/' '_')_${short_name}"
local pattern="${prefix}/${short_name}"
# Check if ACL already exists
if uci -q get haproxy.${acl_name} >/dev/null 2>&1; then
log_debug "ACL exists: $acl_name"
continue
fi
log_info "Adding: $pattern -> $backend"
uci set haproxy.${acl_name}=acl
uci set haproxy.${acl_name}.type="path_beg"
uci set haproxy.${acl_name}.pattern="$pattern"
uci set haproxy.${acl_name}.backend="$backend"
uci set haproxy.${acl_name}.host="$host"
uci set haproxy.${acl_name}.enabled="1"
added=$((added + 1))
done
uci commit haproxy
log_info "Added $added new path ACLs"
# Regenerate and reload
if [ "$added" -gt 0 ]; then
generate_config
cmd_reload
fi
}
cmd_path_add() {
local pattern="$1"
local backend="$2"
local host="${3:-secubox.in}"
[ -n "$pattern" ] || { log_error "Pattern required"; return 1; }
[ -n "$backend" ] || { log_error "Backend required"; return 1; }
local acl_name="path_$(echo "${pattern#/}" | tr '/' '_' | tr '-' '_')"
log_info "Adding path ACL: $pattern -> $backend (host: $host)"
uci set haproxy.${acl_name}=acl
uci set haproxy.${acl_name}.type="path_beg"
uci set haproxy.${acl_name}.pattern="$pattern"
uci set haproxy.${acl_name}.backend="$backend"
uci set haproxy.${acl_name}.host="$host"
uci set haproxy.${acl_name}.enabled="1"
uci commit haproxy
generate_config
cmd_reload
}
cmd_path_remove() {
local name="$1"
[ -n "$name" ] || { log_error "ACL name required"; return 1; }
if uci -q get haproxy.${name} >/dev/null 2>&1; then
uci delete haproxy.${name}
uci commit haproxy
log_info "Removed path ACL: $name"
generate_config
cmd_reload
else
log_error "Path ACL not found: $name"
return 1
fi
}
cmd_path_clear() {
local prefix="$1"
[ -n "$prefix" ] || { log_error "Prefix required (e.g., /gk2)"; return 1; }
local acl_prefix="path_$(echo "${prefix#/}" | tr '/' '_')"
local removed=0
config_load haproxy
_check_and_remove_acl() {
local section="$1"
case "$section" in
${acl_prefix}_*)
uci delete haproxy.${section}
removed=$((removed + 1))
log_info "Removed: $section"
;;
esac
}
config_foreach _check_and_remove_acl acl
if [ "$removed" -gt 0 ]; then
uci commit haproxy
log_info "Removed $removed path ACLs with prefix $prefix"
generate_config
cmd_reload
else
log_info "No path ACLs found with prefix $prefix"
fi
}
# ===========================================
# Main
# ===========================================
@ -1891,6 +2067,18 @@ case "${1:-}" in
esac
;;
path)
shift
case "${1:-}" in
list) shift; cmd_path_list "$@" ;;
sync) shift; cmd_path_sync "$@" ;;
add) shift; cmd_path_add "$@" ;;
remove) shift; cmd_path_remove "$@" ;;
clear) shift; cmd_path_clear "$@" ;;
*) echo "Usage: haproxyctl path {list|sync|add|remove|clear}" ;;
esac
;;
stats) shift; cmd_stats "$@" ;;
connections) shift; lxc_exec sh -c "echo 'show sess' | socat stdio /var/run/haproxy.sock" ;;