Files
tver/docs/deploy.md
T

18 KiB
Raw Blame History

title, tags
title tags
tver 本番配置手順
homelab
tver
deployment

tver 本番配置手順

[!info] 対象 Synology NAS ds923p(192.168.10.201)に PostgreSQL 17 と動画共有を用意し、dockage(192.168.10.155)の /opt/stacks/tver で web・worker を起動する。

[!danger] 秘匿値を記録しない PostgreSQL のパスワードや完成した DATABASE_URL は、この文書や Git 管理下のファイルに書かない。NAS と dockage の .env にだけ保存し、権限を 600 にする。

事前に確認する値

項目 値
NAS ds923p / 192.168.10.201
dockage 192.168.10.155
NAS のNFS共有 /volume1/video
dockage のNFSマウント /mnt/nas-tver
tverの動画保存先 NAS: /volume1/video/tver、dockage: /mnt/nas-tver/tver
PostgreSQL 公開ポート 15434
PostgreSQL ユーザー / DB tver / tver
アプリ配置先 /opt/stacks/tver
Gitea配置用ユーザー tver-deploy(リポジトリ読み取りのみ)

1. NASを準備する

1.1 NFSサービスを有効にする

  1. DSMの「コントロール パネル」→「ファイル サービス」→「NFS」でNFSサービスを有効にする。
  2. 共有フォルダ video が /volume1/video にあることを確認する。
  3. video の「編集」→「NFS 権限」で次を追加する。
設定 値
ホスト名またはIP 192.168.10.155
権限 読み書き
Squash すべてのユーザーをadminに割り当てる
セキュリティ sys
非同期を有効にする オン

[!success] adminへの割り当てを確認済み DSMのadminアカウントが無効な状態でも、上記設定でUID 65532からフォルダ作成・書き込み・読み込み・rename・削除に成功した。NAS上の所有者はUID 1024・GID 100だった。[実機] 2026-10-04 確認

1.2 PostgreSQLのファイルを用意する

  1. NAS上に次のディレクトリを事前作成する。

    /volume1/docker/tver-db/
    /volume1/docker/tver-db/pgdata/
    
  2. ../deploy/nas/tver-db/compose.yaml、.env.example、backup-db.sh、restore-test.shを/volume1/docker/tver-db/に配置する。ディレクトリはroot以外がファイルを削除・置換できないようにし、スクリプトはrootだけが実行できるようにする。

    chmod 755 /volume1/docker/tver-db
    chmod 700 /volume1/docker/tver-db/backup-db.sh
    chmod 700 /volume1/docker/tver-db/restore-test.sh
    ls -ld /volume1/docker/tver-db
    ls -l /volume1/docker/tver-db/backup-db.sh /volume1/docker/tver-db/restore-test.sh
    

    DSMのACLが付いている場合、chmod後のlsでパーミッション表示の末尾から+が外れ、ディレクトリがdrwxr-xr-x、スクリプトが-rwx------になったことを確認する。/volume1/dockerでは全員書き込み可のACLが確認されたため、スクリプトだけでなく配置ディレクトリにもchmod 755を適用する。

  3. .env.exampleを.envへコピーし、POSTGRES_PASSWORDに十分長いランダムな値を設定する。値には、URLへの埋め込みで追加のエスケープが不要な16進文字列を使える。

    cd /volume1/docker/tver-db
    umask 077
    cp .env.example .env
    # openssl rand -hex 64 で値を生成し、.env の POSTGRES_PASSWORD に設定する。
    chmod 600 .env
    

[!important] DBとアプリで同じパスワードを使う ここで設定した値を、後述するdockage側のDATABASE_URLにも使う。値そのものは文書やGitへ残さない。

1.3 Container ManagerでPostgreSQLを起動する

  1. Container Managerの「プロジェクト」で、/volume1/docker/tver-db/compose.yamlを使うtver-dbプロジェクトを作成する。
  2. 起動前に、他のプロジェクトとポート一覧を確認し、15434が空いていることを確認する。
  3. プロジェクトをビルド・起動する。
  4. tver-postgresがhealthyになることを確認する。
  5. コンテナログにdatabase system is ready to accept connectionsがあることを確認する。

初期化に失敗した場合は、先にpgdataの所有権・権限とログを確認する。空のDBを作り直す場合を除き、既存のpgdataを削除しない。

1.4 DSMファイアウォールを設定する

