---
title: Runtime と PRoot
description: rootfs アーティファクト、PRoot セッション規約、デプロイ状態マシン、展開パイプライン、トランザクション保護下のホット更新。
updated: 2026-10-20
---

# Runtime と PRoot

Runtime は、ABI ごとにビルド・配布される**イミュータブルな Ubuntu 24.04 ルートファイルシステム**です。PRoot は、Root 権限のない一般的な Android アプリ環境下でこのファイルシステムを完全に稼働させる役割を担います。

## <DocIcon name="lightbulb" /> なぜ PRoot を採用するのか

Android のセキュリティモデルでは、一般アプリが自身のプライベートディレクトリ内の実行可能ファイルを直接 `execve` することや、`chroot` を実行することは禁止されています。PRoot は `ptrace` によりシステムコールをインターセプトし、ファイルパスをユーザー空間内の仮想ルートディレクトリへ透過的に書き換えることで、「あたかも chroot されているかのような」Linux 環境を実現します。

重要な制約事項：

- **実行可能ファイルの配置場所**：proot 関連バイナリ一式は `lib*.so` 形式で APK の `jniLibs` に格納され、インストール後に `nativeLibraryDir` に配置されます。これは targetSdk 35+ において `execve` の実行が保証される唯一の領域です。
- **ローダーのパス**：`PROOT_LOADER` は `nativeLibraryDir` 内のローダーを直接指定し、プライベートディレクトリの実行制限を回避します。
- **SELinux 対策**：Android の `/dev` には `/dev/shm` が存在せず、ハードリンクの作成も拒否されます。PRoot 起動コマンドはアプリ専用の共有メモリディレクトリをバインドし、`--link2symlink` オプションを有効化することで、Python のマルチプロセスイベントやキューの正常動作を担保します。

## <DocIcon name="handshake" /> セッション管理の契約

```text
App（親プロセス） ──stdin パイプ──▶ proot wrapper ──▶ Runtime プロセスグループ
```

- **stdin パイプは常にオープン状態を維持しなければなりません**。App プロセスが終了して EOF が送出されると、ラッパーは自律的に配下のプロセスグループ全体を kill し、孤児プロセスの残存を完全に防止します。
- `ProotHost` の監視コルーチンをキャンセルする際は、**待機のみを停止**し、必ず `CancellationException` を上位へ再送出してください。プロセスが終了したとみなして `session` ハンドルをクリアしてはなりません（正常停止処理時に stdin を閉じ、リソースを回収するためにこのハンドルが不可欠です）。
- クラッシュ時はバックオフ待機を挟んで再起動を試行し、起動前には必ず残存ロックファイルをクリーンアップします。

::: danger バックグラウンド起動への変更は厳禁です
`nohup` 等を用いて親子関係を切り離すと、親プロセスの死亡検知プロトコルが失われます。親プロセス終了後も Runtime が動作し続けるため、次回起動時に同一の設定ファイルや SQLite データベースに対して競合が発生し、データ破壊の原因になります。
:::

## <DocIcon name="settings" /> デプロイ状態マシン

`ProvisionState` の状態遷移：

| ステート | 状態の定義 |
| --- | --- |
| `Checking` | 既存の導入済みバージョンを検証中 |
| `NotInstalled` | 未デプロイ状態（ユーザーのアクション待ち） |
| `NotBundled` | 軽量版を使用しているが、Release 上に該当アーキテクチャのファイルがない |
| `LowDisk` | 空きストレージ容量が 2 GB 未満 |
| `Downloading` | ダウンロード進行中（圧縮バイト単位で計測） |
| `Extracting` | 展開処理中（圧縮バイト単位で計測） |
| `Ready` | デプロイ完了（起動可能状態） |
| `Failed` | デプロイ異常（ユーザー向けのエラー事由 `reason` を保持） |

### インストール判定ロジック

`isInstalled()` は以下の条件をすべて満たした場合に true を返します：

1. `.provisioned` フラグの記録バージョンと、rootfs 内の `opt/azurpilot/BUILD_MANIFEST` が一致していること。
2. `rootfs_arch` が端末の ABI と合致していること（旧版パッケージで欠落している場合は arm64 と判定）。
3. Python のエントリポイントバイナリが存在すること。

導入済み状態のときは更新の案内のみを表示し、**自動的に上書き置換されることはありません**。

### ダウンロードパイプライン

- マニフェストが 8 MiB 以上の場合：まず `bytes=0-0` のプローブを行い、4つの独立した接続で互いに重複しない範囲を並列取得。
- 各セグメントごとに HTTP 206 レスポンス、`Content-Range`、長さ、バージョンヘッダーを厳密に検証（レスポンス圧縮は無効化）。
- セグメント取得失敗時：全接続をキャンセルし、書き込みスレッドの終了を待機して進捗をゼロリセットした上で、単一接続によるフォールバック取得を1回試行。
- ダウンロード元変更時：100ms ごとに `shouldAbort` をポーリングし、不完全な一時ファイルを削除して新ソースから再取得。
- ダウンロード完了後は、まずファイルサイズを照合し、次に SHA-256 ハッシュを照合。

