# Bucket & Code Server IDE 使用指南

Bucket & Code Server IDE（QPKG `bucketvs`）在 QNAP NAS 上保存原始碼與檔案，並在 NAS 上建置軟體。它自帶網頁伺服器、工具與帳號。

本指南說明套件在 NAS 上執行時的樣子。

## 目錄

- [NAS 上執行的元件](#nas-上執行的元件)
- [安裝套件](#安裝套件)
- [第一次登入](#第一次登入)
- [在面板中工作](#在面板中工作)
- [帳號](#帳號)
- [資料目錄](#資料目錄)
- [網路](#網路)
- [在反向 Proxy（Plesk）後方](#在反向-proxyplesk後方)
- [模組的主機名稱](#模組的主機名稱)
- [發佈到網際網路（Cloudflare Tunnel）](#發佈到網際網路cloudflare-tunnel)
- [模組](#模組)
- [Git](#git)
- [CI runner](#ci-runner)
- [SVN 與 Bucket](#svn-與-bucket)
- [編輯器與 AI 工具](#編輯器與-ai-工具)
- [工具鏈與環境](#工具鏈與環境)
- [App Builder](#app-builder)
- [套件儲存庫](#套件儲存庫)
- [核心工坊](#核心工坊)
- [cron 工作與操作](#cron-工作與操作)
- [備份](#備份)
- [通知](#通知)
- [記錄與支援資料包](#記錄與支援資料包)
- [更新](#更新)
- [bvs 指令](#bvs-指令)
- [檔案與資料夾](#檔案與資料夾)

## NAS 上執行的元件

| 元件 | 功能 | 接聽位置 |
|---|---|---|
| nginx | 區域網路上唯一的入口：提供面板，並把每個路徑轉交給對應的模組 | 9950（HTTP）、9951（HTTPS） |
| bvsd | 啟動並監看其他所有程序，保存帳號與設定，執行工作與 cron 工作，回應面板 | `/usr/bin/bucketvs/var/run/` 中的兩個 Unix socket |
| Git（Forgejo） | 附網頁介面的 Git 代管服務 | `/usr/bin/bucketvs/var/run/forgejo/` 中的一個 Unix socket，以及透過 SSH 使用 Git 的 9952 |
| SVN 與 Bucket（httpd） | Subversion 儲存庫與具備歷史記錄的 WebDAV 資料夾 | 127.0.0.1:9961；其他程式占用時改用下一個可用連接埠 |
| 編輯器（code-server） | 在瀏覽器中執行的 VS Code，每個帳號一個執行個體 | `/usr/bin/bucketvs/var/run/ide/` 中每個帳號各一個 Unix socket |
| CI | Forgejo Actions runner | 沒有自己的連接埠；模組開啟時，nginx 在 127.0.0.1:9960 為它接聽，其他程式占用 9960 時改用下一個可用連接埠 |

核心（nginx、bvsd、面板、cron 工作、工具鏈）一律開啟。Git、CI、SVN 與 Bucket、編輯器是由管理員開啟或關閉的模組。在 NAS 的每個位址上，各模組都在自己的路徑下回應：`/git/`、`/svn/`、`/bucket/`、`/ide/`。面板位於 `/`，本指南位於 `/guide/`。

套件執行期間，其程式位於 `/usr/bin/bucketvs/bin`。套件保存的一切（帳號、設定、儲存庫、環境、記錄）都放在資料磁碟區上的一個資料夾中，也就是[資料目錄](#資料目錄)。

## 安裝套件

套件需要 QTS 6.0 或 QuTS hero 6.0 以上版本，以及 x86_64 或 ARM 64 位元的 NAS；每種架構各有一個套件（`bucketvs_<版本>_x86_64.qpkg`、`bucketvs_<版本>_arm_64.qpkg`）。套件具有程式碼簽署：請從 App Center 安裝（從列出它的儲存庫，或以下載的檔案使用 **手動安裝**）。App Center 會檢查簽署，不需要允許沒有有效簽署的應用程式。

安裝時會建立服務使用者 `bucketvs`（供 Forgejo 與 httpd 使用）、指向安裝資料夾的連結 `/usr/bin/bucketvs`，並啟動 bvsd。系統記錄會寫明資料目錄。

### 資料目錄的初始位置

第一次啟動時，資料目錄是套件所在磁碟區上的 `BucketVS`，例如 `/share/CACHEDEV1_DATA/BucketVS`（QTS）或 `/share/ZFS2_DATA/BucketVS`（QuTS hero）。之後可以從面板把它搬到其他磁碟區（[搬移資料目錄](#搬移資料目錄)）。

套件把資料目錄記在 `/etc/config/bucketvs-data-root`，這個檔案在移除套件後仍會保留。移除套件時，資料目錄與這個檔案都會保留，因此移除後重新安裝，會以相同的資料與相同的帳號啟動。啟動絕不依賴不存在的資料夾。當資料目錄已不存在（已被刪除、所在的硬碟已更換，或所在的磁碟區當時離線）時，啟動會在套件所在磁碟區建立 `BucketVS`。除非那裡已有同名的資料夾，否則這是一個新資料夾；在新資料夾上，[第一次登入](#第一次登入)會設定管理員的密碼。重新安裝後與已安裝的套件都是如此，NAS 開機時也一樣。系統記錄會留下一則警告，`.prev` 檔案則保留舊的路徑：`/etc/config/bucketvs-data-root.prev`，已安裝的套件還有安裝資料夾中的 `etc/data-root.prev`。如果舊資料夾所在的磁碟區當時只是離線，請讓磁碟區重新上線、停止套件，把該檔案複製覆蓋到安裝資料夾中的 `etc/data-root`（例如 `/share/CACHEDEV1_DATA/.qpkg/bucketvs/etc/data-root`），再啟動套件：它會重新使用舊的資料。

### 從 App Center 開啟面板

在 App Center 中對套件按 **開啟**，會以連接埠 9950（HTTP）或 9951（HTTPS）開啟面板。在面板中變更連接埠後（[連接埠](#連接埠)），套件會在下次啟動時把新的連接埠告知 App Center。

### 從 SSH 工作階段設定自動啟動

只要套件在 App Center 中的開關是開啟的，NAS 開機時就會啟動套件。在 NAS 的 SSH 工作階段中，管理員可以用服務指令碼設定同一個開關：

```sh
/etc/init.d/bucketvs.sh disable   # 下次啟動會被拒絕，開機時也一樣
/etc/init.d/bucketvs.sh enable    # 套件會再次隨開機啟動
```

這兩個指令都只寫入開關（`/etc/config/qpkg.conf` 中的 `Enable`），會顯示設定了什麼，並把同一行寫入系統記錄。執行中的套件在 `disable` 之後會繼續執行，`enable` 也不會啟動任何東西：啟動與停止由 `start` 和 `stop` 負責，套件停用期間 `start` 會拒絕執行。`status` 會顯示 bvsd 是否在執行。

## 第一次登入

以 `https://<NAS 位址>:9951/` 或 `http://<NAS 位址>:9950/` 開啟面板。HTTPS 位址一開始使用自我簽署憑證，因此在裝置信任它之前，瀏覽器會顯示警告；比對憑證指紋的方法請見[憑證](#憑證)一節。

新的安裝沒有任何帳號。在第一位管理員設定密碼之前，面板只會開啟一個畫面「設定管理員密碼」，用於帳號 `administrator`。請輸入密碼兩次：至少 10 個字元，最多 72 位元組。儲存後就會建立帳號並登入。

```mermaid
flowchart TD
    start["套件啟動<br/>沒有任何帳號"] --> screen["設定管理員密碼<br/>60 分鐘，僅限區域網路"]
    screen -->|"已儲存"| panel["administrator 已登入"]
    screen -->|"已過 60 分鐘"| restart["在 App Center 重新啟動套件：<br/>重新開放 60 分鐘"]
    restart --> screen
```

- 套件啟動後 60 分鐘內，這個畫面接受密碼。之後畫面會表示設定時段已結束；在 App Center 重新啟動 Bucket & Code Server IDE，會重新開放 60 分鐘。
- 它只接受來自區域網路的密碼：來自 NAS 本身，或來自私有範圍的位址（`10.0.0.0/8`、`172.16.0.0/12`、`192.168.0.0/16`、`169.254.0.0/16`、`fc00::/7`、`fe80::/10`）。
- 畫面開啟期間，QTS 系統記錄會為 NAS 的每個區域網路位址寫入一筆資訊記錄，例如：

  ```text
  bucketvs: no administrator yet: open https://192.168.1.20:9951/ within 60 minutes to set the administrator's password
  ```

- 經由 Proxy 送來的請求（帶有 `X-Forwarded-For`、`X-Forwarded-Host`、`X-Forwarded-Proto`、`X-Real-IP` 或 `Forwarded`）一律拒絕，即使 Proxy 位於區域網路也一樣：請從區域網路中的電腦直接開啟面板來設定密碼。

在 NAS 的 SSH 工作階段中執行 `/usr/bin/bucketvs/bin/bvs user password administrator`，會在尚無帳號時建立帳號並印出隨機密碼；之後第一次登入時，面板會要求您設定自己的密碼（[管理員被鎖在外面時](#管理員被鎖在外面時)）。

首次登入後，總覽頁面會顯示目前只有核心在執行。每個模組的圖塊會說明該模組的用途，並有開啟它的按鈕，例如 **開啟 Git**：開啟您需要的模組（[模組](#模組)）。

## 在面板中工作

面板的操作方式類似程式碼編輯器：每個頁面在分頁中開啟，命令選擇區可依名稱找到頁面與動作。

- **活動列**（左側的圖示）。每個圖示會開啟對應頁面，並在側邊欄顯示其樹狀結構：模組、儲存庫與 bucket、應用程式、套件、環境、工作與備份。圖示上的圓點表示有問題的模組、失敗的建置或失敗的備份。按一下目前頁面的圖示會隱藏側邊欄；Ctrl+B 也一樣。
- **分頁**。以單擊開啟的頁面是預覽，以斜體顯示：下一個開啟的頁面會取代它。編輯頁面或按兩下分頁即可保留。重新載入後，分頁會依瀏覽器與帳號分別還原。在寬度至少 1280 像素的視窗中，**把分頁移到另一側**（`Ctrl+\`）可以並排顯示兩個頁面。
- **物件與其選單**。在側邊欄的列、表格的列或卡片上按一下，即可開啟該物件：其頁面移到前方，物件以綠框標示，或開啟其詳細資料。網址會指出該物件，因此上一頁、下一頁與複製的連結都會回到這裡。按兩下會保留分頁。按右鍵、Shift+F10，或在觸控螢幕上長按，即可開啟物件的選單：開啟方式（在此分頁、在保留的分頁、在另一側、在瀏覽器的新分頁），其頁面能對它執行的動作，以及複製名稱與網址。分頁的選單可關閉該分頁、其他分頁、右側的分頁或全部分頁。只有標示 ↗ 的項目會離開面板。**管理** 圖示會開啟系統頁面。
- **命令選擇區**。Ctrl+Shift+P（或 Ctrl+K、F1）會列出您的角色可執行的指令：開啟頁面、建置或發佈應用程式、執行備份、重新啟動模組、在環境中開啟 shell。Ctrl+P 只要輸入名稱的幾個字母，就能前往頁面、應用程式、儲存庫、環境或套件。
- **底部面板**（Ctrl+J）。**工作** 顯示執行中與最近的工作，以及其進度與記錄。**問題** 列出模組回報的事項：失敗的建置、已停用的備份目標、有問題的模組、即將到期的憑證，以及 ELF 檢視器對目前應用程式的發現。**輸出** 追蹤您最後啟動的工作。
- **移動與調整大小**。拖曳邊界可改變它所相鄰部分的大小：側邊欄、底部面板、並排的兩個頁面，或頁面中並排的兩個部分，例如精靈與其摘要，或專案的檔案清單與檔案。按兩下邊界會讓它還原。拖曳對話框的標題可移動它，拖曳邊緣或右下角可調整大小；按兩下標題會讓它回到中央。若要把底部面板放到頁面旁邊，請把面板的標題列拖到編輯器的右邊緣，或按一下 × 旁的按鈕；拖到下邊緣或再按一次按鈕即可放回下方。分頁可以拖到分頁列的其他位置，或拖到另一側。面板會在這個瀏覽器中為您的帳號保留每個大小與位置，**重設版面配置**（在命令面板中，或在活動列底部您帳號的按鈕裡）會全部還原。使用鍵盤時，方向鍵可移動焦點所在的邊界（按住 Shift 一次移動較多，Home 會還原），Ctrl+Shift+左右鍵可移動焦點所在的分頁；對話框上方有一條橫桿、右下角有一個握把，可用方向鍵移動和調整大小。在手機上，對話框與底部面板維持原狀。
- **狀態列**。模組狀態、執行中的工作、問題、資料磁碟區的可用空間、憑證、已登入的帳號、語言（編輯器會在下次啟動時採用此語言，瑞典文與荷蘭文則維持英文；Git 也會跟著套用，除非您在其中另選語言）、主題（深色、淺色、跟隨系統、深藍、石墨或紙張；主題屬於您的帳號，編輯器與 Git 也會跟著套用，除非您在其中另選主題）與版本。每個項目都會開啟可進一步查看的地方。
- **鍵盤**。Ctrl+/ 或 ? 列出快速鍵。瀏覽器分頁會自行保留 Ctrl+W 與 Ctrl+Tab，因此面板以 Alt+W 關閉分頁，以 Alt+PageDown 與 Alt+PageUp 切換分頁。
- **在手機上**，前幾個活動位於底部，**更多** 會開啟含其餘項目的側邊欄，已開啟的頁面則以清單顯示在頂端。

## 帳號

面板有自己的帳號。QTS 帳號無法登入面板。

### 角色

| 角色 | 可以做的事 |
|---|---|
| viewer | 讀取面板；備份、儲存空間、使用者與記錄頁面僅限管理員。在 Forgejo 中不能建立、分叉或匯入儲存庫，不能建立組織，也永遠不能推送；可讀取有權限的 SVN 儲存庫和 Bucket，但不能寫入任何一個 |
| member | 可讀取 viewer 能讀取的內容，並且可以寫入：推送到獲得寫入權限的 Git 儲存庫，寫入有讀寫權限的 SVN 儲存庫和 Bucket，並在其中還原檔案的舊版本。在 Forgejo 中不能建立、分叉或匯入儲存庫，也不能建立組織；無法推送到由低於 developer 的帳號擁有或管理的儲存庫；CI 模組開啟時，也無法推送到已開啟 Actions 的儲存庫。不能使用編輯器、cron 工作或建置 |
| developer | 另外還能開啟編輯器、讀取已安裝工具鏈的 `bvs use` 行、建立、變更與執行自己的 cron 工作、建立與建置應用程式、把套件發佈到儲存庫（由管理員列出）、檢查核心模組，並在 CI 模組開啟時建立其工作流程會在 NAS 上執行的 Git 儲存庫 |
| admin | 全部：帳號、設定、儲存空間、網路、模組、工具鏈、環境、所有 cron 工作、啟用應用程式、載入核心模組、在儲存庫中列出套件 |

developer 角色在 NAS 上等同 root：它的 cron 指令以 root 執行，其編輯器的終端機也是；CI 模組開啟時，它能推送的儲存庫的工作流程也是。請只把這個角色交給您願意告知 NAS admin 密碼的人。只需要寫入儲存庫的人，member 角色就足夠。

每個人都可以在 **您的帳號** 中看到自己的角色及其允許的操作，並有連到本節的連結。超出角色的頁面會說明需要哪個角色，而只有管理員能變更的項目，會在顯示之處註明。網路、Git 與 CI 會以數值向其他角色顯示其設定，空白的值顯示為「未設定」。

工作流程會以 root 身分在 NAS 上執行，因此 bvsd 會讓 member 或 viewer 擁有或能寫入的每個 Git 儲存庫保持 Actions 關閉：給予這類帳號某個儲存庫的寫入權限，該儲存庫的 Actions 會在一分鐘內關閉，Git 頁面會為管理員列出這些儲存庫。若要在其中使用 Actions，請先在 Forgejo 移除該寫入權限，或把這類帳號擁有的儲存庫移轉到 developer 的帳號。

### 建立與變更帳號

管理員在使用者頁面管理帳號。

- 新帳號一開始是 viewer 角色。developer 與 admin 要刻意選擇：developer 角色就是 NAS 上的 root（見上文）。
- 新帳號一開始沒有任何 cron 工作，即使先前有同名帳號擁有過工作也一樣。
- 名稱為 2 到 32 個字元：第一個是小寫英文字母，之後可用小寫英文字母、數字、`.`、`_` 或 `-`。連續兩個 `.`、`_`、`-`，或以其中之一結尾，都會被拒絕。Forgejo 保留的名稱（`admin`、`api`、`user` 等）、`bucketvs`（套件的系統使用者）、`cli` 與 `system`（兩者都由 bvsd 自己使用）也會被拒絕。
- 「首次登入時必須設定自己的密碼」會讓帳號必須先設定自己的密碼，才能做其他任何事。
- 停用帳號會立即結束其工作階段並停止其 API 權杖。資料會保留。其[cron 工作](#排程工作)也會保留，但在停用期間不會執行，正在進行的執行會被停止。
- 將帳號改為 member 或 viewer 角色時，其 cron 工作也是如此：工作會保留，在角色低於 developer 期間不會執行，正在進行的執行會被停止。
- viewer 永遠不會推送到 Git，即使在 Forgejo 中獲得了寫入權限也一樣。需要推送的帳號請給予 member 角色。
- 刪除帳號會連同其工作階段與 API 權杖一起移除。其家目錄會移到資料目錄中的 `trash/home/<名稱>-<時間>`，7 天後由工作「清空資源回收筒」移除；其 `tmp/tmux-<名稱>` 資料夾會被移除。其 cron 工作會連同執行與記錄一起刪除。
- 最後一位已啟用的管理員不能被降級、停用或刪除。
- 「設定新密碼」會結束該帳號的工作階段並撤銷其 API 權杖。

### 密碼與工作階段

- 密碼至少 10 個字元，最多 72 位元組。
- 變更自己的密碼（在「您的帳號」中）會結束您的其他工作階段並撤銷您的 API 權杖。用來變更密碼的工作階段仍保持登入。
- 工作階段在最後一次請求後 12 小時結束，且無論如何都在登入後 7 天結束。每個帳號最多保有 32 個工作階段；再登入一次會結束最久未使用的那一個。
- 工作階段 Cookie 為 `HttpOnly` 與 `SameSite=Lax`。瀏覽器直接或經由[信任的 Proxy](#信任的-proxy) 以 HTTPS 連到 NAS 時，Cookie 會標記為 `Secure`。

### 密碼錯誤

- 同一位址在 15 分鐘內登入失敗 5 次，該位址會被鎖定 15 分鐘，即使密碼正確也一樣。IPv6 用戶端以其 /64 網路計算。
- 同一位址對同一帳號失敗 5 次，會鎖定該帳號從該位址登入 15 分鐘。
- 來自許多位址對同一帳號的失敗會讓之後的登入變慢：失敗一次後延遲 125 毫秒，之後加倍，最多 2 秒。來自區域網路的失敗絕不會鎖定帳號。
- 從網際網路（區域網路以外的任何位址），自上次成功登入起對同一帳號登入失敗 20 次，無論來自哪些位址，都會鎖定該帳號從網際網路登入，此時連正確的密碼也會被拒絕。帳號仍可從區域網路登入。管理員可在「使用者」頁面（**解除鎖定**）或用 `bvs user unlock <名稱>` 解除鎖定，或給它新密碼。次數在「使用者」頁面的 **來自網際網路的登入** 設定；0 關閉此鎖定。
- 這些計數保存在記憶體中，因此重新啟動 bvsd 會清除它們；鎖定會一直保留到被解除為止。

在 Proxy 後方時，計算的是 Proxy 回報的位址，但前提是該 Proxy 是[信任的 Proxy](#信任的-proxy)。否則每個用戶端都會被算成 Proxy 本身的位址，任何人輸錯 5 次密碼，就會把所有人鎖在外面 15 分鐘。

### 兩步驟驗證

每個帳號在「您的帳號」中自行開啟：用驗證器 App 掃描 QR code（或輸入金鑰），再輸入 App 顯示的 6 位數代碼。之後，登入時會在密碼之後要求輸入代碼。

- 代碼遵循 RFC 6238：6 位數，每 30 秒產生新代碼。早或晚 30 秒的代碼也會接受。同一組代碼絕不會接受兩次。
- 如果剛產生的代碼無法通過，手機時鐘可能不準；請把它設為自動校時。
- 關閉兩步驟驗證需要帳號密碼。
- 手機遺失時，由管理員在使用者頁面將其關閉。之後只要密碼即可登入。

### API 權杖

指令碼與 CI 使用 API 權杖代替密碼。在「您的帳號」中，「新權杖」會建立一個權杖：

- 格式為 `bvs_` 加上 40 個十六進位數字，只顯示一次。bvsd 只保存其雜湊。
- 指令碼以 `Authorization: Bearer bvs_...` 傳送。
- 權杖的範圍為 `read`、`write` 與 `admin`，分別對應角色 viewer、developer 與 admin。權杖以其最高範圍運作，但絕不超過擁有者目前的角色，因此降級帳號也會降級其權杖。沒有對應 member 的範圍：member 只能建立 `read` 權杖。
- 設定新密碼會撤銷擁有者的所有權杖。已停用擁有者的權杖將無法使用。
- 權杖不能變更密碼或兩步驟驗證，也不能建立或刪除權杖：這些都需要已登入的工作階段。因此外洩的權杖無法把擁有者鎖在外面。

### 管理員被鎖在外面時

NAS 管理員可以從 NAS 上的 SSH 工作階段重設任何帳號。`bvs` 指令透過只有 root 能開啟的 socket（`/usr/bin/bucketvs/var/run/bvsd-cli.sock`，模式 600）與 bvsd 通訊：

```bash
/usr/bin/bucketvs/bin/bvs user list
/usr/bin/bucketvs/bin/bvs user password administrator
/usr/bin/bucketvs/bin/bvs user totp-reset administrator
/usr/bin/bucketvs/bin/bvs user unlock administrator
```

- `bvs user password <名稱>` 設定一組 20 個字元的新隨機密碼並印出。該帳號下次登入時必須設定自己的密碼。其工作階段會結束，API 權杖會被撤銷。在尚無任何帳號的新安裝上，`bvs user password administrator` 會建立該帳號。
- `bvs user totp-reset <名稱>` 關閉該帳號的兩步驟驗證。
- `bvs user unlock <名稱>` 解除帳號因來自網際網路的錯誤密碼過多而被鎖定的狀態。

該 socket 上的每個請求都以內建管理員 `cli` 的身分執行，並寫入資料目錄的 `log/audit.log`。執行這些指令時 bvsd 必須在執行中。

## 資料目錄

資料目錄是存放套件所保存一切內容的資料夾。其路徑是 `/usr/bin/bucketvs/etc/data-root` 中唯一的一行。儲存空間頁面會顯示它、它所在的磁碟區，以及該處的可用空間。

只有當資料目錄是實體磁碟區上的資料夾時，bvsd 才會啟動。套件的啟動程序會先檢查這一點：資料夾不存在，或位於 tmpfs 或 ramfs 上時，會依[資料目錄的初始位置](#資料目錄的初始位置)所述，在安裝所在的磁碟區建立新的資料目錄。離線的磁碟區會在 NAS 的 RAM 中留下空的掛載點資料夾，寫在那裡的設定會在下次重新開機後消失。bvsd 本身只有在被手動啟動時才會拒絕這樣的資料夾，原因會寫入 QTS 系統記錄。

### bvsd 給每個程序的環境

QTS 把 `/tmp` 放在小型 RAM 磁碟中，把 `/root` 放在重新開機就會清空的 RAM 磁碟上。因此，bvsd 啟動的每個程序（模組程式、cron 工作、安裝）都會在資料目錄中取得暫存檔案與家目錄：

| 變數 | 值 |
|---|---|
| `HOME` | `<資料目錄>/home/<使用者>` |
| `TMPDIR` | `<資料目錄>/tmp` |
| `XDG_CACHE_HOME`、`XDG_CONFIG_HOME` | 該家目錄中的 `.cache` 與 `.config` |
| `npm_config_cache` | 該家目錄中的 `.npm` |
| `PATH` | `~/.local/bin`、`~/.npm-global/bin`，接著是套件的 `bin`、`sbin` 與 `node/bin`，再來是系統資料夾 |
| `SSL_CERT_FILE`、`GIT_SSL_CAINFO`、`CURL_CA_BUNDLE` | 套件的 CA 憑證包 `/usr/bin/bucketvs/etc/ssl/cacert.pem` |
| `TZ` | QTS 中設定的 NAS 時區 |
| `SHELL`、`EDITOR` | 套件的 `bash` 與 `nano` |
| `LANG`、`LESSCHARSET` | `C.UTF-8` 與 `utf-8`，讓含有中文等字元的檔名照原樣顯示 |

### 搬移資料目錄

儲存空間頁面可以把整個資料目錄搬到 `/share` 下的其他資料夾。選擇共用資料夾或磁碟區後，資料會放進其中名為 `BucketVS` 的新資料夾。若要直接使用某個資料夾，請開啟一個名為 `BucketVS` 的空資料夾。共用資料夾在 QTS 中建立（控制台、共用資料夾）；在共用資料夾或磁碟區內，此頁面可以建立新資料夾。選擇視窗會在資料目前所在的位置開啟並註明這點；請從那裡開啟要移往的共用資料夾或磁碟區。

開始搬移前，頁面會檢查目標，並列出拒絕的所有理由：

- 路徑必須位於 `/share` 之下，且不能是 `/share` 本身；
- 路徑的任何部分，以及連結所指向的路徑，都不能含有 `:`、`&`、`|` 或控制字元；
- 不能是目前的資料目錄、其中的資料夾，或包含它的資料夾；
- 必須是新資料夾或空資料夾；
- 檔案系統必須保留 Unix 擁有者與權限（FAT、exFAT 與 NTFS 會被拒絕：Forgejo 與 SVN 需要擁有者與模式）；
- 磁碟區必須以建置環境所需的 `exec`、`dev` 與 `suid` 掛載；
- 必須可寫入，且可用空間足以容納資料再加 10 %。

```mermaid
flowchart TD
    check["再次檢查目標<br/>可用空間：資料 + 10 %"] --> stop["停止模組<br/>面板繼續運作"]
    stop --> copy["複製<br/>rsync -aHAX --numeric-ids"]
    copy --> verify["驗證副本<br/>檔案、位元組、擁有者、模式、時間、<br/>最多 1000 個檔案的 sha256"]
    verify --> switch["切換資料目錄<br/>/usr/bin/bucketvs/etc/data-root"]
    switch --> start["bvsd 以新的根目錄重新啟動<br/>並啟動模組"]
    start --> old["舊副本保留並標記 MOVED-TO<br/>直到您移除它"]
    copy -->|失敗| failed["繼續使用舊的根目錄<br/>模組重新啟動"]
    verify -->|有差異| failed
```

搬移進行期間：

- Git、CI、SVN 與 Bucket、編輯器不會回應。面板繼續運作並顯示進度。
- 不會開始新的 cron 執行。已在進行的執行會繼續。
- 在搬移結束前，開啟或關閉模組都會被拒絕。

切換後，bvsd 會停止並以新的根目錄重新啟動自己，因此面板會暫時無法使用幾秒鐘。

搬移失敗時，會繼續使用舊的資料目錄，模組也會重新啟動。工作的錯誤訊息會說明哪裡出錯，以及副本的處理方式：

- 磁碟區已滿或取消時，會移除搬移所建立的內容。
- 其他任何失敗（複製、檢查、寫入磁碟）都會保留副本，並以名為 `INCOMPLETE` 的檔案標記，在儲存空間頁面的「搬移失敗後遺留的副本」下列出。
- 搬移期間套件停止時，舊的根目錄仍是資料目錄。下次啟動時，bvsd 會在系統記錄中回報未完成的搬移，並列出該副本。

### 舊副本與遺留的副本

搬移成功後，舊的資料目錄仍留在原處，其中有一個名為 `MOVED-TO` 的檔案。沒有任何東西使用它。儲存空間頁面上的「移除舊副本」會刪除它，「移除此副本」會刪除搬移失敗後遺留的副本。兩種移除都只在同一個檔案系統內進行，絕不跟隨連結。若資料夾是掛載點或共用資料夾本身的資料夾，則只清空內容並保留資料夾。

## 網路

網路頁面包含連接埠、一般 HTTP 的處理方式、模組的主機名稱、信任的 Proxy、公開 URL 與憑證。儲存時會重新啟動變更所需的部分，並列出它們。

### 連接埠

| 設定 | 預設值 | 說明 |
|---|---|---|
| HTTPS | 9951 | 透過 TLS 的面板與模組 |
| HTTP | 9950 | 透過一般 HTTP 的面板與模組 |
| 透過 SSH 使用 Git | 9952 | 透過 SSH 使用 Git |
| 複製 URL 中的 SSH 連接埠 | 0 | SSH 複製位址中顯示的連接埠；0 表示使用上方的 SSH 連接埠。當路由器把其他連接埠轉送到 NAS 時請設定此項。 |
| 複製 URL 中的 SSH 主機 | 空的 | SSH 複製位址中顯示的名稱或位址。空的表示使用 Git 主機名稱，沒有時使用公開 URL 的主機，再沒有時使用 NAS 的區域網路位址；對透過 Cloudflare Tunnel 發佈的名稱，則使用區域網路位址與「透過 SSH 使用 Git」的連接埠。請設為僅 DNS 的名稱，或在路由器轉送 SSH 連接埠時設為路由器的位址。 |

連接埠範圍為 1024 到 65535，且彼此不能相同。127.0.0.1 上的模組連接埠（9960 與 9961）會被拒絕；當其他程式占用 9960 或 9961 時，httpd 或 CI runner 伺服器改用的連接埠也會被拒絕（訊息會指出該連接埠）。若因無法使用新連接埠而讓 nginx 在儲存後仍停留在舊連接埠，通知會說明原因。NAS 具備 IPv6 時，nginx 也會在 IPv6 上接聽。

### 一般 HTTP

- 「直接提供」（預設）：面板與模組也會以一般 HTTP 回應。當區域網路上的 Proxy 以一般 HTTP 連到連接埠 9950 時，請保留此設定。
- 「重新導向到 HTTPS」：一般 HTTP 的請求會收到導向 HTTPS 連接埠的重新導向（307）。來自信任的 Proxy、且表示其用戶端使用 HTTPS 的請求，會以一般 HTTP 回應，避免這類 Proxy 陷入迴圈。

登入頁面經由一般 HTTP 開啟時會顯示警告：此時密碼會以可讀的形式在網路上傳送。

### 憑證

第一次啟動時，bvsd 會為下列名稱建立自我簽署憑證（ECDSA P-256）：

- NAS 的主機名稱、模組的主機名稱與 `localhost`；
- NAS 的區域網路位址與 `127.0.0.1`。

有效期為 825 天，這是 Apple 裝置可接受的上限，即使是手動信任的憑證也一樣。bvsd 在啟動時與每天一次檢查憑證，當剩餘 30 天以下、名稱組合改變或金鑰不相符時，會建立新憑證。建立新憑證時會送出[通知](#通知)。網路頁面會顯示其名稱、有效期間與 SHA-256 指紋：在裝置信任它之前，請先比對指紋。

「新增自己的憑證」接受 PEM 格式的憑證鏈及其私密金鑰：

- 先放伺服器憑證，再放中繼憑證；
- 金鑰不能有密碼（nginx 需要無人值守啟動）；
- 金鑰必須屬於第一張憑證，每張憑證都必須由下一張簽署，任何一張都不能過期，且第一張必須以主體別名（SAN）列出其主機。

這組憑證與金鑰以模式 600 儲存在資料目錄的 `etc/tls/`。當上傳的憑證過期或不再通過這些檢查時，nginx 會改用自我簽署憑證，並以通知說明原因。上傳的憑證剩餘 30 天以下時也會送出通知。「新的自我簽署憑證」會建立一組新的自我簽署憑證並切換回它。

經由 Proxy 連線的用戶端看到的是 Proxy 的憑證，而不是這張憑證。

「網路」頁面上的 **下載憑證** 會把面板提供的憑證（不含金鑰）存成 `<名稱>.crt`。把它加入裝置的受信任憑證後，該裝置的瀏覽器就會信任面板；編輯器的擴充功能檢視需要這一點（[擴充功能](#擴充功能)）。新的自我簽署憑證需要重新信任。

### 信任的 Proxy

IP 位址或 CIDR 範圍的清單，例如 NAS 前方 Proxy 的位址 `192.168.1.5`。只有 TCP 連線來自其中之一的請求，nginx 才會採用：

- `X-Forwarded-For` 中的用戶端位址；
- `X-Forwarded-Proto` 中用戶端使用的協定（只接受 `http` 或 `https`）。

來自其他位址的請求一律視為直接來自其用戶端，nginx 會以自己的值取代其 `X-Forwarded-*` 標頭。指定範圍時，範圍內的每台主機都會受到信任，都能設定用戶端位址與協定，因此請填入 Proxy 本身的位址。涵蓋所有位址的範圍（`0.0.0.0/0`、`::/0`）會被拒絕。

### 公開 URL

只填位址，例如 `https://git.example.com`，不含路徑。各模組的章節會說明哪些連結以它開頭。

## 在反向 Proxy（Plesk）後方

常見的做法是由具有公開憑證的 Proxy 處理 TLS，再由它以一般 HTTP 連到區域網路上的連接埠 9950。以下範例以 `bvs.example.com` 作為公開名稱，`192.168.1.20` 為 NAS，`192.168.1.5` 為 Plesk 伺服器；請換成您自己的值。

```mermaid
flowchart LR
    client["瀏覽器、git、WebDAV 用戶端"] -->|"HTTPS 443"| plesk["Plesk 的 nginx<br/>bvs.example.com<br/>公開憑證"]
    plesk -->|"HTTP 9950<br/>Host, X-Forwarded-For,<br/>X-Forwarded-Proto: https,<br/>WebSocket 用的 Upgrade"| nas["NAS 上的 nginx<br/>192.168.1.20"]
    nas --> panel["/ 面板，/api/ bvsd"]
    nas --> mods["/git/, /svn/, /bucket/, /ide/"]
    sshc["透過 SSH 的 git"] -->|"TCP 9952，不經過 Proxy"| nas
```

### 在 NAS 上

1. 在網路頁面上，把 Plesk 伺服器的位址（以 NAS 看到的為準）加入信任的 Proxy。
2. 一般 HTTP 可以維持「直接提供」。Plesk 伺服器成為信任的 Proxy 後，「重新導向到 HTTPS」也能正常運作。

### 在 Plesk 中

把下列指令放進網域的 Apache 與 nginx 設定中，「其他 nginx 指令」欄位（英文介面為「Additional nginx directives」）：

```nginx
location ~ ^/ {
    proxy_pass http://192.168.1.20:9950;
    proxy_http_version 1.1;

    proxy_set_header Host $http_host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;

    # WebSocket：編輯器及其終端機
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $http_connection;

    # 大型推送與上傳：不限大小、串流傳輸、允許一小時無資料
    client_max_body_size 0;
    proxy_request_buffering off;
    proxy_buffering off;
    proxy_read_timeout 3600s;
    proxy_send_timeout 3600s;
}
```

`location ~ ^/` 是正規表示式 location。nginx 會優先採用相符的正規表示式，而不是一般的 `location /`（Plesk 會自己寫一個），因此這個區塊會處理網站的每個路徑，而不帶路徑的 `proxy_pass` 會把請求路徑原封不動地轉交。請在同一個設定頁面關閉「由 nginx 直接提供靜態檔案」：若開啟，Plesk 會從網域自己的資料夾回應 `.js`、`.css` 與圖片檔案的請求，面板將無法載入。

在網域的代管設定中，開啟從 HTTP 到 HTTPS 的永久 SEO 安全 301 重新導向（英文介面為「Permanent SEO-safe 301 redirect from HTTP to HTTPS」）。其他 nginx 指令也會套用到網域在連接埠 80 上的一般 HTTP 網站；沒有重新導向時，網站在那裡也會回應，密碼將以未加密的方式在網際網路上傳送。

每一行的作用：

| 指令 | 缺少時的問題 |
|---|---|
| `Host $http_host` | NAS 上的 nginx 依 `Host` 選擇伺服器區塊，因此模組的主機名稱永遠不會相符。bvsd 會比對頁面的 `Origin` 與 `Host`（包含連接埠），因此面板中的每項變更都會以 `403 csrf` 失敗；使用 `$host` 會丟掉 443 以外的公開連接埠，同樣會失敗。 |
| `X-Forwarded-For` | 每個用戶端都被算成 Plesk 伺服器：一個人輸錯 5 次密碼，就會把所有人鎖在外面 15 分鐘 |
| `X-Forwarded-Proto $scheme` | 工作階段 Cookie 不會標記為 `Secure`，且在「重新導向到 HTTPS」模式下，每個請求都會被送到公開名稱的連接埠 9951 |
| `Upgrade`、`Connection` | 編輯器的頁面能開啟，但編輯器及其終端機永遠連不上 |
| `client_max_body_size 0`、`proxy_request_buffering off`、逾時設定 | 大型推送與上傳會受到 Proxy 本身的大小限制與逾時影響 |

只有當 Plesk 伺服器列在信任的 Proxy 中時，`X-Forwarded-For` 與 `X-Forwarded-Proto` 才會生效。否則 NAS 上的 nginx 會忽略它們。

### 透過 SSH 使用 Git

HTTP Proxy 不會轉送 SSH。透過 SSH 使用 Git 時，請直接連到 NAS 的連接埠 9952，或經由路由器的 TCP 連接埠轉送。網路頁面上的「複製 URL 中的 SSH 連接埠」設定複製位址所顯示的連接埠；公開名稱只能到達 Proxy 時，「複製 URL 中的 SSH 主機」設定其名稱或位址。

### 檢查設定

- 面板可在 `https://bvs.example.com/` 開啟，且能登入並儲存設定。
- 瀏覽器的開發人員工具顯示工作階段 Cookie `bvs_session` 帶有 `Secure` 旗標。
- 記錄頁面顯示 `nginx-access`：每一行的第一個欄位是用戶端自己的位址，而不是 Plesk 伺服器的位址。

## 模組的主機名稱

模組可以有自己的主機名稱（網路頁面，主機名稱）。此時 nginx 會在該名稱的根目錄只提供這個模組。該名稱必須指向 NAS，或指向 NAS 前方的 Proxy。在 Proxy 上，每個主機名稱都需要一個自己的網站，並使用與上方相同的指令。

| 模組 | 在其主機名稱的根目錄 | 同時提供 |
|---|---|---|
| Git | Forgejo | 其他所有名稱上的 `/git/` 會以相同的路徑、查詢字串與連接埠回應 302，導向 Git 主機名稱 |
| 編輯器 | code-server | 面板的登入頁面與 `/api/`，讓使用者可以在該名稱上登入 |
| SVN 與 Bucket | httpd | 儲存庫仍位於 `/svn/<名稱>`，bucket 仍位於 `/bucket/<名稱>` |

關閉的模組會在其路徑與主機名稱上回應 `503`，並附上一句說明。

### 所有主機名稱共用一次登入

工作階段 Cookie 屬於設定它的那個名稱，除非設定 `network.cookie_domain` 指定了所有模組主機名稱的上層網域。設為 `example.com` 時，在 `bvs.example.com` 登入後，也能用於 `git.example.com` 與 `ide.example.com`。bvsd 會拒絕公開尾碼（`com`、`com.pl`、`co.uk`、`myqnapcloud.com`）、不是所有模組主機名稱上層的網域，以及在尚未設定任何模組主機名稱時的任何網域。該網域下的每個網站都會收到這個 Cookie，因此請設定範圍最小的共同上層網域。

網路頁面不會顯示這個設定。NAS 管理員透過 CLI socket，用套件的 `curl` 設定：

```bash
/usr/bin/bucketvs/bin/curl --unix-socket /usr/bin/bucketvs/var/run/bvsd-cli.sock \
  -X PUT -H 'Content-Type: application/json' \
  -d '{"network":{"cookie_domain":"example.com"}}' \
  http://bvsd/api/v1/settings
```

設為空值（`""`）時，Cookie 會再次只屬於各自的名稱。

## 發佈到網際網路（Cloudflare Tunnel）

**發佈** 頁面（管理，僅限管理員）會以 DNS 位於 Cloudflare 的網域名稱，把套件發佈到網際網路。套件內附的 `cloudflared` 從 NAS 主動連到 Cloudflare：不需要開啟路由器的連接埠，名稱的憑證由 Cloudflare 持有。每個請求都會經過套件的 nginx：面板及其 API 需要登入，沒有帳號時只能開啟您設為公開的內容（公開儲存庫、其網頁檢視、套件庫）。

名稱分為兩種，每種最多一個：

| 類型 | 範例 | 提供的內容 |
|---|---|---|
| 主要主機名稱 | `code.example.com` | 面板及其 API、位於 `/git/` 的 Forgejo、位於 `/svn-web/` 與 `/bucket-web/` 的網頁檢視、位於 `/repo/` 的套件儲存庫（唯讀）與本指南。`/svn/` 與 `/bucket/` 在那裡回應 404，`/ide/` 在您發佈編輯器之前也是（見下文） |
| 套件主機名稱 | `qpkg.example.com` | 只有 `/repo/` 的 GET 與 HEAD，供其他 NAS 的 App Center 使用；其他一切回應 404 |

### 權杖需要的權限

在 dash.cloudflare.com（My Profile、API Tokens）為名稱所在的區域建立具有下列權限的 API 權杖：

- Account、Cloudflare Tunnel、Edit
- Zone、DNS、Edit
- Zone、Cache Rules、Edit
- Zone、Cache Purge、Purge

套件把權杖保存在 NAS 上只有 root 能讀取的檔案中，之後不會再顯示。

如果權杖設有 Client IP Address Filtering，請在其中填入 NAS 的兩個公開位址，IPv4 與 IPv6：NAS 有 IPv6 位址時會透過 IPv6 連線到 Cloudflare。否則發佈頁面會以 `token_ip_filter` 拒絕，並指出 Cloudflare 看到的位址。

### 發佈

輸入名稱與權杖，然後按 **發佈**。套件會先在 Cloudflare 檢查一切，任何檢查失敗時都不會在那裡變更任何東西；訊息會說明要修正什麼：

- 權杖缺少的權限，並指出其名稱；
- 位於權杖看不到的區域中的名稱；
- 名稱已有的 DNS 記錄（其類型與指向）：請在 Cloudflare 刪除它，或改用其他名稱；
- **公開瀏覽**（Git 頁面）關閉時的主要主機名稱，因為其訪客沒有帳號；
- 分屬兩個 Cloudflare 帳號區域的名稱。

接著它會建立通道（除非您指定名稱，否則命名為 `bucketvs-<NAS 名稱>`；Cloudflare 上已有同名的通道則會接手），以經過代理的 CNAME 記錄讓每個名稱指向它，加入快取規則並啟動 cloudflared。以相同的值 **重新發佈** 不會改變任何東西。請把公開 URL（網路）設為主要主機名稱的 `https://` 位址，讓 Forgejo 的連結與 HTTPS 複製位址使用它；兩者不同時，頁面會提醒您。

透過 SSH 使用 Git 不會經過通道：Cloudflare 對已發佈的名稱只傳送 HTTP。因此 SSH 複製位址是 NAS 的區域網路位址及其「透過 SSH 使用 Git」的連接埠（9952），在面板與 Forgejo 自己的複製欄位中都是如此，Git 頁面與總覽會顯示「透過 SSH 複製只能在區域網路使用；從網際網路請透過 HTTPS 複製。」若要從網際網路使用 SSH，請在路由器上轉送 SSH 連接埠，並把 **複製 URL 中的 SSH 主機**（網路）設為路由器的公開位址或指向它的僅 DNS 名稱；路由器使用其他連接埠時，再設定 **複製 URL 中的 SSH 連接埠**。

在管理員於 **發佈** 頁面開啟 **把編輯器發佈在主要主機名稱** 之前，主要主機名稱的 `/ide/` 回應 404。編輯器的終端機是 NAS 上的 root shell，任何從網際網路在那裡以開發者或管理員身分登入的人都能使用，所以請先為這些帳號開啟兩步驟驗證。開關開啟時，編輯器經過與區域網路相同的登入，連接埠代理（`/ide/proxy/<port>/`）在那裡保持關閉，Cloudflare 也從不快取它的頁面。開啟期間，總覽會顯示 **編輯器在網際網路上**。

CI 模組開啟期間，推送到此 Forgejo 任何儲存庫的工作流程，都會透過 NAS 自己的執行器在 NAS 上以 root 執行，無論是從網際網路或從區域網路推送：與編輯器的終端機相同的權限。**發佈** 頁面在 **網際網路可存取的範圍** 下也這麼說。只把推送權限給您願意告知 NAS 管理員密碼的人。

### 快取

開啟 **快取預設** 時，Cloudflare 會快取套件（`*.qpkg`）一年、圖示一天、`repo.xml` 5 分鐘、面板的檔案一年；絕不快取 API、Forgejo 的頁面、登入頁面或網頁檢視。發佈到套件儲存庫時，會立即從 Cloudflare 的快取清除 `repo.xml`（以及變更的圖示或已刪除版本的檔案）。套件把自己的規則加在區域快取規則的最後，絕不變更其他規則。

### 從網際網路登入

第一組密碼絕不能透過已發佈的名稱設定：首次設定只接受本機用戶端。密碼錯誤依網際網路用戶端分別計算，與區域網路相同。以 git 傳送給 Forgejo 的密碼也會計算；Forgejo 存取權杖照常可用。Cloudflare 接受最大 100 MB 的請求，因此較大的推送或上傳請改用區域網路位址。

### 停止與移除

**停止** 會結束 cloudflared；此時名稱會顯示 Cloudflare 自己的錯誤頁面，Cloudflare 上不會有任何變更。**從 Cloudflare 移除** 會刪除套件建立的 DNS 記錄、快取規則與通道（接手的通道會保留），並忘記權杖。名稱發佈期間，總覽會顯示帶有通道狀態的 **網際網路** 標記。

## 模組

總覽頁面會顯示每個模組及其程序、記憶體與 CPU，並可開啟或關閉模組（僅限管理員）。關閉的模組會說明其用途；管理員可在其圖塊或其頁面以其按鈕（**開啟 Git**、**開啟 編輯器** 等）開啟它，編輯器模組也可在「編輯器」分頁中開啟。

- 開啟模組會執行一個工作：模組完成準備、儲存開關狀態，然後啟動其程序。工作最多等候程序 30 秒。
- CI 需要 Git：CI 開啟時無法關閉 Git。
  Git 關閉時，CI 的按鈕為 **開啟 Git 和 CI**，會先開啟 Git。
- 關閉的模組不執行任何程序，也不使用記憶體。
- 核心一律開啟。

| 狀態 | 意義 |
|---|---|
| 執行中 | 模組的每個程序都在執行 |
| 啟動中 | 有程序在不到一秒前啟動 |
| 運作異常 | 有程序在等待重新啟動、模組的檔案無法寫入，或其健康檢查失敗 |
| 不斷重新啟動 | 某個程序在 10 分鐘內結束了 5 次；bvsd 每 15 分鐘重新啟動它一次，並已送出一則通知 |
| 已停止 | 模組的程序未在執行（資料搬移停止了它們，或它們無法啟動） |
| 關閉 | 模組已關閉 |

結束的程序會在 1 秒後重新啟動，之後依序為 2 秒、4 秒，以此類推，最長 60 秒。程序執行滿 10 分鐘後，等候時間會從 1 秒重新計算。不斷重新啟動的程序，在其模組關閉再開啟時會立即啟動。

## Git

Git 模組執行 Forgejo，這是具備議題、合併請求、發行版本、Wiki、LFS、鏡像與 Webhook 的 git 儲存庫網頁介面。管理員在總覽頁面或 Git 頁面以 **開啟 Git** 開啟它。工作會在 `<資料>/forgejo` 中準備 Forgejo 的資料夾與資料庫，把面板的每個帳號加入 Forgejo，然後啟動它。之後 Forgejo 會在面板位址的 `/git/` 開啟；若設定了 Git 主機名稱，則在該名稱的 `/` 開啟（請見[模組的主機名稱](#模組的主機名稱)）。

Git 頁面上的 **新增儲存庫**（尚無儲存庫時，總覽頁面的 Git 圖塊上也有）會開啟 Forgejo 建立儲存庫的表單。面板只對 developer 以上的角色顯示此按鈕與 **遷移或鏡像儲存庫**：Forgejo 不允許 viewer 或 member 建立儲存庫。

### 儲存庫與帳號

- **只需登入一次。** Forgejo 沒有自己的登入：`/git/user/login` 會轉到面板的登入（帳號有驗證碼時也會要求），完成後再回到 Forgejo。
- **帳號來自面板。** 面板的每個帳號在 Forgejo 中都有同名、同密碼的帳號。`admin` 角色在 Forgejo 中是網站管理員。已停用的帳號，或必須先變更密碼的帳號，也無法登入 Forgejo。面板中的變更會在幾秒內反映到 Forgejo。使用者頁面與 Git 頁面會顯示每個帳號在 Forgejo 中的狀態，Git 頁面上的 **立即同步所有帳號** 會重新執行同步。
- **刪除帳號** 時，面板會停用其 Forgejo 帳號：它無法再登入，其權杖與 SSH 金鑰會被移除，但其儲存庫會保留。在管理員於 Forgejo 的網站管理（**使用者帳號**）中重新命名或刪除舊的 Forgejo 帳號之前，這個名稱不能給新的面板帳號使用。
- **在 Forgejo 中直接建立的帳號** 面板絕不會變更。同名的面板帳號會顯示原因，並等到其中之一改名為止。
- **兩步驟驗證與 git。** 對開啟兩步驟驗證的帳號，Forgejo 不接受密碼。此時透過 HTTP 或 HTTPS 使用 git，需要以 Forgejo 存取權杖（Forgejo 的 **設定**、**應用程式**）代替密碼。SSH 金鑰照常可用。
- **儲存庫** 的建立、匯入與鏡像都在 Forgejo 自己的頁面中進行。預設允許從區域網路上的位址匯入（設定：`git.allow_local_networks`），因為 NAS 通常從自己的網路鏡像。

### 透過 HTTPS 與 SSH 複製

在 Git 頁面上，**複製儲存庫** 會為擁有者與儲存庫寫出兩種指令：

| 方式 | 位址 | 登入 |
|---|---|---|
| HTTPS（或 HTTP） | `https://<NAS 位址>:9951/git/<擁有者>/<儲存庫>.git` | 面板的使用者名稱與密碼，或存取權杖 |
| SSH | `ssh://git@<NAS 位址>:9952/<擁有者>/<儲存庫>.git` | 在 Forgejo 的 **設定**、**SSH / GPG 金鑰** 中新增的 SSH 金鑰 |

- 設定了公開 URL 時（在 Proxy 後方，[公開 URL](#公開-url)），HTTPS 位址會使用它；有 Git 主機名稱時，路徑是該名稱上的 `/<擁有者>/<儲存庫>.git`。
- SSH 連接埠是 `network.ssh_port`（9952），或在路由器轉送其他連接埠到它時使用 `network.public_ssh_port`。Proxy 不會轉送 SSH：請在路由器上轉送連接埠（請見[透過 SSH 使用 Git](#透過-ssh-使用-git)）。
- 透過 Cloudflare Tunnel 發佈的名稱不承載 SSH，因此 SSH 複製位址會改用 NAS 的區域網路位址，除非 `network.public_ssh_host`（網路，「複製 URL 中的 SSH 主機」）填入僅 DNS 的名稱或路由器的位址。
- 大型推送不會被截斷：nginx 不對 `/git/` 設定內容大小上限。

### 讓沒有帳號的人檢視儲存庫

Git 頁面上的 **公開瀏覽** 讓任何能連到 Forgejo 的人不需帳號即可閱讀其公開儲存庫。在 Forgejo 中，每個儲存庫都是公開或私人（其 **設定**、**可見性**）。開關開啟時，沒有帳號的訪客可以：

- 開啟公開儲存庫、其資料夾與檔案，並原樣下載檔案；
- 閱讀歷史記錄、開啟提交及其差異（也可以 `.diff` 或 `.patch` 檔案形式），並比較兩個提交；
- 查看其發行版本、下載封存檔，並透過 HTTP 複製（`git clone https://<NAS 位址>:9951/git/<擁有者>/<儲存庫>.git`）；
- 在 **探索** 與擁有者的個人資料中找到公開儲存庫。

私人儲存庫仍然隱藏：其位址會回應「找不到」。開關開啟期間，Forgejo 也不會列出任何帳號（探索的 **使用者** 分頁與使用者搜尋都會關閉，已登入的使用者也一樣），因此訪客無法收集帳號名稱。推送、議題、合併請求與設定仍需要帳號。開關關閉時，每個頁面都會先要求訪客登入。

若要分享儲存庫，請在 Forgejo 中將它設為公開，開啟 **公開瀏覽**，再送出位址。在 Git 主機名稱上，位址為 `https://<Git 主機名稱>/<擁有者>/<儲存庫>`。

### 新專案

**新專案** 是一個頁面，會一步一步把專案從零帶到已建置、已發行的第一個版本。可從命令面板（Ctrl+Shift+P，**新專案**）、編輯器側邊欄頂端、Git 側邊欄標題、Git 頁面與「專案」頁面在專屬分頁中開啟；「範例」頁面上 **編輯器專案** 中的範例有 **以此建立新專案**。需要 developer 角色、Git 與編輯器模組，以及您在 Forgejo 的帳號（Git 頁面會顯示同步狀態）。Git 或編輯器關閉時，頁面會在步驟上方說明，管理員可在那裡用一個按鈕開啟它們。

1. **範本。** 範本分組列出：程式（C、使用 CMake 的 C++、Go、Rust、Node、Python、Java）、QTS 套件（QPKG 套件、App Builder 應用程式、從原始碼建置的套件）、核心（核心模組、USB 序列驅動程式 ch341）、網站（靜態網站：`src/` 中的頁面、建置 `public/` 的 Makefile、以 `make test` 檢查連結）與範例。每張卡片說明建置會產生什麼，並標出此 NAS 尚缺的項目（QDK、建置環境、已關閉的模組），附上新增它的頁面連結。
2. **名稱與可見性。** 名稱即儲存庫與資料夾 `projects/<名稱>` 的名稱；輸入時頁面就會檢查。接著是擁有者（您的帳號，或 Forgejo 允許您建立儲存庫的組織）、私人或公開，以及需要時的說明。App Builder 應用程式還要選擇起始的預設與目標。
3. **服務**（QPKG 套件與從原始碼建置的套件）：以區塊組成套件的啟動與停止方式。選擇一個預設（**命令列工具**將程式連結到 `/usr/bin`；**背景服務**加上帳號、磁碟區上的資料存放資料夾、以該帳號啟動的程式、等待其連接埠，以及有逾時的停止；**網頁應用程式**再加上 App Center 的「開啟」按鈕；**空的**只有動作），然後修改其區塊。每個區塊的卡片會在您輸入時顯示它寫入腳本的行，被拒絕的值會標示在欄位下方。啟動絕不會開啟 App Center 的 Enable，停止也絕不會改動它。之後可在專案檢視中修改區塊。
4. **建置。** 專案建置所用的環境，會為專案保留（之後可在專案檢視變更），也可選擇不指定，由 bvsd 在每次建置時挑選。這一步會先提供 bvsd 自己會挑選、`bvs qbuild` 與 CI 也用來建置的環境：Ubuntu 24.04 builder，沒有時為 Debian 12 builder，再沒有時為其他 Debian 或 Ubuntu 環境。若該範本的 Makefile 以 ENV 行指定每個應用程式的環境，這一步會說明這點而不詢問環境，專案也不保留任何環境。對從原始碼建置的套件，這一步也會說明入門配方的來源網址與 sha256 是暫用的：在配方寫上您程式真正的來源與該檔案的 sha256 之前，第一次建置會失敗。頁面會顯示 CI 每次推送時執行的工作流程。**建立後立即建置** 預設開啟，除非您關閉它；從原始碼建置的套件則預設關閉，您可以自行開啟。若 NAS 上沒有 Debian 或 Ubuntu 環境，這一步會說明這點：管理員可在此用 **建立此環境** 建立建置環境，之後這一步就會提供它；否則請關閉 **建立後立即建置**，之後再建置專案。
5. **發佈**（QPKG 套件、App Builder 應用程式或網站）：**在此 NAS 上測試部署**（管理員，僅限已簽署套件；應用程式則為 **在此 NAS 上啟用**）、**發佈到此 NAS 的 App Center 儲存庫**，以及 **發佈到我的網頁伺服器**（管理員，發佈到下方「自己的網頁伺服器（網頁目標）」之一）；**新增目標** 可在此保存新的目標。每一項都是選用的。發佈的選擇也會成為專案的開關 **發佈到 App Center 儲存庫**（專案檢視的「設定」）：開啟時，專案的 CI 每次執行都會發佈到那裡。
6. **檢閱** 列出所有答案與將執行的步驟。**建立專案** 會送出它們。
7. **進度** 追蹤每個步驟的工作：儲存庫與第一個提交、第一次建置，接著依序是測試部署、發佈到 App Center 與發佈到網頁。每一列顯示其狀態與記錄的結尾；點一下可顯示記錄。失敗的步驟會說明原因並提供 **重試**，之後的步驟會等待。最後提供 **開啟專案**、**在編輯器中開啟** 與 **在 Forgejo 中開啟**。重新載入頁面或回到其分頁時，會重新讀取工作，不會重複送出任何步驟。離開頁面會停止鏈；專案檢視也有相同的步驟。

當 bvsd 拒絕建立專案（名稱已被資料夾、應用程式或儲存庫使用，或您無法在該擁有者下建立），頁面會回到含有該答案的步驟，在欄位下方顯示原因，並保留其他所有答案。模組已關閉（管理員可從頁面開啟）、尚無 Forgejo 帳號、App Builder 應用程式沒有 Debian 或 Ubuntu 環境，會在「檢閱」中說明。

第一個工作會：

- 以您的身分建立儲存庫，除非您選擇公開，否則為私人。
- 在資料資料夾中建立 `projects/<名稱>` 資料夾並做第一個提交：範本的所有檔案與工作流程 `.forgejo/workflows/build.yml`，位於 `main`。提交屬於您（資料夾中以您的名稱與位址設為作者），範本的指令碼在其中可執行。它以您的身分將此提交推送到儲存庫。`origin` 是儲存庫的 HTTPS 位址，因此您的下一次推送會送到那裡。提交也包含 [AI 助理用的技能](#與-ai-助理一起寫程式)（`.claude/skills/qnap-dev/` 與 `AGENTS.md`）。資料夾位於編輯器的共用資料夾中：無論儲存庫是私人或公開，每位 developer 的編輯器都會開啟 `projects/`。
- 為專案保留所選的環境（若該範本的 Makefile 已指定每個應用程式的環境則不保留）。若是 App Builder 應用程式，會以預設的配方建立與專案同名的應用程式並將它與專案綁定，因此該應用程式也能建置您加入專案 `recipes/` 資料夾的配方。也會在工作流程第一次執行讀取之前，把您的發佈選擇保留為專案的開關「發佈到 App Center 儲存庫」。

CI 模組開啟後，工作流程會在每次推送時於 NAS 的 runner 上、在工作流程指定的環境中執行。QPKG 工作流程會將套件發佈到[套件儲存庫](#套件儲存庫)，該儲存庫只接受已程式碼簽署的套件。若儲存庫建立後第一個提交或其推送失敗，工作會移除資料夾、再次刪除儲存庫並說明原因。專案的開關 **發佈到 App Center 儲存庫** 關閉時，工作流程中的 `bvs repo publish` 不會發佈，而是顯示一則通知，執行則繼續進行。

### 專案檢視

資料資料夾中 `projects/` 的每個資料夾都有自己的頁面：精靈建立的專案、「編輯器專案」中範例的複本，或您複製到那裡的儲存庫。可從編輯器側邊欄的 **專案** 分支、「專案」頁面、新專案結束時的 **開啟專案**，以及命令面板（**開啟專案 …**，或 Ctrl+P 加上名稱）開啟。需要開發者角色。「專案」頁面上專案的選單也有建置、重新建置、發佈與發佈到網頁目標。同一個選單也能開啟或關閉 CI 發佈到 App Center。

頁面只讀取資料夾，不會自行變更其中任何內容。它從檔案判斷專案類型：與資料夾同名的 App Builder 應用程式、頂層的 `qpkg.cfg`（QPKG 套件）、頂層的 `Makefile`，或以上皆非（此時由專案的 CI 工作流程建置）。即使有與資料夾同名的 App Builder 應用程式，頂層有 `qpkg.cfg`，或有 Makefile 與 `qpkg/` 中 `qpkg.cfg` 的專案，仍保有自己的建置方式，測試部署與發佈也仍取用其 QPKG。

頂端的條列出各步驟。無法執行的步驟會保留按鈕但停用，並在旁邊與條下方說明原因。 專案根本無法進行的步驟會離開這一條，並在條下方以一句話說明專案產生什麼、建置結果會到哪裡：程式在專案資料夾，核心模組在「核心」頁面，只建置 App Builder 應用程式的 Makefile 則在各應用程式的頁面。

- **建置** 以工作的形式在底部面板中執行。QPKG 套件以 QDK 的 `qbuild` 建置，Makefile 專案以 `make` 建置，兩者都在專案的建置環境中執行。記錄會列出建置寫入 `build/` 的每個 `.qpkg` 及其是否有程式碼簽章。App Builder 應用程式的建置方式與其自身頁面相同。當 Makefile 的預設目標只執行 `./bvsapp.sh create` 與 `./bvsapp.sh build` 這兩種行時，建置會透過 App Builder 做同樣的事：先建立 `create` 行所指、尚不存在的每個應用程式，再逐一建置這些應用程式。`create` 行的格式為 `./bvsapp.sh create NAME RECIPES-FILE [ENV] [LINK] [TARGET] [OTHERS]`。這種 Makefile 會在 `.PHONY` 中列出其預設目標與該目標的先決條件，且只含簡單的指派（每個變數只設定一次）與簡單的規則；含有其他內容（條件、include）的 Makefile 以 make 建置。TARGET 為 `native`、`aarch64` 或 `armv7`，因此一次建置即可產生此 NAS 用與 ARM 64 位元用的應用程式。OTHERS 指靜態應用程式如何處理無法靜態連結的配方（`refuse` 或 `bundle`）。以此方式建立的應用程式也會使用專案自己 `recipes/` 中的配方，除非資料夾名稱不是專案名稱（例如含有空格的名稱）。若同名的應用程式已以其他目標存在，或在資料夾有自己的配方時屬於其他專案，建置會保持停用並說明如何修正。第一次建置之前，這一步會列出它要建置的應用程式。 套件位於 `qpkg/` 的 Makefile 專案，會在編輯器的終端機以 `make qpkg` 產生套件檔；在套件檔出現之前，測試部署、發佈與發佈到網頁會顯示「需要套件檔」。
- **重新建置** 從頭建置：先 `make clean` 再 `make`；QPKG 套件會先移除 `build/` 中舊的 `.qpkg`。沒有 `clean` 目標的 Makefile 無法重新建置，頁面會說明。App Builder 應用程式沒有重新建置，只建立並建置 App Builder 應用程式的 Makefile 也沒有。
- **匯出為 QPKG**（管理員）適用於 App Builder 應用程式，與應用程式頁面相同。 發佈會如同應用程式頁面一樣等待匯出，標題中的 **開啟應用程式** 會開啟該頁面。
- **測試部署** 供管理員使用。它以 `qpkg_cli` 將最新的 `.qpkg` 安裝到此 NAS，如同 App Center 安裝檔案，工作會等到 App Center 確認為止。在您於 App Center 中移除之前，套件會一直保持安裝。沒有程式碼簽章的套件會被拒絕：測試部署只安裝已簽署的套件，也絕不略過 App Center 的簽章檢查（在 App Center 本身，NAS 的擁有者可以允許沒有簽章的應用程式；此頁面不會這樣做）。請以檔案形式的憑證簽署（[QPKG 的程式碼簽署](#qpkg-的程式碼簽署) 的情況 1）。Bucket & Code Server IDE 本身的套件會被拒絕，因為安裝它會取代正在執行的套件。對 App Builder 應用程式而言，測試部署會在此 NAS 上啟用其建置。
- **發佈** 依[套件儲存庫](#套件儲存庫)的規則把最新的 `.qpkg` 放入其中：管理員的發佈會成為列出的版本，開發者的發佈要等管理員，沒有程式碼簽章的套件會被拒絕，除非儲存庫接受未簽署的套件。其下方的一行會說明專案的 CI 是否也會發佈到 App Center 儲存庫；這個按鈕無論如何都會發佈。
- **發佈到網頁**（管理員）會將最新的 `.qpkg`，或網站資料夾 `public/`（否則 `dist/`）複製到網頁目標，並透過目標的公開位址讀回。 若是精靈建立的網站，此步驟會等待寫入 `public/` 的第一次建置。

Makefile 專案可在 **建置** 旁選擇目標（除非選擇其他目標，否則為 make 的預設，也就是第一個目標）；CMake 專案可只建置，或建置並執行測試。核心模組專案在核心工坊中執行中核心的工作區內建置，此建置由管理員執行。執行中的步驟有 **取消**，工作通常會立即結束。當工作流程有 `workflow_dispatch` 觸發條件時，**執行工作流程** 會手動執行專案的 CI 工作流程；否則按鈕會停用，頁面會說明原因。 每個範本的工作流程都有這個觸發條件。**已安裝在此 NAS** 列出此專案的測試部署所安裝的套件，管理員可使用 **從此 NAS 移除**。**刪除專案**（位於標題按鈕旁的選單，或「專案」頁面上專案的選單）供管理員或其 Forgejo 帳號擁有該儲存庫的開發者使用。須先輸入專案名稱；資料夾會被刪除，Forgejo 儲存庫、同名的 App Builder 應用程式與已安裝的套件會保留。頁面接著會提供儲存庫的連結。

**設定** 顯示 bvsd 在資料夾之外為專案保留的內容。Makefile、CMake 或 QPKG 專案會顯示建置與重新建置所用的環境，並可變更。對於只建立並建置 App Builder 應用程式的 Makefile，會列出每個應用程式及其在 Makefile 中那一行所指定的環境。產生套件的專案，或工作流程會執行 `bvs repo publish` 的專案，有開關 **發佈到 App Center 儲存庫**：開啟時，專案 CI 中的 `bvs repo publish` 會把其套件加入儲存庫；關閉時，CI 執行會略過該步驟並顯示通知。沒有保留選擇的專案（範例的複本或複製的儲存庫）開關為開啟。**Git** 顯示分支、依上次 fetch 的結果領先或落後遠端多少（頁面本身從不 fetch）、最新的提交與變更的路徑。**檔案** 一次列出一個資料夾；選擇檔案即可閱讀，`.qpkg` 則會下載。編輯在編輯器中進行，`.git` 資料夾不會列出。

**配方** 會為擁有自己 `recipes/` 資料夾的專案，列出其應用程式使用的配方：每個配方附上版本、若與配方庫中的配方同名則附上它取代的配方庫版本，並在下方列出配方檢查找到的問題與其檔案和行。bvsd 略過的配方有一列說明原因；以相同原因略過的超過三個時，其餘的以一列計數。檢查對整個 `recipes` 資料夾找到的問題有自己的一列。點一下會在編輯器中開啟配方的資料夾；選單也能複製在 NAS 終端機執行同樣檢查的指令：附上專案資料夾的 `bvs recipe lint`。當專案的 `recipes` 是連結、檔案、bvsd 無法讀取的資料夾，或只有連結的資料夾時，也會顯示此卡片，並說明為何不讀取其中任何配方。

**服務與套件**（QPKG 套件，或像從原始碼建置的套件那樣在 `qpkg/` 中有套件的 Makefile 專案）包含以區塊組成的套件啟動與停止腳本、其 `qpkg.cfg` 的欄位（**套件**），以及由它們寫出的檔案（**腳本**）。**儲存**會先顯示每個檔案的差異，再提交到專案的儲存庫；若專案在這期間有變更，請重新規劃。**手動編輯**會停用區塊，改由您在編輯器中編輯腳本；**重新連結區塊**會顯示重新寫入將取代的內容。**更新函式庫**會將此 Bucket & Code Server IDE 的 `bvs-qpkg.sh` 連同差異放入套件。在區塊之前建立的專案會提供**為此腳本使用區塊**。

**版本** 列出專案可切換的目標，**切換到此版本** 會執行切換：

| 版本 | 切換時的動作 |
|---|---|
| `build/` 中的 `.qpkg`，或 App Builder 的匯出 | 對該檔案進行測試部署（管理員） |
| App Builder 的建置 | 啟用它（管理員）；App Builder 只保留一個建置 |
| 套件儲存庫中的版本 | 設為列出的版本（管理員） |
| git 標籤 | 在資料夾中 checkout 該標籤 |

點一下可選取版本；其選單也能測試部署、發佈、發佈到網頁目標或下載 `.qpkg`。標籤會以 detached HEAD 的方式 checkout，Git 卡片中的 **回到 main** 會回到分支。資料夾有尚未提交的變更時會拒絕切換，訊息會列出這些變更；請先在編輯器中提交或 stash。勾選兩個版本並按 **比較兩者**，會列出一方有而另一方沒有的檔案，以及變更過的檔案。兩個標籤或兩個套件以內容比較；套件與提交只以大小比較。在「套件」頁面上，管理員也能將儲存庫的版本安裝到此 NAS（**安裝到此 NAS**），並套用測試部署的檢查。

### 自己的網頁伺服器（網頁目標）

網頁目標是管理員將套件或網站發佈到自己網頁伺服器的位置：此 NAS 的資料夾（由 QTS 的 Web Server 提供的 QTS Web 共用，或其他共用資料夾），或 rclone 遠端（SFTP、FTP、WebDAV、S3，在備份的 `rclone.conf` 中設定），並附上提供服務的公開位址。它們位於「發佈」頁面的 **網頁目標** 區段：**新增目標**、**變更**、**移除** 與 **測試**；測試會複製一個小檔案到那裡，從公開位址取回，然後再刪除。

資料夾目標是共用資料夾中專屬的資料夾（`/share/Web/mysite`，絕不能是整個共用資料夾或磁碟區），位於資料資料夾之外，因為網站發佈會從目標中移除網站已不再包含的內容。位址以 `http://` 或 `https://` 開頭，不含使用者名稱或密碼。

每次發佈都會透過公開位址讀回檔案：套件的 `.qpkg` 及其 `.sha256` 以 sha256 比較，網站的 `index.html` 則逐位元組比較。當位址提供的不是所複製的內容時，工作會以 `web_check:` 與兩個值失敗。

## CI runner

CI 模組執行 Forgejo Actions：儲存庫 `.forgejo/workflows/` 中的工作流程檔案，會在有內容推送時於 runner 上執行。它需要 Git 模組。管理員在 **模組**、**CI**、**啟用** 開啟它。工作會向 Forgejo 註冊 NAS 自己的 runner，以開啟 Actions 的方式重新啟動 Forgejo，並啟動 runner；runner 連上 Forgejo 時工作即結束。

### NAS 上的 runner

- **其標籤** 為 `bucketvs`、`qnap-x86_64`（或 `qnap-aarch64`）與 `self-hosted`，再加上管理員在 CI 頁面設定的額外標籤。`runs-on` 指定其中之一的工作會在這裡執行，例如 `runs-on: bucketvs`。
- **它在 NAS 本身以 admin 身分執行每個工作**，不使用容器。工作可使用套件的工具（git、curl、make 等）、執行 `actions/checkout` 等 JavaScript action 的 Node，以及在環境中建置用的 `bvs env run <env> -- <command>`。因此，能把工作流程推送到儲存庫的人，就能在 NAS 上以 admin 身分執行指令。member 與 viewer 在 Forgejo 中不能建立、分叉或匯入儲存庫，也不能建立組織；developer 與 admin 可以。viewer 永遠不能推送。CI 開啟時，member 推送到已開啟 Actions 的儲存庫會被拒絕並說明原因，以部署金鑰推送也一樣。member 推送或以部署金鑰推送到由低於 developer 的帳號擁有或管理的儲存庫，同樣會被拒絕，即使 CI 關閉也一樣：這類帳號可以自行開啟 Actions。bvsd 會在一分鐘內關閉 member 或 viewer 擁有或能寫入的每個儲存庫的 Actions，開啟 CI 時也會在 runner 啟動前關閉。
- **剩下的** 是 developer 或 admin 自己的操作：在 member 先前寫入過的儲存庫開啟 Actions 或觸發工作流程，或給予這類帳號對其先前寫入過的儲存庫的管理權限。合併或建置 member 提交的 developer，也會以 admin 身分執行這些程式碼。
- **同時執行的工作數** 為 `ci.capacity`（預設 1）。沒有線上 runner 能接手的工作會在 Forgejo 中等候。
- **在 CI 開啟前建立的儲存庫** 其 Forgejo Actions 為關閉，因此推送的工作流程不會啟動任何東西：請在儲存庫的 **設定**、**單元**（Actions）中開啟。CI 開啟期間建立的儲存庫則已開啟。
- `actions/checkout@v4` 等 **Action** 會從 `https://data.forgejo.org` 下載，因此使用它們的工作流程需要 NAS 能連上網際網路。runner 沒有快取：`actions/cache` 找不到任何東西，工作會繼續進行。
- **成品**（`actions/upload-artifact`）會上傳到 Forgejo 自己的位址（Git 頁面上顯示的那個），因此 NAS 必須能連到該位址，且在 HTTPS 時信任其憑證。
- **runner 的狀態** 位於 `<資料>/runner`；其記錄是 **記錄** 下的 **runner**。關閉模組會停止 runner，並在 Forgejo 中關閉 Actions。

### 外部 runner

其他地方的建置機器（伺服器、VM）也可以從這個 Forgejo 接手工作。在 CI 頁面的 **註冊外部 runner** 下，管理員為它命名，即可取得 Linux x86_64 與 aarch64 用的指令。這些指令會從 code.forgejo.org 下載 forgejo-runner 13.2.0、檢查其 sha256、把 runner 的權杖保存在 `runner-token`（只有擁有者可讀取），並以標籤 `linux-amd64`（或 `linux-arm64`）啟動 runner。runner 會立刻出現在清單中，在指令執行之前顯示為離線。權杖只在此時顯示。這類 runner 在自己的機器上、以啟動它的使用者身分執行工作，並透過公開位址連到 Forgejo。

## SVN 與 Bucket

SVN 與 Bucket 模組在 `/svn/<名稱>` 提供 Subversion 儲存庫，在 `/bucket/<名稱>` 提供 bucket。bucket 是一種網路磁碟機（WebDAV），每個儲存的檔案都會成為一個修訂版本，因此可以讀取或放回檔案的任何舊版本。管理員在 **模組** 開啟這個模組後，**SVN 與 Bucket** 頁面即可建立、列出與刪除這兩種項目。

- **帳號** 就是面板的帳號。權限依儲存庫設定：對每個帳號設定讀寫、唯讀或無，另外再為未列出的帳號設定預設值。管理員一律可讀寫；member 與 developer 在有讀寫權限的地方可以寫入；viewer 最多只能讀取。新的儲存庫在管理員設定權限之前（該列的人物圖示，**`<name>` 的權限**），除管理員外沒有任何人有權限。低於 admin 的角色在頁面上會看到自己對每個儲存庫與 bucket 的權限。
- **密碼錯誤** 的計算方式與面板登入相同：15 分鐘內輸錯 5 次密碼，會把該帳號從該位址鎖定 15 分鐘，該位址本身也會被鎖定。
- **兩步驟驗證。** 開啟兩步驟驗證的帳號不以密碼使用 SVN 或 WebDAV，而是在其帳號頁面（**WebDAV 和 SVN 用的應用程式密碼**）建立一組 **應用程式密碼**：25 個隨機字元，只顯示一次。建立新的會取代舊的；變更密碼或關閉兩步驟驗證會移除它。
- **刪除** 儲存庫會把它移到 `<資料>/trash`，7 天後由 `trash` 工作移除。

### SVN 儲存庫

```bash
svn checkout "https://<NAS 位址>:9951/svn/<名稱>"
svn commit -m "…"
```

頁面會顯示含儲存庫位址的 checkout 指令。在 Proxy 後方時，位址會依照公開 URL；有 SVN 主機名稱時，則是該名稱上的 `/svn/<名稱>`。儲存庫的 **歷史記錄** 列出其修訂版本；檔案可以在任何修訂版本中讀取、與其他版本比較，並以新的修訂版本放回（**還原此版本**，限具讀寫權限的帳號）。

### Bucket

- **儲存就是提交。** 用戶端儲存檔案時，會以帳號名稱與時間建立一個修訂版本；該修訂版本會標記為經由 WebDAV 建立。許多用戶端會先寫入空檔案再寫入資料，因此一次儲存可能產生兩個修訂版本。
- **macOS 檔案。** 以 **拒絕 macOS 的 ._\* 和 .DS_Store 檔案**（預設）建立的 bucket，不會儲存 Finder 寫在所複製內容旁的 `._*` 與 `.DS_Store` 檔案。用戶端不會收到錯誤，但檔案永遠不會成為修訂版本。可以刪除這類檔案。
- **回到舊版。** bucket 的歷史記錄會顯示每個檔案的各個版本；**還原此版本** 會把舊內容儲存為新的修訂版本，因此不會遺失任何東西。

### WebDAV 用戶端與其限制

| 用戶端 | 連線方式 | 備註 |
|---|---|---|
| macOS Finder | **前往**、**連接伺服器**、`https://<NAS 位址>:9951/bucket/<名稱>` | Finder 寫入檔案時會鎖定它；bucket 會回應 WebDAV 鎖定 |
| rclone | 設定 `vendor = other` 與 bucket 位址的 `webdav` 遠端 | 適合以指令碼複製 |
| davfs2（Linux） | `mount -t davfs https://<NAS 位址>:9951/bucket/<名稱> /mnt/<資料夾>` | 面板的使用者名稱，以及密碼或應用程式密碼 |
| Cyberduck、WinSCP | 以 WebDAV（HTTPS）連到連接埠 9951，路徑 `/bucket/<名稱>` | |
| curl | `curl -T file -u <使用者> https://<NAS 位址>:9951/bucket/<名稱>/file` | 一次上傳，一個修訂版本（或兩個，見上方） |
| Windows 檔案總管 | **連線網路磁碟機** | Windows 的 WebClient 只會透過使用 Windows 信任憑證的 HTTPS 傳送密碼：請把 bucket 放在具有正式憑證的 Proxy 後方（[在反向 Proxy 後方](#在反向-proxyplesk後方)）；自我簽署憑證不夠 |

- 大型檔案不會被截斷：nginx 不對 `/bucket/` 設定內容大小上限，並在上傳資料抵達時直接轉送。
- 對 bucket 拒絕儲存的檔案（上述的 macOS 檔案），WebDAV 伺服器仍會回應 `201`，因此用戶端不會顯示錯誤；但該檔案並不在 bucket 中。

### 讓沒有帳號的人檢視儲存庫或 bucket

管理員可以在 **SVN 與 Bucket** 頁面的該列上，以開關（**網頁公開**）公開 SVN 儲存庫或 bucket。任何知道位址的人都能以瀏覽器閱讀，不需帳號：

| 位址 | 顯示內容 |
|---|---|
| `https://<NAS 位址>:9951/svn-web/<名稱>/` | SVN 儲存庫 |
| `https://<NAS 位址>:9951/bucket-web/<名稱>/` | bucket |

該列的 **在網頁上檢視** 連結會開啟這個頁面。頁面列出最新或較舊修訂版本的檔案與資料夾，以行號顯示文字檔（最多 1 MiB）或顯示圖片，並提供每個檔案的下載。**歷史記錄** 列出修訂版本及其作者、訊息與變更的檔案。修訂版本頁面會顯示其變更的每個檔案的差異（最多 20 個、每個最多 1 MiB 的檔案；其餘為連結）；任何檔案都能在兩個修訂版本之間比較。

- **唯讀。** 這些頁面無法變更任何內容。WebDAV 用戶端與 `svn` 仍需要帳號，公開儲存庫也一樣。
- **只顯示公開內容。** 未公開的儲存庫與不存在的名稱，都會回應「There is no public repository at this address」，因此頁面不會透露哪些名稱存在。
- **限制。** 一個位址一次最多開啟 60 個頁面，之後每秒 2 個，並同時下載 2 個檔案；超過時頁面會請您稍後再試。差異超過 2 MiB 會被截斷。
- **搜尋引擎** 會被要求不要索引這些頁面。頁面為英文。頁面採用面板的外觀，依訪客瀏覽器的設定顯示深色或淺色。
- 在 Proxy 後方或有 SVN 主機名稱時，位址會依照儲存庫本身的位址（公開 URL，或路徑相同的 SVN 主機名稱）。

## 編輯器與 AI 工具

編輯器模組為每個 developer 與 admin 帳號執行瀏覽器中的 VS Code（code-server）。管理員在 **模組** 開啟它。每個帳號都有自己的執行個體，在帳號開啟編輯器時啟動，並在一段時間沒有流量後停止。

**編輯器的終端機是 NAS 上的 root shell。** 在 QTS 上，除了 Forgejo 與 httpd，套件的程序都以 admin（uid 0）執行。請只把 developer 角色交給您願意告知 NAS admin 密碼的人。

### 開啟編輯器

- 「編輯器設定」頁面上的 **開啟編輯器**（或面板位址的 `/ide/`，或編輯器主機名稱）會在新分頁開啟工作台。第一個請求會啟動執行個體；沒有工作階段時，會先顯示面板的登入。
- 透過已發佈的主要主機名稱，只要編輯器尚未在那裡發佈，「編輯器設定」頁面、「編輯器」分頁、總覽與 **開啟 shell** 會說明編輯器只能從區域網路開啟，並連到它的區域網路位址（`https://<NAS 位址>:<HTTPS 連接埠>/ide/`）；對管理員也會連到 **發佈** 頁面的開關。
- 編輯器會開啟 `<資料>/projects`，「編輯器專案」中的範例會把專案放在這裡。帳號的設定、擴充功能與終端機歷史記錄保存在 `<資料>/ide/<帳號>`，家目錄在 `<資料>/home/<帳號>`。
- 終端機的 `PATH` 最前面是套件的工具：git、svn、node、npm、rsync、curl、bash 等。`bvs env shell <名稱>` 會進入工具鏈的環境，`eval "$(bvs use <id>)"` 會設定好工具鏈或執行環境。工具鏈頁面上的 **開啟 shell** 會開啟編輯器，且終端機已位於該環境中。
- **編輯器** 活動與命令選擇區可以開啟編輯器，並直接進入環境中的 shell 或應用程式的資料夾。編輯器顯示在面板的「編輯器」分頁中；在手機上則在自己的瀏覽器視窗中開啟。只有面板本身位址的頁面才能在頁面中顯示編輯器：套件在編輯器的每個回應中都會這樣告知瀏覽器，因此其他網站無法把編輯器的 root 終端機嵌入自己的頁面。
- **閒置時間**（預設 30 分鐘）會停止沒有瀏覽器流量的執行個體，但只要從其終端機啟動的程式、擴充功能啟動的 AI 代理（Claude Code for VS Code 的 `claude`、Codex、Gemini CLI），或從那裡留著執行的程式仍在執行，就不會停止。設為 0 會關閉閒置停止。帳號可以在「編輯器設定」頁面停止自己的執行個體；管理員可以停止任何執行個體。
- 「編輯器設定」頁面的 **記憶體與程序** 顯示執行個體執行的東西，以及它相對於 NAS 記憶體所佔用的記憶體，標出讓它繼續執行的程序，用 **結束** 結束單一程序，並說明上次停止的原因與它結束了什麼。在提示字元等待輸入的 AI 代理也會讓執行個體繼續執行：不再需要時，就在那裡結束它。
- VS Code 的 **Ports** 檢視可透過 `/ide/proxy/<port>/` 連到 NAS 上的開發伺服器；套件自己的 loopback 連接埠在此會被拒絕。
- 在 **Explorer** 中專案的 `recipes` 資料夾或其中配方資料夾的選單裡，**New recipe** 會詢問名稱，並在那裡以套件的入門配方寫入新的配方：`recipe.json` 與 `build.sh`。在命令選擇區中它是 **Bucket: New recipe**，只要開著某個有 `recipes` 資料夾的專案中的檔案就能使用。每次儲存配方中的檔案時，編輯器會以 `bvs recipe lint` 檢查專案的配方，並在 **Problems** 檢視中列出結果。在您填入來源真正的 sha256 之前，它會對入門配方的 sha256 發出警告。

編輯器只回應自己的頁面。其他頁面帶著帳號的工作階段送出的 WebSocket、表單或指令碼請求，無論來自其他網站或 NAS 的其他連接埠，都會得到 403 且不會啟動任何執行個體，因此其他頁面也無法觸及 **Ports** 檢視背後的開發伺服器。

### 擴充功能

- 在編輯器的 Extensions 檢視（Ctrl+Shift+X）中安裝擴充功能。擴充功能來自 Open VSX：瀏覽器在 open-vsx.org 搜尋，並從 openvsx.eclipsecontent.org 顯示每個擴充功能的頁面與圖示，擴充功能本身則由 NAS 從 open-vsx.org 下載。因此瀏覽器與 NAS 都需要連得上網際網路。
- 每個發行者的第一個擴充功能會詢問「Do you trust the publisher …?」。**Trust Publisher & Install** 會安裝它；在那之前擴充功能顯示「Installing」。
- 每個帳號的擴充功能保存在 `<資料>/ide/<帳號>/extensions`，套件更新後仍會保留。
- 套件附帶一組擴充功能與格式化工具，編輯器會在每個帳號第一次啟動時安裝，套件更新帶來新的一組時也會再安裝。有了它，編輯器會為 Markdown、shell 指令碼、C、C++、Rust、Go、Pascal、Python、Perl、PHP、JSON、YAML、TOML、nginx 設定、CSS、HTML、JavaScript 與 TypeScript 上色並格式化（命令選擇區中的 **Format Document**）。ShellCheck 會標出 shell 指令碼中的問題，systemd 單元檔有上色與自動完成，專案的 `.editorconfig` 決定縮排，`#ff8800` 或 `rgb(0, 128, 0)` 這類色碼在任何檔案中都以其顏色顯示，命令選擇區中的 **Hex Editor: Open Active File in Hex Editor** 會顯示檔案的位元組。
- 在工具鏈頁面安裝 Rust 執行環境，且帳號的編輯器重新啟動後，Rust 才能格式化；在那之前，於 Rust 檔案中執行 Format Document 會顯示要求失敗。
- 在 ARM 架構的 NAS（aarch64 套件）上，Pascal、Perl 與 PHP 只會上色、不會格式化：套件沒有為它建置的 Free Pascal、Perl 或 Mago。
- 帳號在 Extensions 檢視中移除的這組擴充功能會保持移除，套件更新後也一樣；帳號自行更新的則保留較新的版本。為某個語言選定的預設格式化工具（**Format Document With…**，再選 **Configure Default Formatter…**）仍是帳號自己的選擇。
- 所有 `.conf` 檔案都會以 nginx 設定開啟。按狀態列上的語言名稱，可把檔案切換成其他語言。
- 格式化 Python 的 Ruff 會提示一次：沒有安裝 Python 環境的擴充功能。沒有它也能格式化。
- 在編輯器的終端機中執行 `code-server --install-extension <發行者.名稱>`，也會從 Open VSX 安裝擴充功能。
- 有些擴充功能會繪製自己的檢視，例如 Claude Code for VS Code 的面板、十六進位編輯器與 Markdown 預覽。只有瀏覽器信任面板的憑證時，才會顯示這些檢視。若自我簽署憑證只是在瀏覽器的警告頁面上接受，這些檢視會保持空白，「編輯器」分頁與「編輯器設定」頁面會說明這一點。以下任一方式都能解決：
  - 在裝置上信任 NAS 的憑證：在「網路」頁面或該通知中按 **下載憑證**，再把它加入系統或瀏覽器的受信任憑證（[憑證](#憑證)）；
  - 由管理員在「網路」頁面新增 NAS 名稱的憑證；
  - 管理員在已發佈的主要主機名稱上發佈編輯器後，經由該主機開啟編輯器。
- Claude Code for VS Code（發行者 Anthropic）可從 Extensions 檢視安裝，它的面板會詢問如何登入 Anthropic。命令選擇區中的 **Claude Code: Open in Terminal** 會在終端機中執行 Claude Code，這在任何憑證下都能運作。VS Code 自己的聊天功能一開始是關閉的（`chat.disableAIFeatures`）；這個設定不會影響這個擴充功能。

### Claude Code、Gemini CLI 與 Codex CLI

在「編輯器設定」頁面的 **AI 命令列工具** 下，每個帳號可為自己安裝、更新與移除這些工具：

| 工具 | 安裝方式 | 指令 |
|---|---|---|
| Claude Code | Anthropic 的安裝程式（`claude.ai/install.sh`），失敗時改用 npm | `claude` |
| Gemini CLI | `npm install -g @google/gemini-cli` | `gemini` |
| Codex CLI | `npm install -g @openai/codex` | `codex` |

- 工具會安裝到帳號的家目錄（`~/.local/bin`、`~/.npm-global/bin`），這些位置在終端機的 `PATH` 中；其他帳號看不到它們。
- 每次安裝都是一個工作，其記錄可在工作頁面查看。登入各工具（帳號或 API 金鑰）是在編輯器的終端機中、於工具本身內完成。

### 與 AI 助理一起寫程式

**新專案** 建立的每個專案都帶有給 AI 助理的技能：`.claude/skills/qnap-dev/` 以及指向它的 `AGENTS.md`。這個技能以英文寫成，包含與 QNAP NAS 相關一切事項的準則：它在 shell 中是什麼樣子（RAM 磁碟上的根目錄、BusyBox、沒有 systemd 也沒有套件管理員、`getcfg` 和 `setcfg`）、儲存與路徑、如何用本套件建置、簽署和發布（`bvs env run`、`bvs ldd`、`bvs qbuild`、App Builder、核心工坊、套件儲存庫）、連接埠、備份，以及它不可以建議的事，例如在 NAS 上用 `apt`，或在 App Center 中允許未簽署的應用程式。它清楚寫明，用硬體權杖簽署不受支援也未經測試。

- 請在專案的資料夾中啟動助理（`cd <資料>/projects/<名稱>`），而不是在 `projects/` 中：每個工具都會在它啟動的資料夾中尋找。
- Claude Code 會在工作需要時從 `.claude/skills/` 載入技能。Codex CLI 會讀取 `AGENTS.md`。Gemini CLI 在專案的 `.gemini/settings.json` 含有 `{"context": {"fileName": ["AGENTS.md", "GEMINI.md"]}}` 之後就會讀取 `AGENTS.md`。
- 套件把目前的技能保存在 `/usr/bin/bucketvs/share/skills/qnap-dev`。在套件具備技能之前建立的專案，或從範例庫複製的專案，只要把這個資料夾複製到專案的 `.claude/skills/` 即可取得。
- 關於 NAS 有什麼、缺什麼，技能引用 QNAP Club Poland 的知識庫（forum.qnap.net.pl）。
- 套件的建置也會把技能寫成 `qnap-skill-<版本>.zip`，內容是相同的檔案，可在編輯器之外使用：解壓到專案的 `.claude/skills/` 或 `~/.claude/skills/`。技能的文字以 CC BY 4.0 授權（見其 `LICENSE`）：只要附上「QNAP guidelines skill by QNAP Club Poland / qnaputilities.com, CC BY 4.0」這行署名，就可以分享和改作。

## 工具鏈與環境

工具鏈頁面會下載在 NAS 上建置軟體所需的內容：

| 類型 | 說明 | 範例 |
|---|---|---|
| 根檔案系統 | 可解壓縮到環境中的 Linux 系統 | Debian 12 與 13、Ubuntu 22.04 與 24.04、Alpine 3.22 |
| 交叉工具鏈 | 為其他處理器建置的編譯器 | 適用 aarch64、x86-64 與 armv7 的 Bootlin 工具鏈，搭配 glibc 或 musl |
| 執行環境 | 程式語言或建置工具 | Python、Go、Node.js、Zig、Rust、CMake、Ninja、Java（JDK） |
| QNAP | QNAP 自己的工具 | QDK 2.5.0，供 [`bvs qbuild`](#用-bvs-qbuild-建置-qpkg) 與匯出為 QPKG 使用 |
| 核心原始碼 | 供[核心工坊](#核心工坊)使用的 Linux 核心樹 | 來自 kernel.org 的 Linux 6.6.129；QNAP 針對 QTS 5.2.3 發佈的 GPL 核心 |

其他處理器的項目預設隱藏；「顯示其他處理器的項目」會列出它們，但無法安裝。Bootlin 工具鏈在 x86_64 上執行，因此 ARM 的 NAS 沒有交叉工具鏈。

### 目錄

套件內建一份目錄。位於 `https://pub.qnaputilities.com/bucketvs/catalog.json` 的已簽署遠端目錄，可以在不更新套件的情況下新增項目。只有當其 ed25519 簽署與套件內建的金鑰相符，且不比目前使用的目錄舊時，bvsd 才會使用它。「重新整理目錄」會立即取得它，內建的 cron 工作「重新整理工具鏈目錄」每天 04:15 取得它。

### 安裝與移除

安裝由管理員進行，其進度顯示在工作頁面：

1. 封存檔會下載到資料目錄的 `toolchains/cache/`。中斷的下載會在重新開始安裝時續傳。
2. 以目錄檢查其 sha256。不相符時會刪除該檔案。
3. 交叉工具鏈或執行環境會解壓縮到 `toolchains/<kind>/<id>`，並刪除其封存檔。根檔案系統或核心原始碼會保留為封存檔；每個環境或核心工作區都會解壓縮自己的副本。QNAP 把其 GPL 核心分成兩部分發佈：兩部分都會下載，各自以自己的 sha256 檢查後再合併。

磁碟區已滿時，安裝會以「no space left on」與該磁碟區的掛載點失敗。

「移除」會刪除已安裝的項目。從根檔案系統建立的環境是副本，會保留。

### 使用工具鏈或執行環境

`bvs use <id>` 會印出讓已安裝的交叉工具鏈或執行環境可在 shell 中使用的 `export` 行（例如 `PATH`）：

```bash
eval "$(bvs use bootlin-aarch64-glibc-stable)"
```

工具鏈頁面也會在每個已安裝項目的「bvs use」下顯示相同的行。

### 環境

環境是從根檔案系統解壓縮到資料目錄 `envs/<名稱>` 的 Linux 系統。它可以變更：在 Debian 或 Ubuntu 環境中可以執行 `apt install build-essential`，安裝的內容會保留在其中。

- 「新環境」（管理員）會在根檔案系統尚未安裝時先下載它，再解壓縮。
- 在還沒有任何環境時，清單會以一個按鈕提供應用程式範例建置所用的 Ubuntu 24.04 builder（管理員）：其工作會顯示在清單中，工作完成後環境就會出現。不會自動建立任何環境。
- 名稱為 1 到 64 個字元：小寫英文字母、數字、`.`、`_` 或 `-`，以字母或數字開頭。
- 範例庫中的預設組合，會在環境出現前以 apt-get 安裝套件：含 App Builder 建置工具與 patchelf 的 Ubuntu 24.04 建置器（預設建置器）與 Debian 12 建置器（兩者皆為完整版），以及 Clang/LLVM（LLVM 18）、Fortran（gfortran 13）、Kernel builder、.NET 8 SDK 與 Kotlin（Kotlin 1.3.31），這些全都在 Ubuntu 24.04 上。每個預設組合的 glibc 都不高於 2.39，因此在其中建置的東西可以在套件能執行的每一台 NAS 上執行（[應用程式能在哪些 NAS 型號上執行](#應用程式能在哪些-nas-型號上執行)）。apt-get 失敗時不會留下環境。從 shell 執行：`bvs env create <名稱> --from <rootfs-id> --package <名稱> --package <名稱>`。
- 「複製」會以新名稱建立完整副本，例如在不動到原始環境的情況下試驗變更。
- 「匯出」會把環境打包成 `toolchains/exports/<名稱>-<時間>.tar.zst`。
- 「刪除」會移除環境。環境中有 shell 或程序執行時，bvsd 會拒絕。`projects/` 中的檔案不屬於環境，會保留。

在環境中：

- 資料目錄的 `projects/` 會以 `/projects` 出現，shell 從那裡開始；
- `HOME` 是環境的 `/root`，`LANG` 是 `C.UTF-8`；
- 會掛載 `/proc`、唯讀的 `/sys` 與一個自己的小型 `/dev`，並把 NAS 的 `resolv.conf` 與 `hosts` 複製進去；
- 這些掛載屬於 shell 自己的掛載命名空間，shell 結束時就會消失，因此 NAS 上不會留下任何掛載。

從 shell 做同樣的事（範例直接以名稱呼叫 `bvs`；套件執行期間，cron 工作和 NAS 上的 SSH 工作階段都可以這樣呼叫）：

```bash
bvs env list
bvs env create deb12 --from debian-12-x86_64
bvs env shell deb12
bvs env run deb12 -- make -C /projects/hello
```

`bvs env shell` 與 `bvs env run` 會在您執行它們的資料夾中開始。資料目錄 `projects/` 下的資料夾，就是環境中 `/projects` 下的同一個資料夾。其他任何資料夾（CI 工作的 checkout、共用中的資料夾）會掛載到 `/work`，指令從那裡開始。從 `/` 執行時，會從 `/projects` 開始。

### 用 bvs qbuild 建置 QPKG

`bvs qbuild` 會從您執行它的資料夾（含有 `qpkg.cfg` 的資料夾，例如新專案的 QPKG 範本所建立的資料夾）建置 QPKG。它在 Debian 或 Ubuntu 環境中執行 QNAP 的 `qbuild`。qbuild 以完整路徑呼叫系統程式，而在環境中，這些路徑指向環境的程式，而不是 NAS 本身的程式。套件會產生在資料夾的 `build/` 中。

```bash
bvs tc install qdk-2.5.0-x86_64      # 只需一次；ARM 的 NAS 用 qdk-2.5.0-aarch64
cd /share/CACHEDEV1_DATA/BucketVS/projects/MyTool
bvs qbuild                           # --env <名稱> 選擇環境
```

它使用環境 `ubuntu2404-builder`（範例「Ubuntu 24.04 builder」），沒有時使用 `debian12-builder`，再沒有時使用其他 Debian 或 Ubuntu 環境，且其中需要 `rsync`。缺少某些東西時，它會停止並指出加入它的指令，例如 `bvs env run ubuntu2404-builder -- apt-get install -y rsync`。`bvs qbuild` 之後的參數會交給 qbuild。除非資料夾中有 `certificate` 與 `private_key` 檔案（可選 `ca_certs`），否則套件沒有程式碼簽署：有這些檔案時，QDK 的 qbuild 會用 `openssl` 簽署，因此環境需要 `openssl`。請勿把這些檔案放進 git。

環境有自己的檔案與工具。其中的一切都以 root 執行，看得到 NAS 上的每個程序，並使用 NAS 的網路。

### QPKG 的程式碼簽署

App Center 安裝套件時會檢查它的程式碼簽章。簽署需要 QNAP 發給您的程式碼簽署憑證。`bvs qbuild` 會在第一行說出三種情況之一，例如 `bvs: code signing, case 1 (a certificate as files): …`：

| 情況 | 資料夾中有 | 會發生什麼 |
|---|---|---|
| 1，檔案形式的憑證 | `certificate` 和 `private_key`，可另加 `ca_certs`（憑證鏈） | qbuild 用 `openssl` 簽署套件；環境需要 `openssl` 和 `bsdextrautils` |
| 2，硬體權杖上的憑證 | `certificate_hsm` | 不支援也未經測試：`bvs qbuild` 會在建置任何東西之前停止 |
| 3，沒有憑證 | 以上檔案都沒有 | 套件以未簽署的方式建置 |

- **情況 1。** 把三個檔案放在 `qpkg.cfg` 旁邊。環境缺少程式時，`bvs qbuild` 會說出指令，例如 `bvs env run ubuntu2404-builder -- apt-get install -y openssl bsdextrautils`。它會拒絕只有 `certificate` 和 `private_key` 其中之一的資料夾（qbuild 會發出警告並建置未簽署的套件），以及其中任一檔案為空的資料夾。請讓這些檔案不進 git：QPKG 範本的 `.gitignore` 已列出它們。在 CI 中，把它們存成儲存庫的 secrets，只在建置期間寫入資料夾。
- **情況 2。** qbuild 會從變數讀取金鑰在權杖上的位置，但這些變數在環境中傳不到它，而且本套件沒有用權杖測試過。請不帶 `certificate_hsm` 建置，再到持有權杖的電腦上用 QDK 自己的工具簽署套件；給 AI 助理的 QNAP 技能（`references/signing.md`）說明了 QDK 的做法，本套件從未執行過。
- **情況 3。** 在一台 QTS 6.1 的機器上，對未簽署套件執行 `qpkg_cli -m` 沒有安裝任何東西，而在共用資料夾中執行 `sh <檔案>.qpkg` 則安裝並啟動了它。[套件儲存庫](#套件儲存庫)只有在管理員允許未簽署套件時才接受它；另一台 NAS 上的 App Center 只有在該 NAS 的擁有者允許沒有有效數位簽章的應用程式時才會安裝它，本套件從不建議這樣做。**匯出為 QPKG** 一律屬於這種情況。
- `qbuild --sign` 會加上 GnuPG 簽章，這不是 App Center 和儲存庫讀取的程式碼簽章。

## App Builder

App Builder 在環境中把選定的程式編譯到單一資料夾，也就是應用程式的前綴（預設為 `/usr/app/my_app_prefix`）。

**應用程式** 的 **範例** 分頁有現成的應用程式、環境、CI 工作流程、cron 工作與編輯器專案。卡片上的 **檢視程式碼** 會在使用前顯示範例會建立的內容，任何角色都能用：編輯器專案的檔案、工作流程檔、cron 工作的命令，應用程式則是依建置順序列出的配方，以及每個配方的 `build.sh` 與 `recipe.json`。檢視器為唯讀，只顯示文字檔；環境沒有檔案，所以它的卡片沒有這個按鈕。使用範例需要 developer 角色，環境則需要 admin 角色：developer 看到的環境卡片沒有 **使用此範例**。

### 配方

每個程式都從一個配方建置。配方會寫明其原始碼與 sha256、所依賴的配方、環境所需的建置工具，以及證明結果可用的檢查（例如 `bin/bash --version`）。現有的配方包括函式庫（zlib、openssl、ncurses、readline 等）、shell（bash、zsh、busybox）、工具（coreutils、curl、git、jq、rsync、tmux、htop、nano 等）、伺服器（nginx、redis、openssh、dropbear）與一種程式語言（lua）。

- 配方只會在其所依賴的配方之後建置。
- 只要配方的輸入不變（原始碼、配方、其相依項目、環境的編譯器與 C 函式庫、前綴，以及動態或靜態連結），且其安裝的每個檔案仍在前綴中，重新建置時就會沿用配方的結果。
- 連結方式決定程式如何找到其函式庫。**動態** 會給每個程式 `<prefix>/lib` 的 RUNPATH。**relocatable** 以相對於程式本身資料夾的方式寫入（`$ORIGIN/../lib`），因此這個樹狀結構放在任何資料夾都能找到函式庫；但編譯進程式的路徑（例如 terminfo 資料夾）仍指向前綴。**bundle** 除了 relocatable 之外，還會把程式從建置環境載入的函式庫（例如 libstdc++）複製到 `lib`，因此應用程式只需要 NAS 的 C 函式庫。**靜態** 會以靜態方式連結支援的配方。在動態、relocatable 與 bundle 之間切換不會重新建置任何東西。
- 靜態應用程式會拒絕無法靜態連結的配方（openssh、dropbear 與 tmux），並逐一說明原因，除非應用程式設定為以 bundle 建置它們。新應用程式頁面與 **變更** 提供全部四種方式，靜態應用程式還能設定該選項。
- 查詢使用者、群組或主機名稱的靜態程式，無論 `/etc/nsswitch.conf` 如何設定，都只會從 NAS 本身的 `/etc/passwd`、`/etc/group`、`/etc/hosts` 與 DNS 讀取，因此不認得網域或 LDAP 的帳號。需要這些帳號時，請以動態方式建置這類程式。靜態應用程式包含這類程式時，面板會顯示警告。
- 最後一個配方完成後，會在完成的前綴上再次執行每個配方的檢查。
- 前綴 `etc/` 下的設定檔在重新建置後仍會保留：編輯過的檔案會保留，新的預設檔案則以 `<file>.bvs-new` 放在旁邊。

### 配方更新

新的和更新過的配方也會以簽署過的目錄形式從 pub.qnaputilities.com 提供，因此不必等下一個 Bucket & Code Server IDE 版本就能送到 NAS。
bvsd 每天檢查一次是否有更新的目錄。只有在簽章與內建於 bvsd 的金鑰相符、每個檔案都與目錄的清單相符、且配方比隨套件提供
的更新時，才會使用；未通過檢查的目錄永遠不會被使用。目錄使用中時，新應用程式的配方清單會把目錄帶來的配方標示為
**目錄新增** 或 **目錄更新**。

應用程式頁面的 **配方庫** 卡片會顯示建置使用哪個配方庫（附套件版本的 **套件配方庫**，或附目錄版本的 **目錄**），以及
上次檢查和結果。管理員在那裡有兩個按鈕（第二個只在目錄使用中時出現）：

- **檢查配方更新** 立即檢查，不必等每天的檢查。
- **使用套件的配方庫** 回到隨 Bucket & Code Server IDE 提供的配方。目錄會被擱置，直到有更新的目錄；之後建置就使用較新的那一個。

切換之後重新建置時，檔案沒有變動的配方都會重複使用。每次建置前，bvsd 都會再次檢查目錄的檔案。若有檔案在 NAS 上被改動，
建置會在執行任何東西之前停止，之後的建置改用前一個目錄或套件配方庫。

### 建置應用程式

**應用程式**、**新應用程式**（developer 角色）會詢問名稱、前綴（除非另選，否則為 `/usr/app/<名稱>`）、應用程式建置所在的環境（Debian 或 Ubuntu 環境；範例庫可建立「Ubuntu 24.04 builder」，頁面會優先選擇它）、目標（這台 NAS 的架構，或以交叉建置產生 aarch64 或 armv7）、應用程式能執行的 NAS 型號、連結方式與配方。它們所依賴的配方會依建置順序加入。

應用程式也可以使用一個擁有自己 `recipes/` 資料夾的專案的配方。在 **套件** 步驟的 **專案的配方** 中選擇該專案。專案的配方會列在最前面，位於專案名稱之下並標示 **專案**，之後才是配方庫的配方。與配方庫中某個配方同名的專案配方，會在此應用程式中取代該配方，並顯示它取代的配方庫版本。專案略過的配方會連同原因列出。已選的配方若不在專案的清單中，會在選單下方列出並從應用程式中略過；選擇含有它的清單後，它會再回來。應用程式頁面會顯示專案名稱，開發者可從該名稱開啟專案檢視。viewer 會看到應用程式上次建置的配方，依其記錄列出的順序，各附狀態與記錄；第一次建置之前沒有可顯示的順序。

每種連結方式都是一張卡片，附有程式在哪裡找到函式庫的圖示、該方式在大小、更新與應用程式執行位置上的代價，以及支援它的配方。在您選擇配方之前，卡片計算的是整個配方庫；回到第一步時，則計算您這次建置的配方。對靜態應用程式，您要選擇無法靜態連結的配方如何處理：以 bundle 建置，或不納入。

- **建置**（或 **重新建置**）會執行一個工作：下載每個原始碼並檢查其 sha256，在環境中安裝配方所需的建置工具，在應用程式上一次建置的副本中建置，並檢查每個程式。
- 失敗、取消或中斷的建置會讓應用程式維持原狀：啟用、下載與匯出會繼續使用最新一次成功的建置。
- 一個應用程式一次只執行一個建置；使用中應用程式的程式在執行時，建置會等候（請先停止它們）。
- 應用程式頁面會顯示建置順序、每個配方的狀態與記錄、前綴下的檔案，以及最新一次成功建置的封存檔（**下載**，一個 `.tar.zst`）。
- 應用程式頁面上的 **變更** 可以設定其他環境、應用程式能執行的 NAS 型號、連結方式、取用配方的專案或配方；下一次建置會套用。
- **相依性** 是應用程式頁面的一個分頁，顯示最新建置中每個 ELF 檔案需要什麼，以及 NAS 的載入器在哪裡找到它，全程只讀取、不執行任何東西。缺少的函式庫、函式庫未定義的版本，或指向應用程式外部的 RUNPATH，都會以附修正方式的發現列出。developer 可用按鈕執行修正：**加入 … 並建置**、**切換為 … 並建置**、**再次建置**、**修補 RUNPATH**、**設定直譯器**、**選擇其他環境** 或 **只為這台 NAS 建置**。沒有按鈕的修正（在環境中安裝套件、為其他機器建置）是說明該怎麼做的句子。發現下方以圖（程式、應用程式的函式庫、NAS 的函式庫與缺少的函式庫分欄排列；虛線表示函式庫缺少的版本，點線表示到處都找不到的函式庫）或樹狀結構畫出每個檔案及其需求；選擇檔案即可查看詳細資料。在 NAS 上，`bvs deps <app>` 會印出相同的內容。
- **ELF 檢視器**（工具鏈，developer 角色）檢查單一檔案：來自應用程式的建置、專案資料夾或環境的檔案，管理員則可檢查 NAS 上的任何檔案，並依它在這台 NAS 上或環境中執行的情況檢查。`bvs ldd <file>` 在 NAS 上做同樣的事（環境用 `--env <name>`）。
- **每次建置** 都會在取代應用程式前對結果執行相同的檢查；若有會讓程式無法在這台 NAS 上執行的問題，或有程式需要比應用程式允許的更新的 glibc，建置即失敗，問題與修正方式會寫在工作的訊息中。
- **修補工具**（相依性分頁）會以環境的 patchelf，為最新建置中選定的檔案設定 RUNPATH（應用程式的 `lib`、`$ORIGIN` 形式或應用程式的某個資料夾）或程式直譯器；操作在副本上進行，只有成功時才取代建置。**預覽** 會顯示每個檔案修改前後的樣子，以及新的 RUNPATH 在應用程式中找不到的函式庫；**套用修補** 會執行它。下一次建置會再以應用程式的連結方式寫入 RUNPATH。

Qt 的建置會執行 Qt 自己的工具（moc、rcc，以及翻譯用的 Linguist lrelease）。因此在交叉建置中，Qt 的配方（`qtbase`、`qttools`）會先為這台 NAS 把這些工具建置到前綴下的 `host/`，再為目標建置 Qt；以 Qt 建置的配方會從那裡執行這些工具。目標上沒有任何東西會執行 `host/`。它是應用程式建置的一部分，因此封存檔與 **匯出為 QPKG** 也會包含它。

### 自己的配方

當配方庫沒有某個程式的配方時，專案可以帶著自己的配方。配方放在專案的 `recipes/` 資料夾中，格式與配方庫相同，只有指定該專案的應用程式才會建置它們。以這種方式製作套件共有五個步驟：

1. **建立專案。** 在 [新專案](#新專案) 中，範本 **Package from source**（從原始碼建置的套件）會建立一個資料夾，內含以專案命名的入門配方、一個用於兩個應用程式（一個給這台 NAS 的處理器，一個給 aarch64，在 x86_64 的 NAS 上為交叉建置）的 Makefile，以及由 **服務** 步驟產生的套件啟動與停止腳本。
2. **撰寫配方。** 編輯器中的 **New recipe** 會從同一個入門配方再新增一個配方（[開啟編輯器](#開啟編輯器)）。在 `recipe.json` 中填入程式來源真正的位址與其 sha256（在編輯器的終端機執行 `curl -fsSL <位址> | sha256sum` 會印出該值），並把建置步驟寫進 `build.sh`。配方資料夾只放 `recipe.json`、`build.sh`，以及 `recipe.json` 指定的 vendor 檔案，因此修補要以 here-document 的形式寫進 `build.sh`。
3. **檢查。** 每次儲存配方中的檔案，都會檢查專案的配方，並在 **Problems** 檢視中列出結果；終端機中的 `make lint` 與 [專案檢視](#專案檢視) 的 **配方** 卡片會得到相同的結果。檢查只讀取 `recipe.json`，並以 `bash -n` 剖析 `build.sh`，從不執行配方。
4. **建置。** 專案檢視中的 **建置** 會在兩個應用程式不存在時建立它們，再依序建置。建置在開始時只讀取一次專案的配方，因此之後才儲存的檔案要等下一次建置。與配方庫中某個配方同名的專案配方，只會在此專案的應用程式中取代該配方，建置記錄會註明這一點。
5. **打包並安裝。** 在編輯器的終端機執行 `make qpkg`，會以工具鏈中的 QDK 把每個應用程式最新的成功建置打包成套件，沒有成功建置的應用程式則略過。若程式碼簽署檔案放在 `qpkg.cfg` 旁的 `qpkg/` 中，或放在 `QPKG_CERT_DIR` 指定的資料夾中，套件就會被簽署（[QPKG 的程式碼簽署](#qpkg-的程式碼簽署)），而專案檢視中的 **測試部署**（admin）會把它安裝到這台 NAS。

專案的配方有幾條規則：

- 專案略過的配方（資料夾中多出的檔案、連結、超過大小上限的檔案）會在配方卡片與檢查結果中連同原因列出，需要它的建置會被拒絕。
- 專案資料夾被改名或刪除後，它的應用程式就無法建置：建置會回應「沒有專案『…』」，直到在應用程式頁面的 **變更** 中設定專案的新名稱或 **無：僅配方庫**。
- 專案的配方與配方庫的配方一樣，在建置環境中以 root 執行。任何開發者都能撰寫並建置它們，就像任何開發者都能透過建置執行專案的 Makefile。viewer 只會在建置記錄中看到應用程式的配方名稱，看不到配方檔案。
- 專案的配方留在它的專案中。只有經過審閱、對 Bucket & Code Server IDE 本身的修改，才會讓它進入配方庫，以及每台 NAS 都會取得的已簽署目錄。
- 建置需要 npm、Go 或 Cargo 套件的配方，會在 vendor 檔案中列出它們。這份清單由 `/usr/bin/bucketvs/share/bucketvs/tools/` 中的 `npm-vendor.py`、`go-vendor.py` 與 `cargo-vendor.py` 寫出；它們需要 [工具鏈](#工具鏈與環境) 中的 Python 執行環境。

### 應用程式能在哪些 NAS 型號上執行

以 glibc 建置的程式，只能在 glibc 至少與程式要求的最新版本一樣新的 NAS 上啟動。套件假設它能執行的每一台 NAS（QTS 6.0、QuTS hero 6.0 及更新版本）都有 glibc 2.39 或更新版本：這是它的 glibc 下限。2.39 是 Ubuntu 24.04 與 QuTS hero 6.0.1 的 glibc，從該韌體本身的 `/lib/libc.so.6` 讀取而得；QNAP 沒有公布 glibc 版本，而 QNAP 的版本資訊中沒有 QTS 6.0。執行 QTS 6.1.0 與 QuTS hero 6.1.0 的測試 NAS 為 2.43。

新應用程式與 **變更** 中的 **執行於** 可選擇以下兩者之一：

- **套件能執行的每一台 NAS**（預設）：任何程式都不能需要高於 2.39 的 glibc。請在 glibc 為 2.39 或更舊的環境中建置這類應用程式，例如 Ubuntu 24.04 builder（2.39）或 Debian 12 builder（2.36）。**匯出為 QPKG** 與套件儲存庫請選擇此項。
- **只有這台 NAS**：程式可以需要這台 NAS 的 glibc，適用於需要較新 glibc 的軟體。這類應用程式會為這台 NAS 的架構建置，且永遠不會匯出為 QPKG。

交叉建置（aarch64 或 armv7）一律適用於每一台 NAS。當所選環境的 glibc 超出應用程式允許的範圍時，新應用程式會發出警告。每次建置後，bvsd 會讀取每個程式與每個函式庫要求的 glibc 版本，交叉建置也一樣。超過上限的檔案會讓建置失敗，並會指出修正方式：其他環境（**選擇其他環境**）、連結方式 static，或 **只為這台 NAS 建置**。應用程式頁面會說明其最新一次成功的建置需要哪個 glibc，而 **相依性** 分頁會列出每個超過上限的檔案。

在 NAS 上，`bvs tree-deps <前綴> --tree <資料夾> --glibc-max 2.39` 會對任何應用程式樹執行相同的檢查。

### 在 NAS 上啟用應用程式

**啟用**（管理員）會把前綴設為指向應用程式最新一次成功建置的連結，讓其程式在 NAS 上從 `/usr/app/<名稱>/bin` 執行。啟用絕不取代不是它自己建立的東西：前綴位置上已有資料夾、檔案或其他連結時，會拒絕並說明原因。QTS 的系統磁碟在每次開機時都會重建，因此套件每次啟動都會重新建立連結。**停用**（管理員）會移除連結；刪除應用程式時會先停用，再把它移到資源回收筒保留 7 天。developer 只能在應用程式未啟用時刪除它。為其他架構建置的應用程式無法在這裡啟用；請下載其封存檔，供該類 NAS 使用。

### 匯出應用程式

**匯出為 QPKG**（管理員）會把最新一次成功的建置打包成獨立的 QPKG，可以在沒有此套件的其他 NAS 上安裝該應用程式。它會拒絕只為這台 NAS 建置的應用程式；若建置中的程式需要高於 2.39 的 glibc，它會在打包前停止（參見[應用程式能在哪些 NAS 型號上執行](#應用程式能在哪些-nas-型號上執行)）。這需要 QDK：請在工具鏈頁面的 QNAP 分頁安裝「QNAP QDK 2.5.0」。在安裝 QDK 之前不會顯示該按鈕，應用程式頁面會說明取得方式。匯出會在應用程式的環境中執行 qbuild，方式與 [`bvs qbuild`](#用-bvs-qbuild-建置-qpkg) 相同。每次建置都會在該環境安裝 `rsync`；對最後一次以較舊版本套件建置的應用程式，拒絕訊息會指出加入它的指令。匯出的套件在啟動時建立其前綴的連結，停止時移除。它沒有程式碼簽章：工作的記錄會寫出[情況 3](#qpkg-的程式碼簽署)，按鈕下方的一行也是。在 SSH 工作階段中，`/etc/init.d/<名稱>.sh disable` 與 `enable` 會設定匯出套件在 App Center 中的開關，前綴的連結保持不變。匯出的套件每次啟動時，也會在 `/etc/profile` 與 `/root/.profile` 加入一行，把應用程式的 `bin` 與 `sbin` 放到 SSH 登入的 `PATH` 中、排在 NAS 本身的指令之後，admin 與其他可登入的帳號都適用；移除套件時會一併拿掉這一行。

## 套件儲存庫

此套件維護一個 App Center 儲存庫：發佈到其中的 QPKG 套件，可以像任何儲存庫的應用程式一樣，從其他 NAS 的 App Center 安裝。

- **位址。** `bvs repo list`（以及 `GET /api/v1/repo`）會印出位址：`<base>/repo/repo.xml`，其中 `<base>` 在套件發佈到網際網路期間為已發佈的套件主機名稱（沒有時為已發佈的主要主機名稱）（[發佈到網際網路](#發佈到網際網路cloudflare-tunnel)），否則在設定了公開 URL 時為該 URL，再否則為 `http://<NAS 位址>:9950`（一般 HTTP 僅做重新導向時為 `https://…:9951`）。在另一台 NAS 上：App Center、**設定**、**應用程式來源**、**新增**，再輸入這個位址。
- **發佈。** 使用 `bvs repo publish <file.qpkg>`（在 NAS 上以 root 執行），或以檔案作為內容呼叫 `POST /api/v1/repo/packages`（developer 角色）。架構由 QDK 檔名 `<name>_<version>_<arch>.qpkg`（`x86_64`、`arm_64`、`arm-x41`）或 `--arch` 決定。名稱、版本、說明與作者取自套件本身；`PUT /api/v1/repo/packages/<name>/meta` 與 `…/icon`（PNG，管理員）可以變更 App Center 顯示的內容。套件不能命名為 `default`：那是儲存庫本身圖示的名稱。
- **只接受已簽署的套件。** 沒有程式碼簽署的套件會被拒絕，因為 App Center 只會在設定為允許這類套件的 NAS 上安裝它們。當套件的程式碼簽署區塊是包含憑證與簽署者的 PKCS#7 簽署時，儲存庫即視其為已簽署。儲存庫不會把該簽署或其憑證與 QNAP 的比對；那是 App Center 安裝套件時的工作。在已知情況的網路中，管理員可以在設定中開啟 `repo.accept_unsigned`；這類套件會在說明中加上標記。App Builder 匯出的套件沒有簽署，因此需要這個設定。
- **版本。** 每個版本都能下載，但 App Center 每個套件只會看到一個版本，也就是管理員列出的那個。管理員發佈的套件第一版會立即列出；之後的版本要等管理員移動它（`POST /api/v1/repo/packages/<name>/latest`，或 `bvs repo publish --latest`）才會列出。developer 發佈的版本，包括新套件的第一版，在管理員把某個版本設為最新之前都不會列出：因為每台加入此儲存庫的 NAS，都會以 root 安裝列出的版本。刪除版本（管理員）會移除其所有架構；若它是列出的版本，在管理員選擇其他版本前不會列出任何版本。
- **上傳。** 同時最多複製兩個上傳；若上傳會讓資料磁碟區的可用空間少於 256 MiB，儲存庫會拒絕。儲存庫不包含在備份中：還原後請重新發佈套件。
- **在面板中。** **套件** 頁面顯示要複製的地址、套件及其版本，以及是否已簽署；開發者在那裡上傳 QPKG，管理員把某個版本列為最新並刪除版本。它的 **程式碼簽署** 卡片顯示[三種情況](#qpkg-的程式碼簽署)、有多少列出的版本沒有簽章，以及管理員針對未簽署套件的開關，開啟前會先詢問。應用程式頁面會顯示其流程（建置、匯出為 QPKG、發佈）與 **發佈** 按鈕。

## 核心工坊

核心工坊為 NAS 正在執行的核心建置核心模組，逐一與該核心比對，並載入它們。QNAP 尚未公開其目前核心的原始碼（其 GPL 發佈止於 QTS 5.2.3），因此工坊會讀取執行中的核心顯示的自身資訊，並據此設定一棵相符的核心樹。

### 需要的東西

- 範例庫中的 Kernel builder 環境（含 GCC 13 與核心建置工具的 Ubuntu 24.04）。模組必須以與核心相同的 GCC 主要版本建置；工坊會比對兩者。
- admin 角色，以及終端機中的 root：`bvs kernel` 指令會使用 bvsd 的 CLI socket 或載入模組。

### 設定檔

`bvs kernel profile` 會顯示執行中核心的資訊：其版本（`6.6.129-qnap`）、QNAP 建置它時使用的編譯器、QNAP 自己模組的 vermagic、這些模組中 `struct module` 的大小與 init、exit 的位置、模組是否必須簽署，以及核心樹為了相符而取得的設定。

### 工作區

`bvs kernel prepare` 會在資料資料夾的 `kernels/<release>` 中建立執行中核心的工作區：

1. 它採用目錄中對應此版本的核心原始碼（`bvs kernel sources`）：QNAP 有發佈時使用 QNAP 自己的 GPL 核心樹，否則使用 kernel.org 的核心樹，後者缺少 QNAP 自己的修改。`--source` 可選擇其他來源。
2. 它設定核心樹：若是 QNAP 的核心樹，使用 QNAP 針對此型號的設定；否則使用設定檔中的設定。`--option NAME=m` 可加入設定，例如某個驅動程式。
3. 它為建置模組準備核心樹（`--full` 則會建置整個核心，所需時間長得多，但能讓檢查看到核心匯出的符號）。
4. 它確認核心樹建置出的正是執行中核心的版本。

它會以工作的形式在工作頁面執行。prepare 失敗時會保留上一次的核心樹。

### 建置模組

- 您自己的模組是 `projects/` 中含 `Kbuild` 檔案的資料夾。在該資料夾中執行 `bvs kernel make`（或 `bvs kernel make --project <folder>`）即可建置。範例「Kernel module」就是這樣的專案。
- QNAP 未隨附的核心驅動程式，從核心樹中其所在目錄建置：`bvs kernel make --dir drivers/usb/serial`。範例「USB serial driver ch341」就是以這種方式建置 ch341 驅動程式；其設定要先以 `bvs kernel prepare --option USB_SERIAL=m --option USB_SERIAL_CH341=m` 加入。

建置產生的模組保存在 `kernels/<release>/modules/`。QNAP 已為此核心隨附的模組不會保存：載入的仍是 QNAP 自己的那一份。`bvs kernel make --build-only` 不會在那裡保存任何東西：模組留在建置的位置。新專案核心範本的 CI 工作流程就是這樣建置。

### 檢查與載入

`bvs kernel check <module>` 會把模組與執行中的核心比對：vermagic、`struct module` 的大小以及其中 init 與 exit 的位置（與 QNAP 自己的模組比對）、是否需要簽署、所依賴的模組，以及需要的每個符號。每項發現都會說明哪裡不相符，以及該怎麼做。結束狀態 3 表示該模組不得載入。

`bvs kernel load <module>` 會再次執行檢查並載入模組，先載入其所依賴的模組（NAS 上 QNAP 自己的，或您的）。檢查有錯誤的模組絕不會被載入。`bvs kernel unload <module>` 會移除模組；使用中的模組會保持載入。每次載入與卸載都會寫入 `log/kernel-modules.log`。

QNAP 的核心不會列出其資料符號，因此檢查無法查詢 `jiffies` 等符號，只會以警告列出。若其中任一個缺少，核心會在載入時拒絕該模組。

### 應用程式中的模組

應用程式可以指定工作區中的模組。啟用應用程式時，會在建立其連結前載入這些模組；套件每次啟動、面板可用之後，也會再次載入它們；仍在進行中的載入會顯示在核心頁面上。停用時會先卸載它們；有模組在使用中時，應用程式會保持啟用。

- 若 NAS 在載入模組期間重新啟動，下一次啟動不會載入任何模組，並顯示通知：請手動載入，或再次啟用應用程式。
- 若 NAS 在套件已載入模組的那次開機中，未經套件的正常停止就停止運作（當機、斷電或強制重設），下一次啟動不會載入那些模組，因為其中之一可能就是當機的原因。通知與核心頁面會列出它們。手動載入其中之一，或再次啟用其應用程式，啟動時就會再次載入它。NAS 正常關機或重新啟動會按順序停止套件，不會改變任何事。
- 啟動永遠無法結束的模組（例如在等待硬體的驅動程式）不會影響面板運作。一分鐘後會有通知指出它；其他載入與卸載會等待它，重新啟動 NAS 可結束它。

QTS 更新到其他核心後，必須在新核心的工作區中重新建置模組。還原到其他資料資料夾後也一樣：`kernels/` 不包含在備份中。

### 在面板中

**核心** 頁面會顯示執行中核心的設定檔、目錄中的核心原始碼、工作區及其模組。管理員在此準備工作區（來源、環境、額外設定、是否建置整個核心），並從專案資料夾或核心樹的目錄建置模組；兩者都以工作的形式執行。**檢查**（developer）會顯示每項發現及其修正方式；**載入** 與 **卸載**（管理員）的作用與 `bvs kernel load` 與 `unload` 相同，被拒絕的載入會顯示擋下它的檢查結果。資料資料夾下任何位置的 `.ko` 檔案都能以其路徑檢查。

## cron 工作與操作

工作頁面有兩個清單：排程工作（cron）與操作（bvsd 執行的長時間工作，例如安裝與搬移）。

### 排程工作

developer 建立並變更自己的工作；管理員可變更所有工作。工作包含：

- 排程：五個 cron 欄位（`0 3 * * *`）、以秒為首的六個欄位，或描述符（`@daily`、`@hourly`、`@every 6h`）。前綴 `CRON_TZ=<zone>` 可選擇其他時區；沒有前綴時，工作以頁面頂端顯示的 NAS 時區執行。
- 指令：由套件的 `bash -lc` 以 root 執行。多行會作為一個指令碼執行，其輸出成為該次執行的記錄。
- 執行位置：在 NAS 上，或在環境中（使用環境的 `bash`，沒有時使用 `/bin/sh`）。
- 工作資料夾：相對於資料目錄的 `projects/`，或完整路徑。在環境中只看得到 `projects/`，也就是 `/projects`。
- 逾時：以秒為單位（0 表示無）。超過時間仍在進行的執行會被停止，並標記為逾時。
- 執行進行中時：略過下一次執行，或讓它等候（最多一個等候）。
- 失敗時：失敗或逾時的執行會送出[通知](#通知)。

每個工作保留最近 100 次執行及其輸出的結尾（4 KB）。每次執行的完整記錄保留 30 天。

工作代替其擁有者以 root 執行，並以擁有者的家目錄作為 `HOME`，因此只有在擁有者是已啟用的 developer 或 admin 時才會執行。擁有者停用或為 member 或 viewer 角色期間，工作的每個時間點都會記錄為一次已略過的執行並說明原因，例如「its owner ola is disabled」；**立即執行** 也會以相同原因拒絕。不會送出通知。擁有者被停用或改為低於 developer 的角色時正在進行的執行會被停止。帳號重新啟用或再次成為 developer 後，工作就會再度執行。刪除帳號會刪除其工作。內建工作無論帳號發生什麼事都會執行。

指令結束時，它在背景啟動的程序會繼續執行。發生逾時、刪除工作或停止套件時，該次執行的整個程序群組會收到 SIGTERM，5 秒後再收到 SIGKILL。

在調整時鐘的夜晚（日光節約時間），工作會遵循 Debian cron 的規則。分鐘或小時欄位以 `*` 開頭（包含 `*/N`）、`@hourly` 或 `@every` 的工作依經過的時間執行：秋季會在重複的那一小時的兩次經過中都執行，春季則不會執行被跳過的那一小時。其他工作的每個時間點都只執行一次：落在重複那一小時的時間點只執行一次，落在被跳過那一小時的時間點則在時間調整後立即執行。

每個已登入的帳號（包括 `viewer`）都能讀取工作的指令與其執行輸出。不要把密碼或權杖寫進指令：讓指令從 `<data>/etc` 下只有 root 能讀的檔案（`chmod 600`）讀取。

### 內建工作

| 工作 | 時間 | 內容 |
|---|---|---|
| 維護 | 每天 03:30 | 從資料目錄的 `tmp/` 移除 10 天未變更的內容；`log/audit.log` 超過 10 MB 時進行輪替 |
| 清空資源回收筒 | 每天 03:45 | 移除移到資料目錄 `trash/` 超過 7 天的內容（例如已刪除帳號的家目錄） |
| 重新整理工具鏈目錄 | 每天 04:15 | 取得已簽署的工具鏈目錄 |
| 輪替 nginx 記錄 | 每小時第 7 分鐘 | `nginx-access.log` 或 `nginx-error.log` 超過 20 MB 時改名為 `.1`（保留五個舊檔），並通知 nginx 重新開啟其記錄 |

管理員可以變更內建工作的排程、逾時、執行中的處理方式與通知，也可以關閉它。其名稱與指令不變。

### 操作

安裝、環境工作、資料搬移與開啟模組都以操作的形式執行。清單顯示最近 50 個，最新的在前，並附上進度與記錄。操作及其記錄保留 30 天。套件停止時仍在執行的操作會顯示為 `interrupted`。developer 可以取消自己的操作；管理員可以取消任何操作。

## 備份

**備份** 頁面（管理員）設定要備份什麼、何時備份、副本送往何處，以及保留多少份。按下 **立即備份** 可隨時執行備份。新安裝時排程備份為關閉；開啟 **排程備份** 後，預設每天執行一次備份（內建 cron 工作 `backup`）。它會保留最近 7 天、4 週與 6 個月中每一段的最新一次執行，並一律保留最新一次成功的執行。同一時間只會執行一個備份、驗證、還原或刪除。

同一天的兩次執行算作一天：較新的一次完成時，會移除當天較早的那一次。因此下午的 **立即備份** 會取代當天早上的排程執行；若當天早上的狀態很重要，請先把該次執行複製到別處。

### 備份的內容

一次執行就是一個資料夾 `<資料>/backups/<id>/`，其中的 `manifest.json` 列出每個檔案及其 sha256：

| 部分 | 內容 |
|---|---|
| 設定 | `etc/`：設定、帳號、TOTP 密鑰、TLS 金鑰、備份金鑰 |
| Git | Forgejo 的 dump：儲存庫、Wiki、LFS 物件、附件及其資料庫 |
| SVN、bucket | 每個儲存庫的副本，以遞增方式建立：已複製過的修訂版本不會再複製 |
| VS Code 設定 | 每個帳號的 VS Code 設定、按鍵繫結、程式碼片段與擴充功能清單（不含擴充功能的檔案：擴充功能檢視會重新安裝它們） |
| 環境 | 工具鏈的環境（預設關閉：可以重新建立） |
| 專案 | 每個專案資料夾，包含其 Git 歷史與尚未提交的檔案，不含 `node_modules` |
| HOME 資料夾 | 每個帳號的 HOME 資料夾，不含快取和 AI 工具本身的安裝（它們的安裝按鈕會放回） |
| App Builder | 每個應用程式的設定與建置歷史；建置會重新產生應用程式的檔案 |
| 套件儲存庫 | 已發布的套件、它們的圖示，以及 App Center 讀取的索引 |

無論頁面上勾選了什麼，這四個部分都會在每次備份中。自上次備份以來未變更的資料夾會連結到該次備份的副本，不佔用空間。

只有在每個部分都寫入並檢查完成後，該次執行才會出現；失敗或磁碟區已滿時，會移除未完成的資料夾，並保留先前的執行不變。**驗證** 會完整讀回一次執行：每個封存檔的 sha256、每個修訂版本的 `svnadmin verify`、每個儲存庫的 `git fsck`，以及 Forgejo 資料庫的完整性檢查。

### 目標

每次執行都會複製到頁面上的每個目標：

- 這台 NAS 上的 **本機資料夾**：照原樣保存該次執行，未變更的檔案以連結指向上一份副本，因此每份副本只佔用變更的部分。不加密。它與資料位於同一組磁碟上，能防範操作失誤，但無法防範磁碟區故障。
- 透過 SSH 的 **rsync**（`user@host:path`）。**使用套件的備份金鑰登入**：頁面會顯示要加入另一台機器 `authorized_keys` 的公開金鑰。
- **rclone**（`remote:path`，即 `<資料>/etc/rclone.conf` 中的遠端）：雲端儲存空間，以及 rclone 能連到的任何地方。

離開 NAS 的副本（rsync、rclone）必須加密：請開啟 **加密離開 NAS 的副本** 並按 **建立金鑰**。副本會以 age 對金鑰的公開部分加密；**私密部分只顯示一次**，bvsd 不會保存。沒有它，任何 NAS（包括這一台）都無法還原加密的副本：請把它保存在這台 NAS 以外的地方。新金鑰只會加密此後的執行；較舊的副本需要舊的私密金鑰。

失敗的目標會列在該次執行的結果中；執行本身仍然有效。

`rclone.conf` 存有遠端的認證資料。在副本或 **測試** 使用它之前，若它（以及備份金鑰）並非只有 root 能讀，bvsd 會把它設為只有 root 能讀（模式 600），並在工作的日誌中說明。

### 還原

對某次執行按 **還原**，可以放回全部、一個儲存庫（SVN 儲存庫、bucket 或 Git 儲存庫），或一個專案資料夾：

- 在檢查之前不會取代任何東西：每個封存檔依其 sha256 檢查，每份 SVN 副本以 `svnadmin verify` 檢查。任何副本未通過時，還原會在變更使用中的資料前停止。
- 被取代的使用中資料會移到 `<資料>/trash` 保留 7 天，因此還原本身也可以復原。
- **一個儲存庫** 或 **一個專案資料夾** 的還原在模組持續運作時進行。誤刪的專案資料夾會連同其歷史與尚未提交的檔案一起回來。
- **全部** 會停止模組、交換資料，並以還原的設定重新啟動 bvsd：所有工作階段都會結束，帳號與密碼會是備份中的內容。備份之後建立的專案或 HOME 資料夾會保留。

## 通知

bvsd 會把每則通知寫入 QTS 系統記錄。它也能透過 shoutrrr URL 把通知送到聊天與郵件服務（Telegram、Discord、Slack、電子郵件、通用 Webhook 等）。

| 通知 | 時機 | URL 開關 |
|---|---|---|
| 工作失敗 | 設定為失敗時通知的工作，其 cron 執行失敗或逾時；操作失敗 | `notify.on_job_failure` |
| 備份 | 每次備份、驗證與還原的結果，包括成功的；無法開始的排程備份 | `notify.on_backup` |
| 不斷重新啟動 | 某個程序開始不斷重新啟動 | `notify.on_crashloop` |
| 憑證 | 上傳的憑證剩餘 30 天以下；nginx 改回使用自我簽署憑證；已建立新的自我簽署憑證 | `notify.on_cert_expiry` |
| 系統 | 啟動時拒絕設定、發現未完成的資料搬移、結束殘留的程序、無法寫入設定檔 | 一律送出 |

- 開關只決定哪些通知送往 URL。系統記錄會收到每一則通知。
- 相同的通知每 10 分鐘最多送出一次。之後的下一則會說明有幾則被暫緩。
- 每個 URL 各自執行一次 `shoutrrr send`，限時 10 秒。失敗時只記錄 URL 的協定，因為 URL 的其餘部分含有其權杖。

URL 與開關位於系統頁面的 **通知**；開關預設為開啟。管理員在該處變更。其他帳號只看得到開關與每個 URL 的協定，因為 URL 的其餘部分含有其權杖。NAS 管理員也可以透過 CLI socket 設定：

```bash
/usr/bin/bucketvs/bin/curl --unix-socket /usr/bin/bucketvs/var/run/bvsd-cli.sock \
  -X PUT -H 'Content-Type: application/json' \
  -d '{"notify":{"shoutrrr_urls":["telegram://<token>@telegram?chats=<chat id>"]}}' \
  http://bvsd/api/v1/settings
```

## 記錄與支援資料包

每個記錄都是資料目錄中的 `log/<name>.log`。bvsd 及其監看的每個程序的記錄會在 20 MB 時輪替，並保留五個舊檔。`nginx-access.log` 與 `nginx-error.log` 的上限相同，會在 bvsd 每次啟動時與每小時由工作「輪替 nginx 記錄」檢查，因此其中一個最多可能超過 20 MB 一小時的流量。工作「維護」會在 `audit.log` 超過 10 MB 時輪替，並保留三個舊檔。記錄頁面（管理員）會顯示任何記錄的最後幾行、加以篩選，並在新行出現時持續追蹤：

| 記錄 | 寫入者 |
|---|---|
| `bvsd` | bvsd 本身：啟動、停止、套用的設定、問題 |
| `nginx` | nginx 本身的輸出 |
| `nginx-access`、`nginx-error` | 每個請求，以及 nginx 的錯誤 |
| `audit` | CLI socket 上的每個請求 |
| 每個模組程序各一個 | 各模組的程式 |

系統頁面會顯示版本、平台、時區，以及 bvsd 監看的每個程序。「下載支援資料包」（管理員）會為錯誤回報建立一個封存檔：

- 設定，其中 SMTP 密碼與每個 shoutrrr URL 都已遮蔽；
- 產生的 `nginx.conf`、不含金鑰的憑證詳細資料、磁碟區；
- 每個模組、程序與最近操作的狀態；
- 每個記錄的最後 2000 行，其中每個查詢字串都以 `?(query removed)` 取代。

它絕不包含密碼、工作階段、權杖或金鑰。

## 更新

bvsd 在啟動時與每 24 小時讀取 `https://pub.qnaputilities.com/bucketvs/latest.json`。當其中列出較新的版本時，系統頁面會顯示該版本，並附上摘要提供的連結。bvsd 只會告知有新版本：它絕不下載或安裝套件。檢查失敗不會改變面板中的任何內容。

### 安裝新版本

從 App Center 或以 **手動安裝**，把新套件安裝在舊套件上。安裝會停止 bvsd、取代程式檔案，然後重新啟動它；資料目錄、設定、帳號與每個儲存庫都會保留，面板的工作階段也是。編輯器執行個體會隨 bvsd 停止，因此編輯器中開啟的終端機會結束；帳號再次開啟編輯器時，執行個體會重新啟動。

**從使用連接埠 6050、6051 與 6052 的版本升級。** 較早的版本在 6050（HTTP）、6051（HTTPS）與 6052（透過 SSH 使用 Git）上接聽。新版本第一次啟動時，會把它們分別移到 9950、9951 與 9952，並以通知告知；您自己選擇的連接埠則維持不變。請變更指向舊連接埠的設定：書籤、指向 6050 的 Plesk Proxy，以及 `ssh://git@<NAS>:6052/…` 之類的 git 遠端（`git remote set-url`）。

### 移除、重新安裝與損壞的套件

套件被移除、重新安裝或損壞時，您的工作都會保留。您建立的一切都在資料目錄中：帳號與設定、Git 與 SVN 儲存庫、bucket、專案資料夾、HOME 資料夾、VS Code 的設定與擴充功能、App Builder 應用程式、環境、套件儲存庫以及備份。安裝資料夾（`<磁碟區>/.qpkg/bucketvs`）只有程式和一個指出資料目錄的檔案。

- **移除後再安裝。** 移除套件會刪除安裝資料夾，但保留資料目錄以及指出它的檔案 `/etc/config/bucketvs-data-root`。下一次安裝會讀取該檔案，並在同一個資料目錄上啟動：相同的帳號與密碼、相同的專案與儲存庫，已登入的工作階段也包括在內。
- **在已安裝的套件上安裝。** 相同或更新的版本只會取代程式檔案，不會動到資料目錄。
- **損壞的套件。** 安裝資料夾損壞時（缺少程式、資料夾被手動刪除），請從 App Center 或以 **手動安裝** 重新安裝套件。安裝會以相同方式找到資料目錄。仍從已刪除檔案執行的 bvsd 會在啟動時被停止，並啟動已安裝的版本。
- **什麼會刪除資料。** 只有刪除資料目錄資料夾本身。若要刻意從空白開始，請移除套件、刪除資料目錄資料夾，然後再安裝：第一次啟動會建立新的資料目錄（見[資料目錄的初始位置](#資料目錄的初始位置)）。

磁碟故障是另一回事：資料目錄位於 NAS 本身的磁碟上。請在另一台機器上保存備份（[備份](#備份)）；每次備份都包含您的專案資料夾、HOME 資料夾、App Builder 的設定與套件儲存庫。

## bvs 指令

`bvs` 是套件的命令列工具，位於 `/usr/bin/bucketvs/bin`。它在 bvsd 啟動的每個程序（包括 cron 工作）的 `PATH` 中。
套件執行期間，`/usr/bin/bvs` 會連結到它，因此在 NAS 的 SSH 工作階段中也能直接以名稱呼叫；停止套件會移除這個連結。

透過 CLI socket 與 bvsd 通訊的指令都需要 root，因為該 socket 僅限 root：`user`、`jobs`，以及 bvsd 執行時的 `tc install`、`tc remove` 與 `env create`、`clone`、`export`、`delete`。`env shell`、`env run` 與 `qbuild` 也需要 root：它們會啟動 `bvs-chroot`。以其他使用者執行時，需要 socket 的指令會以「bvsd's CLI socket … is open to root only: run bvs as root」停止。

```text
bvs env list
bvs env create <name> --from <rootfs-id> [--package <name>]...
bvs env shell <name>
bvs env run <name> -- <command> [<arg>...]
bvs env clone <name> <new-name>
bvs env export <name>
bvs env delete <name>
bvs tc list
bvs tc install <id>
bvs tc remove <id>
bvs use <id>
bvs jobs
bvs user list
bvs user password <name>
bvs user totp-reset <name>
bvs guide [--path] [en|pl|de|fr|it|nl|sv|ja|zh-TW]
bvs qbuild [--env <name>] [<qbuild argument>...]
bvs kernel profile [--json]
bvs kernel sources
bvs kernel prepare [--source <id>] [--env <name>] [--option NAME=VALUE]... [--full]
bvs kernel make [--project <folder> | --dir <tree directory>] [--build-only] [<target>...]
bvs kernel check <file.ko | module>
bvs kernel load <module>
bvs kernel unload <module>
```

- bvsd 執行時，安裝與環境工作會透過其 CLI socket 交給 bvsd，因此面板也會顯示它們，而 `bvs` 會追蹤到它們結束。沒有 bvsd 時，它們在 `bvs` 程序本身中執行；在指令前加上 `--local` 可刻意這樣做。
- `bvs jobs` 與 `user` 指令需要 bvsd。
- `bvs guide` 在終端機中以套件的 `less` 開啟本指南，其他情況或加上 `--path` 時則印出其檔案路徑。未指定語言時，會依 `LC_ALL`、`LC_MESSAGES` 與 `LANG` 中第一個有設定的值決定：`de_DE.UTF-8` 開啟德文指南，`zh_TW` 與 `zh_HK` 開啟繁體中文指南，指南未提供的語言（`C`、`zh_CN`）則開啟英文指南。

## 檔案與資料夾

| 路徑 | 內容 |
|---|---|
| `/usr/bin/bucketvs` | 套件的程式（指向其安裝資料夾的連結） |
| `/usr/bin/bucketvs/etc/data-root` | 資料目錄的路徑 |
| `/usr/bin/bucketvs/var/run/bvsd.sock` | 面板的 API socket（供 nginx 使用） |
| `/usr/bin/bucketvs/var/run/bvsd-cli.sock` | `bvs` 指令的 socket，僅限 root |
| `<資料目錄>/etc/` | 設定、帳號、工作階段、權杖、憑證、產生的 `nginx.conf`（密鑰為模式 600） |
| `<資料目錄>/log/` | 記錄 |
| `<資料目錄>/tmp/` | 每個程序的暫存檔案 |
| `<資料目錄>/home/<使用者>/` | 每個帳號的家目錄 |
| `<資料目錄>/jobs/` | 操作、cron 工作及其執行，含記錄 |
| `<資料目錄>/toolchains/` | 下載快取、已安裝的工具鏈與執行環境、匯出檔 |
| `<資料目錄>/envs/` | 環境 |
| `<資料目錄>/projects/` | 工作區，在環境中以 `/projects` 出現 |
| `<資料目錄>/trash/` | 已刪除的項目，保留 7 天 |

各模組會加入自己的資料夾；其章節會說明位置。
