## Base options ## ============ { lib, pkgs, config, ...}: let 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 { options = { defaultProfile = { packages = mkOption { type = types.listOf types.package; description = '' List of packages which are available in a login shell. (This is analogous to systemPackages in NixOS, but we don't symlink into /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; }; system.callService = mkOption { type = types.functionTo (types.functionTo types.anything); }; filesystem = mkOption { type = types.anything; description = '' Skeleton filesystem, represented as nested attrset. Consult the source code if you need to add to this ''; # internal = true; # probably a good case to make this internal }; rootfsType = mkOption { default = "squashfs"; type = types.enum [ "btrfs" "ext4" "jffs2" "squashfs" "ubifs" ]; }; rootOptions = mkOption { type = types.nullOr types.str; default = null; }; boot = { commandLine = mkOption { type = types.listOf types.nonEmptyStr; default = []; description = "Kernel command line"; }; commandLineDtbNode = mkOption { type = types.enum [ "bootargs" "bootargs-override" ]; default = "bootargs"; description = "Kernel command line's devicetree node"; }; imageFormat = mkOption { type = types.enum ["fit" "uimage"]; 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 = '' RAM address at which to load data when transferring via TFTP. This is not the address of the flash storage, nor the kernel load address: it should be set to some part of RAM that's not used for anything else and suitable for temporary storage. ''; }; # These names match the uboot environment variables. I reserve # the right to change them if I think of better ones. ipaddr = mkOption { type = types.str; description = '' Our IP address to use when creating scripts to boot or flash from U-Boot. Not relevant in normal operation ''; }; serverip = mkOption { type = types.str; description = '' IP address of the TFTP server. Not relevant in normal operation ''; }; }; }; }; 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 iproute2 ]; # 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" "root=${config.hardware.rootDevice}" "rootfstype=${config.rootfsType}" "fw_devlink=off" ] ++ (map (mtd: "ubi.mtd=${mtd}") config.hardware.ubi.mtds) ++ lib.optional (config.rootOptions != null) "rootflags=${config.rootOptions}" ++ 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 typeChecked = caller: type: value: let inherit (lib) types mergeDefinitions; defs = [{ file = caller; inherit value; }]; type' = types.submodule { options = type; }; in (mergeDefinitions [] type' defs).mergedValue; cp = lib.callPackageWith(pkgs // { svc = config.system.service; }); pkg = cp path {}; checkTypes = t : p : typeChecked (builtins.toString path) t p; in { inherit parameters; build = { dependencies ? [], ... } @ args : let s = pkg (checkTypes parameters (builtins.removeAttrs args ["dependencies"])); in s.overrideAttrs (o: { dependencies = (builtins.map (d: d.name) dependencies) ++ o.dependencies; buildInputs = dependencies ++ o.buildInputs; }); }; users.root = { uid = 0; gid= 0; gecos = "Root of all evaluation"; dir = "/home/root/"; passwd = lib.mkDefault ""; shell = "/bin/sh"; }; groups = { root = { gid = 0; usernames = ["root"]; }; system = { gid = 1; usernames = ["root"]; }; }; filesystem = dir { dev = let node = type: major: minor: mode : { inherit type major minor mode; }; in dir { null = node "c" "1" "3" "0666"; zero = node "c" "1" "5" "0666"; tty = node "c" "5" "0" "0666"; console = node "c" "5" "1" "0600"; pts = dir {}; }; etc = let profile = symlink (pkgs.writeScript ".profile" '' PATH=${lib.makeBinPath config.defaultProfile.packages}:/bin export PATH ${concatStringsSep "\n" (mapAttrsToList exportVar config.defaultProfile.environmentVariables)} ''); in dir { inherit profile; ashrc = profile; }; proc = dir {}; run = dir {}; sys = dir {}; tmp = dir {}; }; }; }