[實作筆記] 個人自動化平台(五) n8n 實作:處理層,Aggregate + Gemini 週報整理

前情提要

收集層跑通後,每次執行輸出統一格式的文章列表。

這篇記錄處理層:把多筆文章合併,送給 Gemini 整理成 AI 週報。

未來也可能抽換成不同的雲端或地端 AI 模型服務。


為什麼要 Aggregate?

收集層輸出的是多筆獨立資料(以 RSS 為例,一次可能拿到 10 筆)。

Basic LLM Chain 預設對每筆各送一次請求,10 筆 = 10 次 API call,超過 Gemini 免費版 5 RPM 限制。

Aggregate 節點先把多筆合成一筆,再送一次請求給 AI。這也更符合「週報」的本意——要的是一份完整報告,不是 10 篇獨立摘要。

1
Edit Fields → Aggregate(All Item Data)→ Basic LLM Chain(Google Gemini)

Prompt 設計

1
2
3
4
5
6
週報日期:{{ $now.toFormat('yyyy年MM月dd日') }}

以下是本週 AI 新聞,請用繁體中文整理成一份週報。
每則新聞說明「是什麼」和「為什麼重要」,條列呈現。

{{ $json.data.map(i => `【${i.source}】${i.title}\n${i.content}`).join('\n\n') }}

執行結果:日期正確,內容有條理,每篇清楚說明「是什麼」和「為什麼重要」。


踩坑:Rate Limit

第一次直接把 10 筆送給 LLM,遇到 429 Too Many Requests

1
quota exceeded: limit 5 requests/minute (gemini-2.5-flash free tier)

解法:加 Aggregate 節點合併成一筆,從 10 次請求降為 1 次,問題消失。


參考

小結

  • Aggregate 節點把多筆合成一筆,是處理層的關鍵,解決 rate limit 也符合週報語意
  • Prompt 用 expression 動態帶入日期與新聞內容,輸出穩定

(fin)

[實作筆記] 個人自動化平台(六) n8n 實作:輸出層,Instagram API 取得 Token 全記錄

前情提要

收集層(RSS 抓資料)和處理層(Gemini 整理週報)都跑通了,接下來要實作輸出層:把 AI 整理好的內容自動發布到社群媒體。

這篇記錄從「選哪個平台」到「拿到 Instagram Long-lived Token」的完整過程,包含踩到的坑和解法。


平台選擇

想把 AI 週報自動發布到社群媒體,先評估三個選項:

平台 純文字 API 帳號門檻 難度
Instagram 不行(必須圖片) Business 或 Creator
YouTube 影片 不行(必須影片) 普通 Google 帳號 最高
YouTube Community 可以 500+ 訂閱
LinkedIn 可以 有(需審查) 需要 Page
Telegram 公開頻道 可以 最簡單

最終決定:Instagram。不是因為它最簡單——Telegram 才是——而是因為這是我實際在用的平台,想把週報發在自己的帳號上。但 IG 必須有圖片,輸出層要加圖片生成步驟。


IG 帳號準備

Instagram API 發文需要 Business 或 Creator 帳號。個人帳號的 Basic Display API 已於 2024 年 12 月關閉。

切換 Creator 帳號(免費,不需要 Facebook Page):

1
IG App → 設定和隱私 → 帳號類型和工具 → 切換為專業帳號 → 創作者

切換後保留所有粉絲和貼文,只是解鎖 API 權限。


Facebook Developer App 設定

App 類型要選對

建 App 時類型有坑:

  • Consumer:給舊版 Basic Display API 用,已關閉,選了找不到 Instagram Graph API
  • Business:正確選項,支援 Instagram Graph API content publishing

前往 developers.facebook.com/apps/create,選 Business

Instagram API 流程選擇

進 App 後找 Instagram 產品,有兩種 API 流程:

Instagram Login Facebook Login
支援 Creator 帳號 需要連 FB Page
需要 Facebook Page 不需要 需要

API setup with Instagram business login(Instagram Login 流程)。

加 Tester 帳號

Step 1 要先在 Roles 頁籤加 Instagram Tester:

  1. 左側選單 → App Roles → Instagram Testers
  2. 輸入 IG username 送出邀請
  3. 在畫面下方找到連結,點開到 https://www.instagram.com/accounts/manage_access/ 接受邀請

踩雷筆記:OAuth 黑/白畫面 bug

Generate token 後,Instagram 授權頁面出現黑畫面或白畫面,token 無法顯示。

這是 Instagram Developer Console 已知的 rendering bug。OAuth 授權其實已完成,authorization code 在 redirect URL 裡,只是 UI 沒渲染出來。

解法:webhook.site 當臨時 Redirect URI

步驟一:前往 webhook.site,取得唯一 URL,例如:

1
https://webhook.site/98ec2192-c62e-459e-848a-334b370887f0

步驟二:加到 Instagram App → Step 3 → OAuth Redirect URLs。

步驟三:手動打開授權 URL(替換 YOUR_WEBHOOK_URL):

1
https://www.instagram.com/oauth/authorize?client_id=APP_ID&redirect_uri=YOUR_WEBHOOK_URL&response_type=code&scope=instagram_business_basic,instagram_business_content_publish

步驟四:Instagram 授權後 redirect 到 webhook.site,從 URL 取得 code= 後面的值。

步驟五:馬上換 short-lived token(code 幾分鐘過期):

1
2
3
4
5
6
curl -X POST https://api.instagram.com/oauth/access_token \
-F client_id=APP_ID \
-F client_secret=APP_SECRET \
-F grant_type=authorization_code \
-F redirect_uri=YOUR_WEBHOOK_URL \
-F "code=YOUR_CODE"

