SSD Nodes Learn 🎉 VPS $5.50/月起
指南 Matt Connor作者: Matt Connor · 已更新 2026-08-13

如何自架 OpenTag 處理 Slack 與 GitHub 提及

在 VPS 上部署 OpenTag 以串接程式設計 Agent。本文說明 TLS Ingress 設定、Webhook 簽章驗證、Token 權限範圍及 v0.9.0 版本的安全執行配置,確保 GitHub 事件能正確觸發本機代理程式。

當您提及 Agent 時 OpenTag 的運作方式

OpenTag 會將 Slack 討論串或 GitHub Issue 中的 @mention 轉化為在您自有機器上執行的程式設計 Agent。當有人在 Issue 上留言 @opentag investigate this 時,監聽器會接收平台事件、檢查簽章、將該提及對應至已綁定的專案,並針對本機的程式碼檢出(checkout)啟動程式設計 Agent,最後將結果回覆至同一個討論串中。

此專案採用 MIT 授權,位於 amplifthq/opentag。截至 2026 年 8 月,最新的標記版本為 v0.9.0,於 2026 年 7 月 28 日發布,並以 npm 套件形式發行。目前沒有官方容器映像檔,因此您鎖定的是 npm 版本。下方所有指令皆會鎖定該版本。

由於 GitHub 的運作機制,這會成為一個 VPS 專案而非筆電專案。GitHub 是透過向您註冊的 URL 發送 HTTP 請求來傳遞儲存庫事件,因此該 URL 必須在未來始終維持在同一個位址。

四個運作組件

監聽器 (The listener) 負責接收平台事件,每個平台各有其機制。GitHub 監聽器是一個位於 3050 埠、路徑為 /github/webhooks 的 HTTP 端點。Slack Events API 監聽器則位於 3040 埠的 /slack/events。Slack 亦可透過 Socket Mode 執行,此時應用程式會開啟對外 WebSocket,無需任何對內連接埠。

調度器 (The dispatcher) 是協調中心。預設監聽 3030 埠,透過 OPENTAG_DATABASE_PATH 設定的本機資料庫檔案保存執行狀態,並記錄每次執行的稽核軌跡。外部網路不應存取此連接埠。

執行者 (The runner) 是本機 daemon。它會輪詢工作、領取執行任務、持有租約,並在任務存續期間預設每 15 秒發送一次心跳訊號。若領取的任務其專案目標不存在,或不在其設定檔的允許清單內,它會拒絕執行;此檢查機制可防止 GitHub 事件將您的代理程式導向未綁定的儲存庫。

執行單元 (The executor) 是程式碼代理程式本身。OpenTag 透過 ACP (agent client protocol) 啟動它,這是一種透過標準輸入與輸出傳輸的 JSON-RPC 協定,因此代理程式會作為 OpenTag 所指定工作目錄下的子處理程序執行。內建名稱包含 echocodexclaude-codecursoropencodehermesopenclaw。請從 echo 開始,這是範例設定檔內建的執行單元,在模型存取您的程式碼前,它能驗證整個路徑是否運作正常。

處理順序固定不變:平台事件、簽章檢查、執行記錄、領取任務、代理程式執行、執行緒回覆。

為什麼筆記型電腦搭配通道是不夠的

GitHub 設定指南要求您執行 ngrok http 3050 並將通道主機名稱貼至儲存庫的 webhook 中。這在最初的 10 分鐘內運作正常。免費的通道主機名稱會在每次程序重啟時變更,且當筆記型電腦進入睡眠時,該通道便會失效。GitHub 會保留舊的 payload URL 並持續嘗試連線,導致 webhook 設定中的 Recent Deliveries 分頁充滿失敗紀錄,而執行緒卻毫無反應。由於一個沒有作用的 webhook 看起來就像是沒人提到的機器人,因此通常一週內都不會有人發現。

VPS 可以解決這兩個導致中斷的問題。DNS 名稱不會變更,因此您只需貼上一次 payload URL 即可長期有效。機器不會進入睡眠,因此即使在 02:00 收到評論也能獲得回應。請先正確設定伺服器:新 VPS 的前 10 分鐘 涵蓋了本指南預設的登入使用者與防火牆設定。

Slack 是個例外。在 Socket Mode 下,它會向外連線且不需要公開 URL,因此僅使用 Slack 的部署可以保持封閉。GitHub 沒有對應的功能。儲存庫的 webhook 是入站 HTTP,這意味著需要公開端點,進而需要 TLS (transport layer security) 與簽章檢查。

從指定版本安裝 Ubuntu 上的 OpenTag 自架服務

OpenTag v0.9.0 需要 Node.js 22 或更新版本。Ubuntu 24.04 的預設儲存庫僅提供 Node 18,因此請從 NodeSource 安裝。

curl -fsSL https://deb.nodesource.com/setup_22.x -o nodesource_setup.sh
sudo -E bash nodesource_setup.sh
sudo apt install -y nodejs
node -v

node -v 必須顯示 v22 或更高版本。若使用 Node 20,安裝過程會顯示 EBADENGINE 警告,且 CLI 啟動後可能會失敗。

請為該服務建立專屬帳號。代理程式會以該使用者的權限執行,因此不應使用您的登入帳號,更不應使用 root。VPS 上的最小權限使用者 一文說明了為何這種隔離措施值得多花這些步驟。

sudo adduser --disabled-password --gecos "" opentag
sudo loginctl enable-linger opentag
sudo npm install -g @opentag/cli@0.9.0
command -v opentag

command -v opentag 應顯示類似 /usr/bin/opentag 的路徑。在 Linux 上,linger 設定至關重要:OpenTag 透過 systemd 安裝背景服務,若未啟用 linger,當您的 SSH 連線關閉時,使用者服務也會隨之停止。

請以該使用者身分執行安裝程式。

sudo -iu opentag opentag setup

安裝程式會詢問六個項目:CLI 語言、本機監聽位址、程式碼代理程式、要處理的本機專案、要儲存的平台憑證,以及執行模式。請將監聽位址保留為 127.0.0.1,因為 Nginx 會處理 TLS termination 並轉發至該位址,因此監聽器無須從外部直接存取。針對 GitHub,它還會詢問 owner/repo 格式的儲存庫名稱、是否允許開啟 pull requests、webhook 埠號(預設為 3050)以及權杖。最後請選擇背景服務模式。如果您已有設定檔並希望在無互動的情況下安裝服務,可使用 opentag setup --service

設定檔會存放在 /home/opentag/.config/opentag/config.json,執行階段狀態則存放在 /home/opentag/.local/state/opentag。安裝程式寫入檔案後,建議手動檢查這些鍵值。

{
  "runnerId": "runner_local",
  "dispatcherUrl": "http://localhost:3030",
  "runnerToken": "...",
  "approvalMode": "ask",
  "repositories": []
}

建議優先使用 runnerToken(執行器範圍的 bearer token),而非舊版的共用 pairingToken。除非您將憑證替換為 secret reference(在啟動時從環境變數或磁碟檔案讀取值),否則設定檔會以純文字儲存憑證。無論採用哪種方式,此檔案都是系統中最敏感的資料:請確保權限為 600,擁有者為 opentag,且絕對不要將其放入 git 儲存庫中。關於此議題的詳細論述,請參閱 如何保護 AI 代理程式的機密資訊

在對外開放服務前,請先檢查安裝狀態。

sudo -iu opentag opentag doctor
sudo -iu opentag opentag status

opentag doctor 可檢查派送器 (dispatcher)、綁定 (bindings)、簽出 (checkouts) 與執行器 (executors)。opentag status 會列印設定與執行階段狀態,若已有執行紀錄,亦可指定單次執行範圍。在將平台指向此伺服器前,請務必修復 doctor 報告的所有問題。

將 TLS 置於前端並僅開放兩條路徑

Nginx 負責終止 TLS 並僅轉發兩條路徑。其餘所有請求均回傳 404,因此掃描器即使發現主機,也無法得知後端運行的服務。

請在 /etc/nginx/sites-available/opentag 建立一個純 HTTP 80 埠的 server 區塊,並包含下方兩條 location,隨後讓 Certbot 加入 TLS 設定。

sudo apt install -y nginx certbot python3-certbot-nginx
sudo ln -s /etc/nginx/sites-available/opentag /etc/nginx/sites-enabled/opentag
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d opentag.example.com

nginx -t 會輸出 syntax is oktest is successful,這是防止輸入錯誤導致重載後網站中斷的唯一防線。Ubuntu 24.04 上使用 Nginx 搭配 Certbot 涵蓋了憑證續期以及 ACME (自動憑證管理環境) 驗證失敗的各種原因。最終的區塊設定如下。

