目錄
重點速讀 2 分鐘|18 分鐘深讀這篇先做一個新手能跑起來的最小版本:Telegram 收訊、Hermes 驗證身分、只允許白名單命令、把結果回傳手機。先不要讓 AI 直接碰任意 shell;等入口、紀錄和安全邊界都穩了,下一篇再接 Claude 規劃與 Codex 執行。
- 第一篇目標不是「完整自動化」,而是先做出安全入口。
- 使用 Telegram Bot API 的
getUpdateslong polling,不需要公開 webhook、HTTPS 或反向代理。 - Hermes 在這裡是控制層:驗證使用者、解析指令、建立紀錄、只放行安全命令。
- 本篇範例只支援
/ping、/whoami、/status、/run date、/run uptime、/run pwd。 - 先跑通這個版本,再把 Claude、ChatGPT Codex、多入口與完整隔離主機拆到後續文章。
- 這篇先做什麼,不做什麼
- 為什麼第一篇先用 Telegram
- 最小架構:Telegram 到 Hermes 到隔離主機
- 準備環境與 Bot token
- 建立 Hermes 專案資料夾
- 貼上最小版 Hermes 程式
- 第一次啟動與測試
- 安全底線:不要把聊天軟體接成 root shell
- 下一篇要接 Claude 和 Codex 時,這層要怎麼升級
這篇先做什麼,不做什麼
這個系列最後會走向「完整龍蝦機器人」:外部入口可以發任務,Hermes 負責調度,Claude 負責規劃與審查,ChatGPT Codex 負責在隔離主機裡讀檔、改檔、跑測試、回報結果。
但第一篇不要一開始就把所有東西接滿。新手最容易出事的地方,不是程式碼寫不出來,而是太早把聊天訊息接到高權限 shell,最後任何一句話都可能變成主機命令。
所以這篇只完成一件事:讓 Telegram 成為一個「受控入口」。你會得到一個 Hermes 小程式,它可以收 Telegram 訊息、檢查是不是允許的使用者、把每次執行寫入 jobs/ 紀錄,並只允許幾個安全命令。
| 這篇會做 | 這篇先不做 |
|---|---|
| Telegram Bot 收訊 | 公開 webhook 與 HTTPS 部署 |
| allowlist 身分驗證 | 多人 RBAC 權限系統 |
| 白名單命令 | 任意 shell / root 權限 |
| 任務紀錄與回報 | 真正呼叫 Claude / Codex |
| 單機最小可跑版 | Docker、VM、Kubernetes、systemd 深入部署 |
如果你只想知道能不能跑,答案是:可以。照著做完,手機傳 /run date,主機會回傳目前時間;傳 /status,主機會回傳任務紀錄數量。
為什麼第一篇先用 Telegram
Telegram 適合當第一篇入口,原因很務實:建立 Bot 快、手機好測、Bot API 是普通 HTTP 介面,而且官方同時支援 getUpdates 和 webhook。getUpdates 是 long polling,程式主動去 Telegram 伺服器拿更新;webhook 則需要 Telegram 對你的 HTTPS URL 發 POST。
新手第一版用 long polling 比較舒服,因為你不需要公開主機,也不需要先處理憑證、反向代理、防火牆和網域。缺點是正式多人服務不適合只靠這種單行程輪詢;但拿來建立最小可用架構,剛好。
如果你未來會把這台隔離主機接到交易所通知、行情或後台提醒,第一階段請只做「讀取與提醒」,不要一開始開自動下單。測試帳號可以從 OKX 或 Bybit 開始理解 API、KYC、商品型態與費率;但所在地限制、槓桿、下架規則和帳號安全,都要以官方最新資訊為準。本文不教自動交易,先把入口隔離做好。
最小架構:Telegram 到 Hermes 到隔離主機
這篇的流程只有五步:
- 你在 Telegram 傳訊息給 Bot。
- Hermes 用
getUpdates收到訊息。 - Hermes 檢查 Telegram user id 是否在 allowlist。
- Hermes 只接受白名單裡的命令,例如
date、uptime、pwd。 - Hermes 把 stdout、stderr、exit code 和時間寫成 job 紀錄,再把摘要傳回 Telegram。

