Skip to content

Instantly share code, notes, and snippets.

@jamietre
Last active June 11, 2024 12:14
Show Gist options
  • Save jamietre/d463f0f9132f564bf1d7727257eabf13 to your computer and use it in GitHub Desktop.
Save jamietre/d463f0f9132f564bf1d7727257eabf13 to your computer and use it in GitHub Desktop.
Script to start WSL and bind localhost to WSL IP
# Example docker-compose.yml for plex.
version: "2"
services:
plex:
image: plexinc/pms-docker:plexpass
runtime: nvidia
container_name: "plex"
restart: always
hostname: "MY-PLEX"
volumes:
- plex-transcode:/transcode
- /plex:/config
- plex-video:/data/video
- plex-music:/data/music
ports:
- "0.0.0.0:32400:32400"
- "0.0.0.0:33400:33400"
- "0.0.0.0:65001:65001"
environment:
TZ: America/New_York
PLEX_CLAIM: <your-claim>
ADVERTISE_IP: https://your.plex.server
PLEX_UID: 1001
PLEX_GID: 1001
NVIDIA_VISIBLE_DEVICES: all
NVIDIA_DRIVER_CAPABILITIES: compute,video,utility
volumes:
# Examples only
plex-music:
driver: local
driver_opts:
type: cifs
device: //192.168.1.2/media/MusicLibrary
o: "username=plex,password=xxxx"
plex-video:
driver: local
driver_opts:
type: cifs
device: //192.168.1.2/media/VideoLibrary
o: "username=plex,password=xxxx"
plex-transcode:
external: false
#!/bin/sh
service docker start
"C:\Program Files\nodejs\node.exe" "c:\scripts\configure-wsl-plex.js" >> "c:\scripts\configure-wsl-plex.log" 2>&1
const { exec } = require("child_process");
const distro = "Ubuntu-20.04";
const ipPattern = /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/;
async function main() {
log("info", "Starting WSL initialization");
const isRunning = await isWslDistroRunning();
if (!isRunning) {
log("info", "Distro stopped, starting...");
await runCommand(`wsl -d ${distro} -u root /etc/init-wsl`);
}
const data = await runCommand("C:\\Windows\\System32\\wsl.exe hostname -I");
const parts = data.split(" ");
const ip = parts[0];
if (!ipPattern.test(ip)) {
throw new Error(`Received '${data}'; could not extract an IP`);
}
log("info", `Configuring netsh to route to ${ip}`);
try {
await runCommand(
'netsh interface portproxy delete v4tov4 listenaddress="0.0.0.0" listenport=32400'
);
} catch (e) {
log("warn", e.message)
}
await runCommand(
`netsh interface portproxy add v4tov4 listenaddress=0.0.0.0 listenport=32400 connectaddress=${ip} connectport=32400`
);
log("info", "Done");
}
async function isWslDistroRunning() {
const distroText = distro.replace(/\./g, "\\.");
const wslStatePattern = new RegExp(
`\\*?\\s*${distroText}\\s*(Running|Stopped)\\s*(1|2)`
);
const wslState = await runCommand("wsl --list --verbose");
const lines = splitLines(wslState);
const distroMatches = lines.filter((line) => wslStatePattern.test(line));
if (distroMatches.length > 1) {
throw new Error(
`More than one match for distro name "${distro}" was found, can't continue.`
);
}
if (distroMatches.length === 0) {
throw new Error(`No matches for distro name "${distro}".`);
}
const matches = distroMatches[0].match(wslStatePattern);
const state = matches[1];
log("info",`Machine state: ${state}`)
switch (state) {
case "Running":
return true;
case "Stopped":
return false;
default:
throw new Error(`Unknown wsl state "${state}"`);
}
}
async function runCommand(command) {
const deferred = createDeferred();
log("info", `> ${command}`);
exec(command, function (err, stdout, stderr) {
if (err) {
deferred.reject(err);
return;
}
if (stderr) {
log("error", stderr);
}
deferred.resolve(stdout);
return;
});
return deferred.promise();
}
/**
* make a promise, and provide the resolve/reject functions
*/
function createDeferred() {
let resolve;
let reject;
const promise = new Promise((_resolve, _reject) => {
resolve = _resolve;
reject = _reject;
});
return {
promise: async () => promise,
resolve: resolve,
reject: reject,
};
}
function splitLines(lines) {
let cleanLines = lines.replace(/\r/g, "").replace(/\0/g, "");
while (cleanLines.endsWith("\n")) {
cleanLines = cleanLines.slice(0, cleanLines.length - 1);
}
return cleanLines.split("\n");
}
function log(level, message) {
const now = new Date().toISOString();
console.log(`${now} [${level}] ${message}`);
}
main()
.then(() => {
process.exit(0);
})
.catch((e) => {
log("error", e.message);
process.exit(1);
});
@jamietre
Copy link
Author

jamietre commented Dec 3, 2021

Plex, Docker, WSL2, hardware transcoding

Since WSL2 supports GPU access -- in latest Windows 10 feature update and Windows 11 - it is actually somewhat reasonable/performant to run Plex in docker in WSL. I want to do this because it's much easier to manage/maintain dockerized plex than the Windows installation; it's also easily portable to another linux box if want to move it. But I want to use my GPU for other things in Windows at the same time so I don't want to dedicate this box to running linux.

Lo and behold, since the latest Windows feature update, it actually works, and I get hardware transcoding running plex in WSL2 in windows! But WSL2 isn't really ready to act as a reliable server foundation out of the box:

  • WSL does not start automatically with windows
  • Docker does not start automatically with WSL2
  • You can't access services running inside WSL2 except by the IP address of the WSL2 distro, which is not static

This script solves these problems.

How to get it working

This gist includes a javascript program to check if WSL is running; start it if not; start docker; and to map localhost to the current WSL IP for plex web port so that the world can access your plex server.

I've included an example docker-compose.yml, but this is not really specific to this problem. There are lots of great guides for running plex in Docker generally.

This assumes you're using an Ubuntu distro; may vary for others.

  1. Within WSL,
  • copy the contents of the script init-wsl => /etc/init-wsl
  • chmod +x /etc/init-wsl
  1. Copy the other scripts to c:\scripts
  2. Edit line 3 of start-wsl.js with the name of your distro
  3. Add a scheduled task that runs on system startup to run c:\scripts\start-wsl.cmd

Using Nvidia GPU in WSL2

The instructions provided by Nvidia pretty much work out of the box. One thing that tripped me up and is not really obvious from anything I read online is that you cannot use Docker Desktop on windows at the same time. Docker desktop does not (yet?) support the nvidia runtime. Uninstall docker desktop, and follow the Docker CE installation instructions in the nvidia guide below. Ignore the warnings when installing it about "you should use docker desktop."

Nvidia GPU access in WSL
Nvidia CUDA WSL setup

@potchy
Copy link

potchy commented Feb 4, 2023

You are a genius!

@mikaelweave
Copy link

Docker Desktop now supports remote WSL2 containers - I think you now only need the compose file and the rest can be achieved natively there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment