Compare commits

...

6 commits

Author SHA1 Message Date
a826cfa41a runit: arreglar booteo 2023-02-02 18:33:03 -03:00
ed307f6f66 usar graphic qemu 2023-02-02 18:28:57 -03:00
2e1d79886c runit: traer stages de define-alpine 2023-02-02 18:28:47 -03:00
51b29b7d3d hacer imagen y kernel y correr qemu 2023-02-02 18:25:11 -03:00
e235497717 esbuild silencioso 2023-02-02 18:14:56 -03:00
214e7342ae kernel 2023-01-23 12:30:21 -03:00
12 changed files with 300 additions and 7 deletions

1
.gitignore vendored
View file

@ -1,3 +1,4 @@
node_modules/ node_modules/
build-javascript build-javascript
cache/ cache/
artifacts/

View file

@ -5,7 +5,6 @@ import {
mkdtemp, mkdtemp,
opendir, opendir,
rm, rm,
rmdir,
symlink, symlink,
writeFile, writeFile,
} from "node:fs/promises"; } from "node:fs/promises";

View file

@ -3,6 +3,29 @@ import {
execFile as execFileCallback, execFile as execFileCallback,
spawn as spawnCallback, spawn as spawnCallback,
} from "node:child_process"; } from "node:child_process";
import { access } from "node:fs/promises";
import { getuid } from "node:process";
export const execFile = promisify(execFileCallback); export const execFile = promisify(execFileCallback);
export const spawn = promisify(spawnCallback); export const spawn = promisify(spawnCallback);
export async function canAccess(path: string): Promise<boolean> {
try {
await access(path);
return true;
} catch {
return false;
}
}
export async function sudoChown(path: string, owner: string): Promise<void> {
await execFile("sudo", ["chown", owner, path]);
}
export async function sudoChownToRunningUser(path: string): Promise<void> {
if (getuid) {
await sudoChown(path, "" + getuid());
} else throw new Error("No tengo getuid");
}
export async function sudoRm(path: string): Promise<void> {
await execFile("sudo", ["rm", path]);
}

View file

@ -1,15 +1,45 @@
import { mkdir, mkdtemp } from "node:fs/promises"; import { mkdir, mkdtemp } from "node:fs/promises";
import { tmpdir } from "node:os"; import { tmpdir } from "node:os";
import path from "node:path"; import path from "node:path";
import { cwd } from "node:process";
import { Alpine } from "./alpine.js"; 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"; 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-")); const rootfsDir = await mkdtemp(path.join(tmpdir(), "define-alpine-"));
console.debug(rootfsDir); console.debug(rootfsDir);
const alpine = await Alpine.makeWorld({ dir: rootfsDir }); const alpine = await Alpine.makeWorld({ dir: rootfsDir });
const runit = await Runit.setup(alpine); const runit = await Runit.setup(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");
runQemu(squashfs, kernel, { graphic: true });
// await makeService({ // await makeService({
// parentDir: rootfsDir, // parentDir: rootfsDir,
// name: "grafana", // name: "grafana",
@ -17,9 +47,9 @@ import { Runit } from "./runit/index.js";
// setup: async (dir) => {}, // setup: async (dir) => {},
// initScript: async (dir) => {}, // initScript: async (dir) => {},
// }); // });
try { // try {
await spawn("sudo", ["chroot", rootfsDir], { stdio: "inherit" }); // await spawn("sudo", ["chroot", rootfsDir], { stdio: "inherit" });
} catch {} // } catch {}
} }
// interface Service {} // interface Service {}

91
kernel.ts Normal file
View file

@ -0,0 +1,91 @@
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<Kernel> {
const kind: Kind = "virt";
await alpine.writeFile(
"/etc/update-extlinux.conf",
`# configuration for extlinux config builder
# Overwrite current /boot/extlinux.conf.
overwrite=1
# vesa_menu
# use fancy vesa menu (vesamenu.c32) menus, won't work with serial
vesa_menu=1
#default_kernel_opts=quiet
default_kernel_opts=
modules=loop,squashfs,sd-mod,usb-storage,ext4,vfat
# root device - if not specified, will be guessed using
# blkid -o export /dev/root
root=/dev/sda
# if set to non-zero, update-extlinux will be a lot more verbose.
verbose=0
hidden=0
timeout=3
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="${features.join(" ")}"`
);
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;
}

View file

@ -5,7 +5,7 @@
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"run": "esbuild --target=node18 --sourcemap --outdir=build-javascript --outbase=. *.ts **/*.ts && node --enable-source-maps build-javascript/index.js" "run": "esbuild --log-level=warning --target=node18 --sourcemap --outdir=build-javascript --outbase=. *.ts **/*.ts && node --enable-source-maps build-javascript/index.js"
}, },
"keywords": [], "keywords": [],
"author": "", "author": "",

58
qemu.ts Normal file
View file

@ -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 });
}
}

View file

@ -53,6 +53,12 @@ export class Runit {
await this.getScript("05-misc.sh") await this.getScript("05-misc.sh")
); );
for (let stage = 1; stage <= 3; stage++)
await alpine.writeExecutable(
"/etc/runit/" + stage,
await this.getScript("" + stage)
);
// https://wiki.gentoo.org/wiki/Runit#Reboot_and_shutdown // https://wiki.gentoo.org/wiki/Runit#Reboot_and_shutdown
await alpine.sudoWriteExecutable( await alpine.sudoWriteExecutable(
"/usr/local/sbin/rpoweroff", "/usr/local/sbin/rpoweroff",

View file

@ -75,4 +75,5 @@ msg "Mounting all non-network filesystems..."
mount -a -t "nosysfs,nonfs,nonfs4,nosmbfs,nocifs" -O no_netdev || emergency_shell mount -a -t "nosysfs,nonfs,nonfs4,nosmbfs,nocifs" -O no_netdev || emergency_shell
# data module # data module
msg "Creating and mounting data directories..." msg "Creating and mounting data directories..."
/usr/local/bin/mount-data || emergency_shell # TODO: todavía no tenemos modulo de data que genere esto
# /usr/local/bin/mount-data || emergency_shell

22
runit/scripts/1 Normal file
View file

@ -0,0 +1,22 @@
#!/bin/sh
PATH=/bin:/usr/bin:/usr/sbin:/sbin
. /etc/runit/functions
msg "Welcome to Nulo!"
[ -r /etc/rc.conf ] && . /etc/rc.conf
# Start core services: one-time system tasks.
detect_virt
for f in /etc/runit/core-services/*.sh; do
[ -r $f ] && . $f
done
# create files for controlling runit
mkdir -p /run/runit
install -m000 /dev/null /run/runit/stopit
install -m000 /dev/null /run/runit/reboot
msg "Initialization complete, running stage 2..."

19
runit/scripts/2 Normal file
View file

@ -0,0 +1,19 @@
#!/bin/sh
PATH=/bin:/usr/bin:/usr/sbin:/sbin
runlevel=default
for arg in $(cat /proc/cmdline); do
if [ -d /etc/runit/runsvdir/"$arg" ]; then
echo "Runlevel detected: '$arg' (via kernel cmdline)"
runlevel="$arg"
fi
done
[ -x /etc/rc.local ] && /etc/rc.local
runsvchdir "${runlevel}"
mkdir -p /run/runit/runsvdir
ln -s /etc/runit/runsvdir/current /run/runit/runsvdir/current
exec env - PATH=$PATH \
runsvdir -P /run/runit/runsvdir/current 'log: ................................'

43
runit/scripts/3 Normal file
View file

@ -0,0 +1,43 @@
#!/bin/sh
PATH=/bin:/usr/bin:/usr/sbin:/sbin
. /etc/runit/functions
detect_virt
[ -r /etc/rc.conf ] && . /etc/rc.conf
echo
msg "Waiting for services to stop..."
sv force-stop /etc/service/*
sv exit /etc/service/*
[ -x /etc/rc.shutdown ] && /etc/rc.shutdown
if [ -z "$VIRTUALIZATION" -a -n "$HARDWARECLOCK" ]; then
hwclock --systohc ${HARDWARECLOCK:+--$(echo $HARDWARECLOCK |tr A-Z a-z)}
fi
halt -w # for wtmp
if [ -z "$VIRTUALIZATION" ]; then
msg "Stopping udev..."
udevadm control --exit
fi
if [ -z "$VIRTUALIZATION" ]; then
msg "Unmounting filesystems, disabling swap..."
swapoff -a
umount -r -a -t nosysfs,noproc,nodevtmpfs,notmpfs
fi
sync
if [ -z "$VIRTUALIZATION" ]; then
deactivate_vgs
deactivate_crypt
if [ -e /run/runit/reboot ] && command -v kexec >/dev/null; then
msg "Triggering kexec..."
kexec -e 2>/dev/null
# not reached when kexec was successful.
fi
fi