在 macOS 上同時連多個 WireGuard:突破官方 App 一次一條的限制,附自製管理腳本 wgm

本篇文章更新時間:2026/09/06
如有資訊過時或語誤之處,歡迎使用 Contact 功能通知或向一介資男的 LINE 社群反應。
如果本站內容對你有幫助,歡迎贊助支持


macOS 官方的 WireGuard App 有個惱人的硬限制:一次只能啟用一條 tunnel。當你需要同時連上多個彼此獨立的內網時,只能在它們之間切換。這篇記錄我怎麼用命令列工具突破這個限制,並附上我自己寫的一支管理腳本 wgm,把多 tunnel 的開關、狀態查看、開機自動啟動都收斂成好記的指令。

先建立正確的心智模型

  • 一條 tunnel = 一個網路介面:每啟用一條 WireGuard tunnel,macOS 就多一個 utun 介面(utun0utun1…),彼此在系統層面獨立。
  • 能否同時連,取決於路由有沒有打架:WireGuard 用 conf 裡的 AllowedIPs 決定「哪些流量走這條 tunnel」,所有 tunnel 最後都寫進同一張系統路由表。網段不重疊才能並存(split tunnel);若多條都寫 0.0.0.0/0,會搶同一個預設出口,只有一條真正生效。
  • App 限制是「App 層」,不是「協議層」:WireGuard 協議本身允許同時起多個介面(Linux 上用 wg-quick 就能做到),是 macOS 官方 App 的 UI 把它限制成一次一條。所以繞過方法就是改用命令列。

解法:用 CLI 取代 App

市面上有一些第三方 GUI/menubar 工具,但它們底層幾乎都是包 wg-quick,而且不少已停止開發,在新版 macOS 上相容性有風險。與其賭它們能不能跑,不如直接用官方 CLI,再自己包一層方便的腳本。

brew install wireguard-tools

這會裝好 wgwg-quick,以及依賴 wireguard-go(macOS 沒有核心模組,靠這個 userspace 實作運行)和新版 bash。conf 檔放在 /opt/homebrew/etc/wireguard/(Apple Silicon),檔名即 tunnel 名稱。conf 含私鑰,記得收緊權限:

chmod 600 /opt/homebrew/etc/wireguard/*.conf

CLI 基本操作

# 啟用(需 sudo,因為要改路由表)
sudo wg-quick up office-vpn
# 關閉
sudo wg-quick down office-vpn
# 同時多條,分別 up 即可(前提:AllowedIPs 不重疊)
sudo wg-quick up datacenter-vpn
# 查看細節
sudo wg show
# 確認各網段路由並存、沒互搶
netstat -rn | grep utun

關鍵陷阱:DNS 副作用

當 conf 裡有 DNS = 這行,wg-quick up 會把 DNS 設定套用到你系統上所有的網路服務(Wi-Fi、各個 VPN…)。這帶來兩個問題:多條 tunnel 的 DNS 會互相覆蓋(最終生效的是最後啟用那條);而且可能干擾其他網路服務的 DNS,關閉時的還原也不保證乾淨。

如果你的 tunnel 是純內網用途、且都用 IP 存取內網機器,那 conf 裡的 DNS = 根本不需要,拿掉它就能消除這串副作用。若有用內網域名解析才需要保留。

wgm:多 tunnel 管理腳本

sudo wg-quick up/down 加一堆驗證指令太瑣碎,所以我包了一支 wgm。完整原始碼也放在這個 Gist:https://gist.github.com/nczz/d0252f26a86a4d75887e15e0ecf9385a

指令行為需要 sudo
wgm list列出所有 tunnel(● 已啟用 / ○ 未啟用)
wgm status完整狀態:handshake、流量、本機/server WG IP
wgm status -q快速狀態:up/down + 路由 + 本機 WG IP
wgm up <name|all>啟用一條或全部
wgm down <name|all>關閉一條或全部
wgm restart <name>重連(down 再 up)
wgm enable-boot <name>設定開機自動啟動(launchd)
wgm disable-boot <name>取消開機自動啟動
wgm boot-status顯示各 tunnel 開機啟動設定

設計重點

  1. 自動列舉 conf:掃描 conf 目錄,up all 自動全開,新增 conf 不用改腳本。
  2. sudo 自動化:偵測非 root 時自動 exec sudo 重呼叫自己,搭配 Touch ID for sudo 就能刷指紋而非打密碼。
  3. 啟用狀態判斷:用 /var/run/wireguard/<name>.name 檔案存在與否判斷,這是 wg-quick 在 macOS 上記錄 tunnel↔utun 對應的官方機制。
  4. 衝突防呆up 前檢查 AllowedIPs 有沒有跟已啟用的 tunnel 重疊、或含全流量,重疊就警告。
  5. 狀態解析用 wg dump:解析機器可讀的 tab 分隔格式,比解析人類可讀輸出穩定。
  6. server WG IP 是推測值:WireGuard 不會回報對端的 WG 內網 IP,腳本取 AllowedIPs 網段的 .1(常見慣例)作推測,明確標注。

開機自動啟動的原理

enable-boot 會產生一個 launchd LaunchDaemon plist 到 /Library/LaunchDaemons/。關鍵是 LaunchDaemon 執行時環境變數極簡,plist 裡必須明確設定 PATH 指向 /opt/homebrew/bin,否則開機會因為找不到 wg/bash 而失敗。

安裝 wgm

# 從 Gist 下載到 ~/.local/bin
mkdir -p ~/.local/bin
curl -fsSL https://gist.github.com/nczz/d0252f26a86a4d75887e15e0ecf9385a/raw/wgm -o ~/.local/bin/wgm
chmod +x ~/.local/bin/wgm
# 確認 ~/.local/bin 在 PATH,然後:
wgm list

(若 Gist 的 raw 連結因檔名而不同,直接開上面的 Gist 頁面複製內容存成 ~/.local/bin/wgm 即可。)

wgm 完整原始碼

#!/opt/homebrew/bin/bash
#
# wgm — WireGuard tunnel manager for macOS (wraps wg-quick)
#
# 依賴:Homebrew wireguard-tools(wg, wg-quick)
# conf 目錄:/opt/homebrew/etc/wireguard/*.conf(檔名 = tunnel 名稱)
#
# 用法:
#   wgm list                 列出所有可用 tunnel(不需 sudo)
#   wgm status               顯示每條的 up/down 與 handshake
#   wgm up <name|all>        啟用一條或全部
#   wgm down <name|all>      關閉一條或全部
#   wgm restart <name>       重連(down 再 up)
#   wgm enable-boot <name>   設定開機自動啟動(launchd)
#   wgm disable-boot <name>  取消開機自動啟動
#   wgm boot-status          顯示各 tunnel 的開機啟動設定狀態
#
set -euo pipefail

# ---- 設定 ----
WG_DIR="/opt/homebrew/etc/wireguard"
BREW_BIN="/opt/homebrew/bin"
WG_QUICK="$BREW_BIN/wg-quick"
WG="$BREW_BIN/wg"
LAUNCHD_DIR="/Library/LaunchDaemons"
LABEL_PREFIX="com.wireguard"

# ---- 顏色 ----
if [[ -t 1 ]]; then
  C_GREEN=$'\033[32m'; C_GREY=$'\033[90m'; C_RED=$'\033[31m'
  C_YELLOW=$'\033[33m'; C_BOLD=$'\033[1m'; C_RESET=$'\033[0m'
else
  C_GREEN=""; C_GREY=""; C_RED=""; C_YELLOW=""; C_BOLD=""; C_RESET=""
fi

err()  { echo "${C_RED}error:${C_RESET} $*" >&2; }
warn() { echo "${C_YELLOW}warn:${C_RESET}  $*" >&2; }
info() { echo "$*"; }

# ---- 前置檢查 ----
[[ -x "$WG_QUICK" ]] || { err "找不到 wg-quick($WG_QUICK)。請先 brew install wireguard-tools"; exit 1; }
[[ -d "$WG_DIR" ]]   || { err "conf 目錄不存在:$WG_DIR"; exit 1; }

# 列出所有 tunnel 名稱(conf 檔名去掉 .conf)
list_tunnels() {
  local f
  shopt -s nullglob
  for f in "$WG_DIR"/*.conf; do
    basename "$f" .conf
  done
  shopt -u nullglob
}

# 某 tunnel 是否存在對應 conf
conf_exists() {
  [[ -f "$WG_DIR/$1.conf" ]]
}

# 某 tunnel 目前是否啟用(wg show interfaces 用的是 utunN,這裡靠 wg-quick 的慣例:
# 啟用中的 tunnel 名稱可從 wg show 的 interface 反查,但 macOS 上 interface 是 utunN,
# 名稱對應存在 /var/run/wireguard/<name>.name。用該檔存在與否判斷最可靠。)
is_up() {
  local name="$1"
  [[ -f "/var/run/wireguard/$name.name" ]]
}

# 取出 conf 的 AllowedIPs(用於衝突檢查),輸出每行一個 CIDR
allowed_ips() {
  local name="$1"
  grep -iE '^[[:space:]]*AllowedIPs[[:space:]]*=' "$WG_DIR/$name.conf" 2>/dev/null \
    | sed -E 's/^[[:space:]]*[Aa][Ll][Ll][Oo][Ww][Ee][Dd][Ii][Pp][Ss][[:space:]]*=[[:space:]]*//' \
    | tr ',' '\n' \
    | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//' \
    | grep -v '^$' || true
}

# 本機在這條 tunnel 的 WG IP(conf 的 Address,取 IPv4,去掉 /mask)
local_wg_ip() {
  local name="$1"
  grep -iE '^[[:space:]]*Address[[:space:]]*=' "$WG_DIR/$name.conf" 2>/dev/null \
    | sed -E 's/^[[:space:]]*[Aa][Dd][Dd][Rr][Ee][Ss][Ss][[:space:]]*=[[:space:]]*//' \
    | tr ',' '\n' \
    | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//' \
    | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' \
    | head -1 \
    | sed -E 's#/.*##' || true
}

# 推測 server 在 WG 網路內的 IP:取第一個 IPv4 AllowedIPs 網段的 .1(WireGuard 慣例,僅供參考)
guess_server_wg_ip() {
  local name="$1" cidr net
  cidr="$(allowed_ips "$name" | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/' | head -1)"
  [[ -z "$cidr" ]] && return 0
  net="${cidr%/*}"                       # 10.10.10.0
  echo "${net%.*}.1"                     # 10.10.10.1
}

# 檢查即將啟用的 tunnel 是否與「已啟用的其他 tunnel」AllowedIPs 重疊(僅字串完全相同比對,
# 做基本防呆;不做完整 CIDR 子網包含運算,避免誤判與過度複雜)
check_conflict() {
  local target="$1" other cidr t_cidrs o_cidrs
  t_cidrs="$(allowed_ips "$target")"
  # 全流量特別警告
  if grep -qE '(^|[[:space:]])0\.0\.0\.0/0([[:space:]]|$)|(^|[[:space:]])::/0([[:space:]]|$)' <<<"$t_cidrs"; then
    while read -r other; do
      [[ -z "$other" || "$other" == "$target" ]] && continue
      if is_up "$other"; then
        warn "$target 含全流量路由 (0.0.0.0/0 或 ::/0),會接管所有流量,可能影響已啟用的 $other"
      fi
    done < <(list_tunnels)
  fi
  while read -r other; do
    [[ -z "$other" || "$other" == "$target" ]] && continue
    is_up "$other" || continue
    o_cidrs="$(allowed_ips "$other")"
    while read -r cidr; do
      [[ -z "$cidr" ]] && continue
      if grep -qxF "$cidr" <<<"$o_cidrs"; then
        warn "AllowedIPs 重疊:$target 與已啟用的 $other 都有 $cidr —— 路由可能衝突"
      fi
    done <<<"$t_cidrs"
  done < <(list_tunnels)
}

# 需要 root 的操作:若非 root 則用 sudo 重新呼叫該子命令
need_root() {
  if [[ "$EUID" -ne 0 ]]; then
    exec sudo -p "wgm 需要管理員權限,請輸入密碼: " "$0" "${ORIG_ARGS[@]}"
  fi
}

cmd_list() {
  info "${C_BOLD}可用的 WireGuard tunnel:${C_RESET}"
  local n found=0
  while read -r n; do
    [[ -z "$n" ]] && continue
    found=1
    if is_up "$n"; then
      echo "  ${C_GREEN}●${C_RESET} $n"
    else
      echo "  ${C_GREY}○ $n${C_RESET}"
    fi
  done < <(list_tunnels)
  [[ "$found" -eq 0 ]] && info "  ${C_GREY}(無,放 .conf 到 $WG_DIR)${C_RESET}"
}

# 人類可讀的相對時間(傳入 Unix timestamp)
_ago() {
  local ts="$1" now diff
  [[ -z "$ts" || "$ts" == "0" ]] && { echo "從未"; return; }
  now="$(date +%s)"
  diff=$(( now - ts ))
  if   (( diff < 0 ));    then echo "剛剛"
  elif (( diff < 60 ));   then echo "${diff} 秒前"
  elif (( diff < 3600 )); then echo "$(( diff / 60 )) 分鐘前"
  elif (( diff < 86400 ));then echo "$(( diff / 3600 )) 小時前"
  else                         echo "$(( diff / 86400 )) 天前"
  fi
}

# 人類可讀的位元組
_hbytes() {
  local b="${1:-0}"
  if   (( b < 1024 ));            then echo "${b} B"
  elif (( b < 1048576 ));         then echo "$(( b / 1024 )) KiB"
  elif (( b < 1073741824 ));      then echo "$(( b / 1048576 )) MiB"
  else                                 echo "$(( b / 1073741824 )) GiB"
  fi
}

# 快速版狀態(免 sudo):只看 up/down + conf 的 AllowedIPs + 路由
_status_quick() {
  local n any=0 cidr
  while read -r n; do
    [[ -z "$n" ]] && continue
    if is_up "$n"; then
      any=1
      echo "${C_GREEN}● $n${C_RESET} ${C_GREY}(up)${C_RESET}"
      local lip; lip="$(local_wg_ip "$n")"
      [[ -n "$lip" ]] && echo "    ${C_GREY}本機 WG IP:${C_RESET} $lip"
      while read -r cidr; do
        [[ -z "$cidr" ]] && continue
        echo "    ${C_GREY}route:${C_RESET} $cidr"
      done < <(allowed_ips "$n")
    else
      echo "${C_GREY}○ $n (down)${C_RESET}"
    fi
  done < <(list_tunnels)
  [[ "$any" -eq 0 ]] && info "${C_GREY}(目前沒有啟用中的 tunnel)${C_RESET}"
  echo "${C_GREY}(快速模式:未讀 handshake。跑 'wgm status' 看完整細節)${C_RESET}"
}

# 完整版狀態:需要 root 讀 wg dump
_status_full() {
  local n any=0 iface line
  while read -r n; do
    [[ -z "$n" ]] && continue
    if ! is_up "$n"; then
      echo "${C_GREY}○ $n (down)${C_RESET}"
      continue
    fi
    any=1
    iface="$(cat "/var/run/wireguard/$n.name" 2>/dev/null || true)"
    echo "${C_GREEN}● $n${C_RESET} ${C_GREY}($iface)${C_RESET}"

    # 本機與 server 的 WG 內網 IP(從 conf 推導)
    local lip sip
    lip="$(local_wg_ip "$n")"
    sip="$(guess_server_wg_ip "$n")"
    [[ -n "$lip" ]] && echo "    ${C_GREY}本機 WG IP:${C_RESET}  $lip"
    [[ -n "$sip" ]] && echo "    ${C_GREY}server WG IP:${C_RESET} ${sip} ${C_GREY}(推測,網段 .1)${C_RESET}"

    # wg show <iface> dump:
    #   行1(interface): private-key pub-key listen-port fwmark
    #   行2+(peer):     pub-key psk endpoint allowed-ips latest-handshake rx tx keepalive
    local first=1
    while IFS=$'\t' read -r f1 f2 f3 f4 f5 f6 f7 f8; do
      if [[ "$first" -eq 1 ]]; then
        first=0
        [[ -n "$f3" ]] && echo "    ${C_GREY}listen-port:${C_RESET} $f3"
        continue
      fi
      # peer 行:f3=endpoint f4=allowed-ips f5=handshake(ts) f6=rx f7=tx f8=keepalive
      local ep="$f3" aips="$f4" hs="$f5" rx="$f6" tx="$f7" ka="$f8"
      local hs_txt; hs_txt="$(_ago "$hs")"
      # 健康判讀:3 分鐘內握手 = healthy
      local health
      if [[ "$hs" == "0" || -z "$hs" ]]; then
        health="${C_YELLOW}⚠ 尚未握手${C_RESET}"
      elif (( $(date +%s) - hs < 180 )); then
        health="${C_GREEN}✓ 連線正常${C_RESET}"
      else
        health="${C_YELLOW}⚠ handshake 已過期(可能閒置或斷線)${C_RESET}"
      fi
      [[ "$ep" == "(none)" || -z "$ep" ]] || echo "    ${C_GREY}endpoint:${C_RESET}    $ep"
      echo "    ${C_GREY}allowed-ips:${C_RESET} $aips"
      echo "    ${C_GREY}handshake:${C_RESET}   $hs_txt   $health"
      echo "    ${C_GREY}transfer:${C_RESET}    ↓ $(_hbytes "$rx")  ↑ $(_hbytes "$tx")"
      [[ "$ka" == "off" || -z "$ka" ]] || echo "    ${C_GREY}keepalive:${C_RESET}   ${ka}s"
    done < <("$WG" show "$iface" dump 2>/dev/null)
  done < <(list_tunnels)
  [[ "$any" -eq 0 ]] && info "${C_GREY}(目前沒有啟用中的 tunnel)${C_RESET}"
}

