diff --git a/nix/configuration/roles/distributed_build/default.nix b/nix/configuration/roles/distributed_build/default.nix index 6e20734..8266c23 100644 --- a/nix/configuration/roles/distributed_build/default.nix +++ b/nix/configuration/roles/distributed_build/default.nix @@ -87,6 +87,9 @@ in ]; maxJobs = 1; supportedFeatures = [ + "gccarch-armv6" + "gccarch-aarch64" + "gccarch-riscv64" "nixos-test" "benchmark" "big-parallel" diff --git a/nix/configuration/roles/emulate_isa/default.nix b/nix/configuration/roles/emulate_isa/default.nix index 183eb4e..477b6d2 100644 --- a/nix/configuration/roles/emulate_isa/default.nix +++ b/nix/configuration/roles/emulate_isa/default.nix @@ -26,6 +26,14 @@ # TODO: Should "x86_64-linux" be in this list or should this list be dependent on the host CPU? "armv6l-linux" # Raspberry Pi gen 1 ]; + + me.optimizations = { + system_features = [ + "gccarch-armv6" + "gccarch-aarch64" + "gccarch-riscv64" + ]; + }; } ] ); diff --git a/nix/yubipi/.gitignore b/nix/yubipi/.gitignore new file mode 100644 index 0000000..b2be92b --- /dev/null +++ b/nix/yubipi/.gitignore @@ -0,0 +1 @@ +result diff --git a/nix/yubipi/configuration.nix b/nix/yubipi/configuration.nix new file mode 100644 index 0000000..3151815 --- /dev/null +++ b/nix/yubipi/configuration.nix @@ -0,0 +1,177 @@ +{ + config, + lib, + pkgs, + modulesPath, + ... +}: + +{ + imports = [ + "${modulesPath}/installer/sd-card/sd-image.nix" + ./roles/image_based_appliance + ./roles/optimized_build + ./roles/raspberry_pi_sd_image + ./roles/reset + # ./util/install_files + ./util/unfree_polyfill + ]; + + nix.settings.experimental-features = [ + "nix-command" + "flakes" + ]; + nix.settings.trusted-users = [ "@wheel" ]; + + hardware.enableRedistributableFirmware = true; + + # Keep outputs so we can build offline. + nix.extraOptions = '' + keep-outputs = true + keep-derivations = true + substitute = false + ''; + + # Technically only needed when building the ISO because nix detects ZFS in the filesystem list normally. I basically always want this so I'm just setting it to always be on. + boot.supportedFilesystems.zfs = true; + # TODO: Is this different from boot.supportedFilesystems = [ "zfs" ]; ? + + services.getty = { + autologinUser = "talexander"; + autologinOnce = true; + }; + users.mutableUsers = false; + users.users.talexander = { + isNormalUser = true; + createHome = true; # https://github.com/NixOS/nixpkgs/issues/6481 + group = "talexander"; + extraGroups = [ "wheel" ]; + uid = 11235; + packages = with pkgs; [ + tree + ]; + # Generate with `mkpasswd -m scrypt` + hashedPassword = "$7$CU..../....VXvNQ8za3wSGpdzGXNT50/$HcFtn/yvwPMCw4888BelpiAPLAxe/zU87fD.d/N6U48"; + openssh.authorizedKeys.keys = [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID0+4zi26M3eYWnIrciR54kOlGxzfgCXG+o4ea1zpzrk openpgp:0x7FF123C8" + "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIEI6mu6I5Jp+Ib0vJxapGHbEShZjyvzV8jz5DnzDrI39AAAABHNzaDo=" + "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIAFNcSXwvy+brYTOGo56G93Ptuq2MmZsjvRWAfMqbmMLAAAABHNzaDo=" + ]; + }; + users.groups.talexander.gid = 11235; + + # Automatic garbage collection + nix.gc = lib.mkIf (!config.me.image_based_appliance.enable) { + # Runs nix-collect-garbage --delete-older-than 5d + automatic = true; + persistent = true; + dates = "monthly"; + # randomizedDelaySec = "14m"; + options = "--delete-older-than 30d"; + }; + nix.settings.auto-optimise-store = true; + nix.settings.substituters = lib.mkForce [ ]; + + # Use doas instead of sudo + security.doas.enable = true; + security.doas.wheelNeedsPassword = false; + security.sudo.enable = false; + security.doas.extraRules = [ + { + # Retain environment (for example NIX_PATH) + keepEnv = true; + persist = true; # Only ask for a password the first time. + } + ]; + + environment.systemPackages = with pkgs; [ + # wget + # mg + # rsync + # libinput + # htop + # tmux + # file + # usbutils # for lsusb + # pciutils # for lspci + # ripgrep + # strace + # # ltrace # Disabled because it uses more than 48GB of /tmp space during test phase. + # trace-cmd # ftrace + # tcpdump + # git-crypt + # gnumake + # ncdu + # nix-tree + # libarchive # bsdtar + # lsof + # doas-sudo-shim # To support --sudo for remote builds + # dmidecode # Read SMBIOS information. + # ipcalc + # gptfdisk # for cgdisk + # nix-output-monitor # For better view into nixos-rebuild + # nix-serve-ng # Serve nix store over http + ]; + + services.openssh = { + enable = true; + settings = { + PasswordAuthentication = false; + KbdInteractiveAuthentication = false; + }; + hostKeys = [ + { + path = "/persist/ssh/ssh_host_ed25519_key"; + type = "ed25519"; + } + { + path = "/persist/ssh/ssh_host_rsa_key"; + type = "rsa"; + bits = 4096; + } + ]; + }; + + boot.initrd.kernelModules = [ + # "vc4" + # "bcm2835_dma" + # "i2c_bcm2835" + ]; + # Compressing through emulation is slow and we're just going to decompress the image anyway. + sdImage.compressImage = false; + + # Write a list of the currently installed packages to /etc/current-system-packages + environment.etc."current-system-packages".text = + let + packages = builtins.map (p: "${p.name}") config.environment.systemPackages; + sortedUnique = builtins.sort builtins.lessThan (lib.unique packages); + formatted = builtins.concatStringsSep "\n" sortedUnique; + in + formatted; + + nixpkgs.overlays = [ + (final: prev: { + efivar = throw "foo"; + }) + ]; + + # This option defines the first version of NixOS you have installed on this particular machine, + # and is used to maintain compatibility with application data (e.g. databases) created on older NixOS versions. + # + # Most users should NEVER change this value after the initial install, for any reason, + # even if you've upgraded your system to a new NixOS release. + # + # This value does NOT affect the Nixpkgs version your packages and OS are pulled from, + # so changing it will NOT upgrade your system - see https://nixos.org/manual/nixos/stable/#sec-upgrading for how + # to actually do that. + # + # This value being lower than the current NixOS release does NOT mean your system is + # out of date, out of support, or vulnerable. + # + # Do NOT change this value unless you have manually inspected all the changes it would make to your configuration, + # and migrated your data accordingly. + # + # For more information, see `man configuration.nix` or https://nixos.org/manual/nixos/stable/options#opt-system.stateVersion . + system.stateVersion = "25.11"; # Did you read the comment? + +} diff --git a/nix/yubipi/flake.lock b/nix/yubipi/flake.lock new file mode 100644 index 0000000..cbf97f7 --- /dev/null +++ b/nix/yubipi/flake.lock @@ -0,0 +1,44 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1759381078, + "narHash": "sha256-gTrEEp5gEspIcCOx9PD8kMaF1iEmfBcTbO0Jag2QhQs=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "7df7ff7d8e00218376575f0acdcc5d66741351ee", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-unoptimized": { + "locked": { + "lastModified": 1759381078, + "narHash": "sha256-gTrEEp5gEspIcCOx9PD8kMaF1iEmfBcTbO0Jag2QhQs=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "7df7ff7d8e00218376575f0acdcc5d66741351ee", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs", + "nixpkgs-unoptimized": "nixpkgs-unoptimized" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/nix/yubipi/flake.nix b/nix/yubipi/flake.nix new file mode 100644 index 0000000..7663e81 --- /dev/null +++ b/nix/yubipi/flake.nix @@ -0,0 +1,43 @@ +{ + description = "My system configuration"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + nixpkgs-unoptimized.url = "github:NixOS/nixpkgs/nixos-unstable"; + }; + + outputs = + { + self, + nixpkgs, + nixpkgs-unoptimized, + ... + }@inputs: + let + base_armv6l_linux = rec { + system = "armv6l-linux-linux"; + specialArgs = { + pkgs-unoptimized = import nixpkgs-unoptimized { + inherit system; + hostPlatform.gcc.arch = "default"; + hostPlatform.gcc.tune = "default"; + }; + }; + modules = [ + ./configuration.nix + ]; + }; + systems = { + yubipi = rec { + main = base_armv6l_linux // { + modules = base_armv6l_linux.modules ++ [ + ./hosts/yubipi + ]; + }; + }; + }; + in + { + nixosConfigurations.yubipi = nixpkgs.lib.nixosSystem systems.yubipi.main; + }; +} diff --git a/nix/yubipi/hosts/yubipi/ISO b/nix/yubipi/hosts/yubipi/ISO new file mode 100755 index 0000000..e2a2887 --- /dev/null +++ b/nix/yubipi/hosts/yubipi/ISO @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +# +set -euo pipefail +IFS=$'\n\t' +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +: "${JOBS:="1"}" + +nix build --extra-experimental-features nix-command --extra-experimental-features flakes "$DIR/../..#nixosConfigurations.yubipi.config.system.build.sdImage" --max-jobs "$JOBS" --log-format internal-json -v "${@}" |& nom --json diff --git a/nix/yubipi/hosts/yubipi/default.nix b/nix/yubipi/hosts/yubipi/default.nix new file mode 100644 index 0000000..98fbb76 --- /dev/null +++ b/nix/yubipi/hosts/yubipi/default.nix @@ -0,0 +1,46 @@ +{ + config, + lib, + pkgs, + ... +}: +{ + imports = [ + ./hardware-configuration.nix + ./wrapped-disk-config.nix + ]; + + config = { + # Generate with `head -c4 /dev/urandom | od -A none -t x4` + networking.hostId = "61f81c12"; + + networking.hostName = "yubipi"; # Define your hostname. + + time.timeZone = "America/New_York"; + i18n.defaultLocale = "en_US.UTF-8"; + + me.optimizations = { + enable = true; + arch = "armv6"; + system_features = [ + "gccarch-armv6l" + "benchmark" + "big-parallel" + "kvm" + "nixos-test" + ]; + }; + + # Early KMS + boot.initrd.kernelModules = [ ]; + + # Mount tmpfs at /tmp + boot.tmp.useTmpfs = true; + + # Enable TRIM + services.fstrim.enable = lib.mkDefault true; + + me.image_based_appliance.enable = true; + me.raspberry_pi_sd_image.enable = true; + }; +} diff --git a/nix/yubipi/hosts/yubipi/disk-config.nix b/nix/yubipi/hosts/yubipi/disk-config.nix new file mode 100644 index 0000000..82b4682 --- /dev/null +++ b/nix/yubipi/hosts/yubipi/disk-config.nix @@ -0,0 +1,12 @@ +{ + fileSystems = { + "/" = { + device = "/dev/disk/by-label/NIXOS_SD"; + fsType = "ext4"; + options = [ + "noatime" + "norelatime" + ]; + }; + }; +} diff --git a/nix/yubipi/hosts/yubipi/hardware-configuration.nix b/nix/yubipi/hosts/yubipi/hardware-configuration.nix new file mode 100644 index 0000000..adf7d70 --- /dev/null +++ b/nix/yubipi/hosts/yubipi/hardware-configuration.nix @@ -0,0 +1,28 @@ +# Do not modify this file! It was generated by ‘nixos-generate-config’ +# and may be overwritten by future invocations. Please make changes +# to /etc/nixos/configuration.nix instead. +{ + config, + lib, + pkgs, + modulesPath, + ... +}: + +{ + imports = [ + (modulesPath + "/installer/scan/not-detected.nix") + ]; + + boot.initrd.availableKernelModules = [ + "nvme" + "xhci_pci" + "thunderbolt" + ]; + boot.initrd.kernelModules = [ ]; + boot.kernelModules = [ ]; + boot.extraModulePackages = [ ]; + + nixpkgs.hostPlatform = lib.mkDefault "armv6l-linux"; + hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; +} diff --git a/nix/yubipi/hosts/yubipi/wrapped-disk-config.nix b/nix/yubipi/hosts/yubipi/wrapped-disk-config.nix new file mode 100644 index 0000000..6810d7e --- /dev/null +++ b/nix/yubipi/hosts/yubipi/wrapped-disk-config.nix @@ -0,0 +1,8 @@ +{ + config, + lib, + pkgs, + ... +}: + +(import ./disk-config.nix) diff --git a/nix/yubipi/roles/blank/default.nix b/nix/yubipi/roles/blank/default.nix new file mode 100644 index 0000000..d38650e --- /dev/null +++ b/nix/yubipi/roles/blank/default.nix @@ -0,0 +1,30 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + imports = [ ]; + + options.me = { + blank.enable = lib.mkOption { + type = lib.types.bool; + default = false; + example = true; + description = "Whether we want to install blank."; + }; + }; + + config = lib.mkIf config.me.blank.enable ( + lib.mkMerge [ + { + environment.systemPackages = with pkgs; [ + ]; + } + (lib.mkIf config.me.graphical { + }) + ] + ); +} diff --git a/nix/yubipi/roles/image_based_appliance/default.nix b/nix/yubipi/roles/image_based_appliance/default.nix new file mode 100644 index 0000000..292ae21 --- /dev/null +++ b/nix/yubipi/roles/image_based_appliance/default.nix @@ -0,0 +1,30 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + imports = [ ]; + + options.me = { + image_based_appliance.enable = lib.mkOption { + type = lib.types.bool; + default = false; + example = true; + description = "Whether we want to install image_based_appliance."; + }; + }; + + config = lib.mkIf config.me.image_based_appliance.enable ( + lib.mkMerge [ + { + # Do not install nix. A full new image must be built to update + # the machine. + nix.enable = false; + system.switch.enable = false; + } + ] + ); +} diff --git a/nix/yubipi/roles/optimized_build/default.nix b/nix/yubipi/roles/optimized_build/default.nix new file mode 100644 index 0000000..439de4e --- /dev/null +++ b/nix/yubipi/roles/optimized_build/default.nix @@ -0,0 +1,78 @@ +{ + config, + lib, + pkgs, + pkgs-unoptimized, + ... +}: + +{ + imports = [ ]; + + options.me = { + optimizations.enable = lib.mkOption { + type = lib.types.bool; + default = false; + example = true; + description = "Whether we want to enable CPU optimizations (will trigger a rebuild from source)."; + }; + + optimizations.arch = lib.mkOption { + type = lib.types.str; + default = null; + example = "znver4"; + description = "The CPU arch for which programs should be optimized."; + }; + + optimizations.system_features = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + example = [ + "gccarch-armv6l" + "benchmark" + "big-parallel" + "kvm" + "nixos-test" + ]; + description = "The list of CPU features that should be enabled on this machine."; + }; + }; + + config = lib.mkMerge [ + (lib.mkIf (!config.me.optimizations.enable) ( + lib.mkMerge [ + { + } + ] + )) + (lib.mkIf config.me.optimizations.enable ( + lib.mkMerge [ + { + nixpkgs.config.allowUnsupportedSystem = true; + + nixpkgs.hostPlatform = { + gcc.arch = config.me.optimizations.arch; + gcc.tune = config.me.optimizations.arch; + system = "armv6l-linux"; + }; + + # Uncomment on of these to enable cross compiling: + # nixpkgs.buildPlatform = builtins.currentSystem; + # nixpkgs.buildPlatform = { + # gcc.arch = "znver4"; + # gcc.tune = "znver4"; + # system = "x86_64-linux"; + # }; + } + ] + )) + (lib.mkIf (config.me.optimizations.system_features != [ ]) ( + lib.mkMerge [ + { + nix.settings.system-features = lib.mkForce config.me.optimizations.system_features; + } + ] + )) + + ]; +} diff --git a/nix/yubipi/roles/raspberry_pi_sd_image/default.nix b/nix/yubipi/roles/raspberry_pi_sd_image/default.nix new file mode 100644 index 0000000..7d222a0 --- /dev/null +++ b/nix/yubipi/roles/raspberry_pi_sd_image/default.nix @@ -0,0 +1,62 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + imports = [ ]; + + options.me = { + raspberry_pi_sd_image.enable = lib.mkOption { + type = lib.types.bool; + default = false; + example = true; + description = "Whether we want to install raspberry_pi_sd_image."; + }; + }; + + config = lib.mkIf config.me.raspberry_pi_sd_image.enable ( + lib.mkMerge [ + { + boot.loader.grub.enable = false; + boot.loader.generic-extlinux-compatible.enable = true; + + boot.consoleLogLevel = lib.mkDefault 7; + boot.kernelPackages = pkgs.linuxKernel.packages.linux_rpi1; + + sdImage = { + populateFirmwareCommands = + let + configTxt = pkgs.writeText "config.txt" '' + # u-boot refuses to start (gets stuck at rainbow polygon) without this, + # at least on Raspberry Pi 0. + enable_uart=1 + + # Prevent the firmware from smashing the framebuffer setup done by the mainline kernel + # when attempting to show low-voltage or overtemperature warnings. + avoid_warnings=1 + + [pi0] + kernel=u-boot-rpi0.bin + + [pi1] + kernel=u-boot-rpi1.bin + ''; + in + '' + (cd ${pkgs.raspberrypifw}/share/raspberrypi/boot && cp bootcode.bin fixup*.dat start*.elf *.dtb $NIX_BUILD_TOP/firmware/) + cp ${pkgs.ubootRaspberryPiZero}/u-boot.bin firmware/u-boot-rpi0.bin + cp ${pkgs.ubootRaspberryPi}/u-boot.bin firmware/u-boot-rpi1.bin + cp ${configTxt} firmware/config.txt + ''; + populateRootCommands = '' + mkdir -p ./files/boot + ${config.boot.loader.generic-extlinux-compatible.populateCmd} -c ${config.system.build.toplevel} -d ./files/boot + ''; + }; + } + ] + ); +} diff --git a/nix/yubipi/roles/reset/default.nix b/nix/yubipi/roles/reset/default.nix new file mode 100644 index 0000000..a5e5cb1 --- /dev/null +++ b/nix/yubipi/roles/reset/default.nix @@ -0,0 +1,16 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + imports = [ ]; + + # Reset some defaults to start from a minimal more-arch-linux-like state. Think of this like a CSS reset sheet. + config = { + # Do not use default packages (nixos includes some defaults like nano) + environment.defaultPackages = lib.mkForce [ ]; + }; +} diff --git a/nix/yubipi/util/install_files/default.nix b/nix/yubipi/util/install_files/default.nix new file mode 100644 index 0000000..7be496d --- /dev/null +++ b/nix/yubipi/util/install_files/default.nix @@ -0,0 +1,333 @@ +{ + config, + lib, + pkgs, + ... +}: + +let + cfg = config.me.install; + inherit (lib) + filter + attrNames + ; + + get_shell_values = + target: + let + homedir = config.users.users."${target.username}".home; + group = config.users.users."${target.username}".group; + in + { + source = lib.strings.escapeShellArg "${target.source}"; + destination = lib.strings.escapeShellArg "${homedir}/${target.target}"; + mode = lib.strings.escapeShellArg "${target.mode}"; + dir_mode = lib.strings.escapeShellArg "${target.dir_mode}"; + username = lib.strings.escapeShellArg "${target.username}"; + group = lib.strings.escapeShellArg "${group}"; + }; + install_user_file = + let + constructors = { + "overwrite" = install_user_file_overwrite; + "symlink" = install_user_file_symlink; + }; + in + stage: target: (constructors."${target.method}"."${stage}" target); + install_user_file_overwrite = { + "check" = (target: ""); + "install" = ( + target: + let + inherit (get_shell_values target) + source + destination + mode + dir_mode + username + group + ; + flags = lib.strings.concatStringsSep " " [ + (if mode != "" then "-m ${mode}" else "") + (if username != "" then "-o ${username}" else "") + (if group != "" then "-g ${group}" else "") + ]; + dir_flags = lib.strings.concatStringsSep " " [ + (if dir_mode != "" then "-m ${dir_mode}" else "") + (if username != "" then "-o ${username}" else "") + (if group != "" then "-g ${group}" else "") + ]; + in + if target.recursive then + [ + '' + find ${source} -type f -print0 | while read -r -d "" file; do + relative_path=$(realpath -s --relative-to ${source} "$file") + full_dest=${destination}/"$relative_path" + create_containing_directories "$full_dest" ${dir_flags} + $DRY_RUN_CMD install $VERBOSE_ARG --compare ${flags} "$file" "$full_dest" + done + '' + ] + else + [ + '' + create_containing_directories ${destination} ${dir_flags} + $DRY_RUN_CMD install $VERBOSE_ARG --compare ${flags} ${source} ${destination} + '' + ] + ); + "uninstall" = ( + target: + let + inherit (get_shell_values target) + source + destination + ; + in + if target.recursive then + [ + '' + find ${source} -type f -print0 | while read -r -d "" file; do + relative_path=$(realpath -s --relative-to ${source} "$file") + full_dest=${destination}/"$relative_path" + $DRY_RUN_CMD echo rm -f "$full_dest" + done + '' + ] + else + [ + '' + $DRY_RUN_CMD echo rm -f ${destination} + '' + ] + ); + }; + install_user_file_symlink = { + "check" = (target: ""); + "install" = ( + target: + let + inherit (get_shell_values target) + source + destination + mode + dir_mode + username + group + ; + owner = lib.strings.concatStringsSep ":" ( + filter (val: val != "") [ + username + group + ] + ); + dir_flags = lib.strings.concatStringsSep " " [ + (if dir_mode != "" then "-m ${dir_mode}" else "") + (if username != "" then "-o ${username}" else "") + (if group != "" then "-g ${group}" else "") + ]; + in + if target.recursive then + [ + '' + find ${source} -type f -print0 | while read -r -d "" file; do + relative_path=$(realpath -s --relative-to ${source} "$file") + full_dest=${destination}/"$relative_path" + create_containing_directories "$full_dest" ${dir_flags} + $DRY_RUN_CMD ln $VERBOSE_ARG -s "$file" "$full_dest" + $DRY_RUN_CMD chown $VERBOSE_ARG -h ${owner} "$full_dest" + done + '' + ] + else + [ + '' + create_containing_directories ${destination} ${dir_flags} + $DRY_RUN_CMD ln $VERBOSE_ARG -s ${source} ${destination} + $DRY_RUN_CMD chown $VERBOSE_ARG -h ${owner} ${destination} + '' + ] + ); + "uninstall" = ( + target: + let + inherit (get_shell_values target) + source + destination + ; + in + if target.recursive then + [ + '' + find ${source} -type f -print0 | while read -r -d "" file; do + relative_path=$(realpath -s --relative-to ${source} "$file") + full_dest=${destination}/"$relative_path" + $DRY_RUN_CMD echo rm -f "$full_dest" + done + '' + ] + else + [ + '' + $DRY_RUN_CMD echo rm -f ${destination} + '' + ] + ); + }; +in +{ + imports = [ ]; + + options.me.install = { + user = lib.mkOption { + type = lib.types.attrsOf ( + lib.types.submodule ( + { name, config, ... }: + let + username = name; + in + { + options = { + enable = lib.mkOption { + type = lib.types.bool; + default = true; + defaultText = "enable"; + example = lib.literalExpression false; + description = "Whether we want to install files in this user's home directory."; + }; + + file = lib.mkOption { + type = lib.types.attrsOf ( + lib.types.submodule ( + { name, config, ... }: + let + path = name; + in + { + options = { + enable = lib.mkOption { + type = lib.types.bool; + default = true; + defaultText = "enable"; + example = lib.literalExpression false; + description = "Whether we want to install this file in this user's home directory."; + }; + username = lib.mkOption { + type = lib.types.str; + defaultText = "username"; + example = "root"; + description = "The username for the user whose home directory will contain the file."; + }; + target = lib.mkOption { + type = lib.types.str; + defaultText = "target"; + example = ".local/share/foo/bar.txt"; + description = "The path where the file should be written."; + }; + method = lib.mkOption { + type = lib.types.enum [ + "symlink" + "overwrite" + # "bind_mount" TODO: for directories? + ]; + default = "symlink"; + defaultText = "me.install.file.‹path›.method"; + example = "overwrite"; + description = "The way in which the file should be installed."; + }; + mode = lib.mkOption { + type = lib.types.str; + default = "0444"; + defaultText = "me.install.file.‹path›.mode"; + example = "0750"; + description = "The read, write, execute permission flags."; + }; + dir_mode = lib.mkOption { + type = lib.types.str; + default = "0755"; + defaultText = "dir_mode"; + example = "0755"; + description = "The read, write, execute permission flags for any parent directories that need to be created."; + }; + source = lib.mkOption { + type = lib.types.path; + defaultText = "me.install.file.‹path›.source"; + example = ./files/foo.txt; + description = "The source file to install into the destination."; + }; + recursive = lib.mkOption { + type = lib.types.bool; + default = false; + defaultText = "recursive"; + example = lib.literalExpression false; + description = "Whether we want to recurse through the directory doing individual installs for each file."; + }; + }; + + config = { + username = lib.mkDefault username; + target = lib.mkDefault path; + }; + } + ) + ); + }; + }; + } + ) + ); + }; + }; + + config = + let + all_users = builtins.map (username: cfg.user."${username}") (attrNames cfg.user); + enabled_users = filter (user: user.enable) all_users; + all_file_targets = lib.flatten ( + builtins.map (user: (builtins.map (path: user.file."${path}") (attrNames user.file))) enabled_users + ); + enabled_file_targets = filter (target: target.enable) all_file_targets; + check_commands = lib.flatten (builtins.map (install_user_file "check") enabled_file_targets); + install_commands = lib.flatten (builtins.map (install_user_file "install") enabled_file_targets); + uninstall_commands = lib.flatten ( + builtins.map (install_user_file "uninstall") enabled_file_targets + ); + in + { + systemd.services.me-install-file = { + enable = true; + description = "me-install-file"; + wantedBy = [ "multi-user.target" ]; + wants = [ "multi-user.target" ]; + before = [ "multi-user.target" ]; + # path = with pkgs; [ + # zfs + # ]; + unitConfig.DefaultDependencies = "no"; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = "yes"; + }; + script = + '' + set -o pipefail + IFS=$'\n\t' + source ${./files/lib.bash} + '' + + (lib.strings.concatStringsSep "\n" ( + [ + ] + ++ check_commands + ++ install_commands + )); + preStop = + '' + set -o pipefail + IFS=$'\n\t' + source ${./files/lib.bash} + '' + + (lib.strings.concatStringsSep "\n" uninstall_commands); + }; + }; +} diff --git a/nix/yubipi/util/install_files/files/lib.bash b/nix/yubipi/util/install_files/files/lib.bash new file mode 100644 index 0000000..1a5c178 --- /dev/null +++ b/nix/yubipi/util/install_files/files/lib.bash @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# + +############## Setup ######################### + +function die { + local status_code="$1" + shift + (>&2 echo "${@}") + exit "$status_code" +} + +function log { + (>&2 echo "${@}") +} + +############## Program ######################### + +function create_containing_directories { + local full_dest="$1" + shift 1 + local dirs_to_create=() + local containing_directory="$full_dest" + while true; do + containing_directory=$(dirname "$containing_directory") + if [ -e "$containing_directory" ] || [ "$containing_directory" = "/" ]; then + break + fi + dirs_to_create+=($containing_directory) + done + + for (( idx=${#dirs_to_create[@]}-1 ; idx>=0 ; idx-- )) ; do + local containing_directory="${dirs_to_create[idx]}" + log "Creating $containing_directory" + $DRY_RUN_CMD install $VERBOSE_ARG -d "${@}" "$containing_directory" + done + +} diff --git a/nix/yubipi/util/unfree_polyfill/default.nix b/nix/yubipi/util/unfree_polyfill/default.nix new file mode 100644 index 0000000..d744cf4 --- /dev/null +++ b/nix/yubipi/util/unfree_polyfill/default.nix @@ -0,0 +1,15 @@ +{ config, lib, ... }: + +let + inherit (builtins) elem; + inherit (lib) getName mkOption; + inherit (lib.types) listOf str; +in +{ + # Pending https://github.com/NixOS/nixpkgs/issues/55674 + options.allowedUnfree = mkOption { + type = listOf str; + default = [ ]; + }; + config.nixpkgs.config.allowUnfreePredicate = p: elem (getName p) config.allowedUnfree; +}