LXDでTVTestをセットアップし録画データはホスト側

LXDコンテナ内にTVTest環境をセットアップすればホスト側を綺麗に保てます。また録画データはホスト側に保存するようにしておけば、コンテナのバックアップや復元もやりやすくなります。
ここでは、そのセットアップ方法を紹介します。

ホストにチューナードライバ適用

OS:Ubuntu 26.04 beta
TVチューナー:DTV02A-1T1S-U

mkdir -p /opt/dtv
cd /opt/dtv
DRIVER_VERSION="0.5.5"
DRIVER_DEB="px4-drv-dkms_${DRIVER_VERSION}_all.deb"
DRIVER_URL="https://github.com/tsukumijima/px4_drv/releases/download/v${DRIVER_VERSION}/${DRIVER_DEB}"

if [ ! -f "${DRIVER_DEB}" ]; then
    curl -L -o "${DRIVER_DEB}" "${DRIVER_URL}"
fi
sudo apt install -y "./${DRIVER_DEB}"
sudo modprobe px4_drv || true

# 認識しているか確認
sudo dmesg | grep -i isdbt

コンテナを作成

続いてコンテナを作成します。ここでは「tv」という名前のコンテナを作成しています。Dockerは使っていないので、lxd-base-minimalコンテナをコピーして作成します。
コンテナ再起動やtailscaleも有効にしたら、ホスト側で次を実行します。

USB接続のTVチューナーをコンテナにパススルー

USBチューナーをパススルーします。ホスト側で下記を実行。

#!/bin/bash
# =============================================================
# チューナーデバイスをLXDコンテナ「tv」にパススルーするスクリプト
# 実行方法: sudo bash lxd_passthrough.sh
# =============================================================
set -e

CONTAINER="tv"

# ============================================================
# USB デバイス(vendorid / productid)
# ============================================================
echo "=== USB チューナーを検出中 ==="

LSUSB_LINE=$(lsusb | grep -i "ISDBT2056" || true)
if [ -z "$LSUSB_LINE" ]; then
    echo "エラー: ISDBT2056 デバイスが見つかりません。接続を確認してください。"
    exit 1
fi
echo "検出: $LSUSB_LINE"

# ID xxxx:xxxx を抽出
IDS=$(echo "$LSUSB_LINE" | grep -oP 'ID \K[0-9a-fA-F]{4}:[0-9a-fA-F]{4}')
VENDOR_ID=$(echo "$IDS" | cut -d: -f1)
PRODUCT_ID=$(echo "$IDS" | cut -d: -f2)
echo "  vendorid : $VENDOR_ID"
echo "  productid: $PRODUCT_ID"

# すでに登録済みなら上書き(remove → add)
if lxc config device show "$CONTAINER" | grep -q "usb-tuner"; then
    echo "usb-tuner は登録済みのため上書きします"
    lxc config device remove "$CONTAINER" usb-tuner
fi
lxc config device add "$CONTAINER" usb-tuner usb \
    vendorid="$VENDOR_ID" \
    productid="$PRODUCT_ID"
echo "usb-tuner を追加しました"

# ============================================================
# isdb2056videoN デバイス
# ============================================================
echo ""
echo "=== isdb2056video デバイスを検出中 ==="

ISDB_DEVS=$(ls /dev/isdb2056video* 2>/dev/null || true)
if [ -z "$ISDB_DEVS" ]; then
    echo "警告: /dev/isdb2056video* が見つかりません。スキップします。"
else
    IDX=0
    for DEV in $ISDB_DEVS; do
        NAME="isdb2056-${IDX}"
        echo "  追加: $DEV -> コンテナ内 $DEV ($NAME)"
        if lxc config device show "$CONTAINER" | grep -q "^${NAME}:"; then
            lxc config device remove "$CONTAINER" "$NAME"
        fi
        lxc config device add "$CONTAINER" "$NAME" unix-char \
            source="$DEV" \
            path="$DEV"
        IDX=$(( IDX + 1 ))
    done
fi

# ============================================================
# DVB デバイス
# ============================================================
echo ""
echo "=== DVB デバイスを検出中 ==="

DVB_DEVS=$(find /dev/dvb -type c 2>/dev/null || true)
if [ -z "$DVB_DEVS" ]; then
    echo "DVB デバイスは見つかりませんでした。スキップします。"
else
    IDX=0
    for DEV in $DVB_DEVS; do
        # /dev/dvb/adapter0/frontend0 -> dvb-adapter0-frontend0
        NAME="dvb-$(echo "$DEV" | sed 's|/dev/dvb/||; s|/|-|g')"
        echo "  追加: $DEV -> コンテナ内 $DEV ($NAME)"
        if lxc config device show "$CONTAINER" | grep -q "^${NAME}:"; then
            lxc config device remove "$CONTAINER" "$NAME"
        fi
        lxc config device add "$CONTAINER" "$NAME" unix-char \
            source="$DEV" \
            path="$DEV"
        IDX=$(( IDX + 1 ))
    done
fi

# ============================================================
# 結果確認
# ============================================================
echo ""
echo "=== 登録済みデバイス一覧 ==="
lxc config device show "$CONTAINER"
echo ""
echo "完了しました。"

コンテナに入って認識しているか確認します。

lxc exec tv -- bash

コンテナ内でそれぞれ見えているか確認。認識出来ていれば問題ありません。

ls /dev/isdb2056*
ls /dev/bus/usb/001/002

GPUをパススルーする

GPUもパススルーするにはLXD-UIから行います。

追加したらコンテナを再起動し、コンテナ内で認識しているか確認します。

# DRMデバイスとして認識しているか
ls /dev/dri/

# renderD128などが見えるはず
# card0  renderD128

# 詳細確認
ls -la /dev/dri/

エンコードに使えるかは次で確認します。

# VAAPIが使えるか(ffmpegのハードウェアエンコードに必要)
vainfo 2>/dev/null || apt install -y vainfo && vainfo

エラーが出るようなら次を実行。これでも見えないならソフトウェアエンコードが無難。

# VAAPIドライバをコンテナ内にインストール
apt install -y mesa-va-drivers vainfo

# 再確認
vainfo

Mirakurun、EDCB、KonomiTVセットアップ

/opt/lxd-data/tv を作成して「/var/local/edcb/HttpPublic/video」にバインドマウントするので、あらかじめコンテナに。

#!/bin/bash
# =============================================================
# DTV環境セットアップスクリプト
# 対応環境: Ubuntu Desktop / LXDコンテナ (Ubuntu 25.10)
# 実行方法: sudo bash install_dtv.sh
# =============================================================
set -e

# root チェック
if [ "$(id -u)" -ne 0 ]; then
    echo "エラー: このスクリプトは root で実行してください。"
    echo "  sudo bash $0"
    exit 1
fi

# -------------------------------------------------------
# 実行環境の判定(LXD コンテナ / 通常 Ubuntu Desktop)
# -------------------------------------------------------
IS_LXD=false
if systemd-detect-virt --container 2>/dev/null | grep -qi lxc; then
    IS_LXD=true
fi
echo "実行環境: $([ "$IS_LXD" = true ] && echo 'LXDコンテナ' || echo 'Ubuntu Desktop/VM')"

# 作業ユーザーの特定
REAL_USER="${SUDO_USER:-$(logname 2>/dev/null || id -un)}"
REAL_HOME=$(getent passwd "$REAL_USER" | cut -d: -f6)
DTV_DIR="$REAL_HOME/dtv"

echo "作業ユーザー: $REAL_USER  ホーム: $REAL_HOME"

# ============================================================
# 0. キー入力
# ============================================================
echo "=== 1/8: キー設定 ==="
echo "キーの内容を貼り付けてください(入力後、Enter を 2 回押すと確定します):"
BCAS_CONTENT=$(sed '/^$/q')

# ============================================================
# 1. 依存パッケージのインストール
# ============================================================
echo "=== 2/8: 依存パッケージをインストール中 ==="
apt update
apt install -y \
    autoconf automake cmake libtool libpcsclite-dev \
    git build-essential pkg-config curl wget \
    libclang-dev libdvbv5-dev libudev-dev \
    nodejs npm ffmpeg liblua5.2-dev lua-zlib g++ make gcc

# ============================================================
# Node.js 18 のセットアップ (nvm / root 用)
# ============================================================
export NVM_DIR="/root/.nvm"
if [ ! -d "$NVM_DIR" ]; then
    curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
fi
# shellcheck source=/dev/null
source "$NVM_DIR/nvm.sh"
nvm install 18
nvm use 18
nvm alias default 18
NODE18_BIN=$(dirname "$(nvm which 18)")
export PATH="$NODE18_BIN:$PATH"
echo "Node.js バージョン確認: $(node -v)  npm: $(npm -v)"

npm install -g pm2 || true