步驟六:換 long-lived token(有效 60 天):

1
curl -X GET "https://graph.instagram.com/access_token?grant_type=ig_exchange_token&client_secret=APP_SECRET&access_token=SHORT_LIVED_TOKEN"

回傳範例:

1
2
3
4
5
{
"access_token": "IGAAYLng3...",
"token_type": "bearer",
"expires_in": 5165491
}

Token 更新(60 天到期前跑一次即可):

1
curl -X GET "https://graph.instagram.com/refresh_access_token?grant_type=ig_refresh_token&access_token=LONG_LIVED_TOKEN"

加速解: IG Token Generator 工具

OAuth 流程太繁瑣,自製工具放在 demo.marsen.me/admin/tools/ig-token,下次取 token 不需要手動操作。

目前只有我能使用,如果有人有興趣可以與我聯絡我再視情況開放工具,具體還是希望 Bug 可以修複

技術棧:Next.js 16 App Router

流程設計

1
2
3
4
5
6
7
8
9
使用者輸入 App ID + App Secret
→ POST /api/instagram/start
→ server 存 session cookie(5 分鐘 TTL)
→ 回傳 Instagram OAuth URL
→ 瀏覽器跳轉到 Instagram 授權
→ Instagram redirect 到 /api/instagram/callback
→ server 讀 cookie 取得 App Secret
→ 換 short-lived token → 換 long-lived token
→ redirect 到工具頁顯示結果

App Secret 全程只在 server-side 流通,不暴露給客戶端。

待改進項目

  • 範例資料不用真實值:截圖或 UI 展示時,input 欄位的 placeholder 不應出現真實 App ID,用假資料代替
  • Input 欄位短暫快取:App ID 和 App Secret 用 sessionStorage 快取 15~30 分鐘,避免每次授權都要重填(注意 App Secret 快取有資安風險,要考慮清楚)
  • Token 長期快取 90 天:取得 long-lived token 後存到 localStorage,90 天內重開頁面自動顯示,不需重新授權

踩雷筆記:Vercel 部署的 prebuilt 限制

這個專案用 GitHub Actions 做 CI/CD,採 vercel build --prebuilt 方案:

1
2
3
# GitHub Actions
- run: vercel build --prod # Actions 裡 build
- run: vercel deploy --prebuilt --prod # 上傳 build 產出物

更新 Vercel 環境變數後,不能直接 Redeploy,因為 Vercel 沒有原始碼可重新 build。必須 push 新 commit 觸發 GitHub Actions 重新 build 才能套用新設定:

1
2
git commit --allow-empty -m "chore: trigger redeploy"
git push origin main

prebuilt 的好處是 CI 與 deploy 分離,確保 lint/type-check/test 通過才部署;代價是 env var 更新要重新 build。


踩雷筆記:Meta 廣告帳號被限制

20260427 處理中

測試期間收到 Meta 通知:「Your professional ad account is restricted from advertising」。

第一反應是懷疑跟自動化發文有關,查了一下:這個限制針對的是廣告功能(投放廣告、Meta Pixel、推廣貼文),不影響 Content Publishing API。用官方 API 發文是 Meta 明確允許的行為,沒有違規。

推測原因:新開 Business App 首次觸發系統自動審查,誤判。已按「Request a Review」申訴中。

API 發文功能在申訴期間不受影響,繼續正常運作。


參考

小結

  • IG API 只支援 Business 或 Creator 帳號,個人帳號的 Basic Display API 已關閉
  • Facebook Developer App 類型要選 Business,流程選 Instagram Login(不需要 FB Page)
  • Developer Console 的 token generator 有 bug,用 webhook.site 當臨時 redirect URI 繞過
  • Long-lived token 有效 60 天,到期前用 refresh API 更新即可
  • 自製工具把 OAuth 流程自動化,之後取 token 一鍵搞定

(待補:圖片生成 + 實際 IG 發文流程)

(fin)

[產業報告] 從 DeepRacer 到 Robotaxi:Tesla vs Waymo 的無人駕駛現況

前情提要

2019 年,我在 AWS DeepRacer 工作坊裡用 Python 寫了幾行 reward function,讓一台 1:18 的小賽車學會跑彎道。

多年後,無人駕駛已經在真實道路上運作,這篇記錄一下產業現況。

兩條路線

Tesla:純視覺派

馬斯克從第一性原理出發:人用兩隻眼睛就能開車,攝影機應該也夠。

2022 年起,Tesla 把 radar 全拔掉,FSD 只靠攝影機。

基礎理論在於道路本來就是為人類視覺設計的,攝影機加上夠大的神經網路應該能學會同樣的事。

FSD v12 起改用 end-to-end 架構——8 顆攝影機的原始影像直接輸入,輸出方向盤與油門指令,中間沒有人工規則,全靠模型自己學。

每台 Tesla 行駛時都在 shadow mode 默默預測「如果 FSD 接管會怎麼做」,遇到預測與駕駛行為不符的片段就自動上傳訓練。

Waymo:多感測器派

Waymo 走另一條路:LiDAR + radar + 精密地圖,多重機制確保安全。

2025 年,Waymo 達成 1 億英里全自動駕駛里程,每週 25 萬次付費乘車,是目前唯一真正商業化的無人駕駛服務

現況數據

資料來源:Obi report(分析 2025/11 - 2026/01 共 94,348 筆乘車紀錄)、Morgan Stanley 估算。

從 2025 年底的數據看:

| | Tesla Robotaxi | Waymo |
| – | – | – |
| 每公里車費 | $1.99 | $5.72 |
| 車輛硬體成本 | ~$30,000(Cybercab) | ~$200,000 |
| 等車時間 | 15 分鐘 | 5.7 分鐘 |
| 無人駕駛狀態 | 仍有安全駕駛員 | 已完全無人 |
| 鳳凰城獲利 | 否 | 是 |

商業規模

資料來源:Contrary Researchnotateslaapp.com

這個差距的根源在商業模式決定的資料規模

Waymo 賣服務,不賣車。他們的車是改裝的 Jaguar I-Pace,加上一堆感測器,一台 $200,000。

要擴大訓練資料,就得多派車上路,成本線性增長。

截至 2025 年初,Waymo 史上累計行駛里程約 7,100 萬英里

Tesla 賣車給消費者。全球幾百萬台 Tesla 都在 shadow mode 默默記錄「如果 FSD 接管會怎麼做」。

每賣出一台車,消費者不只付了錢,還順便幫 Tesla 訓練模型。

到 2025 年底,Tesla FSD 累計行駛里程已超過 85 億英里,整體車隊的 shadow mode 資料更遠不止於此。

兩者相差超過 100 倍。這個差距,不是砸錢買 LiDAR 能追上的。

如果視覺派能解決惡劣天氣的弱點(Tesla FSD 在大雨、濃霧下表現仍不穩定),Tesla 幾乎必勝——因為硬體成本差了 7 倍,資料規模差了超過 100 倍。

Waymo 的策略是「先把安全做到無懈可擊,再壓低成本」。

Tesla 的策略是「先把規模做到無法追趕,再解決邊界問題」。

2026 年,這場賽局還沒有結果。

小結

2019 年,我坐在工作坊裡,看著一台小車學會跑彎道,覺得強化學習很神奇,但離現實很遠。

2026 年,Robotaxi 已經在路上跑了。

技術本身不是護城河 —— 這兩家公司對產業有不同的願景看法,所以有了不同的商業模式。

可以繼續看下去,也期待台灣能早日坐到。

參考

(fin)

[實作筆記] 個人自動化平台(四) n8n 實作:收集層,RSS 資料來源

前情提要

上一篇定好了「收集、處理、輸出」三層架構,這篇開始實作第一層:收集。

目標是從四個 AI 新聞來源拉資料,輸出統一格式,交給處理層處理。


資料來源選擇

驗證過四個 RSS 來源都可用:

來源 URL 說明
OpenAI https://openai.com/news/rss.xml 官方,第一手產品更新
Google Blog AI https://blog.google/innovation-and-ai/technology/ai/rss/ 官方,涵蓋 Gemini 等產品
The Verge AI https://www.theverge.com/rss/ai-artificial-intelligence/index.xml 綜合媒體,覆蓋廣
HackerNews https://hnrss.org/newest?q=AI&points=50 社群篩選,工程師視角

Anthropic 目前無官方 RSS,是個缺口,之後另找方案補上。

CLI 驗證方式:

1
curl -s -o /dev/null -w "%{http_code} %{url_effective}\n" <RSS_URL>

n8n 節點選擇

收集層有兩種做法:

RSS Feed Trigger + 暫存層 RSS Read + Filter
抓取方式 增量,只抓新的 全量,每次重抓
重複抓取 不會 會(Filter 擋掉舊的)
Token 消耗 較省 稍多
架構複雜度 高(需外部暫存) 低(無狀態)
適合 sub-workflow 不適合(Trigger 節點限制) 適合

現況的選擇:RSS Read + Filter

現階段優先走通整條流程,不想多依賴一個外部暫存服務。

RSS Read 是普通節點,可以被 sub-workflow 呼叫,搭配 Filter 過濾近 7 天的文章,邏輯乾淨:

1
Schedule Trigger → RSS Read → Filter(只保留近 7 天文章)

等之後有需要優化 token 消耗,再評估加暫存層。


實作步驟

步驟一:建立 Workflow

新增 Workflow,命名 [收集] The Verge AI

觸發節點選 Schedule Trigger,預設每天午夜執行即可(之後調整為週報頻率)。

步驟二:加入 RSS Read 節點

在 Schedule Trigger 後加 RSS Read 節點,填入 URL:

1
https://www.theverge.com/rss/ai-artificial-intelligence/index.xml

步驟三:Filter 節點過濾日期

在 RSS Read 後加 Filter 節點,條件:

1
isoDate → is after → {{ $now.minus({days: 7}).toISO() }}

步驟四:Edit Fields 節點標準化輸出

在 Filter 後加 Edit Fields(Set) 節點,對應欄位:

Name Value
title {{ $json.title }}
url {{ $json.link }}
content {{ $json.contentSnippet }}
published_at {{ $json.isoDate }}
source The Verge AI

執行成功,每筆輸出格式如下:

1
2
3
4
5
6
7
{
"title": "Cloud development platform Vercel was hacked",
"url": "https://www.theverge.com/tech/914723/vercel-hacked",
"content": "Vercel, a major development platform...",
"published_at": "2026-04-19T19:54:52.000Z",
"source": "The Verge AI"
}

四個步驟完成,收集層跑通。每次執行會輸出統一格式的文章列表,準備交給處理層處理。


API Credential 的資安決策

設定 Google Gemini credential 時,有一個值得討論的選項:Allowed HTTP Request Domains

n8n 官方說明:

Control which domains this credential can be used with in HTTP Request nodes

這個設定限制的是被呼叫端(destination)——也就是「這組 key 只能被送往哪些 domain」,不是「誰能在 n8n 裡使用這組 key」。