TCP 15434について、送信元192.168.10.155だけを許可するルールを追加する。他の送信元を許可する広いルールより前に配置し、LAN内の別ホストからDBへ接続できない構成にする。

DSMファイアウォールを設定した状態で、dockageからNASのTCP 15434へ到達できることを確認済みである。[実機] 2026-10-04 確認

2. dockageを準備する

2.1 NFSクライアントとマウント先を用意する

sudo apt update
sudo apt install -y nfs-common
sudo mkdir -p /mnt/nas-tver

/etc/fstabへ次の1行を追加する。_netdevでネットワーク後に扱い、nofailでNAS停止時もdockageの起動を妨げない。

192.168.10.201:/volume1/video /mnt/nas-tver nfs defaults,_netdev,nofail 0 0

設定を反映し、想定したNFS共有がマウントされたことを確認する。

sudo mount -a
findmnt --target /mnt/nas-tver

実機ではNFSv3でマウントできた。NFS権限を追加した直後の1回目はaccess deniedとなり、直後の2回目で成功した。NFS権限の反映待ちと考えられるため、設定内容が正しい場合は少し待ってsudo mount -aを再実行する。[実機] 2026-10-04 確認

2.2 workerのUIDで書き込みを確認する

既存のanime・drama・misc・personal・varietyとアプリのファイルを分け、admin相当の書き込み範囲を限定するため、tver専用サブフォルダを使う。Dockerグループのkazu634で、workerコンテナと同じUID・GID 65532:65532の確認用コンテナを実行し、フォルダ作成、書き込み、読み取り、rename、削除を確認する。sudoは不要である。

docker run --rm --user 65532:65532 -v /mnt/nas-tver/tver:/videos busybox:stable sh -c 'mkdir /videos/.tver-write-test && echo ok > /videos/.tver-write-test/f && cat /videos/.tver-write-test/f && mv /videos/.tver-write-test/f /videos/.tver-write-test/g && ls -ln /videos/.tver-write-test && rm -r /videos/.tver-write-test && echo cleanup-ok'

期待する結果は、内容のok、ls -lnで表示されるgの所有者1024 100、最後のcleanup-okである。確認後は確認用イメージを削除する。

docker rmi busybox:stable

/mnt/nas-tver/tverは作成済みで、上記のDockerコンテナによる操作は実機確認済みである。[実機] 2026-10-04 確認。別環境へ作り直す場合も、書き込み確認が成功するまでworkerを起動しない。

2.3 Giteaに配置用ユーザーを用意する

GiteaはREQUIRE_SIGNIN_VIEW = trueかつDISABLE_SSH = trueで動作している。匿名HTTPSのinfo/refsは401となり、SSHも使えないため、配置専用の読み取り資格情報を使う。[実機] 2026-10-04 確認

  1. Giteaの管理画面で、配置専用ユーザーtver-deployを作成する。
  2. kazu634/tverリポジトリの「設定」→「共同作業者」でtver-deployを追加し、権限を「読み取り」にする。
  3. tver-deployでGiteaへログインし、ユーザー設定の「アプリケーション」からrepository読み取り権限だけを持つトークンを作成する。
  4. トークンは作成時に安全な場所へ控え、この文書、シェル履歴、Git管理下のファイルへ実値を書かない。

2.4 dockageにclone用トークンを保存する

Dockerホストの管理ユーザーkazu634で次を実行する。トークンは非表示で読み取り、権限600の専用credentialファイルへ保存する。credential helperはGiteaのHTTPS URLにだけ設定する。

install -d -m 700 ~/.config/git
read -rs -p 'Gitea token: ' TOKEN; echo
umask 077
printf 'https://tver-deploy:%s@gitea.kazu634.com\n' "$TOKEN" > ~/.config/git/credentials-tver
unset TOKEN
git config --global credential.https://gitea.kazu634.com.helper 'store --file ~/.config/git/credentials-tver'
git clone https://tver-deploy@gitea.kazu634.com/kazu634/tver.git /opt/stacks/tver
cd /opt/stacks/tver

この手順で認証付きcloneに成功した。[実機] 2026-10-04 確認。トークンを失効・更新した場合は、同じ手順でcredentials-tverを作り直す。

2.5 アプリの環境変数を用意する

.env.exampleをGit管理外の.envへコピーし、DATABASE_URLへパスワード用の伏字を入れる。ファイルを600にしてから、パスワードを非表示で読み取り、伏字だけを置換する。