# ============================================================
# 2. TVチューナードライバ (px4_drv / isdb2056)
# ============================================================
echo "=== 3/8: ドライバをセットアップ中 ==="
mkdir -p "$DTV_DIR" && cd "$DTV_DIR"

DRIVER_VERSION="0.5.5"
DRIVER_DEB="px4-drv-dkms_${DRIVER_VERSION}_all.deb"
[ ! -f "$DRIVER_DEB" ] && curl -L -o "$DRIVER_DEB" \
    "https://github.com/tsukumijima/px4_drv/releases/download/v${DRIVER_VERSION}/${DRIVER_DEB}"
apt install -y "./${DRIVER_DEB}" || echo "警告: px4_drv dkms インストール失敗(LXDでは正常)"
modprobe px4_drv 2>/dev/null || echo "警告: px4_drv modprobe スキップ(LXDでは正常)"

echo "デバイスパーミッションを設定中..."
if [ "$IS_LXD" = true ]; then
    chmod 666 /dev/isdb2056video* 2>/dev/null && echo "isdb2056: OK" || echo "isdb2056: デバイスなし"
    chmod 666 /dev/px4video*      2>/dev/null && echo "px4video: OK"  || echo "px4video: デバイスなし"
else
    tee /etc/udev/rules.d/99-dtv.rules > /dev/null <<'UDEV'
SUBSYSTEM=="usb", ATTRS{idVendor}=="0511", MODE="0666"
KERNEL=="isdb2056video*", MODE="0666", GROUP="video"
KERNEL=="px4video*",      MODE="0666", GROUP="video"
UDEV
    udevadm control --reload-rules
    udevadm trigger
    sleep 2
    chmod 666 /dev/isdb2056video* 2>/dev/null && echo "isdb2056: OK" || echo "isdb2056: デバイスなし"
    chmod 666 /dev/px4video*      2>/dev/null && echo "px4video: OK"  || echo "px4video: デバイスなし"
    usermod -aG video "$REAL_USER" || true
fi

if ! ls /dev/isdb2056video* /dev/px4video* 2>/dev/null | grep -q .; then
    echo "警告: TVチューナーデバイスが見つかりません。"
    if [ "$IS_LXD" = true ]; then
        echo "  LXDのプロファイルでデバイスパススルーが設定されているか確認してください。"
        echo "  例: lxc config device add <container> isdb2056video0 unix-char path=/dev/isdb2056video0"
    else
        echo "  チューナーが正しく接続・認識されているか確認してください。"
    fi
fi

# ============================================================
# 3. libyakisoba / libsobacas のビルド
# ============================================================
echo "=== 4/8: 復号ライブラリのビルド ==="
cd "$DTV_DIR"
for repo in libyakisoba libsobacas; do
    [ ! -d "$repo" ] && git clone "https://github.com/tsunoda14/${repo}.git"
    cd "$repo"
    autoreconf -i
    mkdir -p build && cd build
    [ "$repo" = "libyakisoba" ] && ../configure --sysconfdir=/usr/local/etc || ../configure
    make -j"$(nproc)"
    make install
    cd "$DTV_DIR"
done
ldconfig

# ============================================================
# 4. recisdb のビルド
# ============================================================
echo "=== 5/8: Rust と recisdb のビルド ==="
export CARGO_HOME="/root/.cargo"
export RUSTUP_HOME="/root/.rustup"
if ! command -v cargo &>/dev/null; then
    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --no-modify-path
fi
source "$CARGO_HOME/env" 2>/dev/null || export PATH="$CARGO_HOME/bin:$PATH"

mkdir -p /usr/local/lib/pkgconfig
tee /usr/local/lib/pkgconfig/libsobacas.pc > /dev/null <<'EOF'
prefix=/usr/local
libdir=/usr/local/lib
includedir=/usr/include

Name: libsobacas
Description: PCSC compatible ECM decoder library
Version: 0.0.0
Libs: -L${libdir} -lsobacas
Cflags: -I${includedir}/PCSC
EOF

cd "$DTV_DIR"
[ ! -d recisdb-rs ] && git clone --recursive https://github.com/kazuki0824/recisdb-rs.git
cd recisdb-rs
sed -i 's/pcsclite/sobacas/g' b25-sys/build.rs
cargo build -F dvb --release
cp target/release/recisdb /usr/local/bin/

# ============================================================
# 5. B-CAS キーの配置
# ============================================================
mkdir -p /usr/local/etc
echo "$BCAS_CONTENT" | tee /usr/local/etc/bcas_keys > /dev/null

