diff --git a/content/posts/2026/2026-01-16_Windows Git Bash 改用 Zsh/index.md b/content/posts/2026/2026-01-16_Windows Git Bash 改用 Zsh/index.md index c8f9bd4..2bd46b5 100644 --- a/content/posts/2026/2026-01-16_Windows Git Bash 改用 Zsh/index.md +++ b/content/posts/2026/2026-01-16_Windows Git Bash 改用 Zsh/index.md @@ -3,10 +3,15 @@ slug: Windows Git Bash 改用 Zsh title: Windows Git Bash 改用 Zsh description: toc: true -authors: [] -tags: [] -categories: [] -series: [] +authors: + - awin +tags: + - windows + - terminal +categories: + - System +series: + - Windows date: 2026-01-16T00:00:00 lastmod: 2026-01-16T00:00:00 featuredVideo: diff --git a/content/posts/2026/2026-02-01_用 Caddy 做反向代理 + 用 Authentik 管理帳號(以 Gitea 為例)/featured.jpg b/content/posts/2026/2026-02-01_用 Caddy 做反向代理 + 用 Authentik 管理帳號(以 Gitea 為例)/featured.jpg new file mode 100644 index 0000000..91dbf7d Binary files /dev/null and b/content/posts/2026/2026-02-01_用 Caddy 做反向代理 + 用 Authentik 管理帳號(以 Gitea 為例)/featured.jpg differ diff --git a/content/posts/2026/2026-02-01_用 Caddy 做反向代理 + 用 Authentik 管理帳號(以 Gitea 為例)/index.md b/content/posts/2026/2026-02-01_用 Caddy 做反向代理 + 用 Authentik 管理帳號(以 Gitea 為例)/index.md new file mode 100644 index 0000000..ea4f423 --- /dev/null +++ b/content/posts/2026/2026-02-01_用 Caddy 做反向代理 + 用 Authentik 管理帳號(以 Gitea 為例)/index.md @@ -0,0 +1,519 @@ +--- +slug: 用 Caddy 做反向代理 + 用 Authentik 管理帳號(以 Gitea 為例) +title: 用 Caddy 做反向代理 + 用 Authentik 管理帳號(以 Gitea 為例) +description: +toc: true +authors: + - awin +tags: + - linux + - selfhosted +categories: + - System +series: + - Linux +date: 2026-02-01T00:00:00 +lastmod: 2026-02-01T00:00:00 +featuredVideo: +featuredImage: +draft: false +enableComment: true +--- + +本篇會帶你用 **Caddy** 當反向代理(Reverse Proxy)與自動 TLS,並用 **Authentik** 當身分提供者(IdP / SSO)來管理帳號;最後以 **Gitea** 當範例,完成「反向代理 + 單一登入(OIDC)」整合。 + +這張可愛的圖是 Gemini 生成的。 + + + +本文參考文件: + +- Caddy 文件:[https://caddyserver.com/docs/](https://caddyserver.com/docs/) +- Authentik 文件:[https://docs.goauthentik.io/](https://docs.goauthentik.io/) +- Authentik 整合文件:[https://integrations.goauthentik.io/](https://integrations.goauthentik.io/) +- Authentik × Gitea(以官方整合為主):[https://integrations.goauthentik.io/development/gitea/](https://integrations.goauthentik.io/development/gitea/) + +範例網域: + +- Authentik:`auth.www.myapp.example` +- Gitea:`git.www.myapp.example` + +(以上都以 `www.myapp.example` 為「示意域名」。實作時請換成你自己的可解析網域,並確保 DNS A/AAAA 指向你的主機 IP。) + +> 本文所有服務都以 Docker 方式部署,並以「分開三份 docker-compose」為主。為了降低複雜度,本文不使用共用 Docker network;Caddy 會透過 `host.docker.internal` 反向代理到宿主機上已 publish 的服務埠。 + +--- + +## 1) Caddy 的優點與功能 + +Caddy 是現代化的 Web Server / 反向代理,特別適合「自架服務入口」: + +- **自動 HTTPS**:內建 ACME(例如 Let’s Encrypt),只要網域與 80/443 可用,Caddy 會自動申請與續期憑證。 +- **設定檔簡潔**:Caddyfile 可讀性高,反向代理、gzip/zstd、header、redirect 等都很直覺。 +- **反向代理體驗好**:`reverse_proxy` 直接把請求轉送到後端服務(HTTP/HTTPS/Unix socket 都可)。 +- **可擴充**:模組化(插件)設計,常見需求如 forward auth、rate limit、WAF 等都有社群方案(是否採用依你需求而定)。 +- **適合容器化**:用 Docker 跑 Caddy 非常普遍,資料(憑證、設定)用 volume 掛載即可。 + +在這篇架構裡,Caddy 的角色很單純: + +- 對外只有一個入口(80/443) +- 負責 TLS、HTTP→HTTPS、與把流量分流到 `Authentik` 和 `Gitea` + +--- + +## 2) Authentik 的優點與功能 + +Authentik 是一個自架的身分與存取管理(IAM)/ 單一登入(SSO)平台,你可以把它當成「你自己的 Google/Microsoft/Okta Login」。 + +常見價值: + +- **統一帳號來源**:帳號、群組、權限集中管理,不用每個服務都自己養帳。 +- **多種整合方式**:OIDC(OpenID Connect)、OAuth2、SAML、LDAP、Proxy/ForwardAuth 等。 +- **安全能力**:MFA、條件式存取、登入流程(flow)可控。 +- **自架友善**:Docker 部署成熟,社群整合文件多。 + +在這篇裡 Authentik 的角色是: + +- 當 **OIDC Provider**(發行 token / 提供 userinfo) +- Gitea 透過 OIDC 把「登入」交給 Authentik + +--- + +## 3) 以 Gitea 為例:Caddy、Authentik、Gitea 的角色與登入流程 + +### 三方關係(誰做什麼) + +- **Caddy(反向代理 / 門口)** + + - 接收瀏覽器對 `auth.www.myapp.example` 與 `git.www.myapp.example` 的連線 + - 依網域把請求轉送到對應容器 +- **Authentik(身分提供者 IdP / SSO)** + + - 管理使用者 + - 提供 OIDC discovery / authorization / token / userinfo +- **Gitea(應用服務 / Relying Party)** + + - 提供 Git service 與 Web UI + - 使用者點「用 Authentik 登入」→ 把驗證交給 Authentik + +### 登入流程(OIDC 概念流程) + +以使用者從 `git.www.myapp.example` 進入為例: + +1. 使用者進入 Gitea,選擇「用 Authentik 登入」(OIDC/OAuth2)。 +2. Gitea 將瀏覽器導向 Authentik 的授權端點(authorize),並帶上 `client_id`、`redirect_uri`、`scope`、`state` 等參數。 +3. 使用者在 Authentik 完成登入(可能含 MFA)。 +4. Authentik 將瀏覽器導回 Gitea 的 `redirect_uri`(callback),並附上 code。 +5. Gitea 以 server-to-server 方式向 Authentik 的 token endpoint 換取 token。 +6. Gitea 用 token 取得使用者資訊(或驗證 ID Token),建立/綁定本地帳號並完成登入。 + +重點: + +- **Caddy 不負責「帳號驗證」**,它只是把流量導到 Authentik/Gitea。 +- **Authentik 负责身份**,Gitea 負責「使用 Authentik 身份」來登入。 + +--- + +## 4) 實作:用 Docker 部署三套服務並完成整合 + +### 4.1 先準備:DNS 與防火牆 + +- DNS: + - `auth.www.myapp.example` → 你的主機 IP + - `git.www.myapp.example` → 你的主機 IP +- 對外開放: + - TCP 80、443(給 Caddy 申請/使用 TLS) + +- 建議不要「對外網」放行(但會在主機上 publish 給 Caddy 用): + - Authentik 的 9000(本文用它讓 Caddy 反向代理) + - Authentik 的 9443(通常不需要;除非你自己另外 publish 來除錯) + - Gitea 的 8020(本文用它讓 Caddy 反向代理) + - 2244/22(SSH)是否對外開放依你需求(可先不開) + +> 如果你用「Caddy 反向代理」模式,後端服務的 port 原則上不應對外網可達,避免繞過入口直接連到後端。 + +> 本文為了不使用共用 network,後端服務會用 `ports:` publish 到主機;因此「不要對外放行」指的是:不要在路由器/NAT 或雲端安全群組把這些埠開給 Internet。 + +--- + +## 5) Caddy:docker-compose 與 Caddyfile + +### 5.1 Caddy 的 docker-compose.yml(範例) + +檔案位置示意:`docker/caddy/docker-compose.yml` + +```yaml +services: + caddy: + image: caddy:2.10.2 + container_name: caddy + restart: unless-stopped + ports: + - "80:80" + - "443:443" + extra_hosts: + - "host.docker.internal:host-gateway" + volumes: + - ./config/Caddyfile:/etc/caddy/Caddyfile:ro + - ./data/data:/data + - ./data/config:/config +``` + +說明: + +- `host.docker.internal` 在 Docker Desktop(Windows/macOS)通常可用。 +- 在 Linux/Ubuntu 上常見需要加 `extra_hosts: host.docker.internal:host-gateway`,才能讓容器用 `host.docker.internal` 連到宿主機。 + +### 5.2 Caddyfile(範例) + +檔案位置示意:`docker/caddy/config/Caddyfile` + +```caddyfile +{ + # 可選:管理者信箱,用於 ACME + email admin@www.myapp.example +} + +# Authentik(對外入口) +auth.www.myapp.example { + encode zstd gzip + # 反向代理到宿主機上 publish 的 Authentik 9000(HTTP) + reverse_proxy host.docker.internal:9000 +} + +# Gitea(對外入口) +git.www.myapp.example { + encode zstd gzip + # 反向代理到宿主機上 publish 的 Gitea 8020 + reverse_proxy host.docker.internal:8020 +} +``` + +說明: + +- 這份教學為了降低複雜度,讓 **TLS 只由 Caddy 對外負責**。 +- 所以 Caddy 反向代理到 Authentik 的 `9000`(HTTP)即可,避免 `9443` 牽涉到後端 TLS 憑證驗證問題。 + +> 如果你堅持要反向代理到 Authentik 的 `9443`(HTTPS),才需要在 Caddy 端處理後端 TLS(例如自簽憑證可能需要 `tls_insecure_skip_verify`)。但這會讓設定更複雜,也比較不建議。 + +--- + +## 6) Authentik:docker-compose 與 .env + +### 6.1 Authentik 的 .env(範例,請自行填值) + +檔案位置示意:`docker/authentik/.env` + +```dotenv +# Database +PG_PASS=請填入強密碼 +PG_USER=authentik +PG_DB=authentik + +# Authentik +AUTHENTIK_SECRET_KEY=請填入長度足夠的隨機字串 + +# 版本(可固定,避免 latest 帶來不可預期變更) +AUTHENTIK_TAG=2025.12.2 +``` + +安全提醒: + +- `.env` 不要上傳到公開 repo。 +- `PG_PASS`、`AUTHENTIK_SECRET_KEY` 請使用密碼管理器產生。 + +### 6.2 Authentik 的 docker-compose.yml(範例) + +檔案位置示意:`docker/authentik/docker-compose.yml` + +```yaml +services: + postgresql: + image: postgres:16-alpine + restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"] + start_period: 20s + interval: 30s + retries: 5 + timeout: 5s + volumes: + - ./data/postgresql_data:/var/lib/postgresql/data + environment: + POSTGRES_PASSWORD: ${PG_PASS:?database password required} + POSTGRES_USER: ${PG_USER:-authentik} + POSTGRES_DB: ${PG_DB:-authentik} + env_file: + - .env + + redis: + image: redis:alpine + command: --save 60 1 --loglevel warning + restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "redis-cli ping | grep PONG"] + start_period: 20s + interval: 30s + retries: 5 + timeout: 3s + volumes: + - redis:/data + + server: + image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG:-2025.12.2} + container_name: authentik-server + restart: unless-stopped + command: server + environment: + AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:?secret key required} + AUTHENTIK_REDIS__HOST: redis + AUTHENTIK_POSTGRESQL__HOST: postgresql + AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik} + AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik} + AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS} + volumes: + - ./data/authentik/media:/media + - ./data/authentik/custom-templates:/templates + env_file: + - .env + # 透過 ports publish 到宿主機,讓 Caddy 用 host.docker.internal 反向代理 + ports: + - "9000:9000" + depends_on: + postgresql: + condition: service_healthy + redis: + condition: service_healthy + + worker: + image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG:-2025.12.2} + container_name: authentik-worker + restart: unless-stopped + command: worker + environment: + AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:?secret key required} + AUTHENTIK_REDIS__HOST: redis + AUTHENTIK_POSTGRESQL__HOST: postgresql + AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik} + AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik} + AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS} + user: root + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - ./data/authentik/media:/media + - ./data/authentik/certs:/certs + - ./data/authentik/custom-templates:/templates + env_file: + - .env + depends_on: + postgresql: + condition: service_healthy + redis: + condition: service_healthy + +volumes: + redis: + driver: local +``` + +小提醒: +- 這個「不共用 network」的做法,後端服務需要透過 `ports:` publish 到宿主機,才能讓 Caddy 容器透過 `host.docker.internal` 連到它。 +- 安全性上要靠「不要在路由器/NAT/雲端安全群組把 9000/8020 對外放行」,只放行 80/443 給 Caddy。 + +--- + +## 7) Gitea:docker-compose(範例) + +檔案位置示意:`docker/gitea/docker-compose.yml` + +```yaml +services: + gitea: + image: gitea/gitea:latest + container_name: gitea + restart: unless-stopped + environment: + - USER_UID=1000 + - USER_GID=1000 + volumes: + - ./data:/data + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + ports: + - "8020:3000" + - "2244:22" # SSH 是否對外開放依你需求;如果你要用 git@... 的方式推送才需要 +``` + +反向代理補充(很重要,避免 OIDC callback 出現網址不一致): + +- 請確認 Gitea 的「對外 Base URL」是 `https://git.www.myapp.example/`。 +- 若 Base URL 設錯,常見症狀是:Gitea 顯示的 callback URL 會是 `http://...` 或 host/port 不對,導致 Authentik 報 `redirect_uri mismatch`。 +- 設定位置依你的 Gitea 部署方式而不同(例如 `app.ini` 的 `ROOT_URL`,或在管理介面可調),原則是「以使用者實際從瀏覽器進入的網址」為準。 + +如果你要讓 `git.www.myapp.example` 對外提供 SSH(22)而不是用 2244,也可以改成 `"22:22"`,但請確認主機上沒有其他 SSH 服務衝突(例如 OpenSSH)。 + +--- + +## 8) 啟動順序與驗證 + +分開三份 compose 時,建議啟動順序: + +1) Authentik(含 DB/Redis) +2) Gitea +3) Caddy(最後再把入口打開) + +範例指令(在各自資料夾): + +```bash +docker compose up -d +``` + +驗證: + +- 先確認 Authentik 後台能打開:`https://auth.www.myapp.example/` +- 再確認 Gitea 能打開:`https://git.www.myapp.example/` + +--- + +## 9) Authentik × Gitea 整合(以官方文件流程為準) + +本段建議你同時開著官方整合頁:[https://integrations.goauthentik.io/development/gitea/](https://integrations.goauthentik.io/development/gitea/) + +整體思路: + +1) 在 Authentik 建立一個「對 Gitea 用的 OIDC Provider」 +2) 建立 Application,把 provider 掛上去 +3) 把 Discovery URL / Client ID / Client Secret 填進 Gitea 的 Authentication Source + +### 9.1 在 Authentik 建立 Provider + Application + +在 Authentik 管理介面: + +1. 建立 Provider: + + - Providers → Create → **OAuth2/OpenID Provider** + - 重要欄位(名稱以示意為主): + - Name:`gitea-oidc` + - Client type:通常選 Confidential(Gitea 是 server-side) + - Redirect URIs:填入 Gitea 的 callback URL(下一小節會說怎麼確定) + - Scopes:至少包含 `openid`, `email`, `profile` +2. 建立 Application: + + - Applications → Create + - Name:`Gitea` + - Slug:`gitea` + - Provider:選剛剛建立的 `gitea-oidc` + +建立完成後,你會得到: + +- Client ID +- Client Secret + -(通常也會有)OpenID Configuration / Discovery URL + +建議使用「Discovery URL」方式整合,因為端點與簽章金鑰都能自動跟上。 + +Discovery URL(常見樣式,請以你的 Authentik 介面顯示為準): + +- `https://auth.www.myapp.example/application/o/gitea/.well-known/openid-configuration` + +### 9.2 在 Gitea 新增 Authentication Source(OIDC) + +到 Gitea(需管理者權限): + +1. Site Administration → Authentication Sources → Add Authentication Source +2. Type 選 OAuth2(或 OpenID Connect,依 Gitea 版本 UI) +3. Provider 選 OpenID Connect +4. 填入: + - Auto Discovery URL:`https://auth.www.myapp.example/application/o/gitea/.well-known/openid-configuration` + - Client ID:從 Authentik 複製 + - Client Secret:從 Authentik 複製 + - Scopes:`openid email profile` + +建立後,Gitea 通常會顯示 callback URL 類似: + +- `https://git.www.myapp.example/user/oauth2//callback` + +把這個 callback URL 回填到 Authentik provider 的 Redirect URIs(兩邊要一致)。 + +### 9.3 測試登入 + +回到 Gitea 登入頁: + +- 看到「用 Authentik 登入」的按鈕/選項 +- 點下去會跳到 `auth.www.myapp.example` 完成登入 +- 成功後回到 Gitea,首次登入可能會建立/連結本地使用者 + +--- + +## 10) 常見踩雷整理(避免再踩一次) + +你提供的 Perplexity 連結在這個環境下會遇到 403(無法直接讀取頁面內容),所以我沒辦法逐字引用你當時的對話;但我可以把「你目前設定中已經出現/很常遇到」的坑整理成一章,讓你下次少繞路。 + +### 10.1 `host.docker.internal` 在 Linux/Ubuntu 不一定可用 + +- 在 Docker Desktop(Windows/macOS)通常可以用 `host.docker.internal`。 +- 在 Linux 常見情況是 **預設沒有** 這個 DNS 名稱。 + +可行解法(本文採用):在 Caddy container 加上: + +- `extra_hosts: ["host.docker.internal:host-gateway"]` + +### 10.2 反向代理到 Authentik 的 9443 可能遇到 TLS 驗證問題 + +如果你用: + +```caddyfile +reverse_proxy https://...:9443 +``` + +而 Authentik 端使用自簽憑證或內部憑證,Caddy 會因為無法驗證而拒絕。 + +解法: + +- 最簡單:反向代理到 `host.docker.internal:9000`(HTTP),把 TLS 全交給 Caddy 對外處理。 +- 若必須反向代理到 `:9443`:才處理後端 TLS(可能需要 `tls_insecure_skip_verify`),但要理解它是「跳過驗證」。 + +### 10.3 後端服務不要再對外暴露 port(避免繞過 Caddy) + +在本文「不共用 network」的簡化架構裡,Authentik/Gitea 必須 publish port 到宿主機,Caddy 才連得到。 + +建議的安全作法是: + +- **路由器/NAT**:只轉發 80/443 到這台機器 +- **雲端安全群組(Security Group)**:只開 80/443 inbound +- 讓 9000/8020 只留在「主機本地用途」(給 Caddy 反向代理),不要對外網暴露 + +### 10.4 OIDC 回呼網址(Redirect URI)最容易填錯 + +症狀通常是: + +- 登入後跳轉回來直接錯誤(redirect_uri mismatch / invalid redirect) + +原則: + +- 以 **Gitea 顯示的 callback URL** 為準 +- 100% 原樣貼到 Authentik Provider 的 Redirect URIs +- domain、path、https 都要一致 + +### 10.5 反向代理後網址不對(http/host/port 亂跳) + +如果你發現登入流程中 URL 變成 `http://`、或 host/port 被改掉,通常是「應用程式不知道自己在反向代理後面」造成的。 + +- Gitea:先回頭檢查 Base URL/`ROOT_URL` 是否為 `https://git.www.myapp.example/`。 +- Authentik:確保你是透過 `https://auth.www.myapp.example/` 存取管理介面,並檢查是否有「反向代理/受信任代理(trusted proxy)」或「外部 URL(external host/url)」相關設定需要調整。 + +> 這一段在不同 Authentik 版本/部署方式名稱會不一樣,遇到時建議直接依官方文件用關鍵字搜尋:`reverse proxy`、`trusted proxy`、`X-Forwarded-Proto`。 + +--- + +## 11) 小結 + +Authentik 解決了自己架站一個站就要建一次帳號的問題。 + +1. **Caddy**:負責自動化 TLS 與反向代理,解決了惱人的憑證問題,並統一了對外入口(80/443)。 +2. **Authentik**:作為核心的身分驗證中心,未來若要新增其他服務(如 Portainer、Grafana、Nextcloud 等),只要它們支援 OIDC/OAuth2 或 ForwardAuth,都能輕鬆接入,實現真正的單一登入(SSO)。 +3. **Gitea**:成功展示了從「各服務獨立帳號」轉向「集中式帳號管理」的整合流程。 + +雖然初期在理解 OIDC 流程與反向代理設定上需要花點時間,但建立起這套基礎設施後,未來的維護與擴充將會變得非常輕鬆且安全。希望這篇文章能幫助你順利搭建出自己的 Authentik 單一登入環境! + +希望這篇文章有幫到你,謝謝你的閱讀。 diff --git a/deploy.sh b/deploy.sh index 4b5a655..a93f3bd 100644 --- a/deploy.sh +++ b/deploy.sh @@ -1,6 +1,6 @@ #!/bin/bash -CONTAINER_DIR="/lvm1/docker/blog" +CONTAINER_DIR="/DATA01/dockers/blog" IP_ADDR="192.168.1.24" ../../bin/0.100.1_extend/hugo.exe