hopefully computers don't exist in the real world

This commit is contained in:
Cat /dev/Nulo 2023-08-09 18:42:57 -03:00
parent 48008e3110
commit 46d08afd90
6 changed files with 75 additions and 65 deletions

1
.npmrc Normal file
View file

@ -0,0 +1 @@
@nulo:registry=https://gitea.nulo.in/api/packages/nulo/npm/

112
index.js
View file

@ -1,4 +1,3 @@
// @ts-check
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import { execFile as execFileCallback } from "node:child_process"; import { execFile as execFileCallback } from "node:child_process";
import { import {
@ -6,6 +5,7 @@ import {
copyFile, copyFile,
mkdir, mkdir,
opendir, opendir,
readFile,
readdir, readdir,
rm, rm,
symlink, symlink,
@ -14,31 +14,42 @@ import {
import { tmpdir } from "node:os"; import { tmpdir } from "node:os";
import { basename, join } from "node:path"; import { basename, join } from "node:path";
import { promisify } from "node:util"; import { promisify } from "node:util";
import { init } from "@nulo/apkit";
import * as dotenv from "dotenv"; import * as dotenv from "dotenv";
dotenv.config(); dotenv.config({ override: true });
const execFile = promisify(execFileCallback); const execFile = promisify(execFileCallback);
// TODO: configurar runsc // TODO: configurar runsc
// TODO: cache de apk (robar de define-alpine-the-sequel)
const currRootPath = await setupRootfsDir(); const currRootPath = await setupRootfsDir();
await setupBasicApkEnvironment(currRootPath); const alpine = await init(currRootPath);
const packages = ["alpine-base", "fish", "docker", "linux-virt"]; await alpine.install(["alpine-base", "fish", "docker", "linux-virt"]);
await execFile("doas", [ await alpine.rcUpdate("sysinit", "networking");
"apk", await alpine.rcUpdate("boot", "docker");
"--initdb", await alpine.rcUpdate("boot", "mdev");
"--clean-protected", await alpine.rcUpdate("default", "ntpd");
"--root", await alpine.rcUpdate("default", "local");
currRootPath,
"add", /**
...packages, * @param {string} path
]); * @param {((file: string) => string) | ((file: string) => Promise<string>)} patchFunc
await chroot(currRootPath, ["rc-update", "add", "networking", "sysinit"]); * @returns {Promise<void>}
await chroot(currRootPath, ["rc-update", "add", "docker", "boot"]); */
await chroot(currRootPath, ["rc-update", "add", "acpid", "default"]); async function patchFile(path, patchFunc) {
await chroot(currRootPath, ["rc-update", "add", "ntpd", "default"]); let file = await readFile(path, "utf-8");
await chroot(currRootPath, ["rc-update", "add", "local", "default"]); 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( await writeFile(
"interfaces", "interfaces",
@ -50,46 +61,51 @@ iface eth0
use dhcp`, use dhcp`,
{ mode: 0o600 } { mode: 0o600 }
); );
await execFile("doas", [ await execFile("sudo", [
"mv", "mv",
"interfaces", "interfaces",
join(currRootPath, "/etc/network/interfaces"), join(currRootPath, "/etc/network/interfaces"),
]); ]);
await execFile("doas", [ await execFile("sudo", [
"chown", "chown",
"0:0", "0:0",
join(currRootPath, "/etc/network/interfaces"), join(currRootPath, "/etc/network/interfaces"),
]); ]);
const woodpeckerSecret = getConfig("WOODPECKER_SECRET");
const woodpeckerServer = getConfig("WOODPECKER_SERVER"); const woodpeckerServer = getConfig("WOODPECKER_SERVER");
const woodpeckerAgentSecret = getConfig("WOODPECKER_AGENT_SECRET");
await writeFile( await writeFile(
"woodpecker.start", "woodpecker.start",
`#!/bin/sh `#!/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 \ exec docker run --detach --rm \
--name=woodpecker-agent \ --name=woodpecker-agent \
-v /var/run/docker.sock:/var/run/docker.sock \ -v /var/run/docker.sock:/var/run/docker.sock \
-e WOODPECKER_SERVER='${woodpeckerServer}' \ -e WOODPECKER_SERVER='${woodpeckerServer}' \
-e WOODPECKER_SECRET='${woodpeckerSecret}' \ -e WOODPECKER_AGENT_SECRET='${woodpeckerAgentSecret}' \
-e WOODPECKER_MAX_PROCS=8 \ -e WOODPECKER_MAX_PROCS=8 \
docker.io/woodpeckerci/woodpecker-agent:latest \ docker.io/woodpeckerci/woodpecker-agent:latest \
agent`, agent`,
{ mode: 0o700 } { mode: 0o700 }
); );
await execFile("doas", [ await execFile("sudo", [
"mv", "mv",
"woodpecker.start", "woodpecker.start",
join(currRootPath, "/etc/local.d/woodpecker.start"), join(currRootPath, "/etc/local.d/woodpecker.start"),
]); ]);
await execFile("doas", [ await execFile("sudo", [
"chown", "chown",
"0:0", "0:0",
join(currRootPath, "/etc/local.d/woodpecker.start"), join(currRootPath, "/etc/local.d/woodpecker.start"),
]); ]);
await chroot(currRootPath, ["rc-update", "add", "localmount", "boot"]); await alpine.rcUpdate("boot", "localmount");
await writeFstab(currRootPath); await writeFstab(currRootPath);
// make VM disk image // make VM disk image
@ -99,11 +115,11 @@ await execFile("mkfs.ext4", [ext4Path]);
const mntdir = join(tmpdir(), "woodpecker-in-a-vm-" + nanoid()); const mntdir = join(tmpdir(), "woodpecker-in-a-vm-" + nanoid());
await mkdir(mntdir); await mkdir(mntdir);
await execFile("doas", ["mount", ext4Path, mntdir]); await execFile("sudo", ["mount", ext4Path, mntdir]);
for (const path of await readdir(currRootPath)) for (const path of await readdir(currRootPath))
await execFile("doas", ["cp", "-r", join(currRootPath, path), mntdir]); await execFile("sudo", ["cp", "-r", join(currRootPath, path), mntdir]);
await execFile("doas", ["umount", mntdir]); await execFile("sudo", ["umount", mntdir]);
await execFile("doas", [ await execFile("sudo", [
"qemu-img", "qemu-img",
"convert", "convert",
"-O", "-O",
@ -122,7 +138,7 @@ await copyFile(
"./initramfs-virt", "./initramfs-virt",
constants.COPYFILE_FICLONE constants.COPYFILE_FICLONE
); );
await execFile("doas", [ await execFile("sudo", [
"chown", "chown",
"1000:1000", "1000:1000",
"./vmlinuz-virt", "./vmlinuz-virt",
@ -142,43 +158,21 @@ async function setupRootfsDir() {
} catch {} } catch {}
await symlink(basename(currRootPath), currentPath); await symlink(basename(currRootPath), currentPath);
} }
return currRootPath; 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")
);
} }
/** /**
* @param {string} rootfs * @param {string} rootfs
*/ */
async function writeFstab(rootfs) { async function writeFstab(rootfs) {
const rootfsName = "/dev/vda";
const virtualDiskName = "/dev/vda"; const virtualDiskName = "/dev/vda";
await writeFile( await writeFile(
"fstab", "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 * @param {string[]} cmd
*/ */
async function chroot(rootfs, cmd) { async function chroot(rootfs, cmd) {
await execFile("doas", ["chroot", rootfs, ...cmd]); await execFile("sudo", ["chroot", rootfs, ...cmd]);
} }
/** /**

View file

@ -8,6 +8,7 @@
}, },
"dependencies": { "dependencies": {
"dotenv": "^16.0.3", "dotenv": "^16.0.3",
"nanoid": "^4.0.1" "nanoid": "^4.0.1",
"@nulo/apkit": "https://gitea.nulo.in/Nulo/apkit/archive/hackyshit.tar.gz"
} }
} }

View file

@ -1,6 +1,13 @@
lockfileVersion: '6.0' lockfileVersion: '6.1'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
dependencies: 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: dotenv:
specifier: ^16.0.3 specifier: ^16.0.3
version: 16.0.3 version: 16.0.3
@ -29,3 +36,9 @@ packages:
engines: {node: ^14 || ^16 || >=18} engines: {node: ^14 || ^16 || >=18}
hasBin: true hasBin: true
dev: false 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

View file

@ -5,7 +5,6 @@
1. como woodpecker-agent no tiene state, puedo regenerar la vm con el script las veces que quiera 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 1. no tengo que exponer mi docker real al agent porque está en una vm con el suyo
## compilar imágen ## compilar imágen
```sh ```sh
@ -24,6 +23,6 @@ virt-install --name woodpecker-in-a-vm \
--memory 4096 --vcpus 4 \ --memory 4096 --vcpus 4 \
--import \ --import \
--disk path=/var/lib/libvirt/images/vm.qcow2,format=qcow2 \ --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 --noautoconsole
``` ```

View file

@ -4,6 +4,8 @@
"module": "es2022", "module": "es2022",
"moduleResolution": "node", "moduleResolution": "node",
"esModuleInterop": true, "esModuleInterop": true,
"allowJs": true,
"checkJs": true,
"strict": true "strict": true
} }
} }