OpDeck

era: agent
created_at: 2026/10/12 05:00:00

cover image

一個自架的開發者指令中心,同時也是我自己的密碼管理器:在任何地方按下 Ctrl+K 叫出指令面板(Command Palette),找 secret、跑指令,都在同一個輸入框裡完成,也可以整合 chrome extension 來填密碼。

簡單說就像是 Flow Launcher + 1Password 的合體,再加上一些魔改(?)

目前尚未開源。現階段以提高我自己的開發與使用體驗為主,等未來把權限控管整合好之後再開源。


為什麼會有這個服務

兩個理由,剛好對應到它的兩半。

專案的 secrets 散落各處。 手上的專案越來越多之後,每個專案都有自己的一堆 secrets、環境設定、後台網址、常用的指令,散落在各個地方,換一台電腦或開一個新的 Dev Container 就要重新找一次、貼一次。

自己的密碼也快記不住了。 我一直不想在每個地方都記不同的密碼,以前服務不多的時候,是用生活中的關聯在腦袋裡做 hash,每個平台算出一組不一樣的密碼;服務一多就開始擔心哪天算不出來,也怕這套規則被看穿,一個破了就全破。

既然兩種東西本質上都是「要加密存放、要能分享給對的人、要在需要時一鍵拿到」,就乾脆做成同一個工具:一個自己架的地方統一放,桌面上按個快捷鍵就能叫出來用,瀏覽器登入時自動填。


Demo

影片有旁白,大概一分二十秒,想跳著看的話:

時間內容
0:00面板:設定好的網址一按就開、上下鍵選取,也能開資料夾或 IDE
0:16在測試網站上,擴充功能跳出配對提示,到桌面程式核准,配對完成
0:32擴充功能產生隨機密碼 → 註冊 → 送出後 OpDeck 主動問要不要存 → 取個名字存起來
0:52下次登入點一下帳號欄,選剛存的帳密,自動填入
1:04HTTP 來源 json-todo:輸入不同的 id 即時查詢,只取出事先用 JSON Path 篩好的欄位,Enter 複製

功能說明

由三個部分組成:桌面的指令面板、網頁後台、瀏覽器擴充功能。

架構圖

金鑰只存在桌面程式這一層:網頁後台拿不到金鑰,所以敏感值只能寫、不能讀;擴充功能也沒有金鑰、也不直接連 Server,要查詢或解密都透過本機的 Native Messaging 請桌面程式代勞;敏感值在 Server 上從頭到尾只有密文。

Secrets 列表

指令面板

  • 直接打字就會同時搜尋 secrets、指令與來源,常用的會排在前面
  • Enter 複製選到的 secret,Ctrl+Enter 則是原地偷看一眼而不複製
  • 指令有幾種類型,在後台用表單設定:
    • 開啟網址
    • 開啟資料夾
    • 用 VS Code 開啟某個路徑
    • 呼叫一支 HTTP API,把回應裡的某個值複製出來
  • HTTP 指令可以帶參數,執行時面板會一步一步問(例如要哪個環境);開網址、開資料夾這類指令則可以設定多個目標,執行時用子選單選
  • 指令裡可以放「裝置變數」,同一個指令在不同電腦上會代入各自的路徑或 token,而這些變數只存在那台裝置上
  • 指令可以分組,一層一層點進去

按下 Ctrl+K 之後發生的事

指令列表,依群組分段

HTTP 指令的設定表單:參數、裝置變數、擷取規則

  • 產生隨機密碼
  • 匯入 .env:在網頁後台貼上內容,逐筆加密後才上傳;之後可以重新匯入同步

登入項目與瀏覽器擴充功能

這一半就是密碼管理器。

  • 除了 key/value 的 secret,另外有「登入項目」:網址、帳號、密碼、TOTP 種子
  • 擴充功能在登入頁偵測到表單時,會問要不要存;之後到同一個網站就能自動填入,TOTP 的驗證碼也直接從面板複製
  • 新密碼由面板或擴充功能產生,每個網站一組隨機的,不用再自己想規則
  • 登入項目預設是個人的,可以自願分享給組織;分享和取消分享可以在面板裡完成

自動填入的流程:擴充功能只負責問與填,解密在桌面程式

共享的登入項目,依網域分組

網頁後台

  • 管理 secrets、來源、指令、登入項目、裝置
  • 組織與成員
  • 稽核紀錄
  • 本地帳號可以開啟 TOTP 兩步驟驗證
  • 也可以用 GitHub 登入:網頁走一般的 Authorization Code Flow;桌面程式沒有 callback URL 可以收,所以走 OAuth Device Flow,畫面上顯示一組 code、到瀏覽器按同意就好,client secret 從頭到尾只在 Server

GitHub 登入:桌面程式走 Device Flow


Zero Knowledge

這個服務最核心的設計是:伺服器不會拿到明文,也沒有解密的金鑰。

Client 加密 -> 上傳密文 -> Server 只存密文與 metadata
    -> 有權限的 Client 下載 -> Client 本地解密
  • 每個 secret 用自己的一把隨機金鑰加密,這把金鑰再用每一台有權限的裝置的公鑰分別包一次
  • 裝置的私鑰在裝置上產生,放在作業系統提供的安全儲存區,從頭到尾沒有離開過那台裝置
  • 所以就算資料庫整個被拿走,拿到的也只有密文

一個 secret 從寫入到讀取

這個設計也帶來一些連鎖的限制,而這些限制都沒有為了方便而放寬:

  • 網頁後台只能寫、不能讀:瀏覽器沒有安全的地方放私鑰,所以後台可以新增敏感的 secret,但看不到它的值(標成非敏感的 Plain 類型除外,那種本來就是明文)
  • 擴充功能自己不解密:它透過 Native Messaging 請正在執行的桌面程式解密再填入,本身不持有任何金鑰
  • 新裝置要先被核准,再等一台已授權的裝置上線:新裝置註冊後是待核准狀態,要在另一台已核准的桌面程式上比對指紋、按下核准;之後也得等一台持有金鑰的裝置上線替它重新包一份,因為伺服器沒辦法代替你重新加密
  • 裝置全部遺失就救不回來:所以另外提供了可選的紙本復原金鑰,只顯示一次,不會存在任何地方

組成

  • 後端:Go(Gin + GORM + FX)、PostgreSQL
  • 網頁後台:SvelteKit + Svelte 5 + Tailwind
  • 桌面端:Tauri 2(Rust),支援 Windows 與 macOS
  • 瀏覽器擴充功能:Chromium MV3
  • 部署:Docker Compose