wgctl/commands/config.command.sh
Nuno Duque Nunes a559b73e8e feat: new flag::define syntax, flag::set_constraint
- flag::define: variadic constraint args (key:value) instead of bracket string
- flag::_parse_constraints_from_args: replaces flag::_parse_and_cache
- flag::set_constraint: Option B syntax for post-definition constraints
- choices separator: comma (choices:split,full) — no quoting needed
- guard against empty _CURRENT_COMMAND in exclusive groups lookup
- migrate all commands to new constraint syntax
- add helpful error for unknown constraint args
2026-05-31 00:16:55 +00:00

281 lines
No EOL
7.5 KiB
Bash

#!/usr/bin/env bash
# ============================================
# Lifecycle
# ============================================
function cmd::config::on_load() {
flag::register --name
flag::register --type
flag::register --force
flag::register --dry-run
}
# ============================================
# Help
# ============================================
function cmd::config::help() {
cat <<EOF
Usage: wgctl config --name <name>
Show the WireGuard config file for a client.
Options:
--name <name> Client name (e.g. phone-nuno)
Examples:
wgctl config --name phone-nuno
wgctl config --name laptop-nuno
EOF
}
# ============================================
# Run
# ============================================
function cmd::config::run() {
local subcmd="${1:-show}"
# If first arg is a flag, treat as 'show' subcommand
if [[ "$subcmd" == --* ]]; then
subcmd="show"
else
shift || true
fi
case "$subcmd" in
show) cmd::config::_show "$@" ;;
migrate) cmd::config::migrate "$@" ;;
help) cmd::config::help ;;
*)
log::error "Unknown subcommand: '${subcmd}'"
cmd::config::help
return 1
;;
esac
}
# ============================================
# Show
# ============================================
function cmd::config::_show() {
local name="" type=""
while [[ $# -gt 0 ]]; do
case "$1" in
--name) name="$2"; shift 2 ;;
--type) type="$2"; shift 2 ;;
--help) cmd::config::help; return ;;
*) log::error "Unknown flag: $1"; return 1 ;;
esac
done
[[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1
name=$(peers::resolve_and_require "$name" "$type") || return 1
local conf
conf="$(ctx::clients)/${name}.conf"
log::section "Client Config: ${name}"
cat "$conf"
}
# ============================================
# Migrate
# ============================================
function cmd::config::migrate() {
local force=false dry_run=false
while [[ $# -gt 0 ]]; do
case "$1" in
--force) force=true; shift ;;
--dry-run) dry_run=true; shift ;;
--help) cmd::config::help; return ;;
*) log::error "Unknown flag: $1"; return 1 ;;
esac
done
local wgctl_dir
wgctl_dir="$(ctx::wgctl)"
local config_dir="${wgctl_dir}/config"
local data_dir="${wgctl_dir}/data"
local legacy_conf="${wgctl_dir}/wgctl.conf"
local json_conf="${config_dir}/wgctl.json"
# Check if already migrated
if [[ -f "$json_conf" && ! -f "$legacy_conf" ]]; then
log::wg_warning "Already migrated to new config structure"
return 0
fi
log::section "wgctl Config Migration"
printf "\n"
printf " This will:\n"
printf " 1. Create %s/config/ and %s/data/\n" "$wgctl_dir" "$wgctl_dir"
printf " 2. Convert wgctl.conf → wgctl.json\n"
printf " 3. Move data files to data/\n\n"
if ! $force && ! $dry_run; then
read -r -p " Proceed? [y/N] " confirm
case "$confirm" in [yY]*) ;; *) log::info "Aborted"; return 0 ;; esac
fi
local do=""
$dry_run && do="echo [dry-run]"
# 1. Create directories
$dry_run || mkdir -p "$config_dir" "$data_dir"
$dry_run && printf " Would create: %s/config/\n" "$wgctl_dir"
$dry_run && printf " Would create: %s/data/\n" "$wgctl_dir"
# 2. Convert wgctl.conf → wgctl.json
if [[ -f "$legacy_conf" ]]; then
if ! $dry_run; then
config::_convert_to_json "$legacy_conf" "$json_conf"
fi
printf " %s wgctl.conf → config/wgctl.json\n" "$($dry_run && echo '[dry-run]' || echo '✓')"
else
log::wg_warning "No wgctl.conf found — creating default wgctl.json"
$dry_run || config::_write_default_json "$json_conf"
fi
# 3. Move data files
local -a data_files=(
"hosts.json"
"services.json"
"subnets.json"
"policies.json"
)
local -a data_dirs=(
"rules"
"identities"
"groups"
"blocks"
"meta"
"peer-history"
)
for f in "${data_files[@]}"; do
if [[ -f "${wgctl_dir}/${f}" ]]; then
$dry_run || mv "${wgctl_dir}/${f}" "${data_dir}/${f}"
printf " %s %s → data/%s\n" \
"$($dry_run && echo '[dry-run]' || echo '✓')" "$f" "$f"
fi
done
for d in "${data_dirs[@]}"; do
if [[ -d "${wgctl_dir}/${d}" ]]; then
$dry_run || mv "${wgctl_dir}/${d}" "${data_dir}/${d}"
printf " %s %s/ → data/%s/\n" \
"$($dry_run && echo '[dry-run]' || echo '✓')" "$d" "$d"
fi
done
# 4. Remove legacy conf after successful migration
if ! $dry_run && [[ -f "$legacy_conf" ]]; then
mv "$legacy_conf" "${legacy_conf}.bak"
printf " ✓ wgctl.conf → wgctl.conf.bak (backup)\n"
fi
printf "\n"
$dry_run && log::wg_warning "Dry run — no changes made" \
|| log::wg_success "Migration complete"
}
# function config::_convert_to_json() {
# local legacy_file="$1" output_file="$2"
# # Read legacy conf into variables
# local wg_interface="wg0" wg_endpoint="" wg_dns="10.0.0.103"
# local wg_dns_fallback="" wg_port="51820" wg_subnet="10.1.0.0/16"
# local wg_lan="10.0.0.0/24" wg_hs_check="300" date_format="eu"
# while IFS='=' read -r key value || [[ -n "$key" ]]; do
# [[ "$key" =~ ^[[:space:]]*# ]] && continue
# [[ -z "${key// }" ]] && continue
# key="${key// /}"
# value="${value// /}"
# case "$key" in
# WG_INTERFACE) wg_interface="$value" ;;
# WG_ENDPOINT) wg_endpoint="$value" ;;
# WG_DNS) wg_dns="$value" ;;
# WG_DNS_FALLBACK) wg_dns_fallback="$value" ;;
# WG_PORT) wg_port="$value" ;;
# WG_SUBNET) wg_subnet="$value" ;;
# WG_LAN) wg_lan="$value" ;;
# WG_HANDSHAKE_CHECK_TIME_SEC) wg_hs_check="$value" ;;
# DATE_FORMAT) date_format="$value" ;;
# esac
# done < "$legacy_file"
# # Build fallback DNS array
# local dns_fallback_json="[]"
# if [[ -n "$wg_dns_fallback" ]]; then
# local fallback_array
# fallback_array=$(echo "$wg_dns_fallback" | tr ',' '\n' | \
# while IFS= read -r s; do
# s="${s// /}"
# [[ -n "$s" ]] && printf '"%s",' "$s"
# done | sed 's/,$//')
# dns_fallback_json="[${fallback_array}]"
# fi
# mkdir -p "$(dirname "$output_file")"
# cat > "$output_file" << JSON
# {
# "wireguard": {
# "interface": "${wg_interface}",
# "endpoint": "${wg_endpoint}",
# "port": ${wg_port},
# "subnet": "${wg_subnet}",
# "lan": "${wg_lan}"
# },
# "dns": {
# "primary": "${wg_dns}",
# "fallback": ${dns_fallback_json}
# },
# "handshake": {
# "check_interval_sec": ${wg_hs_check}
# },
# "activity": {
# "total": {"low": 1000000, "medium": 10000000, "high": 100000000},
# "current": {"low": 1000000, "medium": 10000000, "high": 100000000}
# },
# "display": {
# "date_format": "${date_format}"
# }
# }
# JSON
# }
# function config::_write_default_json() {
# local output_file="$1"
# mkdir -p "$(dirname "$output_file")"
# cat > "$output_file" << 'JSON'
# {
# "wireguard": {
# "interface": "wg0",
# "endpoint": "",
# "port": 51820,
# "subnet": "10.1.0.0/16",
# "lan": "10.0.0.0/24"
# },
# "dns": {
# "primary": "10.0.0.103",
# "fallback": []
# },
# "handshake": {
# "check_interval_sec": 300
# },
# "activity": {
# "total": {"low": 1000000, "medium": 10000000, "high": 100000000},
# "current": {"low": 1000000, "medium": 10000000, "high": 100000000}
# },
# "display": {
# "date_format": "eu"
# }
# }
# JSON
# }