この記事は、筆者のWindowsマルチGPU環境で実際に起きた問題を、AIとの調査ログをもとに整理したものです。 具体的なマシン名、ユーザー名、IPアドレス、ローカルパスなどの環境固有情報は省略・一般化しています。 すべての環境で同じ結果になるとは限りませんが、Windows 11、NVIDIA GPU、マルチGPU構成、ローカルLLMを使っている人の切り分け材料になれば幸いです。
ローカルLLM用のサブ機に、中古で買い足したRTX 3090を追加しました。もともと1枚RTX 3090が入っていたので、これで24GB×2の48GB構成になるはずでした。
ところが換装した瞬間から、2枚とも動かなくなりました。デバイスマネージャーで両方ともCode 43です。
構成はこうです。
ここで大事な前提があります。この構成は、2枚目がRTX 3080だったときには、同じスロット配置で数年間まったく問題なく動いていました。 変えたのは2枚目のカードだけです。スロットもケーブルも電源も、そのままです。
また、買い足した中古のRTX 3090は、別のマシンに単体で挿して事前に検品してありました。VRAM全域テスト、OCCTの3D負荷1時間、Blenderでの数時間のレンダリング、全出力端子の確認まで通っています。単体では完全に健全なカードです。
症状には、いくつか奇妙な特徴がありました。
ブートすると必ず2枚とも落ちます。 コールドブートでもウォームリブートでも同じで、しかもWindowsのイベントログには何も残りません。エラーひとつ出ないまま、デバイスマネージャーだけが両方Code 43になっています。
起動後に1枚ずつ、時間を空けて初期化し直すと、確率的に成功します。 デバイスを削除してハードウェアの再スキャンをかける操作を、1枚ずつ60秒以上の間隔を空けて実行すると、体感で3〜5割くらいの確率で両方とも「正常」になりました。同時にやると必ず失敗します。
そして「正常」になっても、計算はできませんでした。
デバイスマネージャーが正常(Code
0)で画面も出ているのに、nvidia-smi
は次のように失敗します。
Failed to initialize NVML: Unknown Error
CUDAの初期化も通りません。cuInit() は
CUDA_ERROR_NO_DEVICE(エラーコード100)を返してきます。つまり表示系だけ生きていて、計算系は完全に死んでいる状態です。
おもしろいことに、両カードのHDMIオーディオ機能だけは、2枚同時でも常に正常に動いていました。 PCIeのバスとしては2枚とも見えていて、動かないのはGPU本体の初期化だけ、ということです。
イベントログが無言なので、証拠がありません。唯一見つかったのが、Windowsが密かに保存していたライブダンプでした。
置き場所はここです。
C:\Windows\LiveKernelReports\
C:\ProgramData\Microsoft\Windows\WER\ReportQueue\
これをWinDbgで開くと、次の内容でした。
VIDEO_MINIPORT_FAILED_LIVEDUMP (0x1B0)
Arg1: 0x00000002 Start device failed
Arg2: 0xC000009A STATUS_INSUFFICIENT_RESOURCES
stack:
dxgkrnl!DpiFdoStartAdapterThread
dxgkrnl!DpiLdaStartAdapterInChain
dxgkrnl!DpiFdoStartAdapter
読み解くと「グラフィックスドライバがアダプタの起動中にリソース確保に失敗し、自分から降りた」という意味になります。電源が落ちたわけでも、PCIeのリンクエラーが出たわけでもありません。実際、この期間中のWHEA(ハードウェアエラー)の記録はゼロでした。
STATUS_INSUFFICIENT_RESOURCES
という名前から、つい「メモリが足りない」「BARが足りない」と考えたくなりますが、これはNTSTATUSとしてはかなり汎用的なコードです。ドライバ内部のどこかで必要な資源を確保できなかった、という以上のことは分かりません。
ここからは消去法でした。丸一日かけて、思いつく限りのソフトウェア側の変数を潰しました。結果は次のとおりです。
| 試したこと | 内容 | 結果 |
|---|---|---|
| ドライバ世代 | 552.44 / 566.36 / 610.47 / 610.88 をそれぞれクリーンインストール | 変わらず |
| Windows更新 | 正常な別マシンと同じビルドまで更新 | 変わらず |
| BIOS更新 | AGESA 1.2.0.E → 1.2.0.F | 変わらず |
| BIOS設定 | 全初期化、SR-IOV無効、Data Link Feature Exchange無効、XMP無効、PBO無効 | 変わらず |
| メモリ整合性(HVCI) | 完全に停止して再起動 | 変わらず |
| ブートの種類 | コールドブート / ウォームリブート | 変わらず |
| モニターの接続先 | どちらのカードに挿しても | 変わらず |
| 2枚目の無効化 | デバイスマネージャーで無効にしても、物理的に挿さっているだけで1枚目のブート初期化が失敗 | 変わらず |
| 割り込みとリソース | MSI割り込み正常、BARと親ブリッジの窓を全段実測して正常を確認 | 割当は正しいのに失敗 |
ドライバの世代で唯一違ったのは、壊れ方の行儀だけでした。
新しいドライバほど「中途半端な状態を許さない」設計になっているように見えました。どれも問題そのものは直しません。
結論から言うと、2枚とも CPU直結のスロットに並べ替えたら、それだけで全部直りました。
このマザーボードには、CPU直結のx16スロットが2本(2枚挿すとx8/x8に分かれる)と、チップセット経由のスロットがあります。もともと2枚目はチップセット側に挿さっていました。これを、CPU直結の2本に並べ替えたわけです。
問題は、CPU直結の2本目には10GbEのネットワークカードが挿さっていたことでした。そこで、そのNICを撤去してオンボードの2.5GbEに降りる、という妥協をしています。
結果は劇的でした。
nvidia-smi が2枚を正常に列挙cuInit() が成功し、CUDAデバイスが2個見える同じドライバ、同じBIOS、同じOS、同じカード、同じ電源です。GPUについて変えたのは、挿す場所だけでした。
ここは推測を含みます。確実に言えるのは「この構成では動かず、CPU直結にしたら動いた」という事実だけで、原因そのものを実測で確定したわけではありません。
そのうえで、筆者の理解はこうです。
チップセット配下のPCIeスロットは、電気的にPCIeの信号が来ているだけで、CPU直結のスロットとは性質が違います。CPUとの間にチップセットという中継が1段入り、その先の帯域はNVMe SSDやUSB、SATAなど他のデバイスと共有されます。レイテンシもトポロジも、ファームウェアから見た扱いも別物です。「挿さって認識するから同じ」ではありません。
そして今回の決定的な違いは、2枚が同じ型番(同じPCIデバイスID)のGPUになったことでした。異なるカード(3090と3080)の組み合わせでは、ドライバは2枚を独立した別物として初期化していたのだと思います。ところが同一デバイスIDのGPUが2枚見えると、ドライバはマルチGPUとしてまとめて扱う経路に入ります。そこで、CPU直結とチップセット経由という非対称なトポロジが問題になったのではないか、という見立てです。
傍証として、世の中で「RTX 3090を2枚で動かしている」構成は、ほぼ例外なくCPU直結のx8/x8です。コミュニティを調べても、チップセット側に2枚目を挿した構成では、Code 43になる報告や、動いたとしても長いコンテキストで出力が壊れるという報告(llama.cppのissue)が見つかりました。
一方で、Linuxでは同じようなチップセット配下の構成が動いている報告もあります。つまりこれはWindowsのドライバ固有の相性という面もありそうです。ただし「OSを選べば動く」を前提にした構成は、あまり安全な賭けではないと思います。
CPU直結の2本に並べる、と言葉にするのは簡単ですが、ここにも罠がありました。
このマザーボードのCPU直結スロットは、**3スロット間隔(60.96mm)**で配置されています。2019年の設計で、当時の2スロット厚のカードを2枚並べる前提です。
ところが載せようとしたカードの厚みは、片方が61mm(3.002スロット)、もう片方が57mmでした。厚い方を上に置くと、計算上の隙間はマイナスになります。つまり物理的に当たります。
薄い57mmのカードを上にすると、下のカードのバックプレートまで数mmの隙間ができます。今回はこの順序で、ファンが干渉しないことを確認したうえで組みました。実際に測ってみるまでは「どちらの順でも入るだろう」と思っていたので、これは事前に計算しておくべきでした。

スロットの間隔は「ブラケット1枚が20.32mm」で計算できます。手持ちのカードの公称寸法(メーカーのスペック表に厚みがmmで載っています)と突き合わせれば、挿す前に判断できます。
当然、この配置では上のカードの吸気がかなり苦しくなります。そこで電力制限を掛けました。全負荷で40分ほど回したところ、上のカードはファン98%で84〜85℃の平衡に落ち着きました。余裕があるとは言えませんが、実用範囲には収まっています。
同じように「GPUを増やしたら動かなくなった」という場合、次の順で見ると切り分けが早いと思います。
Code 0は「表示ドライバが起動した」以上の意味を持ちません。計算まで生きているかは、別に確かめる必要があります。
nvidia-smi
これで全部のカードが列挙されるか。さらに確実なのは、CUDAの初期化そのものを叩いてみることです。CUDA
Toolkitを入れなくても、ドライバに含まれる nvcuda.dll
を直接呼べば確認できます。
$src = @'
using System;
using System.Runtime.InteropServices;
public static class Cuda {
[DllImport("nvcuda.dll")] public static extern int cuInit(uint flags);
[DllImport("nvcuda.dll")] public static extern int cuDeviceGetCount(out int count);
}
'@
Add-Type -TypeDefinition $src
$rc = [Cuda]::cuInit(0)
$n = 0
[Cuda]::cuDeviceGetCount([ref]$n) | Out-Null
"cuInit=$rc devices=$n"
cuInit=0
で、デバイス数が実際の枚数と一致していれば本物です。
「Resizable BARを有効にしたから大丈夫」ではなく、割り当てられた結果を見ます。
pnputil /enum-devices /class Display /resources
これで各カードのメモリ窓が見えます。24GBクラスのカードなら、16MB程度の小さい窓と、256MB(ReBAR有効なら数GB〜数十GB)の大きな窓が並ぶはずです。大きい窓が割り当てられていないカードは、そこで既に詰んでいます。
実は今回、最初にぶつかったのはこれとは別の問題でした。Resizable BARを有効にしていると、このマザーボードのファームウェアがチップセット側のブリッジに17〜24MBしかアドレス窓を用意せず(必要なのは300MB以上)、2枚目の大きなBARが割り当てられないままになっていたのです。Resizable BARを無効にしてAbove 4G Decodingだけ有効にしたら、両方に正しく割り当たりました。
イベントログが無言でも、グラフィックスカーネルがダンプを残していることがあります。
C:\Windows\LiveKernelReports\
C:\ProgramData\Microsoft\Windows\WER\ReportQueue\
0x1B0 の Arg1=2 は「Start device
failed」、Arg2
がドライバの返したNTSTATUSです。ここまで分かると、電源やケーブルを疑う段階は終わります。
pnputil /remove-device "<インスタンスID>"
pnputil /scan-devices
1枚だけ再初期化して、60秒ほど待ってからもう1枚。これで通るなら、同時初期化の競合という線が濃くなります。ただし今回のように、通っても計算が死んでいる場合があるので、必ず手順1まで確認してください。
ここまでで直らなければ、トポロジを疑う番です。マザーボードのマニュアルで、どのスロットがCPU直結で、どのスロットがチップセット経由かを確認します。仕様表には「x16/x8+x8(CPU)」「x4(チップセット)」のように書かれています。
移す前に、前の章で書いたスロット間隔とカード厚の計算をしておくことをおすすめします。
今回の結論はこうです。
カードも電源もドライバも悪くありませんでした。同じ型番のGPUを2枚使うとき、片方をチップセット経由のスロットに挿していたことが問題でした。
ソフトウェア側で丸一日かけて潰した変数——ドライバ4世代、OS更新、BIOS更新、BIOS設定の全初期化、メモリ整合性——は、どれひとつとして症状を変えませんでした。結果として、それらの作業は「ソフトウェアでは直らない」ことを確かめるための消去法になりました。
マルチGPUを組むときは、挿す場所をカードの選定と同じくらい真剣に考える価値があります。特に同じ型番を並べる場合は、CPU直結のレーンに対称に置ける構成を最初から選んだほうが安全です。
最後に、ひとつ注意を書いておきます。この記事で解決したのは、あくまでGPUの初期化が失敗する問題です。このマシンには組み立て当初から続く別の不安定さ(重い処理中の突然の再起動など)があり、そちらはこの変更とは無関係に残りました。ひとつの症状が直ったことを、マシン全体が健康になった証拠と混同しないようにしています。
]]>作業自体は、デバッグモードにしたスマホをUSBでつなぎ、Codex GPT-5.5 xhighに自由にadbコマンドをたたいてもらって95%くらい自律的にやってもらいました。
もし役立てばということで記録します。
Xiaomi Smart Band 7 は小さくて軽く、電池持ちもよく、日常的に着けっぱなしにしやすいスマートバンドです。
ただ、歩数、心拍、睡眠、SpO2 などのデータは基本的に Zepp Life や Mi Fitness のアプリ内で見る前提になっています。自分の用途では、これを外へ出して、InfluxDB に蓄積し、Grafana で長期的に見たいという目的がありました。
今回の方針はこうです。
全体像はこうなりました。
flowchart LR
band["Xiaomi Smart Band 7"] -->|Bluetooth| phone["Galaxy S24 Ultra<br/>Gadgetbridge"]
phone -->|Auto fetch activity data| gbdb["Gadgetbridge internal DB"]
gbdb -->|Auto export database| exportdb["/sdcard/Documents/Gadgetbridge/Gadgetbridge.db"]
exportdb -->|Syncthing-Fork| server["Orion<br/>Syncthing receive-only folder"]
server --> importer["gb2influx<br/>Docker container"]
importer --> influx["InfluxDB"]
influx --> grafana["Grafana dashboard"]スマホは Bluetooth 同期と DB エクスポートだけを担当し、InfluxDB token を持たせない構成にしました。Python で DB を読んで InfluxDB に書き込む処理は、Android ではなく Linux サーバー側で動かします。
今回確認した環境です。
Phone: Galaxy S24 Ultra
Android: 16
Model: SM-S928Q
Band: Xiaomi Smart Band 7
Gadgetbridge: nodomain.freeyourgadget.gadgetbridge 0.91.1
Zepp Life: com.xiaomi.hm.health 6.16.0
ADB: Windows platform-tools from WSL
ADB は WSL から Windows 側の platform-tools を呼び出しました。
/mnt/c/Software/platform-tools/adb.exe
Smart Band 7 を Gadgetbridge で使うには、Huami/Xiaomi の auth key が必要です。
この auth key を取得する前に Zepp Life アプリ側でバンドを解除すると、キーが無効になり、やり直しになります。Android の Bluetooth 設定からペアリング情報を消すことと、Zepp Life アプリ内でデバイス解除することは別物です。
今回やったこと、やらなかったことはこうです。
OK:
- Zepp Life を強制停止する
- Zepp Life を disable-user で無効化する
- Gadgetbridge 接続成功後に Zepp Life を止める
- Android OS 側の Bluetooth ペアリング情報を必要に応じて整理する
NG:
- auth key 取得前に Zepp Life アプリ内でバンドを解除する
- バンド本体を初期化する
Auth key 自体はこの記事には載せません。
auth key を取得し、Gadgetbridge 側へ移行する準備ができたので、Zepp Life を強制停止して無効化しました。
/mnt/c/Software/platform-tools/adb.exe shell am force-stop com.xiaomi.hm.health
/mnt/c/Software/platform-tools/adb.exe shell pm disable-user --user 0 com.xiaomi.hm.health
無効化されていることを確認します。
/mnt/c/Software/platform-tools/adb.exe shell cmd package list packages -d
実行結果です。
package:com.xiaomi.hm.health
戻す場合は以下です。
/mnt/c/Software/platform-tools/adb.exe shell pm enable com.xiaomi.hm.health
Gadgetbridge 側では、次の流れで Smart Band 7 を追加しました。
接続後、Gadgetbridge のメイン画面に Smart Band 7 が表示され、歩数、距離、睡眠などの値が出るようになりました。
この時点で、Zepp Life は「アプリ内で解除した」のではなく、Android 側で止めただけです。
Gadgetbridge では、まずアクティビティデータの自動取得を有効化しました。
Settings -> Automations -> Auto fetch activity data
これは、バンドからスマホ上の Gadgetbridge へデータを取り込む設定です。サーバーへの送信ではありません。
次に DB の自動エクスポートを有効化しました。
Settings -> Automations -> Auto export database
エクスポート先は以下にしました。
/sdcard/Documents/Gadgetbridge/Gadgetbridge.db
Android のパスとしては、こちらと同じ場所です。
/storage/emulated/0/Documents/Gadgetbridge/Gadgetbridge.db
ここで重要なのは、Auto export database はあくまで「スマホ内の通常ストレージへ SQLite DB を書き出す」だけということです。サーバーへ自動で送るには、Syncthing などの別アプリが必要です。
エクスポート間隔は最終的に 3 時間にしました。健康データの可視化であればリアルタイム性はそこまで必要ないので、まずはこのくらいで十分と判断しました。反映遅延は、ざっくり「Gadgetbridge のエクスポート間隔 + Syncthing 同期 + importer の実行間隔」です。
ADB で、スマホ内に DB が出ているか確認しました。
/mnt/c/Software/platform-tools/adb.exe shell '
for d in /sdcard/Documents/Gadgetbridge /storage/emulated/0/Documents/Gadgetbridge; do
echo DIR:$d
ls -la "$d" 2>&1
done
' | tr -d '\r'
実行結果です。
DIR:/sdcard/Documents/Gadgetbridge
total 2740
-rw-rw---- 1 u0_a293 media_rw 2805760 2026-06-13 18:40 Gadgetbridge.db
DIR:/storage/emulated/0/Documents/Gadgetbridge
total 2740
-rw-rw---- 1 u0_a293 media_rw 2805760 2026-06-13 18:40 Gadgetbridge.db
DB ファイルを探す場合は、こうしました。
/mnt/c/Software/platform-tools/adb.exe shell '
find /sdcard/Documents -maxdepth 4 \( -iname "*gadget*" -o -iname "*.zip" -o -iname "*.db" \) -print 2>/dev/null
' | tr -d '\r'
実行結果です。
/sdcard/Documents/Gadgetbridge
/sdcard/Documents/Gadgetbridge/Gadgetbridge.db
初回確認では、Syncthing ではなく ADB で直接 DB を取り出しました。
adb pull で WSL
側のパスにうまく書けないことがあったので、今回は
exec-out cat を使いました。
rm -rf /tmp/gadgetbridge-check
mkdir -p /tmp/gadgetbridge-check
/mnt/c/Software/platform-tools/adb.exe exec-out \
cat /sdcard/Documents/Gadgetbridge/Gadgetbridge.db \
> /tmp/gadgetbridge-check/Gadgetbridge.db
ls -lh /tmp/gadgetbridge-check/Gadgetbridge.db
sqlite3 /tmp/gadgetbridge-check/Gadgetbridge.db 'PRAGMA integrity_check;'
sqlite3 /tmp/gadgetbridge-check/Gadgetbridge.db '.tables'
実行結果です。
-rw-r--r-- 1 ctrluser ctrluser 2.7M Jun 13 18:41 /tmp/gadgetbridge-check/Gadgetbridge.db
ok
Smart Band 7 のデータは、今回の DB では XIAOMI_*
系ではなく、主に HUAMI_* 系のテーブルに入っていました。
件数はこうでした。
65172 HUAMI_EXTENDED_ACTIVITY_SAMPLE
1599 HUAMI_SPO2_SAMPLE
993 HUAMI_STRESS_SAMPLE
40 HUAMI_PAI_SAMPLE
26 BATTERY_LEVEL
6 HUAMI_HEART_RATE_RESTING_SAMPLE
一方で、以下のテーブルは空でした。
XIAOMI_ACTIVITY_SAMPLE
XIAOMI_SLEEP_STAGE_SAMPLE
XIAOMI_SLEEP_TIME_SAMPLE
XIAOMI_DAILY_SUMMARY_SAMPLE
XIAOMI_MANUAL_SAMPLE
MI_BAND_ACTIVITY_SAMPLE
ここは Gadgetbridge
のバージョン、デバイス、移行経路によって変わる可能性があるので、最初に
.tables と PRAGMA table_info(...)
を見るのが安全です。
メインの活動データは HUAMI_EXTENDED_ACTIVITY_SAMPLE
に入っていました。
sqlite3 /tmp/gadgetbridge-check/Gadgetbridge.db \
'PRAGMA table_info(HUAMI_EXTENDED_ACTIVITY_SAMPLE);'
カラムは以下です。
TIMESTAMP
DEVICE_ID
USER_ID
RAW_INTENSITY
STEPS
RAW_KIND
HEART_RATE
UNKNOWN1
SLEEP
DEEP_SLEEP
REM_SLEEP
データ範囲と件数を確認しました。
sqlite3 -header -column /tmp/gadgetbridge-check/Gadgetbridge.db "
SELECT
datetime(MIN(TIMESTAMP),'unixepoch','localtime') AS first_time,
datetime(MAX(TIMESTAMP),'unixepoch','localtime') AS last_time,
COUNT(*) AS rows,
SUM(STEPS) AS total_steps,
SUM(CASE WHEN HEART_RATE > 0 AND HEART_RATE != 255 THEN 1 ELSE 0 END) AS valid_hr_rows
FROM HUAMI_EXTENDED_ACTIVITY_SAMPLE;
"
実行結果です。
first_time last_time rows total_steps valid_hr_rows
2026-04-29 11:58:00 2026-06-13 18:10:00 65172 98252 14867
日別に見るとこうです。
sqlite3 -header -column /tmp/gadgetbridge-check/Gadgetbridge.db "
SELECT
date(TIMESTAMP,'unixepoch','localtime') AS day,
SUM(STEPS) AS steps,
COUNT(*) AS rows,
SUM(CASE WHEN HEART_RATE > 0 AND HEART_RATE != 255 THEN 1 ELSE 0 END) AS valid_hr_rows,
MIN(NULLIF(HEART_RATE,255)) AS min_hr,
MAX(CASE WHEN HEART_RATE != 255 THEN HEART_RATE END) AS max_hr
FROM HUAMI_EXTENDED_ACTIVITY_SAMPLE
GROUP BY day
ORDER BY day DESC
LIMIT 10;
"
実行結果の一部です。
day steps rows valid_hr_rows min_hr max_hr
2026-06-13 2283 1091 609 55 141
2026-06-12 9 1440 348 56 111
2026-06-11 9 1440 324 56 113
2026-06-10 30 1440 341 54 102
2026-06-09 25 1440 388 53 106
2026-06-08 3459 1440 607 51 134
直近の有効な心拍行も見ました。
sqlite3 -header -column /tmp/gadgetbridge-check/Gadgetbridge.db "
SELECT
datetime(TIMESTAMP,'unixepoch','localtime') AS local_time,
STEPS,
HEART_RATE,
RAW_KIND,
RAW_INTENSITY
FROM HUAMI_EXTENDED_ACTIVITY_SAMPLE
WHERE HEART_RATE > 0 AND HEART_RATE != 255
ORDER BY TIMESTAMP DESC
LIMIT 20;
"
実行結果の一部です。
local_time STEPS HEART_RATE RAW_KIND RAW_INTENSITY
2026-06-13 17:44:00 0 141 67 52
2026-06-13 17:43:00 31 140 64 63
2026-06-13 17:42:00 102 125 64 94
HEART_RATE = 255 は心拍なしとして扱い、InfluxDB
へは入れないようにしました。
SpO2 は HUAMI_SPO2_SAMPLE に入っていました。
sqlite3 /tmp/gadgetbridge-check/Gadgetbridge.db \
'PRAGMA table_info(HUAMI_SPO2_SAMPLE);'
TIMESTAMP
DEVICE_ID
USER_ID
TYPE_NUM
SPO2
ここで注意が必要なのは、TIMESTAMP
が13桁のミリ秒タイムスタンプだったことです。Unix 秒にするには
/1000 します。
sqlite3 -header -column /tmp/gadgetbridge-check/Gadgetbridge.db "
SELECT
datetime(TIMESTAMP/1000,'unixepoch','localtime') AS local_time,
TYPE_NUM,
SPO2
FROM HUAMI_SPO2_SAMPLE
ORDER BY TIMESTAMP DESC
LIMIT 10;
"
実行結果の一部です。
local_time TYPE_NUM SPO2
2026-06-13 12:55:30 0 97
2026-06-13 12:50:30 0 98
2026-06-13 12:45:30 0 97
2026-06-13 12:40:30 0 94
ストレスは HUAMI_STRESS_SAMPLE
です。これもミリ秒タイムスタンプでした。
sqlite3 -header -column /tmp/gadgetbridge-check/Gadgetbridge.db "
SELECT
datetime(TIMESTAMP/1000,'unixepoch','localtime') AS local_time,
TYPE_NUM,
STRESS
FROM HUAMI_STRESS_SAMPLE
ORDER BY TIMESTAMP DESC
LIMIT 10;
"
local_time TYPE_NUM STRESS
2026-06-13 13:06:00 1 10
2026-06-13 12:56:00 1 29
2026-06-13 12:51:00 1 20
安静時心拍は HUAMI_HEART_RATE_RESTING_SAMPLE です。
sqlite3 -header -column /tmp/gadgetbridge-check/Gadgetbridge.db "
SELECT
datetime(TIMESTAMP/1000,'unixepoch','localtime') AS local_time,
UTC_OFFSET,
HEART_RATE
FROM HUAMI_HEART_RATE_RESTING_SAMPLE
ORDER BY TIMESTAMP DESC
LIMIT 10;
"
local_time UTC_OFFSET HEART_RATE
2026-06-13 12:56:00 32400000 61
2026-06-12 23:59:00 32400000 61
2026-06-11 23:59:00 32400000 60
バッテリーは BATTERY_LEVEL に入っていました。こちらは Unix
秒でした。
sqlite3 -header -column /tmp/gadgetbridge-check/Gadgetbridge.db "
SELECT
datetime(TIMESTAMP,'unixepoch','localtime') AS local_time,
LEVEL,
BATTERY_INDEX
FROM BATTERY_LEVEL
ORDER BY TIMESTAMP DESC
LIMIT 10;
"
local_time LEVEL BATTERY_INDEX
2026-06-13 18:38:42 84 0
2026-06-13 18:37:42 83 0
2026-06-13 18:36:42 81 0
Gadgetbridge の DB エクスポートはスマホ内で完結します。そこで、Android 側に Syncthing-Fork を入れ、サーバー側の Syncthing と同期させました。
Android 側は以下です。
package: com.github.catfriend1.syncthingfork
version: 2.1.1.0
このときは F-Droid の APK を ADB で入れました。
wget -O /tmp/syncthingfork.apk \
https://f-droid.org/repo/com.github.catfriend1.syncthingfork_2010100.apk
/mnt/c/Software/platform-tools/adb.exe install "$(wslpath -w /tmp/syncthingfork.apk)"
通知権限やバックグラウンド動作まわりも ADB から設定しました。
/mnt/c/Software/platform-tools/adb.exe shell pm grant \
com.github.catfriend1.syncthingfork android.permission.POST_NOTIFICATIONS
/mnt/c/Software/platform-tools/adb.exe shell pm grant \
com.github.catfriend1.syncthingfork android.permission.ACCESS_FINE_LOCATION
/mnt/c/Software/platform-tools/adb.exe shell appops set \
com.github.catfriend1.syncthingfork MANAGE_EXTERNAL_STORAGE allow
/mnt/c/Software/platform-tools/adb.exe shell dumpsys deviceidle whitelist \
+com.github.catfriend1.syncthingfork
Android 側の Syncthing-Fork では、次のフォルダを共有しました。
label: Gadgetbridge
id: gadgetbridge
directory: /storage/emulated/0/Documents/Gadgetbridge
サーバー側は、Syncthing の gadgetbridge フォルダを
receive-only にしました。
id: gadgetbridge
label: Gadgetbridge
path inside container: /var/syncthing/Gadgetbridge
host path: /home/ctrluser/docker/gb2influx/data
type: receiveonly
receive-only にした理由は、Android/Gadgetbridge 側を正とし、サーバー側の古い DB をスマホへ押し戻さないためです。
同期後、サーバー側には以下のように DB が届きました。
ssh orion '
ls -la ~/docker/gb2influx/data
find ~/docker/gb2influx/data -maxdepth 1 -type f \
-printf "%f %s bytes %TY-%Tm-%Td %TH:%TM:%TS\n"
'
実行結果です。
Gadgetbridge.db 2805760 bytes 2026-06-13 09:40:15...
Linux 側の find では UTC 表示なので、これは Android 側の
2026-06-13 18:40 JST のエクスポートに対応します。
Syncthing のフォルダ状態も正常でした。
state: idle
globalFiles: 1
globalBytes: 2805760
localFiles: 1
localBytes: 2805760
needFiles: 0
needBytes: 0
InfluxDB 側は、最初から細かくやりすぎず、測定値ごとに measurement を分けました。
wearable_activity
tags:
source=gadgetbridge
device=xiaomi_smart_band_7
fields:
steps
heart_rate_bpm
raw_intensity
raw_kind
sleep
deep_sleep
rem_sleep
wearable_spo2
tags:
source=gadgetbridge
device=xiaomi_smart_band_7
type_num=<TYPE_NUM>
fields:
spo2_percent
wearable_stress
tags:
source=gadgetbridge
device=xiaomi_smart_band_7
type_num=<TYPE_NUM>
fields:
stress
wearable_resting_heart_rate
tags:
source=gadgetbridge
device=xiaomi_smart_band_7
fields:
heart_rate_bpm
wearable_battery
tags:
source=gadgetbridge
device=xiaomi_smart_band_7
battery_index=<BATTERY_INDEX>
fields:
level_percent
タイムスタンプの扱いは以下です。
HUAMI_EXTENDED_ACTIVITY_SAMPLE.TIMESTAMP: Unix seconds
BATTERY_LEVEL.TIMESTAMP: Unix seconds
HUAMI_SPO2_SAMPLE.TIMESTAMP: Unix milliseconds
HUAMI_STRESS_SAMPLE.TIMESTAMP: Unix milliseconds
HUAMI_HEART_RATE_RESTING_SAMPLE.TIMESTAMP: Unix milliseconds
Importer では、毎回直近 72 時間を再処理するようにしました。Gadgetbridge 側の同期遅延や睡眠データの後補正を拾うためです。
importer はスマホではなく、サーバー側で Docker コンテナとして動かしました。
配置はこうです。
Galaxy S24 Ultra
/sdcard/Documents/Gadgetbridge/Gadgetbridge.db
-> Syncthing-Fork
Orion
/home/ctrluser/docker/gb2influx/data/Gadgetbridge.db
/home/ctrluser/docker/gb2influx/app/gb2influx.py
/home/ctrluser/docker/gb2influx/state/state.json
-> writes to InfluxDB
InfluxDB / Grafana
wearable_* measurements
手動実行は以下です。
ssh orion 'cd ~/docker/gb2influx && docker compose run --rm -e RUN_ONCE=1 gb2influx'
常駐実行は以下です。
ssh orion 'cd ~/docker/gb2influx && docker compose up -d'
初回の historical import では、以下の結果になりました。
written_points=67796 db=/data/Gadgetbridge.db
Syncthing で DB が届いたあとの再実行では、以下です。
written_points=4685 db=/data/Gadgetbridge.db
2回目の件数が少ないのは、state file を持っていて、初回の全量投入後は直近ウィンドウだけを再処理しているためです。
InfluxDB 側では、以下の measurement が確認できました。
wearable_activity
wearable_battery
wearable_resting_heart_rate
wearable_spo2
wearable_stress
件数確認では、以下のようになりました。
wearable_activity 405899 field values
wearable_battery 26 field values
wearable_resting_heart_rate 6 field values
wearable_spo2 1599 field values
wearable_stress 993 field values
wearable_activity が SQLite
の行数より多く見えるのは、InfluxDB 側では field value
単位で数えているためです。1行の SQLite レコードから複数 field
が入るので、元の行数とは一致しません。
Grafana では、最初のダッシュボードとして以下を作りました。
uid: wearable-health-smart-band-7
title: Wearable Health - Smart Band 7
folder: Studio-managed
panels: 11
最初に見るパネルは、次のあたりで十分だと思います。
睡眠については、HUAMI_EXTENDED_ACTIVITY_SAMPLE に
SLEEP, DEEP_SLEEP, REM_SLEEP
のカラムがあります。ただ、これらの値の意味は Gadgetbridge の UI
表示と照合してから使った方がよさそうです。少なくとも最初のダッシュボードでは、睡眠ステージをきれいに解釈するより、就寝中の心拍、心拍の低下、起床前の上昇、データ欠損を見る方が安全だと思います。
2026-06-13 時点で、状態はこうです。
Gadgetbridge pairing: OK
Zepp Life: disabled, not unpaired
Auto fetch activity data: enabled
Auto export database: enabled
Exported DB: /sdcard/Documents/Gadgetbridge/Gadgetbridge.db
DB integrity_check: ok
Syncthing-Fork: Android -> Orion sync OK
Orion Syncthing folder: idle / in-sync
Importer: Docker container running
InfluxDB measurements: wearable_activity, wearable_battery, wearable_resting_heart_rate, wearable_spo2, wearable_stress
Grafana dashboard: deployed
残っている作業は、数日から1週間ほど運用して、同期抜け、エクスポート間隔、バッテリー消費、睡眠データの解釈を確認することです。
今回の構成で分かったのは、Xiaomi Smart Band 7 は Gadgetbridge との相性がかなりよく、買い替え前に試す価値が高いということです。
一方で、Gadgetbridge へつながっただけで自動的に InfluxDB まで流れるわけではありません。実際のワークフローは、バンド、Android、Gadgetbridge、DB export、Syncthing、importer、InfluxDB、Grafana の複数段に分かれます。
ただ、一度流れを作ってしまえば、各段階はかなり素直です。
特に重要なのは、最初に以下を切り分けて考えることでした。
Band -> Gadgetbridge:
Bluetooth pairing, auth key, activity sync
Gadgetbridge -> Android storage:
Auto export database
Android storage -> Server:
Syncthing-Fork
Server -> InfluxDB:
SQLite importer
InfluxDB -> Grafana:
Dashboard
ここを混ぜて考えると、「Gadgetbridge に入れたのに Grafana に出ない」のように見えますが、実際には途中にいくつも確認ポイントがあります。
Smart Band 7 をすでに持っていて、Zepp Life のアプリ内だけでデータが閉じるのが不満な場合は、まずこの Gadgetbridge ルートを試すのがよさそうです。
]]>原因は、古いSMB接続、ドライブ文字の競合、保存済み資格情報の不整合であることが多いです。PC再起動の前に、まずは問題のドライブ文字だけを切り直します。
コピペ用です。実行前に、先頭3行の
DRIVE、SHARE、USER
だけ自分の環境に合わせて書き換えてください。パスワードは画面に出る入力プロンプトで入れます。
CMD版です。コマンドプロンプトを開いて実行します。失敗する場合は管理者として開き直してください。
set DRIVE=Z:
set SHARE=\\NAS\share
set USER=NASユーザー名
net use %DRIVE% /delete /y
net use %DRIVE% "%SHARE%" /user:%USER% * /persistent:yes
PowerShell版です。資格情報ダイアログにNASのパスワードを入力します。
$drive = "Z:"
$share = "\\NAS\share"
$user = "NASユーザー名"
$cred = Get-Credential -UserName $user -Message "NASの資格情報"
Remove-SmbMapping -LocalPath $drive -UpdateProfile -Force -ErrorAction SilentlyContinue
New-SmbMapping -LocalPath $drive -RemotePath $share -Credential $cred -Persistent $true
最初から net use * /delete
を打たないのがポイントです。* /delete
は全ネットワーク接続を切断します。別NASや開いているファイルも巻き込むので、まずは問題のドライブだけを狙います。
Microsoftの資料では、このエラーはシステムエラー85、つまり「ローカルデバイス名がすでに使われている」状態です。ここでいうローカルデバイス名は、たいてい
Z: や X: などのドライブ文字です。
見た目どおりに「別のドライブが見えている」とは限りません。エクスプローラー上では切断済みに見えても、SMBクライアント側には古い接続が残ることがあります。逆に、USBメモリ、カードリーダー、仮想ドライブなどが同じ文字を使っていることもあります。
「NASが壊れた」というより、Windowsがそのドライブ文字を再利用できない状態、と考えると切り分けしやすくなります。
現在の接続を確認します。
net use
PowerShellなら次のように確認できます。
Get-SmbMapping
それでも「使用中」と言われる場合は、エクスプローラーやアプリがそのドライブを開いていないか確認してください。Microsoftの
net use
資料でも、現在のドライブとして使っていたり、プロセスが使用中だったりする共有は切断できないと説明されています。Excel、バックアップソフト、同期ツール、ターミナルのカレントディレクトリが原因になることがあります。
対象ドライブを削除しても残り続ける、net use
では見えないのに同じ文字で再接続できない、ログオフや再起動なら直る、という場合は、Workstation
サービス、つまり LanmanWorkstation を再起動します。
net stop workstation /y
net start workstation
注意点があります。この操作はSMBクライアント機能を一時停止するので、接続中のネットワークドライブや共有フォルダは切れます。共有上のファイルを閉じ、ほかの共有ドライブを使っていないタイミングで実行してください。
同じNASに入れるPCと入れないPCがある、パスワード変更後からおかしい、サーバー名とIPで挙動が違う、という場合は、古い資格情報を疑います。
GUIなら「資格情報マネージャー」から「Windows資格情報」を開き、対象のサーバー名やIPアドレスの資格情報を削除します。その後、正しいユーザー名とパスワードで再接続します。
コマンドで確認するなら、次のように保存済み資格情報を一覧できます。
cmdkey /list
削除は対象名を確認してから行います。別サービスの認証情報を消さないように注意してください。
cmdkey /delete:NAS名またはIPアドレス
net use や Get-SmbMapping に出てこないのに
Z: が使えない場合は、diskmgmt.msc
で「ディスクの管理」を開き、USBドライブ、カードリーダー、仮想ディスクなどに同じ文字がないか確認します。
競合しているローカルディスクがある場合は、そのローカルデバイス側のドライブ文字を変更するのが基本です。ネットワークドライブには、普段ローカル機器に使われにくい
X:、Y:、Z:
などを使うと衝突しにくくなります。
Windows側を直しても再接続できないなら、NASやSamba側を確認します。特に見るべきなのは次の点です。
\\NAS\share が正しいか名前解決が怪しいときは、サーバー名ではなくIPアドレスで
\\192.168.1.10\share
のように試すと切り分けできます。ただし、同じサーバーへ別名・別ユーザーで同時接続すると別の認証エラーを招くことがあるので、先に既存接続を削除しておきます。
net use * /delete /y
は、全ネットワーク接続をまとめて切る強い操作です。どうしても対象ドライブだけでは解消しない場合に限り、開いている共有ファイルを閉じてから実行します。
net use * /delete /y
レジストリ変更はさらに慎重に扱うべきです。Microsoftのシステムエラー85の記事には
ProtectionMode
というレジストリ値に触れた回避策がありますが、古いWindows/Terminal
Server文脈を含む限定的な説明です。Windows
10/11の一般的な復旧手順として最初に触る場所ではありません。
レジストリを変更する場合は、必ずバックアップを取り、変更対象と理由を記録してから行ってください。原因が単なる残留接続や資格情報なら、レジストリ編集まで進む必要はありません。
バックアップソフトやWindowsサービスからNASにアクセスする場合は、Z:\backup
のような割り当て済みドライブではなく、\\NAS\share\backup
のUNCパスを使うのが基本です。
Microsoftは、サービスが実行時に net use
でドライブ文字を割り当てる方法を推奨していません。ドライブマッピングはログオンセッションごとに管理されるため、ユーザーが見ている
Z: とサービスが見ている Z:
が一致しないことがあります。
このエラーは、まず「問題のドライブ文字だけを消して、同じパスへ作り直す」のが最短です。それで直らない場合は、Workstationサービス、資格情報、ディスク管理のドライブ文字、NAS/Samba側の順に切り分けます。
再起動は確かに効くことがありますが、原因を残したままだとまた起きます。net use * /delete
やレジストリ編集のような広い操作は最後に回し、対象を絞って直すのが安全です。





Windows 11のマルチGPU環境で、ローカルLLMを実行しているときに奇妙な症状に遭遇しました。
nvidia-smiでは2枚のGPUにしっかり負荷がかかっているのに、Windowsのタスクマネージャーでは片方のGPU使用率だけが0%のままになっていました。
今回の構成は、ざっくり言うと次のようなものです。
最終的には、HAGS(ハードウェア アクセラレータによる GPU スケジューリング)を無効化して再起動したところ、タスクマネージャー側でもCUDA負荷が見えるようになりました。
このPCは過去にGPU構成を何度か入れ替えていました。
そのため、まず疑ったのはWindows上に残っている古いGPUの履歴です。Device Managerで「非表示のデバイスを表示」したときに出てくる、いわゆる phantom device / non-present device です。
PowerShellでは次のように確認できます。
Get-PnpDevice -Class Display
実際、過去に使っていたGPUや、同じGPUを別スロットに挿していたときのデバイスインスタンスが
CM_PROB_PHANTOM として残っていました。
不要なphantom deviceは、管理者権限のPowerShellなどから
pnputil で削除できます。
pnputil /remove-device "PCI\VEN_...."
この作業で、Device
Manager相当の表示は現在刺さっている2枚のGPUだけになりました。BlenderのCycles設定にも古いGPUが残っていたので、userpref.blend
のバックアップを取った上でデバイス一覧を再生成しました。
その結果、BlenderやDevice Manager上のGPU一覧は正常になりました。
しかし、タスクマネージャーで片方のGPU使用率が見えない問題はまだ残りました。
ローカルLLMで負荷をかけると、nvidia-smiでは両方のGPUが使われていました。
実測では、おおよそ次のような状態でした。
RTX 3080: GPU使用率 約40%, VRAM 約9.7GB, 消費電力 約150W
RTX 3090: GPU使用率 約60%, VRAM 約19GB, 消費電力 約340W
つまり、GPUが使われていないわけではありません。
一方で、タスクマネージャーやWindowsのGPU Engine counterでは、片方のGPUだけ使用率が0%に見えていました。
Windows側のカウンタは次のように確認できます。
Get-Counter '\GPU Engine(*)\Utilization Percentage'
この時点では、GPUメモリ使用量は見えているのに、GPU Engine utilizationだけが0%という状態でした。
ここで重要なのは、Windowsのタスクマネージャーと nvidia-smi
は同じ計測経路を見ているわけではない、ということです。
Microsoftの説明では、タスクマネージャーのGPU表示はWDDMのGPU schedulerやvideo memory manager由来の情報を使っています。
参考:

一方、nvidia-smiはNVIDIAのNVML側からGPU使用率を見ています。
参考:
そのため、CUDAやローカルLLMのようなcompute系の負荷では、nvidia-smiでは見えているのにタスクマネージャーでは見えない、ということが起き得ます。
特に今回のように、片方のGPUがヘッドレス、つまり表示出力を持っていない状態では、Windows側のWDDM GPU Engine counterがうまく出ないことがあるようです。
HAGSは、Hardware-Accelerated GPU Schedulingの略です。
日本語のWindows設定では「ハードウェア アクセラレータによる GPU スケジューリング」と表示されます。
設定場所は次のあたりです。
設定
→ システム
→ ディスプレイ
→ グラフィック
→ 既定のグラフィック設定
→ ハードウェア アクセラレータによる GPU スケジューリング
ざっくり言うと、GPUに投げる仕事の順番管理を、従来よりGPUやドライバ側に寄せる機能です。ゲームではレイテンシやCPU負荷が改善することがあります。
一方で、CUDA、録画、リモート操作、マルチGPU監視などでは、環境によってGPU使用率表示や互換性に影響することがあります。
今回の環境では、HAGSを無効化して再起動したところ、Windows側のGPU Engine counterにCUDA負荷が出るようになりました。
無効化後、同じローカルLLM負荷をかけると、Get-Counterで次のように
engtype_cuda が見えるようになりました。
GPU A: engtype_cuda 約40%
GPU B: engtype_cuda 約53%
nvidia-smi側でも両GPUに負荷が出ており、タスクマネージャー側の表示とも整合するようになりました。
つまり今回の問題は、GPU本体の故障ではありませんでした。
また、phantom deviceの残骸だけが原因でもありませんでした。
最終的には、HAGS有効時に、ヘッドレス側GPUのCUDA engine使用率がWindowsのタスクマネージャーに出ていなかった、という見立てです。
HAGSを無効化すると、GPUスケジューリングは従来方式に戻ります。
あり得る影響としては、次のようなものがあります。
今回のPCはゲーム用メインマシンではなく、ローカルLLMやBlenderなどのGPU作業用サブ機です。そのため、HAGSは無効のまま運用することにしました。
同じように「GPUは動いているはずなのにタスクマネージャーで使用率が見えない」という場合、次の順番で確認するとよさそうです。
まずはタスクマネージャーではなく、nvidia-smiで確認します。
nvidia-smi
ここでGPU使用率、VRAM使用量、温度、消費電力が出ていれば、GPU自体は動いています。
過去にGPUを差し替えている場合は、phantom deviceが残っていることがあります。
Get-PnpDevice -Class Display
CM_PROB_PHANTOM
の古いGPUが大量に残っている場合は、不要なものを整理する価値があります。ただし、削除対象を間違えると面倒なので、実際に刺さっているGPUのInstance
IDと照合してから作業したほうが安全です。
タスクマネージャーの元になっているカウンタは、次のように見られます。
Get-Counter '\GPU Engine(*)\Utilization Percentage'
CUDA負荷なら engtype_cuda や compute
系のカウンタに値が出ることがあります。
HAGSが有効で、特にマルチGPUやヘッドレスGPUで使用率が見えない場合は、HAGS無効化を試す価値があります。
UIから変更するのが一番安全です。
設定
→ システム
→ ディスプレイ
→ グラフィック
→ 既定のグラフィック設定
→ ハードウェア アクセラレータによる GPU スケジューリング
変更後は再起動が必要です。
レジストリで確認する場合は、次のキーを見ます。
Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\GraphicsDrivers' -Name HwSchMode
一般に HwSchMode=1 はHAGS無効、HwSchMode=2
はHAGS有効、値がない場合はWindowsの既定設定に従います。
片方のGPUが完全にヘッドレスの場合、HDMIやDisplayPortのダミープラグを挿すことで、Windows側の扱いが変わることがあります。
ただし、これは原因切り分けとしては有用ですが、常用する必要があるかは環境次第です。
また、NVIDIAドライバのバージョンによって挙動が変わる可能性もあります。古いドライバを使っている場合は、Studio Driverなどの更新も試す価値があります。
今回の結論はシンプルです。
GPUはちゃんと動いていた。タスクマネージャーが見えていなかった。原因はHAGSだった。
Windowsのタスクマネージャーは便利ですが、CUDAやローカルLLMの監視では万能ではありません。
特に、マルチGPU、ヘッドレスGPU、HAGS有効、CUDA/compute負荷の組み合わせでは、nvidia-smiやHWiNFO、MSI
Afterburnerなど、別系統の監視ツールも併用したほうがよさそうです。
ローカルLLMやBlender用のWindowsマシンでGPU使用率が変に見える場合は、HAGSを一度疑ってみる価値があります。
]]>かわいいのでまとめました。
「Gugugaga」は英語の “goo goo ga ga” から来ています。赤ちゃんのしゃべり方を表した言い方のようです。この音がミームとして爆発した経緯には諸説ありますが、中国語圏のゲームメディアでは、2025年にBilibiliで拡散した短いネタ動画『gugugaga』が転機として繰り返し言及されています。ここから「ペンギンっぽいものに咕咕嘎嘎の音を重ねる」という遊びが一気に広まったようです。
で、この流れに、ゲーム『Arknights: Endfield』の管理人(Endministrator)というキャラがなぜか混ざりました。このキャラがなんかペンギンっぽくみえることが理由のようです。

https://i.17173cdn.com/2fhnvk/YWxqaGBf/cms3/gRckjnbttbEoqAf.jpg

そしてAI動画ブームが到来し、このキャラクターをかわいくデフォルメしたキャラにGugugagaといわせるミームが発生しました。
情報源まとめ
WSL2はデフォルトでNAT構成になっており、同一PC内からはlocalhost
で届くものの、LAN上の別PCからはそのままでは届きません。
対処方法はいくつかありますが、今回はWindows 11で使えるmirrored networking modeを使う方法を選びました。portproxyを使う方法と比べて、WSL2のIPアドレスが再起動のたびに変わる問題を気にしなくていいのがメリットです。
ということで、手順をメモ。
WSLのターミナルで実行します。
sudo apt update
sudo apt install -y openssh-server
インストールできたら、systemdでサービスとして登録・起動します。
sudo systemctl enable ssh
sudo systemctl start ssh
起動確認:
systemctl status ssh
Active: active (running) と表示されればOKです。
デフォルトは22番ですが、将来的にWindows側にもsshd(22番)を入れることを想定して、WSL2側は2222番にしておきます。
/etc/ssh/sshd_config の #Port 22 をPort 2222 に変更します。
sudo sed -i 's/^#Port 22/Port 2222/' /etc/ssh/sshd_config
Ubuntu 24.04はsystemdのsocket
activationを使っているので、ポート変更後は以下の3コマンドが必要です。
sudo systemctl daemon-reload
sudo systemctl restart ssh.socket
sudo systemctl restart ssh
リッスンしているか確認:
ss -tlnp | grep 2222
0.0.0.0:2222 が表示されればOKです。
Windows側の設定です。
%USERPROFILE%\.wslconfig
に以下を追記します(ファイルがなければ新規作成)。
[wsl2]
networkingMode=mirrored
PowerShellから書き込む場合:
Add-Content "$env:USERPROFILE\.wslconfig" "`n[wsl2]`nnetworkingMode=mirrored"
追記したらWSLを再起動します。
wsl --shutdown
WSLのターミナルを開き直して、WindowsのLAN
IPが含まれているか確認します。
hostname -I
192.168.xxx.xxx が含まれていればmirrored
modeが有効になっています。
mirrored
modeではWindowsファイアウォールに加えて、Hyper-Vファイアウォールへの許可も必要です。これがないとLANからタイムアウトになります。
管理者権限のPowerShellで以下の2つを実行します。
# Hyper-Vファイアウォール
New-NetFirewallHyperVRule -Name "WSL2 SSH" -DisplayName "WSL2 SSH" -Direction Inbound -VMCreatorId "{40E0AC32-46A5-438A-A0B2-2B479E8F2E90}" -Protocol TCP -LocalPorts 2222
# Windowsファイアウォール
New-NetFirewallRule -Name "WSL2 SSH" -DisplayName "WSL2 SSH" -Direction Inbound -Protocol TCP -LocalPort 2222 -Action Allow
{40E0AC32-46A5-438A-A0B2-2B479E8F2E90}
はWSL用に固定されているGUIDです。以下のコマンドで確認できます。
Get-NetFirewallHyperVVMCreator
別PCから以下で接続できます。
ssh -p 2222 <ユーザー名>@<WindowsのLAN IP>
| 項目 | 設定内容 |
|---|---|
| WSL2ネットワーク | mirrored mode |
| WSL2 sshd | ポート2222 |
| Hyper-Vファイアウォール | TCP 2222 許可 |
| Windowsファイアウォール | TCP 2222 許可 |
portproxyと異なり、すべての設定が永続します。WSL再起動後も追加の操作は不要です。
]]>というネタ、たまにXで見ますよね。
こういうネタを作るためには、「映画の何時間何分目に、このイベントが起こる」というタイムコードを確認して、そのうえで「元旦0時0分からの逆算を計算する」必要があるので結構めんどくさい。間違えると取り返しつかないですからね。
なので計算機を作りました。
A graphics card and driver with support for OpenGL 4.3 or higher is required. Plugging all monitors into your primary graphics card might resolve this issue. Installing the latest driver for your graphics card could also help.
というメッセージが出て終了することがありました(GPU 自体は当然 OpenGL 4.3 以上対応のはずなのに、です)。
イベントログ上では Blender 側が ucrtbase.dll で 0xc0000005(アクセス違反)で落ちる記録(Application Error 1000 / WER 1001)が残ることがあり、また WER 側には LiveKernelEvent(WATCHDOG/141 など)が出ることもありました。一方で、典型的な TDR(Display 4101)や nvlddmkm 153 が常に出る、という形ではありませんでした。
この問題が厄介だったのは、再現性が安定しない点です。ある時は同じシーンで通り、再起動を挟むと同じ操作で落ちる、という挙動になりました。
以下、いろいろChatGPTと相談しながら試した記録を記事にしています。
今回のポイントは「デュアル GPU」だけではなく、モニタが複数枚で、両方の GPU に接続せざるを得ない構成だったことです。私の環境は6モニタなので、RTX 5090 は出力が 4 ポートのため物理的に全モニタを片方の GPU に集約することができません。
この前提があると、Windows と NVIDIA ドライバが「どちらの GPU で OpenGL コンテキストを作るか」を自動判定する場面で、意図しない選択が起きる余地が増えます。
以下を試しました。
Blender 側でデバイスを片方だけ有効にしてレンダリングし、さらに両方を有効にしてレンダリングも実施しました。両方を有効にした場合は、レンダリング時間が約半分(例:1分20秒→40秒程度)まで短縮し、計算自体は 2 枚使えていることが確認できました。ここから「2 枚構成そのものが常に破綻する」という状況ではないと判断しました。
Afterburner で 2 枚とも Power Limit を 80% にした状態でも OCCT の 3D 負荷を継続でき、電力・温度・安定性の観点では致命的な兆候は見えませんでした。さらに、Blender のレンダリング時間が PL 80% と 100% で大差が出ないケースもあり、少なくとも「PL が原因で Blender が落ちる」という説明は成立しにくい状況でした。
ポータブル版を使い、C:\Users\<User>\AppData\Roaming\Blender Foundation\Blender の設定を削除し、起動直後のデフォルトシーンでも試しました。しかし、それでも落ちる時は落ちました。したがって、特定アドオンや設定の破損だけが原因とも言い切れませんでした。
レンダリングを CPU に切り替えても落ちることがありました。ここが非常に重要で、これにより「CUDA/OptiX の計算部分だけの問題」ではなく、Blender が動作するための描画系(≒OpenGL 側)での問題を強く疑う方向に寄りました。
セーフモードで DDU を使い、LAN ケーブルを抜いてオフラインで Studio Driver を入れ直しました。一時的に改善したように見えましたが、再起動後に再発しました。つまり、これは「やる価値はあるが、今回の決定打ではなかった」という位置づけでした。
iGPU(AMD Ryzen 内蔵 GPU)をデバイスマネージャで無効化し、再起動しましたが、問題は継続しました。よって今回の主因が iGPU の混在だと断定するのは難しい状態でした。
NVIDIA コントロールパネルで、グローバル設定の 「OpenGL レンダリング GPU (OpenGL rendering GPU)」 を
へ変更したところ、Blender のエラーが止まり、少なくとも同一条件での再現ができなくなりました。現状、これが最も説明力の高い「解決策」になりました。

Blender は UI とビューポート描画を OpenGL で行います。したがって「レンダリングボタンを押した瞬間」に落ちたように見えても、その瞬間に OpenGL が関与する処理は十分にあり得ます。たとえば、レンダリング開始時には進捗 UI 更新、レンダー結果の表示領域の更新、描画コンテキストの再初期化、ウィンドウ移動・モニタ跨ぎに伴うコンテキスト再確立などが起こり得ます。
また、CPU レンダリングでも落ちることがあった点は、「計算(CUDA/OptiX/CPU)ではなく、表示系(OpenGL)起点のクラッシュ」が混ざっている説明と整合します。
モニタが複数で両方の GPU に接続されていると、アプリのウィンドウがどのモニタ上にあるか、OS がどのアダプタを優先として扱うか、ドライバの自動判定がどう働くか、という条件が絡みます。
この自動判定が不安定だったり、特定条件で誤った GPU を選ぶ(あるいは OpenGL コンテキスト作成が失敗する)と、アプリ側は「OpenGL 4.3 以上が必要」という“能力不足に見えるエラー”として落ちることがあります。実際には GPU の能力不足ではなく、OpenGL コンテキストが期待どおりに作れなかったことが本質です。
そこで OpenGL rendering GPU を固定すると、OpenGL の担当 GPU がブレなくなり、コンテキスト作成・維持の条件が安定します。今回の改善はこの説明が最も自然です。
この挙動も矛盾ではありません。OpenGL rendering GPU を固定した場合、ウィンドウがどの GPU に接続されたモニタ上にあっても、OpenGL の実処理は指定 GPU で行われる(または少なくとも指定 GPU を優先)という形になります。そのため「非指定側モニタで操作したら必ず落ちる」ということにはなりません。
基本的には、次の方針が合理的です。
OpenGL は UI/ビューポートの体感にも関わるため、普段使いの滑らかさや安定性の観点では「メイン側に寄せる」判断が筋が通ります。
使えます。OpenGL rendering GPU の設定は、主に OpenGL の描画担当を決める設定です。一方、Blender の CUDA / OptiX レンダリングは、Blender 側の「使用デバイス設定」で 2 枚とも有効にしていれば、基本的に両方を使います。
実際にタスクマネージャー上で 2 枚とも使用率が上がっている観測があるなら、「計算に使われている」という理解で問題ありません。
グローバル固定は運用が楽で、今回のように「Auto の揺れが原因」っぽいケースでは有効です。一方、理屈上のデメリットは次の通りです。
ただし、挙げられている主要アプリ(Premiere Pro / After Effects / Photoshop / DaVinci Resolve)は、実務上は DirectX/CUDA 側が中心で、OpenGL rendering GPU の固定が直撃するケースは多くありません。したがって、「まずグローバルで固定して安定を取る」という判断は現実的です。
もし将来、特定アプリだけ違和感が出た場合は、その時点で「グローバルは Auto に戻し、Blender(blender.exe)だけプログラム設定で固定する」という運用に切り替えると、影響範囲を最小化できます。
今回の問題は、GPU 性能不足ではなく、デュアル GPU + 複数モニタ構成における OpenGL 担当 GPU の自動選択が不安定で、Blender の OpenGL コンテキストが破綻する瞬間があった、という整理が最も整合します。
DDU やドライバクリーンインストール、iGPU 無効化は「やるべき切り分け」ではありますが、決定打は NVIDIA コントロールパネルで OpenGL rendering GPU を固定することでした。これにより、Blender の UI/ビューポート(OpenGL)経路が安定し、レンダリング開始時のクラッシュが止まりました。
]]>自宅サーバ(Ubuntu)上で、Proxmox のような「全部入りハイパーバイザ」を入れずに、できるだけミニマルな構成で
という目的で環境を構築したときの手順メモ。
ポイントは次の 3 つ。
この記事は、試行錯誤でハマった部分は省き、「未来の自分が見てすぐ再現できる」ことを狙って整理している。
systemd-networkd + netplan(NetworkManager には切り替えない前提)ここでは、作業用 Windows 11 VM(以降 columbia)を作るところまでを対象とする。
まずは KVM / libvirt / virt-install / OVMF / swtpm を入れる。
sudo apt update
# 仮想化基盤(KVM + libvirt)+ UEFI(OVMF) + vTPM(Windows 11 等向け)
sudo apt install -y \
qemu-kvm libvirt-daemon-system libvirt-clients virtinst \
ovmf swtpm swtpm-tools libtpms0
ポイント:
qemu-kvm を指定しているが、Ubuntu 24.04 では裏で qemu-system-x86 が選択される。ovmf は UEFI ファームウェア(OVMF)を提供するパッケージ。swtpm + libtpms0 は vTPM 2.0 のエミュレータ。Windows 11 の TPM 要件を満たすために必須。Cockpit 本体と、VM 管理に必要なアドオンを backports から入れる。
. /etc/os-release
sudo apt install -y -t ${VERSION_CODENAME}-backports \
cockpit cockpit-machines cockpit-storaged cockpit-networkmanager
cockpit-machines … VM 管理 UI(libvirt のフロントエンド)cockpit-storaged … ストレージ状態の確認・操作cockpit-networkmanager … NetworkManager を使う場合のネットワーク管理 UIsystemd-networkd 続投なので、編集機能はあまり使わず、主に状態確認に留める)libvirtd は常駐、Cockpit はソケット起動にする。
sudo systemctl enable --now libvirtd cockpit.socket
ファイアウォールに UFW を使っている場合は 9090 番を開けておく。
sudo ufw allow 9090/tcp
非 root で VM を扱えるように、自分のユーザーを libvirt グループへ。
sudo usermod -aG libvirt $USER
いったんログアウト/ログインし直し、groups で libvirt が含まれていることを確認する。
qemu:///system の VM は libvirt-qemu ユーザーで動くため、通常ユーザーのホームディレクトリにある ISO はそのままでは読めない。
インストールメディアは libvirt 管理下のディレクトリに置くのが安全。
sudo mkdir -p /var/lib/libvirt/iso
ここに Windows 11 の ISO を配置する。例:
# 手元の PC からサーバへコピーする例(IP は適宜変更)
scp Win11_24H2_Japanese_x64.iso \
ctrluser@192.168.100.31:/var/lib/libvirt/iso/
sudo chown root:root /var/lib/libvirt/iso/Win11_24H2_Japanese_x64.iso
sudo chmod 644 /var/lib/libvirt/iso/Win11_24H2_Japanese_x64.iso
OS ディスク(qcow2)は、デフォルトの /var/lib/libvirt/images に作成する方針とする。
ブラウザから以下にアクセス。
https://<サーバIP>:9090/ (例:https://192.168.100.31:9090/)自己署名証明書の警告は許可する。
Ubuntu のユーザー(例:ctrluser)でログインしておく。
左側メニューの「Virtual Machines(仮想マシン)」を開くと、libvirt の System インスタンス(qemu:///system)が見えるようになる。
Cockpit の「Virtual Machines」画面で「Create new virtual machine」をクリックし、以下のように設定する。
columbia(小文字で統一)qemu:///system:サーバ側で常時管理される VM 群)Windows 11 が候補に出ればそれを選択Windows 10 か Generic Windows など近いものを選んでおけばよいqcow2(デフォルト)重要:
ウィザードの最後で 「Create and edit」(作成して編集する) を選び、
作成直後に詳細画面へ入れるようにしておく。
columbia の概要タブで、「Firmware」が UEFI になっていることを確認する。
もし BIOS になっていれば UEFI に変更する。
OVMF のエントリが複数ある場合は、可能なら Secure Boot 付きのものを選ぶ。
例:
UEFI x86_64: /usr/share/OVMF/OVMF_CODE.secboot.fd ...が選べるならこれを使う。
ovmf + swtpm を入れておけば、libvirt は vTPM 2.0 を扱える。
Cockpit に TPM 追加 UI がない場合でも、libvirt の XML に直接書けばよい。
定義を編集:
sudo virsh edit columbia
<devices> ... </devices> ブロック内に、以下のような <tpm> ブロックがあることを確認する。無ければ追加する。
<tpm model='tpm-crb'>
<backend type='emulator' version='2.0'/>
</tpm>
model='tpm-crb' は TPM 2.0 向けのインターフェースbackend type='emulator' version='2.0' で swtpm を使う vTPM 2.0 になる保存に成功すれば、その VM には TPM 2.0 デバイスが追加された状態になる。
初回インストールをスムーズに行うことを優先し、
sata のままe1000e のままで進める。
どちらも Windows 標準ドライバで認識されるので、追加ドライバなしでインストーラが動く。
(後から virtio ドライバを入れてディスク/NIC を virtio 化することは可能。この記事ではそこまでは扱わない。)
Cockpit の columbia 詳細画面で、「Disks」タブから CD-ROM デバイスを確認・設定する。
CD-ROM を選び、/var/lib/libvirt/iso/Win11_24H2_Japanese_x64.isoこれで、VM 起動時に DVD として Windows 11 インストーラがマウントされる。
columbia が Shut off 状態であることを確認し、Cockpit から「Install」または「Start」を押す。
Press any key to boot from CD or DVD...... が一瞬表示されるので、すぐに Enter などを押す。ここでキー入力が間に合わないと DVD ブートがスキップされ、
BdsDxe: No bootable option or device was found.
等と表示されてしまう。
その場合は一度シャットダウンして再度起動し、今度はメッセージが出た瞬間に Enter を押す。
あとは物理マシンと同じ流れでインストールする。
「Windows をインストールする場所」画面では、ディスク 0 に「未割り当ての領域」として ~128GB が見えているはず。
その後は通常どおり、コピー → 再起動 → 初期設定(地域 / アカウント / プライバシーなど)を進めればよい。
余談として、最近のWindows11は、なにがなんでもMicrosoftアカウントとの紐づけをさせてくる仕様になってます。私はそれが好きではなく、ローカルアカウントでセットアップしたい。裏技的な作業になってしまうのですが、方法はあります。以下参考。

Windows 起動後、Win + R から tpm.msc を実行し、TPM 管理画面を開く。
これで Windows 11 の TPM 要件は満たされている。
e1000e のままにしているので、特別なドライバを入れなくてもネットワークは動作しているはず。default ネットワーク(NAT)が使われる。パフォーマンスを詰めたい場合は、別途 virtio-win.iso をダウンロードして
virtio-win-guest-tools.exe を実行することで、virtio 系のドライバをまとめて導入できる。
その後、ディスクや NIC を virtio 化していく、という流れが一般的だが、本記事の範囲外とする。
Cockpit 上で VM を作る際、「Connection」として
qemu:///system)qemu:///session)が選べるが、本記事のような サーバ上で常用する VM はすべて System に置くのが基本方針。
理由:
virsh start columbia などで統一的に扱えるUser session は「そのユーザーがログインしている間だけ動いていればよい一時 VM」向けと考える。
Cockpit の「Services」で
systemd-networkd-wait-online.service
Failed to start Wait for Network to be Configured.
Timeout occurred while waiting for network connectivity.
といったエラーが出ることがある。
これは
systemd-networkd が管理しているすべての NIC が「オンライン」になるまで待ったが、しただけで、実際に使っている NIC(例:enp1s0f0np0)が正常に上がっているなら実害はほぼない。
気になる場合の対処案:
sudo systemctl disable --now systemd-networkd-wait-online.service 多くの自宅サーバ用途では、これで問題になることは少ない。optional: true を付ける。 network: version: 2 renderer: networkd ethernets: enp4s0: dhcp4: true optional: true その上で sudo netplan generate sudo netplan apply とすれば、その NIC が上がらなくても「オンライン」と判断される。このエラー自体は Windows 11 VM とは無関係なので、優先度は低い。「気になったら片付ける」程度の認識でよい。
/var/lib/libvirt/iso など libvirt からアクセス可能なパスにまとめる。columbia のように小文字で統一し、System 接続(qemu:///system)側に揃えておくと、CLI や将来の DNS 管理などがやりやすい。この状態までできていれば、あとは同じ要領で
なども追加し、用途ごとに VM を分離していくことができる。
]]>※自分用メモなので非常に雑ですので役に立つ方のみ自身の責任の上で参考にしてください。
使用するUSBカメラ
バッファロー BSW505MBK

このカメラは、MotionJPEGで出力されるのですが、H264で配信したいので、エンコードもしつつ配信します。Raspberry Pi 4にはハードウェアエンコーダーがあるのでそれを使います。
# カメラの確認
v4l2-ctl --list-devices
v4l2-ctl -d /dev/video0 --list-formats-ext
FFmpegインストール
sudo apt update
sudo apt install -y ffmpeg v4l-utils
私の例
ctrluser@metis:~ $ v4l2-ctl --list-devices
bcm2835-codec-decode (platform:bcm2835-codec):
/dev/video10
/dev/video11
/dev/video12
/dev/video18
/dev/video31
bcm2835-isp (platform:bcm2835-isp):
/dev/video13
/dev/video14
/dev/video15
/dev/video16
/dev/video20
/dev/video21
/dev/video22
/dev/video23
/dev/media0
/dev/media2
rpi-hevc-dec (platform:rpi-hevc-dec):
/dev/video19
/dev/media1
USB 2.0 Camera: USB Camera (usb-0000:01:00.0-1.3):
/dev/video0
/dev/video1
/dev/media4
bcm2835-codec (vchiq:bcm2835-codec):
/dev/media3
ctrluser@metis:~ $ v4l2-ctl -d /dev/video0 --list-formats-ext
ioctl: VIDIOC_ENUM_FMT
Type: Video Capture
[0]: 'MJPG' (Motion-JPEG, compressed)
Size: Discrete 1920x1080
Interval: Discrete 0.033s (30.000 fps)
Size: Discrete 1280x1024
Interval: Discrete 0.033s (30.000 fps)
Size: Discrete 1280x960
Interval: Discrete 0.033s (30.000 fps)
Size: Discrete 1280x720
Interval: Discrete 0.033s (30.000 fps)
Size: Discrete 1280x480
Interval: Discrete 0.033s (30.000 fps)
Size: Discrete 848x480
Interval: Discrete 0.033s (30.000 fps)
Size: Discrete 800x600
Interval: Discrete 0.033s (30.000 fps)
Size: Discrete 640x480
Interval: Discrete 0.033s (30.000 fps)
Size: Discrete 640x400
Interval: Discrete 0.033s (30.000 fps)
Size: Discrete 352x288
Interval: Discrete 0.033s (30.000 fps)
Size: Discrete 320x240
Interval: Discrete 0.033s (30.000 fps)
Size: Discrete 176x144
Interval: Discrete 0.033s (30.000 fps)
Size: Discrete 160x120
Interval: Discrete 0.033s (30.000 fps)
[1]: 'YUYV' (YUYV 4:2:2)
Size: Discrete 1920x1080
Interval: Discrete 0.200s (5.000 fps)
Size: Discrete 1280x1024
Interval: Discrete 0.200s (5.000 fps)
Size: Discrete 1280x960
Interval: Discrete 0.100s (10.000 fps)
Size: Discrete 1280x720
Interval: Discrete 0.100s (10.000 fps)
Size: Discrete 1280x480
Interval: Discrete 0.067s (15.000 fps)
Size: Discrete 848x480
Interval: Discrete 0.050s (20.000 fps)
Size: Discrete 800x600
Interval: Discrete 0.050s (20.000 fps)
Size: Discrete 640x480
Interval: Discrete 0.033s (30.000 fps)
Size: Discrete 640x400
Interval: Discrete 0.033s (30.000 fps)
Size: Discrete 352x288
Interval: Discrete 0.033s (30.000 fps)
Size: Discrete 320x240
Interval: Discrete 0.033s (30.000 fps)
Size: Discrete 176x144
Interval: Discrete 0.033s (30.000 fps)
Size: Discrete 160x120
Interval: Discrete 0.033s (30.000 fps)
ctrluser@metis:~ $ ffmpeg -hide_banner -encoders | grep v4l2m2m
V..... h263_v4l2m2m V4L2 mem2mem H.263 encoder wrapper (codec h263)
V..... h264_v4l2m2m V4L2 mem2mem H.264 encoder wrapper (codec h264)
V..... hevc_v4l2m2m V4L2 mem2mem HEVC encoder wrapper (codec hevc)
V..... mpeg4_v4l2m2m V4L2 mem2mem MPEG4 encoder wrapper (codec mpeg4)
V..... vp8_v4l2m2m V4L2 mem2mem VP8 encoder wrapper (codec vp8)
ctrluser@metis:~ $
MediaMTXインストール
wget https://github.com/bluenviron/mediamtx/releases/download/v1.15.3/mediamtx_v1.15.3_linux_arm64.tar.gz
wget https://github.com/bluenviron/mediamtx/releases/download/v1.15.3/checksums.sha256
# チェックサム確認(行が 'OK' になればOK)
grep mediamtx_v1.15.3_linux_arm64.tar.gz checksums.sha256 | sha256sum --check -
tar xzf mediamtx_v1.15.3_linux_arm64.tar.gz
sudo mv mediamtx /usr/local/bin/
MediaMTXの設定
sudo vi /usr/local/bin/mediamtx.yml
paths:
cam:
runOnInit: >
ffmpeg
-f v4l2 -thread_queue_size 1024
-input_format mjpeg -framerate 30 -video_size 1920x1080
-fflags +discardcorrupt
-i /dev/video0
-f alsa -thread_queue_size 1024 -ar 48000 -ac 1
-i plughw:3,0
-filter:v "scale=in_range=pc:out_range=tv,format=yuv420p,drawtext=fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf:text='%Y-%m-%d %H\:%M\:%S':expansion=strftime:x=10:y=10:fontsize=26:fontcolor=white:box=1:boxcolor=0x00000088:boxborderw=10"
-c:v h264_v4l2m2m -b:v 4M -maxrate 4M -bufsize 8M -g 60
-c:a aac -b:a 128k -ar 48000 -ac 1
-map 0:v:0 -map 1:a:0
-max_interleave_delta 0
-f rtsp -rtsp_transport tcp
rtsp://127.0.0.1:$RTSP_PORT/$MTX_PATH
runOnInitRestart: yes
v4l2 で /dev/video0 を MJPEG 1080p/30 で取得。scale=in_range=pc:out_range=tv,format=yuv420p。
yuv420p に確実に整えます。drawtext を expansion=strftime で使用。
text='%Y-%m-%d %H\:%M\:%S' … ローカル時刻を YYYY-MM-DD HH:MM:SS で描画。box=1:boxcolor=0x00000088:boxborderw=10 … 白文字に半透明黒ボックス+10px の余白(padding)。DejaVuSansMono を fontfile= で明示(sudo apt install -y fonts-dejavu-core で導入)。h264_v4l2m2m(HW H.264)。目安ビットレート 4 Mbps、GOP 60(30fps で約 2 秒)。rtsp://<PiのIP>:8554/cam へ接続。MediaMTX 本体と設定ファイルは以下の配置を前提とします。
/usr/local/bin/mediamtx/usr/local/bin/mediamtx.yml/etc/systemd/system/mediamtx.service:
[Unit]
Description=MediaMTX RTSP server
Wants=network.target
[Service]
ExecStart=/usr/local/bin/mediamtx /usr/local/bin/mediamtx.yml
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
有効化と起動:
sudo systemctl daemon-reload
sudo systemctl enable --now mediamtx
ログ監視は
journalctl -u mediamtx -f
]]>