server {
    listen 443 ssl;
    server_name opentag.example.com;

    ssl_certificate     /etc/letsencrypt/live/opentag.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/opentag.example.com/privkey.pem;

    client_max_body_size 2m;

    location = /github/webhooks {
        proxy_pass http://127.0.0.1:3050;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
    }

    location = /slack/events {
        proxy_pass http://127.0.0.1:3040;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
    }

    location / {
        return 404;
    }
}

location = /github/webhooks 中的 = 代表精確匹配,而 proxy_pass 後面未接任何路徑,會將原始 URI 原封不動地傳遞。若移除 =,則 /github/webhooks/ 下的所有路徑都會被轉發,這會導致暴露過多不必要的攻擊面。

防火牆設定應保持嚴格。

sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw status

連接埠 3030、3040 與 3050 絕不應對外開放。請確認它們僅綁定在 loopback 介面,而非所有介面。

sudo ss -tlnp

每一行 OpenTag 應顯示為 127.0.0.1:3030 或類似內容。若顯示為 0.0.0.0:3050,代表監聽程式正向整個網際網路開放,僅靠 ufw 阻擋;一旦防火牆設定失誤,即可能導致代理程式被觸發。ufw 防火牆基礎 解釋了預設拒絕規則的實際作用。

透過兩項檢查可驗證前端入口。curl -I https://opentag.example.com/ 應從 Nginx 回傳 404,這代表憑證有效且預設的 catch-all 已關閉。任何未帶簽章且指向 /slack/events/github/webhooks 的請求,絕不應回傳 200。

驗證每一項簽章,因為 URL 是公開的

任何人都能找到 payload URL。它存在於您的儲存庫設定、瀏覽器歷史記錄,或是貼在工單裡的截圖中。簽章是區分真實 GitHub 傳送與手動輸入請求的唯一依據。

GitHub 會使用 webhook secret 對每次傳送進行簽章,並將結果放入 x-hub-signature-256 標頭中。OpenTag 會根據 platforms.github.webhookSecret 驗證該標頭。專案的強化說明文件直接規定:請勿在 /github/webhooks 上接收未簽章的來源事件。Slack 會使用 SLACK_SIGNING_SECRET 對每個請求進行簽章並包含時間戳記,因此無法在數小時後重放已擷取的內容。

忽略此步驟風險極大。未經驗證的端點會接收手寫的 issue_comment payload,其中包含 @opentag,接著 OpenTag 會在您的 checkout 中,使用您的 token,根據陌生人的指令執行程式碼代理程式。回覆則會發送到偽造 payload 所指定的任何執行緒。

OpenTag 在此之上增加了兩層防護。來源傳送會透過 delivery ID 進行追蹤,因此重新傳送相同的事件不會觸發第二次執行。Runner 呼叫接受冪等性鍵值(idempotency keys),因此重放請求會回傳成功,而不會附加額外的稽核事件。

速率限制可進行設定且應保持開啟。OPENTAG_RATE_LIMIT_WINDOW_MSOPENTAG_RATE_LIMIT_MAX_REQUESTS 用於限制請求速率,OPENTAG_MAX_REQUEST_BODY_BYTES 用於限制內容大小,過大的 payload 會被 413 request_body_too_large 拒絕。OPENTAG_RATE_LIMIT_DISABLED=true 僅供本地開發使用,不應出現在公開伺服器上。同一份說明文件中的另一項規則:公開轉發 URL 必須使用 HTTPS,而 CLI 僅允許在 localhost 使用純 HTTP。

機器人實際上需要哪些 Token 權限範圍(Scopes)?

在 GitHub 上,OpenTag 使用細粒度個人存取權杖(fine-grained personal access token)而非 GitHub App。文件指出 App 路徑尚在規劃中,並非目前 CLI 的預設設定,這導致了一個常被忽略的後果:機器人會以建立該權杖的使用者身份進行留言。請務必使用一個您願意讓其名稱出現在所有分類回覆中的帳號來建立權杖。

權限範圍請嚴格依照設定指南進行限制。選擇 Only select repositories 並指定單一儲存庫。授予 Issues: Read and write 以及 Pull requests: Read and write 權限。這已足以讀取提及內容並在討論串中回覆。

請注意缺少的部分:程式碼的寫入權限。除非將 preparePullRequestBranch 設為 true,否則 OpenTag 不會推送分支;此外,系統設有獨立的 githubApplyToken,確保負責寫入程式碼的權杖與負責留言的權杖分開。請將兩者分開管理,並在讀取與留言功能穩定運作數週前,保持寫入權杖處於停用狀態。

