diff --git a/flake.lock b/flake.lock index 884ccb5..363b893 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1685604690, - "narHash": "sha256-TEAMrNsfZZ2vuSBRU6giSy4xKkXyzdw41Xtio7UWa7M=", + "lastModified": 1686968143, + "narHash": "sha256-NkXmT9ArJBeu56jo/agURQ1pvqrx0nUHi30yM7sttK8=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "50bc0fd68fcadd68df77d554dff6a27d456827cd", + "rev": "56799517d0537a6f3e91a5171af8c4bfd82c092e", "type": "github" }, "original": { @@ -48,11 +48,11 @@ ] }, "locked": { - "lastModified": 1685587239, - "narHash": "sha256-zpOir1AWpWyQscP5dMpqMrCgBzjzH7Wv0FNUsQ0dcS0=", + "lastModified": 1686968542, + "narHash": "sha256-Gjlj7UeHqMFRAYyefeoLnSjLo8V+0XheIamojNEyTbE=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "acb7e896a73b0cf2c6ffe40b2051eb7f88fc2a10", + "rev": "01d84cd842e48e89be67e4c2d9dc46aa7709adc5", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 440c06f..78b17b9 100644 --- a/flake.nix +++ b/flake.nix @@ -9,112 +9,147 @@ }; }; - outputs = { self, nixpkgs, rust }: { - lib = nixpkgs.lib.extend (import ./lib.nix); + outputs = { self, nixpkgs, rust }: + let inherit (self) lib; + in { + lib = nixpkgs.lib.extend (import ./lib.nix); - overlays.default = final: prev: - let rust' = (prev.extend rust.overlay).rust-bin.nightly.latest.default; - in { - inherit (self) lib; + overlays.default = final: prev: + let rust' = (prev.extend rust.overlay).rust-bin.nightly.latest.default; + in { + lib = prev.lib.extend (import ./lib.nix); - acpi_actor = final.nimPackages.callPackage ./packages/acpi_actor { }; + acpi_actor = final.nimPackages.callPackage ./packages/acpi_actor { }; - fontconfig_actor = - final.nimPackages.callPackage ./packages/fontconfig_actor { }; + fontconfig_actor = + final.nimPackages.callPackage ./packages/fontconfig_actor { }; - libnotify_actor = - final.nimPackages.callPackage ./packages/libnotify_actor { }; + libnotify_actor = + final.nimPackages.callPackage ./packages/libnotify_actor { }; - nimPackages = prev.nimPackages.overrideScope' (final': prev': { - noiseprotocol = final'.callPackage ./packages/nim/noiseprotocol { }; - preserves = final'.callPackage ./packages/preserves-nim { }; - syndicate = final'.callPackage ./packages/syndicate-nim { }; - syndicate_utils = - final'.callPackage ./packages/syndicate_utils-nim { }; - xdg_open_ng = final'.callPackage ./packages/xdg_open_ng { }; - }); + nimPackages = prev.nimPackages.overrideScope' (final': prev': { + noiseprotocol = final'.callPackage ./packages/nim/noiseprotocol { }; + preserves = final'.callPackage ./packages/preserves-nim { }; + syndicate = final'.callPackage ./packages/syndicate-nim { }; + syndicate_utils = + final'.callPackage ./packages/syndicate_utils-nim { }; + xdg_open_ng = final'.callPackage ./packages/xdg_open_ng { }; + }); - nix_actor = final.nimPackages.callPackage ./packages/nix_actor { }; + nix_actor = final.nimPackages.callPackage ./packages/nix_actor { }; - noise-c = final.callPackage ./packages/noise-c { }; + noise-c = final.callPackage ./packages/noise-c { }; - python3Packages = prev.python3Packages.overrideScope (final': prev': { - preserves = final'.callPackage ./packages/preserves-py { }; - syndicate = final'.callPackage ./packages/syndicate-py { }; - }); + python3Packages = prev.python3Packages.overrideScope (final': prev': { + preserves = final'.callPackage ./packages/preserves-py { }; + syndicate = final'.callPackage ./packages/syndicate-py { }; + synit-daemons = final'.callPackage ./packages/synit-daemons { }; + }); - preserves-tools = - final.callPackage ./packages/preserves-tools { rust = rust'; }; + preserves-tools = + final.callPackage ./packages/preserves-tools { rust = rust'; }; - sqlite_actor = - final.nimPackages.callPackage ./packages/sqlite_actor { }; + sqlite_actor = + final.nimPackages.callPackage ./packages/sqlite_actor { }; - squeak = final.callPackage ./packages/squeak { }; - squeaker = final.python3Packages.callPackage ./packages/squeaker { }; + squeak = final.callPackage ./packages/squeak { }; + squeaker = final.python3Packages.callPackage ./packages/squeaker { }; + + syndicate-server = + final.callPackage ./packages/syndicate-server { rust = rust'; }; + + synit-pid1 = + final.callPackage ./packages/synit-pid1 { rust = rust'; }; + + xapian_actor = + final.nimPackages.callPackage ./packages/xapian_actor { }; + }; + + legacyPackages = + lib.pipe { inherit (nixpkgs.legacyPackages) x86_64-linux; } [ + (self.lib.mapAttrs (system: pkgs: pkgs.extend self.overlays.default)) + ]; + + packages = lib.pipe self.legacyPackages [ + (self.lib.mapAttrs (system: pkgs: + with pkgs; { + inherit fontconfig_actor libnotify_actor noise-c preserves-tools + squeak squeaker syndicate-server; + inherit (nimPackages) syndicate_utils xdg_open_ng; + preserves-nim = nimPackages.preserves; + preserves-py = python3Packages.preserves; + syndicate-nim = nimPackages.syndicate; + syndicate-py = python3Packages.syndicate; + })) + ]; + + nixosModules = { + default = self.nixosModules.syndicate-server; syndicate-server = - final.callPackage ./packages/syndicate-server { rust = rust'; }; - - synit-pid1 = final.callPackage ./packages/synit-pid1 { rust = rust'; }; - - xapian_actor = - final.nimPackages.callPackage ./packages/xapian_actor { }; - }; - - legacyPackages = - self.lib.mapAttrs (system: pkgs: pkgs.extend self.overlays.default) { - inherit (nixpkgs.legacyPackages) x86_64-linux; - }; - - packages = self.lib.mapAttrs (system: pkgs: - with pkgs; { - inherit fontconfig_actor libnotify_actor noise-c preserves-tools squeak - squeaker syndicate-server; - inherit (nimPackages) syndicate_utils xdg_open_ng; - preserves-nim = nimPackages.preserves; - preserves-py = python3Packages.preserves; - syndicate-nim = nimPackages.syndicate; - syndicate-py = python3Packages.syndicate; - }) self.legacyPackages; - - nixosModules.default = self.nixosModules.syndicate-server; - nixosModules.syndicate-server = - # A little hack to apply our overlay to this module only. - { config, lib, pkgs, ... }: - (import ./nixos/syndicate-server.nix) { - inherit config lib; - pkgs = pkgs.extend self.overlays.default; - }; - - devShells = self.lib.mapAttrs (system: pkgs: - with pkgs; { - default = - mkShell { packages = builtins.attrValues self.packages.${system}; }; - }) self.legacyPackages; - - checks = self.lib.mapAttrs (system: pkgs': - with import (nixpkgs + "/nixos/lib/testing-python.nix") { - inherit system; - pkgs = pkgs'; - }; { - simple = simpleTest { - name = "http"; - nodes.machine = { config, pkgs, ... }: { - imports = [ self.nixosModules.syndicate-server ]; - - nixpkgs.pkgs = pkgs'; - - services.syndicate.tty1 = { - enable = true; - user = "loser"; - config = [ ]; - }; - users.users.loser.isNormalUser = true; + # A little hack to apply our overlay to this module only. + { config, lib, pkgs, ... }: + (import ./nixos/syndicate-server.nix) { + inherit config lib; + pkgs = pkgs.extend self.overlays.default; }; - testScript = '' - machine.wait_for_job("syndicate-tty1") - ''; - }; - }) { inherit (self.legacyPackages) x86_64-linux; }; - }; + + synit.imports = [ + ./nixos/modules/synit/boot/default.nix + ./nixos/modules/synit/boot/stage-1.nix + ./nixos/modules/synit/boot/stage-2.nix + ./nixos/modules/synit/etc + ./nixos/modules/synit/networking.nix + ./nixos/modules/synit/services + ]; + + }; + + devShells = self.lib.mapAttrs (system: pkgs: + with pkgs; { + default = + mkShell { packages = builtins.attrValues self.packages.${system}; }; + }) self.legacyPackages; + + checks = lib.pipe { inherit (self.legacyPackages) x86_64-linux; } [ + (self.lib.mapAttrs (system: pkgs': + with import (nixpkgs + "/nixos/lib/testing-python.nix") { + inherit system; + pkgs = pkgs'; + }; { + simple = simpleTest { + name = "http"; + nodes.machine = { config, pkgs, ... }: { + imports = [ self.nixosModules.syndicate-server ]; + + nixpkgs.pkgs = pkgs'; + + services.syndicate.tty1 = { + enable = true; + user = "loser"; + config = [ ]; + }; + users.users.loser.isNormalUser = true; + }; + testScript = '' + machine.wait_for_job("syndicate-tty1") + ''; + }; + + synit = simpleTest { + name = "synit"; + nodes.machine = { + imports = [ self.nixosModules.synit ]; + + nixpkgs.pkgs = pkgs'; + }; + testScript = '' + start_all() + machine.wait_for_unit("foo.service") + ''; + }; + + })) + ]; + }; } diff --git a/lib.nix b/lib.nix index 1aa490d..28fa9fc 100644 --- a/lib.nix +++ b/lib.nix @@ -1,8 +1,8 @@ final: prev: -let lib = final; +let lib = prev; in with lib; { - generators = with final.generators; + generators = prev.generators // { /* Generates text-encoded Preserves from an arbitrary value. @@ -18,7 +18,7 @@ in with lib; { */ toPreserves = { }@args: let - toPreserves' = toPreserves args; + toPreserves' = final.generators.toPreserves args; concatItems = toString; recordLabel = list: with builtins; diff --git a/nixos/modules/synit/boot/default.nix b/nixos/modules/synit/boot/default.nix new file mode 100644 index 0000000..5342f01 --- /dev/null +++ b/nixos/modules/synit/boot/default.nix @@ -0,0 +1,15 @@ +{ lib, pkgs, ... }: + +{ + config = { + + boot.initrd = { + enable = true; + systemd.enable = false; + verbose = true; + }; + + systemd.package = pkgs.systemd // { meta.broken = true; }; + + }; +} diff --git a/nixos/modules/synit/boot/stage-1-init.sh b/nixos/modules/synit/boot/stage-1-init.sh new file mode 100644 index 0000000..8baca35 --- /dev/null +++ b/nixos/modules/synit/boot/stage-1-init.sh @@ -0,0 +1,626 @@ +#! @shell@ + +targetRoot=/mnt-root +console=tty1 +verbose="@verbose@" + +info() { + if [[ -n "$verbose" ]]; then + echo "$@" + fi +} + +extraUtils="@extraUtils@" +export LD_LIBRARY_PATH=@extraUtils@/lib +export PATH=@extraUtils@/bin +ln -s @extraUtils@/bin /bin + +# Copy the secrets to their needed location +if [ -d "@extraUtils@/secrets" ]; then + for secret in $(cd "@extraUtils@/secrets"; find . -type f); do + mkdir -p $(dirname "/$secret") + ln -s "@extraUtils@/secrets/$secret" "$secret" + done +fi + + +fail() { + if [ -n "$panicOnFail" ]; then exit 1; fi + + @preFailCommands@ + + # If starting stage 2 failed, allow the user to repair the problem + # in an interactive shell. + cat </dev/$console 2>/dev/$console" + elif [ -n "$allowShell" -a "$reply" = i ]; then + echo "Starting interactive shell..." + setsid @shell@ -c "exec @shell@ < /dev/$console >/dev/$console 2>/dev/$console" || fail + elif [ "$reply" = r ]; then + echo "Rebooting..." + reboot -f + else + info "Continuing..." + fi +} + +trap 'fail' 0 + + +# Print a greeting. +info +info "<<< NixOS Stage 1 >>>" +info + +# Make several required directories. +mkdir -p /etc/udev +touch /etc/fstab # to shut up mount +ln -s /proc/mounts /etc/mtab # to shut up mke2fs +touch /etc/udev/hwdb.bin # to shut up udev +touch /etc/initrd-release + +# Function for waiting for device(s) to appear. +waitDevice() { + local device="$1" + # Split device string using ':' as a delimiter as bcachefs + # uses this for multi-device filesystems, i.e. /dev/sda1:/dev/sda2:/dev/sda3 + local IFS=':' + + # USB storage devices tend to appear with some delay. It would be + # great if we had a way to synchronously wait for them, but + # alas... So just wait for a few seconds for the device to + # appear. + for dev in $device; do + if test ! -e $dev; then + echo -n "waiting for device $dev to appear..." + try=20 + while [ $try -gt 0 ]; do + sleep 1 + udevadm trigger --action=add + if test -e $dev; then break; fi + echo -n "." + try=$((try - 1)) + done + echo + [ $try -ne 0 ] + fi + done +} + +# Mount special file systems. +specialMount() { + local device="$1" + local mountPoint="$2" + local options="$3" + local fsType="$4" + + mkdir -m 0755 -p "$mountPoint" + mount -n -t "$fsType" -o "$options" "$device" "$mountPoint" +} +source @earlyMountScript@ + +# Copy initrd secrets from /.initrd-secrets to their actual destinations +if [ -d "/.initrd-secrets" ]; then + # + # Secrets are named by their full destination pathname and stored + # under /.initrd-secrets/ + # + for secret in $(cd "/.initrd-secrets"; find . -type f); do + mkdir -p $(dirname "/$secret") + cp "/.initrd-secrets/$secret" "$secret" + done +fi + +# Log the script output to /dev/kmsg or /run/log/stage-1-init.log. +mkdir -p /tmp +mkfifo /tmp/stage-1-init.log.fifo +logOutFd=8 && logErrFd=9 +eval "exec $logOutFd>&1 $logErrFd>&2" +if test -w /dev/kmsg; then + tee -i < /tmp/stage-1-init.log.fifo /proc/self/fd/"$logOutFd" | while read -r line; do + if test -n "$line"; then + echo "<7>stage-1-init: [$(date)] $line" > /dev/kmsg + fi + done & +else + mkdir -p /run/log + tee -i < /tmp/stage-1-init.log.fifo /run/log/stage-1-init.log & +fi +exec > /tmp/stage-1-init.log.fifo 2>&1 + + +# Process the kernel command line. +export stage2Init=/init +for o in $(cat /proc/cmdline); do + case $o in + console=*) + set -- $(IFS==; echo $o) + params=$2 + set -- $(IFS=,; echo $params) + console=$1 + ;; + init=*) + set -- $(IFS==; echo $o) + stage2Init=$2 + ;; + boot.persistence=*) + set -- $(IFS==; echo $o) + persistence=$2 + ;; + boot.persistence.opt=*) + set -- $(IFS==; echo $o) + persistence_opt=$2 + ;; + boot.trace|debugtrace) + # Show each command. + set -x + ;; + boot.shell_on_fail) + allowShell=1 + ;; + boot.debug1|debug1) # stop right away + allowShell=1 + fail + ;; + boot.debug1devices) # stop after loading modules and creating device nodes + allowShell=1 + debug1devices=1 + ;; + boot.debug1mounts) # stop after mounting file systems + allowShell=1 + debug1mounts=1 + ;; + boot.panic_on_fail|stage1panic=1) + panicOnFail=1 + ;; + root=*) + # If a root device is specified on the kernel command + # line, make it available through the symlink /dev/root. + # Recognise LABEL= and UUID= to support UNetbootin. + set -- $(IFS==; echo $o) + if [ $2 = "LABEL" ]; then + root="/dev/disk/by-label/$3" + elif [ $2 = "UUID" ]; then + root="/dev/disk/by-uuid/$3" + else + root=$2 + fi + ln -s "$root" /dev/root + ;; + copytoram) + copytoram=1 + ;; + findiso=*) + # if an iso name is supplied, try to find the device where + # the iso resides on + set -- $(IFS==; echo $o) + isoPath=$2 + ;; + esac +done + +# Set hostid before modules are loaded. +# This is needed by the spl/zfs modules. +@setHostId@ + +# Load the required kernel modules. +mkdir -p /lib +ln -s @modulesClosure@/lib/modules /lib/modules +ln -s @modulesClosure@/lib/firmware /lib/firmware +# see comment in stage-1.nix for explanation +echo @extraUtils@/bin/modprobe-kernel > /proc/sys/kernel/modprobe +for i in @kernelModules@; do + info "loading module $(basename $i)..." + modprobe $i +done + + +# Create device nodes in /dev. +@preDeviceCommands@ +info "running udev..." +ln -sfn /proc/self/fd /dev/fd +ln -sfn /proc/self/fd/0 /dev/stdin +ln -sfn /proc/self/fd/1 /dev/stdout +ln -sfn /proc/self/fd/2 /dev/stderr +mkdir -p /etc/udev +ln -sfn @udevRules@ /etc/udev/rules.d +mkdir -p /dev/.mdadm +udevd --daemon +udevadm trigger --action=add +udevadm settle + + +if test -n "$debug1devices"; then fail; fi + + +@postDeviceCommands@ + + +# Check the specified file system, if appropriate. +checkFS() { + local device="$1" + local fsType="$2" + + # Only check block devices. + if [ ! -b "$device" ]; then return 0; fi + + # Don't check ROM filesystems. + if [ "$fsType" = iso9660 -o "$fsType" = udf ]; then return 0; fi + + # Don't check resilient COWs as they validate the fs structures at mount time + if [ "$fsType" = btrfs -o "$fsType" = zfs -o "$fsType" = bcachefs ]; then return 0; fi + + # Skip fsck for apfs as the fsck utility does not support repairing the filesystem (no -a option) + if [ "$fsType" = apfs ]; then return 0; fi + + # Skip fsck for nilfs2 - not needed by design and no fsck tool for this filesystem. + if [ "$fsType" = nilfs2 ]; then return 0; fi + + # Skip fsck for inherently readonly filesystems. + if [ "$fsType" = squashfs ]; then return 0; fi + + # If we couldn't figure out the FS type, then skip fsck. + if [ "$fsType" = auto ]; then + echo 'cannot check filesystem with type "auto"!' + return 0 + fi + + # Device might be already mounted manually + # e.g. NBD-device or the host filesystem of the file which contains encrypted root fs + if mount | grep -q "^$device on "; then + echo "skip checking already mounted $device" + return 0 + fi + + # Optionally, skip fsck on journaling filesystems. This option is + # a hack - it's mostly because e2fsck on ext3 takes much longer to + # recover the journal than the ext3 implementation in the kernel + # does (minutes versus seconds). + if test -z "@checkJournalingFS@" -a \ + \( "$fsType" = ext3 -o "$fsType" = ext4 -o "$fsType" = reiserfs \ + -o "$fsType" = xfs -o "$fsType" = jfs -o "$fsType" = f2fs \) + then + return 0 + fi + + echo "checking $device..." + + fsck -V -a "$device" + fsckResult=$? + + if test $(($fsckResult | 2)) = $fsckResult; then + echo "fsck finished, rebooting..." + sleep 3 + reboot -f + fi + + if test $(($fsckResult | 4)) = $fsckResult; then + echo "$device has unrepaired errors, please fix them manually." + fail + fi + + if test $fsckResult -ge 8; then + echo "fsck on $device failed." + fail + fi + + return 0 +} + + +# Function for mounting a file system. +mountFS() { + local device="$1" + local mountPoint="$2" + local options="$3" + local fsType="$4" + + if [ "$fsType" = auto ]; then + fsType=$(blkid -o value -s TYPE "$device") + if [ -z "$fsType" ]; then fsType=auto; fi + fi + + # Filter out x- options, which busybox doesn't do yet. + local optionsFiltered="$(IFS=,; for i in $options; do if [ "${i:0:2}" != "x-" ]; then echo -n $i,; fi; done)" + # Prefix (lower|upper|work)dir with /mnt-root (overlayfs) + local optionsPrefixed="$( echo "$optionsFiltered" | sed -E 's#\<(lowerdir|upperdir|workdir)=#\1=/mnt-root#g' )" + + echo "$device /mnt-root$mountPoint $fsType $optionsPrefixed" >> /etc/fstab + + checkFS "$device" "$fsType" + + # Optionally resize the filesystem. + case $options in + *x-nixos.autoresize*) + if [ "$fsType" = ext2 -o "$fsType" = ext3 -o "$fsType" = ext4 ]; then + modprobe "$fsType" + echo "resizing $device..." + e2fsck -fp "$device" + resize2fs "$device" + elif [ "$fsType" = f2fs ]; then + echo "resizing $device..." + fsck.f2fs -fp "$device" + resize.f2fs "$device" + fi + ;; + esac + + # Create backing directories for overlayfs + if [ "$fsType" = overlay ]; then + for i in upper work; do + dir="$( echo "$optionsPrefixed" | grep -o "${i}dir=[^,]*" )" + mkdir -m 0700 -p "${dir##*=}" + done + fi + + info "mounting $device on $mountPoint..." + + mkdir -p "/mnt-root$mountPoint" + + # For ZFS and CIFS mounts, retry a few times before giving up. + # We do this for ZFS as a workaround for issue NixOS/nixpkgs#25383. + local n=0 + while true; do + mount "/mnt-root$mountPoint" && break + if [ \( "$fsType" != cifs -a "$fsType" != zfs \) -o "$n" -ge 10 ]; then fail; break; fi + echo "retrying..." + sleep 1 + n=$((n + 1)) + done + + [ "$mountPoint" == "/" ] && + [ -f "/mnt-root/etc/NIXOS_LUSTRATE" ] && + lustrateRoot "/mnt-root" + + true +} + +lustrateRoot () { + local root="$1" + + echo + echo -e "\e[1;33m<<< NixOS is now lustrating the root filesystem (cruft goes to /old-root) >>>\e[0m" + echo + + mkdir -m 0755 -p "$root/old-root.tmp" + + echo + echo "Moving impurities out of the way:" + for d in "$root"/* + do + [ "$d" == "$root/nix" ] && continue + [ "$d" == "$root/boot" ] && continue # Don't render the system unbootable + [ "$d" == "$root/old-root.tmp" ] && continue + + mv -v "$d" "$root/old-root.tmp" + done + + # Use .tmp to make sure subsequent invokations don't clash + mv -v "$root/old-root.tmp" "$root/old-root" + + mkdir -m 0755 -p "$root/etc" + touch "$root/etc/NIXOS" + + exec 4< "$root/old-root/etc/NIXOS_LUSTRATE" + + echo + echo "Restoring selected impurities:" + while read -u 4 keeper; do + dirname="$(dirname "$keeper")" + mkdir -m 0755 -p "$root/$dirname" + cp -av "$root/old-root/$keeper" "$root/$keeper" + done + + exec 4>&- +} + + + +if test -e /sys/power/resume -a -e /sys/power/disk; then + if test -n "@resumeDevice@" && waitDevice "@resumeDevice@"; then + resumeDev="@resumeDevice@" + resumeInfo="$(udevadm info -q property "$resumeDev" )" + else + for sd in @resumeDevices@; do + # Try to detect resume device. According to Ubuntu bug: + # https://bugs.launchpad.net/ubuntu/+source/pm-utils/+bug/923326/comments/1 + # when there are multiple swap devices, we can't know where the hibernate + # image will reside. We can check all of them for swsuspend blkid. + if waitDevice "$sd"; then + resumeInfo="$(udevadm info -q property "$sd")" + if [ "$(echo "$resumeInfo" | sed -n 's/^ID_FS_TYPE=//p')" = "swsuspend" ]; then + resumeDev="$sd" + break + fi + fi + done + fi + if test -n "$resumeDev"; then + resumeMajor="$(echo "$resumeInfo" | sed -n 's/^MAJOR=//p')" + resumeMinor="$(echo "$resumeInfo" | sed -n 's/^MINOR=//p')" + echo "$resumeMajor:$resumeMinor" > /sys/power/resume 2> /dev/null || echo "failed to resume..." + fi +fi + +# If we have a path to an iso file, find the iso and link it to /dev/root +if [ -n "$isoPath" ]; then + mkdir -p /findiso + + for delay in 5 10; do + blkid | while read -r line; do + device=$(echo "$line" | sed 's/:.*//') + type=$(echo "$line" | sed 's/.*TYPE="\([^"]*\)".*/\1/') + + mount -t "$type" "$device" /findiso + if [ -e "/findiso$isoPath" ]; then + ln -sf "/findiso$isoPath" /dev/root + break 2 + else + umount /findiso + fi + done + + sleep "$delay" + done +fi + +# Try to find and mount the root device. +mkdir -p $targetRoot + +exec 3< @fsInfo@ + +while read -u 3 mountPoint; do + read -u 3 device + read -u 3 fsType + read -u 3 options + + # !!! Really quick hack to support bind mounts, i.e., where the + # "device" should be taken relative to /mnt-root, not /. Assume + # that every device that starts with / but doesn't start with /dev + # is a bind mount. + pseudoDevice= + case $device in + /dev/*) + ;; + //*) + # Don't touch SMB/CIFS paths. + pseudoDevice=1 + ;; + /*) + device=/mnt-root$device + ;; + *) + # Not an absolute path; assume that it's a pseudo-device + # like an NFS path (e.g. "server:/path"). + pseudoDevice=1 + ;; + esac + + if test -z "$pseudoDevice" && ! waitDevice "$device"; then + # If it doesn't appear, try to mount it anyway (and + # probably fail). This is a fallback for non-device "devices" + # that we don't properly recognise. + echo "Timed out waiting for device $device, trying to mount anyway." + fi + + # Wait once more for the udev queue to empty, just in case it's + # doing something with $device right now. + udevadm settle + + # If copytoram is enabled: skip mounting the ISO and copy its content to a tmpfs. + if [ -n "$copytoram" ] && [ "$device" = /dev/root ] && [ "$mountPoint" = /iso ]; then + fsType=$(blkid -o value -s TYPE "$device") + fsSize=$(blockdev --getsize64 "$device" || stat -Lc '%s' "$device") + + mkdir -p /tmp-iso + mount -t "$fsType" /dev/root /tmp-iso + mountFS tmpfs /iso size="$fsSize" tmpfs + + cp -r /tmp-iso/* /mnt-root/iso/ + + umount /tmp-iso + rmdir /tmp-iso + continue + fi + + if [ "$mountPoint" = / ] && [ "$device" = tmpfs ] && [ ! -z "$persistence" ]; then + echo persistence... + waitDevice "$persistence" + echo enabling persistence... + mountFS "$persistence" "$mountPoint" "$persistence_opt" "auto" + continue + fi + + mountFS "$device" "$mountPoint" "$options" "$fsType" +done + +exec 3>&- + + +@postMountCommands@ + + +# Emit a udev rule for /dev/root to prevent systemd from complaining. +if [ -e /mnt-root/iso ]; then + eval $(udevadm info --export --export-prefix=ROOT_ --device-id-of-file=/mnt-root/iso) +else + eval $(udevadm info --export --export-prefix=ROOT_ --device-id-of-file=$targetRoot) +fi +if [ "$ROOT_MAJOR" -a "$ROOT_MINOR" -a "$ROOT_MAJOR" != 0 ]; then + mkdir -p /run/udev/rules.d + echo 'ACTION=="add|change", SUBSYSTEM=="block", ENV{MAJOR}=="'$ROOT_MAJOR'", ENV{MINOR}=="'$ROOT_MINOR'", SYMLINK+="root"' > /run/udev/rules.d/61-dev-root-link.rules +fi + + +# Stop udevd. +udevadm control --exit + +# Reset the logging file descriptors. +# Do this just before pkill, which will kill the tee process. +exec 1>&$logOutFd 2>&$logErrFd +eval "exec $logOutFd>&- $logErrFd>&-" + +# Kill any remaining processes, just to be sure we're not taking any +# with us into stage 2. But keep storage daemons like unionfs-fuse. +# +# Storage daemons are distinguished by an @ in front of their command line: +# https://www.freedesktop.org/wiki/Software/systemd/RootStorageDaemons/ +for pid in $(pgrep -v -f '^@'); do + # Make sure we don't kill kernel processes, see #15226 and: + # http://stackoverflow.com/questions/12213445/identifying-kernel-threads + readlink "/proc/$pid/exe" &> /dev/null || continue + # Try to avoid killing ourselves. + [ $pid -eq $$ ] && continue + kill -9 "$pid" +done + +if test -n "$debug1mounts"; then fail; fi + + +# Restore /proc/sys/kernel/modprobe to its original value. +echo /sbin/modprobe > /proc/sys/kernel/modprobe + + +# Start stage 2. `switch_root' deletes all files in the ramfs on the +# current root. The path has to be valid in the chroot not outside. +if [ ! -e "$targetRoot/$stage2Init" ]; then + stage2Check=${stage2Init} + while [ "$stage2Check" != "${stage2Check%/*}" ] && [ ! -L "$targetRoot/$stage2Check" ]; do + stage2Check=${stage2Check%/*} + done + if [ ! -L "$targetRoot/$stage2Check" ]; then + echo "stage 2 init script ($targetRoot/$stage2Init) not found" + fail + fi +fi + +mkdir -m 0755 -p $targetRoot/proc $targetRoot/sys $targetRoot/dev $targetRoot/run + +mount --move /proc $targetRoot/proc +mount --move /sys $targetRoot/sys +mount --move /dev $targetRoot/dev +mount --move /run $targetRoot/run + +exec env -i $(type -P switch_root) "$targetRoot" "$stage2Init" + +fail # should never be reached diff --git a/nixos/modules/synit/boot/stage-1.nix b/nixos/modules/synit/boot/stage-1.nix new file mode 100644 index 0000000..e058b2c --- /dev/null +++ b/nixos/modules/synit/boot/stage-1.nix @@ -0,0 +1,427 @@ +# This module builds the initial ramdisk, which contains an init +# script that performs the first stage of booting the system: it loads +# the modules necessary to mount the root file system, then calls the +# init in the root file system to start the second boot stage. + +{ config, lib, utils, pkgs, ... }: + +with lib; + +let + + udev = pkgs.eudev; + + kernel-name = config.boot.kernelPackages.kernel.name or "kernel"; + + modulesTree = config.system.modulesTree.override { name = kernel-name + "-modules"; }; + firmware = config.hardware.firmware; + + + # Determine the set of modules that we need to mount the root FS. + modulesClosure = pkgs.makeModulesClosure { + rootModules = config.boot.initrd.availableKernelModules ++ config.boot.initrd.kernelModules; + kernel = modulesTree; + firmware = firmware; + allowMissing = false; + }; + + + # The initrd only has to mount `/` or any FS marked as necessary for + # booting (such as the FS containing `/nix/store`, or an FS needed for + # mounting `/`, like `/` on a loopback). + fileSystems = filter utils.fsNeededForBoot config.system.build.fileSystems; + + # A utility for enumerating the shared-library dependencies of a program + findLibs = pkgs.buildPackages.writeShellScriptBin "find-libs" '' + set -euo pipefail + + declare -A seen + left=() + + patchelf="${pkgs.buildPackages.patchelf}/bin/patchelf" + + function add_needed { + rpath="$($patchelf --print-rpath $1)" + dir="$(dirname $1)" + for lib in $($patchelf --print-needed $1); do + left+=("$lib" "$rpath" "$dir") + done + } + + add_needed "$1" + + while [ ''${#left[@]} -ne 0 ]; do + next=''${left[0]} + rpath=''${left[1]} + ORIGIN=''${left[2]} + left=("''${left[@]:3}") + if [ -z ''${seen[$next]+x} ]; then + seen[$next]=1 + + # Ignore the dynamic linker which for some reason appears as a DT_NEEDED of glibc but isn't in glibc's RPATH. + case "$next" in + ld*.so.?) continue;; + esac + + IFS=: read -ra paths <<< $rpath + res= + for path in "''${paths[@]}"; do + path=$(eval "echo $path") + if [ -f "$path/$next" ]; then + res="$path/$next" + echo "$res" + add_needed "$res" + break + fi + done + if [ -z "$res" ]; then + echo "Couldn't satisfy dependency $next" >&2 + exit 1 + fi + fi + done + ''; + + # Some additional utilities needed in stage 1, like mount, fsck + # etc. We don't want to bring in all of those packages, so we just + # copy what we need. Instead of using statically linked binaries, + # we just copy what we need from Glibc and use patchelf to make it + # work. + extraUtils = pkgs.runCommandCC "extra-utils" + { nativeBuildInputs = [pkgs.buildPackages.nukeReferences]; + allowedReferences = [ "out" ]; # prevent accidents like glibc being included in the initrd + } + '' + set +o pipefail + + mkdir -p $out/bin $out/lib + ln -s $out/bin $out/sbin + + copy_bin_and_libs () { + [ -f "$out/bin/$(basename $1)" ] && rm "$out/bin/$(basename $1)" + cp -pdv $1 $out/bin + } + + # Copy BusyBox. + for BIN in ${pkgs.busybox}/{s,}bin/*; do + copy_bin_and_libs $BIN + done + + # Copy some util-linux stuff. + copy_bin_and_libs ${pkgs.util-linux}/sbin/blkid + + # Add RAID mdadm tool. + copy_bin_and_libs ${pkgs.mdadm}/sbin/mdadm + copy_bin_and_libs ${pkgs.mdadm}/sbin/mdmon + + # Copy udev. + copy_bin_and_libs ${udev}/bin/udevd + copy_bin_and_libs ${udev}/bin/udevadm + for BIN in ${udev}/lib/udev/*_id; do + copy_bin_and_libs $BIN + done + + # Copy modprobe. + copy_bin_and_libs ${pkgs.kmod}/bin/kmod + ln -sf kmod $out/bin/modprobe + + # Dirty hack to make sure the kernel properly loads modules + # such as ext4 on demand (e.g. on a `mount(2)` syscall). This is necessary + # because `kmod` isn't linked against `libpthread.so.0` anymore (since + # it was merged into `libc.so.6` since version `2.34`), but still needs + # to access it for some reason. This is not an issue in stage-1 itself + # because of the `LD_LIBRARY_PATH`-variable and anytime later because the rpath of + # kmod/modprobe points to glibc's `$out/lib` where `libpthread.so.6` exists. + # However, this is a problem when the kernel calls `modprobe` inside + # the initial ramdisk because it doesn't know about the + # `LD_LIBRARY_PATH` and the rpath was nuked. + # + # Also, we can't use `makeWrapper` here because `kmod` only does + # `modprobe` functionality if `argv[0] == "modprobe"`. + cat >$out/bin/modprobe-kernel <&1 | grep -q "BusyBox" + $out/bin/blkid -V 2>&1 | grep -q 'libblkid' + $out/bin/udevadm --version + $out/bin/mdadm --version + ${optionalString config.services.multipath.enable '' + ($out/bin/multipath || true) 2>&1 | grep -q 'need to be root' + ($out/bin/multipathd || true) 2>&1 | grep -q 'need to be root' + ''} + + ${config.boot.initrd.extraUtilsCommandsTest} + fi + ''; # */ + + udevRules = pkgs.runCommand "udev-rules" { + allowedReferences = [ extraUtils ]; + preferLocalBuild = true; + } '' + mkdir -p $out + + echo 'ENV{LD_LIBRARY_PATH}="${extraUtils}/lib"' > $out/00-env.rules + + cp -v ${udev}/var/lib/udev/rules.d/60-cdrom_id.rules $out/ + cp -v ${udev}/var/lib/udev/rules.d/60-persistent-storage.rules $out/ + cp -v ${udev}/var/lib/udev/rules.d/75-net-description.rules $out/ + cp -v ${udev}/var/lib/udev/rules.d/80-drivers.rules $out/ + cp -v ${udev}/var/lib/udev/rules.d/80-net-name-slot.rules $out/ + ${config.boot.initrd.extraUdevRulesCommands} + + for i in $out/*.rules; do + substituteInPlace $i \ + --replace ata_id ${extraUtils}/bin/ata_id \ + --replace scsi_id ${extraUtils}/bin/scsi_id \ + --replace cdrom_id ${extraUtils}/bin/cdrom_id \ + --replace ${pkgs.coreutils}/bin/basename ${extraUtils}/bin/basename \ + --replace ${pkgs.util-linux}/bin/blkid ${extraUtils}/bin/blkid \ + --replace ${pkgs.mdadm}/sbin ${extraUtils}/sbin \ + --replace ${pkgs.bash}/bin/sh ${extraUtils}/bin/sh \ + --replace ${udev} ${extraUtils} + done + + # Work around a bug in QEMU, which doesn't implement the "READ + # DISC INFORMATION" SCSI command: + # https://bugzilla.redhat.com/show_bug.cgi?id=609049 + # As a result, `cdrom_id' doesn't print + # ID_CDROM_MEDIA_TRACK_COUNT_DATA, which in turn prevents the + # /dev/disk/by-label symlinks from being created. We need these + # in the NixOS installation CD, so use ID_CDROM_MEDIA in the + # corresponding udev rules for now. This was the behaviour in + # udev <= 154. See also + # http://www.spinics.net/lists/hotplug/msg03935.html + substituteInPlace $out/60-persistent-storage.rules \ + --replace ID_CDROM_MEDIA_TRACK_COUNT_DATA ID_CDROM_MEDIA + ''; # */ + + + # The init script of boot stage 1 (loading kernel modules for + # mounting the root FS). + bootStage1 = pkgs.substituteAll { + src = ./stage-1-init.sh; + + shell = "${extraUtils}/bin/ash"; + + isExecutable = true; + + postInstall = '' + echo checking syntax + # check both with bash + ${pkgs.buildPackages.bash}/bin/sh -n $target + # and with ash shell, just in case + ${pkgs.buildPackages.busybox}/bin/ash -n $target + ''; + + inherit udevRules extraUtils modulesClosure; + + inherit (config.boot) resumeDevice; + + inherit (config.system.build) earlyMountScript; + + inherit (config.boot.initrd) checkJournalingFS verbose + preDeviceCommands postDeviceCommands postMountCommands preFailCommands kernelModules; + + resumeDevices = map (sd: if sd ? device then sd.device else "/dev/disk/by-label/${sd.label}") + (filter (sd: hasPrefix "/dev/" sd.device && !sd.randomEncryption.enable + # Don't include zram devices + && !(hasPrefix "/dev/zram" sd.device) + ) config.swapDevices); + + fsInfo = + let f = fs: [ fs.mountPoint (if fs.device != null then fs.device else "/dev/disk/by-label/${fs.label}") fs.fsType (builtins.concatStringsSep "," fs.options) ]; + in pkgs.writeText "initrd-fsinfo" (concatStringsSep "\n" (concatMap f fileSystems)); + + setHostId = optionalString (config.networking.hostId != null) '' + hi="${config.networking.hostId}" + ${if pkgs.stdenv.isBigEndian then '' + echo -ne "\x''${hi:0:2}\x''${hi:2:2}\x''${hi:4:2}\x''${hi:6:2}" > /etc/hostid + '' else '' + echo -ne "\x''${hi:6:2}\x''${hi:4:2}\x''${hi:2:2}\x''${hi:0:2}" > /etc/hostid + ''} + ''; + }; + + + # The closure of the init script of boot stage 1 is what we put in + # the initial RAM disk. + initialRamdisk = pkgs.makeInitrd { + name = "initrd-${kernel-name}"; + inherit (config.boot.initrd) compressor compressorArgs prepend; + + contents = + [ { object = bootStage1; + symlink = "/init"; + } + { object = pkgs.writeText "mdadm.conf" config.boot.initrd.services.swraid.mdadmConf; + symlink = "/etc/mdadm.conf"; + } + { object = pkgs.runCommand "initrd-kmod-blacklist-ubuntu" { + src = "${pkgs.kmod-blacklist-ubuntu}/modprobe.conf"; + preferLocalBuild = true; + } '' + target=$out + ${pkgs.buildPackages.perl}/bin/perl -0pe 's/## file: iwlwifi.conf(.+?)##/##/s;' $src > $out + ''; + symlink = "/etc/modprobe.d/ubuntu.conf"; + } + { object = config.environment.etc."modprobe.d/nixos.conf".source; + symlink = "/etc/modprobe.d/nixos.conf"; + } + { object = pkgs.kmod-debian-aliases; + symlink = "/etc/modprobe.d/debian.conf"; + } + ] ++ lib.optionals config.services.multipath.enable [ + { object = pkgs.runCommand "multipath.conf" { + src = config.environment.etc."multipath.conf".text; + preferLocalBuild = true; + } '' + target=$out + printf "$src" > $out + substituteInPlace $out \ + --replace ${config.services.multipath.package}/lib ${extraUtils}/lib + ''; + symlink = "/etc/multipath.conf"; + } + ] ++ (lib.mapAttrsToList + (symlink: options: + { + inherit symlink; + object = options.source; + } + ) + config.boot.initrd.extraFiles); + }; + + # Script to add secret files to the initrd at bootloader update time + initialRamdiskSecretAppender = + let + compressorExe = initialRamdisk.compressorExecutableFunction pkgs; + in pkgs.writeScriptBin "append-initrd-secrets" + '' + #!${pkgs.bash}/bin/bash -e + function usage { + echo "USAGE: $0 INITRD_FILE" >&2 + echo "Appends this configuration's secrets to INITRD_FILE" >&2 + } + + if [ $# -ne 1 ]; then + usage + exit 1 + fi + + if [ "$1"x = "--helpx" ]; then + usage + exit 0 + fi + + ${lib.optionalString (config.boot.initrd.secrets == {}) + "exit 0"} + + export PATH=${pkgs.coreutils}/bin:${pkgs.libarchive}/bin:${pkgs.gzip}/bin:${pkgs.findutils}/bin + + function cleanup { + if [ -n "$tmp" -a -d "$tmp" ]; then + rm -fR "$tmp" + fi + } + trap cleanup EXIT + + tmp=$(mktemp -d ''${TMPDIR:-/tmp}/initrd-secrets.XXXXXXXXXX) + + ${lib.concatStringsSep "\n" (mapAttrsToList (dest: source: + let source' = if source == null then dest else toString source; in + '' + mkdir -p $(dirname "$tmp/.initrd-secrets/${dest}") + cp -a ${source'} "$tmp/.initrd-secrets/${dest}" + '' + ) config.boot.initrd.secrets) + } + + (cd "$tmp" && find . -print0 | sort -z | bsdtar --uid 0 --gid 0 -cnf - -T - | bsdtar --null -cf - --format=newc @-) | \ + ${compressorExe} ${lib.escapeShellArgs initialRamdisk.compressorArgs} >> "$1" + ''; + +in + +{ + config = mkIf config.boot.initrd.enable { + + system.build = lib.mapAttrs (_: lib.mkForce) { inherit bootStage1 initialRamdisk initialRamdiskSecretAppender extraUtils; }; + + }; +} diff --git a/nixos/modules/synit/boot/stage-2-init.sh b/nixos/modules/synit/boot/stage-2-init.sh new file mode 100755 index 0000000..f0b9216 --- /dev/null +++ b/nixos/modules/synit/boot/stage-2-init.sh @@ -0,0 +1,134 @@ +#! @shell@ + +systemConfig=@systemConfig@ + +export HOME=/root PATH="@path@" + + +if [ "${IN_NIXOS_SYSTEMD_STAGE1:-}" != true ]; then + # Process the kernel command line. + for o in $(>>\e[0m" + echo + + + # Normally, stage 1 mounts the root filesystem read/writable. + # However, in some environments, stage 2 is executed directly, and the + # root is read-only. So make it writable here. + if [ -z "$container" ]; then + mount -n -o remount,rw none / + fi +fi + + +# Likewise, stage 1 mounts /proc, /dev and /sys, so if we don't have a +# stage 1, we need to do that here. +if [ ! -e /proc/1 ]; then + specialMount() { + local device="$1" + local mountPoint="$2" + local options="$3" + local fsType="$4" + + # We must not overwrite this mount because it's bind-mounted + # from stage 1's /run + if [ "${IN_NIXOS_SYSTEMD_STAGE1:-}" = true ] && [ "${mountPoint}" = /run ]; then + return + fi + + install -m 0755 -d "$mountPoint" + mount -n -t "$fsType" -o "$options" "$device" "$mountPoint" + } + source @earlyMountScript@ +fi + + +if [ "${IN_NIXOS_SYSTEMD_STAGE1:-}" = true ]; then + echo "booting system configuration ${systemConfig}" +else + echo "booting system configuration $systemConfig" > /dev/kmsg +fi + + +# Make /nix/store a read-only bind mount to enforce immutability of +# the Nix store. Note that we can't use "chown root:nixbld" here +# because users/groups might not exist yet. +# Silence chown/chmod to fail gracefully on a readonly filesystem +# like squashfs. +chown -f 0:30000 /nix/store +chmod -f 1775 /nix/store +if [ -n "@readOnlyNixStore@" ]; then + if ! [[ "$(findmnt --noheadings --output OPTIONS /nix/store)" =~ ro(,|$) ]]; then + if [ -z "$container" ]; then + mount --bind /nix/store /nix/store + else + mount --rbind /nix/store /nix/store + fi + mount -o remount,ro,bind /nix/store + fi +fi + + + +# Log the script output to /dev/kmsg or /run/log/stage-2-init.log. +# Only at this point are all the necessary prerequisites ready for these commands. +exec {logOutFd}>&1 {logErrFd}>&2 +if test -w /dev/kmsg; then +exec > >(tee -i /proc/self/fd/"$logOutFd" | while read -r line; do +if test -n "$line"; then +echo "<7>stage-2-init: $line" > /dev/kmsg +fi +done) 2>&1 +else +mkdir -p /run/log +exec > >(tee -i /run/log/stage-2-init.log) 2>&1 +fi + + +# Required by the activation script +install -m 0755 -d /etc /etc/nixos +install -m 01777 -d /tmp + + +# Run the script that performs all configuration activation that does +# not have to be done at boot time. +echo "running activation script..." +$systemConfig/activate + + +# Record the boot configuration. +ln -sfn "$systemConfig" /run/booted-system + +# copy eudev rules over to /var +cp -ru @eudev@/var/lib/udev /var/lib/ + +# Run any user-specified commands. +@shell@ @postBootCommands@ + + +# Reset the logging file descriptors. +exec 1>&$logOutFd 2>&$logErrFd +exec {logOutFd}>&- {logErrFd}>&- + + +mkdir -p \ + /etc/syndicate/user-settings \ + /run/etc/syndicate/core \ + /run/etc/syndicate/services \ + +# Start Synit. +echo "starting Synit..." +exec @synit_pid1@ \ + --log @synit_log@ \ + --server-path @syndicate_server@ diff --git a/nixos/modules/synit/boot/stage-2.nix b/nixos/modules/synit/boot/stage-2.nix new file mode 100644 index 0000000..a267100 --- /dev/null +++ b/nixos/modules/synit/boot/stage-2.nix @@ -0,0 +1,31 @@ +{ config, lib, pkgs, ... }: + +{ + config = { + + system.build.bootStage2 = lib.mkForce (pkgs.substituteAll { + src = ./stage-2-init.sh; + shellDebug = "${pkgs.bashInteractive}/bin/bash"; + shell = "${pkgs.bash}/bin/bash"; + isExecutable = true; + inherit (config.boot) readOnlyNixStore; + inherit (config.system.build) earlyMountScript; + path = lib.makeBinPath [ pkgs.busybox pkgs.eudev pkgs.utillinux ]; + inherit (pkgs) eudev; + synit_pid1 = "${pkgs.synit-pid1}/bin/synit-pid1"; + synit_log = pkgs.writeScript "synit-log" '' + #!${pkgs.execline}/bin/execlineb -P + foreground { ${pkgs.coreutils}/bin/mkdir -p /var/log/synit } + ${pkgs.s6}/bin/s6-log t /var/log/synit + ''; + syndicate_server = "${pkgs.syndicate-server}/bin/syndicate-server"; + + postBootCommands = pkgs.writeText "local-cmds" '' + ${config.boot.postBootCommands} + ${config.powerManagement.powerUpCommands} + ''; + + }); + + }; +} diff --git a/nixos/modules/synit/etc/boot/001-console-getty.pr b/nixos/modules/synit/etc/boot/001-console-getty.pr new file mode 100644 index 0000000..e947669 --- /dev/null +++ b/nixos/modules/synit/etc/boot/001-console-getty.pr @@ -0,0 +1,2 @@ +> + diff --git a/nixos/modules/synit/etc/boot/010-exec.pr b/nixos/modules/synit/etc/boot/010-exec.pr new file mode 100644 index 0000000..589d16c --- /dev/null +++ b/nixos/modules/synit/etc/boot/010-exec.pr @@ -0,0 +1,19 @@ +; Sending causes the command to be run. +; +?? [ + let ?id = timestamp + let ?facet = facet + let ?d = + > + + ? complete> [$facet ! stop] + ? failed> [$facet ! stop] +] + +; If the restart policy is not specified, it is defaulted to `on-error`. +; +?? ! diff --git a/nixos/modules/synit/etc/boot/010-milestone.pr b/nixos/modules/synit/etc/boot/010-milestone.pr new file mode 100644 index 0000000..9e512c3 --- /dev/null +++ b/nixos/modules/synit/etc/boot/010-milestone.pr @@ -0,0 +1,10 @@ +; To "run" a milestone service, +; - assert that it is both started and ready +; - that's it! +; +? > [ + started> + ready> + $log ! state: up }> + ?- state: down }> +] diff --git a/nixos/modules/synit/etc/boot/010-service-state-up.pr b/nixos/modules/synit/etc/boot/010-service-state-up.pr new file mode 100644 index 0000000..e9fd2b2 --- /dev/null +++ b/nixos/modules/synit/etc/boot/010-service-state-up.pr @@ -0,0 +1,4 @@ +; To the usual suite of service states we add `up`, meaning "either `ready` or `complete`". +; +? +? diff --git a/nixos/modules/synit/etc/boot/020-load-core-layer.pr b/nixos/modules/synit/etc/boot/020-load-core-layer.pr new file mode 100644 index 0000000..4f0c4df --- /dev/null +++ b/nixos/modules/synit/etc/boot/020-load-core-layer.pr @@ -0,0 +1,28 @@ +; Attenuate `$config` by rewriting plain `require-service` assertions to `require-core-service` +; assertions. Allow all other assertions through. +; +let ?sys = <* $config [ > + +]>]> + +; Give meaning to `require-core-service`: it is an ordinary `require-service`, plus a +; declaration that the `core` milestone depends on the service. +; +? [ + > + +] + +; Load config in the `core` directory, using the wrapped `config` so that all plain services +; required are changed to be *core* services. +; +> + +; In addition, require the `core` milestone explicitly. +; +> diff --git a/nixos/modules/synit/etc/boot/030-load-services.pr b/nixos/modules/synit/etc/boot/030-load-services.pr new file mode 100644 index 0000000..d46be75 --- /dev/null +++ b/nixos/modules/synit/etc/boot/030-load-services.pr @@ -0,0 +1,26 @@ +; Attenuate `$config` by rewriting plain `require-service` assertions to +; `require-basic-service` assertions. Allow all other assertions through. +; +let ?basic = <* $config [ > + +]>]> + +; Give meaning to `require-basic-service`: it is an ordinary `require-service`, plus a +; declaration that the service depends on the `core` milestone. +; +? [ + up>> + +] + +; Once we see that the `core` milestone is ready, start processing the `services` +; directory. +; +? up> [ + > +] diff --git a/nixos/modules/synit/etc/core/configdirs.pr b/nixos/modules/synit/etc/core/configdirs.pr new file mode 100644 index 0000000..3849a76 --- /dev/null +++ b/nixos/modules/synit/etc/core/configdirs.pr @@ -0,0 +1 @@ +> diff --git a/nixos/modules/synit/etc/core/eudev.pr b/nixos/modules/synit/etc/core/eudev.pr new file mode 100644 index 0000000..97ed7c9 --- /dev/null +++ b/nixos/modules/synit/etc/core/eudev.pr @@ -0,0 +1,6 @@ +> + + +> + up>> +> diff --git a/nixos/modules/synit/etc/core/hostname.pr b/nixos/modules/synit/etc/core/hostname.pr new file mode 100644 index 0000000..5776a7f --- /dev/null +++ b/nixos/modules/synit/etc/core/hostname.pr @@ -0,0 +1,2 @@ +> +> diff --git a/nixos/modules/synit/etc/core/machine-dataspace.pr b/nixos/modules/synit/etc/core/machine-dataspace.pr new file mode 100644 index 0000000..83ac803 --- /dev/null +++ b/nixos/modules/synit/etc/core/machine-dataspace.pr @@ -0,0 +1,7 @@ +let ?ds = dataspace + + +$ds ? ?r [ + $log ! + ?- $log ! +] diff --git a/nixos/modules/synit/etc/default.nix b/nixos/modules/synit/etc/default.nix new file mode 100644 index 0000000..bc169c8 --- /dev/null +++ b/nixos/modules/synit/etc/default.nix @@ -0,0 +1,29 @@ +{ config, pkgs, ... }: + +let + substituteDirectory = args: + pkgs.stdenvNoCC.mkDerivation ({ + name = if args ? name then args.name else baseNameOf (toString args.src); + builder = ./substitute-dir.sh; + preferLocalBuild = true; + allowSubstitutes = false; + } // args); +in { + environment.etc = { + + "syndicate/boot".source = substituteDirectory { + src = ./boot; + getty = "${pkgs.busybox}/bin/busybox getty"; + inherit (config.services.getty) loginProgram; + }; + + "syndicate/core".source = substituteDirectory { + src = ./core; + udevd = "${pkgs.eudev}/bin/udevd"; + udevadm = "${pkgs.eudev}/bin/udevadm"; + hostnameOneShot = + "${pkgs.busybox}/bin/busybox hostname ${config.networking.hostName}"; + }; + + }; +} diff --git a/nixos/modules/synit/etc/substitute-dir.sh b/nixos/modules/synit/etc/substitute-dir.sh new file mode 100644 index 0000000..f2053d9 --- /dev/null +++ b/nixos/modules/synit/etc/substitute-dir.sh @@ -0,0 +1,16 @@ +source $stdenv/setup + +eval "$preInstall" + +pushd $src || exit +find ./* -type f | while read filepath +do + echo "find file $filepath" + fileout=$out/$filepath + dirout=$(dirname "$fileout") + mkdir -p "$dirout" + substituteAll "$filepath" "$fileout" +done +popd || exit + +eval "$postInstall" diff --git a/nixos/modules/synit/networking.nix b/nixos/modules/synit/networking.nix new file mode 100644 index 0000000..cd9214e --- /dev/null +++ b/nixos/modules/synit/networking.nix @@ -0,0 +1,47 @@ +{ config, lib, pkgs, ... }: +let + lib' = lib.extend (import ../../../lib.nix); + toPreserves = lib'.generators.toPreserves { }; +in { + environment.etc = lib.pipe config.networking.interfaces [ + (lib.attrsets.mapAttrs' (ifname: + { ipv4, useDHCP, ... }: { + name = "syndicate/services/network-interface-${ifname}.pr"; + value.text = let + + addresses = map ({ address, prefixLength }: [ + ifname + [ "${address}/${toString prefixLength}" { record = "static"; } ] + { record = "configure-interface"; } + ]) ipv4.addresses; + + routes = map ({ address, options, prefixLength, type, via }: [ + "ipv4" + "${address}/${toString prefixLength}" + ifname + via + { record = "configure-route"; } + ]) ipv4.routes; + + dhcp = lib.optional + (useDHCP == true || (useDHCP == null && ipv4.addresses == [ ])) [ + ifname + [{ record = "dhcp"; }] + { record = "configure-interface"; } + ]; + + in lib.pipe (addresses ++ routes ++ dhcp) [ + (map toPreserves) + lib.strings.concatLines + ]; + })) + ] // { + "syndicate/services/networking.pr".source = pkgs.substituteAll { + src = ./networking.pr; + ip = "${pkgs.busybox}/bin/ip"; + interface_monitor = + "${pkgs.python3Packages.synit-daemons}/bin/interface-monitor"; + udhcpc = "${pkgs.busybox}/bin/udhcpc"; + }; + }; +} diff --git a/nixos/modules/synit/networking.pr b/nixos/modules/synit/networking.pr new file mode 100644 index 0000000..6818777 --- /dev/null +++ b/nixos/modules/synit/networking.pr @@ -0,0 +1,56 @@ +> + +> + ready>> + +> + +? > [ + ! + ?- ! +] + +? > [ + >> +] + +? [ + ! + ?- ! +] + +? >> [ + ["@udhcpc@" "-i" $ifname "-fR"]> +] + + + +? [ + ? ?cap> [ + $cap { + machine: $machine + } + ] + + $machine ? [ + $log ! + $config [ + ! + ?- ! + ] + ] + + $machine ? [ + $config > + ] + $machine ? [ + $config > + ] + + $machine ? [ + $config + ] +] diff --git a/nixos/modules/synit/services/default.nix b/nixos/modules/synit/services/default.nix new file mode 100644 index 0000000..5b6e27c --- /dev/null +++ b/nixos/modules/synit/services/default.nix @@ -0,0 +1,6 @@ +{ + imports = [ ./ntpd.nix ./sshd.nix ./userSettings.nix ./wifi.nix ]; + environment.etc."syndicate/services/configdirs.pr".text = '' + > + ''; +} diff --git a/nixos/modules/synit/services/ntpd.nix b/nixos/modules/synit/services/ntpd.nix new file mode 100644 index 0000000..6cd4b26 --- /dev/null +++ b/nixos/modules/synit/services/ntpd.nix @@ -0,0 +1,14 @@ +{ config, lib, pkgs, ... }: + +{ + config = lib.mkIf config.services.ntp.enable { + environment.etc."syndicate/services/ssh-keygen.pr".text = let + serverList = + lib.lists.concatMap (s: " -p ${s}") config.networking.timeServers; + in '' + > + > + + ''; + }; +} diff --git a/nixos/modules/synit/services/sshd.nix b/nixos/modules/synit/services/sshd.nix new file mode 100644 index 0000000..fe52653 --- /dev/null +++ b/nixos/modules/synit/services/sshd.nix @@ -0,0 +1,9 @@ +{ config, lib, pkgs, ... }: + +{ + config = lib.mkIf config.services.openssh.enable { + environment.etc."syndicate/services/ssh-keygen.pr".text = '' + > + ''; + }; +} diff --git a/nixos/modules/synit/services/userSettings.nix b/nixos/modules/synit/services/userSettings.nix new file mode 100644 index 0000000..e8a718b --- /dev/null +++ b/nixos/modules/synit/services/userSettings.nix @@ -0,0 +1,26 @@ +{ config, lib, pkgs, ... }: + +{ + environment.etc."syndicate/services/userSettings.pr".text = '' + let ?settingsDir = "/etc/syndicate/user-settings" + + let ?settings = <* $config [ + > + ]> + + > + + > + + + ? ?cap> [ + $cap { + config: $config + settingsDir: $settingsDir + } + ] + ''; +} diff --git a/nixos/modules/synit/services/wifi.nix b/nixos/modules/synit/services/wifi.nix new file mode 100644 index 0000000..196e6ef --- /dev/null +++ b/nixos/modules/synit/services/wifi.nix @@ -0,0 +1,45 @@ +{ config, lib, pkgs, ... }: + +{ + config = lib.mkIf config.networking.wireless.enable { + environment.etc."syndicate/services/userSettings.pr".text = '' + ? [ + $machine ? [ + $config [ + >> + > > up>> + >> + ] + ] + + $machine ? [ + $config > + ] + + $config ? >> [ $machine += $s ] + ] + + ? >> [ + { + argv: "/usr/lib/synit/wifi-daemon" + protocol: application/syndicate + }> + + ? [ + ? > ?cap> [ + $cap { + machine: $machine + ifname: $ifname + } + ] + ] + ] + + ? >> [ + [ + "${pkgs.wpa_supplicant}/bin/wpa_supplicant" "-Dnl80211,wext" "-C/run/wpa_supplicant" "-i" $ifname + ]> + ] + ''; + }; +} diff --git a/packages/synit-daemons/default.nix b/packages/synit-daemons/default.nix new file mode 100644 index 0000000..7c2c769 --- /dev/null +++ b/packages/synit-daemons/default.nix @@ -0,0 +1,33 @@ +{ lib, buildPythonApplication, python, pyroute2, syndicate, synit-pid1 }: + +buildPythonApplication rec { + pname = "interface-monitor"; + inherit (synit-pid1) version src; + + propagatedBuildInputs = [ pyroute2 syndicate ]; + + format = "other"; + + installPhase = '' + runHook preInstall + mkdir -p \ + $out/bin \ + $out/lib/${python.libPrefix}/site-packages/synit/daemon \ + $out/protocols \ + + cp -r protocols/schema-bundle.bin $out/protocols + pushd packaging/packages/synit-config/files/usr/lib/synit + for f in *; do + [ -f $f ] && cp $f $out/bin + done + pushd python/synit/daemon + for f in *; do + substitute $f $out/lib/${python.libPrefix}/site-packages/synit/daemon/$f \ + --replace "/home/tonyg/src/synit/protocols" "$out/protocols" + done + popd + popd + runHook postInstall + ''; + +} diff --git a/packages/synit-pid1/default.nix b/packages/synit-pid1/default.nix index 7a8976e..14e4253 100644 --- a/packages/synit-pid1/default.nix +++ b/packages/synit-pid1/default.nix @@ -13,24 +13,12 @@ rustPlatform.buildRustPackage rec { hash = "sha256-VVMttlRIiH/aGytC9CutoaT0YDGkCGhO3GbipYXk3dg="; }; - patchPhase = - # Patch to take children and configuration from /run/booted-system. - '' - runHook prePatch - substituteInPlace src/main.rs \ - --replace '"/usr/bin/syndicate-server"' '"/run/booted-system/sw/bin/syndicate-server"' \ - --replace '"/sbin/synit-log"' '"/run/booted-system/sw/bin/synit-log"' \ - --replace '"/etc/syndicate/boot"' '"/run/booted-system/etc/syndicate/boot"' \ - - runHook postPatch - ''; - sourceRoot = "source/${pname}"; cargoHash = "sha256-57ZUlR+lIwQf3/4pDeeEMUc1juNTddcHpzNQZTUWbtU="; nativeBuildInputs = [ rust ]; meta = src.meta // { - description = "Synit pid 1 program (patched for NixOS)"; + description = "Synit pid 1 program"; homepage = "https://synit.org/"; maintainers = with lib.maintainers; [ ehmry ]; };