cmd_status() {
  # -q / --quick:免 sudo 快速模式
  if [[ "${1:-}" == "-q" || "${1:-}" == "--quick" ]]; then
    _status_quick
    return
  fi
  # 完整模式需要 root 讀 wg dump;非 root 時自動用 sudo(觸發 Touch ID)
  if [[ "$EUID" -ne 0 ]]; then
    # 若沒有任何 tunnel 啟用,不必為了空結果動用 sudo
    local has_up=0 n
    while read -r n; do [[ -n "$n" ]] && is_up "$n" && has_up=1; done < <(list_tunnels)
    if [[ "$has_up" -eq 0 ]]; then
      _status_quick
      return
    fi
    exec sudo -p "wgm status 需要權限讀取 handshake(可刷 Touch ID): " "$0" "${ORIG_ARGS[@]}"
  fi
  _status_full
}

cmd_up() {
  local target="${1:-}"
  [[ -z "$target" ]] && { err "用法:wgm up <name|all>"; exit 1; }
  need_root
  if [[ "$target" == "all" ]]; then
    local n
    while read -r n; do
      [[ -z "$n" ]] && continue
      _up_one "$n"
    done < <(list_tunnels)
  else
    conf_exists "$target" || { err "找不到 conf:$target(用 wgm list 查看)"; exit 1; }
    _up_one "$target"
  fi
}

_up_one() {
  local name="$1"
  if is_up "$name"; then
    info "${C_GREY}$name 已啟用,略過${C_RESET}"
    return 0
  fi
  check_conflict "$name"
  info "${C_BOLD}啟用 $name ...${C_RESET}"
  "$WG_QUICK" up "$name"
}

cmd_down() {
  local target="${1:-}"
  [[ -z "$target" ]] && { err "用法:wgm down <name|all>"; exit 1; }
  need_root
  if [[ "$target" == "all" ]]; then
    local n
    while read -r n; do
      [[ -z "$n" ]] && continue
      is_up "$n" && { info "${C_BOLD}關閉 $n ...${C_RESET}"; "$WG_QUICK" down "$n"; } || info "${C_GREY}$n 未啟用,略過${C_RESET}"
    done < <(list_tunnels)
  else
    conf_exists "$target" || { err "找不到 conf:$target"; exit 1; }
    if is_up "$target"; then
      info "${C_BOLD}關閉 $target ...${C_RESET}"
      "$WG_QUICK" down "$target"
    else
      info "${C_GREY}$target 未啟用${C_RESET}"
    fi
  fi
}

cmd_restart() {
  local target="${1:-}"
  [[ -z "$target" ]] && { err "用法:wgm restart <name>"; exit 1; }
  conf_exists "$target" || { err "找不到 conf:$target"; exit 1; }
  need_root
  is_up "$target" && { info "${C_BOLD}關閉 $target ...${C_RESET}"; "$WG_QUICK" down "$target"; }
  info "${C_BOLD}啟用 $target ...${C_RESET}"
  "$WG_QUICK" up "$target"
}

plist_path() { echo "$LAUNCHD_DIR/$LABEL_PREFIX.$1.plist"; }