應避免的設定是授予 All repositoriesContents: Read and write 權限。如此一來,任何能在這些儲存庫留言的人,都能操控擁有提交權限的代理程式,且稽核紀錄會顯示該操作是由權杖擁有者所執行。請在代理程式通過驗證後,逐一擴大儲存庫的權限範圍。

在 Slack 上,機器人的權限範圍為 app_mentions:readchat:writereactions:writechannels:history。私有頻道還需要 groups:history 以及訂閱 message.groups 事件。Socket Mode 需要一個具備 connections:write 的應用程式層級權杖(即以 xapp- 開頭的那個)。channels:history 用於讀取機器人已加入之公開頻道的訊息歷史,因此請將機器人加入至需要的頻道,而非加入所有頻道。

端對端追蹤單一問題

首先處理 Webhook。在儲存庫中開啟 Settings,接著點選 Webhooks,再選擇 Add webhook。Payload URL 設為 https://opentag.example.com/github/webhooks,Content type 設為 application/json,Secret 則填入設定過程中產生的金鑰。僅訂閱 Issue commentsPull request review comments,其餘項目無需勾選。

儲存後 GitHub 會立即發送一次 ping 請求。請開啟 Recent Deliveries 確認請求是否已送達伺服器。若收到 502 錯誤,代表 nginx 無法連線至監聽服務,這是本地端問題,與 GitHub 無關。

現在開始測試。開啟一個描述錯誤的 issue 並留言:

@opentag triage this. Reproduce the report against the current main branch, then reply with the file and function most likely responsible, plus the test you would write first.

預期的執行順序如下:Recent Deliveries 應記錄到 issue_comment 請求並回傳 2xx 狀態碼。Dispatcher 會記錄一次執行任務,Runner 領取任務並開始發送心跳訊號,Executor 執行 checkout 並開始運作。最終回應會以留言形式出現在該 issue 討論串中。執行期間可透過 sudo -iu opentag opentag status 查看任務狀態,無須盲目猜測。

在首次正式執行前,請將 approvalMode 設為 ask。在 ask 模式下,任務會在執行任何變更狀態的操作前暫停並等待人工確認。後續若已閱讀過一個月的執行紀錄,可考慮使用 autoautonomous 模式,這些模式在成熟的儲存庫中相當實用。

在 Slack 端,相同的任務會以 /bind owner/repo 在頻道中開始,隨後會出現提及(mention)。機器人也會回應 /help/status/doctor/stop/unbind confirm。請使用 OPENTAG_SLACK_BINDING_ADMIN_USER_IDS 限制可變更綁定的人員,此設定為以逗號分隔的 Slack 使用者 ID 列表,因為綁定代表將公開頻道對應至伺服器上的特定 checkout。

Triage(分類)是很好的入門路徑,因為它僅讀取而不寫入,且回應結果容易評估。下一步是 Review(審查),代理程式會針對 diff 而非 issue 進行留言:自架 Pull request 審查代理程式 即是將此架構應用於 pull requests。若希望代理程式在運作時能存取您的系統,請參考 VPS 上的 MCP 伺服器。網頁搜尋是 Triage 經常需要的另一項功能,透過 將代理程式連接至自架 SearXNG 實例,可將查詢保留在您自有的硬體上,代價是增加了一個讓外部文字進入代理程式的管道。

當代理程式在眾人面前出錯時會發生什麼事?

它就是會出錯。問題在於代價為何。

在公開議題上的錯誤回覆,會以貴團隊成員熟悉的名稱發布評論,且 GitHub 會在發布當下將其寄送給所有訂閱者。刪除該評論無法撤回已寄出的郵件。Slack 通知亦然。請預設該回答在公開場合會出錯,而非預期它在私下能完全正確。

以下四種選擇能限制損害,它們比您撰寫的任何提示詞(prompt)都更重要。

  • ask 模式執行,讓代理程式提出建議,由人員進行核准,如此一來錯誤的計畫僅需一次點擊即可修正。
  • preparePullRequestBranch 保持為預設的 false,確保錯誤執行的最壞結果僅是錯誤的評論,而非錯誤的分支。
  • 起步時僅綁定一個儲存庫與一個頻道。執行器會拒絕任何專案目標位於其本地允許清單之外的執行請求,因此未綁定的儲存庫無法將代理程式引入其中。
  • 將評論權限的 token 與執行權限的 token 分開,這樣撤銷寫入權限時,不會連帶影響分類(triage)功能。

