From 9abfa8f1adca8e4baf0456e74740c77212591eb0 Mon Sep 17 00:00:00 2001 From: fyui001 Date: Thu, 5 Nov 2026 01:14:23 +0900 Subject: [PATCH 1/6] =?UTF-8?q?compose=20=E3=81=A7=20db=E3=83=BBmigrate?= =?UTF-8?q?=E3=83=BBapp=E3=83=BBdriver=E3=83=BBvela=E3=83=BBproxy=20?= =?UTF-8?q?=E3=82=92=201=20=E5=8F=B0=E3=81=AB=E7=AB=8B=E3=81=A6=E3=80=81?= =?UTF-8?q?=E8=A3=85=E7=BD=AE=E3=81=AE=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB?= =?UTF-8?q?=E3=81=A8=E8=B5=B7=E5=8B=95=E3=81=AE=E8=A9=A6=E9=A8=93=E3=82=92?= =?UTF-8?q?=E8=B6=B3=E3=81=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5.1 --- .env.example | 35 ++++++ .github/workflows/check.yml | 71 ++++++++++++ Caddyfile | 15 +++ README.md | 195 +++++++++++++++++++++++++++++++- compose.yml | 167 +++++++++++++++++++++++++++ driver/driver.json | 13 +++ driver/examples/driver-dvb.json | 13 +++ driver/examples/driver-usb.json | 12 ++ examples/gpu-intel.yml | 7 ++ examples/tuner-dvb.yml | 9 ++ examples/tuner-usb.yml | 6 + 11 files changed, 541 insertions(+), 2 deletions(-) create mode 100644 .env.example create mode 100644 Caddyfile create mode 100644 compose.yml create mode 100644 driver/driver.json create mode 100644 driver/examples/driver-dvb.json create mode 100644 driver/examples/driver-usb.json create mode 100644 examples/gpu-intel.yml create mode 100644 examples/tuner-dvb.yml create mode 100644 examples/tuner-usb.yml diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..367b946 --- /dev/null +++ b/.env.example @@ -0,0 +1,35 @@ +# .env に写して、この機械に合わせて書く。.env はコミットしない。 + +# データベースのパスワード。空のままでは起動しない。`;` は使えない +POSTGRES_PASSWORD= + +# データベースのデータを置くホスト側のディレクトリ +CARINA_DB_DIR= + +# 録画を書くホスト側のディレクトリ。移行で録画を移すなら、移行元と同じマウントの下に置く +CARINA_RECORDINGS_DIR= + +# エンコード済みファイルを書くホスト側のディレクトリ。ライブラリと一緒に増える +CARINA_ENCODES_DIR= + +# OIDC の client secret を暗号化する鍵を置くホスト側のディレクトリ。データベースとは別に残す +CARINA_KEYS_DIR= + +# ブラウザからこのサーバに届くアドレス (https://tv.example.com など)。ID プロバイダに登録する redirect URI の元になる +CARINA_PUBLIC_ORIGIN= + +# Caddy が開くサイトのアドレス。公開のドメイン (tv.example.com) なら https で開き証明書を取る。LAN だけなら :80 のまま +PYXIS_SITE_ADDRESS=:80 + +# ホスト側で開く http と https のポート。80 と 443 がほかに使われているなら別の番号にする +PYXIS_HTTP_PORT=80 +PYXIS_HTTPS_PORT=443 + +# 録画をあとからエンコードするときの変換器。Software か Vaapi (GPU を渡したとき) +CARINA_ENCODINGS_PREFER=Software + +# ライブと録画再生をその場で変換するときの変換器。Software か Vaapi (GPU を渡したとき) +CARINA_TRANSCODING_PREFER=Software + +# VAAPI で使う GPU の描画ノード +CARINA_RENDER_NODE=/dev/dri/renderD128 diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 2fbc508..7b48e1e 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -17,3 +17,74 @@ jobs: - uses: actions/checkout@v7 - run: pipx install yamllint==1.38.0 - run: yamllint --strict . + + compose-config: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - name: The .env a check needs, with every required value filled in + run: | + cat > .env < .env </` を開き、そのアカウントでログインしてからパスワードを替える。 + +`docker compose ps` の health は、`driver` では `--probe` の結果、`app` では `/api/health`、`vela` では `/login` の応答を表す。 +コンテナのログは json-file 形式で、1 ファイル 20 MB・5 世代までに抑える。 + +### ポートと公開のアドレス + +`proxy` はホストの 80 番と 443 番で待つ。 +ほかのものが使っているなら、`.env` の `PYXIS_HTTP_PORT` と `PYXIS_HTTPS_PORT` で別の番号にする。 + +LAN の中だけで使うなら `PYXIS_SITE_ADDRESS` は `:80` のままでよく、http で開く。 +公開のドメインがあるなら `PYXIS_SITE_ADDRESS=tv.example.com` のように書く。 +Caddy が証明書を取って https で開き、http は https へ転送する。 +証明書を取るには、そのドメインがこの機械を指し、インターネットから 80 番と 443 番が届く必要がある。 +ホストの番号を変えたときは、ルータで 80 番と 443 番をその番号へ転送する。 + +どちらの形でも、ブラウザから届くアドレスを `CARINA_PUBLIC_ORIGIN` に書く(`http://192.168.1.10` や `https://tv.example.com`)。 +ID プロバイダに登録する redirect URI の元になる。 + +`proxy` が `app` の前段にいるので、`app` は compose の網からの `X-Forwarded-*` を信じる。 +網の範囲は `compose.yml` の `networks` と `CARINA_KNOWN_NETWORKS` に同じ値(`172.30.0.0/24`)で書いてある。 +家の網と重なるなら、両方を同じ値で書き換える。 + +### 止め方 + +```bash +docker compose stop # 止める。データは残る +docker compose down # コンテナと網を消す。データは残る +``` + +`down -v` はボリュームごと消す。 +`driver` と `app` のソケットのほか、サムネイル・字幕・Caddy の証明書がボリュームにあるので、普段は使わない。 +`driver` は録画中に止められると、録画が終わるまで(上限 6 時間)待ってから止まる。 + +## 装置の重ね方 + +compose の `devices:` は変数で数を変えられないので、装置ごとのファイルを重ねる。 + +| ファイル | 渡すもの | +| --- | --- | +| `examples/tuner-dvb.yml` | `/dev/dvb/adapter0` と、ホストの pcscd の `/run/pcscd`(読み取り専用) | +| `examples/tuner-usb.yml` | `/dev/bus/usb` | +| `examples/gpu-intel.yml` | `/dev/dri` を `app` に | + +```bash +docker compose -f compose.yml -f examples/tuner-dvb.yml -f examples/gpu-intel.yml up -d +``` + +毎回書かずに済ませるなら、`.env` に `COMPOSE_FILE=compose.yml:examples/tuner-dvb.yml:examples/gpu-intel.yml` を書く。 + +### DVB のチューナー + +`driver/examples/driver-dvb.json` を `driver/driver.json` に写し、`devices` にチューナーを書く。 +アダプタが増えたら、`examples/tuner-dvb.yml` の `devices` にも同じアダプタを足す。 +B-CAS カードはホストの pcscd で読むので、ホストで pcscd を動かしておく。 +`driver.json` のキーは Carina の README の「設定」にある。 + +### USB のチューナー + +`driver/examples/driver-usb.json` を `driver/driver.json` に写す。 +チューナーの id はつないだポートから付く(`usb1-1` など)。 +`driver` は `/dev/bus/usb` を直に動かすので、ノードの番号が変わっても設定はそのままでよい。 + +### Intel の GPU + +`examples/gpu-intel.yml` を重ねると `/dev/dri` が `app` に渡る。 +描画ノードの所有グループは、イメージの entrypoint が装置から読んで `app` に足すので、`group_add` は要らない。 +渡しただけでは変換はソフトウェアのままなので、`.env` で `CARINA_ENCODINGS_PREFER` と `CARINA_TRANSCODING_PREFER` を `Vaapi` にする。 +描画ノードが `/dev/dri/renderD128` でなければ `CARINA_RENDER_NODE` で指す。 + +## 移行 + +現行の録画システムから録画を引き継ぐなら、`CARINA_RECORDINGS_DIR` を移行元と同じマウントの下に置く。 +録画はハードリンクで移すので、マウントをまたぐと 1 本も移せない。 +手順は Carina の README の「移行」にある。 + +## 更新 + +Pyxis のリリースタグが、イメージの版の組み合わせを固定する。 +更新は、タグを取り出してから `app` を止め、スキーマを当て、上げ直す順で進める。 + +```bash +git fetch --tags +git checkout vX.Y.Z +docker compose pull +docker compose stop app +docker compose up -d +``` + +`up -d` は、古い `app` が止まった状態で `migrate` を走らせ、成功してから新しい `app` を上げる。 +列や表を消す migration があっても、動いている古い `app` を壊さない。 +`up -d` が作り直すのは定義の変わったサービスだけなので、`driver` のタグが前と同じなら `driver` は動いたままで、録画は止まらない。 + +`driver` のタグが変わる更新では、`up -d` が `driver` も作り直す。 +`driver` は録画が終わるまで止まらないので、録画・視聴・直近の予約が無い時間に当てる。 +`driver` だけを後回しにするなら、次のとおり `app` と画面だけを入れ替える。 + +```bash +docker compose stop app +docker compose run --rm migrate +docker compose up -d --no-deps app vela proxy +``` + +更新の前に、データベースと鍵の控えを取っておく。 + +```bash +docker compose exec db pg_dump -U carina carina > carina-$(date +%Y%m%d).sql +cp -a "$CARINA_KEYS_DIR" keys-$(date +%Y%m%d) +``` + +## 戻し方 + +`migrate` は進める方向にしか当てないので、戻すときはデータベースを更新前の控えから戻す。 + +```bash +git checkout vX.Y.Z # 戻す先のタグ +docker compose stop app +docker compose exec -T db psql -U carina -d postgres -c 'DROP DATABASE carina' -c 'CREATE DATABASE carina OWNER carina' +docker compose exec -T db psql -U carina -d carina < carina-YYYYMMDD.sql +docker compose up -d +``` + +鍵の置き場は更新で変わらないので、控えから戻すのは中身を失ったときだけでよい。 +録画とエンコード済みファイルは戻さない。 + +## 自前のプロキシを使う + +同梱の `proxy` の代わりに自前のプロキシを使うなら、`compose.override.yml`(コミットしない)で `proxy` を外し、`app` と `vela` のポートをこの機械の内側だけに開く。 + +```yaml +services: + proxy: + profiles: [unused] + app: + ports: + - '127.0.0.1:8080:8080' + vela: + ports: + - '127.0.0.1:3000:3000' +``` + +自前のプロキシは、同じオリジンで `/api/*` を `app` に、それ以外を `vela` に送る。 +WebSocket(`/api/live/ws`)と SSE(`/api/events`)は切らずにバッファせず流し、`Range` ヘッダはそのまま通す。 +アクセスログを取るならパスを残さない。 +外部プレイヤーの URL はパスに再生用のトークンを含む。 +プロキシがこの機械の上で動くなら、`app` には compose の網のアドレスから届くので、`CARINA_KNOWN_NETWORKS` はそのままでよい。 + +## 版の表 + +| Pyxis | carina(app・migrate) | carina(driver) | vela | postgres | caddy | +| --- | --- | --- | --- | --- | --- | +| master | `app-v1.0.0` | `driver-v1.0.0` | `v1.0.0` | `18.6-alpine` | `2.11.7-alpine` | + ## ライセンス ライセンスは AGPL-3.0-only で、著作権者は NewWorldOrg である。 diff --git a/compose.yml b/compose.yml new file mode 100644 index 0000000..1e66766 --- /dev/null +++ b/compose.yml @@ -0,0 +1,167 @@ +x-logging: &logging + driver: json-file + options: + max-size: 20m + max-file: '5' + +services: + db: + image: postgres:18.6-alpine + logging: *logging + restart: unless-stopped + environment: + POSTGRES_DB: carina + POSTGRES_USER: carina + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?} + volumes: + - ${CARINA_DB_DIR:?}:/var/lib/postgresql + healthcheck: + test: ['CMD-SHELL', 'pg_isready -U carina -d carina'] + interval: 5s + timeout: 3s + retries: 10 + start_period: 1m + + # app は uid 10001 で動くので、app が書く置き場の所有者をその番号にそろえてから app を上げる (中のファイルには触らない) + prepare: + image: ghcr.io/newworldorg/carina:app-v1.0.0 + logging: *logging + restart: 'no' + entrypoint: ['chown', '10001:10001', '/var/lib/carina/keys', '/srv/encodes', '/srv/thumbnails', '/srv/captions'] + volumes: + - ${CARINA_KEYS_DIR:?}:/var/lib/carina/keys + - ${CARINA_ENCODES_DIR:?}:/srv/encodes + - thumbnails:/srv/thumbnails + - captions:/srv/captions + + migrate: + image: ghcr.io/newworldorg/carina:app-v1.0.0 + logging: *logging + restart: 'no' + environment: + CARINA_ROLE: migrate + ConnectionStrings__Carina: Host=db;Port=5432;Database=carina;Username=carina;Password=${POSTGRES_PASSWORD:?} + depends_on: + db: + condition: service_healthy + + app: + image: ghcr.io/newworldorg/carina:app-v1.0.0 + logging: *logging + restart: unless-stopped + environment: + CARINA_ROLE: app + ConnectionStrings__Carina: Host=db;Port=5432;Database=carina;Username=carina;Password=${POSTGRES_PASSWORD:?} + CARINA_DATA_PROTECTION_KEYS: /var/lib/carina/keys + CARINA_PUBLIC_ORIGIN: ${CARINA_PUBLIC_ORIGIN:-} + # proxy はこの compose の網にいる。networks の subnet と同じ値にする + CARINA_KNOWN_NETWORKS: 172.30.0.0/24 + Integrity__OutputRoots: primary=/srv/recordings + Thumbnails__WrittenTo: /srv/thumbnails + Captions__WrittenTo: /srv/captions + Encodings__OutputRoots: encodes=/srv/encodes + Encodings__Prefer: ${CARINA_ENCODINGS_PREFER:-Software} + Transcoding__Prefer: ${CARINA_TRANSCODING_PREFER:-Software} + Machine__RenderNode: ${CARINA_RENDER_NODE:-/dev/dri/renderD128} + volumes: + - driver-run:/run/carina + - ${CARINA_RECORDINGS_DIR:?}:/srv/recordings:ro + - ${CARINA_ENCODES_DIR:?}:/srv/encodes + - ${CARINA_KEYS_DIR:?}:/var/lib/carina/keys + - thumbnails:/srv/thumbnails + - captions:/srv/captions + healthcheck: + test: + - CMD + - bash + - -c + - "exec 3<>/dev/tcp/127.0.0.1/8080 && printf 'GET /api/health HTTP/1.0\\r\\nHost: app\\r\\n\\r\\n' >&3 && head -n 1 <&3 | grep -q ' 200 '" + interval: 30s + timeout: 5s + retries: 3 + start_period: 2m + start_interval: 5s + depends_on: + db: + condition: service_healthy + prepare: + condition: service_completed_successfully + migrate: + condition: service_completed_successfully + driver: + condition: service_started + + driver: + image: ghcr.io/newworldorg/carina:driver-v1.0.0 + logging: *logging + # 要求による停止は 0、それ以外は 70 で終わるので on-failure は使わない + restart: unless-stopped + privileged: true + # 録画中に止められると録画の終わり (上限 shutdownGraceHours、既定 6 時間) まで待つ。それより長くする + stop_grace_period: 7h + environment: + CARINA_ROLE: driver + CARINA_DRIVER_CONFIG: /etc/carina/driver.json + volumes: + - ./driver/driver.json:/etc/carina/driver.json:ro + - driver-run:/run/carina + - ${CARINA_RECORDINGS_DIR:?}:/srv/recordings + healthcheck: + test: ['CMD', '/opt/carina/driver/Carina.Driver', '--probe'] + interval: 30s + timeout: 15s + retries: 3 + start_period: 5m + start_interval: 10s + + vela: + image: ghcr.io/newworldorg/vela:v1.0.0 + logging: *logging + restart: unless-stopped + environment: + CARINA_API_BASE_URL: http://app:8080 + HOSTNAME: 0.0.0.0 + healthcheck: + test: + - CMD + - node + - -e + - "fetch('http://127.0.0.1:3000/login').then((r) => process.exit(r.ok ? 0 : 1), () => process.exit(1))" + interval: 30s + timeout: 5s + retries: 3 + start_period: 1m + start_interval: 5s + + proxy: + image: caddy:2.11.7-alpine + logging: *logging + restart: unless-stopped + environment: + PYXIS_SITE_ADDRESS: ${PYXIS_SITE_ADDRESS:-:80} + ports: + - '${PYXIS_HTTP_PORT:-80}:80' + - '${PYXIS_HTTPS_PORT:-443}:443' + - '${PYXIS_HTTPS_PORT:-443}:443/udp' + volumes: + - ./Caddyfile:/etc/caddy/Caddyfile:ro + - caddy-data:/data + - caddy-config:/config + depends_on: + app: + condition: service_started + vela: + condition: service_started + +networks: + default: + ipam: + config: + - subnet: 172.30.0.0/24 + +volumes: + driver-run: + thumbnails: + captions: + caddy-data: + caddy-config: diff --git a/driver/driver.json b/driver/driver.json new file mode 100644 index 0000000..ac386e7 --- /dev/null +++ b/driver/driver.json @@ -0,0 +1,13 @@ +{ + "socketPath": "/run/carina/driver.sock", + "socketGroupId": 10001, + "outputRoots": [{ "name": "primary", "path": "/srv/recordings" }], + "shutdownGraceHours": 6, + "tuner": { + "backend": "fake" + }, + "devices": [ + { "id": "fake-terrestrial", "kind": "terrestrial", "enabled": true }, + { "id": "fake-satellite", "kind": "satellite", "enabled": true } + ] +} diff --git a/driver/examples/driver-dvb.json b/driver/examples/driver-dvb.json new file mode 100644 index 0000000..df8652a --- /dev/null +++ b/driver/examples/driver-dvb.json @@ -0,0 +1,13 @@ +{ + "socketPath": "/run/carina/driver.sock", + "socketGroupId": 10001, + "outputRoots": [{ "name": "primary", "path": "/srv/recordings" }], + "shutdownGraceHours": 6, + "tuner": { + "backend": "dvb" + }, + "devices": [ + { "id": "adapter0.frontend0", "kind": "terrestrial", "devicePath": "/dev/dvb/adapter0/frontend0", "enabled": true }, + { "id": "adapter1.frontend0", "kind": "satellite", "devicePath": "/dev/dvb/adapter1/frontend0", "enabled": false } + ] +} diff --git a/driver/examples/driver-usb.json b/driver/examples/driver-usb.json new file mode 100644 index 0000000..6f59245 --- /dev/null +++ b/driver/examples/driver-usb.json @@ -0,0 +1,12 @@ +{ + "socketPath": "/run/carina/driver.sock", + "socketGroupId": 10001, + "outputRoots": [{ "name": "primary", "path": "/srv/recordings" }], + "shutdownGraceHours": 6, + "tuner": { + "backend": "usb" + }, + "devices": [ + { "id": "usb1-1", "kind": "terrestrial", "devicePath": "/dev/bus/usb/001/002", "enabled": true } + ] +} diff --git a/examples/gpu-intel.yml b/examples/gpu-intel.yml new file mode 100644 index 0000000..0af02c2 --- /dev/null +++ b/examples/gpu-intel.yml @@ -0,0 +1,7 @@ +# Intel の GPU (VAAPI) を app に渡す。 +# 描画ノードの所有グループは、イメージの entrypoint が装置から読んで app に足すので group_add は要らない。 +# 渡しただけでは変換はソフトウェアのままなので、.env で CARINA_ENCODINGS_PREFER と CARINA_TRANSCODING_PREFER を Vaapi にする +services: + app: + devices: + - /dev/dri:/dev/dri diff --git a/examples/tuner-dvb.yml b/examples/tuner-dvb.yml new file mode 100644 index 0000000..3f22487 --- /dev/null +++ b/examples/tuner-dvb.yml @@ -0,0 +1,9 @@ +# Linux DVB のチューナーと B-CAS カード (ホストの pcscd) を driver に渡す。 +# チューナーが増えたら devices の行を足し、driver/driver.json の devices にも同じ装置を書く +services: + driver: + devices: + - /dev/dvb/adapter0:/dev/dvb/adapter0 + volumes: + # pcscd は起動し直すたびにソケットを作り直すので、ファイルではなくディレクトリごと渡す + - /run/pcscd:/run/pcscd:ro diff --git a/examples/tuner-usb.yml b/examples/tuner-usb.yml new file mode 100644 index 0000000..31a75db --- /dev/null +++ b/examples/tuner-usb.yml @@ -0,0 +1,6 @@ +# USB の地上デジタルチューナー (CECH-ZD1) を driver に渡す。 +# 挿し直すとノードの番号が変わるので、ノードではなく /dev/bus/usb をディレクトリごと渡す +services: + driver: + devices: + - /dev/bus/usb:/dev/bus/usb From 2d6450f8791b9eb065a8b5e15e1f43abd8f073e9 Mon Sep 17 00:00:00 2001 From: fyui001 Date: Thu, 5 Nov 2026 01:19:44 +0900 Subject: [PATCH 2/6] =?UTF-8?q?README=20=E3=81=AF=E3=81=93=E3=81=AE=20PR?= =?UTF-8?q?=20=E3=81=A7=E3=81=AF=E5=A4=89=E3=81=88=E3=81=9A=E3=80=81?= =?UTF-8?q?=E9=AA=A8=E7=B5=84=E3=81=BF=E3=81=AE=E3=81=BE=E3=81=BE=E3=81=AB?= =?UTF-8?q?=E6=88=BB=E3=81=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5.1 --- README.md | 195 +----------------------------------------------------- 1 file changed, 2 insertions(+), 193 deletions(-) diff --git a/README.md b/README.md index 4f498b0..b099466 100644 --- a/README.md +++ b/README.md @@ -10,27 +10,16 @@ Pyxis は、日本の地上デジタル放送向けの録画システムを立 | --- | --- | --- | | `driver` | `ghcr.io/newworldorg/carina` | 特権で動き、チューナーを占有して録画ファイルを書く | | `app` | `ghcr.io/newworldorg/carina` | 番組表・予約・ライブ・エンコード・ライブラリの HTTP API | -| `migrate` | `ghcr.io/newworldorg/carina` | スキーマを適用して終わる。`app` はこれが成功してから上がる | -| `prepare` | `ghcr.io/newworldorg/carina` | `app` が書く置き場の所有者を `app` の uid にそろえて終わる | | `vela` | `ghcr.io/newworldorg/vela` | ブラウザで開く画面 | | `db` | `postgres` | データベース | | `proxy` | `caddy` | 前段。`/api/*` を `app` に、それ以外を `vela` に振り分ける | `driver` と `app` は Unix ドメインソケットで通信する。 別のプロセスなので、`app` を入れ替えても進行中の録画は止まらない。 -録画ファイルを書くのは `driver` だけで、`app` は読み取り専用で参照する。 -`app` と `vela` は同じオリジンに置き、振り分けは前段の `proxy` が担う。 +`app` と `vela` は同じオリジンに置き、振り分けは前段の `proxy` が行う。 版は Pyxis のリリースタグで固定する。 -`latest` には頼らず、イメージの版は `compose.yml` に数字で書く。 - -``` -compose.yml 1 台に全部を立てる compose -.env.example .env の雛形 -Caddyfile 前段のプロキシの設定 -driver/ driver.json(合成のチューナー)と、DVB・USB のチューナーの見本 -examples/ compose に重ねる装置ごとのファイル -``` +`latest` には頼らず、イメージの版はファイルに数字で書く。 ## 必要なもの @@ -39,186 +28,6 @@ examples/ compose に重ねる装置ごとのファイル チューナーが無くても、合成のチューナーで起動だけは試せる。 -## 立て方 - -```bash -git clone https://github.com/NewWorldOrg/Pyxis.git -cd Pyxis -cp .env.example .env -``` - -`.env` にデータベースのパスワードと、データベース・録画・エンコード済みファイル・鍵を置くディレクトリを書く。 -どれも空のままでは起動しない。 -ディレクトリは無ければ作られ、`app` が書く置き場は `prepare` が所有者を uid 10001 に替える。 -中のファイルには触らない。 -録画の置き場は `driver` が root で書くので、所有者はそのままでよい。 - -```bash -docker compose up -d -``` - -`driver/driver.json` はそのままなら合成のチューナーで動く。 -実機のチューナーを使うときは、下の「装置の重ね方」のとおりに `driver.json` と重ねるファイルを用意してから上げる。 - -初回の起動で `app` がローカルアカウントを 1 つ作り、ユーザー名とパスワードを 1 度だけログに書く。 - -```bash -docker compose logs app | grep 'Sign in as' -``` - -ブラウザで `http://<この機械のアドレス>/` を開き、そのアカウントでログインしてからパスワードを替える。 - -`docker compose ps` の health は、`driver` では `--probe` の結果、`app` では `/api/health`、`vela` では `/login` の応答を表す。 -コンテナのログは json-file 形式で、1 ファイル 20 MB・5 世代までに抑える。 - -### ポートと公開のアドレス - -`proxy` はホストの 80 番と 443 番で待つ。 -ほかのものが使っているなら、`.env` の `PYXIS_HTTP_PORT` と `PYXIS_HTTPS_PORT` で別の番号にする。 - -LAN の中だけで使うなら `PYXIS_SITE_ADDRESS` は `:80` のままでよく、http で開く。 -公開のドメインがあるなら `PYXIS_SITE_ADDRESS=tv.example.com` のように書く。 -Caddy が証明書を取って https で開き、http は https へ転送する。 -証明書を取るには、そのドメインがこの機械を指し、インターネットから 80 番と 443 番が届く必要がある。 -ホストの番号を変えたときは、ルータで 80 番と 443 番をその番号へ転送する。 - -どちらの形でも、ブラウザから届くアドレスを `CARINA_PUBLIC_ORIGIN` に書く(`http://192.168.1.10` や `https://tv.example.com`)。 -ID プロバイダに登録する redirect URI の元になる。 - -`proxy` が `app` の前段にいるので、`app` は compose の網からの `X-Forwarded-*` を信じる。 -網の範囲は `compose.yml` の `networks` と `CARINA_KNOWN_NETWORKS` に同じ値(`172.30.0.0/24`)で書いてある。 -家の網と重なるなら、両方を同じ値で書き換える。 - -### 止め方 - -```bash -docker compose stop # 止める。データは残る -docker compose down # コンテナと網を消す。データは残る -``` - -`down -v` はボリュームごと消す。 -`driver` と `app` のソケットのほか、サムネイル・字幕・Caddy の証明書がボリュームにあるので、普段は使わない。 -`driver` は録画中に止められると、録画が終わるまで(上限 6 時間)待ってから止まる。 - -## 装置の重ね方 - -compose の `devices:` は変数で数を変えられないので、装置ごとのファイルを重ねる。 - -| ファイル | 渡すもの | -| --- | --- | -| `examples/tuner-dvb.yml` | `/dev/dvb/adapter0` と、ホストの pcscd の `/run/pcscd`(読み取り専用) | -| `examples/tuner-usb.yml` | `/dev/bus/usb` | -| `examples/gpu-intel.yml` | `/dev/dri` を `app` に | - -```bash -docker compose -f compose.yml -f examples/tuner-dvb.yml -f examples/gpu-intel.yml up -d -``` - -毎回書かずに済ませるなら、`.env` に `COMPOSE_FILE=compose.yml:examples/tuner-dvb.yml:examples/gpu-intel.yml` を書く。 - -### DVB のチューナー - -`driver/examples/driver-dvb.json` を `driver/driver.json` に写し、`devices` にチューナーを書く。 -アダプタが増えたら、`examples/tuner-dvb.yml` の `devices` にも同じアダプタを足す。 -B-CAS カードはホストの pcscd で読むので、ホストで pcscd を動かしておく。 -`driver.json` のキーは Carina の README の「設定」にある。 - -### USB のチューナー - -`driver/examples/driver-usb.json` を `driver/driver.json` に写す。 -チューナーの id はつないだポートから付く(`usb1-1` など)。 -`driver` は `/dev/bus/usb` を直に動かすので、ノードの番号が変わっても設定はそのままでよい。 - -### Intel の GPU - -`examples/gpu-intel.yml` を重ねると `/dev/dri` が `app` に渡る。 -描画ノードの所有グループは、イメージの entrypoint が装置から読んで `app` に足すので、`group_add` は要らない。 -渡しただけでは変換はソフトウェアのままなので、`.env` で `CARINA_ENCODINGS_PREFER` と `CARINA_TRANSCODING_PREFER` を `Vaapi` にする。 -描画ノードが `/dev/dri/renderD128` でなければ `CARINA_RENDER_NODE` で指す。 - -## 移行 - -現行の録画システムから録画を引き継ぐなら、`CARINA_RECORDINGS_DIR` を移行元と同じマウントの下に置く。 -録画はハードリンクで移すので、マウントをまたぐと 1 本も移せない。 -手順は Carina の README の「移行」にある。 - -## 更新 - -Pyxis のリリースタグが、イメージの版の組み合わせを固定する。 -更新は、タグを取り出してから `app` を止め、スキーマを当て、上げ直す順で進める。 - -```bash -git fetch --tags -git checkout vX.Y.Z -docker compose pull -docker compose stop app -docker compose up -d -``` - -`up -d` は、古い `app` が止まった状態で `migrate` を走らせ、成功してから新しい `app` を上げる。 -列や表を消す migration があっても、動いている古い `app` を壊さない。 -`up -d` が作り直すのは定義の変わったサービスだけなので、`driver` のタグが前と同じなら `driver` は動いたままで、録画は止まらない。 - -`driver` のタグが変わる更新では、`up -d` が `driver` も作り直す。 -`driver` は録画が終わるまで止まらないので、録画・視聴・直近の予約が無い時間に当てる。 -`driver` だけを後回しにするなら、次のとおり `app` と画面だけを入れ替える。 - -```bash -docker compose stop app -docker compose run --rm migrate -docker compose up -d --no-deps app vela proxy -``` - -更新の前に、データベースと鍵の控えを取っておく。 - -```bash -docker compose exec db pg_dump -U carina carina > carina-$(date +%Y%m%d).sql -cp -a "$CARINA_KEYS_DIR" keys-$(date +%Y%m%d) -``` - -## 戻し方 - -`migrate` は進める方向にしか当てないので、戻すときはデータベースを更新前の控えから戻す。 - -```bash -git checkout vX.Y.Z # 戻す先のタグ -docker compose stop app -docker compose exec -T db psql -U carina -d postgres -c 'DROP DATABASE carina' -c 'CREATE DATABASE carina OWNER carina' -docker compose exec -T db psql -U carina -d carina < carina-YYYYMMDD.sql -docker compose up -d -``` - -鍵の置き場は更新で変わらないので、控えから戻すのは中身を失ったときだけでよい。 -録画とエンコード済みファイルは戻さない。 - -## 自前のプロキシを使う - -同梱の `proxy` の代わりに自前のプロキシを使うなら、`compose.override.yml`(コミットしない)で `proxy` を外し、`app` と `vela` のポートをこの機械の内側だけに開く。 - -```yaml -services: - proxy: - profiles: [unused] - app: - ports: - - '127.0.0.1:8080:8080' - vela: - ports: - - '127.0.0.1:3000:3000' -``` - -自前のプロキシは、同じオリジンで `/api/*` を `app` に、それ以外を `vela` に送る。 -WebSocket(`/api/live/ws`)と SSE(`/api/events`)は切らずにバッファせず流し、`Range` ヘッダはそのまま通す。 -アクセスログを取るならパスを残さない。 -外部プレイヤーの URL はパスに再生用のトークンを含む。 -プロキシがこの機械の上で動くなら、`app` には compose の網のアドレスから届くので、`CARINA_KNOWN_NETWORKS` はそのままでよい。 - -## 版の表 - -| Pyxis | carina(app・migrate) | carina(driver) | vela | postgres | caddy | -| --- | --- | --- | --- | --- | --- | -| master | `app-v1.0.0` | `driver-v1.0.0` | `v1.0.0` | `18.6-alpine` | `2.11.7-alpine` | - ## ライセンス ライセンスは AGPL-3.0-only で、著作権者は NewWorldOrg である。 From 583d3b2abb498d7a274ca20c84264f6b88b05523 Mon Sep 17 00:00:00 2001 From: fyui001 Date: Thu, 5 Nov 2026 01:30:05 +0900 Subject: [PATCH 3/6] =?UTF-8?q?README=20=E3=81=AB=E3=80=81=E7=94=A8?= =?UTF-8?q?=E6=84=8F=E3=81=99=E3=82=8B=E3=82=82=E3=81=AE=E3=83=BB=E7=AB=8B?= =?UTF-8?q?=E3=81=A6=E3=82=8B=E6=89=8B=E9=A0=86=E3=83=BB=E8=A8=AD=E5=AE=9A?= =?UTF-8?q?=E3=83=BB=E6=9B=B4=E6=96=B0=E3=81=A8=E6=88=BB=E3=81=97=E6=96=B9?= =?UTF-8?q?=E3=83=BB=E7=89=88=E3=81=AE=E8=A1=A8=E3=82=92=E9=80=9A=E3=81=97?= =?UTF-8?q?=E3=81=A6=E6=9B=B8=E3=81=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5.1 --- README.md | 239 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 215 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index b099466..2074665 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,229 @@ # Pyxis -Pyxis は、日本の地上デジタル放送向けの録画システムを立てるための配布用リポジトリである。 -バックエンドの [Carina](https://github.com/NewWorldOrg/Carina) と画面の [Vela](https://github.com/NewWorldOrg/Vela) を、確かめた版の組み合わせのまま 1 回の操作で立てる。 -イメージは作らず、置いてあるのは設定と版の固定だけである。 +日本のテレビ放送(地上デジタルと BS・CS)を録画して観るサーバを、Docker の機械 1 台に立てる。 +中身は [Carina](https://github.com/NewWorldOrg/Carina)(録画とライブと API)と [Vela](https://github.com/NewWorldOrg/Vela)(画面)で、Pyxis はこの 2 つを動く組み合わせの版で固定して配る。 -## 構成 +## 用意するもの -| 役割 | イメージ | 中身 | +- Docker 25 以上と Docker Compose v2 が入った Linux の機械。CPU は amd64 +- 録画を置くディスク。地上波は 1 時間で 7 GB ほど増える +- チューナー。PCIe や USB の Linux DVB 対応のもの(`/dev/dvb/adapterN` が見えるもの)か、USB の地上デジタルチューナー CECH-ZD1 +- B-CAS カードとカードリーダ、ホストで動く pcscd。DVB のチューナーでスクランブルを解くのに使う +- Intel の GPU(任意)。録画をあとから H.265 や AV1 にするときと、ライブや再生をその場で変換するときに使う + +チューナーもカードも無くても、合成のチューナーで一式を起動して画面を見るところまでは試せる。 + +## 立てる + +1. このリポジトリを取り、`.env.example` を `.env` に写す。 + + ```bash + git clone https://github.com/NewWorldOrg/Pyxis.git + cd Pyxis + cp .env.example .env + ``` + +2. `.env` を開き、データベースのパスワードと 4 つのディレクトリを書く。 + + ```bash + mkdir -p /srv/tv/{db,recordings,encodes,keys} + ``` + + | 変数 | 書くもの | + | --- | --- | + | `POSTGRES_PASSWORD` | データベースのパスワード。`;` は使えない | + | `CARINA_DB_DIR` | データベースのデータを置くディレクトリ | + | `CARINA_RECORDINGS_DIR` | 録画を置くディレクトリ | + | `CARINA_ENCODES_DIR` | エンコード済みのファイルを置くディレクトリ | + | `CARINA_KEYS_DIR` | 鍵を置くディレクトリ。データベースとは別の場所にする | + + ディレクトリはどれも空でよい。 + 所有者は起動のたびに `prepare` が合わせるので、手で `chown` しなくてよい。 + +3. チューナーを書く。 + `driver/driver.json` は合成のチューナーの設定なので、実機に合わせて書き換える。 + `driver/examples/` に DVB と USB の見本がある。 + + ```bash + cp driver/examples/driver-dvb.json driver/driver.json + ``` + + `devices` に、チューナーごとに `id`・`kind`(`terrestrial` か `satellite`)・`devicePath` を書く。 + DVB なら `/dev/dvb/adapter0/frontend0` のように frontend までを書く。 + 使わないチューナーは `enabled` を `false` にする。 + ほかのキーの意味は Carina の README の `driver` の節にある。 + +4. 装置を渡して起動する。 + compose の装置の指定は数を変えられないので、装置ごとのファイルを `-f` で重ねる。 + + ```bash + docker compose -f compose.yml -f examples/tuner-dvb.yml up -d + ``` + + | 重ねるファイル | 渡すもの | + | --- | --- | + | `examples/tuner-dvb.yml` | `/dev/dvb/adapter0` と、ホストの pcscd(`/run/pcscd`) | + | `examples/tuner-usb.yml` | `/dev/bus/usb`(CECH-ZD1 用) | + | `examples/gpu-intel.yml` | `/dev/dri`(Intel の GPU) | + + チューナーが 2 本以上なら `examples/tuner-dvb.yml` の `devices` に行を足す。 + GPU も使うなら `-f examples/gpu-intel.yml` を続けて書く。 + 毎回書かないなら、`.env` に `COMPOSE_FILE=compose.yml:examples/tuner-dvb.yml:examples/gpu-intel.yml` と書けば `docker compose up -d` だけで済む。 + 何も重ねなければ、合成のチューナーで起動する。 + +5. ブラウザで `http://<機械のアドレス>/` を開く。 + 最初の起動で `app` がローカルアカウントを作り、ユーザー名とパスワードをログに 1 度だけ書く。 + + ```bash + docker compose logs app | grep 'Sign in as' + ``` + + そのアカウントでログインし、設定の画面でパスワードを変える。 + チャンネルのスキャンと受信の確認は、画面の設定にある。 + +## 設定 + +`.env` の変数は次のとおり。 +空にできないものは、空のまま `up` すると compose がその名前を挙げて止まる。 + +| 変数 | 用途 | 既定 | | --- | --- | --- | -| `driver` | `ghcr.io/newworldorg/carina` | 特権で動き、チューナーを占有して録画ファイルを書く | -| `app` | `ghcr.io/newworldorg/carina` | 番組表・予約・ライブ・エンコード・ライブラリの HTTP API | -| `vela` | `ghcr.io/newworldorg/vela` | ブラウザで開く画面 | -| `db` | `postgres` | データベース | -| `proxy` | `caddy` | 前段。`/api/*` を `app` に、それ以外を `vela` に振り分ける | +| `POSTGRES_PASSWORD` | データベースのパスワード | 無し。必須 | +| `CARINA_DB_DIR` | データベースのデータを置くホスト側のディレクトリ | 無し。必須 | +| `CARINA_RECORDINGS_DIR` | 録画を置くホスト側のディレクトリ。`driver` が書き、`app` は読むだけ | 無し。必須 | +| `CARINA_ENCODES_DIR` | エンコード済みのファイルを置くホスト側のディレクトリ | 無し。必須 | +| `CARINA_KEYS_DIR` | OIDC の client secret を暗号化する鍵を置くホスト側のディレクトリ | 無し。必須 | +| `CARINA_PUBLIC_ORIGIN` | ブラウザから届くアドレス(`https://tv.example.com`)。OIDC でサインインするときに要る | 空 | +| `PYXIS_SITE_ADDRESS` | 前段の Caddy が開くサイトのアドレス。ドメインを書くと証明書を取って https で開く | `:80` | +| `PYXIS_HTTP_PORT` | ホストで開く http のポート | `80` | +| `PYXIS_HTTPS_PORT` | ホストで開く https のポート(TCP と UDP) | `443` | +| `CARINA_ENCODINGS_PREFER` | あとからのエンコードに使う変換器。`Software` か `Vaapi` | `Software` | +| `CARINA_TRANSCODING_PREFER` | ライブと再生のその場の変換に使う変換器。`Software` か `Vaapi` | `Software` | +| `CARINA_RENDER_NODE` | VAAPI で使う GPU のノード | `/dev/dri/renderD128` | + +番組表を集める間隔や品質の閾値のような細かい設定は、Carina の README の `app` の節にある。 +足すなら `compose.override.yml` を置いて `app` の `environment` に書く(`.gitignore` 済み)。 + +### GPU + +`examples/gpu-intel.yml` を重ねただけでは、変換はソフトウェアのままになる。 +GPU で変換させるには、`.env` の `CARINA_ENCODINGS_PREFER` と `CARINA_TRANSCODING_PREFER` を `Vaapi` にする。 +GPU が 2 枚あるときは `CARINA_RENDER_NODE` で使うノードを選ぶ。 + +### 外から開く + +家の外から開くなら、ドメインをこの機械に向け、80 番と 443 番を通してから `.env` に書く。 + +``` +PYXIS_SITE_ADDRESS=tv.example.com +CARINA_PUBLIC_ORIGIN=https://tv.example.com +``` + +Caddy が Let's Encrypt から証明書を取り、http は https に送る。 +`CARINA_PUBLIC_ORIGIN` は OIDC の redirect URI の元になるので、ブラウザが使うアドレスと同じにする。 + +### 80 番と 443 番がふさがっているとき + +この機械でほかのものが 80 番や 443 番を使っているなら、`.env` の `PYXIS_HTTP_PORT` と `PYXIS_HTTPS_PORT` を別の番号にする。 +ドメインで https にするときは、ルータの 80 番と 443 番をその番号に向ける。 + +### 前段を自分で持つとき + +前段のプロキシを自分で持つなら、`proxy` を外して `app` と `vela` を直接受ける。 + +```yaml +# compose.override.yml +services: + proxy: + profiles: [unused] + app: + ports: ['127.0.0.1:8080:8080'] + vela: + ports: ['127.0.0.1:3000:3000'] +``` + +自分の前段では次を守る。 + +- `/api/*` を `app` の 8080 番に、それ以外を `vela` の 3000 番に送り、両方を同じオリジンで開く。オリジンが分かれると Cookie が届かない +- `/api/events` はサーバから流れ続ける応答なので、溜めずに流す +- `/api/live/ws` は WebSocket なので、切らずに通す +- `Range` ヘッダをそのまま通す。再生とシークに使う +- アクセスログを取るなら、パスを残さない。外部プレイヤー向けの URL はパスに再生用のトークンを含む + +同梱の `Caddyfile` がこの 5 つをしている。 +前段を自分で持つときは、`CARINA_KNOWN_NETWORKS` に前段のアドレスの範囲を書く(`compose.override.yml` の `app` の `environment`)。 +書かないと `X-Forwarded-*` が読まれず、https で開いてもセッション Cookie に Secure が付かない。 + +## 更新する + +Pyxis の新しい版は、`compose.yml` のイメージの版を上げたリリースとして出る。 +更新は、古い `app` を止めてからデータベースのスキーマを当て、新しい `app` を上げる順にする。 +`app` が動いたままスキーマを当てると、列や表を消す変更で古い `app` が壊れる。 + +```bash +git fetch --tags +git checkout v0.2.0 # 上げたい版のタグ +docker compose pull +docker compose stop app +docker compose up -d +``` + +`up -d` は `migrate` を先に走らせ、成功してから `app` を上げる。 +`driver` は、`compose.yml` の `driver` の版が変わらなければ作り直されず、録画は止まらない。 + +`driver` の版が変わる更新は、録画中とライブの視聴中を避ける。 +録画中に `driver` を止めると、録画が終わるまで(上限は `driver.json` の `shutdownGraceHours`、既定 6 時間)待ってから止まり、そのあいだ新しい `driver` は上がらない。 +ライブの視聴は切れる。 + +`driver` の入れ替えを後に回すなら、`driver` を除いて上げる。 + +```bash +docker compose stop app +docker compose run --rm migrate +docker compose up -d --no-deps app vela proxy +``` + +リリースの本文に、その版で `driver` が変わるかと、列や表を消すスキーマの変更があるかを書く。 + +## 戻す + +更新の前に、データベースと鍵の置き場の控えを取る。 + +```bash +docker compose exec db pg_dump -U carina -Fc carina > carina-$(date +%Y%m%d).dump +tar -C "$(grep ^CARINA_KEYS_DIR= .env | cut -d= -f2)" -cf keys-$(date +%Y%m%d).tar . +``` + +戻すときは、前の版に戻してからデータベースを控えから入れ直す。 + +```bash +docker compose stop app +git checkout v0.1.0 +docker compose exec -T db dropdb -U carina carina +docker compose exec -T db createdb -U carina -O carina carina +docker compose exec -T db pg_restore -U carina -d carina < carina-20260101.dump +docker compose up -d +``` -`driver` と `app` は Unix ドメインソケットで通信する。 -別のプロセスなので、`app` を入れ替えても進行中の録画は止まらない。 -`app` と `vela` は同じオリジンに置き、振り分けは前段の `proxy` が行う。 +スキーマが足すだけの変更だった版なら、データベースを入れ直さずに前の版の `app` を上げるだけで動く。 +どちらかはリリースの本文で分かる。 -版は Pyxis のリリースタグで固定する。 -`latest` には頼らず、イメージの版はファイルに数字で書く。 +録画のファイルはどの版でも同じ場所にあり、戻しても消えない。 -## 必要なもの +## 版 -- Docker と Docker Compose -- チューナー・カードリーダ・GPU のある Linux の機械 1 台 +| Pyxis | Carina app | Carina driver | Vela | PostgreSQL | Caddy | +| --- | --- | --- | --- | --- | --- | +| (未リリース) | v1.0.0 | v1.0.0 | v1.0.0 | 18.6 | 2.11.7 | -チューナーが無くても、合成のチューナーで起動だけは試せる。 +イメージの版は `compose.yml` に数字で書いてあり、`latest` は使わない。 +Pyxis の版は、この組み合わせで起動と録画と再生を確かめてから付ける。 ## ライセンス -ライセンスは AGPL-3.0-only で、著作権者は NewWorldOrg である。 -詳細は `LICENSE` にある。 +Pyxis は AGPL-3.0-only で、著作権者は NewWorldOrg。 +全文は `LICENSE` にある。 -Pyxis が置いているのは設定だけである。 -各イメージに同梱した他のソフトウェアとそのライセンスは、イメージの中の `/usr/share/doc/carina/` と `/usr/share/doc/vela/` にある。 -PostgreSQL と Caddy は公式のイメージをそのまま引く。 +Pyxis が持つのは設定だけで、ソフトウェアはイメージの側にある。 +各イメージに入っている他のソフトウェアとそのライセンスは、イメージの中の `/usr/share/doc/carina/` と `/usr/share/doc/vela/` にある。 +PostgreSQL と Caddy は公式のイメージをそのまま使う。 From 5f37b0f02f8947d1b2ce72c6277e580cdd390d12 Mon Sep 17 00:00:00 2001 From: fyui001 Date: Thu, 5 Nov 2026 01:51:23 +0900 Subject: [PATCH 4/6] =?UTF-8?q?README=20=E3=81=AE=E8=AA=AD=E7=82=B9?= =?UTF-8?q?=E3=81=A7=E3=81=A4=E3=81=AA=E3=81=84=E3=81=A0=E6=96=87=E3=82=92?= =?UTF-8?q?=E5=88=86=E3=81=91=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5.1 --- README.md | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 2074665..e21a99e 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,14 @@ # Pyxis 日本のテレビ放送(地上デジタルと BS・CS)を録画して観るサーバを、Docker の機械 1 台に立てる。 -中身は [Carina](https://github.com/NewWorldOrg/Carina)(録画とライブと API)と [Vela](https://github.com/NewWorldOrg/Vela)(画面)で、Pyxis はこの 2 つを動く組み合わせの版で固定して配る。 +中身は [Carina](https://github.com/NewWorldOrg/Carina)(録画とライブと API)と [Vela](https://github.com/NewWorldOrg/Vela)(画面)。 +Pyxis はこの 2 つを、動く組み合わせの版で固定して配る。 ## 用意するもの - Docker 25 以上と Docker Compose v2 が入った Linux の機械。CPU は amd64 - 録画を置くディスク。地上波は 1 時間で 7 GB ほど増える -- チューナー。PCIe や USB の Linux DVB 対応のもの(`/dev/dvb/adapterN` が見えるもの)か、USB の地上デジタルチューナー CECH-ZD1 +- チューナー。Linux DVB 対応のもの(`/dev/dvb/adapterN` が見える PCIe や USB のカード)か、USB の地上デジタルチューナー CECH-ZD1 - B-CAS カードとカードリーダ、ホストで動く pcscd。DVB のチューナーでスクランブルを解くのに使う - Intel の GPU(任意)。録画をあとから H.265 や AV1 にするときと、ライブや再生をその場で変換するときに使う @@ -48,7 +49,8 @@ cp driver/examples/driver-dvb.json driver/driver.json ``` - `devices` に、チューナーごとに `id`・`kind`(`terrestrial` か `satellite`)・`devicePath` を書く。 + `devices` にチューナーごとの `id`・`kind`・`devicePath` を書く。 + `kind` は `terrestrial` か `satellite`。 DVB なら `/dev/dvb/adapter0/frontend0` のように frontend までを書く。 使わないチューナーは `enabled` を `false` にする。 ほかのキーの意味は Carina の README の `driver` の節にある。 @@ -68,7 +70,8 @@ チューナーが 2 本以上なら `examples/tuner-dvb.yml` の `devices` に行を足す。 GPU も使うなら `-f examples/gpu-intel.yml` を続けて書く。 - 毎回書かないなら、`.env` に `COMPOSE_FILE=compose.yml:examples/tuner-dvb.yml:examples/gpu-intel.yml` と書けば `docker compose up -d` だけで済む。 + 毎回 `-f` を書かないなら、`.env` に `COMPOSE_FILE=compose.yml:examples/tuner-dvb.yml:examples/gpu-intel.yml` と書く。 + 以後は `docker compose up -d` だけで済む。 何も重ねなければ、合成のチューナーで起動する。 5. ブラウザで `http://<機械のアドレス>/` を開く。 @@ -112,7 +115,8 @@ GPU が 2 枚あるときは `CARINA_RENDER_NODE` で使うノードを選ぶ。 ### 外から開く -家の外から開くなら、ドメインをこの機械に向け、80 番と 443 番を通してから `.env` に書く。 +家の外から開くなら、ドメインをこの機械に向けて 80 番と 443 番を通す。 +そのうえで `.env` に書く。 ``` PYXIS_SITE_ADDRESS=tv.example.com @@ -144,20 +148,21 @@ services: 自分の前段では次を守る。 -- `/api/*` を `app` の 8080 番に、それ以外を `vela` の 3000 番に送り、両方を同じオリジンで開く。オリジンが分かれると Cookie が届かない +- `/api/*` を `app` の 8080 番に、それ以外を `vela` の 3000 番に送る。両方を同じオリジンで開く(分かれると Cookie が届かない) - `/api/events` はサーバから流れ続ける応答なので、溜めずに流す - `/api/live/ws` は WebSocket なので、切らずに通す - `Range` ヘッダをそのまま通す。再生とシークに使う - アクセスログを取るなら、パスを残さない。外部プレイヤー向けの URL はパスに再生用のトークンを含む 同梱の `Caddyfile` がこの 5 つをしている。 -前段を自分で持つときは、`CARINA_KNOWN_NETWORKS` に前段のアドレスの範囲を書く(`compose.override.yml` の `app` の `environment`)。 +前段を自分で持つときは、`compose.override.yml` の `app` の `environment` に `CARINA_KNOWN_NETWORKS` を足す。 +値は前段のアドレスの範囲。 書かないと `X-Forwarded-*` が読まれず、https で開いてもセッション Cookie に Secure が付かない。 ## 更新する Pyxis の新しい版は、`compose.yml` のイメージの版を上げたリリースとして出る。 -更新は、古い `app` を止めてからデータベースのスキーマを当て、新しい `app` を上げる順にする。 +更新の順は、古い `app` を止める、データベースのスキーマを当てる、新しい `app` を上げる。 `app` が動いたままスキーマを当てると、列や表を消す変更で古い `app` が壊れる。 ```bash @@ -169,10 +174,12 @@ docker compose up -d ``` `up -d` は `migrate` を先に走らせ、成功してから `app` を上げる。 -`driver` は、`compose.yml` の `driver` の版が変わらなければ作り直されず、録画は止まらない。 +`driver` は `compose.yml` の版が変わらなければ作り直されない。 +録画は止まらない。 `driver` の版が変わる更新は、録画中とライブの視聴中を避ける。 -録画中に `driver` を止めると、録画が終わるまで(上限は `driver.json` の `shutdownGraceHours`、既定 6 時間)待ってから止まり、そのあいだ新しい `driver` は上がらない。 +録画中に `driver` を止めると、録画が終わるまで待ってから止まる。 +待つ上限は `driver.json` の `shutdownGraceHours`(既定 6 時間)で、そのあいだ新しい `driver` は上がらない。 ライブの視聴は切れる。 `driver` の入れ替えを後に回すなら、`driver` を除いて上げる。 @@ -183,7 +190,7 @@ docker compose run --rm migrate docker compose up -d --no-deps app vela proxy ``` -リリースの本文に、その版で `driver` が変わるかと、列や表を消すスキーマの変更があるかを書く。 +その版で `driver` が変わるか、列や表を消すスキーマの変更があるかは、リリースの本文に書く。 ## 戻す From e9de931b3a3cd4a82efef4b037850c37aae83efb Mon Sep 17 00:00:00 2001 From: fyui001 Date: Thu, 5 Nov 2026 01:56:25 +0900 Subject: [PATCH 5/6] =?UTF-8?q?README=20=E3=82=92=E3=80=81=E5=90=8C?= =?UTF-8?q?=E3=81=98=E5=9E=8B=E3=81=AE=E6=96=87=E3=81=A8=E6=8B=AC=E5=BC=A7?= =?UTF-8?q?=E3=81=AE=E6=B3=A8=E9=87=88=E3=82=92=E6=B8=9B=E3=82=89=E3=81=97?= =?UTF-8?q?=E3=81=A6=E6=9B=B8=E3=81=8D=E7=9B=B4=E3=81=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5.1 --- README.md | 215 +++++++++++++++++++++++++----------------------------- 1 file changed, 101 insertions(+), 114 deletions(-) diff --git a/README.md b/README.md index e21a99e..6e21288 100644 --- a/README.md +++ b/README.md @@ -1,137 +1,129 @@ # Pyxis -日本のテレビ放送(地上デジタルと BS・CS)を録画して観るサーバを、Docker の機械 1 台に立てる。 -中身は [Carina](https://github.com/NewWorldOrg/Carina)(録画とライブと API)と [Vela](https://github.com/NewWorldOrg/Vela)(画面)。 -Pyxis はこの 2 つを、動く組み合わせの版で固定して配る。 +日本のテレビ放送を録画して観るサーバを、Docker の入った Linux 1 台に立てる。 +録画と配信は [Carina](https://github.com/NewWorldOrg/Carina)、画面は [Vela](https://github.com/NewWorldOrg/Vela) が受け持つ。 +Pyxis はその 2 つを、動作を確かめた版の組で固定して配る。 ## 用意するもの -- Docker 25 以上と Docker Compose v2 が入った Linux の機械。CPU は amd64 -- 録画を置くディスク。地上波は 1 時間で 7 GB ほど増える -- チューナー。Linux DVB 対応のもの(`/dev/dvb/adapterN` が見える PCIe や USB のカード)か、USB の地上デジタルチューナー CECH-ZD1 -- B-CAS カードとカードリーダ、ホストで動く pcscd。DVB のチューナーでスクランブルを解くのに使う -- Intel の GPU(任意)。録画をあとから H.265 や AV1 にするときと、ライブや再生をその場で変換するときに使う +- Docker 25 以上と Docker Compose v2。CPU は amd64 +- 録画を置くディスク。地上波の録画は 1 時間に 7 GB ほど +- チューナー。Linux DVB で `/dev/dvb/adapterN` として見える PCIe・USB のカードか、USB の地上デジタルチューナー CECH-ZD1 +- B-CAS カードとカードリーダ。ホストで pcscd を動かしておく +- Intel の GPU。無くても動く。録画を H.265 や AV1 にするときと、ライブや再生をその場で変換するときに使う -チューナーもカードも無くても、合成のチューナーで一式を起動して画面を見るところまでは試せる。 +チューナーもカードも無い機械でも、合成のチューナーで一式を起動して画面を開くところまでは試せる。 -## 立てる +## セットアップ -1. このリポジトリを取り、`.env.example` を `.env` に写す。 +リポジトリを取り、`.env.example` を `.env` に写す。 - ```bash - git clone https://github.com/NewWorldOrg/Pyxis.git - cd Pyxis - cp .env.example .env - ``` - -2. `.env` を開き、データベースのパスワードと 4 つのディレクトリを書く。 +```bash +git clone https://github.com/NewWorldOrg/Pyxis.git +cd Pyxis +cp .env.example .env +``` - ```bash - mkdir -p /srv/tv/{db,recordings,encodes,keys} - ``` +`.env` にデータベースのパスワードと、データを置く 4 つのディレクトリを書く。 +ディレクトリは空のままでよい。 - | 変数 | 書くもの | - | --- | --- | - | `POSTGRES_PASSWORD` | データベースのパスワード。`;` は使えない | - | `CARINA_DB_DIR` | データベースのデータを置くディレクトリ | - | `CARINA_RECORDINGS_DIR` | 録画を置くディレクトリ | - | `CARINA_ENCODES_DIR` | エンコード済みのファイルを置くディレクトリ | - | `CARINA_KEYS_DIR` | 鍵を置くディレクトリ。データベースとは別の場所にする | +```bash +mkdir -p /srv/tv/{db,recordings,encodes,keys} +``` - ディレクトリはどれも空でよい。 - 所有者は起動のたびに `prepare` が合わせるので、手で `chown` しなくてよい。 +| 変数 | 書くもの | +| --- | --- | +| `POSTGRES_PASSWORD` | データベースのパスワード。`;` は使えない | +| `CARINA_DB_DIR` | データベースのデータを置く場所 | +| `CARINA_RECORDINGS_DIR` | 録画を置く場所 | +| `CARINA_ENCODES_DIR` | エンコード済みのファイルを置く場所 | +| `CARINA_KEYS_DIR` | 鍵を置く場所。データベースとは別のディスクか別のバックアップの対象にする | -3. チューナーを書く。 - `driver/driver.json` は合成のチューナーの設定なので、実機に合わせて書き換える。 - `driver/examples/` に DVB と USB の見本がある。 +次に `driver/driver.json` を実機のチューナーに合わせる。 +置いてあるのは合成のチューナーの設定で、DVB と USB の見本が `driver/examples/` にある。 - ```bash - cp driver/examples/driver-dvb.json driver/driver.json - ``` +```bash +cp driver/examples/driver-dvb.json driver/driver.json +``` - `devices` にチューナーごとの `id`・`kind`・`devicePath` を書く。 - `kind` は `terrestrial` か `satellite`。 - DVB なら `/dev/dvb/adapter0/frontend0` のように frontend までを書く。 - 使わないチューナーは `enabled` を `false` にする。 - ほかのキーの意味は Carina の README の `driver` の節にある。 +`devices` にチューナーごとの `id`・`kind`・`devicePath` を書く。 +`kind` は `terrestrial` か `satellite`、`devicePath` は `/dev/dvb/adapter0/frontend0` のように frontend までのパスを書く。 +使わないチューナーは `enabled` を `false` にしておく。 +ほかのキーは Carina の README の `driver` の節に説明がある。 -4. 装置を渡して起動する。 - compose の装置の指定は数を変えられないので、装置ごとのファイルを `-f` で重ねる。 +装置はホストから渡す必要があり、compose の装置の指定は数を変えられないため、装置ごとのファイルを `-f` で重ねる。 - ```bash - docker compose -f compose.yml -f examples/tuner-dvb.yml up -d - ``` +```bash +docker compose -f compose.yml -f examples/tuner-dvb.yml up -d +``` - | 重ねるファイル | 渡すもの | - | --- | --- | - | `examples/tuner-dvb.yml` | `/dev/dvb/adapter0` と、ホストの pcscd(`/run/pcscd`) | - | `examples/tuner-usb.yml` | `/dev/bus/usb`(CECH-ZD1 用) | - | `examples/gpu-intel.yml` | `/dev/dri`(Intel の GPU) | +| ファイル | 渡すもの | +| --- | --- | +| `examples/tuner-dvb.yml` | `/dev/dvb/adapter0` と、ホストの pcscd の `/run/pcscd` | +| `examples/tuner-usb.yml` | `/dev/bus/usb`。CECH-ZD1 用 | +| `examples/gpu-intel.yml` | `/dev/dri`。Intel の GPU 用 | - チューナーが 2 本以上なら `examples/tuner-dvb.yml` の `devices` に行を足す。 - GPU も使うなら `-f examples/gpu-intel.yml` を続けて書く。 - 毎回 `-f` を書かないなら、`.env` に `COMPOSE_FILE=compose.yml:examples/tuner-dvb.yml:examples/gpu-intel.yml` と書く。 - 以後は `docker compose up -d` だけで済む。 - 何も重ねなければ、合成のチューナーで起動する。 +チューナーが 2 本以上あるときは、`examples/tuner-dvb.yml` の `devices` に行を足す。 +GPU も渡すときは `-f examples/gpu-intel.yml` を続ける。 +`.env` に `COMPOSE_FILE=compose.yml:examples/tuner-dvb.yml:examples/gpu-intel.yml` と書いておけば、以後は `docker compose up -d` だけで済む。 +何も重ねずに起動すると、合成のチューナーで動く。 -5. ブラウザで `http://<機械のアドレス>/` を開く。 - 最初の起動で `app` がローカルアカウントを作り、ユーザー名とパスワードをログに 1 度だけ書く。 +起動したら、ブラウザで `http://<機械のアドレス>/` を開く。 +最初の起動で `app` がローカルアカウントを作り、ユーザー名とパスワードをログに一度だけ書く。 - ```bash - docker compose logs app | grep 'Sign in as' - ``` +```bash +docker compose logs app | grep 'Sign in as' +``` - そのアカウントでログインし、設定の画面でパスワードを変える。 - チャンネルのスキャンと受信の確認は、画面の設定にある。 +そのアカウントでログインしたら、設定の画面でパスワードを変える。 +チャンネルのスキャンと受信の確認も設定の画面にある。 ## 設定 -`.env` の変数は次のとおり。 -空にできないものは、空のまま `up` すると compose がその名前を挙げて止まる。 +`.env` の変数は次の 12 個。 +空にできない 5 つを空のままにすると、compose は起動せずにその変数の名前を出す。 | 変数 | 用途 | 既定 | | --- | --- | --- | -| `POSTGRES_PASSWORD` | データベースのパスワード | 無し。必須 | -| `CARINA_DB_DIR` | データベースのデータを置くホスト側のディレクトリ | 無し。必須 | -| `CARINA_RECORDINGS_DIR` | 録画を置くホスト側のディレクトリ。`driver` が書き、`app` は読むだけ | 無し。必須 | -| `CARINA_ENCODES_DIR` | エンコード済みのファイルを置くホスト側のディレクトリ | 無し。必須 | -| `CARINA_KEYS_DIR` | OIDC の client secret を暗号化する鍵を置くホスト側のディレクトリ | 無し。必須 | -| `CARINA_PUBLIC_ORIGIN` | ブラウザから届くアドレス(`https://tv.example.com`)。OIDC でサインインするときに要る | 空 | +| `POSTGRES_PASSWORD` | データベースのパスワード | 必須 | +| `CARINA_DB_DIR` | データベースのデータを置くホスト側のディレクトリ | 必須 | +| `CARINA_RECORDINGS_DIR` | 録画を置くホスト側のディレクトリ。`driver` が書き、`app` は読むだけ | 必須 | +| `CARINA_ENCODES_DIR` | エンコード済みのファイルを置くホスト側のディレクトリ | 必須 | +| `CARINA_KEYS_DIR` | OIDC の client secret を暗号化する鍵を置くホスト側のディレクトリ | 必須 | +| `CARINA_PUBLIC_ORIGIN` | ブラウザから届くアドレス。OIDC でサインインするときに要る | 空 | | `PYXIS_SITE_ADDRESS` | 前段の Caddy が開くサイトのアドレス。ドメインを書くと証明書を取って https で開く | `:80` | | `PYXIS_HTTP_PORT` | ホストで開く http のポート | `80` | -| `PYXIS_HTTPS_PORT` | ホストで開く https のポート(TCP と UDP) | `443` | +| `PYXIS_HTTPS_PORT` | ホストで開く https のポート。TCP と UDP | `443` | | `CARINA_ENCODINGS_PREFER` | あとからのエンコードに使う変換器。`Software` か `Vaapi` | `Software` | | `CARINA_TRANSCODING_PREFER` | ライブと再生のその場の変換に使う変換器。`Software` か `Vaapi` | `Software` | | `CARINA_RENDER_NODE` | VAAPI で使う GPU のノード | `/dev/dri/renderD128` | -番組表を集める間隔や品質の閾値のような細かい設定は、Carina の README の `app` の節にある。 -足すなら `compose.override.yml` を置いて `app` の `environment` に書く(`.gitignore` 済み)。 +番組表を集める間隔や品質の閾値といった細かい設定は、Carina の README の `app` の節にある。 +変えたいものがあれば、`compose.override.yml` を置いて `app` の `environment` に書く。 +このファイルは `.gitignore` に入っている。 ### GPU -`examples/gpu-intel.yml` を重ねただけでは、変換はソフトウェアのままになる。 -GPU で変換させるには、`.env` の `CARINA_ENCODINGS_PREFER` と `CARINA_TRANSCODING_PREFER` を `Vaapi` にする。 -GPU が 2 枚あるときは `CARINA_RENDER_NODE` で使うノードを選ぶ。 +`examples/gpu-intel.yml` を重ねただけでは、変換はソフトウェアのまま動く。 +GPU で変換させるには `CARINA_ENCODINGS_PREFER` と `CARINA_TRANSCODING_PREFER` を `Vaapi` にする。 +GPU が 2 枚あるときは `CARINA_RENDER_NODE` で使う方を選ぶ。 ### 外から開く -家の外から開くなら、ドメインをこの機械に向けて 80 番と 443 番を通す。 -そのうえで `.env` に書く。 +家の外から開くには、ドメインをこの機械に向けてルータの 80 番と 443 番を通し、`.env` に 2 行を書く。 ``` PYXIS_SITE_ADDRESS=tv.example.com CARINA_PUBLIC_ORIGIN=https://tv.example.com ``` -Caddy が Let's Encrypt から証明書を取り、http は https に送る。 -`CARINA_PUBLIC_ORIGIN` は OIDC の redirect URI の元になるので、ブラウザが使うアドレスと同じにする。 - -### 80 番と 443 番がふさがっているとき +Caddy が Let's Encrypt から証明書を取り、http で来た要求は https へ送る。 +`CARINA_PUBLIC_ORIGIN` は OIDC の redirect URI の元になるため、ブラウザが使うアドレスと一致させる。 -この機械でほかのものが 80 番や 443 番を使っているなら、`.env` の `PYXIS_HTTP_PORT` と `PYXIS_HTTPS_PORT` を別の番号にする。 -ドメインで https にするときは、ルータの 80 番と 443 番をその番号に向ける。 +ホストの 80 番や 443 番をほかのものが使っているときは、`PYXIS_HTTP_PORT` と `PYXIS_HTTPS_PORT` を空いている番号に変える。 +ドメインで https にするなら、ルータ側の 80 番と 443 番をその番号へ向ける。 -### 前段を自分で持つとき +### 前段を自分で持つ 前段のプロキシを自分で持つなら、`proxy` を外して `app` と `vela` を直接受ける。 @@ -146,23 +138,21 @@ services: ports: ['127.0.0.1:3000:3000'] ``` -自分の前段では次を守る。 +その前段には、同梱の `Caddyfile` がしているのと同じ 5 つを持たせる。 -- `/api/*` を `app` の 8080 番に、それ以外を `vela` の 3000 番に送る。両方を同じオリジンで開く(分かれると Cookie が届かない) +- `/api/*` を `app` の 8080 番へ、それ以外を `vela` の 3000 番へ送り、両方を同じオリジンで開く。オリジンが分かれるとブラウザが Cookie を送らない - `/api/events` はサーバから流れ続ける応答なので、溜めずに流す - `/api/live/ws` は WebSocket なので、切らずに通す - `Range` ヘッダをそのまま通す。再生とシークに使う -- アクセスログを取るなら、パスを残さない。外部プレイヤー向けの URL はパスに再生用のトークンを含む +- アクセスログを取るなら、パスを残さない。外部プレイヤー向けの URL は、パスに再生用のトークンを含む -同梱の `Caddyfile` がこの 5 つをしている。 -前段を自分で持つときは、`compose.override.yml` の `app` の `environment` に `CARINA_KNOWN_NETWORKS` を足す。 -値は前段のアドレスの範囲。 -書かないと `X-Forwarded-*` が読まれず、https で開いてもセッション Cookie に Secure が付かない。 +あわせて `app` の `environment` に `CARINA_KNOWN_NETWORKS` を足し、前段のアドレスの範囲を書く。 +これが無いと `X-Forwarded-*` が読まれず、https で開いてもセッション Cookie に Secure が付かない。 -## 更新する +## 更新 Pyxis の新しい版は、`compose.yml` のイメージの版を上げたリリースとして出る。 -更新の順は、古い `app` を止める、データベースのスキーマを当てる、新しい `app` を上げる。 +古い `app` を止めてからデータベースのスキーマを当て、新しい `app` を上げる。 `app` が動いたままスキーマを当てると、列や表を消す変更で古い `app` が壊れる。 ```bash @@ -173,16 +163,15 @@ docker compose stop app docker compose up -d ``` -`up -d` は `migrate` を先に走らせ、成功してから `app` を上げる。 -`driver` は `compose.yml` の版が変わらなければ作り直されない。 -録画は止まらない。 +`up -d` は `migrate` を先に走らせ、それが成功してから `app` を上げる。 +`driver` の版が前と同じなら `driver` は作り直されず、録画は続く。 `driver` の版が変わる更新は、録画中とライブの視聴中を避ける。 -録画中に `driver` を止めると、録画が終わるまで待ってから止まる。 -待つ上限は `driver.json` の `shutdownGraceHours`(既定 6 時間)で、そのあいだ新しい `driver` は上がらない。 +録画中の `driver` は録画が終わるまで止まらず、その間は新しい `driver` も上がらない。 +待つ上限は `driver.json` の `shutdownGraceHours` で、既定は 6 時間。 ライブの視聴は切れる。 -`driver` の入れ替えを後に回すなら、`driver` を除いて上げる。 +`driver` の入れ替えだけを後に回すこともできる。 ```bash docker compose stop app @@ -190,18 +179,18 @@ docker compose run --rm migrate docker compose up -d --no-deps app vela proxy ``` -その版で `driver` が変わるか、列や表を消すスキーマの変更があるかは、リリースの本文に書く。 +`driver` が変わるかどうかと、列や表を消すスキーマの変更があるかどうかは、各リリースの本文にある。 -## 戻す +## 戻し方 -更新の前に、データベースと鍵の置き場の控えを取る。 +更新の前に、データベースと鍵の控えを取っておく。 ```bash docker compose exec db pg_dump -U carina -Fc carina > carina-$(date +%Y%m%d).dump tar -C "$(grep ^CARINA_KEYS_DIR= .env | cut -d= -f2)" -cf keys-$(date +%Y%m%d).tar . ``` -戻すときは、前の版に戻してからデータベースを控えから入れ直す。 +戻すときは、前の版のタグに戻してからデータベースを控えから入れ直す。 ```bash docker compose stop app @@ -212,25 +201,23 @@ docker compose exec -T db pg_restore -U carina -d carina < carina-20260101.dump docker compose up -d ``` -スキーマが足すだけの変更だった版なら、データベースを入れ直さずに前の版の `app` を上げるだけで動く。 -どちらかはリリースの本文で分かる。 - +スキーマの変更が足すだけだった版なら、データベースはそのままで前の版の `app` を上げれば動く。 +どちらかはリリースの本文に書いてある。 録画のファイルはどの版でも同じ場所にあり、戻しても消えない。 ## 版 | Pyxis | Carina app | Carina driver | Vela | PostgreSQL | Caddy | | --- | --- | --- | --- | --- | --- | -| (未リリース) | v1.0.0 | v1.0.0 | v1.0.0 | 18.6 | 2.11.7 | +| 未リリース | v1.0.0 | v1.0.0 | v1.0.0 | 18.6 | 2.11.7 | イメージの版は `compose.yml` に数字で書いてあり、`latest` は使わない。 -Pyxis の版は、この組み合わせで起動と録画と再生を確かめてから付ける。 +Pyxis の版は、その組で起動・録画・再生を確かめてから付ける。 ## ライセンス -Pyxis は AGPL-3.0-only で、著作権者は NewWorldOrg。 -全文は `LICENSE` にある。 +AGPL-3.0-only。著作権者は NewWorldOrg で、全文は `LICENSE` にある。 -Pyxis が持つのは設定だけで、ソフトウェアはイメージの側にある。 -各イメージに入っている他のソフトウェアとそのライセンスは、イメージの中の `/usr/share/doc/carina/` と `/usr/share/doc/vela/` にある。 +Pyxis が持つのは設定だけで、ソフトウェアは各イメージの側にある。 +イメージに同梱した他のソフトウェアとそのライセンスは、イメージの中の `/usr/share/doc/carina/` と `/usr/share/doc/vela/` で読める。 PostgreSQL と Caddy は公式のイメージをそのまま使う。 From 797738e0943b2fa97aa2bc6e6fe8a161715c5ccf Mon Sep 17 00:00:00 2001 From: fyui001 Date: Thu, 5 Nov 2026 02:18:06 +0900 Subject: [PATCH 6/6] =?UTF-8?q?README=20=E3=81=AB=E3=80=81=E3=81=A7?= =?UTF-8?q?=E3=81=8D=E3=82=8B=E3=81=93=E3=81=A8=E3=83=BB=E6=A7=8B=E6=88=90?= =?UTF-8?q?=E3=83=BB=E5=9B=B0=E3=81=A3=E3=81=9F=E3=81=A8=E3=81=8D=E3=82=92?= =?UTF-8?q?=E8=B6=B3=E3=81=97=E3=80=81=E6=96=87=E3=81=94=E3=81=A8=E3=81=AB?= =?UTF-8?q?=E6=94=B9=E8=A1=8C=E3=81=97=E3=81=A6=E8=A1=A8=E7=A4=BA=E3=81=95?= =?UTF-8?q?=E3=81=9B=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5.1 --- README.md | 105 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 74 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 6e21288..03ab121 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,34 @@ # Pyxis -日本のテレビ放送を録画して観るサーバを、Docker の入った Linux 1 台に立てる。 -録画と配信は [Carina](https://github.com/NewWorldOrg/Carina)、画面は [Vela](https://github.com/NewWorldOrg/Vela) が受け持つ。 +日本のテレビ放送を録画して観るサーバを、Docker の入った Linux 1 台に立てる。\ +録画と配信は [Carina](https://github.com/NewWorldOrg/Carina)、画面は [Vela](https://github.com/NewWorldOrg/Vela) が受け持つ。\ Pyxis はその 2 つを、動作を確かめた版の組で固定して配る。 +立てると、ブラウザから次のことをする。 + +- 番組表を見て予約する。キーワードや曜日の条件で自動で予約するルールも作れる +- ライブを観る。録画した番組を再生し、字幕を出す +- 録画を H.264・H.265・AV1 にエンコードする。CM・OP・ED は自動で検出して飛ばせる +- 録画ごとのドロップや受信の状態を見て、壊れた録画を探す +- B-CAS カードを 1 枚だけ持つ家で、別の機械のチューナーにもカードを貸す + +## 構成 + +`docker compose ps` に出るサービスは 7 つ。 + +| サービス | イメージ | 役割 | +| --- | --- | --- | +| `driver` | Carina | 特権で動く。チューナーを占有し、スクランブルを解いて録画ファイルを書く | +| `app` | Carina | 番組表・予約・ライブ・エンコード・ライブラリの API。特権は持たない | +| `migrate` | Carina | 起動のたびにデータベースのスキーマを当てて終わる | +| `prepare` | Carina | `app` が書く置き場の所有者を合わせて終わる | +| `vela` | Vela | 画面 | +| `db` | PostgreSQL | データベース | +| `proxy` | Caddy | 前段。`/api/*` を `app` に、それ以外を `vela` に送る | + +`driver` と `app` は別のコンテナなので、`app` を入れ替えても録画は止まらない。\ +録画ファイルを書くのは `driver` だけで、`app` は読むだけ。 + ## 用意するもの - Docker 25 以上と Docker Compose v2。CPU は amd64 @@ -24,7 +49,7 @@ cd Pyxis cp .env.example .env ``` -`.env` にデータベースのパスワードと、データを置く 4 つのディレクトリを書く。 +`.env` にデータベースのパスワードと、データを置く 4 つのディレクトリを書く。\ ディレクトリは空のままでよい。 ```bash @@ -39,16 +64,16 @@ mkdir -p /srv/tv/{db,recordings,encodes,keys} | `CARINA_ENCODES_DIR` | エンコード済みのファイルを置く場所 | | `CARINA_KEYS_DIR` | 鍵を置く場所。データベースとは別のディスクか別のバックアップの対象にする | -次に `driver/driver.json` を実機のチューナーに合わせる。 +次に `driver/driver.json` を実機のチューナーに合わせる。\ 置いてあるのは合成のチューナーの設定で、DVB と USB の見本が `driver/examples/` にある。 ```bash cp driver/examples/driver-dvb.json driver/driver.json ``` -`devices` にチューナーごとの `id`・`kind`・`devicePath` を書く。 -`kind` は `terrestrial` か `satellite`、`devicePath` は `/dev/dvb/adapter0/frontend0` のように frontend までのパスを書く。 -使わないチューナーは `enabled` を `false` にしておく。 +`devices` にチューナーごとの `id`・`kind`・`devicePath` を書く。\ +`kind` は `terrestrial` か `satellite`、`devicePath` は `/dev/dvb/adapter0/frontend0` のように frontend までのパスを書く。\ +使わないチューナーは `enabled` を `false` にしておく。\ ほかのキーは Carina の README の `driver` の節に説明がある。 装置はホストから渡す必要があり、compose の装置の指定は数を変えられないため、装置ごとのファイルを `-f` で重ねる。 @@ -63,24 +88,24 @@ docker compose -f compose.yml -f examples/tuner-dvb.yml up -d | `examples/tuner-usb.yml` | `/dev/bus/usb`。CECH-ZD1 用 | | `examples/gpu-intel.yml` | `/dev/dri`。Intel の GPU 用 | -チューナーが 2 本以上あるときは、`examples/tuner-dvb.yml` の `devices` に行を足す。 -GPU も渡すときは `-f examples/gpu-intel.yml` を続ける。 -`.env` に `COMPOSE_FILE=compose.yml:examples/tuner-dvb.yml:examples/gpu-intel.yml` と書いておけば、以後は `docker compose up -d` だけで済む。 +チューナーが 2 本以上あるときは、`examples/tuner-dvb.yml` の `devices` に行を足す。\ +GPU も渡すときは `-f examples/gpu-intel.yml` を続ける。\ +`.env` に `COMPOSE_FILE=compose.yml:examples/tuner-dvb.yml:examples/gpu-intel.yml` と書いておけば、以後は `docker compose up -d` だけで済む。\ 何も重ねずに起動すると、合成のチューナーで動く。 -起動したら、ブラウザで `http://<機械のアドレス>/` を開く。 +起動したら、ブラウザで `http://<機械のアドレス>/` を開く。\ 最初の起動で `app` がローカルアカウントを作り、ユーザー名とパスワードをログに一度だけ書く。 ```bash docker compose logs app | grep 'Sign in as' ``` -そのアカウントでログインしたら、設定の画面でパスワードを変える。 +そのアカウントでログインしたら、設定の画面でパスワードを変える。\ チャンネルのスキャンと受信の確認も設定の画面にある。 ## 設定 -`.env` の変数は次の 12 個。 +`.env` の変数は次の 12 個。\ 空にできない 5 つを空のままにすると、compose は起動せずにその変数の名前を出す。 | 変数 | 用途 | 既定 | @@ -98,14 +123,14 @@ docker compose logs app | grep 'Sign in as' | `CARINA_TRANSCODING_PREFER` | ライブと再生のその場の変換に使う変換器。`Software` か `Vaapi` | `Software` | | `CARINA_RENDER_NODE` | VAAPI で使う GPU のノード | `/dev/dri/renderD128` | -番組表を集める間隔や品質の閾値といった細かい設定は、Carina の README の `app` の節にある。 -変えたいものがあれば、`compose.override.yml` を置いて `app` の `environment` に書く。 +番組表を集める間隔や品質の閾値といった細かい設定は、Carina の README の `app` の節にある。\ +変えたいものがあれば、`compose.override.yml` を置いて `app` の `environment` に書く。\ このファイルは `.gitignore` に入っている。 ### GPU -`examples/gpu-intel.yml` を重ねただけでは、変換はソフトウェアのまま動く。 -GPU で変換させるには `CARINA_ENCODINGS_PREFER` と `CARINA_TRANSCODING_PREFER` を `Vaapi` にする。 +`examples/gpu-intel.yml` を重ねただけでは、変換はソフトウェアのまま動く。\ +GPU で変換させるには `CARINA_ENCODINGS_PREFER` と `CARINA_TRANSCODING_PREFER` を `Vaapi` にする。\ GPU が 2 枚あるときは `CARINA_RENDER_NODE` で使う方を選ぶ。 ### 外から開く @@ -117,10 +142,10 @@ PYXIS_SITE_ADDRESS=tv.example.com CARINA_PUBLIC_ORIGIN=https://tv.example.com ``` -Caddy が Let's Encrypt から証明書を取り、http で来た要求は https へ送る。 +Caddy が Let's Encrypt から証明書を取り、http で来た要求は https へ送る。\ `CARINA_PUBLIC_ORIGIN` は OIDC の redirect URI の元になるため、ブラウザが使うアドレスと一致させる。 -ホストの 80 番や 443 番をほかのものが使っているときは、`PYXIS_HTTP_PORT` と `PYXIS_HTTPS_PORT` を空いている番号に変える。 +ホストの 80 番や 443 番をほかのものが使っているときは、`PYXIS_HTTP_PORT` と `PYXIS_HTTPS_PORT` を空いている番号に変える。\ ドメインで https にするなら、ルータ側の 80 番と 443 番をその番号へ向ける。 ### 前段を自分で持つ @@ -146,13 +171,13 @@ services: - `Range` ヘッダをそのまま通す。再生とシークに使う - アクセスログを取るなら、パスを残さない。外部プレイヤー向けの URL は、パスに再生用のトークンを含む -あわせて `app` の `environment` に `CARINA_KNOWN_NETWORKS` を足し、前段のアドレスの範囲を書く。 +あわせて `app` の `environment` に `CARINA_KNOWN_NETWORKS` を足し、前段のアドレスの範囲を書く。\ これが無いと `X-Forwarded-*` が読まれず、https で開いてもセッション Cookie に Secure が付かない。 ## 更新 -Pyxis の新しい版は、`compose.yml` のイメージの版を上げたリリースとして出る。 -古い `app` を止めてからデータベースのスキーマを当て、新しい `app` を上げる。 +Pyxis の新しい版は、`compose.yml` のイメージの版を上げたリリースとして出る。\ +古い `app` を止めてからデータベースのスキーマを当て、新しい `app` を上げる。\ `app` が動いたままスキーマを当てると、列や表を消す変更で古い `app` が壊れる。 ```bash @@ -163,12 +188,12 @@ docker compose stop app docker compose up -d ``` -`up -d` は `migrate` を先に走らせ、それが成功してから `app` を上げる。 +`up -d` は `migrate` を先に走らせ、それが成功してから `app` を上げる。\ `driver` の版が前と同じなら `driver` は作り直されず、録画は続く。 -`driver` の版が変わる更新は、録画中とライブの視聴中を避ける。 -録画中の `driver` は録画が終わるまで止まらず、その間は新しい `driver` も上がらない。 -待つ上限は `driver.json` の `shutdownGraceHours` で、既定は 6 時間。 +`driver` の版が変わる更新は、録画中とライブの視聴中を避ける。\ +録画中の `driver` は録画が終わるまで止まらず、その間は新しい `driver` も上がらない。\ +待つ上限は `driver.json` の `shutdownGraceHours` で、既定は 6 時間。\ ライブの視聴は切れる。 `driver` の入れ替えだけを後に回すこともできる。 @@ -201,23 +226,41 @@ docker compose exec -T db pg_restore -U carina -d carina < carina-20260101.dump docker compose up -d ``` -スキーマの変更が足すだけだった版なら、データベースはそのままで前の版の `app` を上げれば動く。 -どちらかはリリースの本文に書いてある。 +スキーマの変更が足すだけだった版なら、データベースはそのままで前の版の `app` を上げれば動く。\ +どちらかはリリースの本文に書いてある。\ 録画のファイルはどの版でも同じ場所にあり、戻しても消えない。 +## 困ったとき + +```bash +docker compose ps +docker compose logs app +docker compose logs driver +``` + +| 症状 | 見るところ | +| --- | --- | +| `proxy` が上がらず `address already in use` と出る | ホストの 80 番か 443 番をほかのものが使っている。`PYXIS_HTTP_PORT` と `PYXIS_HTTPS_PORT` を変える | +| `up` が変数の名前を出して止まる | `.env` のその変数が空 | +| `driver` のログに `No smart-card reader is attached` と出る | ホストで pcscd が動いていないか、カードリーダがつながっていない。`systemctl status pcscd` で確かめる | +| `driver` のログに `could not be opened` と出る | `examples/tuner-dvb.yml` の `devices` と `driver/driver.json` の `devicePath` が合っていない。ホストで `ls /dev/dvb` を見る | +| 録画がスクランブルされたまま | カードリーダにカードが入っていないか、pcscd がカードを見失っている。pcscd を起動し直す | +| ログインのパスワードが分からない | `docker compose logs app \| grep 'Sign in as'`。2 回目以降の起動では書かれない | +| 画面は開くがライブも再生も始まらない | 前段を自分で持っているなら、`/api/live/ws` と `Range` が通っているか。`CARINA_KNOWN_NETWORKS` も確かめる | + ## 版 | Pyxis | Carina app | Carina driver | Vela | PostgreSQL | Caddy | | --- | --- | --- | --- | --- | --- | | 未リリース | v1.0.0 | v1.0.0 | v1.0.0 | 18.6 | 2.11.7 | -イメージの版は `compose.yml` に数字で書いてあり、`latest` は使わない。 +イメージの版は `compose.yml` に数字で書いてあり、`latest` は使わない。\ Pyxis の版は、その組で起動・録画・再生を確かめてから付ける。 ## ライセンス AGPL-3.0-only。著作権者は NewWorldOrg で、全文は `LICENSE` にある。 -Pyxis が持つのは設定だけで、ソフトウェアは各イメージの側にある。 -イメージに同梱した他のソフトウェアとそのライセンスは、イメージの中の `/usr/share/doc/carina/` と `/usr/share/doc/vela/` で読める。 +Pyxis が持つのは設定だけで、ソフトウェアは各イメージの側にある。\ +イメージに同梱した他のソフトウェアとそのライセンスは、イメージの中の `/usr/share/doc/carina/` と `/usr/share/doc/vela/` で読める。\ PostgreSQL と Caddy は公式のイメージをそのまま使う。