cmd_enable_boot() {
  local name="${1:-}"
  [[ -z "$name" ]] && { err "用法:wgm enable-boot <name>"; exit 1; }
  conf_exists "$name" || { err "找不到 conf:$name"; exit 1; }
  need_root
  local plist; plist="$(plist_path "$name")"
  cat > "$plist" <<PLIST
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>$LABEL_PREFIX.$name</string>
    <key>ProgramArguments</key>
    <array>
        <string>$WG_QUICK</string>
        <string>up</string>
        <string>$name</string>
    </array>
    <key>EnvironmentVariables</key>
    <dict>
        <key>PATH</key>
        <string>$BREW_BIN:/usr/bin:/bin:/usr/sbin:/sbin</string>
        <key>WG_QUICK_USERSPACE_IMPLEMENTATION</key>
        <string>wireguard-go</string>
    </dict>
    <key>RunAtLoad</key>
    <true/>
    <key>StandardOutPath</key>
    <string>/var/log/wgm-$name.log</string>
    <key>StandardErrorPath</key>
    <string>/var/log/wgm-$name.log</string>
</dict>
</plist>
PLIST
  chown root:wheel "$plist"
  chmod 644 "$plist"
  launchctl load -w "$plist" 2>/dev/null || launchctl bootstrap system "$plist" 2>/dev/null || true
  info "${C_GREEN}已設定開機自動啟動:$name${C_RESET}"
  info "  plist: $plist"
  info "  log:   /var/log/wgm-$name.log"
}

cmd_disable_boot() {
  local name="${1:-}"
  [[ -z "$name" ]] && { err "用法:wgm disable-boot <name>"; exit 1; }
  need_root
  local plist; plist="$(plist_path "$name")"
  [[ -f "$plist" ]] || { warn "$name 未設定開機啟動"; return 0; }
  launchctl unload -w "$plist" 2>/dev/null || launchctl bootout system "$plist" 2>/dev/null || true
  rm -f "$plist"
  info "${C_GREEN}已取消開機自動啟動:$name${C_RESET}"
}

cmd_boot_status() {
  local n plist
  info "${C_BOLD}開機啟動設定狀態:${C_RESET}"
  while read -r n; do
    [[ -z "$n" ]] && continue
    plist="$(plist_path "$n")"
    if [[ -f "$plist" ]]; then
      echo "  ${C_GREEN}✓${C_RESET} $n ${C_GREY}(開機自動啟動)${C_RESET}"
    else
      echo "  ${C_GREY}✗ $n (不自動啟動)${C_RESET}"
    fi
  done < <(list_tunnels)
}

usage() {
  cat <<'USAGE'
wgm — WireGuard tunnel manager (macOS)

用法:
  wgm list                 列出所有 tunnel(● 已啟用 / ○ 未啟用)
  wgm status               顯示啟用狀態、handshake、流量、本機/server WG IP(需權限,可刷 Touch ID)
  wgm status -q            快速狀態(免 sudo,只看 up/down + 路由 + 本機 WG IP)
  wgm up <name|all>        啟用一條或全部
  wgm down <name|all>      關閉一條或全部
  wgm restart <name>       重連(down 再 up)

  wgm enable-boot <name>   設定開機自動啟動(launchd LaunchDaemon)
  wgm disable-boot <name>  取消開機自動啟動
  wgm boot-status          顯示各 tunnel 開機啟動設定

conf 目錄:/opt/homebrew/etc/wireguard/(檔名即 tunnel 名稱)
需要 root 的操作會自動以 sudo 重新執行。
USAGE
}

# ---- 主流程 ----
ORIG_ARGS=("$@")
cmd="${1:-}"; shift || true
case "$cmd" in
  list)         cmd_list ;;
  status)       cmd_status "${1:-}" ;;
  up)           cmd_up "${1:-}" ;;
  down)         cmd_down "${1:-}" ;;
  restart)      cmd_restart "${1:-}" ;;
  enable-boot)  cmd_enable_boot "${1:-}" ;;
  disable-boot) cmd_disable_boot "${1:-}" ;;
  boot-status)  cmd_boot_status ;;
  ""|-h|--help|help) usage ;;
  *) err "未知指令:$cmd"; echo; usage; exit 1 ;;
esac

一頁速查

安裝:      brew install wireguard-tools
conf 目錄: /opt/homebrew/etc/wireguard/(檔名=tunnel 名)
權限:      chmod 600 conf 檔

日常:
  wgm list / wgm status / wgm status -q
  wgm up <name|all> / wgm down <name|all> / wgm restart <name>
開機自動:
  wgm enable-boot <name> / wgm disable-boot <name> / wgm boot-status

多條並存鐵則:各 conf 的 AllowedIPs 不能重疊;最多一條可以是全流量。
純內網 split tunnel:考慮拿掉 conf 的 DNS = 那行。
驗證連上:sudo wg show → 看 latest handshake + transfer received


Share:

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *


文章
Filter
Mastodon