diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..97c56e9 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +@nulo:registry=https://gitea.nulo.in/api/packages/nulo/npm/ diff --git a/index.js b/index.js index 01dcf43..ead2bbd 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,3 @@ -// @ts-check import { nanoid } from "nanoid"; import { execFile as execFileCallback } from "node:child_process"; import { @@ -6,6 +5,7 @@ import { copyFile, mkdir, opendir, + readFile, readdir, rm, symlink, @@ -14,31 +14,42 @@ import { import { tmpdir } from "node:os"; import { basename, join } from "node:path"; import { promisify } from "node:util"; +import { init } from "@nulo/apkit"; import * as dotenv from "dotenv"; -dotenv.config(); +dotenv.config({ override: true }); const execFile = promisify(execFileCallback); // TODO: configurar runsc -// TODO: cache de apk (robar de define-alpine-the-sequel) const currRootPath = await setupRootfsDir(); -await setupBasicApkEnvironment(currRootPath); -const packages = ["alpine-base", "fish", "docker", "linux-virt"]; -await execFile("doas", [ - "apk", - "--initdb", - "--clean-protected", - "--root", - currRootPath, - "add", - ...packages, -]); -await chroot(currRootPath, ["rc-update", "add", "networking", "sysinit"]); -await chroot(currRootPath, ["rc-update", "add", "docker", "boot"]); -await chroot(currRootPath, ["rc-update", "add", "acpid", "default"]); -await chroot(currRootPath, ["rc-update", "add", "ntpd", "default"]); -await chroot(currRootPath, ["rc-update", "add", "local", "default"]); +const alpine = await init(currRootPath); +await alpine.install(["alpine-base", "fish", "docker", "linux-virt"]); +await alpine.rcUpdate("sysinit", "networking"); +await alpine.rcUpdate("boot", "docker"); +await alpine.rcUpdate("boot", "mdev"); +await alpine.rcUpdate("default", "ntpd"); +await alpine.rcUpdate("default", "local"); + +/** + * @param {string} path + * @param {((file: string) => string) | ((file: string) => Promise)} patchFunc + * @returns {Promise} + */ +async function patchFile(path, patchFunc) { + let file = await readFile(path, "utf-8"); + file = await patchFunc(file); + await writeFile(path, file); +} + +await patchFile( + join(currRootPath, "/etc/modprobe.d/blacklist.conf"), + (blacklist) => blacklist.replace("tiny_power_button", "button") +); +await patchFile( + join(currRootPath, "/etc/rc.conf"), + (rc) => rc + '\nrc_cgroup_mode="unified"\n' +); await writeFile( "interfaces", @@ -50,46 +61,51 @@ iface eth0 use dhcp`, { mode: 0o600 } ); -await execFile("doas", [ +await execFile("sudo", [ "mv", "interfaces", join(currRootPath, "/etc/network/interfaces"), ]); -await execFile("doas", [ +await execFile("sudo", [ "chown", "0:0", join(currRootPath, "/etc/network/interfaces"), ]); -const woodpeckerSecret = getConfig("WOODPECKER_SECRET"); const woodpeckerServer = getConfig("WOODPECKER_SERVER"); +const woodpeckerAgentSecret = getConfig("WOODPECKER_AGENT_SECRET"); await writeFile( "woodpecker.start", `#!/bin/sh -docker pull docker.io/woodpeckerci/woodpecker-agent:latest || exit $? + +modprobe tiny-power-button + +while ! docker pull docker.io/woodpeckerci/woodpecker-agent:latest; do + sleep 1 +done exec docker run --detach --rm \ --name=woodpecker-agent \ -v /var/run/docker.sock:/var/run/docker.sock \ -e WOODPECKER_SERVER='${woodpeckerServer}' \ - -e WOODPECKER_SECRET='${woodpeckerSecret}' \ + -e WOODPECKER_AGENT_SECRET='${woodpeckerAgentSecret}' \ -e WOODPECKER_MAX_PROCS=8 \ docker.io/woodpeckerci/woodpecker-agent:latest \ agent`, { mode: 0o700 } ); -await execFile("doas", [ +await execFile("sudo", [ "mv", "woodpecker.start", join(currRootPath, "/etc/local.d/woodpecker.start"), ]); -await execFile("doas", [ +await execFile("sudo", [ "chown", "0:0", join(currRootPath, "/etc/local.d/woodpecker.start"), ]); -await chroot(currRootPath, ["rc-update", "add", "localmount", "boot"]); +await alpine.rcUpdate("boot", "localmount"); await writeFstab(currRootPath); // make VM disk image @@ -99,11 +115,11 @@ await execFile("mkfs.ext4", [ext4Path]); const mntdir = join(tmpdir(), "woodpecker-in-a-vm-" + nanoid()); await mkdir(mntdir); -await execFile("doas", ["mount", ext4Path, mntdir]); +await execFile("sudo", ["mount", ext4Path, mntdir]); for (const path of await readdir(currRootPath)) - await execFile("doas", ["cp", "-r", join(currRootPath, path), mntdir]); -await execFile("doas", ["umount", mntdir]); -await execFile("doas", [ + await execFile("sudo", ["cp", "-r", join(currRootPath, path), mntdir]); +await execFile("sudo", ["umount", mntdir]); +await execFile("sudo", [ "qemu-img", "convert", "-O", @@ -122,7 +138,7 @@ await copyFile( "./initramfs-virt", constants.COPYFILE_FICLONE ); -await execFile("doas", [ +await execFile("sudo", [ "chown", "1000:1000", "./vmlinuz-virt", @@ -142,43 +158,21 @@ async function setupRootfsDir() { } catch {} await symlink(basename(currRootPath), currentPath); } - return currRootPath; -} - -/** - * @param {string} rootfs - */ -async function setupBasicApkEnvironment(rootfs) { - { - const apkKeysDir = join(rootfs, "/etc/apk/keys"); - const keysSrcDir = "alpine/keys"; - await mkdir(apkKeysDir, { recursive: true }); - for await (const { name } of await opendir(keysSrcDir)) - await copyFile(join(keysSrcDir, name), join(apkKeysDir, name)); - } - - await writeFile( - join(rootfs, "/etc/apk/repositories"), - [ - // "https://dl-cdn.alpinelinux.org/alpine/v3.17/main", - // "https://dl-cdn.alpinelinux.org/alpine/v3.17/community", - "http://alpinelinux.c3sl.ufpr.br/v3.17/main", - "http://alpinelinux.c3sl.ufpr.br/v3.17/community", - ].join("\n") - ); + return "./" + currRootPath; } /** * @param {string} rootfs */ async function writeFstab(rootfs) { + const rootfsName = "/dev/vda"; const virtualDiskName = "/dev/vda"; await writeFile( "fstab", - `${virtualDiskName} / ext4 rw,relatime,discard 0 1 + `${rootfsName} / squashfs rw,relatime,discard 0 1 ` ); - await execFile("doas", ["mv", "fstab", join(rootfs, "/etc/fstab")]); + await execFile("sudo", ["mv", "fstab", join(rootfs, "/etc/fstab")]); } /** @@ -187,7 +181,7 @@ async function writeFstab(rootfs) { * @param {string[]} cmd */ async function chroot(rootfs, cmd) { - await execFile("doas", ["chroot", rootfs, ...cmd]); + await execFile("sudo", ["chroot", rootfs, ...cmd]); } /** diff --git a/package.json b/package.json index eb142f1..59875d0 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ }, "dependencies": { "dotenv": "^16.0.3", - "nanoid": "^4.0.1" + "nanoid": "^4.0.1", + "@nulo/apkit": "https://gitea.nulo.in/Nulo/apkit/archive/hackyshit.tar.gz" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f7d0ba9..1d61afa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,6 +1,13 @@ -lockfileVersion: '6.0' +lockfileVersion: '6.1' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false dependencies: + '@nulo/apkit': + specifier: https://gitea.nulo.in/Nulo/apkit/archive/hackyshit.tar.gz + version: '@gitea.nulo.in/Nulo/apkit/archive/hackyshit.tar.gz' dotenv: specifier: ^16.0.3 version: 16.0.3 @@ -29,3 +36,9 @@ packages: engines: {node: ^14 || ^16 || >=18} hasBin: true dev: false + + '@gitea.nulo.in/Nulo/apkit/archive/hackyshit.tar.gz': + resolution: {tarball: https://gitea.nulo.in/Nulo/apkit/archive/hackyshit.tar.gz} + name: '@nulo/apkit' + version: 0.0.1 + dev: false diff --git a/readme.md b/readme.md index 1788946..f89df72 100644 --- a/readme.md +++ b/readme.md @@ -1,11 +1,10 @@ # woodpecker-in-a-vm 1. script que genera una vm con woodpecker agent adentro -1. lo configuré para que borre en el disco real si se borra algo dentro de la vm -1. como woodpecker-agent no tiene state, puedo regenerar la vm con el script las veces que quiera +1. lo configuré para que borre en el disco real si se borra algo dentro de la vm +1. como woodpecker-agent no tiene state, puedo regenerar la vm con el script las veces que quiera 1. no tengo que exponer mi docker real al agent porque está en una vm con el suyo - ## compilar imágen ```sh @@ -24,6 +23,6 @@ virt-install --name woodpecker-in-a-vm \ --memory 4096 --vcpus 4 \ --import \ --disk path=/var/lib/libvirt/images/vm.qcow2,format=qcow2 \ - --boot kernel=/var/lib/libvirt/images/vmlinuz-virt,initrd=/var/lib/libvirt/images/initramfs-virt,kernel_args="console=/dev/ttyS0 quiet root=/dev/vda rw modules=ext4" \ + --boot kernel=/var/lib/libvirt/images/vmlinuz-virt,initrd=/var/lib/libvirt/images/initramfs-virt,kernel_args="console=/dev/ttyS0 quiet root=/dev/vda rw modules=ext4 tiny_power_button.power_signal=12" \ --noautoconsole ``` diff --git a/tsconfig.json b/tsconfig.json index 228e398..828ac35 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,8 @@ "module": "es2022", "moduleResolution": "node", "esModuleInterop": true, + "allowJs": true, + "checkJs": true, "strict": true } }