mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-09-12 11:24:37 +08:00
362 lines
8.5 KiB
Bash
Executable File
362 lines
8.5 KiB
Bash
Executable File
#!/bin/sh
|
|
|
|
CONFIG=haproxy_manager
|
|
HAPROXY_CFG=/etc/haproxy.cfg
|
|
TMP_CFG=/tmp/haproxy-manager-generated.cfg
|
|
BACKUP_LIMIT=7
|
|
INCIDENT_LIMIT=7
|
|
FIREWALL_RULE_NAME='HAProxy Manager: WAN listeners'
|
|
FIREWALL_DISABLED_PREFIX='HAProxy Manager disabled: '
|
|
|
|
uci_get() {
|
|
local section="$1"
|
|
local option="$2"
|
|
local default="${3:-}"
|
|
uci -q get "$CONFIG.$section.$option" 2>/dev/null || printf '%s\n' "$default"
|
|
}
|
|
|
|
backup_dir() {
|
|
local dir
|
|
dir="$(uci_get main backup_dir /root/haproxy-manager-backups)"
|
|
is_safe_storage_dir "$dir" || {
|
|
echo "Recovery point directory must be a plain path under /root or /mnt: $dir" >&2
|
|
return 1
|
|
}
|
|
printf '%s\n' "$dir"
|
|
}
|
|
|
|
incident_dir() {
|
|
local dir
|
|
dir="$(uci_get main incident_dir /root/haproxy-manager-incidents)"
|
|
is_safe_storage_dir "$dir" || {
|
|
echo "Incident directory must be a plain path under /root or /mnt: $dir" >&2
|
|
return 1
|
|
}
|
|
printf '%s\n' "$dir"
|
|
}
|
|
|
|
is_safe_storage_dir() {
|
|
case "$1" in
|
|
/root/*|/mnt/*) ;;
|
|
*) return 1 ;;
|
|
esac
|
|
case "/${1#/}/" in
|
|
*/../*|*/./*|*[!A-Za-z0-9_./-]*) return 1 ;;
|
|
esac
|
|
return 0
|
|
}
|
|
|
|
resolve_storage_dir() {
|
|
local dir="$1" resolved
|
|
[ -d "$dir" ] || return 1
|
|
resolved="$(cd "$dir" 2>/dev/null && pwd -P)" || return 1
|
|
is_safe_storage_dir "$resolved" || return 1
|
|
printf '%s\n' "$resolved"
|
|
}
|
|
|
|
is_safe_temp_path() {
|
|
local path="$1" name
|
|
name="${path#/tmp/}"
|
|
[ "$path" = "/tmp/$name" ] || return 1
|
|
case "$name" in
|
|
haproxy-manager-[A-Za-z0-9._-]*) return 0 ;;
|
|
*) return 1 ;;
|
|
esac
|
|
}
|
|
|
|
operation_lock_acquire() {
|
|
local lock_dir="$1" lock_pid
|
|
if mkdir "$lock_dir" 2>/dev/null; then
|
|
printf '%s\n' "$$" > "$lock_dir/pid"
|
|
return 0
|
|
fi
|
|
|
|
lock_pid="$(cat "$lock_dir/pid" 2>/dev/null || true)"
|
|
case "$lock_pid" in
|
|
''|*[!0-9]*) lock_pid=0 ;;
|
|
esac
|
|
if [ "$lock_pid" -gt 1 ] 2>/dev/null && kill -0 "$lock_pid" 2>/dev/null; then
|
|
return 1
|
|
fi
|
|
rm -f "$lock_dir/pid"
|
|
rmdir "$lock_dir" 2>/dev/null || return 1
|
|
mkdir "$lock_dir" 2>/dev/null || return 1
|
|
printf '%s\n' "$$" > "$lock_dir/pid"
|
|
}
|
|
|
|
operation_lock_set_pid() {
|
|
printf '%s\n' "$2" > "$1/pid"
|
|
}
|
|
|
|
operation_lock_release() {
|
|
rm -f "$1/pid"
|
|
rmdir "$1" 2>/dev/null || true
|
|
}
|
|
|
|
atomic_install_file() {
|
|
local source="$1" destination="$2" temporary mode
|
|
[ -f "$source" ] || return 1
|
|
temporary="$destination.haproxy-manager.$$"
|
|
rm -f "$temporary"
|
|
if ! cp "$source" "$temporary"; then
|
|
rm -f "$temporary"
|
|
return 1
|
|
fi
|
|
mode="$(stat -c '%a' "$destination" 2>/dev/null || stat -c '%a' "$source" 2>/dev/null || echo 600)"
|
|
chmod "$mode" "$temporary" || { rm -f "$temporary"; return 1; }
|
|
mv -f "$temporary" "$destination" || { rm -f "$temporary"; return 1; }
|
|
}
|
|
|
|
active_mode() {
|
|
local mode
|
|
mode="$(uci_get main active_mode)"
|
|
case "$mode" in
|
|
generated|raw|none) printf '%s\n' "$mode" ;;
|
|
*)
|
|
if [ "$(uci_get main enabled 0)" = 1 ] &&
|
|
grep -q '^# Generated by luci-app-haproxy-manager\.' "$HAPROXY_CFG" 2>/dev/null; then
|
|
printf 'generated\n'
|
|
else
|
|
printf 'none\n'
|
|
fi
|
|
;;
|
|
esac
|
|
}
|
|
|
|
wan_interface() {
|
|
local interface
|
|
interface="$(uci_get main wan_interface wan)"
|
|
case "$interface" in
|
|
''|*[!A-Za-z0-9_.-]*) return 1 ;;
|
|
esac
|
|
printf '%s\n' "$interface"
|
|
}
|
|
|
|
wan_ip() {
|
|
local configured interface
|
|
configured="$(uci_get main wan_bind_ip auto)"
|
|
if [ "$configured" != auto ] && [ -n "$configured" ]; then
|
|
printf '%s\n' "$configured"
|
|
return 0
|
|
fi
|
|
|
|
interface="$(wan_interface)" || return 1
|
|
ubus call "network.interface.$interface" status 2>/dev/null \
|
|
| jsonfilter -e '@["ipv4-address"][0].address' 2>/dev/null \
|
|
| grep -m1 .
|
|
}
|
|
|
|
lan_ip() {
|
|
local configured
|
|
configured="$(uci_get main lan_bind_ip auto)"
|
|
if [ "$configured" != auto ] && [ -n "$configured" ]; then
|
|
printf '%s\n' "$configured"
|
|
return 0
|
|
fi
|
|
|
|
ubus call network.interface.lan status 2>/dev/null \
|
|
| jsonfilter -e '@["ipv4-address"][0].address' 2>/dev/null \
|
|
| grep -m1 .
|
|
}
|
|
|
|
route_name() {
|
|
printf '%s' "$1" | tr '.:-' '___' | tr -cd 'A-Za-z0-9_'
|
|
}
|
|
|
|
is_valid_host() {
|
|
case "$1" in
|
|
''|[!A-Za-z0-9]*|*[!A-Za-z0-9]) return 1 ;;
|
|
*[!A-Za-z0-9.-]*) return 1 ;;
|
|
esac
|
|
return 0
|
|
}
|
|
|
|
is_valid_ip_or_host() {
|
|
case "$1" in
|
|
''|*[!A-Za-z0-9_.:-]*) return 1 ;;
|
|
esac
|
|
return 0
|
|
}
|
|
|
|
is_valid_backup_id() {
|
|
case "$1" in
|
|
[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-[0-9][0-9][0-9][0-9][0-9][0-9]) return 0 ;;
|
|
*) return 1 ;;
|
|
esac
|
|
}
|
|
|
|
backend_address() {
|
|
local host="$1"
|
|
local port="$2"
|
|
|
|
case "$host" in
|
|
*:*) printf '[%s]:%s\n' "$host" "$port" ;;
|
|
*) printf '%s:%s\n' "$host" "$port" ;;
|
|
esac
|
|
}
|
|
|
|
is_valid_port() {
|
|
local port="$1"
|
|
[ "$port" -ge 1 ] 2>/dev/null && [ "$port" -le 65535 ] 2>/dev/null
|
|
}
|
|
|
|
route_sections() {
|
|
uci -q show "$CONFIG" \
|
|
| sed -n "s/^$CONFIG\\.\\(@route\\[[0-9][0-9]*\\]\\)=route/\\1/p"
|
|
}
|
|
|
|
route_kind() {
|
|
local section="$1"
|
|
local kind
|
|
|
|
kind="$(uci_get "$section" kind)"
|
|
[ -n "$kind" ] || kind="$(uci_get "$section" protocol http)"
|
|
printf '%s\n' "$kind"
|
|
}
|
|
|
|
route_port_maps() {
|
|
local section="$1"
|
|
local kind="$2"
|
|
local public_port backend_port
|
|
|
|
case "$kind" in
|
|
ssh)
|
|
public_port="$(uci_get "$section" ssh_listen_port 22)"
|
|
backend_port="$(uci_get "$section" ssh_backend_port 22)"
|
|
printf '%s:%s\n' "$public_port" "$backend_port"
|
|
;;
|
|
rdp)
|
|
public_port="$(uci_get "$section" rdp_listen_port 3389)"
|
|
backend_port="$(uci_get "$section" rdp_backend_port 3389)"
|
|
printf '%s:%s\n' "$public_port" "$backend_port"
|
|
;;
|
|
custom)
|
|
for map in $(uci_get "$section" port_map); do
|
|
printf '%s\n' "$map"
|
|
done
|
|
;;
|
|
tcp)
|
|
public_port="$(uci_get "$section" listen_port "$(uci_get "$section" backend_port)")"
|
|
backend_port="$(uci_get "$section" backend_port)"
|
|
printf '%s:%s\n' "$public_port" "$backend_port"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
is_valid_port_map() {
|
|
local map="$1"
|
|
local public_port backend_port
|
|
|
|
case "$map" in
|
|
*:*:*) return 1 ;;
|
|
*:*) ;;
|
|
*) return 1 ;;
|
|
esac
|
|
|
|
public_port="${map%%:*}"
|
|
backend_port="${map#*:}"
|
|
is_valid_port "$public_port" && is_valid_port "$backend_port"
|
|
}
|
|
|
|
required_ports() {
|
|
local section enabled kind http_enabled https_enabled map
|
|
local http_port https_port
|
|
|
|
http_port="$(uci_get main http_port 80)"
|
|
https_port="$(uci_get main https_port 443)"
|
|
[ "$(uci_get main enabled 0)" = 1 ] || return 0
|
|
|
|
for section in $(route_sections); do
|
|
enabled="$(uci_get "$section" enabled 1)"
|
|
[ "$enabled" = 1 ] || continue
|
|
kind="$(route_kind "$section")"
|
|
|
|
case "$kind" in
|
|
web)
|
|
http_enabled="$(uci_get "$section" web_http 1)"
|
|
https_enabled="$(uci_get "$section" web_https 1)"
|
|
[ "$http_enabled" != 1 ] || printf '%s\n' "$http_port"
|
|
[ "$https_enabled" != 1 ] || printf '%s\n' "$https_port"
|
|
;;
|
|
http)
|
|
printf '%s\n' "$http_port"
|
|
;;
|
|
https)
|
|
printf '%s\n' "$https_port"
|
|
;;
|
|
both)
|
|
printf '%s\n%s\n' "$http_port" "$https_port"
|
|
;;
|
|
ssh|rdp|custom|tcp)
|
|
for map in $(route_port_maps "$section" "$kind"); do
|
|
printf '%s\n' "${map%%:*}"
|
|
done
|
|
;;
|
|
esac
|
|
done | sort -nu
|
|
}
|
|
|
|
port_spec_contains() {
|
|
local spec="$1"
|
|
local wanted="$2"
|
|
local token first last
|
|
|
|
for token in $(printf '%s' "$spec" | tr ',' ' '); do
|
|
case "$token" in
|
|
*-*)
|
|
first="${token%%-*}"
|
|
last="${token#*-}"
|
|
[ "$wanted" -ge "$first" ] 2>/dev/null && [ "$wanted" -le "$last" ] 2>/dev/null && return 0
|
|
;;
|
|
*)
|
|
[ "$token" = "$wanted" ] && return 0
|
|
;;
|
|
esac
|
|
done
|
|
|
|
return 1
|
|
}
|
|
|
|
set_uhttpd_list() {
|
|
local option values current address
|
|
option="$1"
|
|
values="$2"
|
|
current="$(uci -q get "uhttpd.main.$option" 2>/dev/null || true)"
|
|
[ "$current" = "$values" ] && return 0
|
|
|
|
uci -q delete "uhttpd.main.$option" || true
|
|
for address in $values; do
|
|
uci add_list "uhttpd.main.$option=$address"
|
|
done
|
|
UHTTPD_CHANGED=1
|
|
}
|
|
|
|
capture_uhttpd_bindings() {
|
|
local saved_http saved_https address
|
|
[ "$(uci_get main uhttpd_bind_owned 0)" != 1 ] || return 0
|
|
|
|
saved_http="$(uci -q get uhttpd.main.listen_http 2>/dev/null || true)"
|
|
saved_https="$(uci -q get uhttpd.main.listen_https 2>/dev/null || true)"
|
|
uci -q delete "$CONFIG.main.uhttpd_saved_listen_http" || true
|
|
uci -q delete "$CONFIG.main.uhttpd_saved_listen_https" || true
|
|
for address in $saved_http; do
|
|
uci add_list "$CONFIG.main.uhttpd_saved_listen_http=$address"
|
|
done
|
|
for address in $saved_https; do
|
|
uci add_list "$CONFIG.main.uhttpd_saved_listen_https=$address"
|
|
done
|
|
uci set "$CONFIG.main.uhttpd_bind_owned=1"
|
|
uci commit "$CONFIG"
|
|
}
|
|
|
|
restore_uhttpd_bindings() {
|
|
[ "$(uci_get main uhttpd_bind_owned 0)" = 1 ] || return 0
|
|
|
|
set_uhttpd_list listen_http "$(uci_get main uhttpd_saved_listen_http)"
|
|
set_uhttpd_list listen_https "$(uci_get main uhttpd_saved_listen_https)"
|
|
uci -q delete "$CONFIG.main.uhttpd_saved_listen_http" || true
|
|
uci -q delete "$CONFIG.main.uhttpd_saved_listen_https" || true
|
|
uci -q delete "$CONFIG.main.uhttpd_bind_owned" || true
|
|
uci commit "$CONFIG"
|
|
}
|