Skip to content

Instantly share code, notes, and snippets.

@brad-jones
Created January 16, 2023 03:03
Show Gist options
  • Save brad-jones/64fa6ae463548dbddafa7470d33b98a2 to your computer and use it in GitHub Desktop.
Save brad-jones/64fa6ae463548dbddafa7470d33b98a2 to your computer and use it in GitHub Desktop.
WSL Home Disk Auto Mount
ARG USERNAME
ARG WSL_DISTRO_NAME
RUN mv /home /home-tpl
COPY ./mount-home.ts /usr/bin/mount-home.ts
RUN deno cache /usr/bin/mount-home.ts
RUN chmod +x /usr/bin/mount-home.ts
RUN sed -i "s/%USERNAME%/$(printf '%s\n' "$USERNAME" | sed -e 's/[\/&]/\\&/g')/g" /usr/bin/mount-home.ts
RUN sed -i "s/%WSL_DISTRO_NAME%/$(printf '%s\n' "$WSL_DISTRO_NAME" | sed -e 's/[\/&]/\\&/g')/g" /usr/bin/mount-home.ts
COPY ./mount-home.service /usr/lib/systemd/system/mount-home.service
RUN systemctl enable mount-home.service
[Unit]
Description=Mount VHDX for /home
Before=local-fs-pre.target
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/deno run --cached-only -A /usr/bin/mount-home.ts
[Install]
WantedBy=multi-user.target
#!/usr/bin/env -S deno run --cached-only -A
import * as fs from "https://deno.land/[email protected]/fs/mod.ts#^";
import { $, _, env, NonZeroExitCode } from "https://deno.land/x/[email protected]/mod.ts#^";
import { z } from "https://deno.land/x/[email protected]/mod.ts#^";
const USERNAME = "%USERNAME%"; // sed replaced in Dockerfile
const WSL_DISTRO_NAME = "%WSL_DISTRO_NAME%"; // sed replaced in Dockerfile
const USERPROFILE_WIN = `C:\\Users\\${USERNAME}`;
const USERPROFILE = `/mnt/c/Users/${USERNAME}`;
const VHDX_WIN = `${USERPROFILE_WIN}\\.wsl\\home.vhdx`;
const VHDX = `${USERPROFILE}/.wsl/home.vhdx`;
const VHDX_SIZE_GB = 50;
const wsl = `${USERPROFILE}/AppData/Local/Microsoft/WindowsApps/wsl.exe`;
const pwsh = `${USERPROFILE}/.scoop/apps/pwsh/current/pwsh.exe`;
console.log({
USERNAME,
WSL_DISTRO_NAME,
VHDX,
VHDX_WIN,
VHDX_SIZE_GB,
wsl,
pwsh,
});
const createDisk = async () => {
await _`${pwsh} -NoProfile -C ${`New-VHD -Path ${VHDX_WIN} -Dynamic -SizeBytes ${VHDX_SIZE_GB}GB`}`;
console.log(`mount-home | created brand new disk @ ${VHDX_WIN}`);
};
const attachDisk = async (retry = 0) => {
try {
await env({
WSL_UTF8: "1",
WSLENV: `${Deno.env.get("WSLENV") ?? ""}:WSL_UTF8`,
}, _`${wsl} -d ${WSL_DISTRO_NAME} --mount --vhd ${VHDX_WIN} --bare`);
} catch (e) {
if (e instanceof NonZeroExitCode) {
if (e.stderr.includes("WSL_E_USER_VHD_ALREADY_ATTACHED")) {
console.log(
`mount-home | ${VHDX_WIN} already attached to ${WSL_DISTRO_NAME}`,
);
return;
}
// Workaround for: https://github.com/microsoft/WSL/issues/8843
// Only seems to happen intermittently for me, not sure what the trigger is.
if (e.stderr.includes("MZ: not found")) {
console.log(`mount-home | caught MZ error, retry: ${retry}`);
if (retry === 0 && await fs.exists("/usr/lib/binfmt.d/WSLInterop.conf")) {
const exitsing = await Deno.readTextFile("/usr/lib/binfmt.d/WSLInterop.conf");
console.log(`mount-home | hmmm... interesting, /usr/lib/binfmt.d/WSLInterop.conf already exists?`);
console.log(`---`);
console.log(exitsing);
console.log(`---`);
}
// I actually think we will need to reboot in order to solve this issue
if (retry > 3) throw e;
// Write the file that supposedly needs to exist
await fs.ensureDir("/usr/lib/binfmt.d");
await Deno.writeTextFile("/usr/lib/binfmt.d/WSLInterop.conf", ":WSLInterop:M::MZ::/init:PF");
console.log(`mount-home | written /usr/lib/binfmt.d/WSLInterop.conf`);
console.log(`mount-home | retrying in 500ms...`);
await new Promise((r) => setTimeout(r, 500));
await attachDisk(++retry);
}
}
throw e;
}
console.log(`mount-home | attached ${VHDX_WIN} to ${WSL_DISTRO_NAME}`);
};
const getDeviceID = async () => {
const lsblkOutput = z.object({
blockdevices: z.array(z.object({
name: z.string(),
"maj:min": z.string(),
rm: z.boolean(),
size: z.string(),
ro: z.boolean(),
type: z.string(),
mountpoints: z.array(z.string().nullable()),
})),
});
const device = lsblkOutput.parse(JSON.parse(await $(_`lsblk --json`)))
.blockdevices
.filter((_) =>
_.mountpoints.length === 0 ||
(_.mountpoints.length === 1 && _.mountpoints[0] === null)
)
.find((_) => _.size === `${VHDX_SIZE_GB}G`);
if (!device) {
throw new Error("mount-home | failed to find device id");
}
const deviceID = `/dev/${device.name}`;
console.log(`mount-home | found device @ ${deviceID}`);
return deviceID;
};
const fmtDisk = async (deviceID: string) => {
await _`mkfs.ext4 ${deviceID}`;
console.log(`mount-home | ${deviceID} formatted`);
};
const mountDisk = async (deviceID: string) => {
await fs.ensureDir("/home");
await _`mount ${deviceID} /home`;
console.log(`mount-home | ${deviceID} mounted to /home`);
};
const setupDisk = async () => {
await _`cp -R /home-tpl/. /home`;
await _`chown -R ${`${USERNAME}:${USERNAME}`} /home/${USERNAME}`;
};
if (await fs.exists(VHDX)) {
await attachDisk();
const deviceID = await getDeviceID();
await mountDisk(deviceID);
Deno.exit(0);
}
await createDisk();
await attachDisk();
const deviceID = await getDeviceID();
await fmtDisk(deviceID);
await mountDisk(deviceID);
await setupDisk();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment