# NetBird 部署摘要 ## 部署結果 **NetBird Self-Hosted 已部署於 `192.168.42.127`** - 部署日期:2026-04-17 - 存取方式:`http://192.168.42.127` - 部署模式:HTTP-only(無 TLS)、IP-only(無域名) ## 部署時的關鍵決策 ### 1. 為何不用官方 `getting-started.sh` 腳本? 官方腳本是互動式的(讀取 `/dev/tty`),且預設選項需要公網域名與 Let's Encrypt。本次為內網 IP 部署,手動產生設定檔更直接。 腳本原始碼中支援 `NETBIRD_DOMAIN=use-ip` 模式(line 308),會自動偵測 IP 並切換為 HTTP、port 80、`rel://` relay 協定。本次部署採用了相同的邏輯但以手動設定檔實現。 ### 2. 為何選 Caddy 而非 Traefik 作為反向代理? - Traefik 內建 Let's Encrypt ACME,但無域名時會反覆嘗試失敗 - Caddy 設定檔更精簡(約 10 行),純 HTTP 路由對本場景足夠 - 兩者都能正確處理 gRPC (h2c) 後端 ### 3. 為何需要 crun 取代 runc? **遇到的錯誤**: ``` OCI runtime create failed: runc create failed: unable to start container process: error during container init: open sysctl net.ipv4.ip_unprivileged_port_start file: reopen fd 8: permission denied ``` **根因分析**: - Proxmox 非特權 LXC 將 `/proc/sys` 掛載為唯讀(`ro`) - runc 1.2+ 為防止 TOCTOU 攻擊,會用「reopen fd」機制存取 sysctl 檔案 - 此機制在 LXC 的 `/proc` mount namespace 限制下失敗 **嘗試但無效的方法**: - `--privileged` flag - `--security-opt apparmor=unconfined --security-opt seccomp=unconfined` - `mount -o remount,rw /proc/sys` - apt 安裝的 crun 1.14.1(OCI spec 太舊,Docker 不接受) **最終解法**: 下載 GitHub 最新 crun binary (1.20),並設為 Docker 預設 runtime。 ## 安裝步驟摘要 ``` 1. apt 安裝前置套件 (curl, jq, openssl) ← 約 1 分鐘 2. 安裝 Docker Engine ← 約 2 分鐘(網速慢) 3. 下載 crun binary,設定為 Docker 預設 runtime ← 約 30 秒 4. 建立 /opt/netbird/ 下四個設定檔 ← 約 30 秒 5. docker compose pull + up -d ← 約 1 分鐘 6. 等待 netbird-server 下載 GeoLite2 資料庫 ← 約 2-5 分鐘 ``` 總耗時約 10 分鐘(含 GeoIP 下載)。 ## 服務驗證 所有服務已驗證正常運作: | 端點 | 狀態 | 說明 | |------|------|------| | `http://192.168.42.127/` | 200 | Dashboard Web UI | | `http://192.168.42.127/oauth2/.well-known/openid-configuration` | 200 | OIDC Discovery | | STUN `:3478/udp` | | NAT 穿透 | | 內部 gRPC (Signal + Management) | | h2c via Caddy | ## 疑難排解記錄 | 問題 | 原因 | 解法 | |------|------|------| | 初次 apt install 卡住 | 背景執行的 apt 行程未結束就被觸發第二個,導致 lock 競爭 | 清除 `/var/lib/dpkg/lock*` 與 `/var/lib/apt/lists/lock`,重啟 apt | | `curl: command not found` | apt 仍在安裝中 | 等待安裝完成 | | 所有 Docker 容器啟動失敗 | LXC `/proc/sys` 唯讀 + runc 1.3 的 reopen fd 機制 | 改用 crun 1.20 | | Docker Compose `crun` runtime 未知 | `/etc/docker/daemon.json` 設定未生效 | `systemctl restart docker` | | apt 的 crun 1.14.1 報 `unknown version specified` | crun 的 OCI spec 1.0.0 與 Docker 29.4 不相容 | 從 GitHub 下載 crun 1.20 | | 502 Bad Gateway(所有 API 端點) | netbird-server 下載 GeoLite2 中,尚未開始監聽 port 80 | 等待 2-5 分鐘 | | 手機 App:`server closed the stream without sending trailers` | 透過 NPM 對外公開時,NPM 用 `proxy_pass` 而非 `grpc_pass` 轉 gRPC;加上 Caddy `:80` 預設不支援 h2c | NPM 加 `advanced_config`(gRPC 路由)+ Caddy 全域啟用 `protocols h1 h2 h2c`;見 [npm-grpc-fix.md](./npm-grpc-fix.md) | | `PUT /api/setup-keys/{id}` 回 200 但 `name` 沒改 | NetBird API 只更新 `auto_groups` 和 `revoked`,`name` 是 read-only | 改 `store.db` 的 `setup_keys.name` → 重啟 `netbird-server`;見 [setup-keys-and-pat.md](./setup-keys-and-pat.md) | | peer 之間一直走 Relay,`[stun:...:3478] is Checking...` | STUN port 3478 被 Headscale (`.126`) 搶先佔用 DNAT | NetBird 改用 3479/udp(server config + OpenWrt DNAT);見 [stun-port-conflict.md](./stun-port-conflict.md) | | STUN 都通了,仍然 `Connection type: Relayed`、`ICE candidate: -/-` | 客戶端 **Force Relay Connection** 開關被打開,跳過 ICE gathering | App Settings → Advanced → 關掉 Force Relay;見 [client-troubleshooting.md](./client-troubleshooting.md) | | ping RTT 尖峰到 2-4 秒 | 當下 WiFi/ISP 有 bufferbloat | 用 `networkQuality -v` 確認(看 RPM),與 NetBird 無關 | | Mac `netbird status` 卡在 `Management: Disconnected, rpc error` | daemon 本身卡死,`netbird up`/`down` 救不回 | `sudo launchctl kickstart -k system/netbird`(服務名是 `netbird` 而非 `io.netbird.client`);見 [peer-deployment-ops.md](./peer-deployment-ops.md) | | LXC CT100 SSH root 密碼被擋(`Permission denied`) | sshd_config 內有 `PermitRootLogin prohibit-password` 比 `yes` 先出現,first-match-wins | 加 `/etc/ssh/sshd_config.d/99-allow-root.conf` 覆蓋;socket activation 不需重啟 sshd | ## 檔案位置 ### 本機(/Users/timmy/dev/42_127/) ``` ├── README.md ← 專案總覽 ├── QUICKSTART.md ← 快速部署步驟 ├── SUMMARY.md ← 本檔(部署摘要) ├── netbird-selfhosted-setup.md ← 完整安裝指南 ├── npm-grpc-fix.md ← 經 NPM 對外公開時的 gRPC 修復 ├── setup-keys-and-pat.md ← Setup Keys / PAT 操作與 API 限制繞道 ├── stun-port-conflict.md ← STUN 改用 3479 (3478 被 Headscale 佔用) ├── client-troubleshooting.md ← 客戶端 Force Relay / bufferbloat 排錯 ├── peer-deployment-ops.md ← Docker 部署客戶端、改名、Mac daemon 重啟 └── groups-and-policies.md ← Groups 與 Access Control 設計與操作 ``` ### 遠端伺服器(192.168.42.127:/opt/netbird/) ``` ├── docker-compose.yml ← 服務編排 ├── Caddyfile ← 反向代理路由 ├── config.yaml ← NetBird Server 設定 └── dashboard.env ← Dashboard 環境變數 ``` ### Docker Volume - `netbird_netbird_data`:NetBird SQLite 資料庫與 GeoIP 資料 ## 後續維運 ### 日常指令 ```bash ssh root@192.168.42.127 cd /opt/netbird # 查看日誌 docker compose logs -f # 重啟 docker compose restart # 更新映像 docker compose pull && docker compose up -d # 備份資料庫 docker compose exec netbird-server sqlite3 /var/lib/netbird/store.db ".backup /tmp/backup.db" docker cp netbird-server:/tmp/backup.db ./backup-$(date +%F).db ``` ### 可能的擴展 - **加上 TLS**:若之後取得域名,可切換為 Traefik + Let's Encrypt(或本機自簽憑證) - **外部 IdP**:需支援 OIDC 的 IdP(如 Authentik、Keycloak、Google)可取代內建 Dex - **高可用性**:SQLite 不支援叢集,需改用 PostgreSQL;Relay 可獨立部署 - **監控**:已暴露 `metricsPort: 9090` 的 Prometheus 指標 ## 參考資料 - 官方文件:https://docs.netbird.io/selfhosted/selfhosted-quickstart - Getting Started 腳本:https://github.com/netbirdio/netbird/releases/latest/download/getting-started.sh - crun release:https://github.com/containers/crun/releases - Proxmox LXC Docker 相容性:https://pve.proxmox.com/wiki/Linux_Container#_features