Slack 針對執行中的錯誤流程提供了 /stop 指令。每次執行都會留下稽核紀錄,包含啟動該任務的提及內容以及代理程式的動作,這正是您事後用來釐清錯誤發生的依據。

社交層面的影響與設定同樣重要。請將機器人放置在一個人們預期它是機器、且知道它可能會出錯的頻道中。在一個有四十人且假設內容已由人類審核的頻道中,一個自信滿滿的錯誤回答,其代價遠高於分類工作所節省的時間。請在頻道說明中寫明誰負責該機器人,以及誰負責檢查其輸出內容。

備份、升級與版本鎖定

所有資料皆存放於兩個路徑:/home/opentag/.config/opentag/config.json/home/opentag/.local/state/opentag。前者包含您的憑證,後者則存放執行紀錄與資料庫檔案。請務必備份這兩處,並將權限設為 600,且務必存放在伺服器外部。遺失這些檔案僅需重新建立 Token 與綁定,無需重建伺服器。

升級僅需提升版本號並重新啟動服務。

sudo npm install -g @opentag/cli@0.9.0
sudo -iu opentag opentag service stop
sudo -iu opentag opentag service start
sudo -iu opentag opentag doctor

請鎖定版本,而非追蹤 @latest。此軟體會使用即時 Token 對您的儲存庫執行編碼代理(coding agent),因此若在夜間發布新版本,即代表未經審查的變更已套用。安全性政策不會進行舊版本回溯修補,修正僅會出現在最新版本中,因此鎖定版本意味著您需閱讀變更日誌並主動進行更新。這並不代表要永久停留在 v0.9.0。截至 2026 年 7 月的歷史紀錄顯示,每月會發布多次更新,這正是每次升級前應閱讀發布說明的充分理由。

FAQ

執行 OpenTag 一定需要 VPS 嗎?還是筆記型電腦就夠了?

若僅使用 Slack,筆記型電腦已足夠,因為 Socket Mode 會開啟對外 WebSocket 連線,無需開放任何入口埠。但 GitHub 的情況不同。儲存庫 Webhook 是透過入口 HTTP 傳送到您註冊的 URL,因此該位址必須保持不變,且在您離線時仍能回應。免費帳號提供的隧道主機位址會在每次重啟後變更,導致 GitHub 持續向舊位址發送請求,這會在儲存庫的 Recent Deliveries 分頁中顯示為失敗,且執行緒中不會有任何回應。使用具備固定 DNS 名稱與憑證的 VPS 可同時解決這兩個問題。

OpenTag 需要哪些 GitHub 權限?

需要一個細粒度的個人存取權杖(Personal Access Token),並限制為 Only select repositories,且具備 Issues: Read and writePull requests: Read and write 權限。這足以讀取提及內容並在執行緒中回覆。除非您將 preparePullRequestBranch 設為 true 以允許 OpenTag 推送分支,否則不需要程式碼的寫入權限;此外,系統提供獨立的 githubApplyToken 設定,可將程式碼寫入權杖與留言權杖分開。請避免使用具備 contents write 權限且涵蓋所有儲存庫的權杖,因為任何能在這些儲存庫留言的人,都可能藉此操控具備提交權限的代理程式。

如何停止執行錯誤的任務?

Slack 提供了 /stop 指令專門處理此情況。在伺服器端,opentag status 可顯示目前執行中的任務,而 opentag service stop 可停止 daemon,這會終止整個管線而非單一任務。為避免需要上述操作,請將 approvalMode 設為 ask,讓任務在變更任何內容前先暫停等待人工確認,並將 preparePullRequestBranch 保持為 false,確保錯誤的任務僅會產生留言而非建立分支。

為什麼我的 Webhook 回傳 502,但執行緒卻沒有反應?

502 錯誤來自 nginx 而非 OpenTag,這代表代理伺服器無法連線至監聽程式。/var/log/nginx/error.log 將顯示 connect() failed (111: Connection refused) while connecting to upstream。這通常是因為監聽程式已停止,或是其監聽的埠與 proxy_pass 設定行指定的埠不符。請執行 sudo ss -tlnp 並確認 GitHub 使用的 127.0.0.1:3050 以及 Slack 使用的 127.0.0.1:3040 埠皆有程式在監聽,接著執行 opentag doctor 檢查綁定與執行器狀態。

#opentag#ai-agents#slack#github#webhooks#self-hosting