- _policy_read: remove erroneous _POLICY_DEFAULTS merge (introduced during split) - fmt.sh: fmt::bytes extracted from cmd::activity::_fmt_bytes - identity/subnet/policy list: ui::sort_rows applied - ctx::policies moved from policy.module.sh to context.sh
294 lines
No EOL
8.6 KiB
Bash
294 lines
No EOL
8.6 KiB
Bash
#!/usr/bin/env bash
|
|
# subnet.command.sh — manage the subnet map (subnets.json)
|
|
#
|
|
# Subcommands:
|
|
# wgctl subnet list
|
|
# wgctl subnet show --name <name>
|
|
# wgctl subnet add --name <name> --subnet <cidr> [--type <type>]
|
|
# [--tunnel-mode split|full] [--desc <desc>]
|
|
# [--group <parent>]
|
|
# wgctl subnet rm --name <name>
|
|
# wgctl subnet rename --name <old> --new-name <new>
|
|
|
|
# ============================================
|
|
# Lifecycle
|
|
# ============================================
|
|
|
|
function cmd::subnet::on_load() {
|
|
flag::register --name
|
|
flag::register --subnet
|
|
flag::register --type
|
|
flag::register --tunnel-mode
|
|
flag::register --desc
|
|
flag::register --group
|
|
flag::register --new-name
|
|
}
|
|
|
|
# ============================================
|
|
# Help
|
|
# ============================================
|
|
|
|
function cmd::subnet::help() {
|
|
cat <<EOF
|
|
Usage: wgctl subnet <subcommand> [options]
|
|
|
|
Manage the subnet map.
|
|
|
|
Subcommands:
|
|
list List all configured subnets
|
|
show --name <name> Show details for a subnet
|
|
add --name <name> Add a new subnet entry
|
|
--subnet <cidr>
|
|
[--type <type>]
|
|
[--tunnel-mode split|full]
|
|
[--desc <desc>]
|
|
[--group <parent>]
|
|
rm --name <name> Remove a subnet (refused if in use)
|
|
rename --name <old> Rename a subnet (refused if in use)
|
|
--new-name <new>
|
|
|
|
Examples:
|
|
wgctl subnet list
|
|
wgctl subnet show --name guests
|
|
wgctl subnet add --name iot-cctv --subnet 10.1.211.0/24 --type iot
|
|
wgctl subnet add --name desktop --subnet 10.1.101.0/24 --group guests
|
|
wgctl subnet rm --name iot-cctv
|
|
wgctl subnet rename --name iot-cctv --new-name cctv
|
|
EOF
|
|
}
|
|
|
|
# ============================================
|
|
# Run
|
|
# ============================================
|
|
|
|
function cmd::subnet::run() {
|
|
local subcmd="${1:-list}"
|
|
shift || true
|
|
|
|
case "$subcmd" in
|
|
list) cmd::subnet::_list "$@" ;;
|
|
show) cmd::subnet::_show "$@" ;;
|
|
add) cmd::subnet::_add "$@" ;;
|
|
rm) cmd::subnet::_rm "$@" ;;
|
|
rename) cmd::subnet::_rename "$@" ;;
|
|
--help) cmd::subnet::help ;;
|
|
*)
|
|
log::error "Unknown subcommand '${subcmd}'. Available: list, show, add, rm, rename"
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# ============================================
|
|
# Subcommands
|
|
# ============================================
|
|
|
|
function cmd::subnet::_list() {
|
|
local data
|
|
data=$(subnet::list_data | ui::sort_rows 1)
|
|
|
|
if [[ -z "$data" ]]; then
|
|
log::info "No subnets defined."
|
|
return 0
|
|
fi
|
|
|
|
echo ""
|
|
local prev_group=""
|
|
while IFS='|' read -r display_name subnet type_key tunnel_mode desc is_group group_parent; do
|
|
if [[ "$is_group" == "true" ]]; then
|
|
# Print group parent header when we encounter first child
|
|
if [[ "$group_parent" != "$prev_group" ]]; then
|
|
[[ -n "$prev_group" ]] && ui::subnet::group_separator
|
|
ui::subnet::row_group_parent "$group_parent"
|
|
prev_group="$group_parent"
|
|
fi
|
|
ui::subnet::row_group_child "$type_key" "$subnet" "$tunnel_mode"
|
|
else
|
|
# Scalar entry
|
|
[[ -n "$prev_group" ]] && ui::subnet::group_separator
|
|
prev_group=""
|
|
ui::subnet::row_scalar "$display_name" "$subnet" "$tunnel_mode"
|
|
fi
|
|
done <<< "$data"
|
|
echo ""
|
|
}
|
|
|
|
function cmd::subnet::_maybe_group_separator() {
|
|
local is_group="${1:-}" group_parent="${2:-}" prev_group="${3:-}"
|
|
if [[ "$is_group" == "true" && "$group_parent" != "$prev_group" && -n "$prev_group" ]]; then
|
|
ui::subnet::group_separator
|
|
elif [[ "$is_group" == "false" && -n "$prev_group" ]]; then
|
|
ui::subnet::group_separator
|
|
fi
|
|
}
|
|
|
|
function cmd::subnet::_update_prev_group() {
|
|
local is_group="${1:-}" group_parent="${2:-}" prev_group="${3:-}"
|
|
if [[ "$is_group" == "true" ]]; then
|
|
echo "$group_parent"
|
|
else
|
|
echo ""
|
|
fi
|
|
}
|
|
|
|
function cmd::subnet::_show() {
|
|
local name=""
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--name) name="$2"; shift 2 ;;
|
|
--help) cmd::subnet::help; return ;;
|
|
*) log::error "Unknown flag: $1"; return 1 ;;
|
|
esac
|
|
done
|
|
|
|
[[ -z "$name" ]] && { log::error "Missing required flag: --name"; return 1; }
|
|
subnet::require_exists "$name" || return 1
|
|
|
|
local data
|
|
data=$(subnet::show_data "$name")
|
|
|
|
local is_group="false"
|
|
local show_name="" show_subnet="" show_tunnel="" show_desc=""
|
|
|
|
while IFS='|' read -r key val rest; do
|
|
case "$key" in
|
|
name) show_name="$val" ;;
|
|
is_group) is_group="$val" ;;
|
|
subnet) show_subnet="$val" ;;
|
|
tunnel_mode) show_tunnel="$val" ;;
|
|
desc) show_desc="$val" ;;
|
|
esac
|
|
done <<< "$data"
|
|
|
|
if [[ "$is_group" == "true" ]]; then
|
|
# Group display
|
|
ui::subnet::show_group "$show_name"
|
|
|
|
while IFS='|' read -r key val rest; do
|
|
[[ "$key" != "child" ]] && continue
|
|
local c_type="$val"
|
|
local c_subnet c_tunnel c_desc
|
|
c_subnet=$(echo "$rest" | cut -d'|' -f1)
|
|
c_tunnel=$(echo "$rest" | cut -d'|' -f2)
|
|
c_desc=$(echo "$rest" | cut -d'|' -f3)
|
|
ui::subnet::show_child_row "$c_type" "$c_subnet" "$c_tunnel" "$c_desc"
|
|
done <<< "$data"
|
|
|
|
local peers_using
|
|
peers_using=$(subnet::peers_using "$name")
|
|
ui::subnet::show_peers_annotated "$peers_using" "$(ctx::subnets)"
|
|
else
|
|
# Scalar display
|
|
ui::subnet::show_scalar "$show_name" "$show_subnet" "$show_tunnel" "$show_desc"
|
|
|
|
local peers_using
|
|
peers_using=$(subnet::peers_using "$name")
|
|
ui::subnet::show_peers "$peers_using"
|
|
fi
|
|
|
|
echo ""
|
|
}
|
|
|
|
function cmd::subnet::_add() {
|
|
local name="" cidr="" type_key="" tunnel_mode="split" desc="" group_parent=""
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--name) name="$2"; shift 2 ;;
|
|
--subnet) cidr="$2"; shift 2 ;;
|
|
--type) type_key="$2"; shift 2 ;;
|
|
--tunnel-mode) tunnel_mode="$2"; shift 2 ;;
|
|
--desc) desc="$2"; shift 2 ;;
|
|
--group) group_parent="$2"; shift 2 ;;
|
|
--help) cmd::subnet::help; return ;;
|
|
*) log::error "Unknown flag: $1"; return 1 ;;
|
|
esac
|
|
done
|
|
|
|
[[ -z "$name" ]] && { log::error "Missing required flag: --name"; return 1; }
|
|
[[ -z "$cidr" ]] && { log::error "Missing required flag: --subnet"; return 1; }
|
|
|
|
cmd::subnet::_validate_tunnel_mode "$tunnel_mode" || return 1
|
|
cmd::subnet::_validate_cidr "$cidr" || return 1
|
|
|
|
json::subnet_add "$(ctx::subnets)" "$name" "$cidr" \
|
|
"${type_key:-$name}" "$tunnel_mode" "$desc" "$group_parent"
|
|
|
|
log::ok "Subnet '${name}' added (${cidr})"
|
|
}
|
|
|
|
function cmd::subnet::_rm() {
|
|
local name=""
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--name) name="$2"; shift 2 ;;
|
|
--help) cmd::subnet::help; return ;;
|
|
*) log::error "Unknown flag: $1"; return 1 ;;
|
|
esac
|
|
done
|
|
|
|
[[ -z "$name" ]] && { log::error "Missing required flag: --name"; return 1; }
|
|
subnet::require_exists "$name" || return 1
|
|
|
|
local peers_using
|
|
peers_using=$(subnet::peers_using "$name")
|
|
|
|
if [[ -n "$peers_using" ]]; then
|
|
log::error "Cannot remove subnet '${name}' — in use by: ${peers_using//,/, }"
|
|
log::error "Migrate or remove those peers first."
|
|
return 1
|
|
fi
|
|
|
|
json::subnet_remove "$(ctx::subnets)" "$name" ""
|
|
log::ok "Subnet '${name}' removed"
|
|
}
|
|
|
|
function cmd::subnet::_rename() {
|
|
local name="" new_name=""
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--name) name="$2"; shift 2 ;;
|
|
--new-name) new_name="$2"; shift 2 ;;
|
|
--help) cmd::subnet::help; return ;;
|
|
*) log::error "Unknown flag: $1"; return 1 ;;
|
|
esac
|
|
done
|
|
|
|
[[ -z "$name" ]] && { log::error "Missing required flag: --name"; return 1; }
|
|
[[ -z "$new_name" ]] && { log::error "Missing required flag: --new-name"; return 1; }
|
|
subnet::require_exists "$name" || return 1
|
|
|
|
local peers_using
|
|
peers_using=$(subnet::peers_using "$name")
|
|
if [[ -n "$peers_using" ]]; then
|
|
log::error "Cannot rename subnet '${name}' — configs already distributed to: ${peers_using//,/, }"
|
|
log::error "Client configs reference the subnet CIDR which cannot change after distribution."
|
|
return 1
|
|
fi
|
|
|
|
json::subnet_rename "$(ctx::subnets)" "$name" "$new_name" ""
|
|
log::ok "Subnet '${name}' renamed to '${new_name}'"
|
|
}
|
|
|
|
# ============================================
|
|
# Validation Helpers
|
|
# ============================================
|
|
|
|
function cmd::subnet::_validate_tunnel_mode() {
|
|
local mode="${1:-}"
|
|
case "$mode" in
|
|
split|full) return 0 ;;
|
|
*)
|
|
log::error "Invalid --tunnel-mode '${mode}'. Use: split, full"
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
function cmd::subnet::_validate_cidr() {
|
|
local cidr="${1:-}"
|
|
if ! echo "$cidr" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/[0-9]+$'; then
|
|
log::error "Invalid CIDR format: '${cidr}'"
|
|
return 1
|
|
fi
|
|
} |