hopefully computers don't exist in the real world
This commit is contained in:
parent
48008e3110
commit
46d08afd90
6 changed files with 75 additions and 65 deletions
1
.npmrc
Normal file
1
.npmrc
Normal file
|
@ -0,0 +1 @@
|
|||
@nulo:registry=https://gitea.nulo.in/api/packages/nulo/npm/
|
112
index.js
112
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<string>)} patchFunc
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
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]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
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
|
||||
```
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
"module": "es2022",
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"strict": true
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue