diff --git a/.forgejo/workflows/build.yaml b/.forgejo/workflows/build.yaml new file mode 100644 index 0000000..71964a9 --- /dev/null +++ b/.forgejo/workflows/build.yaml @@ -0,0 +1,50 @@ +name: build liminix +on: + pull_request: + types: [opened, synchronize, edited, reopened] + branches: + - main + push: + branches: + - main + +jobs: + build_vm_qemu_mips: + runs-on: nix + steps: + - uses: actions/checkout@v3 + + - name: Build VM QEMU MIPS + run: | + # Enter the shell + nix-build ci.nix -A qemu + + build_zyxel-nwa50ax_mips: + runs-on: nix + steps: + - uses: actions/checkout@v3 + + - name: Build VM QEMU MIPS + run: | + # Enter the shell + nix-build ci.nix -A qemu + + test_hostapd: + runs-on: nix + steps: + - uses: actions/checkout@v3 + + - name: Build VM QEMU MIPS + run: | + # Enter the shell + nix-build ci.nix -A wlan + + test_shell_customization: + runs-on: nix + steps: + - uses: actions/checkout@v3 + + - name: Build VM QEMU MIPS + run: | + # Enter the shell + nix-build ci.nix -A custom-shell diff --git a/ci.nix b/ci.nix index ec345dd..73d8c97 100644 --- a/ci.nix +++ b/ci.nix @@ -1,21 +1,15 @@ { - nixpkgs -, unstable -, liminix + sources ? import ./lon.nix +, nixpkgs ? sources.nixpkgs +, unstable ? nixpkgs +, liminix ? ./. , ... }: let - inherit (builtins) map; - pkgs = (import nixpkgs {}); + pkgs = (import nixpkgs { }); borderVmConf = ./bordervm.conf-example.nix; - inherit (pkgs.lib.attrsets) genAttrs; + inherit (pkgs.lib.attrsets) genAttrs mapAttrs; devices = [ - "gl-ar750" - "gl-mt300a" - "gl-mt300n-v2" "qemu" - "qemu-aarch64" - "qemu-armv7l" - "tp-archer-ax23" "zyxel-nwa50ax" ]; vanilla = ./vanilla-configuration.nix; @@ -25,7 +19,7 @@ let device = import (liminix + "/devices/${name}"); liminix-config = vanilla; }).outputs.default; - tests = import ./tests/ci.nix; + tests = mapAttrs (_: v: v { inherit liminix nixpkgs; }) (import ./tests/ci.nix); jobs = (genAttrs devices for-device) // tests // @@ -44,12 +38,6 @@ let imports = [ ./modules/all-modules.nix ]; }; }).outputs.optionsJson; - installers = map (f: "system.outputs.${f}") [ - "vmroot" - "mtdimage" - "ubimage" - ]; - inherit (pkgs.lib) concatStringsSep; in pkgs.stdenv.mkDerivation { name = "liminix-doc"; nativeBuildInputs = with pkgs; [ diff --git a/default.nix b/default.nix index e372095..fd1c357 100644 --- a/default.nix +++ b/default.nix @@ -26,9 +26,13 @@ let eval = evalModules { modules = [ { - nixpkgs.overlays = [ - overlay - ]; + nixpkgs = { + source = nixpkgs; + overlays = [ overlay ]; + config.permittedInsecurePackages = [ + "python-2.7.18.8" + ]; + }; } device.module liminix-config diff --git a/devices/qemu/default.nix b/devices/qemu/default.nix index b6e860d..fe98952 100644 --- a/devices/qemu/default.nix +++ b/devices/qemu/default.nix @@ -1,7 +1,7 @@ # This "device" generates images that can be used with the QEMU # emulator. The default output is a directory containing separate # kernel (uncompressed vmlinux) and initrd (squashfs) images -{ +rec { system = { crossSystem = { config = "mips-unknown-linux-musl"; @@ -41,6 +41,9 @@ ../../modules/arch/mipseb.nix ../families/qemu.nix ]; + + nixpkgs.hostPlatform = system.crossSystem; + kernel = { config = { MIPS_MALTA= "y"; diff --git a/devices/zyxel-nwa50ax/default.nix b/devices/zyxel-nwa50ax/default.nix index e58430f..2b4813d 100644 --- a/devices/zyxel-nwa50ax/default.nix +++ b/devices/zyxel-nwa50ax/default.nix @@ -1,4 +1,4 @@ -{ +rec { system = { crossSystem = { config = "mipsel-unknown-linux-musl"; @@ -135,6 +135,8 @@ ../../modules/zyxel-dual-image ]; + nixpkgs.hostPlatform = system.crossSystem; + filesystem = dir { lib = dir { firmware = dir { @@ -170,7 +172,7 @@ maxLEBcount = "256"; }; - flash.eraseBlockSize = 65536; + flash.eraseBlockSize = 64 * 1024; # This is a FIT containing a kernel padded and # a UBI volume rootfs. @@ -181,8 +183,8 @@ # Aligned on 2kb. alignment = 2048; - rootDevice = "ubi:rootfs"; - alternativeRootDevice = "ubi:rootfs"; + rootDevice = "ubi0:rootfs"; + alternativeRootDevice = "ubi1:rootfs"; # Auto-attach MTD devices: ubi_a then ubi_b. ubi.mtds = [ "ubi_a" "ubi_b" ]; @@ -222,8 +224,9 @@ imageFormat = "fit"; tftp = { - # 5MB is nice. - freeSpaceBytes = 5 * 1024 * 1024; + # 20MB is pretty good on this device as we have plenty of RAM. + freeSpaceBytes = 20 * 1024 * 1024; + appendDTB = true; loadAddress = lim.parseInt "0x2000000"; }; }; diff --git a/lib/eval-config.nix b/lib/eval-config.nix index 7befab1..4c3c009 100644 --- a/lib/eval-config.nix +++ b/lib/eval-config.nix @@ -1,4 +1,4 @@ -{ nixpkgs ? , pkgs ? (import {}), lib ? pkgs.lib }: +{ nixpkgs ? , pkgs ? (import nixpkgs {}), lib ? pkgs.lib }: args: let modulesPath = builtins.toString ../modules; diff --git a/lon.lock b/lon.lock new file mode 100644 index 0000000..50209e4 --- /dev/null +++ b/lon.lock @@ -0,0 +1,15 @@ +{ + "version": "1", + "sources": { + "nixpkgs": { + "type": "GitHub", + "fetchType": "tarball", + "owner": "nixos", + "repo": "nixpkgs", + "branch": "nixos-unstable-small", + "revision": "b6227cadb5123c7e4cb159bf6f9f5705ae081a47", + "url": "https://github.com/nixos/nixpkgs/archive/b6227cadb5123c7e4cb159bf6f9f5705ae081a47.tar.gz", + "hash": "sha256-KFR30GNFhjzXl0kVEn+KK4xrFr0gggb1NBroP8ukbxY=" + } + } +} diff --git a/lon.nix b/lon.nix new file mode 100644 index 0000000..5f320ea --- /dev/null +++ b/lon.nix @@ -0,0 +1,41 @@ +# Generated by lon. Do not modify! +let + + lock = builtins.fromJSON (builtins.readFile ./lon.lock); + + # Override with a path defined in an environment variable. If no variable is + # set, the original path is used. + overrideFromEnv = + name: path: + let + replacement = builtins.getEnv "LON_OVERRIDE_${name}"; + in + if replacement == "" then + path + else + # this turns the string into an actual Nix path (for both absolute and + # relative paths) + if builtins.substring 0 1 replacement == "/" then + /. + replacement + else + /. + builtins.getEnv "PWD" + "/${replacement}"; + + fetchSource = + args@{ fetchType, ... }: + if fetchType == "git" then + builtins.fetchGit { + url = args.url; + ref = args.branch; + rev = args.revision; + narHash = args.hash; + } + else if fetchType == "tarball" then + builtins.fetchTarball { + url = args.url; + sha256 = args.hash; + } + else + builtins.throw "Unsupported source type ${fetchType}"; + +in +builtins.mapAttrs (name: args: overrideFromEnv name (fetchSource args)) lock.sources diff --git a/modules/all-modules.nix b/modules/all-modules.nix index abce5db..98352c1 100644 --- a/modules/all-modules.nix +++ b/modules/all-modules.nix @@ -8,6 +8,7 @@ ./bridge ./busybox.nix ./dhcp6c + ./jitter-rng ./dnsmasq ./firewall ./hardware.nix diff --git a/modules/arch/mips.nix b/modules/arch/mips.nix index 155c147..359bb71 100644 --- a/modules/arch/mips.nix +++ b/modules/arch/mips.nix @@ -14,5 +14,8 @@ boot.commandLine = [ "console=ttyS0,115200" # true of all mips we've yet encountered ]; + boot.tftp.commandLine = [ + "console=ttyS0,115200" # true of all mips we've yet encountered + ]; }; } diff --git a/modules/base.nix b/modules/base.nix index 1c8b67c..5107e1e 100644 --- a/modules/base.nix +++ b/modules/base.nix @@ -4,11 +4,13 @@ { lib, pkgs, config, ...}: let - inherit (lib) mkEnableOption mkOption types isDerivation hasAttr ; + inherit (lib) mkEnableOption mkOption types isDerivation hasAttr concatStringsSep mapAttrsToList; inherit (pkgs.pseudofile) dir symlink; inherit (pkgs.liminix.networking) address interface; inherit (pkgs.liminix.services) bundle; + # TODO: escape shell argument. + exportVar = name: value: "export ${name}=\"${value}\""; type_service = pkgs.liminix.lib.types.service; in { @@ -22,6 +24,24 @@ in { /run/current-system, we just add the paths in /etc/profile ''; }; + + environmentVariables = mkOption { + type = types.attrsOf types.str; + description = '' + Attribute set of environment variables to make available + in a login shell. + + The value is assumed to be escaped and the name to be valid. + ''; + }; + + prompt = mkOption { + type = types.str; + default = "$(whoami)@$(hostname) # "; + description = '' + Prompt string (PS1) for the shell. + ''; + }; }; services = mkOption { type = types.attrsOf type_service; @@ -69,6 +89,14 @@ in { default = "uimage"; }; tftp = { + commandLine = mkOption { + type = types.listOf types.str; + default = config.boot.commandLine; + description = '' + TFTP-specific command line. + Defaults to the classical one if unset. + ''; + }; loadAddress = mkOption { type = types.ints.unsigned; description = '' @@ -98,8 +126,13 @@ in { }; }; config = { + # By default, we enable cross-compilation support. + nixpkgs.buildPlatform = lib.mkDefault builtins.currentSystem; + defaultProfile.packages = with pkgs; [ s6 s6-init-bin execline s6-linux-init s6-rc ]; + # Set the useful PS1 prompt by default. + defaultProfile.environmentVariables.PS1 = lib.mkDefault config.defaultProfile.prompt; boot.commandLine = [ "panic=10 oops=panic init=/bin/init loglevel=8" @@ -109,7 +142,13 @@ in { ] ++ (map (mtd: "ubi.mtd=${mtd}") config.hardware.ubi.mtds) ++ lib.optional (config.rootOptions != null) "rootflags=${config.rootOptions}" - ++ lib.optional (config.hardware.alternativeRootDevice != null) "altroot=${config.hardware.alternativeRootDevice}"; + ++ lib.optional (config.hardware.alternativeRootDevice != null) "rootalt=${config.hardware.alternativeRootDevice}"; + + boot.tftp.commandLine = [ + "panic=10 oops=panic init=/bin/init loglevel=8" + "fw_devlink=off" + "rootfstype=${config.rootfsType}" + ]; system.callService = path : parameters : let @@ -164,9 +203,10 @@ in { etc = let profile = symlink (pkgs.writeScript ".profile" '' - PATH=${lib.makeBinPath config.defaultProfile.packages}:/bin + PATH=${lib.makeBinPath config.defaultProfile.packages}:/bin export PATH - ''); + ${concatStringsSep "\n" (mapAttrsToList exportVar config.defaultProfile.environmentVariables)} + ''); in dir { inherit profile; ashrc = profile; diff --git a/modules/bridge/default.nix b/modules/bridge/default.nix index 32df4a5..3a24d7c 100644 --- a/modules/bridge/default.nix +++ b/modules/bridge/default.nix @@ -20,6 +20,7 @@ in system.service.bridge = { primary = mkOption { type = liminix.lib.types.serviceDefn; }; members = mkOption { type = liminix.lib.types.serviceDefn; }; + ready = mkOption { type = liminix.lib.types.serviceDefn; }; }; }; config.system.service.bridge = { @@ -28,6 +29,12 @@ in type = types.str; description = "bridge interface name to create"; }; + + macAddressFromInterface = mkOption { + type = types.nullOr liminix.lib.types.service; + default = null; + description = "reuse mac address from an existing interface service"; + }; }; members = config.system.callService ./members.nix { primary = mkOption { @@ -36,8 +43,33 @@ in }; members = mkOption { - type = types.listOf liminix.lib.types.interface; - description = "interfaces to add to the bridge"; + type = types.attrsOf (types.submodule ({ ... }: { options = { + member = mkOption { + type = liminix.lib.types.interface; + description = "interface to add"; + }; + + dependencies = mkOption { + type = types.listOf liminix.lib.types.service; + default = []; + description = "extra dependencies before attaching this interface to the bridge"; + }; + }; })); + + description = "set of bridge members"; + }; + }; + + # TODO: generalize it outside + ready = config.system.callService ./ready.nix { + primary = mkOption { + type = liminix.lib.types.service; + description = "primary bridge interface"; + }; + + members = mkOption { + type = liminix.lib.types.service; + description = "members service"; }; }; }; diff --git a/modules/bridge/members.nix b/modules/bridge/members.nix index a278730..560ba14 100644 --- a/modules/bridge/members.nix +++ b/modules/bridge/members.nix @@ -7,26 +7,22 @@ { members, primary } : let - inherit (liminix.networking) interface; - inherit (liminix.services) bundle oneshot; - inherit (lib) mkOption types; - addif = member : - # how do we get sight of services from here? maybe we need to - # implement ifwait as a regualr derivation instead of a - # servicedefinition - svc.ifwait.build { - state = "running"; - interface = member; - dependencies = [ primary member ]; - service = oneshot { - name = "${primary.name}.member.${member.name}"; - up = '' - ip link set dev $(output ${member} ifname) master $(output ${primary} ifname) - ''; - down = "ip link set dev $(output ${member} ifname) nomaster"; - }; - }; -in bundle { + inherit (liminix.services) structuredBundle oneshot; + inherit (lib) mapAttrs; + addif = name: { dependencies ? [ ], member }: oneshot { + name = "${primary.name}.member.${name}"; + up = '' + echo "attaching $(output ${member} ifname) to $(output ${primary} ifname) bridge" + ip link set dev $(output ${member} ifname) master $(output ${primary} ifname) + ''; + down = '' + echo "detaching $(output ${member} ifname) from any bridge" + ip link set dev $(output ${member} ifname) nomaster + ''; + + dependencies = [ primary member ] ++ dependencies; + }; +in structuredBundle { name = "${primary.name}.members"; - contents = map addif members; + contents = mapAttrs addif members; } diff --git a/modules/bridge/primary.nix b/modules/bridge/primary.nix index c25e5fe..f5e1219 100644 --- a/modules/bridge/primary.nix +++ b/modules/bridge/primary.nix @@ -3,15 +3,24 @@ , ifwait , lib }: -{ ifname } : +{ ifname, macAddressFromInterface ? null } : let inherit (liminix.services) bundle oneshot; - inherit (lib) mkOption types; + inherit (lib) mkOption types optional; in oneshot rec { name = "${ifname}.link"; up = '' - ip link add name ${ifname} type bridge - ${liminix.networking.ifup name ifname} + ${if macAddressFromInterface == null then + "ip link add name ${ifname} type bridge" + else + "ip link add name ${ifname} address $(output ${macAddressFromInterface} ether) type bridge"} + + (in_outputs ${name} + echo ${ifname} > ifname + cat /sys/class/net/${ifname}/address > ether + ) ''; - down = "ip link set down dev ${ifname}"; + down = "ip link delete ${ifname}"; + + dependencies = optional (macAddressFromInterface != null) macAddressFromInterface; } diff --git a/modules/bridge/ready.nix b/modules/bridge/ready.nix new file mode 100644 index 0000000..f8c40b3 --- /dev/null +++ b/modules/bridge/ready.nix @@ -0,0 +1,18 @@ +{ + liminix +, ifwait +, lib +}: +{ primary, members } : +let + inherit (liminix.services) oneshot; +in oneshot { + name = "${primary.name}.oper"; + up = '' + ip link set up dev $(output ${primary} ifname) + ${ifwait}/bin/ifwait -v $(output ${primary} ifname) running + ''; + down = "ip link set down dev $(output ${primary} ifname)"; + + dependencies = [ members ]; +} diff --git a/modules/hostapd/default.nix b/modules/hostapd/default.nix index 2bcf4d6..67f4f72 100644 --- a/modules/hostapd/default.nix +++ b/modules/hostapd/default.nix @@ -20,15 +20,30 @@ in { system.service.hostapd = mkOption { type = liminix.lib.types.serviceDefn; }; + + system.service.hostapd-ready = mkOption { + type = liminix.lib.types.serviceDefn; + }; }; config = { system.service.hostapd = liminix.callService ./service.nix { interface = mkOption { type = liminix.lib.types.service; }; + package = mkOption { + type = types.package; + default = pkgs.hostapd; + }; params = mkOption { type = types.attrs; }; }; + + system.service.hostapd-ready = liminix.callService ./ready.nix { + interface = mkOption { + type = liminix.lib.types.interface; + description = "Interface for which to wait that the oper state Master or Master (VLAN) has been reached."; + }; + }; }; } diff --git a/modules/hostapd/ready.nix b/modules/hostapd/ready.nix new file mode 100644 index 0000000..7c10c32 --- /dev/null +++ b/modules/hostapd/ready.nix @@ -0,0 +1,16 @@ +{ + liminix +, ifwait +, lib +}: +{ interface } : +let + inherit (liminix.services) oneshot; +in oneshot { + name = "${interface.name}.wlan-oper"; + up = '' + ${ifwait}/bin/ifbridgeable -v $(output ${interface} ifname) + ''; + + dependencies = [ interface ]; +} diff --git a/modules/hostapd/service.nix b/modules/hostapd/service.nix index 2bd5453..68e9754 100644 --- a/modules/hostapd/service.nix +++ b/modules/hostapd/service.nix @@ -1,10 +1,9 @@ { liminix -, hostapd , writeText , lib }: -{ interface, params} : +{ package, interface, params } : let inherit (liminix.services) longrun; inherit (lib) concatStringsSep mapAttrsToList; @@ -35,5 +34,5 @@ let in longrun { inherit name; dependencies = [ interface ]; - run = "${hostapd}/bin/hostapd -i $(output ${interface} ifname) -P /run/${name}.pid -S ${conf}"; + run = "${package}/bin/hostapd -i $(output ${interface} ifname) -P /run/${name}.pid -S ${conf}"; } diff --git a/modules/hostname.nix b/modules/hostname.nix index d5cd962..e05b724 100644 --- a/modules/hostname.nix +++ b/modules/hostname.nix @@ -1,7 +1,6 @@ { lib, pkgs, config, ...}: let inherit (lib) mkOption types; - inherit (pkgs.liminix.services) oneshot; in { options = { hostname = mkOption { @@ -12,12 +11,21 @@ in { default = "liminix"; type = types.nonEmptyStr; }; - }; - config = { - services.hostname = oneshot { - name = "hostname"; - up = "echo ${config.hostname} > /proc/sys/kernel/hostname"; - down = "true"; + hostname-script = mkOption { + description = '' + Script that outputs the system hostname on stdin. + ''; + default = pkgs.writeScript "hostname-gen" '' + #!/bin/sh + echo ${config.hostname} + ''; + defaultText = '' + pkgs.writeScript "hostname-gen" ''' + #!/bin/sh + echo ''${config.hostname} + ''' + ''; + type = types.package; }; }; } diff --git a/modules/jitter-rng/default.nix b/modules/jitter-rng/default.nix new file mode 100644 index 0000000..aa6f9e5 --- /dev/null +++ b/modules/jitter-rng/default.nix @@ -0,0 +1,21 @@ +## CPU Jitter RNG +## ============== +## +## CPU Jitter RNG is a random number generator # providing non-physical true +## random generation # that works equally for kernel and user-land. It relies +## on the availability of a high-resolution timer. +{ lib, pkgs, ... }: +let + inherit (lib) mkOption types; + inherit (pkgs) liminix; +in { + options.system.service.jitter-rng = mkOption { + type = liminix.lib.types.serviceDefn; + }; + + config = { + system.service.jitter-rng = pkgs.liminix.callService ./jitter-rng.nix { + }; + }; +} + diff --git a/modules/jitter-rng/jitter-rng.nix b/modules/jitter-rng/jitter-rng.nix new file mode 100644 index 0000000..1072be1 --- /dev/null +++ b/modules/jitter-rng/jitter-rng.nix @@ -0,0 +1,18 @@ +{ + liminix +, lib +, jitterentropy-rngd +}: +{ }: +let + inherit (liminix.services) longrun; + name = "jitterentropy-rngd"; +in +longrun { + # Does it need to be unique? + inherit name; + run = '' + mkdir -p /run/jitterentropy-rngd + ${jitterentropy-rngd}/bin/jitterentropy-rngd -v -p /run/jitterentropy-rngd/${name}.pid + ''; +} diff --git a/modules/network/dhcpc.nix b/modules/network/dhcpc.nix index ed665b3..cd652b2 100644 --- a/modules/network/dhcpc.nix +++ b/modules/network/dhcpc.nix @@ -17,7 +17,7 @@ let ip address replace $ip/$mask dev $interface (in_outputs ${name} for i in lease mask ip router siaddr dns serverid subnet opt53 interface ; do - printenv $i > $i + (printenv $i || true) > $i done) } case $action in @@ -40,7 +40,7 @@ let ''; in longrun { inherit name; - run = "/bin/udhcpc -f -i $(output ${interface} ifname) -x hostname:$(cat /proc/sys/kernel/hostname) -s ${script}"; + run = "exec /bin/udhcpc -f -i $(output ${interface} ifname) -x hostname:$(cat /proc/sys/kernel/hostname) -s ${script}"; notification-fd = 10; dependencies = [ interface ]; } diff --git a/modules/nixpkgs.nix b/modules/nixpkgs.nix index 15c9c0c..206d506 100644 --- a/modules/nixpkgs.nix +++ b/modules/nixpkgs.nix @@ -83,11 +83,11 @@ let localSystem = cfg.hostPlatform; }; in - import ({ + import cfg.source ({ inherit (cfg) config overlays; } // systemArgs) else - import { + import cfg.source { inherit (cfg) config overlays localSystem crossSystem; }; @@ -97,6 +97,14 @@ in { options.nixpkgs = { + source = mkOption { + type = types.package // { + description = "Source of a nixpkgs repository"; + }; + + default = ; + defaultText = ""; + }; pkgs = mkOption { defaultText = literalExpression '' diff --git a/modules/outputs/initramfs.nix b/modules/outputs/initramfs.nix index 89d017f..b1e0ea8 100644 --- a/modules/outputs/initramfs.nix +++ b/modules/outputs/initramfs.nix @@ -36,22 +36,43 @@ in kernel.config = { BLK_DEV_INITRD = "y"; INITRAMFS_SOURCE = builtins.toJSON "${config.system.outputs.initramfs}"; -# INITRAMFS_COMPRESSION_LZO = "y"; + INITRAMFS_COMPRESSION_ZSTD = "y"; }; system.outputs = { initramfs = - let inherit (pkgs.pkgsBuildBuild) gen_init_cpio; + let + inherit (pkgs.pkgsBuildBuild) gen_init_cpio cpio writeScript; + inherit (pkgs) busybox; + failsafe-init = writeScript "init" '' + #!/bin/sh + exec >/dev/console + echo Running in initramfs + PATH=${busybox}/bin:$PATH + export PATH + mount -t proc none /proc + mount -t sysfs none /sys + ${busybox}/bin/sh + ''; + refs = pkgs.writeReferencesToFile busybox; in runCommand "initramfs.cpio" {} '' - cat << SPECIALS | ${gen_init_cpio}/bin/gen_init_cpio /dev/stdin > $out + cat << SPECIALS | ${gen_init_cpio}/bin/gen_init_cpio /dev/stdin > out dir /proc 0755 0 0 dir /dev 0755 0 0 nod /dev/console 0600 0 0 c 5 1 dir /target 0755 0 0 dir /target/persist 0755 0 0 dir /target/nix 0755 0 0 + dir /nix 0755 0 0 + dir /nix/store 0755 0 0 + dir /bin 0755 0 0 + file /bin/sh ${busybox}/bin/sh 0755 0 0 file /init ${pkgs.preinit}/bin/preinit 0755 0 0 + file /failsafe-init ${failsafe-init} 0755 0 0 SPECIALS + + find $(cat ${refs}) | ${cpio}/bin/cpio -H newc -o -A -v -O out + mv out $out ''; systemConfiguration = pkgs.systemconfig config.filesystem.contents; diff --git a/modules/outputs/jffs2.nix b/modules/outputs/jffs2.nix index 02c7c10..0a69a1f 100644 --- a/modules/outputs/jffs2.nix +++ b/modules/outputs/jffs2.nix @@ -5,7 +5,8 @@ , ... }: let - inherit (lib) mkIf mkOption types; + inherit (pkgs) liminix; + inherit (lib) mkIf; o = config.system.outputs; in { @@ -24,17 +25,10 @@ in }; boot.initramfs.enable = true; system.outputs = { - rootfs = - let - inherit (pkgs.pkgsBuildBuild) runCommand mtdutils; - endian = if pkgs.stdenv.isBigEndian - then "--big-endian" else "--little-endian"; - in runCommand "make-jffs2" { - depsBuildBuild = [ mtdutils ]; - } '' - tree=${o.bootablerootdir} - (cd $tree && mkfs.jffs2 --compression-mode=size ${endian} -e ${toString config.hardware.flash.eraseBlockSize} --enable-compressor=lzo --pad --root . --output $out --squash --faketime ) - ''; + rootfs = liminix.builders.jffs2 { + bootableRootDirectory = o.bootablerootdir; + inherit (config.hardware.flash) eraseBlockSize; + }; }; }; } diff --git a/modules/outputs/tftpboot-fit.its b/modules/outputs/tftpboot-fit.its new file mode 100644 index 0000000..fcb58ab --- /dev/null +++ b/modules/outputs/tftpboot-fit.its @@ -0,0 +1,14 @@ +/dts-v1/; + +/ { + description = "Liminix TFTP bootscript"; + #address-cells = <1>; + + images { + bootscript { + description = "Bootscript"; + data = /incbin/("boot.scr"); + type = "script"; + compression = "none"; + }; +}; diff --git a/modules/outputs/tftpboot.nix b/modules/outputs/tftpboot.nix index f8f0572..4fd139d 100644 --- a/modules/outputs/tftpboot.nix +++ b/modules/outputs/tftpboot.nix @@ -5,10 +5,23 @@ , ... }: let - inherit (lib) mkOption types concatStringsSep; + inherit (lib) mkOption mkIf types concatStringsSep; + inherit (pkgs) liminix; cfg = config.boot.tftp; hw = config.hardware; arch = pkgs.stdenv.hostPlatform.linuxArch; + + # UBI cannot run on the top of phram. + needsJffs2 = config.rootfsType == "ubifs"; + # squashfs doesn't work out for us because only `bootablerootdir` + # contain what we need to boot, not `config.filesystem.contents` alas. + rootfstype = if needsJffs2 then "jffs2" else config.rootfsType; + rootfs = if needsJffs2 then + liminix.builders.jffs2 { + bootableRootDirectory = config.system.outputs.bootablerootdir; + inherit (config.hardware.flash) eraseBlockSize; + } + else config.system.outputs.rootfs; in { imports = [ ../ramdisk.nix ]; options.boot.tftp = { @@ -51,11 +64,56 @@ in { It uses the Linux `phram `_ driver to emulate a flash device using a segment of physical RAM. ''; }; + + tftpboot-fit = mkOption { + type = types.package; + description = '' + tftpboot-fit + ************ + + This output is a variant that encloses the `boot.scr` in a FIT + if that's simpler to transfer for you. + ''; + }; }; config = { boot.ramdisk.enable = true; + kernel.config = mkIf needsJffs2 { + JFFS2_FS = "y"; + JFFS2_LZO = "y"; + JFFS2_RTIME = "y"; + JFFS2_COMPRESSION_OPTIONS = "y"; + JFFS2_ZLIB = "y"; + JFFS2_CMODE_SIZE = "y"; + }; + system.outputs = rec { + tftpboot-fit = + let + tftpboot-fit = pkgs.writeText "tftpboot.its" '' + /dts-v1/; + + / { + description = "Liminix TFTP bootscript"; + #address-cells = <1>; + + images { + bootscript { + description = "Bootscript"; + data = /incbin/("${tftpboot}/boot.scr"); + type = "script"; + compression = "none"; + }; + }; + }; + ''; + in + pkgs.runCommand "tftpboot-fit" { nativeBuildInputs = with pkgs.pkgsBuildBuild; [ ubootTools ]; } '' + mkdir -p $out/ + cp -rf ${tftpboot}/* $out/ + mkimage -f ${tftpboot-fit} $out/script.ub + ''; tftpboot = let inherit (pkgs.lib.trivial) toHexString; @@ -69,7 +127,7 @@ in { zimage = "bootz"; }; in choices.${cfg.kernelFormat}; - cmdline = concatStringsSep " " config.boot.commandLine; + cmdline = concatStringsSep " " config.boot.tftp.commandLine; objcopy = "${pkgs.stdenv.cc.bintools.targetPrefix}objcopy"; stripAndZip = '' ${objcopy} -O binary -R .reginfo -R .notes -R .note -R .comment -R .mdebug -R .note.gnu.build-id -S vmlinux.elf vmlinux.bin @@ -84,7 +142,7 @@ in { hex() { printf "0x%x" $1; } rootfsStart=${toString cfg.loadAddress} - rootfsSize=$(binsize64k ${o.rootfs} ) + rootfsSize=$(binsize64k ${rootfs} ) rootfsSize=$(($rootfsSize + ${toString cfg.freeSpaceBytes} )) ln -s ${o.manifest} manifest @@ -98,13 +156,13 @@ in { dtbStart=$(($rootfsStart + $rootfsSize)) ${if cfg.compressRoot then '' - lzma -z9cv ${o.rootfs} > rootfs.lz + lzma -z9cv ${rootfs} > rootfs.lz rootfsLzStart=$dtbStart rootfsLzSize=$(binsize rootfs.lz) dtbStart=$(($dtbStart + $rootfsLzSize)) '' else '' - ln -s ${o.rootfs} rootfs + ln -s ${rootfs} rootfs '' } @@ -121,7 +179,7 @@ in { fdtput -p -t s dtb /reserved-memory/$node compatible phram fdtput -p -t lx dtb /reserved-memory/$node reg $ac_prefix $(hex $rootfsStart) $sz_prefix $(hex $rootfsSize) - cmd="liminix ${cmdline} mtdparts=phram0:''${rootfsSize}(rootfs) phram.phram=phram0,''${rootfsStart},''${rootfsSize},${toString config.hardware.flash.eraseBlockSize} root=/dev/mtdblock0"; + cmd="liminix ${cmdline} mtdparts=phram0:''${rootfsSize}(rootfs) phram.phram=phram0,''${rootfsStart},''${rootfsSize},${toString config.hardware.flash.eraseBlockSize} rootfstype=${rootfstype} root=/dev/mtdblock0"; fdtput -t s dtb /chosen ${config.boot.commandLineDtbNode} "$cmd" dtbSize=$(binsize ./dtb ) diff --git a/modules/pki/default.nix b/modules/pki/default.nix new file mode 100644 index 0000000..b328f4d --- /dev/null +++ b/modules/pki/default.nix @@ -0,0 +1,83 @@ +{ config, lib, pkgs, ... }: +# Inspired from nixpkgs/NixOS. + +with lib; + +let + inherit (pkgs.pseudofile) dir symlink; + cfg = config.security.pki; + + cacertPackage = pkgs.cacert.override { + blacklist = [ ]; + extraCertificateFiles = cfg.certificateFiles; + extraCertificateStrings = cfg.certificates; + }; + caBundleName = "ca-bundle.crt"; + caBundle = "${cacertPackage}/etc/ssl/certs/${caBundleName}"; + +in + +{ + + options = { + security.pki.installCACerts = mkEnableOption "installing CA certificates to the system" // { + default = false; + }; + + security.pki.certificateFiles = mkOption { + type = types.listOf types.path; + default = []; + example = literalExpression ''[ "''${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt" ]''; + description = '' + A list of files containing trusted root certificates in PEM + format. These are concatenated to form + {file}`/etc/ssl/certs/ca-certificates.crt`, which is + used by many programs that use OpenSSL, such as + {command}`curl` and {command}`git`. + ''; + }; + + security.pki.certificates = mkOption { + type = types.listOf types.str; + default = []; + example = literalExpression '' + [ ''' + NixOS.org + ========= + -----BEGIN CERTIFICATE----- + MIIGUDCCBTigAwIBAgIDD8KWMA0GCSqGSIb3DQEBBQUAMIGMMQswCQYDVQQGEwJJ + TDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0 + ... + -----END CERTIFICATE----- + ''' + ] + ''; + description = '' + A list of trusted root certificates in PEM format. + ''; + }; + }; + + config = mkIf cfg.installCACerts { + # NixOS canonical location + Debian/Ubuntu/Arch/Gentoo compatibility. + filesystem = dir { + etc = dir { + ssl = dir { + certs = dir { + "ca-certificates.crt" = symlink caBundle; + "ca-bundle.crt" = symlink caBundle; + }; + }; + + # CentOS/Fedora compatibility. + pki = dir { + certs = dir { + "ca-bundle.crt" = symlink caBundle; + }; + }; + }; + }; + }; + +} + diff --git a/modules/s6/default.nix b/modules/s6/default.nix index cd05de7..e1ce9c8 100644 --- a/modules/s6/default.nix +++ b/modules/s6/default.nix @@ -30,6 +30,8 @@ let installPhase = '' mkdir $out cp -r $src $out/scripts + substituteInPlace $out/scripts/rc.init \ + --replace-fail 'config.hostname' "${config.hostname-script}" chmod -R +w $out ''; }; diff --git a/modules/s6/scripts/rc.init b/modules/s6/scripts/rc.init index c098ffb..3888e50 100755 --- a/modules/s6/scripts/rc.init +++ b/modules/s6/scripts/rc.init @@ -36,6 +36,7 @@ fi ### (replace /run/service with your scandir) s6-rc-init -d -c /etc/s6-rc/compiled /run/service +config.hostname > /proc/sys/kernel/hostname ### 2. Starting the wanted set of services ### This is also called every time you change runlevels with telinit. diff --git a/modules/ubus/default.nix b/modules/ubus/default.nix new file mode 100644 index 0000000..827f537 --- /dev/null +++ b/modules/ubus/default.nix @@ -0,0 +1,24 @@ +## ubus +## ==== +## +## ubus is a micro-bus à la D-Bus for all your needs. + +{ lib, pkgs, config, ...}: +let + inherit (lib) mkOption types; + inherit (pkgs) liminix; +in { + options = { + system.service.ubus = mkOption { + type = liminix.lib.types.serviceDefn; + }; + }; + config = { + system.service.ubus = liminix.callService ./service.nix { + package = mkOption { + type = types.package; + default = pkgs.ubus; + }; + }; + }; +} diff --git a/modules/ubus/service.nix b/modules/ubus/service.nix new file mode 100644 index 0000000..80548c6 --- /dev/null +++ b/modules/ubus/service.nix @@ -0,0 +1,16 @@ +{ + liminix +, writeText +, lib +}: +{ package } : +let + inherit (liminix.services) longrun; +in longrun { + # Long term: make it unique so that user can spawn multiple buses if they want. + name = "ubus"; + run = '' + mkdir -p /run/ubus + ${package}/bin/ubusd -s /run/ubus/ubus.sock + ''; +} diff --git a/overlay.nix b/overlay.nix index 09acaad..891e9a0 100644 --- a/overlay.nix +++ b/overlay.nix @@ -1,23 +1,24 @@ final: prev: let + isCross = final.stdenv.buildPlatform != final.stdenv.hostPlatform; + crossOnly = pkg : amendFn : if isCross then (amendFn pkg) else pkg; extraPkgs = import ./pkgs/default.nix { inherit (final) lib callPackage; }; inherit (final) fetchpatch; - lua_no_readline = prev.lua5_3; -# lua_no_readline = prev.lua5_3.overrideAttrs(o: { -# name = "lua-tty"; -# preBuild = '' -# makeFlagsArray+=(PLAT="posix" SYSLIBS="-Wl,-E -ldl" CFLAGS="-O2 -fPIC -DLUA_USE_POSIX -DLUA_USE_DLOPEN") -# ''; -# # lua in nixpkgs has a postInstall stanza that assumes only -# # one output, we need to override that if we're going to -# # convert to multi-output -# # outputs = ["bin" "man" "out"]; -# makeFlags = -# builtins.filter (x: (builtins.match "(PLAT|MYLIBS).*" x) == null) -# o.makeFlags; -# }); + luaHost = prev.lua5_3.overrideAttrs(o: { + name = "lua-tty"; + preBuild = '' + makeFlagsArray+=(PLAT="posix" SYSLIBS="-Wl,-E -ldl" CFLAGS="-O2 -fPIC -DLUA_USE_POSIX -DLUA_USE_DLOPEN") + ''; + # lua in nixpkgs has a postInstall stanza that assumes only + # one output, we need to override that if we're going to + # convert to multi-output + # outputs = ["bin" "man" "out"]; + makeFlags = + builtins.filter (x: (builtins.match "(PLAT|MYLIBS).*" x) == null) + o.makeFlags; + }); s6 = prev.s6.overrideAttrs(o: let @@ -42,7 +43,6 @@ let (if o ? patches then o.patches else []) ++ (if patch_needed then [ patch ] else []); }); - lua = let s = lua_no_readline.override { self = s; }; in s; in extraPkgs // { # liminix library functions @@ -130,9 +130,18 @@ extraPkgs // { "CONFIG_LIBNL32=y" "CONFIG_PKCS12=y" "CONFIG_RSN_PREAUTH=y" + "CONFIG_UBUS=y" "CONFIG_TLS=internal" ]; h = prev.hostapd.overrideAttrs(o: { + buildInputs = o.buildInputs ++ [ final.libubox final.ubus ]; + src = final.fetchFromGitea { + domain = "git.dgnum.eu"; + owner = "DGNum"; + repo = "hostapd"; + rev = "hostap-liminix-integration"; + hash = "sha256-5Xi90keCHxvuKR5Q7STuZDzuM9h9ac6aWoXVQYvqkQI="; + }; extraConfig = ""; configurePhase = '' cat > hostapd/defconfig < hostapd/defconfig < ifname + cat /sys/class/net/${ifname}/address > ether ) ''; } diff --git a/pkgs/liminix-tools/services/default.nix b/pkgs/liminix-tools/services/default.nix index c0dbaf0..466a978 100644 --- a/pkgs/liminix-tools/services/default.nix +++ b/pkgs/liminix-tools/services/default.nix @@ -39,6 +39,7 @@ let , contents ? [] , buildInputs ? [] , isTrigger ? false + , passthru ? {} } @ args: stdenvNoCC.mkDerivation { # we use stdenvNoCC to avoid generating derivations with names @@ -50,6 +51,8 @@ let dependencies = builtins.map (d: d.name) dependencies; contents = builtins.map (d: d.name) contents; builder = ./builder.sh; + + inherit passthru; }; longrun = { @@ -100,7 +103,18 @@ let serviceType = "bundle"; inherit contents dependencies; }); + structuredBundle = { + name + , contents ? {} + , dependencies ? [] + , ... + } @ args: service (args // { + serviceType = "bundle"; + contents = builtins.attrValues contents; + inherit dependencies; + passthru.components = contents; + }); target = bundle; in { - inherit target bundle oneshot longrun output; + inherit target bundle oneshot longrun output structuredBundle; } diff --git a/pkgs/min-copy-closure/liminix-rebuild.sh b/pkgs/min-copy-closure/liminix-rebuild.sh index 16ec7f1..18f57f0 100755 --- a/pkgs/min-copy-closure/liminix-rebuild.sh +++ b/pkgs/min-copy-closure/liminix-rebuild.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +set -Eeuo pipefail ssh_command=${SSH_COMMAND-ssh} @@ -13,19 +14,24 @@ case "$1" in reboot="soft" shift ;; + "--root") + root_prefix="$2" + shift + shift + ;; esac target_host=$1 shift if [ -z "$target_host" ] ; then - echo Usage: liminix-rebuild \[--no-reboot\] target-host params + echo Usage: liminix-rebuild \[--no-reboot\] \[--fast\] target-host params exit 1 fi if toplevel=$(nix-build "$@" -A outputs.systemConfiguration --no-out-link); then echo systemConfiguration $toplevel - min-copy-closure $target_host $toplevel + min-copy-closure --root "$root_prefix" $target_host $toplevel $ssh_command $target_host $toplevel/bin/install case "$reboot" in reboot) diff --git a/pkgs/preinit/parseopts.c b/pkgs/preinit/parseopts.c index 1803341..243f3b0 100644 --- a/pkgs/preinit/parseopts.c +++ b/pkgs/preinit/parseopts.c @@ -65,13 +65,18 @@ static char * eat_param(char *p, char *param_name, char **out) return p; } +#define SCAN_CMDLINE(cmdline, identifier, field) do { \ + for (char* p = strdup(cmdline); *p; p++) { \ + p = eat_param(p, identifier, &(opts->field)); \ + } \ +} while(0) + + void parseopts(char * cmdline, struct root_opts *opts) { - for(char *p = cmdline; *p; p++) { - p = eat_param(p, "root=", &(opts->device)); - p = eat_param(p, "rootfstype=", &(opts->fstype)); - p = eat_param(p, "rootflags=", &(opts->mount_opts)); - p = eat_param(p, "altroot=", &(opts->altdevice)); - }; + SCAN_CMDLINE(cmdline, "root=", device); + SCAN_CMDLINE(cmdline, "rootfstype=", fstype); + SCAN_CMDLINE(cmdline, "rootflags=", mount_opts); + SCAN_CMDLINE(cmdline, "rootalt=", altdevice); } #ifdef TESTS @@ -85,6 +90,8 @@ void parseopts(char * cmdline, struct root_opts *opts) { #define S(x) #x #define expect_equal(actual, expected) \ if(!actual || strcmp(actual, expected)) die("%d: expected \"%s\", got \"%s\"", __LINE__, expected, actual); +#define expect_null(actual) \ + if (actual) die("%d: expected null, got \"%s\"", __LINE__, actual); int main() @@ -92,6 +99,7 @@ int main() struct root_opts opts = { .device = "/dev/hda1", .fstype = "xiafs", + .altdevice = NULL, .mount_opts = NULL }; char *buf; @@ -103,14 +111,22 @@ int main() expect_equal(opts.fstype, "ubifs"); expect_equal(opts.mount_opts, "subvol=1"); - // finds altroot= options - buf = strdup("liminix console=ttyS0,115200 panic=10 oops=panic init=/bin/init loglevel=8 root=/dev/ubi0_4 rootfstype=ubifs rootflags=subvol=1 fw_devlink=off mtdparts=phram0:18M(rootfs) phram.phram=phram0,0x40400000,18874368,65536 root=/dev/mtdblock0 altroot=/dev/mtdblock6 foo"); + // finds rootalt= options + buf = strdup("liminix console=ttyS0,115200 panic=10 oops=panic init=/bin/init loglevel=8 root=/dev/ubi0_4 rootfstype=ubifs rootflags=subvol=1 fw_devlink=off mtdparts=phram0:18M(rootfs) phram.phram=phram0,0x40400000,18874368,65536 root=/dev/mtdblock0 rootalt=/dev/mtdblock6 foo"); memset(&opts, '\0', sizeof opts); parseopts(buf, &opts); expect_equal(opts.device, "/dev/mtdblock0"); expect_equal(opts.altdevice, "/dev/mtdblock6"); expect_equal(opts.fstype, "ubifs"); expect_equal(opts.mount_opts, "subvol=1"); + // Ensure that `altdevice` is NULL. + buf = strdup("liminix console=ttyS0,115200 panic=10 oops=panic init=/bin/init loglevel=8 fw_devlink=off rootfstype=ubifs mtdparts=phram0:19791872(rootfs) phram.phram=phram0,33554432,19791872,65536 rootfstype=jffs2 root=/dev/mtdblock0"); + memset(&opts, '\0', sizeof opts); parseopts(buf, &opts); + expect_equal(opts.device, "/dev/mtdblock0"); + expect_null(opts.altdevice); + expect_equal(opts.fstype, "jffs2"); + expect_null(opts.mount_opts); + // in case of duplicates, chooses the latter // also: works if the option is end of string buf = strdup("liminix console=ttyS0,115200 panic=10 oops=panic init=/bin/init loglevel=8 root=/dev/ubi0_4 rootfstype=ubifs fw_devlink=off mtdparts=phram0:18M(rootfs) phram.phram=phram0,0x40400000,18874368,65536 root=/dev/mtdblock0"); @@ -146,12 +162,12 @@ int main() if(opts.altdevice) die("expected null altdevice, got \"%s\"", opts.altdevice); // provides empty strings for empty options - buf = strdup("liminix rootfstype= fw_devlink=off root= altroot= /dev/hda1"); + buf = strdup("liminix rootfstype= fw_devlink=off root= rootalt= /dev/hda1"); memset(&opts, '\0', sizeof opts); parseopts(buf, &opts); if(strlen(opts.fstype)) die("expected empty rootfstype, got \"%s\"", opts.fstype); if(strlen(opts.device)) die("expected empty root, got \"%s\"", opts.device); - if(strlen(opts.altdevice)) die("expected empty altroot, got \"%s\"", opts.altdevice); + if(strlen(opts.altdevice)) die("expected empty rootalt, got \"%s\"", opts.altdevice); expect_equal("01", pr_u32(1)); expect_equal("ab", pr_u32(0xab)); diff --git a/pkgs/preinit/preinit.c b/pkgs/preinit/preinit.c index 7dd896b..c9a1821 100644 --- a/pkgs/preinit/preinit.c +++ b/pkgs/preinit/preinit.c @@ -4,9 +4,13 @@ #include #include #include +#include #include #include #include +#include + +#include #include /* for COMMAND_LINE_SIZE */ @@ -44,6 +48,25 @@ static int fork_exec(char * command, char *args[]) return execve(command, args, NULL); } +static void debug_listdir(const char * path) +{ + DIR *mydir; + struct dirent *myfile; + struct stat mystat; + + char buf[512]; + mydir = opendir(path); + while((myfile = readdir(mydir)) != NULL) + { + sprintf(buf, "%s/%s", path, myfile->d_name); + stat(buf, &mystat); + printf("%llu", mystat.st_size); + printf(" %s\n", myfile->d_name); + } + closedir(mydir); + +} + char banner[] = "Running pre-init...\n"; char buf[COMMAND_LINE_SIZE]; @@ -98,6 +121,7 @@ int main(int argc, char *argv[], char *envp[]) AVER(mount(opts.device, "/target/persist", opts.fstype, 0, opts.mount_opts)); } else { if(mount(opts.device, "/target/persist", opts.fstype, 0, opts.mount_opts) < 0) { + ERR("failed to mount primary device, mount the alternative device\n"); AVER(mount(opts.altdevice, "/target/persist", opts.fstype, 0, opts.mount_opts)); } } @@ -107,7 +131,12 @@ int main(int argc, char *argv[], char *envp[]) "bind", MS_BIND, NULL)); char *exec_args[] = { "activate", "/target", NULL }; - AVER(fork_exec("/target/persist/activate", exec_args)); + if (fork_exec("/target/persist/activate", exec_args) < 0) { + ERR("failed to activate the system\n"); + pr_u32(errno); ERR ( " - "); ERR(strerror(errno)); ERR("\n"); + goto failsafe; + } + AVER(chdir("/target")); AVER(mount("/target", "/", "bind", MS_BIND | MS_REC, NULL)); @@ -118,5 +147,23 @@ int main(int argc, char *argv[], char *envp[]) AVER(execve("/persist/init", argv, envp)); } + +failsafe: + debug_listdir("/"); + debug_listdir("/target"); + + ERR("failed to mount the rootfs\n"); + ERR("final stand using the failsafe initialization method\n"); + ERR("the boot process is manual from now on\n"); + + argv[0] = "init"; + argv[1] = NULL; + // Attempt to unmount the /target mount-bind. + AVER(umount("/target")); + AVER(execve("/failsafe-init", argv, envp)); + + debug_listdir("/"); + debug_listdir("/target"); + die(); } diff --git a/pkgs/run-liminix-vm/default.nix b/pkgs/run-liminix-vm/default.nix index 1bf0389..f3df1e1 100644 --- a/pkgs/run-liminix-vm/default.nix +++ b/pkgs/run-liminix-vm/default.nix @@ -2,14 +2,15 @@ qemuLim , socat , writeShellScript -, writeFennel , runCommand +, fennel , lib , lua , pkgsBuildBuild }: let - run-liminix-vm = pkgsBuildBuild.writeFennel "run-liminix-vm" { - packages = [ qemuLim pkgsBuildBuild.lua.pkgs.luaposix pkgsBuildBuild.lua.pkgs.fennel ]; + writeFennel = pkgsBuildBuild.writeFennel.override { inherit lua; }; + run-liminix-vm = writeFennel "run-liminix-vm" { + packages = [ qemuLim lua.pkgs.luaposix fennel ]; } ./run-liminix-vm.fnl; connect = writeShellScript "connect-vm" '' export PATH="${lib.makeBinPath [socat]}:$PATH" diff --git a/pkgs/ubus/default.nix b/pkgs/ubus/default.nix new file mode 100644 index 0000000..fa063bf --- /dev/null +++ b/pkgs/ubus/default.nix @@ -0,0 +1,35 @@ +{ stdenv, fetchFromGitea, lib, cmake, libubox, json_c, lua, defaultSocketLocation ? "/run/ubus/ubus.sock" }: +stdenv.mkDerivation { + pname = "ubus"; + version = "unstable-04-09-2024"; + + src = fetchFromGitea { + domain = "git.dgnum.eu"; + owner = "DGNum"; + repo = "ubus"; + rev = "ebb1dc92e4985538a8e18b7e926264118138f281"; + hash = "sha256-fo4zleC9R6uzlcOJ/jQ0t0nSBHUAq/uqPVd9xJdkAM0="; + }; + + # We don't use /var/run/ in Liminix by default. + postPatch = '' + substituteInPlace CMakeLists.txt \ + --replace-fail "/var/run/ubus/ubus.sock" "${defaultSocketLocation}" + ''; + + nativeBuildInputs = [ + cmake + ]; + + buildInputs = [ + lua + libubox + json_c + ]; + + cmakeFlags = [ + "-DBUILD_LUA=on" + "-DLUAPATH=${placeholder "out"}/lib/lua/${lua.luaversion}" + "-DBUILD_EXAMPLES=off" + ]; +} diff --git a/pkgs/write-fennel/default.nix b/pkgs/write-fennel/default.nix index 67d1908..e72762d 100644 --- a/pkgs/write-fennel/default.nix +++ b/pkgs/write-fennel/default.nix @@ -27,7 +27,7 @@ name : echo "#!${lua}/bin/lua ${luaFlags}" echo "package.path = ${lib.strings.escapeShellArg (builtins.concatStringsSep "" luapath)} .. package.path" echo "package.cpath = ${lib.strings.escapeShellArg (builtins.concatStringsSep "" luacpath)} .. package.cpath" - echo "local ok, stdlib = pcall(require,'posix.stdlib'); if ok then stdlib.setenv('PATH',${lib.escapeShellArg (lib.makeBinPath packages)} .. \":\" .. os.getenv('PATH')) end" + echo "local ok, stdlib = pcall(require,'posix.stdlib'); if ok then stdlib.setenv('PATH', \"${lib.makeBinPath packages}\" .. \":\" .. os.getenv('PATH')) end" fennel ${if correlate then "--correlate" else ""} --compile ${source} ) > ${name}.lua ''; diff --git a/tests/ci.nix b/tests/ci.nix index 4fbd953..1334885 100644 --- a/tests/ci.nix +++ b/tests/ci.nix @@ -10,4 +10,5 @@ tftpboot = import ./tftpboot/test.nix; updown = import ./updown/test.nix; inout = import ./inout/test.nix; + custom-shell = import ./custom-shell/test.nix; } diff --git a/tests/custom-shell/check-prompt.expect b/tests/custom-shell/check-prompt.expect new file mode 100644 index 0000000..4cc7656 --- /dev/null +++ b/tests/custom-shell/check-prompt.expect @@ -0,0 +1,7 @@ +set timeout 60 + +spawn socat unix-connect:vm/console - +expect { + "root@liminix blah blah > " { exit 0 } + timeout { exit 1 } +} diff --git a/tests/custom-shell/configuration.nix b/tests/custom-shell/configuration.nix new file mode 100644 index 0000000..b908137 --- /dev/null +++ b/tests/custom-shell/configuration.nix @@ -0,0 +1,13 @@ +{ config, pkgs, lib, ... } : +let + inherit (pkgs.liminix.networking) interface address hostapd route dnsmasq; + inherit (pkgs.liminix.services) oneshot longrun bundle target; +in rec { + imports = [ + ../../modules/network + ]; + + defaultProfile.prompt = "$(whoami)@$(hostname) blah blah > "; + + defaultProfile.packages = with pkgs; [ ]; +} diff --git a/tests/custom-shell/test.nix b/tests/custom-shell/test.nix new file mode 100644 index 0000000..e5273e1 --- /dev/null +++ b/tests/custom-shell/test.nix @@ -0,0 +1,21 @@ +{ + liminix +, nixpkgs +}: +let img = (import liminix { + inherit nixpkgs; + device = import "${liminix}/devices/qemu/"; + liminix-config = ./configuration.nix; + }).outputs.default; + pkgs = import nixpkgs { overlays = [(import ../../overlay.nix)]; }; +in pkgs.runCommand "check" { + nativeBuildInputs = with pkgs; [ + expect socat + ] ; +} '' +. ${../test-helpers.sh} + +mkdir vm +${img}/run.sh --background ./vm +expect ${./check-prompt.expect} |tee output && mv output $out +'' diff --git a/tests/ext4/test.nix b/tests/ext4/test.nix index f972583..5c5a582 100644 --- a/tests/ext4/test.nix +++ b/tests/ext4/test.nix @@ -6,7 +6,7 @@ let img = (import liminix { device = import "${liminix}/devices/qemu/"; liminix-config = ./configuration.nix; }).outputs.vmroot; - pkgs = import { overlays = [(import ../../overlay.nix)]; }; + pkgs = import nixpkgs { overlays = [(import ../../overlay.nix)]; }; in pkgs.runCommand "check" { nativeBuildInputs = with pkgs; [ expect diff --git a/tests/fennel/test.nix b/tests/fennel/test.nix index fda2a89..81206d9 100644 --- a/tests/fennel/test.nix +++ b/tests/fennel/test.nix @@ -4,7 +4,7 @@ }: let overlay = import "${liminix}/overlay.nix"; - pkgs = import { overlays = [overlay]; }; + pkgs = import nixpkgs { overlays = [overlay]; }; script = pkgs.writeFennelScript "foo" [] ./hello.fnl; inherit (pkgs.lua.pkgs) fifo; netlink = pkgs.netlink-lua; diff --git a/tests/inout/test.nix b/tests/inout/test.nix index 5f382e4..3f3b198 100644 --- a/tests/inout/test.nix +++ b/tests/inout/test.nix @@ -6,7 +6,7 @@ let img = (import liminix { device = import "${liminix}/devices/qemu/"; liminix-config = ./configuration.nix; }).outputs.vmroot; - pkgs = import { overlays = [(import ../../overlay.nix)]; }; + pkgs = import nixpkgs { overlays = [(import ../../overlay.nix)]; }; in pkgs.runCommand "check" { nativeBuildInputs = with pkgs; [ expect diff --git a/tests/jffs2/configuration.nix b/tests/jffs2/configuration.nix index 2515fbf..ea7670b 100644 --- a/tests/jffs2/configuration.nix +++ b/tests/jffs2/configuration.nix @@ -5,7 +5,6 @@ in { imports = [ ../../vanilla-configuration.nix ../../modules/squashfs.nix - ../../modules/outputs/jffs2.nix ]; config.rootfsType = "jffs2"; config.filesystem = dir { diff --git a/tests/jffs2/test.nix b/tests/jffs2/test.nix index f972583..5c5a582 100644 --- a/tests/jffs2/test.nix +++ b/tests/jffs2/test.nix @@ -6,7 +6,7 @@ let img = (import liminix { device = import "${liminix}/devices/qemu/"; liminix-config = ./configuration.nix; }).outputs.vmroot; - pkgs = import { overlays = [(import ../../overlay.nix)]; }; + pkgs = import nixpkgs { overlays = [(import ../../overlay.nix)]; }; in pkgs.runCommand "check" { nativeBuildInputs = with pkgs; [ expect diff --git a/tests/min-copy-closure/configuration.nix b/tests/min-copy-closure/configuration.nix index fe80bf2..0797341 100644 --- a/tests/min-copy-closure/configuration.nix +++ b/tests/min-copy-closure/configuration.nix @@ -13,7 +13,6 @@ let in { imports = [ ../../vanilla-configuration.nix - ../../modules/outputs/jffs2.nix ]; config = { services.sshd = longrun { diff --git a/tests/min-copy-closure/test.nix b/tests/min-copy-closure/test.nix index 14f0225..774b882 100644 --- a/tests/min-copy-closure/test.nix +++ b/tests/min-copy-closure/test.nix @@ -8,7 +8,7 @@ let lmx = (import liminix { }); rogue = lmx.pkgs.rogue; img = lmx.outputs.vmroot; - pkgs = import { overlays = [(import ../../overlay.nix)]; }; + pkgs = import nixpkgs { overlays = [(import ../../overlay.nix)]; }; in pkgs.runCommand "check" { nativeBuildInputs = with pkgs; [ expect diff --git a/tests/pppoe/test.nix b/tests/pppoe/test.nix index c8007a9..6972256 100644 --- a/tests/pppoe/test.nix +++ b/tests/pppoe/test.nix @@ -6,7 +6,7 @@ let img = (import liminix { device = import "${liminix}/devices/qemu"; liminix-config = ./configuration.nix; }).outputs.default; - pkgs = import { overlays = [(import ../../overlay.nix)]; }; + pkgs = import nixpkgs { overlays = [(import ../../overlay.nix)]; }; inherit (pkgs.pkgsBuildBuild) routeros; in pkgs.runCommand "check" { nativeBuildInputs = with pkgs; [ diff --git a/tests/tftpboot/test.nix b/tests/tftpboot/test.nix index b7a3e87..b6b9f1a 100644 --- a/tests/tftpboot/test.nix +++ b/tests/tftpboot/test.nix @@ -1,5 +1,6 @@ { - liminix + liminix, + ... }: let check = deviceName : config : let derivation = (import liminix { diff --git a/tests/updown/test.nix b/tests/updown/test.nix index 589e383..45f7c91 100644 --- a/tests/updown/test.nix +++ b/tests/updown/test.nix @@ -6,7 +6,7 @@ let img = (import liminix { device = import "${liminix}/devices/qemu/"; liminix-config = ./configuration.nix; }).outputs.vmroot; - pkgs = import { overlays = [(import ../../overlay.nix)]; }; + pkgs = import nixpkgs { overlays = [(import ../../overlay.nix)]; }; in pkgs.runCommand "check" { nativeBuildInputs = with pkgs; [ expect diff --git a/tests/wlan/configuration.nix b/tests/wlan/configuration.nix index 6dc4b44..ed1d948 100644 --- a/tests/wlan/configuration.nix +++ b/tests/wlan/configuration.nix @@ -7,6 +7,7 @@ in rec { ../../modules/wlan.nix ../../modules/hostapd ../../modules/network + ./wpa_supplicant.nix ]; services.hostap = config.system.service.hostapd.build { @@ -27,5 +28,21 @@ in rec { }; }; - defaultProfile.packages = with pkgs; [ tcpdump ] ; + services.wpa_supplicant = config.system.service.wpa_supplicant.build { + interface = "wlan1"; + driver = "nl80211"; + config-file = pkgs.writeText "wpa_supplicant.conf" '' + country=us + update_config=1 + ctrl_interface=/run/wpa_supplicant + + network={ + scan_ssid=1 + ssid="liminix" + psk="colourless green ideas" + } + ''; + }; + + defaultProfile.packages = with pkgs; [ tcpdump wpa_supplicant ]; } diff --git a/tests/wlan/test.nix b/tests/wlan/test.nix index fcc2715..f1f20c2 100644 --- a/tests/wlan/test.nix +++ b/tests/wlan/test.nix @@ -3,10 +3,11 @@ , nixpkgs }: let img = (import liminix { - device = import "${liminix}/devices/qemu-armv7l/"; + inherit nixpkgs; + device = import "${liminix}/devices/qemu/"; liminix-config = ./configuration.nix; }).outputs.default; - pkgs = import { overlays = [(import ../../overlay.nix)]; }; + pkgs = import nixpkgs { overlays = [(import ../../overlay.nix)]; }; in pkgs.runCommand "check" { nativeBuildInputs = with pkgs; [ expect socat diff --git a/tests/wlan/wait-for-wlan.expect b/tests/wlan/wait-for-wlan.expect index 1d42a4e..5e0f073 100644 --- a/tests/wlan/wait-for-wlan.expect +++ b/tests/wlan/wait-for-wlan.expect @@ -14,10 +14,10 @@ expect { } expect "#" while { $FINISHED < 10 } { - send "date && grep AP-ENABLED /run/uncaught-logs/* || echo \$NOT\r\n" + send "date && grep CTRL-EVENT-CONNECTED /run/uncaught-logs/* || echo \$NOT\r\n" expect { - "wlan0: AP-ENABLED" { set FINISHED 999; set EXIT 0; } + "wlan1: CTRL-EVENT-CONNECTED" { set FINISHED 999; set EXIT 0; } "not_present" { send_user "waiting ...\n" ; sleep 5 } } set FINISHED [ expr $FINISHED + 1 ] diff --git a/tests/wlan/wpa_service.nix b/tests/wlan/wpa_service.nix new file mode 100644 index 0000000..ae70db3 --- /dev/null +++ b/tests/wlan/wpa_service.nix @@ -0,0 +1,21 @@ +{ + liminix, + wpa_supplicant, + lib, +}: +{ + interface, + driver, + config-file, +}: +let + inherit (liminix.services) longrun; + inherit (lib.strings) escapeShellArg; +in +longrun { + name = "wpa_supplicant"; + run = + '' + ${wpa_supplicant}/bin/wpa_supplicant -D${driver} -i${interface} -c ${config-file} + ''; +} diff --git a/tests/wlan/wpa_supplicant.nix b/tests/wlan/wpa_supplicant.nix new file mode 100644 index 0000000..98f7f08 --- /dev/null +++ b/tests/wlan/wpa_supplicant.nix @@ -0,0 +1,15 @@ +{ config, lib, pkgs, ... }: +with lib; { + options.system.service.wpa_supplicant = mkOption { type = pkgs.liminix.lib.types.serviceDefn; }; + config.system.service.wpa_supplicant = config.system.callService ./wpa_service.nix { + interface = mkOption { + type = types.str; + }; + driver = mkOption { + type = types.str; + }; + config-file = mkOption { + type = types.package; + }; + }; +}