# ============================================================
# 6. チャンネルスキャンと Mirakurun 設定
# ============================================================
echo "=== 6/8: チャンネルスキャンと Mirakurun 設定 ==="
wget -q https://github.com/tsukumijima/ISDBScanner/releases/download/v1.3.3/isdb-scanner \
    -O /usr/local/bin/isdb-scanner
chmod +x /usr/local/bin/isdb-scanner

npm install -g \
    --unsafe-perm \
    --foreground-scripts \
    --production \
    --ignore-engines \
    mirakurun@3.9.0-rc.4

mkdir -p /usr/local/etc/mirakurun

mirakurun start || true
sleep 5
mirakurun stop  || true

mkdir -p "$DTV_DIR/scanned"
if [ -z "$(ls -A "$DTV_DIR/scanned/" 2>/dev/null)" ]; then
    isdb-scanner "$DTV_DIR/scanned/"
fi

if [ ! -f "$DTV_DIR/scanned/Mirakurun/channels.yml" ]; then
    echo "エラー: チャンネルスキャンが完了しませんでした。"
    echo "デバイスのパススルー設定を確認してから再実行してください。"
    exit 1
fi

cp -a "$DTV_DIR/scanned/Mirakurun/channels.yml" /usr/local/etc/mirakurun/channels.yml
cp -a "$DTV_DIR/scanned/Mirakurun/tuners.yml"   /usr/local/etc/mirakurun/tuners.yml
chown -R root:root /usr/local/etc/mirakurun/
mirakurun start

# ============================================================
# 7. EDCB (EpgTimerSrv) のセットアップ
# ============================================================
echo "=== 7/8: EDCB セットアップ ==="
cd "$DTV_DIR"
[ ! -d EDCB ] && git clone https://github.com/xtne6f/EDCB
cd EDCB/Document/Unix
make -j"$(nproc)"
make install
make extra
make install_extra

mkdir -p /var/local/edcb
chown -R "$REAL_USER:$REAL_USER" /var/local/edcb
make setup_ini

# EMWUI
cd "$DTV_DIR"
[ ! -d EDCB_Material_WebUI ] && git clone https://github.com/EMWUI/EDCB_Material_WebUI
cp -r EDCB_Material_WebUI/HttpPublic /var/local/edcb/
cp -r EDCB_Material_WebUI/Setting    /var/local/edcb/

# BonDriver
[ ! -d BonDriver_LinuxMirakc ] && \
    git clone https://github.com/matching/BonDriver_LinuxMirakc.git --recurse-submodules
cd BonDriver_LinuxMirakc
make -j"$(nproc)"
cp BonDriver_LinuxMirakc.so     /usr/local/lib/edcb/
cp BonDriver_LinuxMirakc.so.ini_sample /usr/local/lib/edcb/BonDriver_LinuxMirakc.so.ini

# チャンネル設定をコピー
cp "$DTV_DIR/scanned/EDCB-Wine/ChSet5.txt" /var/local/edcb/Setting/
cp "$DTV_DIR/scanned/EDCB-Wine/BonDriver_mirakc(BonDriver_mirakc).ChSet4.txt" \
   '/var/local/edcb/Setting/BonDriver_LinuxMirakc(LinuxMirakc).ChSet4.txt'

# WebUI 設定
sed -i 's/^ALLOW_SETTING=.*/ALLOW_SETTING=true/' /var/local/edcb/HttpPublic/legacy/util.lua

# ============================================================
# 録画フォルダのセットアップ
# ホストマウントポイント /opt/lxd-data/tv を
# /var/local/edcb/HttpPublic/video にバインドマウント
# ============================================================
RECORD_SRC="/opt/lxd-data/tv"
RECORD_DST="/var/local/edcb/HttpPublic/video"

echo "録画フォルダをセットアップ中: $RECORD_SRC -> $RECORD_DST"
mkdir -p "$RECORD_SRC" "$RECORD_DST"

# fstab への登録(重複防止)
if grep -q "$RECORD_DST" /etc/fstab; then
    echo "fstab: $RECORD_DST は既に登録済みのためスキップします"
else
    echo "$RECORD_SRC $RECORD_DST none bind 0 0" >> /etc/fstab
    echo "fstab: $RECORD_DST を登録しました"
fi

# 即時マウント
if mountpoint -q "$RECORD_DST"; then
    echo "バインドマウント: $RECORD_DST は既にマウント済みです"
else
    mount --bind "$RECORD_SRC" "$RECORD_DST"
    echo "バインドマウント: $RECORD_DST をマウントしました"
fi

# チューナー数を自動検出
ISDB_DEV_COUNT=$(ls /dev/isdb2056video* 2>/dev/null | wc -l)
PX4_DEV_COUNT=$(ls /dev/px4video*       2>/dev/null | wc -l)
if [ "$ISDB_DEV_COUNT" -gt 0 ]; then
    TUNER_COUNT=$ISDB_DEV_COUNT
elif [ "$PX4_DEV_COUNT" -gt 0 ]; then
    TUNER_COUNT=$(( PX4_DEV_COUNT / 4 ))
    [ "$TUNER_COUNT" -eq 0 ] && TUNER_COUNT=1
else
    TUNER_COUNT=$(grep -c '^ *- name:' /usr/local/etc/mirakurun/tuners.yml 2>/dev/null || echo 1)
    [ "$TUNER_COUNT" -eq 0 ] && TUNER_COUNT=1
fi
echo "検出チューナー数: ${TUNER_COUNT}"

# EpgTimerSrv.ini
tee /var/local/edcb/EpgTimerSrv.ini > /dev/null <<EOT
[SET]
EnableHttpSrv=1
EnableTCPSrv=1
RecEndMode=0
Data=1
HttpAccessControlList=+127.0.0.0/8,+10.0.0.0/8,+172.16.0.0/12,+192.168.0.0/16,+169.254.0.0/16,+100.64.0.0/10
[TunerNum]
BonDriver_LinuxMirakc(LinuxMirakc).so=${TUNER_COUNT}
[BonDriver_LinuxMirakc.so]
Count=${TUNER_COUNT}
GetEpg=1
EPGCount=0
Priority=0
EOT

# Common.ini(録画先は /var/local/edcb/HttpPublic/video のまま=バインドマウント経由)
tee /var/local/edcb/Common.ini > /dev/null <<'EOT'
[SET]
RecFolderPath0=/var/local/edcb/HttpPublic/video
RecFolderNum=1
EOT

# systemd 登録
tee /etc/systemd/system/edcb.service > /dev/null <<EOT
[Unit]
Description=EpgTimerSrv
After=network-online.target
[Service]
Type=simple
User=$REAL_USER
ExecStart=/usr/local/bin/EpgTimerSrv
Restart=always
[Install]
WantedBy=default.target
EOT

systemctl daemon-reload
systemctl enable edcb
systemctl start edcb

# ============================================================
# 8. KonomiTV インストール
# ============================================================
echo "=== 8/8: KonomiTV インストール ==="
cd "$DTV_DIR"
curl -LO https://github.com/tsukumijima/KonomiTV/releases/latest/download/KonomiTV-Installer.elf
chmod +x KonomiTV-Installer.elf
./KonomiTV-Installer.elf

KONOMI_CONFIG=$(find /opt /usr/local /home -name "config.yaml" -path "*/KonomiTV/*" 2>/dev/null | head -1)
if [ -z "$KONOMI_CONFIG" ]; then
    echo "警告: KonomiTV の config.yaml が見つかりませんでした。パスを確認してください。"
else
    echo "KonomiTV config.yaml を更新中: $KONOMI_CONFIG"
    sed -i 's/always_receive_tv_from_mirakurun: false/always_receive_tv_from_mirakurun: true/' "$KONOMI_CONFIG"
    pm2 restart KonomiTV
fi

echo ""
echo "=== 環境構築が完了しました! ==="
echo ""
echo "録画ファイルの保存先:"
echo "  コンテナ内: $RECORD_DST"
echo "  ホスト側  : $RECORD_SRC"

ハードウェアエンコードが利用出来るかどうか確実ではない場合はいったんFFmpegにしておいたほうが無難でしょう。TVTestやEDCBの動作を確認したら切り替えてみて、切り替え出来るようなら利用可能です。

/opt/KonomiTV
tcp://127.0.0.1:4510/
FFmpeg
/var/local/edcb/HttpPublic/video

インストールが終了したら、KonomiTVを立ち上げてみます。番組表などはまだ受信されていないので、EDCBにアクセスして、EPGを受信します。

http://tv:5510

EPGを取得します。これにはかなり時間がかかります。tmpファイルが無くなれば受信完了。

ls /var/local/edcb/Setting/EpgData

EDCBの再起動は下記コマンドです。

sudo systemctl restart edcb

/mnt/lxd-storage/lxd-data/tv

タイトルとURLをコピーしました