---
title: 操作手冊
subtitle: 發一版、看結果、憑證過期、換 build 機
---

# 操作手冊

給要發版、或要換 build 機的人看。名詞先講清楚：

- **pipeline**：GitLab 上一次完整的自動化執行，由好幾個 job（工作）組成。
- **runner**：真正去執行 job 的那台機器上的程式。我們的 runner 裝在 188（`192.168.50.188`）。
- **artifacts**：job 跑完後留在 GitLab 上、可以從網頁下載的檔案。
- **SBOM**：軟體物料清單，產品用了哪些套件與版本的「成分表」，給客戶資安稽核用。

## 發一版

發之前先確認：要發的 commit **已經 push 到 GitLab 的 main**。CI 只會拿 GitLab 上有的 commit 來做。

### 方法一：GitLab 網頁

1. 打開 BE 專案（`compliance-manager-be`）的 GitLab 頁面
2. 左側選單 **Build → Pipelines**
3. 右上角 **Run pipeline**
4. Branch 選 `main`
5. `environment` 選 `e2e`
6. `skip_be` 依需要勾選（見下方）
7. 按 **Run pipeline**

**按下去就會部署到 e2e，沒有第二道確認。**

### 方法二：命令列

```bash
glab ci run -b main -i environment:e2e -i skip_be:true
```

要重新編譯 BE 就把 `skip_be:true` 改成 `skip_be:false`（或整個 `-i skip_be:...` 拿掉，預設是不跳過）。`glab` 要先登入過，見下方「本機 git 設定」。

### `skip_be` 什麼時候勾

| 這次改了什麼 | 勾不勾 |
|---|---|
| 有改 BE 程式、BE 依賴（`pyproject.toml`／`poetry.lock`）、或 BE 版號剛 bump | **不勾**，要重新編譯 |
| 只改 FE、init（migration／seed）、或證據分類服務 | **可以勾**，省掉 BE 編譯的 17 分鐘 |

勾了之後，BE 會沿用 188 上同版號最新那顆 image。如果這個版號在 188 上從沒編過 BE，build-be 會直接失敗，這時取消勾選重跑即可。

## 看結果

### Pipelines 頁

Build → Pipelines 會列出每一次執行。點進去看到四段（verify／build／package／deploy），每個 job 一個圓圈：綠勾＝成功、紅叉＝失敗、藍色轉圈＝執行中、灰色＝因前面失敗沒跑到。全程約 30 分鐘（勾 `skip_be` 約 15 分鐘）。

deploy 綠了就代表 190 已經升到新版，可以打開 `https://192.168.50.190` 驗。

### SBOM 在哪

點進 **verify** 或 **package** job，右側 **Job artifacts** 區塊按 **Browse** 或 **Download**，在 `out/sbom/` 底下：

- `*.cdx.json`：SBOM 本體（CycloneDX 格式）
- `osv-*.txt`：OS 層漏洞報告，只供參考，不會擋發版

artifacts 保留 30 天。安裝包裡的 `sbom/` 目錄也有一份同樣的 SBOM。

### 安裝包在哪、怎麼給客戶

安裝包**不在 GitLab 上**（約 2.5GB，超過 GitLab 上限），留在 188：

```
/opt/guidant-ai-be/.build/bundle/guidant-ai-<版號>.tar.gz
/opt/guidant-ai-be/.build/bundle/guidant-ai-<版號>.tar.gz.sha256
```

package job 的 log 最後一行也會印出這個路徑。要給客戶時，兩個檔一起拿走：

```bash
scp jedi@192.168.50.188:/opt/guidant-ai-be/.build/bundle/guidant-ai-<版號>.tar.gz* ./
```

⚠️ CI 封的是**測試包**（跳過正式簽章金鑰檢查）。要交給客戶的正式包，請在 188 上另外跑一次不帶 `--skip-prod-key-check` 的 `build_bundle.sh`。

⚠️ 同版號每發一次就覆蓋一次。要保留某一包，發下一次之前先複製走。

### Harbor 上的 image

位置：`harbor.jedicogy.com.tw:8081` 的 `guidant-ai` 專案。tag 長這樣：

```
guidant-ai-be:1.23.1-a1b2c3d       ← 版號-commit 短 hash
guidant-ai-fe:1.23.1-a1b2c3d
guidant-ai-init:1.23.1-a1b2c3d
evidence-classifier:<分類服務套件版號>   ← 跟自己的套件版號走，不帶 hash
```

看 hash 就知道是哪一個 commit 打出來的。

## 失敗了怎麼看

第一步一律是：**點那個紅叉的 job，看 log 最後幾十行**。腳本失敗時都會印一行 `❌` 開頭的白話原因。

| 紅在哪 | 常見原因 | 怎麼處理 |
|---|---|---|
| **verify** | ① 相依套件有漏洞（log 有 `pip-audit` 結果）② 188 的工作樹不乾淨（log 列出哪些檔）③ 188 上有人正在手跑 build | ① 升級套件，或確認無害後寫進 `scripts/build/audit_ignore.txt`（每行要附理由）② 到 188 `/opt/guidant-ai-be` 看是誰留的改動，處理掉再重跑——CI 不會自動清 ③ 等手跑那條跑完 |
| **build-be** | Nuitka 編譯失敗、smoke 驗證失敗 | 下載 job artifacts 的 `out/logs/`，或直接到 188 看 `/opt/guidant-ai-be/.build/logs/` |
| **build-fe／init／classifier** | 個別 image 打不起來 | 看 job log |
| **package** | 磁碟滿、封包失敗、推 Harbor 失敗 | 看 job log；推 Harbor 失敗先確認 188 的 Harbor 登入還有效 |
| **deploy** | 安裝包傳輸壞掉、升級失敗、服務沒有全部 healthy | 到 190 看升級紀錄：`/srv/guidant-ai/log/upgrade-*.log`（最新那支）；`sudo guidantai status` 看哪個服務不正常 |

修好之後重新 Run pipeline 即可；不需要清任何東西。

## 本機 git 設定

要 push 程式到 GitLab、用 `glab` 發 pipeline，本機要有兩組各自獨立的登入。

### git push 的憑證過期

macOS 會把 GitLab 的登入存在 Keychain（鑰匙圈），憑證到期後的症狀：

1. `git push` 回 `HTTP Basic: Access denied`
2. 之後 Keychain 會自動把那筆清掉，再 push 就變成 `could not read Username`

兩個都是同一件事：憑證過期，要開一組新的 PAT。

### 開 Personal Access Token（PAT）

PAT（個人存取權杖）是拿來代替密碼給 git 用的一組字串。

1. 打開 `https://gitlab.com/-/user_settings/personal_access_tokens`
2. 按 **Add new token**
3. 類型選 **Legacy token**（一組就涵蓋你能存取的所有 repo）
4. 名稱填 `mac-git-push`
5. 到期日設最長（一年）
6. scopes 勾 `read_repository` 與 `write_repository`
7. 按建立。畫面會顯示一串 `glpat-` 開頭的字串，**只會顯示這一次**，先複製起來

這串字串等同密碼：不要貼進任何檔案、文件、聊天訊息或 Notion。

### 存回 Keychain

在終端機跑一次：

```bash
git push origin main
```

會問兩個問題：

- **Username**：填你的 GitLab 帳號
- **Password**：貼剛剛複製的 `glpat-` 字串（畫面不會顯示任何字，貼上直接按 Enter）

push 成功，Keychain 就會自動記住，之後不會再問，直到一年後再次到期。

### `glab` 登入

`glab` 是 GitLab 的官方命令列工具。登入後 Claude 才能用 `glab ci run` 幫你發 pipeline。

```bash
glab auth login
```

依序選：

1. GitLab instance：**gitlab.com**
2. 登入方式：**Web**（會開瀏覽器授權）
3. git 操作協定：**HTTPS**

**`glab` 的登入和 git push 的憑證是分開的兩件事**：一個過期不影響另一個。git push 失敗照上面重開 PAT；`glab` 失敗就重跑 `glab auth login`。

### 推 tag

`git push` 預設不會把 tag 一起推上去。定版打完 tag 後要另外推：

```bash
git push origin v<X.Y.Z>
```

## 換 build 機／重裝 runner

照 `scripts/build/README.md` 末尾「188 runner 怎麼裝的」那張表逐項做，該表是 canonical。重點順序：

1. **裝 gitlab-runner**（官方 apt repo）
2. **讓服務以 `jedi` 使用者跑**：`gitlab-runner install --user jedi --working-directory /home/jedi/gitlab-runner`。poetry、Nuitka、docker 群組、`.venv` 全在 `jedi` 底下，預設的 `gitlab-runner` 帳號什麼都沒有
3. **註冊**：到 GitLab group（`auditmanager`）的 Runners 頁建一個 runner，取得 `glrt-` 開頭的註冊 token，然後：

   ```bash
   gitlab-runner register --non-interactive --url https://gitlab.com --token <glrt-...> --executor shell
   ```

   名稱 `188-guidantai-build`、tag `guidantai-build`、不接沒有 tag 的 job
4. **改並行數**：`/etc/gitlab-runner/config.toml` 的 `concurrent` 設 `3`，build 段的 job 才會平行
5. **移除 `/home/jedi/.bash_logout`**：不移除的話每個 job 都會在準備環境階段就紅（原因見 design.md「踩過的坑」）
6. **裝掃描工具**：`syft`、`osv-scanner` 放 `/usr/local/bin`（只能從 GitHub release 下載，188 下載很慢要耐心等）
7. **登入 Harbor**：用 `jedi` 跑 `docker login harbor.jedicogy.com.tw:8081`（帳密向管理者索取，不寫進任何檔案）
8. **`/etc/hosts` 加一行**：`192.168.50.171 harbor.jedicogy.com.tw`
9. **到 190 的免密 ssh**：`jedi` 能免密碼 `ssh jedi@192.168.50.190`
10. **190 的 `jedi` 要免密 sudo**：190 的 sudoers 給 `jedi` 設 `NOPASSWD: ALL`，`deploy_e2e.sh` 要用它跑 `install.sh --upgrade`
11. **工作樹**：`/opt/guidant-ai-be`（含 `.venv`、Prompt Guard 模型、`.env`）與 `/opt/guidant-ai-fe` 都要就位，路徑和 `.gitlab-ci.yml` 頂部的 `REPO_ROOT`／`FE_REPO` 一致

裝完先手動 Run 一次 pipeline、勾 `skip_be` 跑通，再跑一次不勾的完整版。

## GitLab 專案層要有的設定

- **group runner 已註冊**：runner 註冊在 `auditmanager` group，BE 專案才看得到。到 BE 專案 Settings → CI/CD → Runners 確認有綠燈的 `188-guidantai-build`。
- **FE 專案允許 BE 的 job token**：FE 專案 Settings → CI/CD → Job token permissions 已加 `compliance-manager-be`。目前 CI 讀的是 188 上的 FE 本地 clone、不經這個權限，屬於備用設定，換機器不必動它。
- **Cloudflare Pages 的 `external` 失敗與 CI 無關**：GitLab 的 commit 旁有時會出現一個名為 `external` 的紅叉，那是文件站的 Cloudflare Pages 專案回報的狀態，不是我們的 pipeline。該專案已關閉自動部署，可以忽略。