cd /opt/stacks/tver
umask 077
cp .env.example .env
sed -i 's|^DATABASE_URL=.*|DATABASE_URL=postgres://tver:__SET_PASSWORD__@192.168.10.201:15434/tver?sslmode=disable|' .env
chmod 600 .env
read -rs -p 'PostgreSQL password: ' DB_PASSWORD; echo
sed -i "s|__SET_PASSWORD__|${DB_PASSWORD}|" .env
unset DB_PASSWORD
if grep -q '__SET_PASSWORD__' .env; then echo 'DATABASE_URLのパスワードが未設定です' >&2; exit 1; fi

完成した.envは次の項目を持つ。DATABASE_URLの実値は表示したり、文書へ転記したりしない。

DATABASE_URL=<設定済み・表示しない>
VIDEO_HOST_DIR=/mnt/nas-tver/tver
VIDEO_DIR=/videos
WORKER_CONCURRENCY=1

[!warning] パスワードの文字種 この手順で使うPostgreSQLパスワードには、URLやsedの区切りに影響する@・:・/・?・#・%・|を含めない。NAS準備時に16進文字列で生成すれば、この制約を満たせる。

2.6 起動する

/opt/stacks/tverでwebとworkerをビルドして起動する。

cd /opt/stacks/tver
docker compose up -d --build

この方法で起動すると、Dockgeの画面にもtverスタックとして表示された。[実機] 2026-10-04 確認。Dockge上でweb・workerがともにrunningになり、再起動を繰り返していないことを確認する。

コマンドでも状態とログを確認できる。

cd /opt/stacks/tver
docker compose ps
docker compose logs --tail=100 web worker

3. 動作を確認する

  1. ヘルスチェックが200とokを返すことを確認する。

    curl --fail --show-error http://192.168.10.155:8080/healthz
    
  2. ブラウザでhttp://192.168.10.155:8080/を開き、新着一覧が表示されることを確認する。

  3. 番組またはエピソードを1件登録し、/queueで待ち行列、進捗、完了を確認する。

  4. /libraryから動画を再生し、シークできることを確認する。

  5. NASの/volume1/video/tverに規則どおりのMP4が作成されたことを確認する。

  6. tver-postgresのログとweb・workerのログに、接続失敗やマイグレーション失敗がないことを確認する。

[!success] 本番の動作確認結果 /healthzはHTTP 200、新着バラエティは98件を表示した。4分の本編ep5hcgopp2を登録すると20秒以内に完了し、NASのvideo/tver/<番組名>/へ92,340,468バイトの1080p H.264/AAC MP4が保存された。所有者はUID 1024・GID 100、.incomingは空だった。/libraryの表示・再生とHTTP Range 206も確認した。[実機] 2026-10-04 確認

[!note] Synologyの@eaDir NASが番組フォルダ内に所有者UID 0の@eaDirを作る。アプリのライブラリ一覧はファイルシステムの走査ではなくDBから作るため、表示や再生には影響しない。[実機] 2026-10-04 確認

4. 更新する

cd /opt/stacks/tver
git status --short
git pull --ff-only

git status --shortに追跡対象ファイルの変更がある場合は、内容を確認してから更新する。pull後、Dockgeでtverスタックを再ビルドして再配置し、#3. 動作を確認するを繰り返す。

yt-dlpを更新するときはDockerfile.workerのYT_DLP_VERSIONの既定値を変更してリポジトリへ反映し、workerイメージをキャッシュなしで再ビルドする。更新後はworkerログと少数の実動画で抽出・保存を確認する。

5. PostgreSQLをバックアップする

バックアップはNAS上のDSMタスクスケジューラから毎日実行する。tver-postgresコンテナ内のpg_dumpを使って/volume1/backups/tver/tver_<YYYYmmdd_HHMMSS>.sql.gzへ保存し、既定では14日より古い世代を削除する。

[!warning] バックアップ先も同じNASにある このバックアップはDBの誤操作や破損からの復元用であり、NAS本体の故障には備えられない。/volume1/backups/tver/はSynology Hyper Backupの対象にも含める。

5.1 DSMの日次タスクを登録する

DSMの「コントロール パネル」→「タスク スケジューラ」で「スケジュールされたタスク」→「ユーザー定義のスクリプト」を作成する。

設定 値
ユーザー root
実行周期 毎日
ユーザー定義のスクリプト bash /volume1/docker/tver-db/backup-db.sh

