From 51b29b7d3d817916a431fbb072a85333391f1533 Mon Sep 17 00:00:00 2001 From: Nulo Date: Thu, 2 Feb 2023 18:25:11 -0300 Subject: [PATCH] hacer imagen y kernel y correr qemu --- .gitignore | 1 + alpine.ts | 1 - helpers.ts | 23 ++++++++++++++++++++ index.ts | 27 ++++++++++++++++++++---- kernel.ts | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++---- qemu.ts | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 162 insertions(+), 9 deletions(-) create mode 100644 qemu.ts diff --git a/.gitignore b/.gitignore index ab34496..f5cd5ef 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules/ build-javascript cache/ +artifacts/ \ No newline at end of file diff --git a/alpine.ts b/alpine.ts index 5dbcd92..a42c4f6 100644 --- a/alpine.ts +++ b/alpine.ts @@ -5,7 +5,6 @@ import { mkdtemp, opendir, rm, - rmdir, symlink, writeFile, } from "node:fs/promises"; diff --git a/helpers.ts b/helpers.ts index 8276089..3d4532d 100644 --- a/helpers.ts +++ b/helpers.ts @@ -3,6 +3,29 @@ import { execFile as execFileCallback, spawn as spawnCallback, } from "node:child_process"; +import { access } from "node:fs/promises"; +import { getuid } from "node:process"; export const execFile = promisify(execFileCallback); export const spawn = promisify(spawnCallback); + +export async function canAccess(path: string): Promise { + try { + await access(path); + return true; + } catch { + return false; + } +} + +export async function sudoChown(path: string, owner: string): Promise { + await execFile("sudo", ["chown", owner, path]); +} +export async function sudoChownToRunningUser(path: string): Promise { + if (getuid) { + await sudoChown(path, "" + getuid()); + } else throw new Error("No tengo getuid"); +} +export async function sudoRm(path: string): Promise { + await execFile("sudo", ["rm", path]); +} diff --git a/index.ts b/index.ts index f7b402a..7a9f986 100644 --- a/index.ts +++ b/index.ts @@ -3,23 +3,42 @@ import { tmpdir } from "node:os"; import path from "node:path"; import { cwd } from "node:process"; import { Alpine } from "./alpine.js"; -import { spawn } from "./helpers.js"; +import { execFile, spawn, sudoChownToRunningUser } from "./helpers.js"; import { setupKernel } from "./kernel.js"; +import { runQemu } from "./qemu.js"; import { Runit } from "./runit/index.js"; { console.time("Building"); + const artifactsDir = path.join(cwd(), "artifacts/"); + const kernelDir = path.join(artifactsDir, "kernel"); + await mkdir(artifactsDir, { recursive: true }); + await mkdir(kernelDir, { recursive: true }); + const rootfsDir = await mkdtemp(path.join(tmpdir(), "define-alpine-")); console.debug(rootfsDir); const alpine = await Alpine.makeWorld({ dir: rootfsDir }); const runit = await Runit.setup(alpine); - await setupKernel(alpine); + const kernel = await setupKernel(alpine, kernelDir); + + const squashfs = path.join(artifactsDir, "image.squashfs"); + await execFile("sudo", [ + "mksquashfs", + alpine.dir, + squashfs, + "-comp", + "zstd", + "-Xcompression-level", + "3", + "-noappend", + "-quiet", + ]); + await sudoChownToRunningUser(squashfs); console.timeEnd("Building"); - const artifactsDir = path.join(cwd(), "artifacts/"); - await mkdir(artifactsDir); + runQemu(squashfs, kernel, { graphic: false }); // await makeService({ // parentDir: rootfsDir, diff --git a/kernel.ts b/kernel.ts index 570524b..7ac0225 100644 --- a/kernel.ts +++ b/kernel.ts @@ -1,6 +1,22 @@ -import { Alpine } from "./alpine"; +import { constants } from "node:fs"; +import { copyFile } from "node:fs/promises"; +import path from "node:path"; +import { Alpine } from "./alpine.js"; +import { canAccess, sudoChownToRunningUser, sudoRm } from "./helpers.js"; + +export type Kind = "lts" | "virt"; +export type Kernel = { + initramfs: string; + vmlinuz: string; + kind: Kind; +}; + +export async function setupKernel( + alpine: Alpine, + output: string +): Promise { + const kind: Kind = "virt"; -export async function setupKernel(alpine: Alpine): Promise { await alpine.writeFile( "/etc/update-extlinux.conf", `# configuration for extlinux config builder @@ -29,10 +45,47 @@ default=lts ` ); + const features = [ + "squashfs", + "ata", + "base", + "cdrom", + "ext4", + "keymap", + "kms", + "mmc", + "nvme", + "scsi", + "usb", + ...(kind === "virt" ? ["virtio"] : []), + ]; + await alpine.writeFile( "/etc/mkinitfs/mkinitfs.conf", - 'features="squashfs ata base cdrom ext4 keymap kms mmc nvme scsi usb virtio"' + `features="${features.join(" ")}"` ); - await alpine.addPackages(["linux-virt"]); + await alpine.addPackages([`linux-${kind}`]); + + const initramfs = path.join(alpine.dir, `/boot/initramfs-${kind}`); + const vmlinuz = path.join(alpine.dir, `/boot/vmlinuz-${kind}`); + + if (!(await canAccess(initramfs))) + throw new Error("Didn't generate initramfs"); + else if (!(await canAccess(vmlinuz))) + throw new Error("Didn't generate vmlinuz"); + + const kernel: Kernel = { + kind, + initramfs: path.join(output, "initramfs"), + vmlinuz: path.join(output, "vmlinuz"), + }; + + await sudoChownToRunningUser(initramfs); + await sudoChownToRunningUser(vmlinuz); + await copyFile(initramfs, kernel.initramfs, constants.COPYFILE_FICLONE); + await copyFile(vmlinuz, kernel.vmlinuz, constants.COPYFILE_FICLONE); + await sudoRm(initramfs); + await sudoRm(vmlinuz); + return kernel; } diff --git a/qemu.ts b/qemu.ts new file mode 100644 index 0000000..c95deb1 --- /dev/null +++ b/qemu.ts @@ -0,0 +1,58 @@ +import { mkdtemp, rm } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import { execFile } from "./helpers.js"; +import { Kernel } from "./kernel.js"; + +export async function runQemu( + squashfs: string, + kernel: Kernel, + { graphic }: { graphic: boolean } = { graphic: true } +) { + const tmp = await mkdtemp(path.join(tmpdir(), "define-alpine-qemu-")); + try { + const disk = path.join(tmp, "tmp.ext4"); + await execFile("fallocate", ["--length", "1G", disk]); + await execFile("mkfs.ext4", ["-F", disk]); + + let kernelAppend = [ + "root=/dev/sda", + "rootfstype=squashfs", + "modules=ext4", + "quiet", + "init=/sbin/runit-init", + ]; + let qemuAppend: string[] = []; + + if (!graphic) { + kernelAppend.push("console=ttyS0"); + qemuAppend.push("-nographic"); + } + + // sudo chown root:$(id -u) -R boot/ && sudo chmod g+rw -R boot/ + await execFile("qemu-system-x86_64", [ + "-enable-kvm", + "-m", + "2048", + "-drive", + `file=${squashfs},media=disk,format=raw`, + "-drive", + `file=${disk},media=disk,format=raw`, + "-net", + "nic,model=virtio-net-pci", + "-net", + "user,hostfwd=tcp:127.0.0.1:8080-:80", + "-kernel", + kernel.vmlinuz, + "-initrd", + kernel.initramfs, + "-append", + kernelAppend.join(" "), + ...qemuAppend, + "-no-shutdown", + ]); + // -append "root=/dev/sda rootfstype=squashfs modules=ext4 quiet init=/sbin/runit-init $append" $qemuappend + } finally { + await rm(tmp, { recursive: true, force: true }); + } +}