#!/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 } 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 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 log::section "Network Services" printf "\n %-20s %-16s %-6s %s\n" "NAME" "IP" "PORTS" "DESCRIPTION" local divider divider=$(printf '─%.0s' {1..72}) printf " %s\n" "$divider" local found=false while IFS="|" read -r name ip desc tags ports; do [[ -z "$name" ]] && continue # Tag filter if [[ -n "$filter_tag" ]]; then [[ "$tags" != *"$filter_tag"* ]] && continue fi found=true local tag_display="" [[ -n "$tags" ]] && tag_display=" \033[0;37m[${tags}]\033[0m" printf " %-20s %-16s %-6s %s%b\n" \ "$name" "$ip" "${ports}p" "${desc:-—}" "$tag_display" if $detailed; then local has_ports=false # Show ports inline while IFS="|" read -r ptype pname pport pproto pdesc; do [[ "$ptype" != "port" ]] && continue has_ports=true local ann ann=$(net::annotation "$ip" "$pport" "$pproto") printf " \033[0;37m%-18s %s:%s%s\033[0m\n" \ "${pname}" "$pport" "$pproto" \ "${pdesc:+ # $pdesc}" done < <(json::net_show "$net_file" "$name") $has_ports && printf "\n" # newline after each service with ports fi done < <(json::net_list "$net_file") if ! $found; then [[ -n "$filter_tag" ]] && \ log::wg_warning "No services with tag: ${filter_tag}" || \ log::wg_warning "No services configured" fi printf "\n" return 0 } # ============================================ # 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" while IFS="|" read -r key val1 val2 val3 val4; do case "$key" in name) ui::row "Name" "$val1" ;; ip) ui::row "IP" "$val1" ;; desc) ui::row "Description" "${val1:-—}" ;; tags) ui::row "Tags" "${val1:-—}" ;; port) # val1=port_name val2=port val3=proto val4=desc local ann ann=$(net::annotation "$(json::net_resolve "$(ctx::net)" "$name")" \ "$val2" "$val3" 2>/dev/null || true) printf " %-20s \033[0;36m%s\033[0m %s:%s%s\n" \ "${val1}:" "" "$val2" "$val3" \ "${val4:+ # $val4}" ;; esac done < <(json::net_show "$(ctx::net)" "$name") printf "\n" return 0 } # ============================================ # 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 }