DOCKER、CONTAINER、BACKUP_DIR、KEEP_DAYS、DB_USER、DB_NAMEは環境変数で上書きできる。通常運用では既定値を使う。

[!success] NASで手動実行を確認済み DSMタスクスケジューラへrootの日次タスクを登録し、手動実行で2つのgzipダンプを作成した。復元確認も成功している。翌日以降の日次自動実行は未確認である。[実機] 2026-10-04 確認

[!todo] 日次自動実行を確認する 翌日以降にDSMタスクの実行履歴と新しいダンプを確認する。[要確認]

5.2 手動でバックアップを確認する

NASへSSH接続し、初回は手動で実行する。

sudo bash /volume1/docker/tver-db/backup-db.sh
LATEST=$(sudo find /volume1/backups/tver -name 'tver_*.sql.gz' -type f -print | sort | tail -n 1)
test -n "$LATEST"
sudo test -s "$LATEST"
sudo gzip -t "$LATEST"
sudo ls -lh "$LATEST"

スクリプトがOK:、保存先、バイト数を表示し、gzip -tが成功することを確認する。pg_dumpの失敗や空のダンプでは非0で終了し、完成ファイルを残さない。同じ秒の完成ファイルが既にある場合も非0で終了し、既存ファイルを上書きしない。同時実行時は同一ディレクトリ内のハードリンク作成に成功した一方だけが完成ファイルを公開する。

[!success] NASでのバックアップ結果 DSMタスクスケジューラから実行し、tver_20261004_182838.sql.gz(2,286バイト)とtver_20261004_183256.sql.gz(2,289バイト)を/volume1/backups/tver/へ作成した。ダンプはroot所有・権限600で、バックアップディレクトリはroot所有・drwxr-xr-xだった。[実機] 2026-10-04 確認

5.3 別DBへの復元を確認する

最新ダンプを本番DBへ戻す前に、同じコンテナ内の一時DBtver_restore_testへ復元する。

sudo bash /volume1/docker/tver-db/restore-test.sh
sudo tail -n 100 /volume1/backups/tver/restore-test.log

restore-test.shはrootで実行する。バックアップディレクトリとログがroot所有のため、一般ユーザーではrestore-test.logへの書き込みに失敗する。download_jobs、subscriptions、known_episodes、schema_migrationsの行数が本番DBと一致し、最後に「復元確認に成功」と表示されることを確認する。復元SQLの通常出力は抑制され、ログには開始、各表の結果、成功または失敗が残る。SQLエラーは標準エラーとしてログに残る。一時DBは成功・失敗のどちらでもEXIT trapで削除される。本番DBに対しては行数取得のSELECTだけを実行する。

[!success] NASでの復元確認結果 一般ユーザーkazu634での実行はrestore-test.logへ書き込めず、touch: Permission deniedで終了した。sudoで実行すると、download_jobs 1件、subscriptions 1件、known_episodes 1件、schema_migrations 3件が本番DBと一時DBで一致し、「復元確認に成功」と表示された。同じ結果がログに残り、一時DBも削除された。[実機] 2026-10-04 確認

[!note] 配置ディレクトリの権限変更後の状態 /volume1/docker/tver-dbをchmod 755にした後も、本番アプリの/queueと/subscriptionsはHTTP 200を返し、DBを読み取れた。PostgreSQLコンテナを再起動した後の動作は未確認である。[要確認]

5.4 障害時に本番DBへ復元する

[!danger] 本番DBを置き換える操作 先に#5.3 別DBへの復元を確認するを成功させ、復元対象のダンプを明示してから実行する。tver以外のDB名へ読み替えない。

  1. dockageの/opt/stacks/tverでwebとworkerを停止し、DBへの書き込みを止める。

    cd /opt/stacks/tver
    docker compose stop web worker
    
  2. NASで復元対象を指定し、存在・サイズ・gzip形式を確認する。

    DUMP=/volume1/backups/tver/tver_<YYYYmmdd_HHMMSS>.sql.gz
    test -s "$DUMP"
    gzip -t "$DUMP"
    
  3. NASで本番DBを作り直し、ダンプを復元する。

    docker exec tver-postgres dropdb --if-exists -U tver tver
    docker exec tver-postgres createdb -U tver tver
    gzip -dc "$DUMP" | docker exec -i tver-postgres psql -v ON_ERROR_STOP=1 -U tver -d tver
    
  4. dockageでwebとworkerを再開し、#3. 動作を確認するを実施する。

    cd /opt/stacks/tver
    docker compose start web worker