Files

32 KiB
Raw Permalink Blame History

title, tags, status
title tags status
tver の現状
homelab
tver
本番稼働中

tver の現状

[!info] このファイルの位置づけ tver プロジェクトの現状(決定事項・要件・構成・課題)を積み上げていく。運用規約は AGENTS を参照。

今の状態

  • Go の HTTP サーバー、TVer API クライアント、ドラマ・バラエティの新着表示、確認画面つきの動画複数削除、テスト、web・worker コンテナの定義がある
  • 本番稼働中。 dockageのweb・worker、NASのPostgreSQL 17、NFS動画保存を構成し、実動画の取得・再生・Range配信まで確認済み
  • リポジトリ: https://gitea.kazu634.com/kazu634/tver.git

目的

TVer(https://tver.jp/)で公開中の動画情報を取得し、選んだ動画を yt-dlp でダウンロードし、アプリ内で再生する Web アプリを作る。

決定事項

項目 決定
TVer 利用規約との関係 鑑賞を前提とするダウンロードのため、問題無しと判断(ユーザー判断)
利用者・公開範囲 自分のみ・LAN 内だけ。EC2 nginx では公開しない
保存先 NAS(ds923p)の /volume1/video/tver。既存のanime・drama・misc・personal・varietyと分け、admin相当の書き込み範囲を限定する。/volume1/videoをdockageの/mnt/nas-tverへNFSマウントし、.envはVIDEO_HOST_DIR=/mnt/nas-tver/tverとする
自動ダウンロード 作る(番組を登録し、新しい回を自動でダウンロードする)
再生 アプリの中で再生する
ダウンロード yt-dlp を使う
デプロイ 読み取り専用のGiteaユーザーtver-deployでdockage VMの/opt/stacks/tverへcloneし、docker compose up -d --buildで起動する。Dockgeにも同じスタックが表示される。更新はgit pull --ff-only後に再ビルドする
配置用のGit認証 tver-deployをtverの読み取り共同作業者とし、repository読み取りトークンをdockageの権限600の専用credentialファイルに保存する。トークンの実値はGitや文書へ残さない
開発言語 Go(web・worker とも)。yt-dlp は外部コマンドとして呼び、進捗・結果は yt-dlp の出力から読み取る
DB NAS上のtver専用Postgres 17コンテナ(Giteaと同じ方式)。ユーザー・DB名はtver、公開ポートは15434。dockageからの到達を確認済み
DB ドライバ github.com/jackc/pgx/v5 の pgxpool
DB テスト github.com/fergusstrange/embedded-postgres v1.34.0 の Postgres 17 を起動する。Apple Silicon では Rosetta 2 が必要
定期実行 worker 内のスケジューラ
NAS のマウント NFS。dockageの/etc/fstabへ_netdev,nofail付きで登録し、DSMでは接続元192.168.10.155だけを許可する
NFS 権限 読み書き、Squash「すべてのユーザーをadminに割り当てる」、sys、非同期オン。DSMのadminアカウントが無効でもUID 65532から書き込み確認済み
DBの通信制限 DSMファイアウォールでTCP 15434を192.168.10.155からだけ許可する
DBバックアップ NASのDSMタスクスケジューラからrootで毎日実行する。tver-postgres内のpg_dumpをgzip圧縮して/volume1/backups/tver/tver_<YYYYmmdd_HHMMSS>.sql.gzへ保存し、14日より古い世代を削除する。同名の完成ファイルは上書きせず失敗させる。初回は一時DBへの復元と主要4表の行数一致まで確認する
保持期間 削除は手動のみ。自動削除はしない
待ち行列 Postgres のテーブルだけで実装する。専用のキュー基盤は持たない
定期確認の間隔 登録番組の新しい回を 24 時間ごとに確認する
定期確認の走査 worker のスケジューラが起動直後と1時間ごとに、最終確認から24時間以上経過した有効な番組を探す。番組は1件ずつ確認し、間に数秒空ける
定期確認の予告 180秒以下の回は自動登録せず、既知としてだけ記録する。新着画面の予告判定と同じしきい値を使う
定期確認の失敗 last_checked_at を更新せず、失敗日時と秘匿値を伏せた短いメッセージを保存する。次の1時間走査で再試行し、成功時にエラーを消す
登録時の既存回 登録の都度「公開中の回もダウンロードする」かを選ぶ。選ばない場合も公開中の回を既知として記録する
番組の一時停止 登録は残し、自動ダウンロードだけ止める。再開後は停止中に公開された未確認の回も対象にする
動画のマウント先 web と worker は VIDEO_DIR(既定 /videos)に同じ NAS 共有をマウントする。download_jobs.file_path は VIDEO_DIR からの相対パスを保存する
ファイル名 <番組名>/<番組名> <回タイトル> [<エピソードID>].mp4。禁止文字と制御文字を _ に置換し、空の要素は 名称未設定とする。番組名はUTF-8で90バイト、回タイトルは140バイト、サニタイズ後のエピソードIDは64バイトまでとし、ファイル名全体を255バイト以下に収める。64バイトを超えるIDはworkerでinvalid_outputにする。実際のTVer IDは約10文字で、yt-dlpのextractorも英数字として扱うため64バイトを上限とした。既存ファイルは改名せず、新しい保存から適用する
動画ファイルの境界 webはVIDEO_DIRをos.OpenRootで開き、DBの相対file_pathをRoot.Open・Root.Removeへ渡す。VIDEO_DIR外へのパストラバーサルと外向きシンボリックリンクをOSのディレクトリ境界で拒否する
同名ファイル 移動先に同名ファイルがあれば上書きする。ファイル名にエピソード ID が入り episode_id は一意なので、同名は同じ回の前回処理の残りに限られる。再実行で同じ結果になることを優先する
ライブラリ一覧 DBの完了済みジョブから作り、NASのフォルダ走査やファイル存在確認は行わない。番組一覧、番組ごとの回一覧、再生の3段構成にする
回の自然順 回タイトル中の半角・全角数字の並びを数値として比較し、同順位は完了日時の古い順、さらにジョブIDの小さい順にする
動画の削除 アプリからだけ行い、削除前にサーバーが描画する確認画面を必ず挟む。ライブラリでは番組単位、番組ページでは選択した回または番組全体を複数削除できる。確認画面で確定したジョブ ID だけを実行するため、確認後に増えた回は削除しない。各回はファイルを先に削除してからジョブ記録を削除し、親の番組フォルダが完全に空なら削除する。ファイルが既に無い場合も記録は削除し、1件の失敗で残りを止めない。登録番組の known_episodes は残す
同時ダウンロード数 既定は 1。WORKER_CONCURRENCY で 2まで増やせる
ダウンロードの再試行 自動では再試行しない。失敗を failed で残し、画面から手動で再試行する
実行中の取り消し web で running を canceled にし、worker が検知して yt-dlp のプロセスグループを停止する
yt-dlp の作業場所 VIDEO_DIR/.incoming/<job id>/ に %(id)s.%(ext)s で保存し、検査後に Go が media.RelativeVideoPath の場所へ同一ファイルシステム内で移動する。終了状態にかかわらず作業場所を片付ける

要件

機能要件

# 要件 補足
F1 ドラマ・バラエティの新着一覧を表示する 番組名・回タイトル・放送局・長さ・配信終了日時・サムネイル
F2 一覧を絞り込む 予告(「60秒PR」等)を除外できる。ダウンロード済み・待ち行列中を区別して表示する
F3 選んだ動画をダウンロードの待ち行列に入れる 複数選択できる
F4 ダウンロードの進捗・結果を表示する 失敗理由(DRM・地域制限・TVer 側の変更による抽出失敗)を区別して表示する
F5 同じ回を二重にダウンロードしない 手動・自動の両方で共通
F6 番組(シリーズ)を登録し、新しい回を自動でダウンロードする 登録・解除・一覧。定期的に新しい回を確認する。番組URL(tver.jp/series/…)から登録でき、登録前に確認画面を表示する
F7 ダウンロード済みの動画をアプリ内で再生する シーク(HTTP Range)に対応する
F8 ダウンロード済みの動画を一覧・削除する NAS の容量管理のため
F9 保存ファイルの名前と置き場所を規則で決める 例: <番組名>/<番組名> <回タイトル> [<エピソードID>].mp4

非機能要件

  • yt-dlp を素早く更新できること。 TVer 側の変更で抽出が壊れやすいので、worker のイメージを再ビルドするだけで更新できるようにする
  • TVer への負荷を抑える。 一覧取得はキャッシュし、同時ダウンロード数は 1〜2 に制限する
  • ブラウザで再生できる形式で保存する。 映像 H.264 + 音声を MP4 に結合する(ffmpeg が必要)
  • 認証は持たない(LAN 内限定のため)
  • homelab の規約に従う(homelab/docs/conventions): Compose v2、秘匿情報は .env、TZ=Asia/Tokyo

構成(案)

コンテナ 役割
web UI・API。新着一覧の取得、待ち行列への登録、番組URLを含む番組登録、動画の配信(再生)
worker Go + yt-dlp + ffmpeg。待ち行列からダウンロードし、登録番組の新しい回を定期的に確認する
  • 現在の web は /healthz、新着(/)、待ち行列(/queue)、登録番組(/subscriptions)、番組単位のライブラリ(/library)、番組ごとの回一覧(/library/series/{series_id})、再生ページ(/library/{id})、Range 対応の MP4 配信(/videos/{id})を提供する
  • ライブラリはDBの完了済みジョブだけを基に、番組一覧から番組ページ、再生ページへ進む。番組ページは完了が新しい順と、回タイトルの半角・全角数字を数値比較する話の順を切り替えられる。再生ページは話の順で前後の回へ移動できる
  • 動画配信はクライアントからジョブ ID だけを受け取り、DB の相対 file_path を os.Root の Open・Remove で扱う。絶対パス・..・VIDEO_DIR 外を指すシンボリックリンクをディレクトリ境界で拒否する。削除はライブラリまたは番組ページの選択、もしくは再生ページの1件削除からサーバー描画の確認画面へ進み、解決済みのジョブ ID を実行画面へ引き継ぐ。実行時は1リクエストにつき動画ルートを1回だけ開き、各回を独立して処理して削除・失敗・対象外の件数を結果画面へ表示する。動画削除後に実ディレクトリの親が空なら削除する。削除後は新着から手動で再登録できるが、登録番組の既知記録は残るため自動再登録しない
  • 新着の「番組を登録」から、公開中の回をダウンロードするか選ぶ。web はサーバー側の新着キャッシュから番組 ID と名前を引き、TVer の season ごとの公開中エピソードを取得する。取得失敗時は登録しない
  • /subscriptionsではhttps://tver.jp/series/<ID>形式の番組URLを受け付け、検証した番組IDからTVer APIの接続先を組み立てる。登録前に番組名・番組ID・公開中の回数を確認し、登録時にもサーバー側で番組名と公開中の回を取り直す。クライアントから番組名は受け取らない
  • 登録と公開中の回の既知記録・任意の待ち行列投入は1トランザクションで行い、登録時刻を最終確認日時に設定する。known_episodes は番組解除時に連動して削除する。一時停止中は投入を拒否する
  • worker のスケジューラは起動直後と1時間ごとに、enabled かつ最終確認から24時間以上の番組を抽出する。TVer から公開中の回を順次取得し、未確認の180秒超の回だけを source=subscription で投入する。180秒以下も既知にし、成功時の最終確認更新・エラー消去まで1トランザクションで行う
  • TVer の番組取得に失敗した場合は最終確認日時を変えず、失敗日時と伏字済みの短いメッセージを subscriptions に記録して画面に表示する。番組間には3秒空け、SIGTERM では取得中または待機中の確認を中断する
  • 新着の登録ボタンはエピソード ID とジャンルだけを送る。web が有効な新着キャッシュから番組情報を引き、episode_id の一意制約で重複登録を防ぐ。新着一覧はジョブ状態を一括取得し、失敗・取り消しには再試行を表示する
  • 待ち行列は実行中・待機中・失敗・取り消し・完了を状態別に直近50件ずつ表示する。段階別の進捗と失敗理由を表示し、待機中の取り消しと失敗・取り消しの再試行ができる。実行中または待機中のジョブがある間だけ5秒ごとに一覧を更新する
  • 実行中のジョブにも取り消しボタンを表示する。worker は DB の canceled を定期確認し、yt-dlp をプロセスグループごと停止する
  • 新着はジャンルごとにメモリへ 10 分間キャッシュする。キャッシュ中は TVer セッションの再発行と一覧の再取得を行わない。TVer の取得に失敗した場合、一覧の場所にエラーを表示して詳細をログへ記録する
  • 「予告を隠す」は既定でオンとし、180 秒以下の件を非表示にする。長さは秒から分へ切り上げて表示し、配信終了日時は Asia/Tokyo で表示する。ジャンルと予告表示の状態は URL に残す
  • 画面は html/template と htmx 2.0.11 を使い、htmx は Go の embed から /static/ でローカル配信する
  • compose.yaml は web と worker を定義する。web は dockage VM の LAN アドレス 192.168.10.155:8080 で公開し、待ち受けは WEB_ADDR で変更できる。両方へ DATABASE_URL と同じ動画ボリュームを渡し、worker の並行数は WORKER_CONCURRENCY(既定1、最大2)で指定する
  • NAS用のdeploy/nas/tver-db/compose.yamlはpostgres:17を15434:5432で公開し、/volume1/docker/tver-db/pgdataへ保存する。パスワードは同ディレクトリのGit管理外.envから注入する
  • NAS用のbackup-db.shはコンテナ内のpg_dumpを検査後にgzip圧縮し、同一ディレクトリ内のハードリンクで完成ファイルを上書きせず公開する。restore-test.shは最新ダンプを一時DBへ復元してdownload_jobs・subscriptions・known_episodes・schema_migrationsの行数を本番DBと比較する。一時DBはEXIT trapで必ず削除し、復元SQLの通常出力はログへ記録しない
  • 本番配置と更新の手順はdocs/deployにまとめる。dockageでは/volume1/videoを/mnt/nas-tverへNFSマウントし、その配下のtverだけをコンテナ内の/videosへ渡す
  • Giteaは匿名閲覧とSSHを無効にしているため、dockageのclone・pullは読み取り専用tver-deployのrepository読み取りトークンを使う。Gitのcredential helperはGiteaのHTTPS URLに限定する
  • DB は NAS 上に tver 専用 Postgres 17 コンテナを置く。internal/store の埋め込み SQL を schema_migrations で管理し、pg_advisory_lock で複数プロセスからの同時適用を直列化する。web は起動時に pgxpool で接続してマイグレーションを適用する
  • download_jobs は episode_id を一意にして二重登録を防ぎ、ジョブの状態・段階別進捗・結果を持つ。subscriptions はシリーズ単位の登録と最終確認日時を持つ。worker からの操作と画面用の一覧取得を internal/store にまとめる
  • worker は yt-dlp[default] と ffmpeg を外部コマンドとして使い、映像・音声・結合・移動の段階別進捗を1秒間隔に間引いて保存する。独立した heartbeat を5秒ごとに更新し、正確な全体パーセントは保存しない。after_move:filepath で実パスを取り、ffprobe で H.264 と AAC の MP4 を確認して成功とする。失敗は取り消し、DRM、地域制限、ネットワーク、配信不可、抽出失敗、出力不正、その他の順に扱い、自動再試行しない
  • worker は起動時にマイグレーションと停止中ジョブの RequeueStale を行い、登録番組の確認も直ちに1回行う。SIGTERM では yt-dlp と確認処理を止め、担当中ジョブを queued に戻してから終了する。Dockerfile.worker は yt-dlp 2026.08.19 を ARG で固定し、ffmpeg・ffprobe とともに非 root で実行する
  • 保存先の NAS 共有フォルダは dockage VM に NFS でマウントし、worker(書き込み)と web(読み取り・削除)に同じ VIDEO_DIR でバインドマウントする

外部とのつながり

相手 用途 備考
TVer platform-api.tver.jp 新着一覧(/service/api/v1/callNewerDetail/{drama|variety})・番組名(/service/api/v2/callSeries/{series_id})・番組のエピソード一覧 非公開 API。セッションは /v2/api/platform_users/browser/create で発行する(yt-dlp と同じ方式)
TVer service-api.tver.jp 番組の season 一覧(/api/v1/callSeriesSeasons/{series_id}) 非公開 API。yt-dlp の TVer extractor と同じ取得方式
yt-dlp ダウンロード TVer extractor を使う
Gitea gitea.kazu634.com 配置元 匿名cloneとSSHは使えない。tver-deployのHTTPSトークンでclone・pullする
NAS ds923p(NFS) 動画の保存先 /volume1/videoをdockageの/mnt/nas-tverへNFSv3でマウントし、アプリはtverサブフォルダを使う。DSMのNFS権限は192.168.10.155のみ
NAS ds923p(Postgres 17) DB tver専用コンテナを15434:5432で公開し、DSMファイアウォールで192.168.10.155のみ許可する。dockageから接続確認済み

確認済みの事実

  • 新着 API は drama / variety それぞれ 100 件を返す。予告動画も含まれる。各件に id・seriesID・seriesTitle・title・broadcasterName・duration・endAt・thumbnailPath がある [実機] 2026-10-03 確認
  • 番組 sr0lhdrtdb の callSeriesSeasons は result.contents に type=season の1件(ss3i6njoq3)を返し、callSeasonEpisodes は type=episode の5件を返した。エピソードには新着と同じ id・seriesID・seriesTitle・title・broadcasterName・duration・endAt・thumbnailPath が含まれる。他の番組での season 数や一覧件数は未確認 [実機] 2026-10-03 確認
  • dockage VM から TVer API に到達でき(HTTP 200)、送信元は日本と判定される [実機] 2026-10-03 確認
  • yt-dlp 2026.08.19 で TVer のエピソードを抽出できる(--simulate)。映像(H.264 / avc1)と音声が別ストリームで、結合に ffmpeg が要る。1080p・約 50 分で約 1.2GiB [実機] 2026-10-03 確認
  • 5 分の epq432nee4 を yt-dlp 2026.08.19 で取得した。--newline と --progress-template の 1 行 1 JSON は Go の外部プロセスから逐次読める。ただし uvx yt-dlp のみで pycryptodomex が無いと AES-128 HLS を ffmpeg に委譲し、途中のダウンロード進捗が出なかった。yt-dlp[default] に含まれる pycryptodomex を加えた試行では途中の進捗が出た [実機] 2026-10-03 確認
  • 別ストリームの映像と音声は進捗が個別に始まり、format_id で区別できる。結合処理は postprocess: の出力で検知でき、--print after_move:filepath で実ファイルのパスを取得した。完成した MP4 は 24,613,787 バイトで、ffprobe では H.264 640×360 と AAC LC だった。初回取得は約 10 秒だった [実機] 2026-10-03 確認
  • SIGTERM で止めると .part と .ytdl が残り、再実行で 437,066 バイトから 438,090 バイトへ続きから取得できた [実機] 2026-10-03 確認
  • 到達不能のプロキシ経由の通信失敗と存在しないエピソードは、ともに yt-dlp の終了コード 1 だった。stderr の文言で分類が必要。DRM・地域制限の失敗文言と終了コードは実測していない(yt-dlp の extractor ソースのみ確認) [実機] 2026-10-03 確認
  • yt-dlp の TVer extractor は日本のみ(_GEO_COUNTRIES = ['JP'])。DRM 付きの作品は取得できない [公式] https://github.com/yt-dlp/yt-dlp/blob/master/yt_dlp/extractor/tver.py (取得日: 2026-10-03)
  • DSMのNFS権限を接続元192.168.10.155、読み書き、全ユーザーをadminへ割り当て、sys、非同期オンにすると、dockageからNFSv3で/volume1/videoをマウントできた。設定直後の1回目はaccess denied、直後の2回目は成功したため、権限の反映待ちと考えられる [実機] 2026-10-04 確認
  • DSMのadminアカウントが無効でも、UID 65532のコンテナからtverサブフォルダの作成・書き込み・読み込み・rename・削除に成功した。NAS上の所有者はUID 1024・GID 100。/mnt/nas-tver/tverは作成済み [実機] 2026-10-04 確認
  • NFSマウントのファイル名上限はnamlen=255 [実機] 2026-10-04 確認
  • GiteaはREQUIRE_SIGNIN_VIEW = true・DISABLE_SSH = trueで、匿名HTTPSのinfo/refsはHTTP 401だった。読み取り専用ユーザーtver-deployとrepository読み取りトークンで/opt/stacks/tverへのcloneに成功した [実機] 2026-10-04 確認
  • dockageでdocker compose up -d --buildを実行してweb・workerを起動でき、Dockgeにもtverスタックとして表示された。DSMファイアウォール設定後、dockageからNASのTCP 15434へ到達できた [実機] 2026-10-04 確認
  • 本番の/healthzはHTTP 200、新着バラエティは98件だった。4分の本編ep5hcgopp2は登録から20秒以内に完了し、92,340,468バイトの1080p H.264/AAC MP4をNASへ保存した。所有者はUID 1024・GID 100、.incomingは空で、/libraryとHTTP Range 206も確認した [実機] 2026-10-04 確認
  • NASは番組フォルダ内に所有者UID 0の@eaDirを作る。ライブラリ一覧はDBから作るため、アプリへの影響はない [実機] 2026-10-04 確認
  • callNewerDetail/dramaは毎回100件を返し、応答に続きや総件数を示す項目はなかった。result.contentsはtype=newerで、contentはid・version・titleのみ。page=2・offset=100・limit=200を付けても先頭・末尾IDが同じ100件となり、パラメータは無視された。100件を超えて取得する手段は見つからなかったが、非公開APIのため不可能とは断定しない。新着一覧は直近100件までとなる一方、登録番組の自動確認は番組ごとのcallSeriesSeasons→callSeasonEpisodesを使うため、新着から外れた回も取りこぼさない [実機] 2026-10-04 確認
  • dockage上の使い捨てpostgres:17コンテナへマイグレーション001〜003を適用してテスト用の行を投入し、backup-db.shとrestore-test.shを実行した。gzip圧縮・権限600のダンプ作成、同じSTAMPでの2回目がlnの失敗で非0終了して既存ダンプを保持すること、主要4表の行数一致、一時DBの削除、ログにpsqlの標準出力が残らないことを確認した [実機] 2026-10-04 確認
  • NASの/volume1/docker/tver-db/へスクリプトを配置し、DSMタスクスケジューラのrootタスクから実行して、/volume1/backups/tver/へtver_20261004_182838.sql.gz(2,286バイト)とtver_20261004_183256.sql.gz(2,289バイト)を作成した。ダンプはroot所有・権限600で、バックアップディレクトリはroot所有・drwxr-xr-xだった。restore-test.shをsudoで実行し、download_jobs 1件・subscriptions 1件・known_episodes 1件・schema_migrations 3件の一致、ログへの結果記録、一時DBの削除を確認した。一般ユーザーではroot所有のログへ書き込めず失敗する [実機] 2026-10-04 確認
  • DSMのACLで全員書き込み可だった/volume1/docker配下について、/volume1/docker/tver-dbをchmod 755、backup-db.shとrestore-test.shをchmod 700にしてACLが外れたことを確認した。変更後も本番アプリの/queueと/subscriptionsはHTTP 200でDBを読み取れた。PostgreSQLコンテナ再起動後の動作は未確認 [実機] 2026-10-04 確認
  • platform-apiのv2/callSeries/{series_id}はresult.content.content.titleに番組名を返し、存在しないIDにはHTTP 404を返した。sr8a864g0wでは番組名がエピソード側のseriesTitleと一致した。v1/callEpisode/{episode_id}はseriesID・seriesTitleを返した(今回は使わない) [実機] 2026-10-07 確認

課題

  • 登録済みのDSM日次バックアップタスクが翌日以降に自動実行され、新しいダンプを作ることを確認する [要確認]
  • DRM・地域制限の実例探しはTVerへの大量アクセスになるため行わず、運用中に観測する。workerは失敗時のerror_messageをDBへ保存し、既知の文言に一致しない失敗はunknownにする。実例が出た時点で分類を見直す

経緯

  • ステップ13で、ライブラリの番組単位削除、番組ページの回選択・番組全体削除、再生ページの1件削除を、すべてサーバー描画の確認画面を経由する方式に統一した。確認時に解決した最大1,000件のジョブ ID だけを実行し、途中の失敗後も残りを処理して結果を表示する
  • ステップ12でライブラリを番組一覧、番組ページ、再生ページの3段構成にした。番組単位の集計と登録状態はDBから取得し、番組ページの話の順と再生ページの前後移動には同じ自然順を使う。動画削除後は空になった番組フォルダだけをos.Root経由で削除する

確かめ方

  • Codex では go test -count=1 ./... を環境変数の前置なしで実行する。go test を許可する rules に一致させ、Postgres の共有メモリ作成に必要な権限で実行するため。初回のモジュール取得にはネットワーク接続が必要
  • web を起動するには .env.example を参考に、Git 管理外の .env に実際の DATABASE_URL と VIDEO_HOST_DIR を設定する。VIDEO_DIR の既定は /videos。DB 接続がある環境では起動時にマイグレーションが適用される
  • DB テストは embedded-postgres v1.34.0 が Postgres 17 を一度起動し、テストごとに独立した DB を作る。バイナリキャッシュ、実行ファイル、データは /private/tmp 配下に置く。Apple Silicon での実行には Rosetta 2 の導入が必要
  • TVer 応答のテストは internal/tver/testdata/newer_drama.json(2026-10-03 の実応答を 3 件に縮小)と httptest を使い、テストから本物の TVer にはアクセスしない
  • 番組一覧の応答は internal/tver/testdata/series_seasons.json と season_episodes.json(2026-10-03 の実応答を各1件に縮小)に記録した。テストは httptest を使い、実 API にアクセスしない
  • 番組情報の応答はinternal/tver/testdata/series.json(2026-10-07の実応答を番組情報だけに縮小)に記録した。番組URLは純関数でscheme・host・path・IDを検証し、TVerクライアントのテストはhttptestだけへ接続する
  • 新着画面のテストは偽物の TVer クライアント・store と差し替え可能な時計を使い、表示項目、絞り込みの境界、10 分キャッシュ、取得失敗、JST 表示、登録・状態表示・待ち行列の操作を確認する
  • ライブラリのテストは番組単位の集計、番組別・ID指定の完了済み回取得、3画面、半角・全角数字の自然順、前後移動を確認する。一時ディレクトリの小さな動画を使い、Rangeの206・範囲外の416、os.Rootによる相対パスとシンボリックリンクの拒否、404、サーバー確認画面の入力検証・重複除去・HTMLエスケープ、複数削除の部分失敗・対象外・確認後の新着保持、ファイル削除後の記録削除、空の番組フォルダだけを削除することも確認する。共有のファイル名規則はinternal/mediaで90・140・255バイトの境界とUTF-8の完全性をテストする
  • worker のテストは internal/worker/testdata の偽 yt-dlp・ffprobe を使い、本物の TVer へ接続せずに正常完了、失敗分類、取消、終了時の待機復帰、進捗の間引き、heartbeat、出力検査、移動先、作業場所の片付けを確認する
  • 登録番組の定期確認は偽の TVer クライアントと差し替え可能な時計・タイマーを使い、24時間の境界、順次確認、1時間走査、失敗記録、予告の既知化、終了時の中断を確認する。store の抽出・既知化・投入・日時更新・エラー消去は embedded Postgres 17 で確認する
  • NAS用Composeはdeploy/nas/tver-db/compose.yamlをYAMLパーサーで読み、services.postgres、環境変数、ポート、保存先、healthcheckを確認する。Dockerが使える環境ではdocker compose --env-file deploy/nas/tver-db/.env.example -f deploy/nas/tver-db/compose.yaml configでも展開結果を確認する
  • DBバックアップスクリプトはdeploy/nas/tver-db/scripts_test.goからPATH上の偽dockerを使って実行し、正常な圧縮、同名・同時実行時の上書き防止、空ダンプとpg_dump失敗、14日の世代境界、復元失敗時の一時DB削除、主要表の行数不一致を確認する。NAS実機ではdocs/deploy#5. PostgreSQLをバックアップするに従って手動バックアップと復元確認を行う
  • 本番の確認手順はdocs/deployに従い、NFS上の/mnt/nas-tver/tverをUID 65532で読み書き・rename・削除できること、DBがhealthyであること、webの/healthz、実動画の保存・再生・シークを確認する
  • env GOPATH=/private/tmp/tver-gopath GOCACHE=/private/tmp/tver-go-cache GOMODCACHE=/private/tmp/tver-go-modcache GOPROXY=off go vet ./... で静的解析する
  • DATABASE_URL、VIDEO_DIR、WEB_ADDR=127.0.0.1:18080 を設定して go run ./cmd/web で web を起動できる。worker は同じ DATABASE_URL と VIDEO_DIR を設定して go run ./cmd/worker で起動する。worker の実行は TVer への実アクセスを伴うため手動確認は少数回に留める