DNSPilot Server 是 dnspilot Android app 的控制端,四大核心需求+擴充:
| 需求 | 功能 | 端點 / 頁面 |
|---|---|---|
| R1 | 版本化 config 派發:app 輪詢領取,304 無更新;合法 token 自動注入(token 更新閉環) | GET /v1/config、管理端 Config 分頁 |
| R2 | 合法 token client up 記錄:校驗 token(SHA-256、可即時撤銷)+事件記錄 | GET /v1/addip、事件分頁 |
| R3 | 使用者前台:Google OAuth 雙通道(網頁 code flow+app GSI id_token)→ 簽發/輪轉/撤銷/複製 token;首登自動簽發 | /portal/ |
| R4 | 管理端:client/token 清單、事件查詢、config 草稿→發布、每用戶 token 上限 | /admin/ |
| M4 | pfSense 管理:pfrest 連線狀態、硬體/健康資訊、DNS_allow_domain(host 型 alias)dry-run diff+apply 回讀驗證、alias 改名 | 管理端 pfSense 分頁 |
技術棧:FastAPI + SQLite(WAL) + JWT session(7 天)+ 每 IP 限流 120/min; Docker 部署(python:3.12-slim、non-root、healthcheck、自動 seed);測試基準 44/44 通過(test_api / test_oauth / test_pfsense / test_admin_login)。
本機 e2e(無 Docker)——載入 .env(含 pfrest 環境變數+管理端帳密)後啟動:
cd /opt/data/projects/dnspilot-server set -a; . ./.env; set +a .venv/bin/python -m app.cli run --host 0.0.0.0 # 預設 127.0.0.1:8090
Docker(部署):docker compose up -d --build(監聽 0.0.0.0:8090,反代 https 流量過來)。
管理端登入:/admin/ 以 .env 的 ADMIN_USERNAME/ADMIN_PASSWORD 帳密登入
(POST /api/admin/login,帳密走 JSON body;424 未設定 / 401 錯誤 / 403 撞名非 admin 用戶)。
首次登入會自動建立 admin 用戶,不需 bootstrap。
使用者前台 /portal/ 走 Google OAuth(本環境未設定 → 開發用 mock 登入)。
服務根路徑 / 是 dnspilot 產品介紹頁:不含任何管理端連結,唯一 CTA 導向使用者前台
/portal/。靜態檔不再整目錄公開(舊 /docs-static mount 已移除)。
未登入時顯示帳密登入框(無 session 或 session 非帳密登入時都會回到此框)。
密碼比對用 secrets.compare_digest(防 timing attack);失敗記 LOGIN fail 事件但不含帳密。
兩卡:① 全站統計(用戶/token/事件/config 版本);② pfSense 速覽(連線狀態、alias 數、uptime、CPU、記憶體、溫度)—— 本圖為真實設備 192.168.1.250 的即時數據。五個分頁:總覽 / 客戶 / 事件 / Config / pfSense。
token 狀態標記:有效 / 已輪轉 / 已撤銷。 「Token 上限」實現 item4 決策:一帳號先 1 個,後台可調(1–100);超額自動輪轉最舊。
事件類型:ADD_IP(client up)、HEARTBEAT、CONFIG_FETCH、
LOGIN、PFS_*(pfSense 操作稽核)。可按類型篩選;token 一律 redact(前 12 字元)。
流程:「+新草稿(以目前 current 為基底)」→ 編輯 JSON(dnspilot config v1 schema)→ 「發布」。發布前 = DRAFT 不生效;發布後所有 app 下個 poll 週期即改用(含 token 自動注入)。
上半部:連線狀態、alias 搜尋/選擇、該 alias 的 host 項目編輯(新增一行;留空=移除)。 下半部:pfSense 硬體/健康(平台、uptime、CPU 型號/使用率/load、記憶體/swap/mbuf、磁碟、溫度、BIOS)。
「預覽 diff」= apply=false:只回傳將要新增/移除/保留的項目,不改設備;
確認無誤再按「套用」(apply=true:PATCH 寫入+回讀驗證)。
改名功能(建新 alias→搬項目→刪舊,含規則引用檢查)在頁面下方。
使用者在此登入(Google OAuth 雙通道;本環境未設定 → 顯示提示+mock 登入)。 首次登入自動簽發一個 token,登入後即可見。
token 卡片:完整值(複製按鈕)、狀態、建立/最後使用/最後事件/最後 IP。 「+簽發新 token」受 max_tokens 限制(超出自動輪轉最舊)。 app 取得 token 兩路徑:① 自動領取(R1,app 端待實作)② 手動複製貼到 app 設定頁。
via claim,/api/admin/* 與 /admin/ 只認
via=admin 的 session——mock/Google 登入的帳號即使 role=ADMIN 也無法進管理端(舊版 admin@test.local
mock 登入繞道已封閉)。POST /api/admin/bootstrap(未驗證即可建 admin)已移除;帳密首次登入自動建 admin。{username, password}),不再出现在 query string(access log / 瀏覽器歷史)。/ 改 dnspilot 介紹頁(無 admin 連結);/docs-static 整目錄公開已移除。配套測試:新增 test_admin_login.py 8 項(成功/錯密/錯帳名/query 拒絕/424/mock-admin 擋住/撞名 403/bootstrap 移除);
全套 44/44 通過。另 /api/me 改回傳明確欄位(含 session_via),不再整列回傳 user row。
| 端點 | 用途 |
|---|---|
GET /v1/health | 健康檢查 |
GET /v1/addip?token= | client up(白名單註冊);非法 token→401+記 fail 事件 |
GET /v1/ping?token= | 心跳(無 token 也 200;合法 token 記 HEARTBEAT) |
GET /v1/ip | 對外 IP(純文字,可替 ifconfig.me) |
GET /v1/config?token=&since= | 領取 config(200/304;token 自動注入) |
GET/POST /api/tokens、POST /api/tokens/{id}/rotate|revoke | 使用者 token 管理 |
POST /api/admin/login(JSON body {username,password}) | 管理端帳密登入(424 未設定 / 401 錯誤 / 403 撞名非 admin) |
GET /api/admin/clients|events|stats | 管理查詢 |
POST /api/admin/configs(草稿)、.../{id}/publish(生效)、POST /api/admin/users/{id}/max-tokens | 管理動作 |
GET /api/admin/pfsense/status|system|aliases、POST /api/admin/pfsense/alias({name,hosts,apply})、POST /api/admin/pfsense/rename | pfSense 管理(未設定 env→424) |
GET /auth/google→/auth/google/callback(網頁)、GET /auth/google/app?id_token=(app 通道) | Google OAuth 雙通道 |
POST /auth/mock-login?email= | 開發/內網 mock 登入(僅 portal;管理端不認) |
--host 0.0.0.0 起於 8090,/v1/health 200。configured:true、1 個 alias;system 回真實硬體/健康數據;
alias 列表撈到 DNS_allow_domain(2 個 host 項目);dry-run 與設備一致(圖 9)。/api/admin/* → 401(B1 封閉驗證);撞名非 admin 用戶 → 403(B2 驗證)。已知限制:token 走 query param(v2 加 header);無 HMAC(靠 TLS); presence 留空;事件保留清檔任務待排程。 另:wsandroid.guguselect.com 目前反代指向 zpage,app 對接 /v1/* 前需改反代路由到本服務。