### 展開パイプライン

1. `rootfs.tmp` ディレクトリへ展開し、完了後に**アトミックにリネーム**。
2. 純粋な Java ストリーミング展開（commons-compress tar ＋ tukaani xz）を使用。busybox tar は ubuntu-base のハードリンク前方参照で破損するため使用しません。
3. シンボリックリンクは `Os.symlink` を使用。ハードリンクは実体コピーとして展開し、前方参照は2次パスで補完。
4. ディレクトリトラバーサル攻撃や、展開済みシンボリックリンクを跨ぐ不正エントリは拒否。
5. tar 内のパーミッションフラグに基づき、実行可能属性（proot および Python 依存バイナリ）を確実に復元。
6. `config/`、`password.txt`、`log/` を保持。テンプレートやシードは新バージョンのものを配置。
7. `.provisioned` フラグを書き込み、ディレクトリ名を `rootfs` へ切り替えて完了。

## <DocIcon name="clock" /> 初回起動が遅い理由

rootfs のサイズ圧縮のため、配布イメージからは `__pycache__` が完全に剥離されています。新 rootfs の初回起動時は全モジュールのバイトコードが再生成されるため、WebUI の準備完了までに90秒以上かかる場合があります。この際、`ProotHost` のログには以下が出力されます：

```text
AzurPilot WebUI not ready within 90000ms
```

これは想定内の警告メッセージです。2回目以降の起動時はキャッシュが効くため通常の速度で起動します。

## <DocIcon name="clipboard" /> ビルドマニフェスト

`BUILD_MANIFEST` は Runtime の身元証明書です：

| フィールド | 用途 |
| --- | --- |
| `azurpilot_commit` | 内包されている上流のコミットハッシュ |
| `rootfs_version` | `<コミット先頭12文字>-<ビルド入力ハッシュ先頭10文字>` |
| `rootfs_arch` | ターゲット ABI |
| `android_host_commit` | ビルド時の Android ホスト側コミットハッシュ |
| `node_version` | 内包されている Node.js ツールチェーン |
| `hot_update_mode=local` | 端末内でのフロントエンドセルフビルドに対応している旨を宣言 |

`rootfs_version` のハッシュ値にはビルドスクリプト、overlay、シード、ツールチェーンの構成が含まれます。ハッシュ値が変更された場合は完全パッケージ更新が必須となり、ハッシュが同一でコミットのみが異なる場合はホットアップデートが可能です。

## <DocIcon name="refresh" /> ホットアップデートトランザクション

```text
/android/update/status を確認
  → 対象コミットを /opt/azurpilot-update/source へシャロークローン
    → uv.lock の差分を比較
       ├── 差分あり → requiresRuntimeUpdate=true（処理終了）
       └── 差分なし → npm ci && npm run build（タイムアウト600秒）
            → トランザクションログ config/android-update-transaction.json を記録
              → インスタンス停止 → 旧 dist 退避 → ソース切り替え → 新 dist 配置 → マニフェスト更新
                → supervisor によるインスタンスの再ロードと復帰
```

障害復旧シナリオ：

- ソース切り替え前の障害：作業ディレクトリを破棄。本番コードと dist は無傷で維持。
- 切り替え中の障害：退避していた旧ソースコード、dist、マニフェストを復元。
- プロセス自体の突然死：`android_host.py` が次回の WebUI 起動時にトランザクションログを読み込んで自動復旧。復旧不能時はログを保持して起動をブロック。

ホットアップデート処理は完全パッケージ更新と共通のパイプラインロックを共有します。

::: warning 依存ライブラリの自動インストールは無効化されています
未検証の Python ライブラリが本番環境に混入することを防ぐため、ホットアップデート時の Python パッケージインストールは無効化されています。`uv.lock` に差分がある場合は完全パッケージ更新を行ってください。
:::

## <DocIcon name="folder" /> オーバーレイレイヤーとシード

| ディレクトリ | 役割 | 制約事項 |
| --- | --- | --- |
| `rootfs/overlays/` | Android / PRoot 環境固有の互換性パッチ | ユーザー設定には手を触れない |
| `rootfs/seeds/` | 初回起動時の設定テンプレート | 初回プロビジョニング時のみ配置 |
| `rootfs/build/` | `build-azurpilot.sh` 等のビルドスクリプト | 対象 ABI のネイティブ環境下で実行 |

オーバーレイには `android_process_compat.py` 等の互換レイヤーが含まれており、`rootfs/tests/` 配下の単体テストで検証されます：

```bash
uv run --with psutil python -m unittest discover -s rootfs/tests
```

## <DocIcon name="zap" /> ストレージ容量とパフォーマンス

| 項目 | 容量規模 |
| --- | --- |
| rootfs 圧縮アーカイブ | 数百 MB |
| 展開後の実容量 | 1.5〜2.5 GB |
| ホットアップデート作業用領域 | 追加で数百 MB |
| フロントエンドの `node_modules` | 数百 MB（ビルド完了後にクリーンアップ可能） |

端末内でのフロントエンドビルドは CPU、メモリ、ネットワークを消費します。容量不足やビルド失敗時は安全に旧版が維持されます。
