From 6a50670238a88beb2e8f58ff9b41e9ce3312f307 Mon Sep 17 00:00:00 2001 From: Nulo Date: Fri, 17 Jun 2022 21:06:19 -0300 Subject: [PATCH] Empezar --- .gitignore | 4 + alpine.lua | 120 ++++++++++++++ modules/kernel.lua | 31 ++++ modules/runit.lua | 369 ++++++++++++++++++++++++++++++++++++++++++++ qemu.sh | 12 ++ setup.lua | 55 +++++++ utils.lua | 38 +++++ utils/templater.lua | 16 ++ 8 files changed, 645 insertions(+) create mode 100644 .gitignore create mode 100644 alpine.lua create mode 100644 modules/kernel.lua create mode 100644 modules/runit.lua create mode 100755 qemu.sh create mode 100644 setup.lua create mode 100644 utils.lua create mode 100644 utils/templater.lua diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..507e3f4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +root/ +v00001/ +boot/ +image.squashfs diff --git a/alpine.lua b/alpine.lua new file mode 100644 index 0000000..f2cf0aa --- /dev/null +++ b/alpine.lua @@ -0,0 +1,120 @@ +local alpine = {} + +local utils = require("utils") +local t = require("utils/templater") + +-- Returns nil when no failure, otherwise apk's status code +function alpine.init_rootfs(path, alpine_base_version, alpine_version) + local status = os.execute(t("doas rm -rf {{path}} && mkdir -p {{path}}", {path = path})) + if not (status == 0) then return status end + local url = t("https://dl-cdn.alpinelinux.org/alpine/v{{base_version}}/releases/x86_64/alpine-minirootfs-{{version}}-x86_64.tar.gz", { + base_version = alpine_base_version, + version = alpine_version + }) + local status = os.execute(t("cd {{path}} && wget --no-verbose -O- {{url}} | tar zx", { path = path, url = url })) + if not (status == 0) then return status end +end + +function alpine.move_boot(path) + local status = os.execute(t("doas rm -rf {{path}}/../boot && doas mv {{path}}/boot {{path}}/../ && doas mkdir {{path}}/boot", { + path = path, + })) + if not (status == 0) then return status end +end + +function alpine.make_squashfs(path, output_path) + local status = os.execute(t("mksquashfs {{path}} {{output_path}} -comp zstd -noappend -quiet", { + path = path, + output_path = output_path, + })) + if not (status == 0) then return status end + -- status = os.execute(t("qemu-img convert {{output_path}} {{output_path}}.qcow2 -O qcow2", { + -- output_path = output_path, + -- })) + -- if not (status == 0) then return status end +end + +function alpine.mkdir(rootfs_path, path) + local real_path = rootfs_path..path + + local cmd = t("mkdir -p {{real_path}}", { + real_path = real_path, + }) + + -- XXX: Usar lua-posix + local status = os.execute(cmd) + if not (status == 0) then return status end +end + +function alpine.write_file(rootfs_path, path, content) + local real_path = rootfs_path..path + + local cmd = t("mkdir -p {{real_dirname}} && test -f {{real_path}} || exit 0 && doas chown $(id -u) {{real_path}}", { + real_path = real_path, + real_dirname = utils.dirname(real_path), + }) + + -- XXX: Usar lua-posix + local status = os.execute(cmd) + if not (status == 0) then return status end + + local file, err = io.open(real_path, "w+") + if not file then return err end + + file:write(content) + file:close() +end + +function alpine.symlink(rootfs_path, path, target) + local real_path = rootfs_path..path + + local cmd = t("mkdir -p {{real_dirname}} && ln -s {{target}} {{real_path}}", { + real_path = real_path, + target = target, + real_dirname = utils.dirname(real_path), + }) + + -- XXX: Usar lua-posix + local status = os.execute(cmd) + if not (status == 0) then return status end +end + +function alpine.chmod(rootfs_path, path, perms) + local real_path = rootfs_path..path + + local cmd = t("chmod {{perms}} {{real_path}}", { + real_path = real_path, + perms = perms, + }) + + -- XXX: Usar lua-posix + local status = os.execute(cmd) + if not (status == 0) then return status end +end + +-- Returns nil when no failure, otherwise string error or apk's status code +function alpine.make_world(rootfs_path, packages) + local world = alpine.write_file(rootfs_path, + "/etc/apk/world", + utils.join_table(packages, "\n")) + if err then return err end + + local status = os.execute("doas apk update --no-cache --root "..rootfs_path) + if not (status == 0) then return status end + local status = os.execute("doas apk upgrade --no-cache --root "..rootfs_path) + if not (status == 0) then return status end + local status = os.execute("doas apk add --no-cache --root "..rootfs_path) + if not (status == 0) then return status end +end + +-- Returns nil when no failure, otherwise status code +function alpine.set_password(rootfs_path, user, password) + local status = os.execute(t("echo '{{password}}\n{{password}}' | doas chroot {{rootfs_path}} passwd {{user}}", { + password = password, + rootfs_path = rootfs_path, + user = user, + })) + if not (status == 0) then return status end +end + +return alpine diff --git a/modules/kernel.lua b/modules/kernel.lua new file mode 100644 index 0000000..53fdf4c --- /dev/null +++ b/modules/kernel.lua @@ -0,0 +1,31 @@ +print("=> Module: kernel") +modules.kernel = {} + +add_packages({ "linux-virt", "syslinux" }) +add_file("/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 +]]) + +add_file("/etc/mkinitfs/mkinitfs.conf", + 'features="squashfs ata base cdrom ext4 keymap kms mmc nvme scsi usb virtio"') diff --git a/modules/runit.lua b/modules/runit.lua new file mode 100644 index 0000000..245d72b --- /dev/null +++ b/modules/runit.lua @@ -0,0 +1,369 @@ +print("=> Module: runit") + +mkdir("/etc/runit/runsvdir/default") +add_symlink("/etc/runit/runsvdir/current", "/etc/runit/runsvdir/default") +add_symlink("/etc/service", "/etc/runit/runsvdir/current") + +local t = require "../utils/templater" +local function add_executable(path, script) + add_file(path, script) + chmod(path, "700") +end +local function add_service(name, script) + local params = {name = name} + local run_script_path = t("/etc/sv/{{name}}/run", params) + add_executable(run_script_path, script) + add_symlink("/etc/runit/runsvdir/default/"..name, "/etc/sv/"..name) + add_symlink( + t("/etc/sv/{{name}}/supervise", params), + t("/run/runit/supervise.{{name}}", params) + ) +end + +modules.runit = { + add_service = add_service, +} + +add_mount("tmpfs /var/log tmpfs defaults 0 0") + +-- Estos scripts fueron robados de Void Linux +add_executable("/etc/runit/functions", [[ +msg() { + # bold + printf "\\033[1m=> $@\\033[m\\n" +} + +msg_ok() { + # bold/green + printf "\\033[1m\\033[32m OK\\033[m\\n" +} + +msg_error() { + # bold/red + printf "\\033[1m\\033[31mERROR: $@\\033[m\\n" +} + +msg_warn() { + # bold/yellow + printf "\\033[1m\\033[33mWARNING: $@\\033[m\\n" +} + +emergency_shell() { + echo + echo "Cannot continue due to errors above, starting emergency shell." + echo "When ready type exit to continue booting." + /bin/sh -l +} + +detect_virt() { + # Detect LXC (and other) containers + [ -z "${container+x}" ] || export VIRTUALIZATION=1 +} + +deactivate_vgs() { + _group=${1:-All} + if [ -x /sbin/vgchange -o -x /bin/vgchange ]; then + vgs=$(vgs|wc -l) + if [ $vgs -gt 0 ]; then + msg "Deactivating $_group LVM Volume Groups..." + vgchange -an + fi + fi +} + +deactivate_crypt() { + if [ -x /sbin/dmsetup -o -x /bin/dmsetup ]; then + msg "Deactivating Crypt Volumes" + for v in $(dmsetup ls --target crypt --exec "dmsetup info -c --noheadings -o open,name"); do + [ ${v%%:*} = "0" ] && cryptsetup close ${v##*:} + done + deactivate_vgs "Crypt" + fi +} +]]) + +add_executable("/etc/runit/core-services/00-pseudofs.sh", [[ +msg "Mounting pseudo-filesystems..." +mountpoint -q /proc || mount -o nosuid,noexec,nodev -t proc proc /proc +mountpoint -q /sys || mount -o nosuid,noexec,nodev -t sysfs sys /sys +mountpoint -q /run || mount -o mode=0755,nosuid,nodev -t tmpfs run /run +mountpoint -q /dev || mount -o mode=0755,nosuid -t devtmpfs dev /dev +mkdir -p -m0755 /run/runit /run/lvm /run/user /run/lock /run/log /dev/pts /dev/shm +mountpoint -q /dev/pts || mount -o mode=0620,gid=5,nosuid,noexec -n -t devpts devpts /dev/pts +mountpoint -q /dev/shm || mount -o mode=1777,nosuid,nodev -n -t tmpfs shm /dev/shm +mountpoint -q /sys/kernel/security || mount -n -t securityfs securityfs /sys/kernel/security + +if [ -d /sys/firmware/efi/efivars ]; then + mountpoint -q /sys/firmware/efi/efivars || mount -o nosuid,noexec,nodev -t efivarfs efivarfs /sys/firmware/efi/efivars +fi + +if [ -z "$VIRTUALIZATION" ]; then + _cgroupv1="" + _cgroupv2="" + + case "${CGROUP_MODE:-hybrid}" in + legacy) + _cgroupv1="/sys/fs/cgroup" + ;; + hybrid) + _cgroupv1="/sys/fs/cgroup" + _cgroupv2="${_cgroupv1}/unified" + ;; + unified) + _cgroupv2="/sys/fs/cgroup" + ;; + esac + + # cgroup v1 + if [ -n "$_cgroupv1" ]; then + mountpoint -q "$_cgroupv1" || mount -o mode=0755 -t tmpfs cgroup "$_cgroupv1" + while read -r _subsys_name _hierarchy _num_cgroups _enabled; do + [ "$_enabled" = "1" ] || continue + _controller="${_cgroupv1}/${_subsys_name}" + mkdir -p "$_controller" + mountpoint -q "$_controller" || mount -t cgroup -o "$_subsys_name" cgroup "$_controller" + done < /proc/cgroups + fi + + # cgroup v2 + if [ -n "$_cgroupv2" ]; then + mkdir -p "$_cgroupv2" + mountpoint -q "$_cgroupv2" || mount -t cgroup2 -o nsdelegate cgroup2 "$_cgroupv2" + fi +fi]]) + +add_executable("/etc/runit/core-services/01-static-devnodes.sh", [[ +# Some kernel modules must be loaded before starting udev(7). +# Load them by looking at the output of `kmod static-nodes`. + +for f in $(kmod static-nodes 2>/dev/null|awk '/Module/ {print $2}'); do + modprobe -bq $f 2>/dev/null +done +]]) + +add_executable("/etc/runit/core-services/02-udev.sh", [[ +[ -n "$VIRTUALIZATION" ] && return 0 + +if [ -x /sbin/udevd -o -x /bin/udevd ]; then + _udevd=udevd +else + msg_warn "cannot find udevd!" +fi + +if [ -n "${_udevd}" ]; then + msg "Starting udev and waiting for devices to settle..." + ${_udevd} --daemon + udevadm trigger --action=add --type=subsystems + udevadm trigger --action=add --type=devices + udevadm settle +fi +]]) + + +add_executable("/etc/runit/core-services/03-filesystems.sh", [[ +[ -n "$VIRTUALIZATION" ] && return 0 + +#msg "Remounting rootfs read-only..." +#mount -o remount,ro / || emergency_shell + +if [ -x /sbin/dmraid -o -x /bin/dmraid ]; then + msg "Activating dmraid devices..." + dmraid -i -ay +fi + +if [ -x /bin/mdadm ]; then + msg "Activating software RAID arrays..." + mdadm -As +fi + +if [ -x /bin/btrfs ]; then + msg "Activating btrfs devices..." + btrfs device scan || emergency_shell +fi + +if [ -x /sbin/vgchange -o -x /bin/vgchange ]; then + msg "Activating LVM devices..." + vgchange --sysinit -a ay || emergency_shell +fi + +if [ -e /etc/crypttab ]; then + msg "Activating encrypted devices..." + awk -f /etc/runit/crypt.awk /etc/crypttab + + if [ -x /sbin/vgchange -o -x /bin/vgchange ]; then + msg "Activating LVM devices for dm-crypt..." + vgchange --sysinit -a ay || emergency_shell + fi +fi + +if [ -x /usr/bin/zpool -a -x /usr/bin/zfs ]; then + if [ -e /etc/zfs/zpool.cache ]; then + msg "Importing cached ZFS pools..." + zpool import -N -a -c /etc/zfs/zpool.cache + else + msg "Scanning for and importing ZFS pools..." + zpool import -N -a -o cachefile=none + fi + + msg "Mounting ZFS file systems..." + zfs mount -a -l + + msg "Sharing ZFS file systems..." + zfs share -a + + # NOTE(dh): ZFS has ZVOLs, block devices on top of storage pools. + # In theory, it would be possible to use these as devices in + # dmraid, btrfs, LVM and so on. In practice it's unlikely that + # anybody is doing that, so we aren't supporting it for now. +fi + +[ -f /fastboot ] && FASTBOOT=1 +[ -f /forcefsck ] && FORCEFSCK="-f" +for arg in $(cat /proc/cmdline); do + case $arg in + fastboot) FASTBOOT=1;; + forcefsck) FORCEFSCK="-f";; + esac +done + +if [ -z "$FASTBOOT" ]; then + msg "Checking filesystems:" + fsck -A -T -a -t noopts=_netdev $FORCEFSCK + if [ $? -gt 1 ]; then +s emergency_shell + fi +fi + +msg "Mounting all non-network filesystems..." +mount -a -t "nosysfs,nonfs,nonfs4,nosmbfs,nocifs" -O no_netdev || emergency_shell +]]) + +add_executable("/etc/runit/core-services/04-swap.sh", [[ +[ -n "$VIRTUALIZATION" ] && return 0 + +msg "Initializing swap..." +swapon -a || emergency_shell +]]) + +add_executable("/etc/runit/core-services/05-misc.sh", [[ +install -m0664 -o root -g utmp /dev/null /run/utmp +#halt -B # for wtmp + +msg "Setting up loopback interface..." +ip link set up dev lo + +loadkeys -q -u us +]]) + +-- Initial boot +add_executable("/etc/runit/1", [[#!/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 + +dmesg >/var/log/dmesg.log +if [ $(sysctl -n kernel.dmesg_restrict 2>/dev/null) -eq 1 ]; then + chmod 0600 /var/log/dmesg.log +else + chmod 0644 /var/log/dmesg.log +fi + +# 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..." +]]) +add_executable("/etc/runit/2", [[#!/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 + +ls -la /run/runit/runsvdir/current/ + +exec env - PATH=$PATH \ + runsvdir -P /run/runit/runsvdir/current 'log: ................................' +]]) + +-- Shutdown +add_executable("/etc/runit/3", [[#!/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 + +msg "Sending TERM signal to processes..." +pkill --inverse -s0,1 -TERM +sleep 1 +msg "Sending KILL signal to processes..." +pkill --inverse -s0,1 -KILL + +if [ -z "$VIRTUALIZATION" ]; then + msg "Unmounting filesystems, disabling swap..." + swapoff -a + umount -r -a -t nosysfs,noproc,nodevtmpfs,notmpfs + msg "Remounting rootfs read-only..." + mount -o remount,ro / +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 +]]) + +add_packages({ "runit", "procps", "eudev" }) +modules.runit.add_service("getty-tty1", [[#!/bin/sh +exec chpst -P getty 38400 tty1 linux]]) +modules.runit.add_service("getty-tty2", [[#!/bin/sh +exec chpst -P getty 38400 tty2 linux]]) diff --git a/qemu.sh b/qemu.sh new file mode 100755 index 0000000..c27b25f --- /dev/null +++ b/qemu.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +if test "$NOGRAPHIC" = true; then + append="console=ttyS0" + qemuappend="-nographic" +fi + +doas chown root:$(id -u) -R boot/ && doas chmod g+rw -R boot/ +qemu-system-x86_64 -enable-kvm -m 2048 \ + -drive file=image.squashfs,media=disk \ + -kernel boot/vmlinuz-virt -initrd boot/initramfs-virt \ + -append "root=/dev/sda rootfstype=squashfs init=/sbin/runit-init $append" $qemuappend diff --git a/setup.lua b/setup.lua new file mode 100644 index 0000000..7a12c0a --- /dev/null +++ b/setup.lua @@ -0,0 +1,55 @@ +local utils = require("utils") +local alpine = require("alpine") + +local alpine_base_version = "3.15" +local alpine_version = alpine_base_version..".0" +local packages = { + "alpine-baselayout", + -- "alpine-conf", + "apk-tools", + "busybox", + "libc-utils", + "alpine-keys", +} +local mounts = { + "tmpfs /tmp tmpfs defaults 0 0", +} +modules = {} + +local root = "./root" + +function add_packages(new_packages) + utils.table_concat(packages, new_packages) +end +function add_mount(new_mount) + table.insert(mounts, new_mount) +end +function add_file(path, content) + utils.expect_nil(alpine.write_file(root, path, content)) +end +function add_symlink(path, target) + utils.expect_nil(alpine.symlink(root, path, target)) +end +function chmod(path, perms) + utils.expect_nil(alpine.chmod(root, path, perms)) +end +function mkdir(path) + utils.expect_nil(alpine.mkdir(root, path)) +end + +print("=> Initializing rootfs...") +utils.expect_nil(alpine.init_rootfs(root, alpine_base_version, alpine_version)) + +require("modules/kernel") +require("modules/runit") + +print("=> Writing fstab...") +add_file("/etc/fstab", utils.join_table(mounts, "\n")) +print("=> Installing and upgrading packages...") +utils.expect_nil(alpine.make_world(root, packages)) +print("=> Setting password...") +utils.expect_nil(alpine.set_password(root, "root", "12345678")) +print("=> Moving boot...") +utils.expect_nil(alpine.move_boot(root)) +print("=> Making image...") +utils.expect_nil(alpine.make_squashfs(root, "image.squashfs")) diff --git a/utils.lua b/utils.lua new file mode 100644 index 0000000..827ee40 --- /dev/null +++ b/utils.lua @@ -0,0 +1,38 @@ +local utils = {} + +function utils.expect_nil(result) + if result then + print("Error: "..result) + os.exit(1) + end + return nil +end + +-- https://stackoverflow.com/a/15278426 +function utils.table_concat(t1, t2) + for i=1,#t2 do + t1[#t1+1] = t2[i] + end + return t1 +end + +-- Turns a table of strings into a string separated by separator. +function utils.join_table(table, separator) + local string = "" + for i=1,#table do + string = string .. table[i] .. separator + end + return string +end + +-- https://github.com/Donearm/scripts/blob/ad3429dc4b69e6108f538bf1656216c7a192c9fd/lib/basename.lua +function utils.basename(str) + return string.gsub(str, "(.*/)(.*)", "%2") +end + +-- https://gist.github.com/AndrewHazelden/a7b6551915a71a44770e +function utils.dirname(str) + return str:match("(.*/)") +end + +return utils diff --git a/utils/templater.lua b/utils/templater.lua new file mode 100644 index 0000000..a7106c5 --- /dev/null +++ b/utils/templater.lua @@ -0,0 +1,16 @@ +local utils = require "../utils" +return function (template, params, pattern) + local content = string.gsub(template, pattern or "{{([%w_]+)}}", function (s) + if params[s] == nil then + utils.expect_nil("No variable "..s) + end + if params[s] == true then + return "true" + end + if params[s] == false then + return "false" + end + return params[s] + end) + return content +end