#!/usr/bin/env bash function cmd::net::on_load() { flag::register --name flag::register --ip flag::register --port flag::register --desc flag::register --tag flag::register --detailed flag::register --force command::mixin json_output } function cmd::net::help() { cat < [options] Manage named network services for use with block/allow rules. Services map names to IPs and ports, making rules more readable. Subcommands: list List all services show --name Show service details add --name --ip Add a service add --name --port Add a port to a service rm --name Remove service or port rm --name Remove all ports from service Options for add (service): --name Service name (e.g. proxmox) --ip Service IP address --desc Optional description --tag Optional tag (repeatable) Options for add (port): --name Service:port-name (e.g. proxmox:web-ui) --port Port and protocol (e.g. 8006:tcp) --desc Optional description Options for list: --detailed Show ports for each service --tag Filter by tag Examples: wgctl net list wgctl net list --detailed wgctl net list --tag admin wgctl net show --name proxmox wgctl net add --name proxmox --ip 10.0.0.100 --desc "Proxmox VE" wgctl net add --name proxmox:web-ui --port 8006:tcp --desc "Web UI" wgctl net add --name proxmox:ssh --port 22:tcp wgctl net rm --name proxmox:web-ui wgctl net rm --name proxmox:ports wgctl net rm --name proxmox EOF } function cmd::net::run() { local subcmd="${1:-list}" shift || true if command::json; then cmd::net::_output_json return 0 fi case "$subcmd" in list) cmd::net::list "$@" ;; show) cmd::net::show "$@" ;; add) cmd::net::add "$@" ;; rm|remove|del) cmd::net::rm "$@" ;; help) cmd::net::help ;; *) log::error "Unknown subcommand: '${subcmd}'" cmd::net::help return 1 ;; esac } # ============================================ # List # ============================================ function cmd::net::list() { local detailed=false filter_tag="" while [[ $# -gt 0 ]]; do case "$1" in --detailed) detailed=true; shift ;; --tag) filter_tag="$2"; shift 2 ;; --help) cmd::net::help; return ;; *) log::error "Unknown flag: $1"; return 1 ;; esac done local net_file net_file="$(ctx::net)" if [[ ! -f "$net_file" ]]; then log::wg_warning "No services configured. Use 'wgctl net add' to add one." return 0 fi # Collect filtered data and build ports display per service local filtered_data="" while IFS="|" read -r name ip desc tags port_count; do [[ -z "$name" ]] && continue [[ -n "$filter_tag" && "$tags" != *"$filter_tag"* ]] && continue # Build ports display from json::net_show local ports_display="" while IFS="|" read -r ptype pname pport pproto pdesc; do [[ "$ptype" != "port" ]] && continue local port_str=":${pport}" [[ -n "$pproto" && "$pproto" != "tcp" ]] && port_str="${port_str}/${pproto}" ports_display+="${port_str}, " done < <(json::net_show "$net_file" "$name") ports_display="${ports_display%, }" [[ -z "$ports_display" ]] && ports_display="-" filtered_data+="${name}|${ip}|${desc}|${tags}|${ports_display}"$'\n' done < <(json::net_list "$net_file") [[ -z "$filtered_data" ]] && { [[ -n "$filter_tag" ]] && \ log::wg_warning "No services with tag: ${filter_tag}" || \ log::wg_warning "No services configured" return 0 } # Measure column widths local w_name=12 w_ip=13 w_ports=16 while IFS="|" read -r name ip desc tags ports; do [[ -z "$name" ]] && continue (( ${#name} > w_name )) && w_name=${#name} (( ${#ip} > w_ip )) && w_ip=${#ip} (( ${#ports} > w_ports )) && w_ports=${#ports} done <<< "$filtered_data" (( w_name += 2 )) (( w_ip += 2 )) (( w_ports += 2 )) log::section "Network Services" echo "" while IFS="|" read -r name ip desc tags ports; do [[ -z "$name" ]] && continue ui::net::list_row "$name" "$ip" "$desc" "$tags" "$ports" \ "$w_name" "$w_ip" "$w_ports" if $detailed; then while IFS="|" read -r ptype pname pport pproto pdesc; do [[ "$ptype" != "port" ]] && continue ui::net::show_port_row "$pname" "$pport" "$pproto" "$pdesc" done < <(json::net_show "$net_file" "$name") echo "" fi done <<< "$filtered_data" echo "" } # ============================================ # Show # ============================================ function cmd::net::show() { local name="" while [[ $# -gt 0 ]]; do case "$1" in --name) util::require_flag "--name" "${2:-}" || return 1 name="$2"; shift 2 ;; --help) cmd::net::help; return ;; *) log::error "Unknown flag: $1"; return 1 ;; esac done [[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1 net::require_exists "$name" || return 1 log::section "Service: ${name}" printf "\n" local has_ports=false while IFS="|" read -r key val1 val2 val3 val4; do case "$key" in name) ui::row "Name" "$val1" ;; desc) ui::row "Description" "${val1:-—}" ;; tags) ui::row "Tags" "${val1:-—}" ;; ip) ui::row "IP" "$val1" ;; port) if ! $has_ports; then printf " %-20s\n" "Ports:" has_ports=true fi ui::net::show_port_row "$val1" "$val2" "$val3" "$val4" ;; esac done < <(json::net_show "$(ctx::net)" "$name") printf "\n" } # ============================================ # Add # ============================================ function cmd::net::add() { local name="" ip="" port="" desc="" tags=() while [[ $# -gt 0 ]]; do case "$1" in --name) util::require_flag "--name" "${2:-}" || return 1 name="$2"; shift 2 ;; --ip) ip="$2"; shift 2 ;; --port) port="$2"; shift 2 ;; --desc) desc="$2"; shift 2 ;; --tag) tags+=("$2"); shift 2 ;; --help) cmd::net::help; return ;; *) log::error "Unknown flag: $1"; return 1 ;; esac done [[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1 if [[ "$name" == *:* ]]; then # Port mode: proxmox:web-ui local svc_name="${name%%:*}" local port_name="${name##*:}" [[ -z "$port" ]] && log::error "Missing required flag: --port" && return 1 net::require_exists "$svc_name" || return 1 local port_num proto if [[ "$port" == *:* ]]; then port_num="${port%%:*}" proto="${port##*:}" else port_num="$port" proto="tcp" fi json::net_add_port "$(ctx::net)" "$svc_name" "$port_name" \ "$port_num" "$proto" "$desc" log::wg_success "Added port: ${svc_name}:${port_name} → ${port_num}/${proto}" else # Service mode: proxmox [[ -z "$ip" ]] && log::error "Missing required flag: --ip" && return 1 local tags_str tags_str=$(IFS=','; echo "${tags[*]}") json::net_add_service "$(ctx::net)" "$name" "$ip" "$desc" "$tags_str" log::wg_success "Service added: ${name} → ${ip}" fi return 0 } # ============================================ # Remove # ============================================ function cmd::net::rm() { local name="" force=false while [[ $# -gt 0 ]]; do case "$1" in --name) util::require_flag "--name" "${2:-}" || return 1 name="$2"; shift 2 ;; --force) force=true; shift ;; --help) cmd::net::help; return ;; *) log::error "Unknown flag: $1"; return 1 ;; esac done [[ -z "$name" ]] && log::error "Missing required flag: --name" && return 1 # Validate existence if [[ "$name" == *:* ]]; then local svc_name="${name%%:*}" local port_name="${name##*:}" if [[ "$port_name" != "ports" ]]; then # Check specific port exists local exists exists=$(json::net_exists "$(ctx::net)" "$name") if [[ "$exists" != "true" ]]; then log::error "Port not found: ${name}" return 1 fi else net::require_exists "$svc_name" || return 1 fi else net::require_exists "$name" || return 1 fi if ! $force; then local what="service '${name}'" [[ "$name" == *:ports ]] && what="all ports from '${name%%:*}'" [[ "$name" == *:* && "$name" != *:ports ]] && what="port '${name}'" read -r -p "Remove ${what}? [y/N] " confirm case "$confirm" in [yY]*) ;; *) log::info "Aborted"; return 0 ;; esac fi json::net_remove "$(ctx::net)" "$name" log::wg_success "Removed: ${name}" return 0 } function cmd::net::_output_json() { local net_file net_file="$(ctx::net)" local data data=$(json::net_list "$net_file" 2>/dev/null) local -a services=() while IFS='|' read -r name ip desc tags port_count; do [[ -z "$name" ]] && continue # Build tags array local tags_json="[]" if [[ -n "$tags" ]]; then local tags_array tags_array=$(echo "$tags" | tr ',' '\n' | \ while IFS= read -r t; do [[ -n "$t" ]] && printf '"%s",' "$t"; done | sed 's/,$//') tags_json="[${tags_array}]" fi services+=("$(printf '{"name":"%s","ip":"%s","desc":"%s","tags":%s,"port_count":%s}' \ "$name" "$ip" "$desc" "$tags_json" "$port_count")") done <<< "$data" local count=${#services[@]} local array array=$(printf '%s\n' "${services[@]:-}" | paste -sd ',' -) printf '{"services":[%s]}' "${array:-}" | json::envelope "net list" "$count" }