能防護的範圍

  • 攻擊者透過 HTTP Request 節點把 key 帶著打到外部惡意 server

防護不到的範圍

  • n8n 原生節點(如 Google Gemini Chat Model)
  • 直接存取 n8n 資料庫
  • 從 n8n UI 讀取 credential

決策:填 generativelanguage.googleapis.com,遵守最小權限原則。但主要防線是 Cloudflare Access(限制誰能登入 n8n),這個設定是額外的縱深防禦,不能過度依賴。

核心原則:理解每層保護的邊界,不要以為設了就完全安全。


參考

小結

  • 收集層用 RSS Read + Filter 過濾近 7 天,四個來源統一輸出格式
  • Credential 的 Allowed HTTP Request Domains 是縱深防禦,主防線還是 Cloudflare Access
  • RSS Read 是「簡單優先」的選擇,之後有需要再加暫存層優化

(fin)

[實作筆記] 個人自動化平台(三) 收集、處理、輸出:三層可插拔管道設計

前情提要

前兩篇把 GCP VM + n8n + cloudflared tunnel 都設好了,環境就緒。

這篇不急著動手,先把架構想清楚。

目標是一個可以自由擴充的個人內容自動化平台:新增資料來源、換 AI 模型、改輸出管道,都不需要重寫整個流程。


核心設計:三層分離

把整個管道拆成三層,每層只負責一件事:

1
收集(Fetch)   →   處理(Process)   →   輸出(Sink)
  • 收集:從外部拿資料,不做任何處理
  • 處理:理解資料,產生新資訊
  • 輸出:把結果推出去

每層都是介面,實作可以自由替換或組合。


每層的邊界

收集(Fetch)

負責從各種來源拉資料:RSS、API、Webhook、爬蟲……

不管來源是什麼,輸出格式統一:

1
2
3
4
5
6
7
{
"title": "文章標題",
"url": "https://...",
"content": "原文內容或摘要",
"source": "來源名稱",
"published_at": "2026-04-20T00:00:00Z"
}

這個格式就是「收集」和「處理」之間的契約。只要每個來源都輸出這個 schema,「處理」完全不需要知道資料從哪來。

新增來源:建一個新的 sub-workflow,輸出同樣格式。
停用來源:把那個 sub-workflow 關掉。

處理(Process)

負責理解資料,產生新資訊。

目前規劃的功能:

功能 說明
摘要歸納 把長文濃縮成重點
事實查核 驗證內容可信度(未來)
創意發想 從資料延伸新觀點(未來)

AI 是「處理」層的實作細節,不是介面的一部分。

今天用 Claude,明天換 Gemini,或跑地端模型(Ollama)——主流程不在意,只呼叫「處理」的 sub-workflow,不管裡面用的是什麼模型。

「處理」層的輸出格式:

1
2
3
4
5
6
7
8
{
"title": "文章標題",
"url": "https://...",
"summary": "AI 整理的重點",
"tags": ["AI", "LLM"],
"source": "來源名稱",
"published_at": "2026-04-20T00:00:00Z"
}

輸出(Sink)

負責把結果推出去。

兩個維度都可以擴充:

格式

  • 純文字
  • 簡報(未來)
  • 影音(未來)

管道

  • Email
  • LINE
  • YouTube / Instagram / Facebook(未來)

從「處理」到「輸出」可以有多條路線,同一批摘要可以同時走不同路線,互不干擾:

1
2
3
4
處理(摘要結果)
├─ → Email 週報(長版純文字)
├─ → LINE 推播(短版)
└─ → 存檔(JSON,備用)

在 n8n 的實作方式

n8n 的 sub-workflow 機制很適合做這件事:

  • 每個「收集」來源 → 一個 sub-workflow
  • 每個「處理」功能 → 一個 sub-workflow
  • 每個「輸出」管道 → 一個 sub-workflow
  • 主流程:用 Execute Workflow 節點串起來,用 Merge 合併多個來源,用 Switch / If 分流到不同輸出

新增或停用任何環節,只需要在主流程加減節點,不動其他邏輯。


小結

  • 三層分離(收集、處理、輸出),每層依賴介面不依賴實作
  • 各層之間用固定的 JSON schema 溝通,是可替換性的基礎
  • 「處理」層的 AI 模型是實作細節,抽成 sub-workflow 就能自由替換
  • 「輸出」層支援多路線同時輸出,格式和管道各自獨立擴充
  • n8n 的 sub-workflow 機制天然適合這個設計

下一篇開始動手實作第一條 pipeline:AI 新聞週報。

參考

(fin)

[實作筆記] 個人自動化平台(番外) 拆掉重建 GCP VM & Cloudflared Tunnel & Cloudflare Access

前情提要

前兩篇把 GCP 免費 VM + n8n + cloudflared tunnel + Cloudflare Access 都設好了。

但整個過程是邊做邊踩坑,步驟不一定是最乾淨的順序。
這篇把環境完整清理掉,再照前兩篇的步驟重建一次,驗證文章可以重現。


清理步驟

清理順序:從外到內,先 Cloudflare 再 GCP。

步驟一:刪除 Cloudflare Access Application

進 Cloudflare 主控台 → Zero TrustAccess → Applications

找到 butler.marsen.me 的 application,刪除。

驗證:開瀏覽器試 https://butler.marsen.me,如果 Access 已清除,不會再看到驗證頁面。

步驟二:刪除 Cloudflare Tunnel

SSH 進 VM,先停 cloudflared service,再刪 tunnel:

1
2
sudo systemctl stop cloudflared
cloudflared tunnel delete butler

驗證

1
cloudflared tunnel list

butler 不在列表裡就是刪成功了。

也可以在 UI 刪:Zero Trust → Networks → Tunnels → 找 butler → 刪除。
但要先確認 tunnel 已停止(cloudflared service 停掉或 VM 已關機)。

步驟三:刪除 DNS 記錄

進 Cloudflare 主控台 → marsen.me → DNS

找到 butler.marsen.me 的 CNAME 記錄(4ba20fc4-...cfargotunnel.com),刪除。

驗證:開瀏覽器,出現 DNS_PROBE_FINISHED_NXDOMAIN 就是清除成功。

DNS 記錄目前只能透過 UI 刪除,或使用 Cloudflare API(需要 API Token)。

步驟四:刪除 GCP VM

1
2
3
gcloud compute instances delete ai-butler-vm00 \
--zone=us-east1-b \
--project=YOUR_PROJECT_ID

確認後輸入 Y,VM 和磁碟都會一起移除。

參考

清理完成後,照以下兩篇文章重建

小結

  • 清理順序:從外到內,先 Cloudflare 再 GCP,避免 tunnel 刪除時出現狀態不一致
  • 刪 VM 時磁碟會一起移除,n8n 資料不保留

(fin)

個人自動化平台(二) Cloudflare Access & Cloudflare Tunnel

前情提要

上一篇在 GCP 免費 VM 上把 n8n 跑起來了,但還沒辦法從外部存取。

這篇用 cloudflared tunnel 解決這個問題。

好處是:

  • 不用改 GCP 防火牆,不暴露任何 port
  • 自動 HTTPS
  • 可以加 Cloudflare Access(Google 帳號驗證)擋在前面

架構概觀

這篇要做的事情分兩層:

第一層:cloudflared tunnel

在 VM 裡跑一個 cloudflared process,它會主動連到 Cloudflare 的網路,建立一條加密的隧道。
外部流量進來的路徑是:

1
使用者瀏覽器 → Cloudflare → cloudflared tunnel(VM 內)→ n8n(127.0.0.1:5678)

VM 不需要開任何防火牆 port,因為連線是由 VM 內部主動發起的。

第二層:Cloudflare Access(Zero Trust)

cloudflared tunnel 只負責轉流量,本身不做身份驗證,任何人都能連進來。
Cloudflare Access 是加在 tunnel 前面的門禁:

1
使用者瀏覽器 → Cloudflare Access(驗證身份)→ cloudflared tunnel → n8n

只有通過驗證(這裡用 email OTP)的人才能到達 n8n。
這就是「Zero Trust」的概念:不信任任何人,每次都要驗證身份。

這篇的步驟順序

  1. 安裝 cloudflared
  2. 登入 Cloudflare、建立 tunnel、設定 config、新增 DNS
  3. 設定 Cloudflare Access(先做,tunnel 上線前就有門禁
  4. 設定 systemd 並啟動 tunnel

步驟零:安裝 cloudflared

在 VM 上執行:

1
2
3
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null
echo 'deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared bookworm main' | sudo tee /etc/apt/sources.list.d/cloudflared.list
sudo apt update && sudo apt install cloudflared

看到 Setting up cloudflared 就安裝成功了。


命名決策

這台 VM 不只跑 n8n,之後可能還有其他自動化服務。
所以 tunnel 和 subdomain 都以機器用途命名,而不是以某個服務命名。

以這篇為例,選 butler(管家),因為這台機器的定位是「幫你自動處理事情的後台」。

  • <TUNNEL_NAME> = butler
  • <YOUR_DOMAIN> = marsen.me
  • 對外網址:butler.marsen.me

之後如果加新服務,在 config 裡多加一條 ingress 規則就好。


步驟一:登入 Cloudflare

在 VM 上跑:

1
cloudflared tunnel login

它會印出一個 URL,複製到瀏覽器,選你要授權的 domain(這裡是 <YOUR_DOMAIN>)。

授權完成後 cert 會自動存到:

1
~/.cloudflared/cert.pem

步驟二:建立 Tunnel

1
cloudflared tunnel create <TUNNEL_NAME>

成功後會顯示 tunnel ID(UUID 格式),同時在 ~/.cloudflared/ 產生一個 credentials JSON 檔。

1
2
Tunnel credentials written to /home/USER/.cloudflared/<tunnel-id>.json
Created tunnel <TUNNEL_NAME> with id <tunnel-id>

把 tunnel ID 記下來,後面要用。


步驟三:建立 config 檔

1
2
3
4
5
6
7
8
9
cat > ~/.cloudflared/config.yml << 'EOF'
tunnel: <your-tunnel-id>
credentials-file: /home/<USER>/.cloudflared/<your-tunnel-id>.json

ingress:
- hostname: <TUNNEL_NAME>.<YOUR_DOMAIN>
service: http://127.0.0.1:5678
- service: http_status:404
EOF

注意最後的 EOF 必須從行首開始,前面不能有任何空格。

為什麼用 127.0.0.1 而不是 localhost
localhost 在某些環境會被解析成 IPv6 的 [::1],但 n8n 預設只監聽 IPv4。
直接寫 127.0.0.1 避免這個問題。

其中 5678 是 n8n 在 Docker 容器裡對外暴露的 port。

可以用以下指令驗證 config 是否正確:

1
cloudflared tunnel ingress validate

看到 OK 就是正確。


步驟四:新增 DNS 記錄

1
cloudflared tunnel route dns <TUNNEL_NAME> <TUNNEL_NAME>.<YOUR_DOMAIN>

這會在 Cloudflare DNS 自動加一筆 CNAME,指向你的 tunnel。


步驟五:設定 Cloudflare Access

在啟動 tunnel 之前先設好 Access,這樣 n8n 上線的那一刻就已經有門禁,不會有公開暴露的空窗期。

注意:不要用 Onboarding 精靈。 Zero Trust 首次進入會有引導精靈,精靈的「指派 Tunnel」步驟需要 tunnel 已在執行中才能選取。我們要先設 Access 再啟動 tunnel,所以改用手動建立 Application。

進 Cloudflare 主控台 → Zero TrustAccess → Applications+ Add an application → 選 Self-hosted

第一次進入 Zero Trust 需要選擇團隊名稱(之後可以改),Free 方案 50 人以下免費。

填入應用程式資訊:

欄位 填什麼
Application name <APP_NAME>
Subdomain <TUNNEL_NAME>
Domain <YOUR_DOMAIN>

下一步設定 Policy:

欄位 填什麼
Policy name allow
Action Allow
Selector Emails
<YOUR_EMAIL>

存檔。Access 現在保護 <TUNNEL_NAME>.<YOUR_DOMAIN>,只有通過驗證的 email 才能進去。

1
2
3
4
5
6
7
使用者瀏覽器
↓ HTTPS
Cloudflare Access(驗證身份)
↓ 通過後
cloudflared tunnel(在 VM 裡跑)
↓ 本機連線
127.0.0.1:5678(n8n)

為什麼要在 tunnel 啟動前先做?

你一開 n8n 就會看到「Set up owner account」頁面,這個頁面是公開的。
如果沒有 Access 保護,任何人都能搶先填、搶走 owner 帳號。


步驟六:設定開機自動啟動並啟動 Tunnel

讓 cloudflared 在 VM 重開機後自動啟動,不用每次手動跑。

cloudflared service install 需要知道 config 在哪,但用 sudo 跑時 home 會變成 root 的,
所以要明確指定路徑。安裝後 systemd 會把 config 複製到 /etc/cloudflared/config.yml

1
2
3
sudo cloudflared --config /home/<USER>/.cloudflared/config.yml service install
sudo systemctl start cloudflared
sudo systemctl status cloudflared

<USER> 換成你的 OS Login 用戶名稱(Gmail 帳號把 .@ 換成 _,例如 yourname_gmail_com)。

看到 active (running) 就完成了。

這時開 https://<TUNNEL_NAME>.<YOUR_DOMAIN>,應該會先看到 Cloudflare Access 的驗證頁面。

之後 VM 重開機,cloudflared 和 n8n 都會自動恢復。


踩坑紀錄

踩坑一:瀏覽器顯示連線錯誤

症狀:tunnel 連上了(看到 Registered tunnel connection),但開瀏覽器只看到錯誤頁面。

瀏覽器的連線錯誤原因很多,不要猜。先查 n8n log:

1
sudo docker logs n8n --tail 20

根據 log 的錯誤訊息再對症下藥。


踩坑二:n8n 啟動失敗(permission denied)

出現時機docker logs 看到 EACCES: permission denied

原因docker run 之前沒有預先建立 ~/.n8n,Docker 自動用 root 身份建立這個目錄。
n8n 容器裡的 node 用戶(UID 1000)不是 root,沒有寫入權限,n8n 啟動就 crash。

解法:把目錄 owner 改成 UID 1000,再重建容器:

1
2
3
4
5
6
7
8
9
sudo chown -R 1000:1000 ~/.n8n
sudo docker rm -f n8n
sudo docker run -d \
--name n8n \
--restart unless-stopped \
-p 5678:5678 \
-v ~/.n8n:/home/node/.n8n \
-e N8N_SECURE_COOKIE=false \
docker.n8n.io/n8nio/n8n

預防:照第一篇步驟五,docker run 前先執行 mkdir -p ~/.n8n && sudo chown 1000:1000 ~/.n8n,就不會踩到這個坑。

n8n 映像檔本來就用非 root 的 node 用戶執行,不需要加 --user


參考

小結

  • cloudflared tunnel 讓 n8n 對外,不用開 GCP 防火牆、不暴露任何 port
  • Cloudflare Access 擋在前面,只有通過驗證的 email 才能進到 n8n
  • cloudflared 設成 systemd service,VM 重開機自動恢復,不需要手動啟動
  • 整條鏈路:瀏覽器 → Cloudflare Access(驗證)→ cloudflared tunnel → n8n

(fin)

[實作筆記] 個人自動化平台(一) n8n & GCP VM

前情提要

最近想整合 AI 與自已的職能,建立一些小工具。

第一個想法是資料搜集器,可以自由擴充的個人內容自動化平台。

核心概念

「捕捉資料」、「整理資料」、「發送報告」拆成三種抽象概念元件,

隨時可以組合或替換。

1
2
3
4
5
6
7
Sources(捕捉資料)  →  Processors(整理)  →  Sinks(發送)
───────────────── ───────────────── ─────────────
AI 新聞 摘要整理 LINE
RSS 訂閱 翻譯 Email
YouTube 事實查核 Instagram
Gmail 格式化 GitHub
... ... ...

一條 pipeline = 選幾個 Source + Processor + Sink 組合起來。
加新管道只需要加一個 Sink 節點,不動其他邏輯。

技術選擇與環境規格

選 n8n 當 runtime,因為它本來就是這個思路設計的,

而且可以自己架,不用把資料交給第三方。

也有考慮過其他方案:

方案 優點 缺點
CCR(Claude Code Remote,Anthropic 雲端排程 agent) 免維護 無法自訂節點、靈活度低
VM + cron 自由度高 要自己維護腳本
n8n 視覺化、可擴充、節點生態豐富、私心想學 需要維護 VM

考慮控制力與學習新技能,最後選 n8n。

一、跑在 GCP e2-micro,always free,完全不用花錢。

GCP Always Free 的條件:

  • 機器:e2-micro(2 vCPU 共享、1 GB RAM)
  • 區域:美國區(us-east1 / us-west1 / us-central1)
  • 磁碟:30 GB 標準永久磁碟
  • 網路:每月 1 GB 對外流量

關於 IP 費用

GCP 的外部 IP 分兩種:

類型 說明 費用
臨時外部 IP VM 運行時自動分配,刪除後釋放 免費
靜態外部 IP(附在 VM 上) 固定 IP,VM 刪了還保留 免費
靜態外部 IP(未附在 VM) 保留但沒有用 收費

我們用臨時 IP,免費。

關於流量費用

GCP 計算的是 VM 的對外流量(VM → 外部)。
後面會用 cloudflared tunnel,流量路徑是 VM → Cloudflare → 使用者,
GCP 端的出站流量極小,個人 n8n 使用幾乎不會超過每月 1GB 的免費額度。

個人用的 n8n 排程任務,這個規格完全夠。

二、在 GCP 的 VM 上安裝 n8n(文章待補)

三、在 n8n 上設定 pipeline(文章待補)


步驟一:建立 VM

以下我直接讓 AI 執行,你也可以手動或是使用瀏覽器的圖形介面操作

先裝 gcloud CLI:

1
brew install google-cloud-sdk

登入並設定 project:

1
2
gcloud auth login
gcloud config set project YOUR_PROJECT_ID

建立 VM:

1
2
3
4
5
6
7
8
gcloud compute instances create n8n-server \
--zone=us-east1-b \
--machine-type=e2-micro \
--image-family=debian-12 \
--image-project=debian-cloud \
--boot-disk-size=30GB \
--boot-disk-type=pd-standard \
--tags=http-server,https-server

步驟二:設定 SSH(OS Login)

預設的 SSH key 方式要管理本機的 key 檔案,換機器或多人共用都麻煩。

我個人比較喜歡 OS Login 改用 Google 帳號做身份驗證,key 綁在帳號上,不依賴本機檔案。

實際上還是有 ssh key 只是不用特別在意它。

開啟 OS Login:

1
2
3
4
gcloud compute instances add-metadata n8n-server \
--metadata enable-oslogin=TRUE \
--zone=us-east1-b \
--project=YOUR_PROJECT_ID

之後連線就直接,不需要帶任何 key 參數:

1
gcloud compute ssh n8n-server --zone=us-east1-b --project=YOUR_PROJECT_ID

第一次連會問你要不要建立 SSH key,輸入 y 就好,之後不用再動。


步驟三:設定 Swap

e2-micro 只有 1 GB RAM,n8n 本身就吃掉一半左右。
加 swap 避免 OOM crash:

1
2
3
4
5
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

最後一行讓 swap 重開機後也自動生效。


步驟四:安裝 Docker

1
2
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker $USER

usermod 讓你的帳號不用 sudo 就能跑 docker,要重新登入才生效。


步驟五:跑 n8n

n8n 容器裡的 node 用戶 UID 是 1000。

先把 volume 目錄的 owner 設成 1000,避免容器啟動後出現 permission denied:

port 使用預設的 5678。後面會用 cloudflared tunnel 轉發,這個 port 不會對外暴露,改不改都一樣。

1
2
3
4
5
6
7
8
9
mkdir -p ~/.n8n
sudo chown 1000:1000 ~/.n8n
sudo docker run -d \
--name n8n \
--restart unless-stopped \
-p 5678:5678 \
-v ~/.n8n:/home/node/.n8n \
-e N8N_SECURE_COOKIE=false \
docker.n8n.io/n8nio/n8n

幾個參數說明:

參數 說明
--restart unless-stopped VM 重開機自動重啟 n8n
-v ~/.n8n:/home/node/.n8n 資料持久化,不會因為容器重建而消失
N8N_SECURE_COOKIE=false n8n 預設要求 HTTPS 才設定 cookie。對外雖然是 HTTPS,但 cloudflared 到 n8n 的內部連線是 HTTP,所以這個設定要保留

n8n 映像檔本來就用非 root 的 node 用戶執行,不需要加 --user

確認有跑起來:

1
2
sudo docker ps
sudo docker logs n8n --tail 20

正常的話會看到 port 0.0.0.0:5678->5678/tcp,STATUS 是 Up

log 裡可能有 community packages 的 error,是 n8n 找不到額外套件,不影響運作,忽略即可。


參考


小結

到這裡,n8n 已經在 GCP 免費 VM 上跑起來了。

  • GCP e2-micro 免費,加 swap 跑 n8n 沒問題
  • OS Login 用 Google 帳號認證,不依賴本機 SSH key 檔案
  • n8n image 約 2.3GB,第一次 pull 要等幾分鐘
  • --restart unless-stopped 確保 VM 重開機後 n8n 自動恢復

目前 n8n 只能從 VM 內部存取,下一篇會用 cloudflared tunnel 讓它可以從外部用 HTTPS 開啟,不用動防火牆。

(fin)

[生活筆記] 面試記錄 B 社 — AI Workflow Tech Lead

前情提要

Gap year 期間收到一封獵頭的 LinkedIn 邀約,職位是 AI Workflow Tech Lead。

獵頭來自一家 HR Consulting 公司,背後的客戶公司(以下稱 B 社)資訊目前尚未公開。


獵頭提供的基本資訊

  • 職稱:AI Workflow Tech Lead
  • 產業:台北軟體公司,提供決策營運優化工具,協助品牌提升轉換率與營收
  • 薪資:年薪 2-4M(依經驗而定)
  • 工作型態:混合遠距辦公(比例未確認)
  • 團隊規模:帶領 3-5 人工程團隊

JD 重點

這個角色的核心是替公司內部建立 AI 驅動的開發流程與自動化系統。

主要工作包含:

  • 評估與導入 AI 工具(Claude、Codex 等 LLM / Agent 平台)
  • 建立 AI 輔助 workflow:code generation、code review、文件知識檢索、任務 orchestration
  • 架構設計:LLM + API + 自動化工具 + human-in-the-loop
  • 跨團隊識別瓶頸,轉化成 AI 解決方案
  • 建立 prompt design、workflow 可靠性、治理等最佳實踐
  • mentor 團隊成員

值得注意的是,JD 描述偏向「內部 AI 工具建設者」,但獵頭口頭說的比較像帶產品團隊的 Tech Lead。

兩者定位有落差,預計在 meet 時釐清。


目前已知 / 未知

項目 狀態
薪資範圍 已知(2-4M,標準未知)
工作型態 混合遠距,比例未確認
公司名稱 未公開
公司規模與穩定性 未知
角色定位(產品 vs 內部工具) 待確認
直屬主管 未知
團隊現況 未知

自我梳理

對方特別在意兩件事:n8n 以外的 AI workflow 工具廣度,以及 AI Agent 系統設計的實際深度。

這讓我重新整理了一下自己在這塊的積累。

技術工具不是重點,思路才是。我做 AI 相關系統時,習慣先把任務拆成多個 step,

用 workflow 控制整個流程,而不是只寫一段 prompt 然後祈禱它跑對。

具體做過的事:

  • RAG 系統:使用者上傳法律合約(PDF / 圖片轉文字)→ Qdrant 向量搜尋 → LLM 回答問題,
    應用在專利法規與公司內部產品知識,有實際賣給 SONY 和電子廠的商業案例
  • Edge deployment:因為客戶要求本地部署,處理過 Ollama(本地模型)與 API 模型的整合與切換
  • 工程架構:Python + Node.js 拆服務,queue / worker 處理流程,
    加上 cache 與模型分級使用,控制成本與效能

核心原則只有一個:讓 AI 真正整合進既有流程,提升效率,不只是做 demo。


小結

技術面的吻合度高,AI Agent 系統設計、RAG、導入 AI 開發流程都是近期實際做過的事。

主要的疑慮是角色定位的模糊,以及混合遠距的實際彈性。

這兩點是一面時的優先確認項目。

(fin)

[生活筆記] 系統思考打工人生

前情提要

最近看到一篇文章,討論台灣的系統性剝削。

「系統性剝削」讓財富自動從勞動者流向資產持有者,普通人很難透過努力翻身。

這是我們這代人的問題。反思一下,身為玩家可以做什麼?


以台灣為例

最低工資每年調,但同時:

  • 匯率壓低、利率壓低 → 貨幣寬鬆 → 通膨
  • 房價持有成本趨近於零(沒有空屋稅)→ 房價只漲不跌
  • 引入大量移工 → 勞動供給過剩 → 薪資天花板下降

設計規則的人也是持有資產的人。

在這樣的賽局裡,你再努力,薪資漲幅永遠追不上資產漲幅。

因為規則保證了這件事。

40 年來台灣 GDP 一直在漲,但多數人的體感是愈來愈苦。

以前一個男人能養家買房,後來要雙薪,現在年輕人連婚姻想都不想了。

這是制度的必然產物,不見得是個人努力不夠。


那要怎麼辦?

在一場注定輸的賽局裡,有兩條路:繼續玩、或是換桌子。

繼續玩的問題在於:你投入的時間與精力,最終會被制度吸走。

努力的成果被通膨稀釋、被房價吃掉、被移工競爭壓制。

要如何破局? 抗爭 ? 躺平 ?

我想是降低對這套制度的依賴,至少換張好一點牌桌。

收益結構

勞動所得的天花板由雇主決定。

你的薪資,是在別人設定的規則裡拿到的分數。

AI 工具讓「建立自動化系統、服務全球市場」的門檻大幅下降。

從勞動所得換成系統所得,才有機會脫離這個天花板。

資產配置

持有現金本身變成一個高風險的事情,通膨不斷的在消耗它的價值。

把所有資產鎖在台灣計價的東西裡,就是讓財富的增減由地主民代的政策來決定。

至少配置部分全球指數基金(VTI、VT 之類),

讓資產跟全球經濟走,不只跟台灣的政策走。

技能可攜帶

稅制剝削不走你的技能。

投資通用的、可以帶著走的能力,

而不是深綁單一公司制度或內部系統的專業。

可攜帶的技能是最難被制度稀釋的資產。

不玩不合理的遊戲

房價所得比畸形就不接盤。

不是放棄,是選擇把資源投入到勝率更好的地方。

換桌子,不是撞牆。


小結

  • 台灣的制度讓薪資漲幅永遠追不上資產漲幅,這是設計,不是意外
  • 在結構化賽局裡努力玩,輸的速度只是慢一點
  • 破局的核心:降低對單一制度的依賴
  • AI 與遠端工作讓個人破局的可行性比過去高很多

(fin)

Please enable JavaScript to view the LikeCoin. :P