這裡的 Hermes 不是神祕框架,而是一個你自己控制的中介層。它的價值在於:外部入口不直接碰主機權限,AI 也不直接拿到所有密鑰。之後要接 Claude 或 Codex,也是接到 Hermes 後面,不是讓 Telegram 直接呼叫高權限工具。
準備環境與 Bot token
你需要:
- 一台可以長時間開著的 macOS 或 Linux 主機。
- Python 3。
- Telegram 帳號。
- 一個新的 Telegram Bot token。
先在 Telegram 找 @BotFather:
- 傳
/newbot。 - 依照提示取 Bot 名稱與 username。
- BotFather 會回傳一串 token。
- 不要把 token 貼到群組、文章、GitHub 或截圖裡。
接著在主機建立工作資料夾。
mkdir -p hermes-bot/jobs hermes-bot/logs
cd hermes-bot
python3 -m venv .venv
source .venv/bin/activate
建立 .env:
touch .env
chmod 600 .env
先放 token,ALLOWED_TELEGRAM_USERS 可以先留空,等一下用 /whoami 查自己的 user id。
TELEGRAM_BOT_TOKEN=把_BotFather_給你的_token_貼在這裡
ALLOWED_TELEGRAM_USERS=
建立 Hermes 專案資料夾
最小版目錄長這樣:
hermes-bot/
hermes.py
.env
jobs/
logs/
jobs/ 會放每次任務的 JSON 紀錄。logs/ 先保留給之後常駐服務使用。這兩個資料夾看起來很普通,但它們是後續升級成真正任務佇列、審批流程、Codex 執行紀錄的起點。
這篇的白名單命令如下:
| Telegram 指令 | 主機實際執行 | 用途 | 風險 |
|---|---|---|---|
/ping |
不執行 shell | 確認 Bot 活著 | 低 |
/whoami |
不執行 shell | 回傳你的 Telegram user id | 低 |
/status |
不執行 shell | 查看 jobs 數量 | 低 |
/run date |
date |
測試主機時間 | 低 |
/run uptime |
uptime |
測試主機負載 | 低 |
/run pwd |
pwd |
確認執行位置 | 低 |
注意,這裡沒有 /run 任意內容。這是故意的。
貼上最小版 Hermes 程式
在 hermes-bot/ 裡建立 hermes.py,貼上下面這份程式。
#!/usr/bin/env python3
import datetime as dt
import json
import os
import pathlib
import subprocess
import time
import urllib.parse
import urllib.request
ROOT = pathlib.Path(__file__).resolve().parent
JOBS_DIR = ROOT / "jobs"
ENV_FILE = ROOT / ".env"
SAFE_COMMANDS = {
"date": ["date"],
"uptime": ["uptime"],
"pwd": ["pwd"],
}
def load_env():
if not ENV_FILE.exists():
return
for raw_line in ENV_FILE.read_text(encoding="utf-8").splitlines():
line = raw_line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
key, value = line.split("=", 1)
value = value.strip().strip('"').strip("'")
os.environ.setdefault(key.strip(), value)
def require_config():
token = os.environ.get("TELEGRAM_BOT_TOKEN", "").strip()
if not token:
raise SystemExit("Missing TELEGRAM_BOT_TOKEN in .env")
raw_users = os.environ.get("ALLOWED_TELEGRAM_USERS", "")
allowed_users = {
int(item.strip())
for item in raw_users.split(",")
if item.strip().isdigit()
}
return token, allowed_users
def telegram_api(token, method, payload):
url = f"https://api.telegram.org/bot{token}/{method}"
data = urllib.parse.urlencode(payload).encode("utf-8")
req = urllib.request.Request(url, data=data)
with urllib.request.urlopen(req, timeout=40) as response:
body = response.read().decode("utf-8")
result = json.loads(body)
if not result.get("ok"):
raise RuntimeError(result)
return result["result"]
def send_message(token, chat_id, text):
if len(text) > 3500:
text = text[:3500] + "\n\n[輸出太長,已截斷]"
telegram_api(token, "sendMessage", {
"chat_id": chat_id,
"text": text,
"disable_web_page_preview": "true",
})
def write_job(command_name, command, completed):
JOBS_DIR.mkdir(exist_ok=True)
job_id = dt.datetime.now(dt.timezone.utc).strftime("%Y%m%dT%H%M%SZ")
job_path = JOBS_DIR / f"{job_id}-{command_name}.json"
job_path.write_text(json.dumps({
"jobId": job_id,
"commandName": command_name,
"command": command,
"completedAt": dt.datetime.now(dt.timezone.utc).isoformat(),
**completed,
}, ensure_ascii=False, indent=2), encoding="utf-8")
return job_id
def run_safe_command(command_name):
command = SAFE_COMMANDS[command_name]
started = time.time()
try:
proc = subprocess.run(
command,
cwd=ROOT,
text=True,
capture_output=True,
timeout=10,
check=False,
)
completed = {
"exitCode": proc.returncode,
"stdout": proc.stdout.strip(),
"stderr": proc.stderr.strip(),
"durationSeconds": round(time.time() - started, 3),
}
except subprocess.TimeoutExpired as exc:
completed = {
"exitCode": None,
"stdout": exc.stdout or "",
"stderr": "Command timed out after 10 seconds",
"durationSeconds": round(time.time() - started, 3),
}
job_id = write_job(command_name, command, completed)
return job_id, completed
def help_text():
return (
"Hermes ready.\n\n"
"可用指令:\n"
"/whoami - 顯示你的 Telegram user id\n"
"/ping - 測試連線\n"
"/status - 查看任務紀錄\n"
"/run date - 顯示主機時間\n"
"/run uptime - 顯示主機負載\n"
"/run pwd - 顯示執行目錄"
)
def handle_message(token, allowed_users, message):
chat_id = message["chat"]["id"]
user = message.get("from", {})
user_id = user.get("id")
text = (message.get("text") or "").strip()
if text == "/whoami":
send_message(token, chat_id, f"你的 Telegram user id 是:{user_id}")
return
if not allowed_users:
send_message(token, chat_id, "尚未設定 ALLOWED_TELEGRAM_USERS;目前只回覆 /whoami。")
return
if user_id not in allowed_users:
send_message(token, chat_id, "拒絕:這個 Telegram user id 不在 allowlist。")
return
if text in ("/start", "/help"):
send_message(token, chat_id, help_text())
return
if text == "/ping":
send_message(token, chat_id, "pong")
return
if text == "/status":
count = len(list(JOBS_DIR.glob("*.json"))) if JOBS_DIR.exists() else 0
send_message(token, chat_id, f"Hermes online. jobs={count}")
return
if text.startswith("/run "):
command_name = text.removeprefix("/run ").strip()
if command_name not in SAFE_COMMANDS:
send_message(token, chat_id, f"拒絕:{command_name} 不在 SAFE_COMMANDS 白名單。")
return
job_id, completed = run_safe_command(command_name)
send_message(token, chat_id, (
f"job={job_id}\n"
f"command={command_name}\n"
f"exit={completed['exitCode']}\n\n"
f"stdout:\n{completed['stdout'] or '(empty)'}\n\n"
f"stderr:\n{completed['stderr'] or '(empty)'}"
))
return
send_message(token, chat_id, help_text())
def main():
load_env()
token, allowed_users = require_config()
offset = None
print("Hermes Telegram loop started. Press Ctrl+C to stop.")
while True:
updates = telegram_api(token, "getUpdates", {
"timeout": 25,
"offset": offset or "",
"allowed_updates": json.dumps(["message"]),
})
for update in updates:
offset = update["update_id"] + 1
message = update.get("message")
if message:
handle_message(token, allowed_users, message)
if __name__ == "__main__":
main()
第一次啟動與測試
先啟動 Hermes:
python3 hermes.py
Telegram 打開你剛建立的 Bot,傳:
/whoami
Hermes 會回傳你的 Telegram user id。把它填回 .env:
TELEGRAM_BOT_TOKEN=你的_token
ALLOWED_TELEGRAM_USERS=123456789
停止程式後重跑:
python3 hermes.py
接著在 Telegram 依序測:
/ping
/status
/run date
/run uptime
/run pwd
如果成功,你會看到主機回傳時間、負載與目前目錄;jobs/ 裡也會出現 JSON 紀錄。這代表最小入口已經完成:Telegram 可以下指令,Hermes 可以驗證、執行白名單命令、寫紀錄、回報結果。
安全底線:不要把聊天軟體接成 root shell
這篇最重要的不是程式碼,而是邊界。Telegram、LINE、Slack、Discord 都只是入口;入口不應該直接等於主機權限。
| 風險 | 錯誤做法 | 第一篇採用的做法 |
|---|---|---|
| 假使用者或陌生人傳訊息 | 所有人都能執行 | 用 Telegram user id allowlist |
| 任意命令注入 | /run rm -rf ... 也丟給 shell |
只允許 SAFE_COMMANDS |
| token 外洩 | token 寫進文章、截圖、repo | token 只放 .env,檔案權限 600 |
| 無法追蹤 | 執行完就消失 | 每次 /run 寫入 jobs/*.json |
| 權限過大 | 用 root 跑 Bot | 用普通使用者跑,後續再做更細隔離 |
這也是為什麼本篇沒有做「輸入什麼就執行什麼」。如果你要讓 AI 主機長期運作,白名單、審批、紀錄和可撤銷權限,比一開始看起來帥氣的全自動更重要。
下一篇要接 Claude 和 Codex 時,這層要怎麼升級
第一篇完成後,你已經有入口和任務紀錄。下一篇才適合把角色拆清楚:
- Claude:負責理解需求、拆任務、寫計畫、審查結果。
- ChatGPT Codex:負責在隔離工作區讀檔、改檔、跑測試、產出回報。
- Hermes:負責驗證、排隊、審批、紀錄、回傳,並決定哪些任務可以交給 Claude 或 Codex。
升級時,不要讓 Telegram 直接呼叫 Codex,也不要讓 Claude 直接拿到所有 shell 權限。比較穩的升級方式是新增幾個高階任務:
| 未來指令 | Hermes 應該做的事 | 是否需要人工確認 |
|---|---|---|
/plan 寫一篇文章 |
交給 Claude 產生計畫 | 低風險可不需要 |
/codex status |
查看 Codex 工作區狀態 | 通常不需要 |
/codex run <任務> |
建立 Codex 任務,而不是直接跑 shell | 需要 |
/approve <job> |
放行已審核任務 | 必須 |
/cancel <job> |
中止排隊或執行中的任務 | 視任務而定 |
到這裡就可以收工。第一篇的成果不是一個華麗機器人,而是一個乾淨、可解釋、可延伸的入口。這種骨架做好,後面要接 Claude、Codex、LINE、Slack、Web Console 或 API,都會穩很多。
延伸閱讀
- 第二篇:Claude、ChatGPT Codex、Hermes 分工
- 第三篇:AI 隔離主機安全設定:權限、密鑰、審批與 audit log 清單
- 第四篇:AI 主機入口怎麼選?Telegram、LINE、Slack、Web Console、API 優缺點比較
大家都在問
脆上快聊,群裡慢慢拆。
脆友問第一篇為什麼不用 webhook啊
脆上怎麼說看脆 因為 webhook 需要公開 HTTPS URL、憑證、路由和重送處理。新手第一版用 getUpdates long polling 比較少環境問題,也不需要把隔離主機直接暴露到網路上。
剛入坑群友問這個 Hermes 可以直接拿來跑 Codex 嗎?
老群友答 不建議直接改成任意 Codex shell。比較安全的方式是讓 Hermes 建立任務檔,人工或下一層 runner 再決定是否交給 Codex。危險任務要有 /approve。
剛入坑群友問Telegram user id 算安全登入嗎?
老群友答 它只是一層基本 allowlist,不等於完整登入系統。正式環境還要加上密鑰輪替、審批、速率限制、平台簽章或 webhook secret、操作日誌與權限分級。
剛入坑群友問可以把 Bot 加到群組嗎?
老群友答 第一版不建議。先用私人對話測通,等你有群組權限規則、指令前綴、審批流程和日誌後,再考慮放進群組。
脆友問所以為什麼不用現成 Bot framework啊
脆上怎麼說看脆 可以用,但第一篇刻意用 Python 標準函式庫,讓讀者看懂 Bot API、輪詢、allowlist、白名單命令和任務紀錄。理解骨架後,再換 framework 會更穩。
資料查證:2026-06-13。本文參考 Telegram Bot API 官方文件、Python venv 官方文件、OpenAI Codex 官方產品與開發者文件、Anthropic Claude 產品資料;實際 API、產品功能、平台限制與安全建議仍以官方最新資訊為準。
延伸閱讀
- 第二篇:Claude、ChatGPT Codex、Hermes 分工
- 第三篇:AI 隔離主機安全設定:權限、密鑰、審批與 audit log 清單
- 第四篇:AI 主機入口怎麼選?Telegram、LINE、Slack、Web Console、API 優缺點比較
- 懶人包:AI 隔離主機懶人包,Telegram、Hermes、Claude、Codex 快速搭起來