diff --git a/README.md b/README.md index cc5f3c1..349cb08 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,6 @@ # Liminix -Līminis + Nix - -* Līminis : Latin, genitive declension of limen. "Of the threshold" -* Nix : a tool for reproducible and declarative configuration management -* Liminix : a Nix-based system for configuring consumer wifi routers +A Nix-based system for configuring consumer wifi routers. ## What is this? @@ -16,6 +12,11 @@ This is not NixOS-on-your-router: it's aimed at devices that are underpowered for the full NixOS experience. It uses busybox tools, musl instead of GNU libc, and s6-rc instead of systemd. +The Liminix name comes from Liminis, in Latin the genitive declension +of "limen", or "of the threshold". Your router stands at the threshold +of your (online) home and everything you send to/receive from the +outside word goes across it. + ## Building @@ -52,3 +53,16 @@ took full advantage of the basic application armoring features provided by the operating system. Indeed, only one or two models even came close, and no brand did well consistently across all models tested" + + + + +----- + +s6/ directory has the result of running s6-linux-init-maker, plus edits +to the scripts for s6-rc + +- need to fix the bin/ directory (maybe they were copied from + s6-linux-init?) + +- maybe the run-image directory can be added as a squashfs source directory? diff --git a/make-image.nix b/make-image.nix index 1d51d9f..afd1571 100644 --- a/make-image.nix +++ b/make-image.nix @@ -3,6 +3,7 @@ let inherit (pkgs) callPackage closureInfo + lib runCommand s6-rc stdenv @@ -46,36 +47,42 @@ let mount -t proc none /proc mkdir -p /run/services ''; - storefs = callPackage { - storeContents = [ rcS pkgs.pkgsStatic.busybox s6db pkgs.s6-linux-init ] ++ config.packages ; - # comp = "xz -Xdict-size 100%" - }; + s6-pseudofiles = pkgs.s6-init-files; pseudofiles = writeText "pseudofiles" '' / d 0755 0 0 /bin d 0755 0 0 /etc d 0755 0 0 /run d 0755 0 0 /dev d 0755 0 0 - /dev/console c 0600 root root 5 1 /dev/null c 0666 root root 1 3 - /dev/tty c 0777 root root 5 0 + /dev/zero c 0666 root root 1 5 /dev/tty1 c 0777 root root 4 1 /dev/tty2 c 0777 root root 4 2 /dev/tty3 c 0777 root root 4 3 /dev/tty4 c 0777 root root 4 4 - /dev/zero c 0666 root root 1 5 + /dev/tty c 0777 root root 5 0 + /dev/console c 0600 root root 5 1 /proc d 0555 root root /dev/pts d 0755 0 0 /etc/init.d d 0755 0 0 - /bin/init s 0755 0 0 ${pkgs.pkgsStatic.busybox}/bin/init + /bin/init s 0755 0 0 /etc/s6-linux-init/current/bin/init /bin/sh s 0755 0 0 ${pkgs.pkgsStatic.busybox}/bin/sh - /bin/busybox s 0755 0 0 ${pkgs.pkgsStatic.busybox}/bin/busybox + /bin/busybox s 0755 0 0 ${pkgs.busybox}/bin/busybox /etc/init.d/rcS s 0755 0 0 ${rcS} + /etc/s6-rc d 0755 0 0 + /etc/s6-rc/compiled s 0755 0 0 ${s6db}/compiled + /etc/passwd f 0644 0 0 echo "root::0:0:root:/:/bin/sh" ''; -in runCommand "frob-squashfs" {} '' - cp ${storefs} ./store.img - chmod +w store.img - ${pkgs.buildPackages.squashfsTools}/bin/mksquashfs - store.img -no-recovery -quiet -no-progress -root-becomes store -p "/ d 0755 0 0" - ${pkgs.buildPackages.squashfsTools}/bin/mksquashfs - store.img -no-recovery -quiet -no-progress -root-becomes nix -pf ${pseudofiles} - cp store.img $out -'' + storefs = callPackage { + storeContents = [ pseudofiles pkgs.strace s6-pseudofiles rcS pkgs.pkgsStatic.busybox s6db pkgs.s6-linux-init ] ++ config.packages ; + # comp = "xz -Xdict-size 100%" + }; +in runCommand "frob-squashfs" { + nativeBuildInputs = with pkgs.buildPackages; [ squashfsTools qprint ]; + } '' + cp ${storefs} ./store.img + chmod +w store.img + mksquashfs - store.img -no-recovery -quiet -no-progress -root-becomes store -p "/ d 0755 0 0" + mksquashfs - store.img -no-recovery -quiet -no-progress -root-becomes nix -pf ${pseudofiles} -pf ${s6-pseudofiles} + cp store.img $out + '' diff --git a/overlay.nix b/overlay.nix index d2368b7..f252960 100644 --- a/overlay.nix +++ b/overlay.nix @@ -1,3 +1,5 @@ final: prev: { -# s6-rc = final.callPackage ./pkgs/s6-rc; + pseudofile = final.callPackage ./pkgs/pseudofile {}; + s6-init-files = final.callPackage ./pkgs/s6-init-files {}; + strace = prev.strace.override { libunwind = null; }; } diff --git a/pkgs/pseudofile/default.nix b/pkgs/pseudofile/default.nix new file mode 100644 index 0000000..52d6821 --- /dev/null +++ b/pkgs/pseudofile/default.nix @@ -0,0 +1,37 @@ +{ + writeText +, lib +}: +filename : attrset : +let + inherit (lib.debug) traceSeqN; + inherit (lib.attrsets) mapAttrsToList; + visit = prefix: attrset: + let + qprint = msg : builtins.replaceStrings ["\n" "=" "\""] ["=0A" "=3D" "=22"] msg; + l = + mapAttrsToList + (filename: attrs: + let + attrs' = {type = "f"; } // attrs; + mode = if attrs ? mode then attrs.mode else + (if attrs'.type == "d" then "0755" else "0644"); + line = "${prefix}/${filename} ${attrs'.type} ${mode} 0 0"; + in + if attrs'.type == "f" then + "${line} echo -n \"${qprint attrs'.file}\" |qprint -d" + else if attrs'.type == "d" then + (visit "${prefix}/${filename}" attrs.contents) + + "\n" + line + else if attrs'.type == "s" then + "${line} ${attrs'.target}" + else if attrs'.type == "l" then + "${prefix}/${filename} l ${attrs'.target}" + else if attrs'.type == "i" then + "${line} ${attrs.subtype}" + else + line) + attrset; + in builtins.concatStringsSep "\n" l; + res = (visit "" attrset); +in writeText filename res diff --git a/pkgs/s6-init-files/default.nix b/pkgs/s6-init-files/default.nix new file mode 100644 index 0000000..482c475 --- /dev/null +++ b/pkgs/s6-init-files/default.nix @@ -0,0 +1,156 @@ +{ + execline +, s6 +, s6-linux-init +, s6-rc +, pseudofile +, lib +, stdenvNoCC +, busybox +} : +let + initscripts = stdenvNoCC.mkDerivation { + name = "s6-scripts"; + src = ./scripts; + phases = ["unpackPhase" "installPhase" ]; + buildInputs = [busybox]; + installPhase = '' + mkdir $out + cp -r $src $out/scripts + chmod -R +w $out + patchShebangs $out/scripts + ''; + }; + dir = contents: { type = "d"; inherit contents; }; + symlink = target: { type = "s"; inherit target; }; + hpr = arg: "#!${execline}/bin/execlineb -S0\n${s6-linux-init}/bin/s6-linux-init-hpr ${arg} \$@"; + bin = dir { + shutdown = symlink "${s6-linux-init}/bin/s6-linux-init-shutdown"; + telinit = symlink "${s6-linux-init}/bin/s6-linux-init-telinit"; + reboot = { type="f"; file = hpr "-r"; mode="0755"; }; + poweroff = { type="f"; file = hpr "-p"; mode="0755"; }; + halt = { type="f"; file = hpr "-h"; mode="0755"; }; + init = { + type="f"; mode="0755"; + file = "#!${execline}/bin/execlineb -S0\n${s6-linux-init}/bin/s6-linux-init -c /etc/s6-linux-init/current -m 0022 -p ${lib.makeBinPath [execline s6-linux-init s6-rc]}:/usr/bin:/bin -d /dev -- \"\\$@\""; + }; + }; + scripts = symlink "${initscripts}/scripts"; + env = dir {}; + run-image = dir { + service = dir { + s6-linux-init-runleveld = dir { + notification-fd = { file = "3"; }; + run = { + file = '' + #!${execline}/bin/execlineb -P + ${execline}/bin/fdmove -c 2 1 + ${execline}/bin/bin/fdmove 1 3 + ${s6}/s6-ipcserver -1 -a 0700 -c 1 -- s + ${s6}/bin/s6-sudod -dt30000 -- "/etc/s6-linux-init/current"/scripts/runlevel + ''; + mode = "0755"; + }; + }; + s6-linux-init-shutdownd = dir { + fifo = { + type = "i"; + subtype = "f"; + mode = "0600"; + }; + run = { + file = '' + #!${execline}/bin/execlineb -P + ${s6-linux-init}/bin/s6-linux-init-shutdownd -c "/etc/s6-linux-init/current" -g 3000 + ''; + mode = "0755"; + }; + + }; + s6-svscan-log = dir { + fifo = { + type = "i"; + subtype = "f"; + mode = "0600"; + }; + notification-fd = { file = "3"; }; + run = { + file = '' + #!${execline}/bin/execlineb -P + ${execline}/bin/redirfd -w 1 /dev/null + ${execline}/bin/redirfd -rnb 0 fifo + ${s6}/bin/s6-log -bpd3 -- t /run/uncaught-logs + ''; + mode = "0755"; + }; + }; + getty = dir { + run = { + file = '' + #!${execline}/bin/execlineb -P + ${busybox}/bin/getty -l ${busybox}/bin/login 38400 /dev/console + ''; + mode = "0755"; + }; + }; + ".s6-svscan" = + let + quit = message: '' + #!${execline}/bin/execlineb -P + ${execline}/bin/redirfd -w 2 /dev/console + ${execline}/bin/bin/fdmove -c 1 2 + ${execline}/bin/foreground { ${s6-linux-init}/bin/s6-linux-init-echo -- ${message} } + ${s6-linux-init}/bin/s6-linux-init-hpr -fr + ''; + shutdown = action: '' + #!${execline}/bin/execlineb -P + ${s6-linux-init}/bin/s6-linux-init-hpr -a #{action} -- now + ''; + empty = "#!${execline}/bin/execlineb -P\n"; + in dir { + crash = { + file = quit "s6-svscan crashed. Rebooting."; + mode = "0755"; + }; + finish = { + file = quit "s6-svscan exited. Rebooting."; + mode = "0755"; + }; + SIGINT = { + file = shutdown "-r"; + mode = "0755"; + }; + SIGPWR = { + file = shutdown "-p"; + mode = "0755"; + }; + SIGQUIT = { + file = empty; + mode = "0755"; + }; + SIGTERM = { + file = empty; + mode = "0755"; + }; + SIGUSR1 = { + file = shutdown "-p"; + mode = "0755"; + }; + SIGUSR2 = { + file = shutdown "-h"; + mode = "0755"; + }; + SIGWINCH = { + file = empty; + mode = "0755"; + }; + + }; + }; + uncaught-logs = (dir {}) // {mode = "2750";}; + }; + structure = { etc = dir { s6-linux-init = dir { current = dir { + inherit bin scripts env run-image; + };};};}; + +in pseudofile "pseudo.s6-init" structure diff --git a/pkgs/s6-init-files/scripts/rc.init b/pkgs/s6-init-files/scripts/rc.init new file mode 100755 index 0000000..e4b8ff5 --- /dev/null +++ b/pkgs/s6-init-files/scripts/rc.init @@ -0,0 +1,42 @@ +#!/usr/bin/env sh + +rl="$1" +shift + +### argv now contains the arguments of the kernel command line that are +### not of the form key=value. (The key=value arguments were stored by +### s6-linux-init into an envdir, if instructed so via the -s option.) +### Normally this argv remains unused because programs that need the +### kernel command line usually read it later on from /proc/cmdline - +### but just in case, it's available here. + + +### 1. Early preparation +### This is done only once at boot time. +### Ideally, this phase should just initialize the service manager. + +### If your services are managed by s6-rc: +### (replace /run/service with your scandir) +s6-rc-init /run/service -d -c /etc/s6-rc/compiled + + +### 2. Starting the wanted set of services +### This is also called every time you change runlevels with telinit. +### (edit the location to suit your installation) +### By default, $rl is the string "default", unless you changed it +### via the -D option to s6-linux-init-maker. +### Numeric arguments from 1 to 5 on the kernel command line will +### override the default. + +exec /etc/s6-linux-init/current/scripts/runlevel "$rl" + + +### If this script is run in a container, then 1. and 2. above do not +### apply and you should just call your CMD, if any, or let your +### services run. +### Something like this: + +# if test -z "$*" ; then return 0 ; fi +# $@ +# echo $? > /run/s6-linux-init-container-results/exitcode +# halt diff --git a/pkgs/s6-init-files/scripts/rc.shutdown b/pkgs/s6-init-files/scripts/rc.shutdown new file mode 100755 index 0000000..83a29dd --- /dev/null +++ b/pkgs/s6-init-files/scripts/rc.shutdown @@ -0,0 +1,19 @@ +#!/usr/bin/env sh + + +### Things to do before hardware halt/reboot/poweroff. +### Ideally, it should be a single call to the service manager, +### telling it to bring all the services down. + +### If your s6-linux-init-maker invocation was made with the -1 +### option, messages from rc.shutdown will appear on /dev/console +### as well as be logged by the catch-all logger. +### If your s6-linux-init-maker invocation did NOT include the -1 +### option, messages from rc.shutdown will only be logged by the +### catch-all logger and will NOT appear on /dev/console. In order +### to print them to /dev/console instead, you may want to +### uncomment the following line: +exec >/dev/console 2>&1 + +### If your services are managed by s6-rc: +exec s6-rc -v2 -bDa change diff --git a/pkgs/s6-init-files/scripts/rc.shutdown.final b/pkgs/s6-init-files/scripts/rc.shutdown.final new file mode 100755 index 0000000..048c6db --- /dev/null +++ b/pkgs/s6-init-files/scripts/rc.shutdown.final @@ -0,0 +1,18 @@ +#!/nix/store/xbdqbi2mscmhl5wcpbgpjdwxbsrvpkil-bash-5.1-p16/bin/sh -e + +### Things to do *right before* the machine gets rebooted or +### powered off, at the very end of the shutdown sequence, +### when all the filesystems are unmounted. + +### This is a last resort hook; normally nothing should be +### done here (your rc.shutdown script should have taken care +### of everything) and you should leave this script empty. + +### Some distributions, however, may need to perform some +### actions after unmounting the filesystems: typically if +### an additional teardown action is required on a filesystem +### after unmounting it, or if the system needs to be +### pivot_rooted before it can be shut down, etc. + +### Those are all exceptional cases. If you don't know for +### certain that you need to do something here, you don't. diff --git a/pkgs/s6-init-files/scripts/runlevel b/pkgs/s6-init-files/scripts/runlevel new file mode 100755 index 0000000..a7fbb48 --- /dev/null +++ b/pkgs/s6-init-files/scripts/runlevel @@ -0,0 +1,12 @@ +#!/usr/bin/env sh -e + +### This script is called once at boot time by rc.init, and is +### also called by the runleveld service every time the user +### requests a machine state change via telinit. +### Ideally, it should just be a call to the service manager. + +test "$#" -gt 0 || { echo 'runlevel: fatal: too few arguments' 1>&2 ; exit 100 ; } + + +### If your services are managed by s6-rc: +exec s6-rc -v2 -up change "$1" diff --git a/tests/pseudofiles/result.expected b/tests/pseudofiles/result.expected new file mode 100644 index 0000000..f624ee6 --- /dev/null +++ b/tests/pseudofiles/result.expected @@ -0,0 +1,13 @@ +/service/s6-linux-init-runleveld/notification-fd f 0644 0 0 echo -n "3" |qprint -d +/service/s6-linux-init-runleveld/run f 0755 0 0 echo -n " hello=0A newline=0A" |qprint -d +/service/s6-linux-init-runleveld d 0755 0 0 +/service/s6-linux-init-shutdownd/fifo i 0600 0 0 f +/service/s6-linux-init-shutdownd/run f 0755 0 0 echo -n "s6-linux-init/bin/s6-linux-init-shutdownd -c =22/etc/s6-linux-init/current=22 -g 3000=0A" |qprint -d +/service/s6-linux-init-shutdownd d 0755 0 0 +/service/s6-svscan-log/fifo i 0600 0 0 f +/service/s6-svscan-log/notification-fd f 0644 0 0 echo -n "3" |qprint -d +/service/s6-svscan-log/run f 0644 0 0 echo -n "gdsgdfgsdgf=0A" |qprint -d +/service/s6-svscan-log d 0755 0 0 +/service d 0755 0 0 + +/uncaught-logs d 2750 0 0 \ No newline at end of file diff --git a/tests/pseudofiles/run.sh b/tests/pseudofiles/run.sh new file mode 100755 index 0000000..088c407 --- /dev/null +++ b/tests/pseudofiles/run.sh @@ -0,0 +1,17 @@ +set -e + +expr=$(cat <<"EXPR" +let + overlay = import ./overlay.nix; + nixpkgs = import ( {overlays = [overlay]; }); + structure = import ./tests/pseudofiles/structure.nix; +in nixpkgs.pkgs.pseudofile "pseudo.s6-init" structure +EXPR + ) + +NIXPKGS_ALLOW_UNSUPPORTED_SYSTEM=1 nix-build -E "${expr}" -o tests/pseudofiles/result $* +diff tests/pseudofiles/result tests/pseudofiles/result.expected +test -f /tmp/out.squashfs && rm /tmp/out.squashfs +nix-shell -p squashfsTools -p qprint --run "mksquashfs - /tmp/out.squashfs -p '/ d 755 0 0' -pf tests/pseudofiles/result -quiet -no-progress" +foo="$(nix-shell -p squashfsTools --run 'unsquashfs -cat /tmp/out.squashfs service/s6-linux-init-runleveld/run')" +test "$foo" = "$(printf "hello\nworld")" diff --git a/tests/pseudofiles/structure.nix b/tests/pseudofiles/structure.nix new file mode 100644 index 0000000..a762aa0 --- /dev/null +++ b/tests/pseudofiles/structure.nix @@ -0,0 +1,45 @@ +let + dir = contents: { type = "d"; inherit contents; }; + structure = { + service = dir { + s6-linux-init-runleveld = dir { + notification-fd = { file = "3"; }; + run = { + file = '' + hello + world + ''; + mode = "0755"; + }; + }; + s6-linux-init-shutdownd = dir { + fifo = { + type = "i"; + subtype = "f"; + mode = "0600"; + }; + run = { + file = '' + s6-linux-init/bin/s6-linux-init-shutdownd -c "/etc/s6-linux-init/current" -g 3000 + ''; + mode = "0755"; + }; + + }; + s6-svscan-log = dir { + fifo = { + type = "i"; + subtype = "f"; + mode = "0600"; + }; + notification-fd = { file = "3"; }; + run = { + file = '' + gdsgdfgsdgf + ''; + }; + }; + }; + uncaught-logs = (dir {}) // {mode = "2750";}; + }; +in structure