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 { 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]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
# woodpecker-in-a-vm
|
# woodpecker-in-a-vm
|
||||||
|
|
||||||
1. script que genera una vm con woodpecker agent adentro
|
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. 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. 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
|
||||||
```
|
```
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
"module": "es2022",
|
"module": "es2022",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
|
"allowJs": true,
|
||||||
|
"checkJs": true,
|
||||||
"strict": true
|
"strict": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue