# Bucket & Code Server IDE ユーザーガイド

Bucket & Code Server IDE（QPKG `bucketvs`）は、ソースコードとファイルを QNAP NAS に保管し、その NAS でソフトウェアをビルドします。独自の Web サーバー、ツール、アカウントを備えています。

このガイドでは、NAS 上で動作しているパッケージについて説明します。

## 目次

- [NAS で動くもの](#nas-で動くもの)
- [パッケージのインストール](#パッケージのインストール)
- [最初のサインイン](#最初のサインイン)
- [パネルでの作業](#パネルでの作業)
- [アカウント](#アカウント)
- [データフォルダー](#データフォルダー)
- [ネットワーク](#ネットワーク)
- [リバースプロキシ（Plesk）の背後で使う](#リバースプロキシpleskの背後で使う)
- [モジュールのホスト名](#モジュールのホスト名)
- [インターネットへの公開（Cloudflare Tunnel）](#インターネットへの公開cloudflare-tunnel)
- [モジュール](#モジュール)
- [Git](#git)
- [CI ランナー](#ci-ランナー)
- [SVN と Bucket](#svn-と-bucket)
- [エディターと AI ツール](#エディターと-ai-ツール)
- [ツールチェーンと環境](#ツールチェーンと環境)
- [App Builder](#app-builder)
- [パッケージリポジトリ](#パッケージリポジトリ)
- [カーネル工房](#カーネル工房)
- [cron ジョブと操作](#cron-ジョブと操作)
- [バックアップ](#バックアップ)
- [通知](#通知)
- [ログとサポートバンドル](#ログとサポートバンドル)
- [アップデート](#アップデート)
- [bvs コマンド](#bvs-コマンド)
- [ファイルとフォルダー](#ファイルとフォルダー)

## NAS で動くもの

| 構成要素 | 役割 | 待ち受け先 |
|---|---|---|
| nginx | LAN からの唯一の入口。パネルを配信し、各パスをそのモジュールへ渡します | 9950（HTTP）、9951（HTTPS） |
| bvsd | ほかのすべてのプロセスを起動・監視し、アカウントと設定を保持し、ジョブと cron ジョブを実行し、パネルに応答します | `/usr/bin/bucketvs/var/run/` 内の 2 つの Unix ソケット |
| Git（Forgejo） | Web インターフェース付きの Git ホスティング | `/usr/bin/bucketvs/var/run/forgejo/` 内の Unix ソケットと、SSH 経由の Git 用の 9952 |
| SVN と Bucket（httpd） | Subversion リポジトリと、履歴付きの WebDAV フォルダー | 127.0.0.1:9961。ほかのプログラムが使っている場合は次の空きポート |
| エディター（code-server） | ブラウザーで動く VS Code。アカウントごとに 1 インスタンス | `/usr/bin/bucketvs/var/run/ide/` 内のアカウントごとの Unix ソケット |
| CI | Forgejo Actions のランナー | 独自のポートはありません。モジュールが有効な間は nginx がランナー用に 127.0.0.1:9960 で待ち受けます。ほかのプログラムが 9960 を使っている場合は次の空きポートを使います |

コア（nginx、bvsd、パネル、cron ジョブ、ツールチェーン）は常に動作しています。Git、CI、SVN と Bucket、エディターは、管理者がオン・オフを切り替えるモジュールです。NAS のどのアドレスでも、各モジュールはそれぞれのパス（`/git/`、`/svn/`、`/bucket/`、`/ide/`）で応答します。パネルは `/`、このガイドは `/guide/` にあります。

パッケージの動作中、プログラムは `/usr/bin/bucketvs/bin` にあります。パッケージが保持するもの（アカウント、設定、リポジトリ、環境、ログ）はすべて、データボリューム上の
1 つのフォルダー、つまり[データフォルダー](#データフォルダー)に置かれます。

## パッケージのインストール

パッケージには QTS 6.0 または QuTS hero 6.0 以降と、x86_64 または ARM 64 ビットの NAS が必要です。パッケージはアーキテクチャーごとに 1 つあります（`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   # 次回の起動を拒否します（NAS の起動時も）
/etc/init.d/bucketvs.sh enable    # NAS の起動時に再びパッケージを起動します
```

どちらのコマンドもスイッチ（`/etc/config/qpkg.conf` の `Enable`）だけを書き込み、設定した内容を表示して、同じ行をシステムログに記録します。実行中のパッケージは `disable` の後も動き続け、`enable` は何も起動しません。起動と停止は `start` と `stop` で行います。パッケージが無効の間、`start` は拒否されます。`status` は bvsd が動いているかを示します。

## 最初のサインイン

パネルを `https://<NAS のアドレス>:9951/` または `http://<NAS のアドレス>:9950/` で開きます。HTTPS のアドレスは最初は自己署名証明書を使うので、デバイスがその証明書を信頼するまでブラウザーは警告を表示します。フィンガープリントの確認方法は[証明書](#証明書)の節を参照してください。

新しいインストールにはアカウントがありません。最初の管理者にパスワードが設定されるまで、パネルはアカウント `administrator` 用の画面「管理者パスワードを設定」だけを表示します。パスワードを 2 回入力してください。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 の LAN アドレスごとに次のような情報エントリが記録されます。

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

- プロキシ経由のリクエスト（`X-Forwarded-For`、`X-Forwarded-Host`、`X-Forwarded-Proto`、`X-Real-IP`、`Forwarded` のいずれかを含むもの）は、LAN 上のプロキシからでも拒否されます。パスワードを設定するには、ローカルネットワーク内のコンピューターから直接パネルを開いてください。

NAS 上の SSH セッションで `/usr/bin/bucketvs/bin/bvs user password administrator` を実行すると、まだアカウントがない場合はアカウントが作成され、ランダムなパスワードが表示されます。その後、最初のサインインでパネルが自分のパスワードの設定を求めます（[管理者が締め出されたとき](#管理者が締め出されたとき)）。

最初のサインインの後、概要ページにはコアだけが動いていると表示されます。各モジュールのタイルには、そのモジュールでできることと、**Git をオンにする** のようなオンにするボタンがあります。必要なものをオンにしてください（[モジュール](#モジュール)）。

## パネルでの作業

パネルはコードエディターのように動作します。各ページはタブで開き、コマンドパレットから名前でページやアクションを呼び出せます。

- **アクティビティバー**（左側のアイコン）。各アイコンはそのページを開き、サイドバーにツリーを表示します。モジュール、リポジトリとバケット、アプリ、パッケージ、環境、ジョブ、バックアップです。アイコンの点は、問題のあるモジュール、失敗したビルド、失敗したバックアップを示します。前面のページのアイコンをクリックするとサイドバーが隠れます。Ctrl+B でも同じです。
- **タブ**。シングルクリックで開いたページはプレビューで、斜体で表示されます。次に開いたページがその場所に置き換わります。残しておくには、ページを編集するか、タブをダブルクリックします。タブは再読み込み後も、ブラウザーごと・アカウントごとに復元されます。幅 1280 ピクセル以上のウィンドウでは、**タブを反対側へ移動**（`Ctrl+\`）で 2 つのページを並べて表示できます。
- **オブジェクトとそのメニュー**。サイドバーの行、表の行、カードを 1 回クリックすると、そのオブジェクトが開きます。そのページが前面に出て、オブジェクトが緑の枠で示されるか、詳細が開きます。アドレスがオブジェクトを指すので、戻る・進む・コピーしたリンクで同じ場所に戻れます。ダブルクリックするとタブが残ります。右クリック、Shift+F10、またはタッチスクリーンでの長押しでオブジェクトのメニューが開きます。開き方（このタブで、残すタブで、反対側で、ブラウザーの新しいタブで）、そのページがオブジェクトに対してできる操作、名前とアドレスのコピーが並びます。タブのメニューでは、そのタブ、ほかのタブ、右側のタブ、すべてのタブを閉じられます。パネルの外へ出るのは ↗ の付いた項目だけです。**管理** アイコンはシステムページを開きます。
- **コマンドパレット**。Ctrl+Shift+P（または Ctrl+K、F1）で、自分のロールが実行できるコマンドが一覧表示されます。ページを開く、アプリをビルドまたは公開する、バックアップを実行する、モジュールを再起動する、環境でシェルを開く、などです。Ctrl+P では、名前の数文字でページ、アプリ、リポジトリ、環境、パッケージへ移動できます。
- **下部パネル**（Ctrl+J）。**ジョブ** には実行中と最近のジョブが進行状況とログ付きで表示されます。**問題** にはモジュールが報告する内容が並びます。失敗したビルド、無効になったバックアップ先、問題のあるモジュール、期限切れが近い証明書、前面のアプリに対する ELF インスペクターの指摘です。**出力** は最後に開始したジョブを追います。
- **移動とサイズ変更**。境界をドラッグすると、その境界に接するものの大きさが変わります。サイドバー、下部パネル、左右に並んだ 2 つのページ、またはページ内で左右に並んだ 2 つの部分（ウィザードとその概要、プロジェクトのファイル一覧とファイルなど）です。境界をダブルクリックすると元に戻ります。ダイアログはタイトルをドラッグすると移動し、端か右下の角をドラッグするとサイズが変わります。タイトルをダブルクリックすると中央に戻ります。下部パネルをページの横に置くには、パネルの見出しをエディターの右端へドラッグするか、× の隣のボタンをクリックします。下端へドラッグするか、もう一度ボタンをクリックすると戻ります。タブはタブ列の別の位置や反対側へドラッグできます。大きさと位置はこのブラウザーでアカウントごとに保存され、**レイアウトをリセット**（コマンドパレット、またはアクティビティバー下部のアカウントのボタンの中）ですべて元に戻ります。キーボードでは、フォーカスのある境界を矢印キーで動かし（Shift で大きく、Home で元に戻る）、Ctrl+Shift+左右でフォーカス中のタブを動かします。ダイアログには上部のバーと右下のつまみがあり、矢印キーで移動とサイズ変更ができます。スマートフォンでは、ダイアログと下部パネルはそのままです。
- **ステータスバー**。モジュールの状態、実行中のジョブ、問題、データボリュームの空き容量、証明書、サインイン中のアカウント、言語（エディターは次回の起動時にこの言語になります。スウェーデン語とオランダ語では英語のままです。Git もこの言語になりますが、Git で別の言語を選んだ場合はそちらが優先されます）、テーマ（ダーク、ライト、システムに合わせる、ディープブルー、グラファイト、ペーパー。テーマはアカウントに保存され、エディターと Git も同じテーマになります。エディターや Git で別のテーマを選んだ場合はそちらが優先されます）、バージョンです。各項目をクリックすると、詳しく確認できる場所が開きます。
- **キーボード**。Ctrl+/ または ? でショートカットの一覧が表示されます。Ctrl+W と Ctrl+Tab はブラウザーのタブが使うので、パネルでは Alt+W でタブを閉じ、Alt+PageDown と Alt+PageUp でタブを切り替えます。
- **スマートフォンでは**、最初のいくつかのアクティビティが下部に並び、**その他** で残りを含むサイドバーが開き、開いているページは上部に一覧表示されます。

## アカウント

パネルには独自のアカウントがあります。QTS のアカウントではサインインできません。

### ロール

| ロール | できること |
|---|---|
| viewer | パネルの閲覧。バックアップ、ストレージ、ユーザー、ログの各ページは管理者専用です。Forgejo ではリポジトリの作成・フォーク・インポートも、組織の作成も、プッシュもできません。権限のある SVN リポジトリとバケットを読み取れますが、どれにも書き込めません |
| member | viewer が読めるものを読み、さらに書き込みます。書き込み権限を与えられた Git リポジトリにプッシュし、読み書き権限のある SVN リポジトリとバケットに書き込み、そこでファイルの古いバージョンを復元します。Forgejo ではリポジトリの作成・フォーク・インポートも組織の作成もできず、developer より下のロールのアカウントが所有または管理するリポジトリにはプッシュできず、CI モジュールが有効な間は Actions がオンのリポジトリにもプッシュできません。エディター、cron ジョブ、ビルドは使えません |
| developer | さらにエディターを開き、インストール済みツールチェーンの `bvs use` 行を読み、自分の cron ジョブを作成・変更・実行し、アプリを作成・ビルドし、リポジトリにパッケージを公開し（掲載するのは管理者）、カーネルモジュールをチェックし、CI モジュールが有効な間は NAS 上でワークフローが実行される Git リポジトリを作成できます |
| admin | すべて。アカウント、設定、ストレージ、ネットワーク、モジュール、ツールチェーン、環境、すべての cron ジョブ、アプリの有効化、カーネルモジュールのロード、リポジトリへのパッケージの掲載 |

developer ロールは NAS 上の root と同じです。cron コマンドは root として実行され、エディターのターミナルも同様です。CI モジュールが有効な間は、プッシュできるリポジトリのワークフローも root で実行されます。NAS の admin パスワードを渡してもよい相手にだけ付与してください。リポジトリに書き込むだけの人には member ロールで十分です。

**アカウント** では、各自が自分のロールとそのロールでできることを確認でき、このセクションへのリンクもあります。ロールを超えるページには必要なロールが表示され、管理者だけが変更できるものは、表示される場所でそう示されます。ネットワーク、Git、CI の設定は、それ以外のロールには値として表示され、空の値は「未設定」と表示されます。

ワークフローは NAS 上で root として実行されるため、bvsd は member または viewer が所有するか書き込めるすべての Git リポジトリで Actions をオフにしておきます。そのようなアカウントにリポジトリへの書き込み権限を与えると、1 分以内にそのリポジトリの Actions がオフになり、Git ページには管理者向けにそのリポジトリが一覧表示されます。そこで Actions を使うには、まず Forgejo でその書き込み権限を外すか、そのようなアカウントが所有するリポジトリを developer のアカウントに移してください。

### アカウントの作成と変更

管理者はユーザーページでアカウントを管理します。

- 新しいアカウントは viewer ロールで始まります。developer と admin は意図して選びます。developer ロールは NAS の root です（上記）。
- 新しいアカウントは cron ジョブなしで始まります。同じ名前のアカウントが以前ジョブを持っていた場合も同じです。
- 名前は 2～32 文字です。先頭は小文字の英字、その後は小文字の英字、数字、`.`、`_`、`-` を使えます。`.`、`_`、`-` を 2 つ続けたもの、末尾に置いたものは拒否されます。Forgejo が予約している名前（`admin`、`api`、`user` など）、`bucketvs`（パッケージのシステムユーザー）、`cli` と `system`（どちらも bvsd 自身が使用）も拒否されます。
- 「初回サインイン時に自分のパスワードを決める必要があります」をオンにすると、そのアカウントはほかの操作の前に自分のパスワードを設定する必要があります。
- アカウントを無効にすると、そのセッションは即座に終了し、API トークンも使えなくなります。データは残ります。[cron ジョブ](#スケジュールジョブ)も残りますが、無効の間は実行されず、実行中のものは停止されます。
- アカウントに member または viewer ロールを与えた場合も、cron ジョブは同じ扱いになります。ジョブは残りますが、ロールが developer より下の間は実行されず、実行中のものは停止されます。
- viewer は、Forgejo で書き込み権限を与えられた場所でも、Git にプッシュすることはありません。プッシュするアカウントには member ロールを与えます。
- アカウントを削除すると、セッションと API トークンごと削除されます。ホームフォルダーはデータフォルダーの `trash/home/<名前>-<時刻>` に移され、7 日後にジョブ「ゴミ箱を空にする」が削除します。`tmp/tmux-<名前>` フォルダーは削除されます。cron ジョブは実行履歴とログごと削除されます。
- 最後の有効な管理者は、降格、無効化、削除ができません。
- 「新しいパスワードを設定」は、そのアカウントのセッションを終了し、API トークンを失効させます。

### パスワードとセッション

- パスワードは 10 文字以上、72 バイト以下です。
- 自分のパスワードを変更すると（「アカウント」で）、ほかのセッションが終了し、API トークンが失効します。変更に使ったセッションはサインインしたままです。
- セッションは最後のリクエストから 12 時間後に終了し、いずれにしてもサインインから 7 日後には終了します。1 つのアカウントのセッションは最大 32 個で、さらにサインインすると最も長く使われていないセッションが終了します。
- セッション Cookie は `HttpOnly` かつ `SameSite=Lax` です。ブラウザーが HTTPS で NAS に直接、または[信頼するプロキシ](#信頼するプロキシ)経由で接続した場合は `Secure` が付きます。

### 誤ったパスワード

- 1 つのアドレスから 15 分以内に 5 回サインインに失敗すると、そのアドレスは正しいパスワードでも 15 分間ロックされます。IPv6 クライアントは /64 ネットワーク単位で数えます。
- 1 つのアドレスから 1 つのアカウントへの失敗が 5 回になると、そのアドレスからそのアカウントへのサインインが 15 分間ロックされます。
- 多数のアドレスから 1 つのアカウントへの失敗は、次のサインインを遅くします。1 回の失敗で 125 ms、その後倍増し、最大 2 秒です。ローカルネットワークからの失敗でロックされることはありません。
- インターネット（ローカルネットワーク外のすべてのアドレス）から、最後に成功したサインイン以降に 1 つのアカウントへのサインイン失敗が 20 回になると、どのアドレスからの失敗であっても、そのアカウントはインターネットからロックされ、正しいパスワードも拒否されます。ローカルネットワークからは引き続きサインインできます。管理者は「ユーザー」ページ（**ロック解除**）または `bvs user unlock <名前>` でロックを解除するか、新しいパスワードを設定します。回数は「ユーザー」ページの **インターネットからのサインイン** で設定し、0 でこのロックをオフにします。
- これらのカウントはメモリー上にあるので、bvsd を再起動するとリセットされます。ロックは解除されるまで残ります。

プロキシの背後では、プロキシが報告するアドレスが数えられますが、それはプロキシが[信頼するプロキシ](#信頼するプロキシ)である場合だけです。そうでない場合、すべてのクライアントがプロキシ自身のアドレスとして数えられ、誰かが 5 回パスワードを間違えると全員が 15 分間締め出されます。

### 2 要素認証

各アカウントは「アカウント」で自分で有効にします。認証アプリで QR コードを読み取り（またはキーを入力し）、アプリに表示される 6 桁のコードを入力します。以後、サインインではパスワードのあとにコードが求められます。

- コードは RFC 6238 に従います。6 桁で、30 秒ごとに新しいコードになります。30 秒早いコードや遅いコードも受け付けます。同じコードが 2 回受け付けられることはありません。
- 生成したばかりのコードが通らない場合は、スマートフォンの時計がずれている可能性があります。時刻の自動設定を有効にしてください。
- 無効にするにはアカウントのパスワードが必要です。
- スマートフォンを紛失した場合は、管理者がユーザーページで無効にします。その後はパスワードだけでサインインできます。

### API トークン

スクリプトや CI は、パスワードの代わりに API トークンを使います。「アカウント」の「新しいトークン」で作成します。

- `bvs_` に続く 40 桁の 16 進数の形式で、表示されるのは一度だけです。bvsd はそのハッシュだけを保存します。
- スクリプトは `Authorization: Bearer bvs_...` として送ります。
- スコープは `read`、`write`、`admin` で、それぞれロール viewer、developer、admin に対応します。トークンは最も高いスコープで動作しますが、所有者の現在のロールを超えることはありません。アカウントを降格すると、そのトークンも降格します。member に対応するスコープはないため、member は `read` トークンを作成します。
- 新しいパスワードを設定すると、所有者のトークンはすべて失効します。無効になった所有者のトークンは使えなくなります。
- トークンでは、パスワードや 2 要素認証の変更、トークンの作成や削除はできません。これらにはサインインしたセッションが必要です。そのため、漏えいしたトークンで所有者が締め出されることはありません。

### 管理者が締め出されたとき

NAS の管理者は、NAS 上の SSH セッションから任意のアカウントをリセットできます。`bvs` コマンドは、root だけが開けるソケット（`/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 <名前>` は、そのアカウントの 2 要素認証を無効にします。
- `bvs user unlock <名前>` は、インターネットからの誤ったパスワードが多すぎたことによるアカウントのロックを解除します。

このソケット上のリクエストはすべて組み込み管理者 `cli` として動作し、データフォルダーの `log/audit.log` に記録されます。これらのコマンドには bvsd が動作している必要があります。

## データフォルダー

データフォルダーは、パッケージが保持するものをすべて収めるフォルダーです。そのパスは `/usr/bin/bucketvs/etc/data-root` の 1 行です。ストレージページに、パスと、それが置かれたボリュームとその空き容量が表示されます。

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` という名前のファイル付きで元の場所に残ります。何もそれを使いません。ストレージページの「古いコピーを削除」で削除でき、「このコピーを削除」で失敗した移動の残りのコピーを削除できます。どちらの削除も 1 つのファイルシステム内で行われ、リンクをたどることはありません。マウントポイントや共有フォルダー自体のフォルダーは、中身を空にしたうえで残されます。

## ネットワーク

ネットワークページには、ポート、平文の HTTP の扱い、モジュールのホスト名、信頼するプロキシ、公開 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 の LAN アドレスです。Cloudflare Tunnel で公開した名前には、LAN アドレスと SSH 経由の Git のポートを使います。DNS のみの名前か、ルーターが SSH ポートを転送している場合はルーターのアドレスを設定します。 |

ポートは 1024～65535 で、互いに異なる必要があります。127.0.0.1 上のモジュール用ポート（9960 と 9961）は拒否されます。ほかのプログラムが 9960 や 9961 を使っているために httpd や CI ランナーのサーバーが代わりに使っているポートも拒否されます（メッセージにそのポートが示されます）。新しいポートを使えなかったために保存後も nginx が古いポートのままの場合は、通知で理由が示されます。NAS に IPv6 がある場合、nginx は IPv6 でも待ち受けます。

### 平文の HTTP

- 「そのまま提供」（既定）: パネルとモジュールは平文の HTTP でも応答します。LAN 上のプロキシがポート 9950 に平文の HTTP で接続する場合はこのままにしてください。
- 「HTTPS にリダイレクト」: 平文の HTTP のリクエストには、HTTPS ポートへのリダイレクト（307）が返ります。クライアントが HTTPS を使ったと伝える信頼するプロキシからのリクエストは平文の HTTP で応答するので、そのようなプロキシがループすることはありません。

サインインページは、平文の HTTP で開かれたときに警告を表示します。この場合、パスワードは読み取れる状態でネットワークを流れます。

### 証明書

最初の起動時に、bvsd は次の名前に対する自己署名証明書（ECDSA P-256）を作成します。

- NAS のホスト名、モジュールのホスト名、`localhost`
- NAS の LAN アドレスと `127.0.0.1`

有効期間は 825 日で、手動で信頼した証明書であっても Apple のデバイスが受け入れる上限です。bvsd は起動時と 1 日 1 回証明書を確認し、残りが 30 日以下になったとき、名前の組み合わせが変わったとき、鍵が一致しないときに新しい証明書を作成します。新しい証明書を作成すると[通知](#通知)が送られます。ネットワークページには、名前、有効期間、SHA-256 フィンガープリントが表示されます。デバイスで信頼する前にフィンガープリントを照合してください。

「独自の証明書を追加」では、PEM 形式の証明書チェーンとその秘密鍵を登録します。

- 先頭にサーバー証明書、続いて中間証明書
- パスフレーズのない鍵（nginx は無人で起動するため）
- 鍵は最初の証明書に対応し、各証明書は次の証明書で署名され、期限切れのものがなく、最初の証明書がサブジェクト代替名（SAN）でホストを示していること

証明書と鍵はデータフォルダーの `etc/tls/` にモード 600 で保存されます。アップロードした証明書が期限切れになったり、これらの確認を通らなくなったりすると、nginx は代わりに自己署名証明書を使い、通知で理由を伝えます。アップロードした証明書の残りが 30 日以下になった場合も通知が送られます。「新しい自己署名証明書」は新しい自己署名のペアを作成し、そちらに切り替えます。

プロキシ経由で接続するクライアントに見えるのは、この証明書ではなくプロキシの証明書です。

ネットワークページの **証明書をダウンロード** は、パネルが提供している証明書を鍵なしで `<名前>.crt` として保存します。デバイスの信頼された証明書に追加すると、そのデバイスのブラウザーがパネルを信頼します。エディターの拡張機能のビューにはこれが必要です（[拡張機能](#拡張機能)）。新しい自己署名証明書は、あらためて信頼する必要があります。

### 信頼するプロキシ

NAS の前に置いたプロキシのアドレス（例: `192.168.1.5`）など、IP アドレスまたは CIDR 範囲のリストです。TCP 接続がこれらのいずれかから来たリクエストに限り、nginx は次の値を採用します。

- `X-Forwarded-For` からクライアントのアドレス
- `X-Forwarded-Proto` からクライアントが使ったスキーム（`http` と `https` だけが有効）

それ以外のアドレスからのリクエストはクライアントから直接来たものとして扱われ、nginx はその `X-Forwarded-*` ヘッダーを自分の値で置き換えます。範囲を指定すると、その中のすべてのホストが信頼され、どのホストもクライアントのアドレスとスキームを設定できるようになります。プロキシ自身のアドレスを指定してください。すべてのアドレスを含む範囲（`0.0.0.0/0`、`::/0`）は拒否されます。

### 公開 URL

`https://git.example.com` のように、パスを含まないアドレスだけを指定します。どのリンクがこの URL で始まるかは、各モジュールの節に記載しています。

## リバースプロキシ（Plesk）の背後で使う

一般的な構成では、公開証明書を持つプロキシで TLS を終端し、プロキシから LAN 上のポート 9950 へ平文の HTTP で接続します。以下の例では、公開名に `bvs.example.com`、NAS に `192.168.1.20`、Plesk サーバーに `192.168.1.5` を使います。実際の値に置き換えてください。

```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、プロキシを通らない"| nas
```

### NAS 側

1. ネットワークページで、Plesk サーバーのアドレス（NAS から見えるアドレス）を信頼するプロキシに追加します。
2. 平文の HTTP は「そのまま提供」のままで構いません。Plesk サーバーを信頼するプロキシにすれば、「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;

    # 大きなプッシュとアップロード: サイズ制限なし、ストリーミング、1 時間の無通信を許容
    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 サーバーとして数えられ、1 人が 5 回パスワードを間違えると全員が 15 分間締め出されます |
| `X-Forwarded-Proto $scheme` | セッション Cookie に `Secure` が付かず、「HTTPS にリダイレクト」モードではすべてのリクエストが公開名のポート 9951 に送られます |
| `Upgrade`、`Connection` | エディターのページは開きますが、エディターとそのターミナルが接続されません |
| `client_max_body_size 0`、`proxy_request_buffering off`、タイムアウト | 大きなプッシュやアップロードにプロキシ自身のサイズ制限とタイムアウトが適用されます |

`X-Forwarded-For` と `X-Forwarded-Proto` が有効なのは、Plesk サーバーが信頼するプロキシに含まれている場合だけです。含まれていない場合、NAS 上の nginx はこれらを無視します。

### SSH 経由の Git

HTTP プロキシは SSH を中継しません。SSH 経由の Git は、NAS のポート 9952 に直接、またはルーターの TCP ポート転送を経由して接続します。ネットワークページの「クローン URL の SSH ポート」で、クローンアドレスに表示するポートを設定し、公開名がプロキシにしか届かない場合は「クローン URL の SSH ホスト」で名前またはアドレスを設定します。

### 設定の確認

- パネルが `https://bvs.example.com/` で開き、サインインと設定の保存ができること。
- ブラウザーの開発者ツールで、セッション Cookie `bvs_session` に `Secure` フラグが付いていること。
- ログページの `nginx-access` で、各行の最初のフィールドが Plesk サーバーではなくクライアント自身のアドレスであること。

## モジュールのホスト名

モジュールには専用のホスト名を設定できます（ネットワークページのホスト名）。その場合、nginx はそのホスト名のルートでそのモジュールだけを配信します。ホスト名は NAS、または NAS の前に置いたプロキシを指している必要があります。プロキシでは、ホスト名ごとに上と同じディレクティブを持つサイトが必要です。

| モジュール | ホスト名のルート | そのほかにあるもの |
|---|---|---|
| Git | Forgejo | ほかのすべての名前の `/git/` は、同じパス、クエリ、ポートで Git のホスト名へ 302 を返します |
| エディター | code-server | パネルのサインインページと `/api/`。そのホスト名でサインインできます |
| SVN と Bucket | httpd | リポジトリは `/svn/<名前>`、バケットは `/bucket/<名前>` のままです |

オフのモジュールは、そのパスでもホスト名でも、その旨を記した文とともに `503` を返します。

### すべてのホスト名で 1 回のサインイン

セッション Cookie は、設定されたホスト名に属します。ただし、設定 `network.cookie_domain` がすべてのモジュールホスト名の親ドメインを指定している場合は別です。`example.com` を設定すると、`bvs.example.com` でのサインインが `git.example.com` と `ide.example.com` にも有効になります。bvsd は、パブリックサフィックス（`com`、`com.pl`、`co.uk`、`myqnapcloud.com`）、すべてのモジュールホスト名の親ではないドメイン、モジュールホスト名が 1 つも設定されていない間のすべてのドメインを拒否します。そのドメイン配下のすべてのサイトが Cookie を受け取るので、できるだけ狭い共通の親ドメインを設定してください。

この設定はネットワークページには表示されません。NAS の管理者が、パッケージの `curl` で CLI ソケット経由で設定します。

```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 にはサインインが必要で、アカウントなしで開けるのは公開したもの(公開リポジトリ、その Web ビュー、パッケージリポジトリ)だけです。

名前には 2 種類あり、それぞれ最大 1 つです。

| 種類 | 例 | 提供するもの |
|---|---|---|
| メインのホスト名 | `code.example.com` | パネルとその API、`/git/` の Forgejo、`/svn-web/` と `/bucket-web/` の Web 表示、`/repo/` のパッケージリポジトリ（読み取りのみ）、ガイド。`/svn/` と `/bucket/` はそこでは 404 を返し、`/ide/` もエディターを公開するまで 404 です（下記） |
| パッケージ用のホスト名 | `qpkg.example.com` | ほかの NAS の App Center 向けに `/repo/` の GET と HEAD のみ。それ以外はすべて 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 ですべてを確認し、確認に失敗した場合は Cloudflare 側で何も変更しません。メッセージには直すべき点が示されます。

- トークンに足りない権限（その名前）
- トークンから見えないゾーンにある名前
- その名前にすでにある DNS レコード（種類と指す先）。Cloudflare で削除するか、別の名前を選んでください
- **公開閲覧**（Git ページ）がオフのときのメインのホスト名。訪問者にはアカウントがないためです
- 2 つの Cloudflare アカウントのゾーンにまたがる名前

その後、トンネルを作成し（名前を指定しなければ `bucketvs-<NAS 名>`。Cloudflare にその名前のトンネルが残っていれば引き継ぎます）、プロキシされた CNAME レコードで各名前をトンネルに向け、キャッシュルールを追加して cloudflared を起動します。同じ値で **再公開** しても何も変わりません。公開 URL（ネットワーク）をメインのホスト名の `https://` アドレスに設定すると、Forgejo のリンクと HTTPS のクローンアドレスにその名前が使われます。両者が異なる間は、ページがそのことを知らせます。

SSH 経由の Git はトンネルを通りません。Cloudflare が公開した名前に運ぶのは HTTP だけです。そのため SSH のクローンアドレスは、パネルでも Forgejo 自身のクローン欄でも、NAS の LAN アドレスとその SSH 経由の Git のポート（9952）になり、Git ページと概要には「SSH でのクローンはローカルネットワークで使えます。インターネットからは HTTPS でクローンしてください。」と表示されます。インターネットから SSH を使うには、ルーターで SSH ポートを転送し、**クローン URL の SSH ホスト**（ネットワーク）をルーターの公開アドレスか、それを指す DNS のみの名前に設定します。ルーターが別のポートを使う場合は **クローン URL の SSH ポート** も設定します。

メインのホスト名の `/ide/` は、管理者が **公開** ページで **エディターをメインのホスト名に公開** をオンにするまで 404 を返します。エディターのターミナルは NAS の root シェルで、インターネットからそこに開発者または管理者としてサインインした人は誰でも使えるため、先にそれらのアカウントで 2 要素認証を有効にしてください。スイッチがオンのとき、エディターは LAN と同じサインインを通り、ポートプロキシ（`/ide/proxy/<port>/`）はそこではオフのままで、Cloudflare はそのページをキャッシュしません。オンの間、概要には **インターネット上のエディター** と表示されます。

CI モジュールがオンの間、この Forgejo の任意のリポジトリへプッシュされたワークフローは、インターネットからでも LAN
からでも、NAS 自身のランナーによって NAS 上で root として実行されます。エディターのターミナルと同じ権限です。**公開** ページの
**インターネットから届く範囲** にもそう書かれています。プッシュ権限は、NAS の管理者パスワードを渡してもよい人にだけ与えてください。

### キャッシュ

**キャッシュのプリセット** がオンのとき、Cloudflare はパッケージ（`*.qpkg`）を 1 年、アイコンを 1 日、`repo.xml` を 5 分、パネルのファイルを 1 年キャッシュします。API、Forgejo のページ、サインインページ、Web 表示は決してキャッシュしません。パッケージリポジトリに公開すると、`repo.xml`（と、変更されたアイコンや削除されたバージョンのファイル）が Cloudflare のキャッシュからすぐに消去されます。パッケージは自分のルールをゾーンのキャッシュルールの最後に追加し、ほかのルールは変更しません。

### インターネットからのサインイン

最初のパスワードは、公開した名前からは決して設定できません。初期設定はローカルのクライアントだけを受け付けます。誤ったパスワードは、LAN と同じようにインターネットのクライアントごとに数えられます。git で Forgejo に送ったパスワードも数えられます。Forgejo のアクセストークンはこれまでどおり使えます。Cloudflare が受け付けるリクエストは 100 MB までなので、それより大きいプッシュやアップロードは LAN のアドレスを使ってください。

### 停止と削除

**停止** は cloudflared を終了します。名前には Cloudflare 自身のエラーページが表示され、Cloudflare 側は何も変わりません。**Cloudflare から削除** は、パッケージが作成した DNS レコード、キャッシュルール、トンネルを削除し（引き継いだトンネルは残ります）、トークンを破棄します。名前を公開している間は、概要ページにトンネルの状態を示す **インターネット** の表示が出ます。

## モジュール

概要ページには、各モジュールがプロセス、メモリー、CPU とともに表示され、オン・オフを切り替えられます（管理者のみ）。オフのモジュールには、そのモジュールでできることが表示されます。管理者は、タイル、モジュール自身のページ、エディターモジュールならエディタータブにあるボタン（**Git をオンにする**、**エディター をオンにする** など）でオンにします。

- モジュールをオンにするとジョブが実行されます。モジュールが準備を行い、スイッチの状態が保存され、プロセスが起動します。ジョブはプロセスの起動を最大 30 秒待ちます。
- CI には Git が必要です。CI がオンの間は Git をオフにできません。
  Git がオフの間、CI のボタンは **Git と CI をオンにする** になり、先に Git をオンにします。
- オフのモジュールはプロセスを実行せず、メモリーも使いません。
- コアは常に動作しています。

| 状態 | 意味 |
|---|---|
| 実行中 | モジュールのすべてのプロセスが動作しています |
| 起動中 | 1 秒以内に起動したプロセスがあります |
| 一部異常 | 再起動待ちのプロセスがある、モジュールのファイルを書き込めなかった、またはヘルスチェックに失敗しています |
| 再起動を繰り返しています | あるプロセスが 10 分以内に 5 回終了しました。bvsd は 15 分ごとに再起動し、通知が 1 回送られています |
| 停止 | モジュールのプロセスが動作していません（データの移動で停止したか、起動に失敗しました） |
| オフ | モジュールはオフです |

終了したプロセスは 1 秒後に再起動され、その後 2 秒、4 秒と延び、最大 60 秒になります。10 分間動作すると、待ち時間は 1 秒に戻ります。再起動を繰り返しているプロセスは、モジュールをオフにしてからオンにするとすぐに起動します。

## Git

Git モジュールは Forgejo を実行します。Forgejo は、イシュー、プルリクエスト、リリース、Wiki、LFS、ミラー、Webhook を備えた git リポジトリの Web インターフェースです。管理者は、概要ページまたは Git ページの **Git をオンにする** でオンにします。ジョブが `<データ>/forgejo` に Forgejo のフォルダーとデータベースを準備し、パネルの全アカウントを Forgejo に追加して起動します。以後、Forgejo はパネルのアドレスの `/git/`、または Git のホスト名を設定した場合はその `/` で開きます（[モジュールのホスト名](#モジュールのホスト名)を参照）。

Git ページの **新しいリポジトリ**（リポジトリがまだない間は概要ページの Git タイルにも表示）は、Forgejo のリポジトリ作成フォームを開きます。パネルは、このボタンと **リポジトリの移行またはミラー** を developer ロール以上にだけ表示します。Forgejo では viewer と member はリポジトリを作成できないためです。

### リポジトリとアカウント

- **サインインは 1 回。** Forgejo には独自のサインインがありません。`/git/user/login` はパネルのサインイン（アカウントに設定されていれば 認証コードも）に移り、終わると Forgejo に戻ります。
- **アカウントはパネルから。** パネルの各アカウントには、同じ名前と同じパスワードの Forgejo アカウントがあります。ロール `admin` は Forgejo のサイト管理者です。無効なアカウントや、先にパスワードを変更する必要があるアカウントは、Forgejo にもサインインできません。パネルでの変更は数秒以内に Forgejo に反映されます。ユーザーページと Git ページには各アカウントの Forgejo での状態が表示され、Git ページの **今すぐすべてのアカウントを同期** で同期をやり直せます。
- **アカウントを削除する** と、パネルはその Forgejo アカウントを使用停止にします。サインインできなくなり、トークンと SSH 鍵は削除されますが、リポジトリは残ります。管理者が Forgejo のサイト管理（**ユーザーアカウント**）で古い Forgejo アカウントの名前を変更するか削除するまで、その名前を新しいパネルアカウントに使うことはできません。
- **Forgejo で直接作成したアカウント** をパネルが変更することはありません。同じ名前のパネルアカウントは理由を表示し、どちらかの名前が変わるまで待ちます。
- **2 要素認証と git。** 2 要素認証が有効なアカウントでは、Forgejo はパスワードを受け付けません。HTTP または HTTPS 経由の git では、パスワードの代わりに Forgejo のアクセストークン（Forgejo の **設定**、**アプリケーション**）が必要です。SSH 鍵はこれまでどおり使えます。
- **リポジトリ** の作成、インポート、ミラーは Forgejo のページで行います。NAS は通常自分のネットワークからミラーするので、LAN 上のアドレスからのインポートは既定で許可されています（設定: `git.allow_local_networks`）。

### HTTPS と SSH でのクローン

Git ページの **リポジトリをクローン** は、所有者とリポジトリに対する両方のコマンドを表示します。

| 方法 | アドレス | サインイン |
|---|---|---|
| HTTPS（または HTTP） | `https://<NAS のアドレス>:9951/git/<所有者>/<リポジトリ>.git` | パネルのユーザー名とパスワード、またはアクセストークン |
| SSH | `ssh://git@<NAS のアドレス>:9952/<所有者>/<リポジトリ>.git` | Forgejo の **設定**、**SSH / GPG キー** で追加した SSH 鍵 |

- 公開 URL を設定している場合（プロキシの背後、[公開 URL](#公開-url)）、HTTPS のアドレスにはそれが使われます。Git のホスト名がある場合、パスはそのホスト名の `/<所有者>/<リポジトリ>.git` です。
- SSH ポートは `network.ssh_port`（9952）、ルーターが別のポートを転送している場合は `network.public_ssh_port` です。プロキシは SSH を中継しないので、ルーターでポートを転送してください（[SSH 経由の Git](#ssh-経由の-git)を参照）。
- Cloudflare Tunnel で公開した名前は SSH を通さないため、SSH のクローンアドレスは NAS の LAN アドレスになります。ただし `network.public_ssh_host`（ネットワーク、「クローン URL の SSH ホスト」）に DNS のみの名前かルーターのアドレスを設定した場合はそれを使います。
- 大きなプッシュも切り詰められません。nginx は `/git/` にボディサイズの上限を設けていません。

### アカウントのない人にリポジトリを見せる

Git ページの **公開閲覧** をオンにすると、Forgejo にアクセスできる人は誰でも、アカウントなしで公開リポジトリを閲覧できます。Forgejo では、各リポジトリが公開か非公開かを設定します（リポジトリの **設定**、**公開範囲**）。スイッチがオンのとき、アカウントのない訪問者は次のことができます。

- 公開リポジトリとそのフォルダー、ファイルを開き、ファイルをそのままダウンロードする
- 履歴を読み、コミットを差分付きで開き（`.diff` や `.patch` ファイルとしても）、2 つのコミットを比較する
- リリースを見る、アーカイブをダウンロードする、HTTP でクローンする（`git clone https://<NAS のアドレス>:9951/git/<所有者>/<リポジトリ>.git`）
- **探索** や所有者のプロフィールで公開リポジトリを見つける

非公開のリポジトリは見えないままで、そのアドレスは「見つかりません」を返します。スイッチがオンの間、Forgejo はアカウントの一覧も表示しません（探索の **ユーザー** タブとユーザー検索は、サインイン済みのユーザーに対してもオフになります）。そのため訪問者がアカウント名を集めることはできません。プッシュ、イシュー、プルリクエスト、設定には引き続きアカウントが必要です。スイッチがオフのときは、どのページも訪問者に最初にサインインを求めます。

リポジトリを共有するには、Forgejo でリポジトリを公開にし、**公開閲覧** をオンにして、アドレスを送ります。Git のホスト名がある場合、アドレスは `https://<Git のホスト名>/<所有者>/<リポジトリ>` です。

### 新しいプロジェクト

**新しいプロジェクト** は、プロジェクトを何もない状態から、ビルドして発行した最初のバージョンまで、手順に沿って進めるページです。コマンドパレット（Ctrl+Shift+P、**新しいプロジェクト**）、エディターのサイドバーの上部、Git サイドバーのヘッダー、Git ページ、［プロジェクト］ページから専用のタブで開きます。［例］ページの **エディター用プロジェクト** にある例には **この例からプロジェクトを作成** があります。developer ロール、Git とエディターのモジュール、Forgejo 上の自分のアカウントが必要です（Git ページに同期状態が表示されます）。Git またはエディターがオフのあいだは、ページが手順の上にそのことを示し、管理者はそこでボタン 1 つでオンにできます。

1. **テンプレート。** テンプレートはグループに分かれています。プログラム（C、CMake を使う C++、Go、Rust、Node、Python、Java）、QTS 用パッケージ（QPKG パッケージ、App Builder アプリ、ソースからのパッケージ）、カーネル（カーネルモジュール、USB シリアルドライバー ch341）、Web サイト（静的サイト：`src/` のページ、`public/` をビルドする Makefile、`make test` のリンク確認）、例です。各カードはビルドで何ができるかを示し、この NAS にまだないもの（QDK、ビルド環境、オフのモジュール）を、それを追加するページへのリンク付きで示します。
2. **名前と公開範囲。** 名前はリポジトリとフォルダー `projects/<名前>` の名前になり、入力中に確認されます。続いて所有者（自分のアカウント、または Forgejo でリポジトリを作成できる組織）、非公開か公開か、必要なら説明を指定します。App Builder アプリでは、元にするプリセットとターゲットを選びます。
3. **サービス**（QPKG パッケージとソースからのパッケージ）。パッケージの起動と停止をブロックで組み立てます。プリセットを選び（**コマンドラインツール**はプログラムを `/usr/bin` にリンク、**バックグラウンドサービス**はアカウント、ボリューム上のデータフォルダー、そのアカウントでのプログラム起動、ポートの待機、タイムアウト付きの停止を追加、**Web アプリ**は App Center の「開く」ボタンを追加、**空**は動詞だけ）、そのブロックを変更します。各ブロックのカードには、入力中からスクリプトに書き込まれる行が表示され、拒否された値はその欄の下に示されます。起動が App Center の Enable をオンにすることはなく、停止が 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 アプリ、Web サイトの場合）：**この NAS でテストデプロイ**（管理者、署名済みパッケージのみ。アプリでは **この NAS で有効化**）、**この NAS の App Center リポジトリに公開**、**自分の Web サーバーに公開**（管理者、下の「自分の Web サーバー（Web ターゲット）」のいずれか）。**保存先を追加** で新しいターゲットを保存できます。どれも任意です。公開の選択は、プロジェクトのスイッチ **App Center リポジトリに公開**（プロジェクトビューの［設定］）の値にもなります。オンの間は、プロジェクトの CI が実行のたびにそこへ公開します。
6. **確認** にはすべての回答と、実行される手順が並びます。**プロジェクトを作成** で送信します。
7. **進行状況** は各手順のジョブを追います。リポジトリと最初のコミット、最初のビルド、続いてテストデプロイ、App Center への公開、Web への公開を順に実行します。各行には状態とログの末尾が表示され、クリックするとログが表示されます。失敗した手順は理由を示して **再試行** を出し、後の手順は待ちます。最後に **プロジェクトを開く**、**エディターで開く**、**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 のランナーで、ワークフローに書かれた環境で実行されます。QPKG のワークフローはパッケージを[パッケージリポジトリ](#パッケージリポジトリ)に公開します。リポジトリはコード署名されたパッケージだけを受け付けます。リポジトリ作成後に最初のコミットやそのプッシュが失敗した場合、ジョブはフォルダーを削除し、リポジトリも削除して、理由を示します。プロジェクトのスイッチ **App Center リポジトリに公開** がオフの間は、ワークフローの `bvs repo publish` は公開せずに通知を出し、実行はそのまま続きます。

### プロジェクトビュー

データフォルダーの `projects/` にある各フォルダーには専用のページがあります。ウィザードで作ったプロジェクト、エディター用プロジェクトの例のコピー、そこにクローンしたリポジトリのどれでもかまいません。エディターのサイドバーの **プロジェクト** から、［プロジェクト］ページから、「新しいプロジェクト」の最後の **プロジェクトを開く** から、コマンドパレット（**プロジェクト … を開く**、または Ctrl+P と名前）から開きます。開発者ロールが必要です。［プロジェクト］ページのプロジェクトのメニューにも、ビルド、再ビルド、公開、Web ターゲットへの公開があります。同じメニューで、CI による App Center への公開をオンまたはオフにできます。

ページはフォルダーを読むだけで、自分からは何も変更しません。プロジェクトの種類はファイルから判断します。フォルダー名と同じ名前の App Builder のアプリ、先頭の `qpkg.cfg`（QPKG パッケージ）、先頭の `Makefile`、またはそのどれでもない場合（プロジェクトの CI ワークフローがビルドします）です。フォルダー名と同じ名前の App Builder のアプリがあっても、先頭に `qpkg.cfg` があるプロジェクト、または Makefile と `qpkg/` の `qpkg.cfg` があるプロジェクトは自身のビルド方法を保ち、テストデプロイと公開は引き続きその QPKG を使います。

上部の帯には手順が並びます。実行できない手順はボタンを無効にしたまま残し、その横と帯の下に理由を表示します。 プロジェクトがまったく行えない手順は帯から外れ、その代わりに帯の下の 1 文で、プロジェクトが何を作り、ビルドの結果がどこに行くかを示します。プログラムならプロジェクトのフォルダー、カーネルモジュールなら「カーネル」ページ、App Builder のアプリだけをビルドする Makefile なら各アプリのページです。

- **ビルド** は下部パネルのジョブとして実行されます。QPKG パッケージは QDK の `qbuild` で、Makefile のプロジェクトは `make` でビルドし、どちらもプロジェクトのビルド環境で実行します。ログには、ビルドが `build/` に書いた `.qpkg` とコード署名の有無が出ます。App Builder のアプリは自分のページと同じようにビルドします。Makefile の既定ターゲットが `./bvsapp.sh create` と `./bvsapp.sh build` の行だけを実行する場合、ビルドは同じことを App Builder で行います。`create` の行が指すアプリのうちまだないものを作り、それからアプリを 1 つずつ順にビルドします。`create` の行は `./bvsapp.sh create NAME RECIPES-FILE [ENV] [LINK] [TARGET] [OTHERS]` です。このような Makefile は、既定ターゲットとその前提条件を `.PHONY` に挙げ、単純な代入（各変数は 1 回だけ）と単純なルールだけを含みます。それ以外（条件、include など）を含む Makefile は make でビルドします。TARGET は `native`、`aarch64`、`armv7` のいずれかなので、1 回のビルドでこの NAS 用のアプリと ARM 64 ビット用のアプリを作れます。OTHERS は、静的リンクできないレシピを静的アプリがどう扱うか（`refuse` または `bundle`）です。こうして作ったアプリは、プロジェクト自身の `recipes/` のレシピも使います。ただし、フォルダー名がプロジェクト名にならない場合（スペースを含む名前など）は使いません。同じ名前のアプリが別のターゲットで、またはフォルダーに独自のレシピがあるのに別のプロジェクトで既にある場合、ビルドは無効のままで、直し方が表示されます。最初のビルドの前には、このステップがビルドするアプリの名前を示します。 パッケージを `qpkg/` に持つ Makefile のプロジェクトは、エディターのターミナルで `make qpkg` を実行してパッケージファイルを作ります。それができるまで、テストデプロイ、公開、Web に公開には「パッケージファイルが必要です」と表示されます。
- **再ビルド** は最初からビルドします。`make clean` のあと `make` を実行し、QPKG パッケージでは先に `build/` の古い `.qpkg` を削除します。`clean` ターゲットのない Makefile は再ビルドできず、ページにそう表示されます。App Builder のアプリにも、App Builder のアプリを作ってビルドするだけの Makefile にも、再ビルドはありません。
- **QPKG としてエクスポート**（管理者）は App Builder のアプリにあり、アプリのページと同じです。 公開はアプリのページと同じくエクスポートを待ち、ヘッダーの **アプリを開く** でそのページが開きます。
- **テストデプロイ** は管理者向けです。最新の `.qpkg` を、App Center がファイルをインストールするのと同じく `qpkg_cli` でこの NAS にインストールし、ジョブは App Center が確認するまで待ちます。パッケージは App Center で削除するまでインストールされたままです。コード署名のないパッケージは拒否されます。テストデプロイは署名済みのパッケージだけをインストールし、App Center の署名の確認を省くことはありません（App Center 自体では、NAS の所有者が署名のないアプリを許可できますが、このページはそうしません）。ファイルの証明書で署名してください（[QPKG のコード署名](#qpkg-のコード署名) のケース 1）。Bucket & Code Server IDE 自身のパッケージは、インストールすると動作中のパッケージを置き換えてしまうため拒否されます。App Builder のアプリでは、テストデプロイはこの NAS でそのビルドを有効化します。
- **公開** は最新の `.qpkg` を [パッケージリポジトリ](#パッケージリポジトリ) に、そのルールどおりに入れます。管理者の公開は掲載されるバージョンになり、開発者の公開は管理者を待ちます。コード署名のないパッケージは、リポジトリが署名なしのパッケージを受け付ける設定でない限り拒否されます。その下の行には、プロジェクトの CI も App Center リポジトリに公開するかどうかが表示されます。このボタンはどちらの場合も公開します。
- **Web に公開**（管理者）は最新の `.qpkg`、または Web サイトのフォルダー `public/`（なければ `dist/`）を Web ターゲットにコピーし、ターゲットの公開アドレスから読み戻します。 ウィザードで作った Web サイトでは、この手順は `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 しません）、最新のコミット、変更されたパスが出ます。**ファイル** は一度に 1 つのフォルダーを表示し、ファイルを選ぶと読めます。`.qpkg` はダウンロードされます。編集はエディターで行い、`.git` フォルダーは表示しません。

**レシピ** は、独自の `recipes/` フォルダーを持つプロジェクトで、そのアプリが使うレシピを一覧にします。各レシピにはバージョン、ライブラリのレシピと同じ名前ならそれが置き換えるライブラリのバージョン、その下にレシピの検査で見つかったこと（ファイルと行）が表示されます。bvsd が除外したレシピには理由付きの行があり、同じ理由で除外されたものが 3 つを超えると、残りは 1 行で数えられます。`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 が保持するビルドは 1 つです |
| パッケージリポジトリのバージョン | 掲載バージョンにする（管理者） |
| git タグ | フォルダーにタグをチェックアウト |

クリックするとバージョンを選べます。そのメニューからは、テストデプロイ、公開、Web ターゲットへの公開、`.qpkg` のダウンロードもできます。タグは detached HEAD としてチェックアウトされ、Git カードの **main に戻る** でブランチに戻ります。フォルダーにコミットしていない変更があるあいだは切り替えは拒否され、メッセージに変更が一覧されます。先にエディターでコミットするか stash してください。2 つのバージョンにチェックを入れて **2 つを比較** を押すと、一方にあって他方にないファイルと、変更されたファイルが一覧されます。タグ同士やパッケージ同士は内容で比較し、パッケージとコミットはサイズだけで比較します。［パッケージ］ページでは、管理者がリポジトリのバージョンをこの NAS にインストールすることもできます（**この NAS にインストール**、テストデプロイと同じ確認付き）。

### 自分の Web サーバー（Web ターゲット）

Web ターゲットは、管理者がパッケージや Web サイトを自分の Web サーバーに公開する先です。この NAS のフォルダー（QTS の Web Server が配信する QTS の Web 共有、またはほかの共有フォルダー）か、rclone のリモート（SFTP、FTP、WebDAV、S3。バックアップの `rclone.conf` で設定）で、それを配信する公開アドレスを持ちます。［公開］ページの **Web ターゲット** にあり、**保存先を追加**、**変更**、**削除**、**テスト** があります。テストは小さなファイルをコピーし、公開アドレスから取得して、また削除します。

フォルダーのターゲットは、共有フォルダー内の専用フォルダー（`/share/Web/mysite`。共有フォルダー全体やボリュームは不可）で、データフォルダーの外にあります。Web サイトの公開では、サイトにもうないものをターゲットから削除するためです。アドレスは `http://` か `https://` で始まり、ユーザー名やパスワードは含めません。

公開のたびに、公開アドレスからファイルを読み戻します。パッケージは `.qpkg` とその `.sha256` を sha256 で、Web サイトは `index.html` をバイト単位で比較します。アドレスがコピーしたものを配信していない場合、ジョブは `web_check:` と両方の値を示して失敗します。

## CI ランナー

CI モジュールは Forgejo Actions を実行します。リポジトリの `.forgejo/workflows/` にあるワークフローファイルが、何かがプッシュされたときにランナーで実行されます。Git モジュールが必要です。管理者が **モジュール**、**CI**、**有効にする** でオンにします。ジョブは NAS 自身のランナーを Forgejo に登録し、Actions を有効にして Forgejo を再起動し、ランナーを起動します。ランナーが Forgejo に接続できた時点でジョブは終わります。

### NAS 上のランナー

- **ラベル** は `bucketvs`、`qnap-x86_64`（または `qnap-aarch64`）、`self-hosted` に、管理者が CI ページで設定した追加ラベルを加えたものです。`runs-on` がこれらのいずれかを指定したジョブはここで実行されます。例: `runs-on: bucketvs`。
- **すべてのジョブを NAS 自体の上で admin として実行します**。コンテナーは使いません。ジョブでは、パッケージのツール（git、curl、make など）、`actions/checkout` のような JavaScript アクション用の Node、環境内でビルドするための `bvs env run <env> -- <command>` を使えます。つまり、リポジトリにワークフローをプッシュできる人は、NAS 上で admin としてコマンドを実行できます。member と viewer は Forgejo でリポジトリの作成、フォーク、インポートや組織の作成ができませんが、developer と admin はできます。viewer はプッシュできません。CI がオンの間、Actions がオンのリポジトリへの member のプッシュは理由とともに拒否され、デプロイキーによるプッシュも同様です。developer より下のロールのアカウントが所有または管理するリポジトリへの member のプッシュやデプロイキーによるプッシュも、CI がオフの間でも拒否されます。そのようなアカウントは自分で Actions をオンにできるためです。bvsd は、member または viewer が所有するか書き込めるすべてのリポジトリで 1 分以内に Actions をオフにし、CI をオンにしたときはランナーの起動前にもオフにします。
- **残るもの** は developer または admin 自身の操作です。member が以前に書き込んだリポジトリで Actions をオンにするかワークフローを起動すること、またはそのようなアカウントに、以前に書き込んだリポジトリの管理権限を与えることです。member のコミットをマージまたはビルドする developer も、それを admin として実行します。
- **同時ジョブ数** は `ci.capacity`（既定 1）です。オンラインのランナーが受け取れないジョブは Forgejo で待機します。
- **CI をオンにする前に作成したリポジトリ** では Forgejo の Actions がオフなので、ワークフローをプッシュしても何も始まりません。リポジトリの **設定**、**ユニット**（Actions）でオンにしてください。CI がオンの間に作成したリポジトリではオンになっています。
- `actions/checkout@v4` のような **アクション** は `https://data.forgejo.org` からダウンロードされるので、それを使うワークフローには NAS のインターネット接続が必要です。ランナーにはキャッシュがありません。`actions/cache` は何も見つけず、ジョブはそのまま続きます。
- **アーティファクト**（`actions/upload-artifact`）は Forgejo 自身のアドレス（Git ページに表示されるもの）にアップロードされるので、NAS はそのアドレスに到達でき、HTTPS の場合はその証明書を信頼している必要があります。
- **ランナーの状態** は `<データ>/runner` にあり、ログは **ログ** の **runner** です。モジュールをオフにするとランナーが停止し、Forgejo の Actions がオフになります。

### 外部ランナー

ほかの場所にあるビルドマシン（サーバー、VM）も、この Forgejo からジョブを受け取れます。CI ページの **外部ランナーを登録** で、管理者が名前を付けると、Linux x86_64 用と aarch64 用のコマンドが表示されます。コマンドは forgejo-runner 13.2.0 を code.forgejo.org からダウンロードして sha256 を確認し、ランナーのトークンを `runner-token`（所有者だけが読める）に保存し、ラベル `linux-amd64`（または `linux-arm64`）でランナーを起動します。ランナーはすぐに一覧に表示され、コマンドが実行されるまではオフラインです。トークンが表示されるのはこのときだけです。このようなランナーは、自分のマシン上で、起動したユーザーとしてジョブを実行し、Forgejo の公開アドレスに接続します。

## SVN と Bucket

SVN と Bucket モジュールは、Subversion リポジトリを `/svn/<名前>` で、バケットを `/bucket/<名前>` で配信します。バケットはネットワークドライブ（WebDAV）で、保存したファイルがすべてリビジョンになるので、ファイルの以前のバージョンをいつでも読んだり戻したりできます。管理者が **モジュール** でモジュールをオンにすると、**SVN と Bucket** ページで両方の作成、一覧表示、削除ができます。

- **アカウント** はパネルのものです。権限はリポジトリごとに、各アカウントに対して読み書き、読み取り、なしを設定し、一覧にないアカウントの既定値も設定します。管理者は常に読み書きでき、member と developer は読み書き権限のある場所に書き込め、viewer は読み取りまでです。新しいリポジトリでは、管理者が権限を設定するまで（行の人物アイコン、**`<name>` の権限**）管理者以外には何の権限もありません。admin より下のロールでは、ページに各リポジトリとバケットに対する自分の権限が表示されます。
- **誤ったパスワード** はパネルのサインインと同じように数えられます。15 分以内に 5 回間違えると、そのアドレスからのそのアカウントと、アドレス自体が 15 分間ロックされます。
- **2 要素認証。** 2 要素認証を使うアカウントは、SVN や WebDAV にパスワードを使いません。アカウントページ（**WebDAV と SVN 用のアプリパスワード**）で **アプリパスワード** を 1 つ作成します。ランダムな 25 文字で、表示は一度だけです。新しく作ると古いものは置き換えられます。パスワードを変更するか 2 要素認証をオフにすると削除されます。
- リポジトリを **削除** すると `<データ>/trash` に移され、7 日後に `trash` ジョブが削除します。

### SVN リポジトリ

```bash
svn checkout "https://<NAS のアドレス>:9951/svn/<名前>"
svn commit -m "…"
```

ページには、リポジトリのアドレスを含むチェックアウトコマンドが表示されます。プロキシの背後ではアドレスは公開 URL に従い、SVN のホスト名がある場合はそのホスト名の `/svn/<名前>` になります。リポジトリの **履歴** にはリビジョンが一覧表示されます。ファイルは任意のリビジョンで読むことができ、別のリビジョンと比較し、新しいリビジョンとして戻すこともできます（**このバージョンを復元**、読み書き権限のあるアカウント）。

### バケット

- **保存がコミットです。** クライアントがファイルを保存すると、アカウント名と時刻付きのリビジョンが作成されます。そのリビジョンには WebDAV 経由で作成されたことが記録されます。多くのクライアントは先に空のファイルを書き、そのあとでデータを書くので、1 回の保存で 2 つのリビジョンができることがあります。
- **macOS のファイル。** **macOS の ._\* と .DS_Store ファイルを拒否する**（既定）で作成したバケットは、Finder がコピーしたものの隣に書く `._*` と `.DS_Store` のファイルを保存しません。クライアントにはエラーが返りませんが、そのファイルはリビジョンになりません。そのようなファイルの削除は許可されます。
- **元に戻す。** バケットの履歴には各ファイルのバージョンが表示されます。**このバージョンを復元** は古い内容を新しいリビジョンとして保存するので、何も失われません。

### WebDAV クライアントとその制限

| クライアント | 接続方法 | 注意 |
|---|---|---|
| macOS の Finder | **移動**、**サーバへ接続**、`https://<NAS のアドレス>:9951/bucket/<名前>` | Finder は書き込み中のファイルをロックします。バケットは WebDAV のロックに応答します |
| rclone | `vendor = other` とバケットのアドレスを指定した `webdav` リモート | スクリプトでのコピーに向いています |
| davfs2（Linux） | `mount -t davfs https://<NAS のアドレス>:9951/bucket/<名前> /mnt/<フォルダー>` | パネルのユーザー名と、パスワードまたはアプリパスワード |
| Cyberduck、WinSCP | ポート 9951、パス `/bucket/<名前>` への WebDAV（HTTPS）接続 | |
| curl | `curl -T file -u <ユーザー> https://<NAS のアドレス>:9951/bucket/<名前>/file` | 1 回のアップロードで 1 リビジョン（または 2 つ、上記参照） |
| Windows エクスプローラー | **ネットワーク ドライブの割り当て** | Windows の WebClient は、Windows が信頼する証明書を使った HTTPS でしかパスワードを送りません。実際の証明書を持つプロキシの背後にバケットを置いてください（[リバースプロキシの背後](#リバースプロキシpleskの背後で使う)）。自己署名証明書では足りません |

- 大きなファイルも切り詰められません。nginx は `/bucket/` にボディサイズの上限を設けず、アップロードを届いた順にそのまま渡します。
- バケットが保存を拒否するファイル（上記の macOS のファイル）にも WebDAV サーバーは `201` を返すので、クライアントにはエラーが表示されませんが、そのファイルはバケットにありません。

### アカウントのない人にリポジトリやバケットを見せる

管理者は、**SVN と Bucket** ページの行にあるスイッチ（**Web 公開**）で、SVN リポジトリやバケットを公開できます。アドレスを知っている人は誰でも、アカウントなしでブラウザーから閲覧できます。

| アドレス | 表示されるもの |
|---|---|
| `https://<NAS のアドレス>:9951/svn-web/<名前>/` | SVN リポジトリ |
| `https://<NAS のアドレス>:9951/bucket-web/<名前>/` | バケット |

行の **Web で表示** リンクでそのページが開きます。ページは最新または以前のリビジョンのファイルとフォルダーを一覧表示し、テキストファイルを行番号付きで（1 MiB まで）、または画像を表示し、各ファイルをダウンロードできるようにします。**履歴** には、作成者、メッセージ、変更されたファイル付きでリビジョンが表示されます。リビジョンのページには、そのリビジョンで変更された各ファイルの差分が表示されます（1 MiB 以下のファイル 20 個まで。それ以外はリンク）。任意のファイルを 2 つのリビジョン間で比較できます。

- **読み取り専用。** これらのページからは何も変更できません。WebDAV クライアントと `svn` には、公開リポジトリであっても引き続きアカウントが必要です。
- **公開されたものだけ。** 公開されていないリポジトリも、存在しない名前も、どちらも「There is no public repository at this address」を返すので、どの名前が存在するかはページからわかりません。
- **制限。** 1 つのアドレスは一度に 60 ページまで開け、その後は 1 秒に 2 ページ、ダウンロードは同時に 2 ファイルまでです。それを超えると、ページは少し待って再試行するよう求めます。差分は 2 MiB で打ち切られます。
- **検索エンジン** には、これらのページをインデックスしないよう求めています。ページは英語です。ページはパネルと同じ外観で、訪問者のブラウザーの設定に合わせてダークまたはライトで表示されます。
- プロキシの背後や SVN のホスト名がある場合、アドレスはリポジトリ自身のアドレス（公開 URL、または同じパスの SVN ホスト名）に従います。

## エディターと AI ツール

エディターモジュールは、developer と admin の各アカウントに、ブラウザーで動く VS Code（code-server）を提供します。管理者が **モジュール** でオンにします。各アカウントには専用のインスタンスがあり、アカウントがエディターを開くと起動し、通信のない状態がしばらく続くと停止します。

**エディターのターミナルは NAS 上の root シェルです。** QTS では、Forgejo と httpd を除くパッケージのプロセスが admin（uid 0）として動作します。NAS の admin パスワードを渡してもよい相手にだけ developer ロールを付与してください。

### エディターを開く

- ［エディター設定］ページの **エディターを開く**（またはパネルのアドレスの `/ide/`、エディターのホスト名）で、新しいタブにワークベンチが開きます。最初のリクエストでインスタンスが起動します。セッションがない場合は先にパネルのサインインが表示されます。
- 公開したメインのホスト名経由では、エディターがそこに公開されていない間、［エディター設定］ページ、エディタータブ、概要、**シェルを開く** はエディターがローカルネットワークからだけ開くことを示し、LAN のアドレス（`https://<NAS のアドレス>:<HTTPS ポート>/ide/`）へリンクします。管理者には **公開** ページのスイッチへのリンクも表示します。
- エディターは `<データ>/projects` を開きます。エディター用プロジェクトの例はここにプロジェクトを置きます。アカウントの設定、拡張機能、ターミナル履歴は `<データ>/ide/<アカウント>` に、ホームフォルダーは `<データ>/home/<アカウント>` に保存されます。
- ターミナルの `PATH` の先頭にはパッケージのツールがあります。git、svn、node、npm、rsync、curl、bash などです。`bvs env shell <名前>` でツールチェーンの環境に入り、`eval "$(bvs use <id>)"` でツールチェーンやランタイムを使える状態にします。ツールチェーンページの **シェルを開く** は、その環境に入ったターミナル付きでエディターを開きます。
- **エディター** アクティビティとコマンドパレットは、環境内のシェルやアプリのフォルダーを指定してエディターを開きます。エディターはパネルのエディタータブ内に表示され、スマートフォンでは別のブラウザーウィンドウで開きます。エディターを埋め込めるのはパネル自身のアドレスのページだけです。パッケージはエディターのすべての応答でブラウザーにそう伝えるので、ほかのサイトがエディターの root ターミナルを自分のページに埋め込むことはできません。
- **アイドル時間**（既定 30 分）が過ぎると、ブラウザーからの通信がないインスタンスは停止します。ただし、そのターミナルから起動したプログラム、拡張機能が起動した AI エージェント（Claude Code for VS Code の `claude`、Codex、Gemini CLI）、そこから動かしたままにしたものがまだ動いている間は停止しません。0 にするとアイドルによる停止はオフになります。アカウントは［エディター設定］ページで自分のインスタンスを停止でき、管理者はどのインスタンスでも停止できます。
- ［エディター設定］ページの **メモリとプロセス** は、インスタンスが動かしているものと、NAS のメモリに対して使っているメモリ量を示し、インスタンスを動かし続けているものに印を付け、**終了** で 1 つのプロセスを終了し、前回の停止の理由と終了したものを示します。プロンプトで入力を待っている AI エージェントもインスタンスを動かし続けます。不要になったらそこで終了してください。
- VS Code の **Ports** ビューから、NAS 上の開発サーバーに `/ide/proxy/<port>/` でアクセスできます。パッケージ自身のループバックポートはここでは拒否されます。
- **Explorer** でプロジェクトの `recipes` フォルダー、またはその中のレシピのフォルダーのメニューにある **New recipe** は、名前を尋ねて、そこに新しいレシピとしてパッケージのスターターレシピから `recipe.json` と `build.sh` を書きます。コマンドパレットでは **Bucket: New recipe** で、`recipes` フォルダーのあるプロジェクトのファイルを開いている間に使えます。レシピのファイルを保存するたびに、エディターは `bvs recipe lint` でプロジェクトのレシピを検査し、結果を **Problems** ビューに表示します。スターターレシピの 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、シェルスクリプト、C、C++、Rust、Go、Pascal、Python、Perl、PHP、JSON、YAML、TOML、nginx の設定、CSS、HTML、JavaScript、TypeScript を色分けし、整形します（コマンドパレットの **Format Document**）。ShellCheck はシェルスクリプトの問題を示し、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 のパネル、16 進エディター、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 に関するあらゆることのガイドラインを含みます。シェルから見た NAS の姿（RAM ディスク上のルート、BusyBox、systemd もパッケージマネージャーもないこと、`getcfg` と `setcfg`）、ストレージとパス、このパッケージでのビルド・署名・公開の方法（`bvs env run`、`bvs ldd`、`bvs qbuild`、App Builder、カーネル工房、パッケージリポジトリ）、ポート、バックアップ、そして NAS 上での `apt` や App Center で未署名アプリを許可することなど、提案してはいけないことです。ハードウェアトークンでの署名はサポートもテストもされていないとはっきり書いています。

- アシスタントは `projects/` ではなくプロジェクトのフォルダー（`cd <データ>/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` にある署名付きのリモートカタログは、新しいパッケージなしで項目を追加できます。bvsd がリモートカタログを使うのは、その ed25519 署名がパッケージに組み込まれた鍵と一致し、使用中のカタログより古くない場合だけです。「カタログを更新」ですぐに取得でき、組み込みの cron ジョブ「ツールチェーンカタログの更新」が毎日 04:15 に取得します。

### インストールと削除

インストールは管理者の作業で、進行状況はジョブページに表示されます。

1. アーカイブをデータフォルダーの `toolchains/cache/` にダウンロードします。中断したダウンロードは、インストールを再開すると続きから再開します。
2. sha256 をカタログと照合します。一致しない場合はファイルを削除します。
3. クロスツールチェーンやランタイムは `toolchains/<kind>/<id>` に展開し、アーカイブを削除します。ルートファイルシステムやカーネルソースはアーカイブのまま残り、各環境やカーネルのワークスペースがそれぞれコピーを展開します。QNAP は GPL カーネル公開物を 2 つに分けて公開しているので、両方をダウンロードし、それぞれの sha256 で確認してから結合します。

ボリュームがいっぱいの場合、インストールは「no space left on」とボリュームのマウントポイントを示して失敗します。

「削除」はインストール済みの項目を削除します。ルートファイルシステムから作った環境はコピーなので残ります。

### ツールチェーンやランタイムを使う

`bvs use <id>` は、インストール済みのクロスツールチェーンやランタイムをシェルで使えるようにする `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 が失敗した場合、環境は残りません。シェルからは `bvs env create <名前> --from <rootfs-id> --package <名前> --package <名前>` です。
- 「クローン」は新しい名前で完全なコピーを作ります。たとえば元の環境に触れずに変更を試す場合に使います。
- 「エクスポート」は環境を `toolchains/exports/<名前>-<時刻>.tar.zst` にまとめます。
- 「削除」は環境を削除します。中でシェルやプロセスが動いている間、bvsd は削除を拒否します。`projects/` 内のファイルは環境の一部ではないので残ります。

環境の中では次のようになります。

- データフォルダーの `projects/` が `/projects` として見え、シェルはそこで始まります。
- `HOME` は環境の `/root` で、`LANG` は `C.UTF-8` です。
- `/proc`、読み取り専用の `/sys`、専用の小さな `/dev` がマウントされ、NAS の `resolv.conf` と `hosts` がコピーされます。
- マウントはシェル専用のマウント名前空間に属し、シェルが終わると消えるので、NAS に何かがマウントされたまま残ることはありません。

シェルからは次のように操作します（例では `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 ジョブのチェックアウト、共有内のフォルダー）は `/work` にマウントされ、コマンドはそこで始まります。`/` から実行すると `/projects` で始まります。

### bvs qbuild で QPKG をビルドする

`bvs qbuild` は、実行したフォルダー（新しいプロジェクトの QPKG テンプレートが作るような、`qpkg.cfg` のあるフォルダー）から QPKG をビルドします。QNAP の `qbuild` を Debian または Ubuntu の環境の中で実行します。qbuild はシステムのプログラムをフルパスで呼び出すので、環境の中ではそのパスが NAS 自身のプログラムではなく環境のプログラムを指します。パッケージはフォルダーの `build/` に作られます。

```bash
bvs tc install qdk-2.5.0-x86_64      # 最初に 1 回。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` は最初の行で 3 つのケースのどれかを示します。たとえば `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。** 3 つのファイルを `qpkg.cfg` の横に置きます。環境にプログラムが足りなければ、`bvs qbuild` がコマンドを示します。たとえば `bvs env run ubuntu2404-builder -- apt-get install -y openssl bsdextrautils` です。`certificate` と `private_key` の片方だけがあるフォルダー（qbuild は警告して未署名のパッケージをビルドします）と、どちらかが空のフォルダーは拒否します。これらのファイルは git に入れないでください。QPKG テンプレートの `.gitignore` に載っています。CI ではリポジトリのシークレットとして保存し、ビルドの間だけフォルダーに書き出します。
- **ケース 2。** qbuild はトークン上の鍵の位置を変数から読みますが、その変数は環境の中の 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 は、選んだプログラムを環境の中で 1 つのフォルダー、つまりアプリのプレフィックス（既定は `/usr/app/my_app_prefix`）にコンパイルします。

**アプリ** の **例** タブには、すぐ使えるアプリ、環境、CI ワークフロー、cron ジョブ、エディター用プロジェクトがあります。カードの **コードを見る** は、使う前に例が作るものを表示します（どのロールでも使えます）。エディター用プロジェクトのファイル、ワークフローファイル、cron ジョブのコマンド、アプリならビルド順のレシピと各レシピの `build.sh` と `recipe.json` です。ビューアーは読み取り専用でテキストファイルを表示します。環境にはファイルがないため、そのカードにこのボタンはありません。例を使うには developer ロールが、環境には admin ロールが必要です。developer には環境のカードに **この例を使う** は表示されません。

### レシピ

各プログラムはレシピからビルドします。レシピには、sha256 付きのソース、依存するレシピ、環境に必要なビルドツール、結果が動くことを確かめるチェック（`bin/bash --version` など）が記述されています。ライブラリ（zlib、openssl、ncurses、readline など）、シェル（bash、zsh、busybox）、ツール（coreutils、curl、git、jq、rsync、tmux、htop、nano など）、サーバー（nginx、redis、openssh、dropbear）、言語（lua）のレシピがあります。

- レシピは、依存するレシピのあとでビルドされます。
- 再ビルドでは、入力（ソース、レシピ、依存関係、環境のコンパイラーと C ライブラリ、プレフィックス、動的リンクか静的リンクか）が変わらず、インストールしたファイルがすべてプレフィックスに残っている間は、レシピの結果を再利用します。
- リンク方式は、プログラムがライブラリを見つける方法を決めます。**動的** は各プログラムに RUNPATH `<prefix>/lib` を与えます。**relocatable** はそれをプログラム自身のフォルダーからの相対パス（`$ORIGIN/../lib`）で書くので、ツリーはどのフォルダーに置いてもライブラリを見つけられます。ただし terminfo フォルダーのようにプログラムにコンパイルされたパスは、引き続きプレフィックスを指します。**bundle** は relocatable に加えて、プログラムがビルド環境から読み込むライブラリ（libstdc++ など）を `lib` にコピーするので、アプリには NAS の C ライブラリだけが必要です。**静的** は、対応するレシピを静的リンクします。動的、relocatable、bundle の間で切り替えても再ビルドは発生しません。
- 静的アプリは、静的リンクできないレシピ（openssh、dropbear、tmux）を、それぞれ理由を示して拒否します。ただし、それらを bundle でビルドするようアプリを設定している場合は別です。新しいアプリページと **変更** では 4 つの方式すべてを選べ、静的アプリではその選択もできます。
- ユーザー、グループ、ホスト名を参照する静的プログラムは、`/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 は 1 日に 1 回、新しいカタログがないか確認します。署名が bvsd に組み込まれた
鍵と一致し、すべてのファイルがカタログの一覧と一致し、レシピがパッケージに同梱のものより新しい場合にだけ使います。
チェックに失敗したカタログは使われません。カタログの使用中は、新しいアプリのレシピ一覧で、カタログが持ち込んだレシピに
**カタログで追加** または **カタログで更新** の印が付きます。

アプリページの **レシピライブラリ** カードには、ビルドが使うライブラリ (パッケージのバージョン付きの
**パッケージのライブラリ**、またはカタログのバージョン付きの **カタログ**) と、最後のチェックとその結果が表示されます。
管理者にはボタンが 2 つあります (2 つ目はカタログの使用中だけ表示されます)。

- **レシピの更新を確認** は、毎日のチェックを待たずにすぐ確認します。
- **パッケージのライブラリを使用** は、Bucket & Code Server IDE に同梱のレシピに戻します。カタログは新しいものが届くまで保留になり、
  届いたらビルドは新しいほうを使います。

切り替えた後の再ビルドでは、ファイルが変わっていないレシピはすべて再利用されます。bvsd はビルドのたびにカタログの
ファイルをもう一度チェックします。NAS 上でファイルが変わっていた場合、ビルドは何も実行せずに止まり、以後は一つ前の
カタログかパッケージのライブラリが使われます。

### アプリのビルド

**アプリ**、**新しいアプリ**（developer ロール）では、名前、プレフィックス（別のものを選ばなければ `/usr/app/<名前>`）、アプリをビルドする環境（Debian または Ubuntu の環境。例ギャラリーで「Ubuntu 24.04 builder」を作れ、ページはこれを最初に選びます）、ターゲット（この NAS のアーキテクチャー、またはクロスビルドとして aarch64 か armv7）、アプリが動く NAS のモデル、リンク方式、レシピを指定します。依存するレシピはビルド順に追加されます。

アプリは、独自の `recipes/` フォルダーを持つ 1 つのプロジェクトのレシピも使えます。**パッケージ** の手順で **プロジェクトのレシピ** からそのプロジェクトを選びます。プロジェクトのレシピは一覧の先頭に、プロジェクト名の下で **プロジェクト** の印付きで並び、その後にライブラリのレシピが続きます。ライブラリのレシピと同じ名前のプロジェクトのレシピは、このアプリではそのレシピを置き換え、置き換えるライブラリのバージョンを表示します。プロジェクトが除外するレシピは、理由とともに示されます。選んだレシピがプロジェクトの一覧にない場合は、選択欄の下にその名前が示され、アプリから除外されます。そのレシピを含む一覧を選ぶと元に戻ります。アプリのページにはプロジェクト名が表示され、開発者はその名前からプロジェクトビューを開けます。viewer には、アプリの前回のビルドのレシピが、そのログに記された順に、それぞれの状態とログ付きで表示されます。最初のビルドの前には表示する順序がありません。

リンク方式はそれぞれカードになっていて、プログラムがライブラリを見つける場所の図、その方式のサイズ、更新、アプリの動作場所についてのコスト、対応するレシピが示されます。レシピを選ぶ前は、カードはレシピライブラリ全体を数えます。最初の手順に戻ると、ビルドのレシピを数えます。静的アプリでは、静的リンクできないレシピをどうするか（bundle でビルドするか、除外するか）を選びます。

- **ビルド**（または **再ビルド**）はジョブを実行します。各ソースをダウンロードして sha256 を確認し、レシピに必要なビルドツールを環境にインストールし、アプリの前回のビルドのコピーの中でビルドし、各プログラムをチェックします。
- 失敗、キャンセル、中断したビルドは、アプリを元のままにします。有効化、ダウンロード、エクスポートには、成功した最新のビルドが引き続き使われます。
- 1 つのアプリのビルドは同時に 1 つだけ実行されます。有効なアプリのプログラムが動いている間、ビルドは待機します（先に停止してください）。
- アプリのページには、ビルド順、各レシピの状態とログ、プレフィックス配下のファイル、成功した最新のビルドのアーカイブ（**ダウンロード**、`.tar.zst`）が表示されます。
- アプリのページの **変更** で、別の環境、アプリが動く NAS のモデル、リンク方式、レシピを取り込むプロジェクト、レシピを設定できます。次回のビルドで反映されます。
- **依存関係**（アプリのページのタブ）は、最新のビルドの各 ELF ファイルが何を必要とし、NAS のローダーがそれをどこで見つけるかを、何も実行せずに読み取って表示します。見つからないライブラリ、ライブラリが定義していないバージョン、アプリの外を指す RUNPATH は、修正方法付きの指摘として一覧に出ます。developer は修正をボタンで実行できます。**… を追加してビルド**、**… に切り替えてビルド**、**もう一度ビルド**、**RUNPATH を修正**、**インタープリターを設定**、**別の環境を選ぶ**、**この NAS 専用でビルド** です。ボタンのない修正（環境へのパッケージのインストール、別のマシン向けのビルド）は、対応方法を示す文です。指摘の下には、各ファイルと必要なものがグラフ（プログラム、アプリのライブラリ、NAS のライブラリ、見つからないものを列に並べ、破線はライブラリにないバージョン、点線はどこにもないライブラリ）またはツリーで描かれます。ファイルを選ぶと詳細が表示されます。NAS 上では `bvs deps <app>` が同じ内容を出力します。
- **ELF インスペクター**（ツールチェーン、developer ロール）は 1 つのファイルをチェックします。アプリのビルド、プロジェクトフォルダー、環境のファイル、管理者なら NAS 上の任意のファイルを、この NAS 上または環境内で動かした場合として調べます。`bvs ldd <file>` は NAS 上で同じことを行います（環境には `--env <name>`）。
- **すべてのビルド** は、アプリを置き換える前に結果に対して同じチェックを実行し、この NAS でプログラムが動かなくなる問題や、アプリが許すより新しい glibc を必要とするプログラムがあれば失敗します。問題と修正方法はジョブのメッセージに示されます。
- **パッチツール**（依存関係タブ）は、最新のビルドから選んだファイルの RUNPATH（アプリの `lib`、`$ORIGIN` 形式、またはアプリ内のフォルダー）やプログラムインタープリターを、環境の patchelf で設定します。作業はコピーで行い、成功した場合にだけビルドを置き換えます。**プレビュー** は各ファイルの変更前と変更後、新しい RUNPATH でアプリ内に見つからないライブラリを表示し、**パッチを適用** で実行します。次回のビルドでは、RUNPATH がアプリのリンク方式で再び書き込まれます。

Qt のビルドは Qt 自身のツール（moc、rcc、翻訳用の Linguist の lrelease）を実行します。そのためクロスビルドでは、Qt のレシピ（`qtbase`、`qttools`）がまずこれらのツールをこの NAS 向けにプレフィックス配下の `host/` へビルドし、その後でターゲット向けの Qt をビルドします。Qt を使ってビルドするレシピは、そこにあるツールを実行します。ターゲット上で `host/` を実行するものはありません。`host/` はアプリのビルドの一部なので、アーカイブと **QPKG としてエクスポート** にも含まれます。

### 独自のレシピ

レシピライブラリにプログラムのレシピがないときは、プロジェクトが独自のレシピを持てます。レシピはプロジェクトの `recipes/` フォルダーにライブラリと同じ形式で置き、そのプロジェクトを指定したアプリだけがビルドします。パッケージは次の 5 つの手順で作れます。

1. **プロジェクトを作る。** [新しいプロジェクト](#新しいプロジェクト) でテンプレート **Package from source**（ソースからのパッケージ）を選ぶと、プロジェクト名のスターターレシピ、2 つのアプリ（この NAS のプロセッサー用と、x86_64 の NAS ではクロスビルドになる aarch64 用）の Makefile、**サービス** の手順で作ったパッケージの起動・停止スクリプトを含むフォルダーができます。
2. **レシピを書く。** エディターの **New recipe** は、同じスターターレシピから別のレシピを追加します（[エディターを開く](#エディターを開く)）。`recipe.json` にプログラムの本当のソースのアドレスとその sha256 を入れ（エディターのターミナルで `curl -fsSL <アドレス> | sha256sum` を実行すると値が表示されます）、ビルドの手順を `build.sh` に書きます。レシピのフォルダーに置けるのは `recipe.json`、`build.sh`、`recipe.json` が指定する vendor ファイルだけなので、パッチはヒアドキュメントとして `build.sh` に入れます。
3. **検査する。** レシピのファイルを保存するたびにプロジェクトのレシピが検査され、結果が **Problems** ビューに表示されます。ターミナルの `make lint` と [プロジェクトビュー](#プロジェクトビュー) の **レシピ** カードも同じ結果を示します。検査は `recipe.json` を読み、`build.sh` を `bash -n` で構文解析するだけで、レシピを実行することはありません。
4. **ビルドする。** プロジェクトビューの **ビルド** は、2 つのアプリがなければ作り、順にビルドします。ビルドはプロジェクトのレシピを開始時に 1 回だけ読むので、あとで保存したファイルは次のビルドで使われます。ライブラリのレシピと同じ名前のプロジェクトのレシピは、このプロジェクトのアプリでだけそのレシピを置き換え、ビルドのログにそのことが記されます。
5. **パッケージにしてインストールする。** エディターのターミナルで `make qpkg` を実行すると、各アプリの最新の成功したビルドがツールチェーンの QDK でパッケージにまとめられ、成功したビルドのないアプリは除外されます。コード署名のファイルが `qpkg.cfg` の隣の `qpkg/` か、`QPKG_CERT_DIR` が指すフォルダーにあれば、パッケージは署名されます（[QPKG のコード署名](#qpkg-のコード署名)）。プロジェクトビューの **テストデプロイ**（admin）でこの NAS にインストールできます。

プロジェクトのレシピには次の決まりがあります。

- プロジェクトが除外するレシピ（フォルダー内の余分なファイル、リンク、サイズの上限を超えるファイル）は、レシピカードと検査で理由とともに示され、それを必要とするビルドは拒否されます。
- プロジェクトのフォルダーの名前を変えたり削除したりすると、そのアプリはビルドされません。アプリのページの **変更** でプロジェクトの新しい名前か **なし：ライブラリのみ** を設定するまで、ビルドは「プロジェクト『…』はありません」と答えます。
- プロジェクトのレシピは、ライブラリのレシピと同じくビルド環境の中で root として実行されます。開発者なら誰でも、ビルドでプロジェクトの Makefile を実行できるのと同じように、レシピを書いてビルドできます。viewer にはアプリのビルドのログにあるレシピ名が見えるだけで、レシピのファイルは見えません。
- プロジェクトのレシピはそのプロジェクトにとどまります。レシピライブラリや、すべての NAS が受け取る署名付きカタログに入るのは、Bucket & Code Server IDE 自体へのレビュー済みの変更を通してだけです。
- ビルドに 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 です。

新しいアプリと **変更** の **動作先** で、次の 2 つから選びます。

- **パッケージが動くすべての 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 にまとめます。この QPKG は、このパッケージのない別の NAS にアプリをインストールします。この NAS 専用でビルドしたアプリは拒否され、ビルドのプログラムが 2.39 より新しい glibc を必要とする場合はまとめる前に止まります（[アプリが動く NAS のモデル](#アプリが動く-nas-のモデル)を参照）。QDK が必要です。ツールチェーンページの QNAP タブで「QNAP QDK 2.5.0」をインストールしてください。QDK がインストールされるまでボタンは表示されず、アプリのページに入手先が示されます。エクスポートは、[`bvs qbuild`](#bvs-qbuild-で-qpkg-をビルドする) と同じようにアプリの環境の中で qbuild を実行します。ビルドのたびにその環境に `rsync` がインストールされます。古いバージョンのパッケージで最後にビルドしたアプリでは、拒否のメッセージに `rsync` を追加するコマンドが示されます。エクスポートしたパッケージは、起動時にプレフィックスのリンクを作り、停止時に削除します。コード署名はされていません。ジョブのログは[ケース 3](#qpkg-のコード署名) と示し、ボタンの下の行も同じことを示します。SSH セッションでは、`/etc/init.d/<名前>.sh disable` と `enable` で、エクスポートしたパッケージの App Center のスイッチを設定できます。プレフィックスのリンクはそのまま残ります。エクスポートしたパッケージは起動のたびに、アプリの `bin` と `sbin` を SSH ログインの `PATH` の NAS 自身のコマンドの後ろに加える行を `/etc/profile` と `/root/.profile` に追加します。admin にも、ログインできるほかのアカウントにも効きます。削除するとその行も取り除かれます。

## パッケージリポジトリ

このパッケージは App Center のリポジトリを持っています。そこに公開した QPKG パッケージは、ほかの NAS の App Center から、ほかのリポジトリのアプリと同じようにインストールできます。

- **アドレス。** `bvs repo list`（と `GET /api/v1/repo`）で表示されます。`<base>/repo/repo.xml` で、`<base>` は、パッケージをインターネットに公開している間は公開したパッケージ用のホスト名（なければ公開したメインのホスト名）（[インターネットへの公開](#インターネットへの公開cloudflare-tunnel)）、そうでなければ公開 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 に見えるのはパッケージごとに 1 つ、管理者が掲載したバージョンだけです。管理者が公開したパッケージの最初のバージョンはすぐに掲載され、以降のバージョンは管理者が切り替えたときに掲載されます（`POST /api/v1/repo/packages/<name>/latest`、または `bvs repo publish --latest`）。developer が公開したバージョンは、新しいパッケージの最初のバージョンも含め、管理者がいずれかのバージョンを最新にするまでどこにも掲載されません。リポジトリを追加したすべての NAS が、掲載されたバージョンを root としてインストールするからです。バージョンの削除（管理者）はそのすべてのアーキテクチャーを削除します。それが掲載中のバージョンだった場合、管理者が別のバージョンを選ぶまで何も掲載されません。
- **アップロード。** 同時にコピーされるアップロードは 2 つまでです。データボリュームの空きが 256 MiB 未満になるアップロードは拒否されます。リポジトリはバックアップに含まれません。復元後はパッケージを公開し直してください。
- **パネルでは。** **パッケージ** ページには、コピー用のアドレスと、パッケージとそのバージョン、署名の有無が表示されます。開発者はそこで QPKG をアップロードし、管理者はバージョンを最新として掲載し、バージョンを削除します。その **コード署名** カードには、[3 つのケース](#qpkg-のコード署名)、署名のない掲載バージョンの数、未署名パッケージ用の管理者のスイッチが表示され、スイッチはオンにする前に確認を求めます。アプリのページには、パイプライン（ビルド、QPKG としてエクスポート、公開）と **公開** ボタンが表示されます。

## カーネル工房

カーネル工房は、NAS で動いているカーネル用のカーネルモジュールをビルドし、それぞれをそのカーネルと照合してからロードします。QNAP は現在のカーネルのソースを公開していません（GPL の公開物は QTS 5.2.3 まで）。そのため工房は、動作中のカーネルが自分自身について示す情報を読み取り、それに合うようにカーネルツリーを設定します。

### 必要なもの

- 例ギャラリーの Kernel builder 環境（GCC 13 とカーネルのビルドツールを含む Ubuntu 24.04）。モジュールはカーネルと同じメジャーバージョンの GCC でビルドする必要があり、工房が両者を比較します。
- admin ロールと、ターミナルでの root。`bvs kernel` コマンドは bvsd の CLI ソケットを使うか、モジュールをロードします。

### プロファイル

`bvs kernel profile` は、動作中のカーネルについて次の情報を表示します。リリース（`6.6.129-qnap`）、QNAP がビルドに使ったコンパイラー、QNAP 自身のモジュールの vermagic、それらのモジュール内の `struct module` のサイズと init、exit の位置、モジュールに署名が必要かどうか、そしてカーネルツリーを合わせるために設定する項目です。

### ワークスペース

`bvs kernel prepare` は、動作中のカーネルのワークスペースをデータフォルダーの `kernels/<release>` に作ります。

1. カタログにあるこのバージョン用のカーネルソース（`bvs kernel sources`）を使います。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 が止まった場合（クラッシュ、停電、ハードリセット）、次の起動ではそれらのモジュールをロードしません。どれかがクラッシュの原因かもしれないからです。通知とカーネルページにそのモジュールが示されます。手動で 1 つロードするか、そのアプリを再び有効化すると、起動時に再びロードされるようになります。NAS の通常のシャットダウンや再起動ではパッケージが正規に停止するので、何も変わりません。
- 起動処理が終わらないモジュール（ハードウェアを待っているドライバーなど）があってもパネルは動作します。1 分後に通知でそのモジュールが示されます。ほかのロードとアンロードはそれを待ち、NAS を再起動すると終わります。

QTS のアップデートで別のカーネルになった場合は、新しいカーネルのワークスペースでモジュールをビルドし直す必要があります。別のデータフォルダーに復元した場合も同じです。`kernels/` はバックアップに含まれません。

### パネルでは

**カーネル** ページには、動作中のカーネルのプロファイル、カタログのカーネルソース、ワークスペースとそのモジュールが表示されます。管理者はここでワークスペースを準備し（ソース、環境、追加の設定、カーネル全体をビルドするかどうか）、プロジェクトフォルダーやツリーのディレクトリからモジュールをビルドします。どちらもジョブとして実行されます。**チェック**（developer）は各指摘を修正方法とともに表示します。**ロード** と **アンロード**（管理者）は `bvs kernel load` と `unload` と同じことを行い、拒否されたロードでは止めたチェックの内容が表示されます。データフォルダー配下の任意の `.ko` ファイルを、パスを指定してチェックできます。

## cron ジョブと操作

ジョブページには 2 つの一覧があります。スケジュールジョブ（cron）と、操作（インストールや移動など bvsd が実行する長いジョブ）です。

### スケジュールジョブ

developer は自分のジョブを作成・変更でき、管理者はすべてのジョブを変更できます。ジョブには次の項目があります。

- スケジュール: cron の 5 つのフィールド（`0 3 * * *`）、先頭に秒を加えた 6 つのフィールド、または記述子（`@daily`、`@hourly`、`@every 6h`）。先頭に `CRON_TZ=<zone>` を付けると別のタイムゾーンを使えます。付けない場合、ジョブはページ上部に表示される NAS のタイムゾーンで実行されます。
- コマンド: パッケージの `bash -lc` により root で実行されます。複数行は 1 つのスクリプトとして実行され、その出力が実行のログになります。
- 実行場所: NAS 上、または環境の中（環境の `bash`、なければ `/bin/sh`）。
- 作業フォルダー: データフォルダーの `projects/` からの相対パス、またはフルパス。環境の中では `projects/` だけが `/projects` として見えます。
- タイムアウト: 秒単位（0 はなし）。これを過ぎても続いている実行は停止され、タイムアウトとして記録されます。
- 実行中のとき: 次の実行をスキップするか、待機させます（待機できるのは 1 つまで）。
- 失敗時: 失敗またはタイムアウトした実行は[通知](#通知)を送ります。

各ジョブは直近 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` は経過時間に従います。秋には繰り返される 1 時間の両方で実行され、春には飛ばされた 1 時間の分は実行されません。そのほかのジョブは、それぞれの時刻に 1 回ずつ実行されます。繰り返される 1 時間にある時刻は 1 回だけ実行され、飛ばされた 1 時間にある時刻は切り替えの直後に実行されます。

サインインしたすべてのアカウント（`viewer` も）が、ジョブのコマンドと実行の出力を読めます。パスワードやトークンはコマンドに
書かず、root だけが読める `<data>/etc` 配下のファイル（`chmod 600`）から読み込ませてください。

### 組み込みジョブ

| ジョブ | 実行時刻 | 内容 |
|---|---|---|
| メンテナンス | 毎日 03:30 | データフォルダーの `tmp/` から 10 日間変更のないものを削除し、10 MB を超えた `log/audit.log` をローテーションします |
| ゴミ箱を空にする | 毎日 03:45 | データフォルダーの `trash/` に移されてから 7 日を過ぎたもの（削除したアカウントのホームフォルダーなど）を削除します |
| ツールチェーンカタログの更新 | 毎日 04:15 | 署名付きのツールチェーンカタログを取得します |
| nginx のログのローテーション | 毎時 7 分 | `nginx-access.log` や `nginx-error.log` が 20 MB を超えたら `.1` に名前を変え（古いファイルは 5 つ保持）、nginx にログを開き直させます |

管理者は、組み込みジョブのスケジュール、タイムアウト、実行中の扱い、通知を変更し、オフにできます。名前とコマンドは変わりません。

### 操作

インストール、環境のジョブ、データの移動、モジュールのオンは操作として実行されます。一覧には直近 50 件が新しい順に、進行状況とログ付きで表示されます。操作とそのログは 30 日間保存されます。パッケージの停止時に実行中だった操作は `interrupted` と表示されます。developer は自分の操作を、管理者はどの操作でもキャンセルできます。

## バックアップ

**バックアップ** ページ（管理者）では、何を、いつバックアップし、コピーをどこに送り、いくつ残すかを設定します。**今すぐバックアップ** でいつでもバックアップを実行できます。新しいインストールではスケジュールバックアップはオフです。**スケジュールバックアップ** をオンにすると、既定では毎日バックアップが実行されます（組み込みの cron ジョブ `backup`）。直近 7 日、4 週、6 か月のそれぞれについて最新の実行を残し、成功した最新の実行は常に残します。バックアップ、検証、復元、削除は同時に 1 つだけ実行されます。

同じ日の 2 回の実行は 1 日分として数えます。新しいほうが終わると、その日の前の実行は削除されます。そのため、午後に **今すぐバックアップ** を実行すると、その朝の予定された実行が置き換えられます。その朝の状態が必要な場合は、先に実行を別の場所にコピーしてください。

### バックアップの内容

1 回の実行は、すべてのファイルを sha256 とともに記載した `manifest.json` を含むフォルダー `<データ>/backups/<id>/` です。

| 部分 | 内容 |
|---|---|
| 設定 | `etc/`: 設定、アカウント、TOTP のシークレット、TLS 鍵、バックアップ用の鍵 |
| Git | Forgejo のダンプ: リポジトリ、Wiki、LFS オブジェクト、添付ファイル、データベース |
| SVN、バケット | 各リポジトリのコピー。差分で作成し、コピー済みのリビジョンは再びコピーしません |
| VS Code の設定 | 各アカウントの VS Code の設定、キーバインド、スニペット、拡張機能の一覧（拡張機能のファイルは含みません。拡張機能ビューから再インストールされます） |
| 環境 | ツールチェーンの環境（既定ではオフ。作り直せるため） |
| プロジェクト | 各プロジェクトフォルダー。Git の履歴とまだコミットしていないファイルを含み、`node_modules` は除きます |
| HOME フォルダー | 各アカウントの HOME フォルダー。キャッシュと AI ツール自体のインストールは除きます（それぞれのインストールボタンで戻せます） |
| App Builder | 各アプリの設定とビルド履歴。アプリのファイルはビルドで作り直されます |
| パッケージリポジトリ | 公開したパッケージ、そのアイコン、App Center が読むインデックス |

この 4 つはページでのチェックにかかわらず、すべてのバックアップに含まれます。前回のバックアップから変わっていないフォルダーは、そのバックアップのコピーにリンクされ、容量を使いません。

実行が見えるようになるのは、すべての部分が書き込まれ確認されたあとです。失敗やボリュームの容量不足の場合は、未完成のフォルダーが削除され、以前の実行はそのまま残ります。**検証** は実行全体を読み直します。各アーカイブの 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 も暗号化されたコピーを復元できません。この NAS とは別の場所に保管してください。新しい鍵を作ると、それ以降の実行はその鍵で暗号化されます。古いコピーには古い秘密鍵が必要です。

失敗した保存先は実行の結果に示されますが、実行自体は有効なままです。

`rclone.conf` にはリモートの認証情報が入っています。コピーや **テスト** がそれを使う前に、bvsd はそれ（とバックアップ鍵）を
root だけが読めるように（モード 600）し、そうでなかった場合はジョブのログにそう記録します。

### 復元

実行の **復元** は、すべて、1 つのリポジトリ（SVN リポジトリ、バケット、Git リポジトリ）、または 1 つのプロジェクトフォルダーを元に戻します。

- 確認するまで何も置き換えません。各アーカイブは sha256 と、各 SVN のコピーは `svnadmin verify` で照合します。確認に通らないコピーがあると、稼働中のデータを変更する前に復元が止まります。
- 置き換えられる稼働中のデータは `<データ>/trash` に 7 日間移されるので、復元自体も取り消せます。
- **1 つのリポジトリ** や **1 つのプロジェクトフォルダー** の復元は、モジュールを動かしたまま行います。誤って削除したプロジェクトフォルダーは、履歴とまだコミットしていないファイルごと戻ります。
- **すべて** はモジュールを停止し、データを入れ替え、復元した設定で 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 分に 1 回までです。その後の次の通知には、何件が保留されたかが示されます。
- 各 URL には 10 秒の制限付きで個別に `shoutrrr send` を実行します。失敗は URL のスキームだけを記録します。URL の残りの部分にはトークンが含まれるからです。

URL とスイッチは、システムページの **通知** にあります。スイッチは既定でオンです。管理者はそこで変更します。ほかのアカウントに見えるのはスイッチと各 URL のスキームだけです。URL の残りの部分にはトークンが含まれるからです。NAS の管理者は CLI ソケット経由で設定することもできます。

```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 と、bvsd が監視する各プロセスのログは 20 MB でローテーションされ、古いファイルは 5 つ保持されます。`nginx-access.log` と `nginx-error.log` にも同じ上限があり、bvsd の起動時と、ジョブ「nginx のログのローテーション」によって毎時確認されるので、1 時間分の通信量までは 20 MB を超えることがあります。ジョブ「メンテナンス」は、10 MB を超えた `audit.log` をローテーションし、古いファイルを 3 つ保持します。ログページ（管理者）は、任意のログの最後の行を表示し、絞り込み、新しい行を届いた順に追いかけます。

| ログ | 書き込むもの |
|---|---|
| `bvsd` | bvsd 自身: 起動、停止、適用した設定、問題 |
| `nginx` | nginx 自身の出力 |
| `nginx-access`、`nginx-error` | すべてのリクエストと nginx のエラー |
| `audit` | CLI ソケット上のすべてのリクエスト |
| モジュールのプロセスごとに 1 つ | 各モジュールのプログラム |

システムページには、バージョン、プラットフォーム、タイムゾーン、bvsd が監視するすべてのプロセスが表示されます。「サポートバンドルをダウンロード」（管理者）は、バグ報告用のアーカイブを 1 つ作成します。

- 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 プロキシ、`ssh://git@<NAS>:6052/…` のような git のリモート（`git remote set-url`）です。

### 削除、再インストール、壊れたパッケージ

パッケージを削除しても、再インストールしても、パッケージが壊れても、作業内容は残ります。作成したものはすべてデータフォルダーにあります。アカウントと設定、Git と SVN のリポジトリ、バケット、プロジェクトフォルダー、HOME フォルダー、VS Code の設定と拡張機能、App Builder のアプリ、環境、パッケージリポジトリ、バックアップです。インストールフォルダー（`<ボリューム>/.qpkg/bucketvs`）にあるのは、プログラムとデータフォルダーを指す 1 つのファイルだけです。

- **削除してから再インストール。** パッケージを削除するとインストールフォルダーは消えますが、データフォルダーと、それを指すファイル `/etc/config/bucketvs-data-root` は残ります。次のインストールはこのファイルを読み、同じデータフォルダーで起動します。アカウントとパスワード、プロジェクトとリポジトリ、サインイン中のセッションもそのままです。
- **インストール済みのパッケージに上書きインストール。** 同じバージョンでも新しいバージョンでも、置き換わるのはプログラムのファイルだけで、データフォルダーには触れません。
- **壊れたパッケージ。** インストールフォルダーが壊れた場合（プログラムがない、フォルダーを手で削除した）、App Center から、または **手動インストール** でパッケージを再インストールしてください。インストールは同じ方法でデータフォルダーを見つけます。削除されたファイルからまだ動いている bvsd は起動時に停止され、インストールされた bvsd が起動します。
- **データが消えるとき。** データフォルダーのフォルダー自体を削除したときだけです。意図して空から始めるには、パッケージを削除し、データフォルダーのフォルダーを削除してからインストールします。最初の起動で新しいデータフォルダーが作られます（[データフォルダーの初期位置](#データフォルダーの初期位置)を参照）。

ディスクの故障は別の問題です。データフォルダーは NAS 自身のディスクにあります。バックアップは別のマシンに保管してください（[バックアップ](#バックアップ)）。どのバックアップにも、プロジェクトフォルダー、HOME フォルダー、App Builder の設定、パッケージリポジトリが含まれます。

## bvs コマンド

`bvs` はパッケージのコマンドラインで、`/usr/bin/bucketvs/bin` にあります。cron ジョブを含め、bvsd が起動するすべてのプロセスの `PATH` に含まれています。パッケージの動作中は
`/usr/bin/bvs` がこのコマンドへのリンクになるため、NAS 上の SSH セッションでも名前で呼び出せます。パッケージを停止すると、このリンクは削除されます。

CLI ソケットで bvsd と通信するコマンドは、ソケットが root 専用なので root が必要です。`user`、`jobs`、そして bvsd の動作中は `tc install`、`tc remove`、`env create`、`clone`、`export`、`delete` です。`env shell`、`env run`、`qbuild` も `bvs-chroot` を起動するので root が必要です。ほかのユーザーで実行すると、ソケットが必要なコマンドは「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 ソケット経由で 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 ソケット（nginx が使用） |
| `/usr/bin/bucketvs/var/run/bvsd-cli.sock` | `bvs` コマンドのソケット（root 専用） |
| `<データフォルダー>/etc/` | 設定、アカウント、セッション、トークン、証明書、生成された `nginx.conf`（シークレットはモード 600） |
| `<データフォルダー>/log/` | ログ |
| `<データフォルダー>/tmp/` | 各プロセスの一時ファイル |
| `<データフォルダー>/home/<ユーザー>/` | 各アカウントのホームフォルダー |
| `<データフォルダー>/jobs/` | 操作、cron ジョブとその実行、ログ |
| `<データフォルダー>/toolchains/` | ダウンロードキャッシュ、インストール済みのツールチェーンとランタイム、エクスポート |
| `<データフォルダー>/envs/` | 環境 |
| `<データフォルダー>/projects/` | ワークスペース。環境内では `/projects` として見えます |
| `<データフォルダー>/trash/` | 削除したもの。7 日間保持します |

モジュールはそれぞれ独自のフォルダーを追加します。その場所は各モジュールの節に記載しています。
