add.command.sh
· 6.0 KiB · Bash
Неформатований
#!/usr/bin/env bash
# ============================================
# Lifecycle
# ============================================
function cmd::add::on_load() {
flag::register --name
flag::register --type
flag::register --subtype
flag::register --ip
flag::register --preset
flag::register --guest
flag::register --tunnel
flag::register --show-config
flag::register --show-qr
}
# ============================================
# Help
# ============================================
function cmd::add::help() {
cat <<EOF
Usage: wgctl add --name <name> --type <type> [options]
Add a new WireGuard client.
Options:
--name <name> Client name (e.g. nuno)
--type <type> Device type: desktop, laptop, phone, tablet, guest
--ip <ip> Override auto-assigned IP (optional)
--preset <name> Apply a firewall preset (repeatable)
--guest Shorthand for --type guest
--tunnel <mode> Tunnel mode: split (default) or full
--show-config Shows the WireGuard peer config
--show-qr Shows the WireGuard config in a QR Code
Device Types and Subnets:
desktop 10.1.1.x
laptop 10.1.2.x
phone 10.1.3.x
tablet 10.1.4.x
guest 10.1.100.x
Tunnel Modes:
split Route only VPN subnet + LAN through WireGuard (default)
full Route all traffic through WireGuard
Examples:
wgctl add --name nuno --type phone
wgctl add --name nuno --type laptop --ip 10.1.2.5
wgctl add --name nuno --type phone --tunnel full
wgctl add --name guest1 --type phone --guest
wgctl add --name restricted --type desktop --preset no-docker --preset no-proxmox
EOF
}
# ============================================
# Validation
# ============================================
function cmd::add::validate() {
local name="$1"
local type="$2"
local ip="$3"
local tunnel="$4"
if [[ -z "$name" ]]; then
log::error "Missing required flag: --name"
return 1
fi
if [[ -z "$type" ]]; then
log::error "Missing required flag: --type"
return 1
fi
if ! config::is_valid_type "$type"; then
log::error "Invalid device type: ${type}"
log::info "Valid types: $(config::device_types | tr ' ' ', ')"
return 1
fi
if [[ -n "$tunnel" && "$tunnel" != "split" && "$tunnel" != "full" ]]; then
log::error "Invalid tunnel mode: ${tunnel} (use 'split' or 'full')"
return 1
fi
if [[ -n "$ip" ]]; then
ip::require_valid "$ip"
fi
local full_name="${type}-${name}"
if [[ -f "$(ctx::clients)/${full_name}.conf" ]]; then
log::error "Client already exists: ${full_name}"
return 1
fi
}
# ============================================
# Display helpers
# ============================================
function cmd::add::is_mobile() {
local type="$1"
[[ "$type" == "phone" || "$type" == "tablet" ]]
}
# ============================================
# Run
# ============================================
function cmd::add::run() {
local name=""
local type=""
local subtype=""
local ip=""
local tunnel=""
local guest=false
local presets=()
local show_config=false
local show_qr=false
# Parse flags
while [[ $# -gt 0 ]]; do
case "$1" in
--name) name="$2"; shift 2 ;;
--type) type="$2"; shift 2 ;;
--subtype) subtype="$2"; shift 2 ;;
--ip) ip="$2"; shift 2 ;;
--preset) presets+=("$2"); shift 2 ;;
--guest) guest=true; shift ;;
--tunnel) tunnel="$2"; shift 2 ;;
--show-config) show_config=true; shift ;;
--show-qr) show_qr=true; shift ;;
--help) cmd::add::help; return ;;
*)
log::error "Unknown flag: $1"
cmd::add::help
return 1
;;
esac
done
# --guest shorthand
if $guest; then
type="guest"
fi
# Resolve guest subtype
local effective_type="$type"
if [[ "$type" == "guest" && -n "$subtype" ]]; then
# Validate subtype
local valid_subtypes="desktop laptop phone tablet"
if ! echo "$valid_subtypes" | grep -qw "$subtype"; then
log::error "Invalid subtype: ${subtype} (valid: desktop, laptop, phone, tablet)"
return 1
fi
effective_type="guest-${subtype}"
fi
# Build full client name
local full_name="${type}-${name}"
# Validate
cmd::add::validate "$name" "$type" "$ip" "$tunnel" || return 1
# Resolve tunnel mode — flag > device default
if [[ -z "$tunnel" ]]; then
tunnel=$(config::default_tunnel_for "$type")
fi
local allowed_ips
allowed_ips=$(config::allowed_ips_for "$effective_type" "$tunnel") || return 1
log::section "Adding client: ${full_name}"
# Auto-assign IP if not provided
if [[ -z "$ip" ]]; then
ip=$(ip::next_for_type "$effective_type") || return 1
fi
log::wg_add "Name: ${full_name}"
log::wg_add "Type: ${type}"
log::wg_add "IP: ${ip}"
log::wg_add "Tunnel: ${tunnel} (${allowed_ips})"
# Generate keys
keys::generate_pair "$full_name" || return 1
# Create client config
peers::create_client_config "$full_name" "$effective_type" "$ip" "$allowed_ips" || return 1
# Store subtype in meta
if [[ -n "$subtype" ]]; then
peers::set_meta "$full_name" "subtype" "$subtype"
fi
# Add peer to server config
local public_key
public_key=$(keys::public "$full_name") || return 1
peers::add_to_server "$full_name" "$public_key" "$ip" || return 1
# Apply presets
for preset in "${presets[@]}"; do
firewall::apply_preset "$preset" "${ip}" || return 1
done
# Apply guest rules if guest type
if config::is_guest_type "$type"; then
firewall::apply_guest_rules
fi
# Reload WireGuard
peers::reload || return 1
log::wg_success "Client added successfully: ${full_name} (${ip}) [${tunnel} tunnel]"
# Show QR for mobile by default, config for desktop/laptop
local display_type="${subtype:-$type}"
if cmd::add::is_mobile "$display_type"; then
keys::qr "$full_name"
else
log::section "Client Config"
cat "$(ctx::clients)/${full_name}.conf"
fi
}
| 1 | #!/usr/bin/env bash |
| 2 | |
| 3 | # ============================================ |
| 4 | # Lifecycle |
| 5 | # ============================================ |
| 6 | |
| 7 | function cmd::add::on_load() { |
| 8 | flag::register --name |
| 9 | flag::register --type |
| 10 | flag::register --subtype |
| 11 | flag::register --ip |
| 12 | flag::register --preset |
| 13 | flag::register --guest |
| 14 | flag::register --tunnel |
| 15 | flag::register --show-config |
| 16 | flag::register --show-qr |
| 17 | } |
| 18 | |
| 19 | # ============================================ |
| 20 | # Help |
| 21 | # ============================================ |
| 22 | |
| 23 | function cmd::add::help() { |
| 24 | cat <<EOF |
| 25 | Usage: wgctl add --name <name> --type <type> [options] |
| 26 | |
| 27 | Add a new WireGuard client. |
| 28 | |
| 29 | Options: |
| 30 | --name <name> Client name (e.g. nuno) |
| 31 | --type <type> Device type: desktop, laptop, phone, tablet, guest |
| 32 | --ip <ip> Override auto-assigned IP (optional) |
| 33 | --preset <name> Apply a firewall preset (repeatable) |
| 34 | --guest Shorthand for --type guest |
| 35 | --tunnel <mode> Tunnel mode: split (default) or full |
| 36 | --show-config Shows the WireGuard peer config |
| 37 | --show-qr Shows the WireGuard config in a QR Code |
| 38 | |
| 39 | Device Types and Subnets: |
| 40 | desktop 10.1.1.x |
| 41 | laptop 10.1.2.x |
| 42 | phone 10.1.3.x |
| 43 | tablet 10.1.4.x |
| 44 | guest 10.1.100.x |
| 45 | |
| 46 | Tunnel Modes: |
| 47 | split Route only VPN subnet + LAN through WireGuard (default) |
| 48 | full Route all traffic through WireGuard |
| 49 | |
| 50 | Examples: |
| 51 | wgctl add --name nuno --type phone |
| 52 | wgctl add --name nuno --type laptop --ip 10.1.2.5 |
| 53 | wgctl add --name nuno --type phone --tunnel full |
| 54 | wgctl add --name guest1 --type phone --guest |
| 55 | wgctl add --name restricted --type desktop --preset no-docker --preset no-proxmox |
| 56 | EOF |
| 57 | } |
| 58 | |
| 59 | # ============================================ |
| 60 | # Validation |
| 61 | # ============================================ |
| 62 | |
| 63 | function cmd::add::validate() { |
| 64 | local name="$1" |
| 65 | local type="$2" |
| 66 | local ip="$3" |
| 67 | local tunnel="$4" |
| 68 | |
| 69 | if [[ -z "$name" ]]; then |
| 70 | log::error "Missing required flag: --name" |
| 71 | return 1 |
| 72 | fi |
| 73 | |
| 74 | if [[ -z "$type" ]]; then |
| 75 | log::error "Missing required flag: --type" |
| 76 | return 1 |
| 77 | fi |
| 78 | |
| 79 | if ! config::is_valid_type "$type"; then |
| 80 | log::error "Invalid device type: ${type}" |
| 81 | log::info "Valid types: $(config::device_types | tr ' ' ', ')" |
| 82 | return 1 |
| 83 | fi |
| 84 | |
| 85 | if [[ -n "$tunnel" && "$tunnel" != "split" && "$tunnel" != "full" ]]; then |
| 86 | log::error "Invalid tunnel mode: ${tunnel} (use 'split' or 'full')" |
| 87 | return 1 |
| 88 | fi |
| 89 | |
| 90 | if [[ -n "$ip" ]]; then |
| 91 | ip::require_valid "$ip" |
| 92 | fi |
| 93 | |
| 94 | local full_name="${type}-${name}" |
| 95 | |
| 96 | if [[ -f "$(ctx::clients)/${full_name}.conf" ]]; then |
| 97 | log::error "Client already exists: ${full_name}" |
| 98 | return 1 |
| 99 | fi |
| 100 | } |
| 101 | |
| 102 | # ============================================ |
| 103 | # Display helpers |
| 104 | # ============================================ |
| 105 | |
| 106 | function cmd::add::is_mobile() { |
| 107 | local type="$1" |
| 108 | [[ "$type" == "phone" || "$type" == "tablet" ]] |
| 109 | } |
| 110 | |
| 111 | # ============================================ |
| 112 | # Run |
| 113 | # ============================================ |
| 114 | |
| 115 | function cmd::add::run() { |
| 116 | local name="" |
| 117 | local type="" |
| 118 | local subtype="" |
| 119 | local ip="" |
| 120 | local tunnel="" |
| 121 | local guest=false |
| 122 | local presets=() |
| 123 | local show_config=false |
| 124 | local show_qr=false |
| 125 | |
| 126 | # Parse flags |
| 127 | while [[ $# -gt 0 ]]; do |
| 128 | case "$1" in |
| 129 | --name) name="$2"; shift 2 ;; |
| 130 | --type) type="$2"; shift 2 ;; |
| 131 | --subtype) subtype="$2"; shift 2 ;; |
| 132 | --ip) ip="$2"; shift 2 ;; |
| 133 | --preset) presets+=("$2"); shift 2 ;; |
| 134 | --guest) guest=true; shift ;; |
| 135 | --tunnel) tunnel="$2"; shift 2 ;; |
| 136 | --show-config) show_config=true; shift ;; |
| 137 | --show-qr) show_qr=true; shift ;; |
| 138 | --help) cmd::add::help; return ;; |
| 139 | *) |
| 140 | log::error "Unknown flag: $1" |
| 141 | cmd::add::help |
| 142 | return 1 |
| 143 | ;; |
| 144 | esac |
| 145 | done |
| 146 | |
| 147 | # --guest shorthand |
| 148 | if $guest; then |
| 149 | type="guest" |
| 150 | fi |
| 151 | |
| 152 | # Resolve guest subtype |
| 153 | local effective_type="$type" |
| 154 | if [[ "$type" == "guest" && -n "$subtype" ]]; then |
| 155 | # Validate subtype |
| 156 | local valid_subtypes="desktop laptop phone tablet" |
| 157 | if ! echo "$valid_subtypes" | grep -qw "$subtype"; then |
| 158 | log::error "Invalid subtype: ${subtype} (valid: desktop, laptop, phone, tablet)" |
| 159 | return 1 |
| 160 | fi |
| 161 | effective_type="guest-${subtype}" |
| 162 | fi |
| 163 | |
| 164 | # Build full client name |
| 165 | local full_name="${type}-${name}" |
| 166 | |
| 167 | # Validate |
| 168 | cmd::add::validate "$name" "$type" "$ip" "$tunnel" || return 1 |
| 169 | |
| 170 | # Resolve tunnel mode — flag > device default |
| 171 | if [[ -z "$tunnel" ]]; then |
| 172 | tunnel=$(config::default_tunnel_for "$type") |
| 173 | fi |
| 174 | |
| 175 | local allowed_ips |
| 176 | allowed_ips=$(config::allowed_ips_for "$effective_type" "$tunnel") || return 1 |
| 177 | |
| 178 | log::section "Adding client: ${full_name}" |
| 179 | |
| 180 | # Auto-assign IP if not provided |
| 181 | if [[ -z "$ip" ]]; then |
| 182 | ip=$(ip::next_for_type "$effective_type") || return 1 |
| 183 | fi |
| 184 | |
| 185 | log::wg_add "Name: ${full_name}" |
| 186 | log::wg_add "Type: ${type}" |
| 187 | log::wg_add "IP: ${ip}" |
| 188 | log::wg_add "Tunnel: ${tunnel} (${allowed_ips})" |
| 189 | |
| 190 | # Generate keys |
| 191 | keys::generate_pair "$full_name" || return 1 |
| 192 | |
| 193 | # Create client config |
| 194 | peers::create_client_config "$full_name" "$effective_type" "$ip" "$allowed_ips" || return 1 |
| 195 | |
| 196 | # Store subtype in meta |
| 197 | if [[ -n "$subtype" ]]; then |
| 198 | peers::set_meta "$full_name" "subtype" "$subtype" |
| 199 | fi |
| 200 | |
| 201 | # Add peer to server config |
| 202 | local public_key |
| 203 | public_key=$(keys::public "$full_name") || return 1 |
| 204 | peers::add_to_server "$full_name" "$public_key" "$ip" || return 1 |
| 205 | |
| 206 | # Apply presets |
| 207 | for preset in "${presets[@]}"; do |
| 208 | firewall::apply_preset "$preset" "${ip}" || return 1 |
| 209 | done |
| 210 | |
| 211 | # Apply guest rules if guest type |
| 212 | if config::is_guest_type "$type"; then |
| 213 | firewall::apply_guest_rules |
| 214 | fi |
| 215 | |
| 216 | # Reload WireGuard |
| 217 | peers::reload || return 1 |
| 218 | |
| 219 | log::wg_success "Client added successfully: ${full_name} (${ip}) [${tunnel} tunnel]" |
| 220 | |
| 221 | # Show QR for mobile by default, config for desktop/laptop |
| 222 | local display_type="${subtype:-$type}" |
| 223 | |
| 224 | if cmd::add::is_mobile "$display_type"; then |
| 225 | keys::qr "$full_name" |
| 226 | else |
| 227 | log::section "Client Config" |
| 228 | cat "$(ctx::clients)/${full_name}.conf" |
| 229 | fi |
| 230 | } |