├── .envrc ├── .gitignore ├── LICENSE ├── README.md ├── apps └── setup-hetzner-storage-boxes.nix ├── flake-modules ├── default.nix ├── globals.nix └── nodes.nix ├── flake.lock ├── flake.nix ├── hm-modules ├── default.nix ├── i3.nix └── wallpapers.nix ├── lib ├── disko.nix ├── misc.nix ├── net.nix ├── netu.nix ├── shift.nix └── types.nix ├── modules ├── boot.nix ├── default.nix ├── globals.nix ├── guests │ ├── common-guest-config.nix │ ├── container.nix │ ├── default.nix │ └── microvm.nix ├── interface-naming.nix ├── nginx.nix ├── node.nix ├── restic.nix ├── topology-wireguard.nix ├── wireguard.nix └── wireguardGlobals.nix ├── overlay.nix ├── pkgs ├── default.nix └── home-assistant │ └── bar-card.nix ├── statix.toml └── tests ├── default.nix ├── net.nix └── shift.nix /.envrc: -------------------------------------------------------------------------------- 1 | use flake 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .pre-commit-config.yaml 2 | .direnv 3 | result 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 oddlama PatrickDaG 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [Installation](#installation) 2 | 3 | # 🍵 nixos-extra-modules 4 | 5 | This repository contains extra modules for nixos that are very opinionated and mainly 6 | useful to me and my colleagues. All modules in here are opt-in, so nothing will 7 | be changed unless you decide you want to use that specific module. 8 | 9 | ## Overview 10 | 11 | #### NixOS Modules 12 | 13 | | Name | Type | Source | Requires | Optional deps | Description | 14 | |---|---|---|---|---|---| 15 | Networking library and extensions | Lib | [Link](./lib/net.nix) | - | - | Integrates [this libary](https://gist.github.com/duairc/5c9bb3c922e5d501a1edb9e7b3b845ba) which adds option types for IPs, CIDRs, MACs, and more. Also adds some extensions for missing functions and cross-node hashtable-based lazy IP/MAC assignment. 16 | Interface naming by MAC | Module | [Link](./modules/interface-naming.nix) | - | - | Allows you to define pairs of MAC address and interface name which will be enforced via udev as early as possible. 17 | EFI/BIOS boot config | Module | [Link](./modules/boot.nix) | - | - | Allows you to specify a boot type (bios/efi) and the correct loader will automatically be configured 18 | Nginx recommended options | Module | [Link](./modules/nginx.nix) | - | agenix | Sets many recommended settings for nginx with a single switch plus some opinionated defaults. Also adds a switch for setting recommended security headers on each location. 19 | Node options | Module | [Link](./modules/node.nix) | - | - | A module that stores meta information about your nodes (hosts). Required for some other modules that operate across nodes. 20 | Guests (MicroVMs & Containers) | Module | [Link](./modules/guests) | zfs, disko, node options | - | This module implements a common interface to use guest systems with microvms or nixos-containers. 21 | Restic hetzner storage box setup | Module | [Link](./modules/restic.nix) | agenix, agenix-rekey | - | This module exposes new options for restic backups that allow a simple setup of hetzner storage boxes. There's [an app](./apps/setup-hetzner-storage-boxes.nix) that you should expose on your flake to automate remote setup. 22 | Wireguard overlay networks | Module | [Link](./modules/wireguard.nix) | agenix, agenix-rekey, nftables-firewall, inputs.self.nodes | - | This module automatically creates cross-node wireguard networks including automatic semi-stable ip address assignments 23 | nix-topology for wireguard | Module | [Link](./modules/topology-wireguard.nix) | nix-topology | - | This module automatically adds wireguard networks and interfaces based on the wireguard configuration from our wireguard module 24 | 25 | #### Home Manager Modules 26 | 27 | | Name | Type | Source | Requires | Optional deps | Description | 28 | |---|---|---|---|---|---| 29 | i3 systemd targets | Module | [Link](./hm-modules/i3.nix) | - | - | Makes i3 setup and reach graphical-session.target so that other services are properly executed. 30 | Wallpapers | Module | [Link](./hm-modules/wallpapers.nix) | - | - | A simple wallpaper service that changes the wallpaper of each monitor to a random image after a specified interval. 31 | 32 | ## Installation 33 | 34 | To use the extra modules, you will have to add this project to your `flake.nix`, 35 | and import the provided main NixOS module in your hosts. Afterwards the new options 36 | will be available. 37 | 38 | Certain modules may require the use of additional flakes. In particular 39 | depending on the modules you want to use, you might need: 40 | 41 | - [agenix](https://github.com/ryantm/agenix) 42 | - [agenix-rekey](https://github.com/oddlama/agenix-rekey) 43 | - [disko](https://github.com/nix-community/disko) 44 | - [home-manager](https://github.com/nix-community/home-manager) 45 | - [impermanence](https://github.com/nix-community/impermanence) 46 | - [microvm.nix](https://github.com/astro/microvm.nix) 47 | 48 | You also must have a `specialArgs.inputs` that refers to all of your flake's inputs, 49 | and `inputs.self.pkgs.${system}` must refer to an initialized package set for that 50 | specific system that includes extra-modules as an overlay. 51 | 52 | All cross-node configuration modules (like wireguard) require you to expose 53 | all relevant nodes in your flake as `inputs.self.nodes`, so their configuration 54 | can be accessed by other nodes. 55 | 56 | Here's an example configuration: 57 | 58 | ```nix 59 | { 60 | inputs = { 61 | flake-utils.url = "github:numtide/flake-utils"; 62 | 63 | nixos-extra-modules = { 64 | url = "github:oddlama/nixos-extra-modules"; 65 | inputs.nixpkgs.follows = "nixpkgs"; 66 | inputs.flake-utils.follows = "flake-utils"; 67 | }; 68 | 69 | nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 70 | 71 | # Additional inputs, may or may not be needed for a particular module or extension. 72 | # Enable what you use. 73 | 74 | # agenix = { 75 | # url = "github:ryantm/agenix"; 76 | # inputs.home-manager.follows = "home-manager"; 77 | # inputs.nixpkgs.follows = "nixpkgs"; 78 | # }; 79 | # 80 | # agenix-rekey = { 81 | # url = "github:oddlama/agenix-rekey"; 82 | # inputs.nixpkgs.follows = "nixpkgs"; 83 | # inputs.flake-utils.follows = "flake-utils"; 84 | # }; 85 | # 86 | # disko = { 87 | # url = "github:nix-community/disko"; 88 | # inputs.nixpkgs.follows = "nixpkgs"; 89 | # }; 90 | # 91 | # home-manager = { 92 | # url = "github:nix-community/home-manager"; 93 | # inputs.nixpkgs.follows = "nixpkgs"; 94 | # }; 95 | # 96 | # impermanence.url = "github:nix-community/impermanence"; 97 | # 98 | # microvm = { 99 | # url = "github:astro/microvm.nix"; 100 | # inputs.nixpkgs.follows = "nixpkgs"; 101 | # inputs.flake-utils.follows = "flake-utils"; 102 | # }; 103 | }; 104 | 105 | outputs = { 106 | self, 107 | nixos-extra-modules, 108 | flake-utils, 109 | nixpkgs, 110 | ... 111 | } @ inputs: { 112 | # Example system configuration 113 | nixosConfigurations.yourhostname = let 114 | system = "x86_64-linux"; 115 | pkgs = self.pkgs.${system}; 116 | in nixpkgs.lib.nixosSystem { 117 | inherit system; 118 | modules = [ 119 | ./configuration.nix 120 | nixos-extra-modules.nixosModules.default 121 | { 122 | # We cannot force the package set via nixpkgs.pkgs and 123 | # inputs.nixpkgs.nixosModules.readOnlyPkgs, since nixosModules 124 | # should be able to dynamicall add overlays via nixpkgs.overlays. 125 | # So we just mimic the options and overlays defined by the passed pkgs set 126 | # to not lose what we already have defined below. 127 | nixpkgs.hostPlatform = system; 128 | nixpkgs.overlays = pkgs.overlays; 129 | nixpkgs.config = pkgs.config; 130 | } 131 | ]; 132 | specialArgs = { 133 | inherit inputs; 134 | # Very important to inherit lib here, so that the additional 135 | # lib overlays are available early. 136 | inherit (pkgs) lib; 137 | }; 138 | }; 139 | 140 | # Required for cross-node configuration like in the wireguard module 141 | nodes = self.nixosConfigurations; 142 | } 143 | // flake-utils.lib.eachDefaultSystem (system: rec { 144 | pkgs = import nixpkgs { 145 | inherit system; 146 | overlays = [ 147 | nixos-extra-modules.overlays.default 148 | # (enable hird-party modules if needed) 149 | # agenix-rekey.overlays.default 150 | # ... 151 | ]; 152 | }; 153 | } 154 | } 155 | ``` 156 | -------------------------------------------------------------------------------- /apps/setup-hetzner-storage-boxes.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | nixosConfigurations, 4 | decryptIdentity, 5 | }: let 6 | inherit 7 | (pkgs.lib) 8 | attrValues 9 | concatLines 10 | concatStringsSep 11 | escapeShellArg 12 | filterAttrs 13 | flatten 14 | flip 15 | forEach 16 | getExe 17 | groupBy 18 | head 19 | length 20 | mapAttrs 21 | mapAttrsToList 22 | optional 23 | throwIf 24 | unique 25 | ; 26 | 27 | allBoxDefinitions = flatten ( 28 | forEach (attrValues nixosConfigurations) ( 29 | hostCfg: 30 | forEach (attrValues hostCfg.config.services.restic.backups) ( 31 | backupCfg: 32 | optional backupCfg.hetznerStorageBox.enable ( 33 | backupCfg.hetznerStorageBox 34 | // {sshPrivateKeyFile = hostCfg.config.age.secrets.${backupCfg.hetznerStorageBox.sshAgeSecret}.rekeyFile;} 35 | ) 36 | ) 37 | ) 38 | ); 39 | 40 | subUserFor = box: "${box.mainUser}-sub${toString box.subUid}"; 41 | boxesBySubuser = groupBy subUserFor allBoxDefinitions; 42 | 43 | # We need to know the main storage box user to create subusers 44 | boxSubuserToMainUser = 45 | flip mapAttrs boxesBySubuser (_: boxes: 46 | head (unique (forEach boxes (box: box.mainUser)))); 47 | 48 | boxSubuserToPrivateKeys = 49 | flip mapAttrs boxesBySubuser (_: boxes: 50 | unique (forEach boxes (box: box.sshPrivateKeyFile))); 51 | 52 | # Any subuid that has more than one path in use 53 | boxSubuserToPaths = 54 | flip mapAttrs boxesBySubuser (_: boxes: 55 | unique (forEach boxes (box: box.path))); 56 | 57 | duplicates = filterAttrs (_: boxes: length boxes > 1) boxSubuserToPaths; 58 | 59 | # Only one path must remain per subuser. 60 | boxSubuserToPath = throwIf (duplicates != {}) '' 61 | At least one storage box subuser has multiple paths assigned to it: 62 | ${concatStringsSep "\n" (mapAttrsToList (n: v: "${n}: ${toString v}") duplicates)} 63 | '' (mapAttrs (_: head) boxSubuserToPaths); 64 | 65 | authorizeResticCommand = privateKey: '' 66 | ( 67 | echo -n 'command="rclone serve restic --stdio --append-only ./repo" ' 68 | PATH="$PATH:${pkgs.age-plugin-yubikey}/bin" ${pkgs.rage}/bin/rage -d -i ${decryptIdentity} ${escapeShellArg privateKey} \ 69 | | (exec 3<&0; ssh-keygen -f /proc/self/fd/3 -y) 70 | ) >> "$TMPFILE" 71 | ''; 72 | 73 | setupSubuser = subuser: privateKeys: let 74 | mainUser = boxSubuserToMainUser.${subuser}; 75 | path = boxSubuserToPath.${subuser}; 76 | in '' 77 | echo "${mainUser} (for ${subuser}): Removing old ${path}/.ssh if it exists" 78 | # Remove any .ssh folder if it exists 79 | ${pkgs.openssh}/bin/ssh -p 23 "${mainUser}@${mainUser}.your-storagebox.de" -- rm -r ./${path}/.ssh &>/dev/null || true 80 | echo "${mainUser} (for ${subuser}): Creating ${path}/.ssh" 81 | # Create subuser directory and .ssh 82 | ${pkgs.openssh}/bin/ssh -p 23 "${mainUser}@${mainUser}.your-storagebox.de" -- mkdir -p ./${path}/.ssh 83 | # Create repo directory 84 | ${pkgs.openssh}/bin/ssh -p 23 "${mainUser}@${mainUser}.your-storagebox.de" -- mkdir -p ./${path}/repo 85 | 86 | # Derive and upload all authorized keys 87 | TMPFILE=$(mktemp) 88 | ${concatLines (map authorizeResticCommand privateKeys)} 89 | echo "${mainUser} (for ${subuser}): Uploading $(wc -l < "$TMPFILE") authorized_keys" 90 | ${pkgs.openssh}/bin/scp -P 23 "$TMPFILE" "${mainUser}@${mainUser}.your-storagebox.de":./${path}/.ssh/authorized_keys 91 | rm "$TMPFILE" 92 | ''; 93 | in { 94 | type = "app"; 95 | program = getExe (pkgs.writeShellApplication { 96 | name = "setup-hetzner-storage-boxes"; 97 | text = '' 98 | set -euo pipefail 99 | 100 | ${concatLines (mapAttrsToList setupSubuser boxSubuserToPrivateKeys)} 101 | 102 | echo 103 | echo "Please visit https://robot.hetzner.com/storage and make sure" 104 | echo "that the following subusers are setup correctly:" 105 | ${concatLines (mapAttrsToList (u: p: "echo ' ${u}: ${p}'") boxSubuserToPath)} 106 | ''; 107 | }); 108 | } 109 | -------------------------------------------------------------------------------- /flake-modules/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | imports = [ 3 | ./globals.nix 4 | ./nodes.nix 5 | ]; 6 | } 7 | -------------------------------------------------------------------------------- /flake-modules/globals.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs, 3 | lib, 4 | config, 5 | ... 6 | }: 7 | let 8 | inherit (lib) mkOption types; 9 | in 10 | { 11 | options.globals = { 12 | optModules = mkOption { 13 | type = types.listOf types.deferredModule; 14 | default = [ ]; 15 | description = '' 16 | Modules defining global options. 17 | These should not include any config only option declaration. 18 | Will be included in the exported nixos Modules from this flake to be included 19 | into the host evaluation. 20 | Be aware that at most 1 of these modules can have a default 21 | ''; 22 | }; 23 | defModules = mkOption { 24 | type = types.listOf types.deferredModule; 25 | default = [ ]; 26 | description = '' 27 | Modules configuring global options. 28 | These should not include any option declaration use {option}`optModules` for that. 29 | Will not included in the exported nixos Modules. 30 | ''; 31 | }; 32 | attrkeys = mkOption { 33 | type = types.listOf types.str; 34 | default = [ ]; 35 | description = '' 36 | The toplevel attrNames for your globals. 37 | Make sure the keys of this attrset are trivially evaluatable to avoid infinite recursion, 38 | therefore we inherit relevant attributes from the config. 39 | ''; 40 | }; 41 | }; 42 | config = { 43 | globals = { 44 | optModules = [ 45 | ../modules/wireguardGlobals.nix 46 | ]; 47 | attrkeys = [ "wireguard" ]; 48 | }; 49 | flake = flakeSubmod: { 50 | globals = 51 | let 52 | globalsSystem = lib.evalModules { 53 | prefix = [ "globals" ]; 54 | specialArgs = { 55 | inherit (inputs.self.pkgs.x86_64-linux) lib; 56 | inherit inputs; 57 | inherit (flakeSubmod.config) nodes; 58 | }; 59 | modules = 60 | config.globals.optModules 61 | ++ config.globals.defModules 62 | ++ [ 63 | ../modules/globals.nix 64 | ( 65 | { lib, ... }: 66 | { 67 | globals = lib.mkMerge ( 68 | lib.concatLists ( 69 | lib.flip lib.mapAttrsToList flakeSubmod.config.nodes ( 70 | name: cfg: 71 | builtins.addErrorContext "while aggregating globals from nixosConfigurations.${name} into flake-level globals:" cfg.config._globalsDefs 72 | ) 73 | ) 74 | ); 75 | } 76 | ) 77 | ]; 78 | }; 79 | in 80 | lib.genAttrs config.globals.attrkeys (x: globalsSystem.config.globals.${x}); 81 | }; 82 | }; 83 | } 84 | -------------------------------------------------------------------------------- /flake-modules/nodes.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs, 3 | self, 4 | lib, 5 | config, 6 | ... 7 | }: 8 | let 9 | inherit (lib) mkOption types; 10 | topConfig = config; 11 | in 12 | { 13 | options.node = { 14 | path = mkOption { 15 | type = types.path; 16 | description = "The path containing your host definitions"; 17 | }; 18 | nixpkgs = mkOption { 19 | type = types.path; 20 | default = inputs.nixpkgs; 21 | description = "The path to your nixpkgs."; 22 | }; 23 | }; 24 | config.flake = 25 | { 26 | config, 27 | lib, 28 | ... 29 | }: 30 | let 31 | inherit (lib) 32 | concatMapAttrs 33 | filterAttrs 34 | flip 35 | genAttrs 36 | mapAttrs' 37 | nameValuePair 38 | ; 39 | 40 | # Creates a new nixosSystem with the correct specialArgs, pkgs and name definition 41 | mkHost = 42 | { minimal }: 43 | name: 44 | let 45 | pkgs = config.pkgs.x86_64-linux; 46 | in 47 | (import "${topConfig.node.nixpkgs}/nixos/lib/eval-config.nix") { 48 | system = null; 49 | specialArgs = { 50 | # Use the correct instance lib that has our overlays 51 | inherit (pkgs) lib; 52 | inherit (config) nodes globals; 53 | inherit minimal; 54 | extraModules = [ 55 | ../modules 56 | ] ++ topConfig.globals.optModules; 57 | inputs = inputs // { 58 | inherit (topConfig.node) nixpkgs; 59 | }; 60 | }; 61 | modules = [ 62 | ( 63 | { config, ... }: 64 | { 65 | node.name = name; 66 | node.secretsDir = topConfig.node.path + "/${name}/secrets"; 67 | nixpkgs.pkgs = self.pkgs.${config.nixpkgs.hostPlatform.system}; 68 | } 69 | ) 70 | (topConfig.node.path + "/${name}") 71 | ../modules 72 | ] ++ topConfig.globals.optModules; 73 | }; 74 | 75 | # Load the list of hosts that this flake defines, which 76 | # associates the minimum amount of metadata that is necessary 77 | # to instanciate hosts correctly. 78 | hosts = builtins.attrNames ( 79 | filterAttrs (_: type: type == "directory") (builtins.readDir topConfig.node.path) 80 | ); 81 | in 82 | # Process each nixosHosts declaration and generatea nixosSystem definitions 83 | { 84 | nixosConfigurations = genAttrs hosts (mkHost { 85 | minimal = false; 86 | }); 87 | minimalConfigurations = genAttrs hosts (mkHost { 88 | minimal = true; 89 | }); 90 | 91 | # True NixOS nodes can define additional guest nodes that are built 92 | # together with it. We collect all defined guests from each node here 93 | # to allow accessing any node via the unified attribute `nodes`. 94 | guestConfigurations = flip concatMapAttrs config.nixosConfigurations ( 95 | _: node: 96 | flip mapAttrs' (node.config.guests or { }) ( 97 | guestName: guestDef: 98 | nameValuePair guestDef.nodeName ( 99 | if guestDef.backend == "microvm" then 100 | node.config.microvm.vms.${guestName}.config 101 | else 102 | node.config.containers.${guestName}.nixosConfiguration 103 | ) 104 | ) 105 | ); 106 | # All nixosSystem instanciations are collected here, so that we can refer 107 | # to any system via nodes. 108 | nodes = config.nixosConfigurations // config.guestConfigurations; 109 | }; 110 | } 111 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "blank": { 4 | "locked": { 5 | "lastModified": 1625557891, 6 | "narHash": "sha256-O8/MWsPBGhhyPoPLHZAuoZiiHo9q6FLlEeIDEXuj6T4=", 7 | "owner": "divnix", 8 | "repo": "blank", 9 | "rev": "5a5d2684073d9f563072ed07c871d577a6c614a8", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "divnix", 14 | "repo": "blank", 15 | "type": "github" 16 | } 17 | }, 18 | "crane": { 19 | "inputs": { 20 | "flake-compat": "flake-compat_2", 21 | "flake-utils": "flake-utils_2", 22 | "nixpkgs": [ 23 | "nixt", 24 | "std", 25 | "paisano-mdbook-preprocessor", 26 | "nixpkgs" 27 | ], 28 | "rust-overlay": "rust-overlay" 29 | }, 30 | "locked": { 31 | "lastModified": 1676162383, 32 | "narHash": "sha256-krUCKdz7ebHlFYm/A7IbKDnj2ZmMMm3yIEQcooqm7+E=", 33 | "owner": "ipetkov", 34 | "repo": "crane", 35 | "rev": "6fb400ec631b22ccdbc7090b38207f7fb5cfb5f2", 36 | "type": "github" 37 | }, 38 | "original": { 39 | "owner": "ipetkov", 40 | "repo": "crane", 41 | "type": "github" 42 | } 43 | }, 44 | "devshell": { 45 | "inputs": { 46 | "nixpkgs": [ 47 | "nixpkgs" 48 | ] 49 | }, 50 | "locked": { 51 | "lastModified": 1735644329, 52 | "narHash": "sha256-tO3HrHriyLvipc4xr+Ewtdlo7wM1OjXNjlWRgmM7peY=", 53 | "owner": "numtide", 54 | "repo": "devshell", 55 | "rev": "f7795ede5b02664b57035b3b757876703e2c3eac", 56 | "type": "github" 57 | }, 58 | "original": { 59 | "owner": "numtide", 60 | "repo": "devshell", 61 | "type": "github" 62 | } 63 | }, 64 | "devshell_2": { 65 | "inputs": { 66 | "flake-utils": [ 67 | "nixt", 68 | "std", 69 | "flake-utils" 70 | ], 71 | "nixpkgs": [ 72 | "nixt", 73 | "std", 74 | "nixpkgs" 75 | ] 76 | }, 77 | "locked": { 78 | "lastModified": 1682700442, 79 | "narHash": "sha256-qjaAAcCYgp1pBBG7mY9z95ODUBZMtUpf0Qp3Gt/Wha0=", 80 | "owner": "numtide", 81 | "repo": "devshell", 82 | "rev": "fb6673fe9fe4409e3f43ca86968261e970918a83", 83 | "type": "github" 84 | }, 85 | "original": { 86 | "owner": "numtide", 87 | "repo": "devshell", 88 | "type": "github" 89 | } 90 | }, 91 | "dmerge": { 92 | "inputs": { 93 | "haumea": "haumea", 94 | "namaka": "namaka", 95 | "nixlib": [ 96 | "nixt", 97 | "std", 98 | "nixpkgs" 99 | ], 100 | "yants": [ 101 | "nixt", 102 | "std", 103 | "yants" 104 | ] 105 | }, 106 | "locked": { 107 | "lastModified": 1684178600, 108 | "narHash": "sha256-EtSQcCHRQUBBEj4vbYU0vgPUYiKP261ero5k1QfQ3Bc=", 109 | "owner": "divnix", 110 | "repo": "dmerge", 111 | "rev": "ac9932f26325afac5baa59cf6478432d17762a4e", 112 | "type": "github" 113 | }, 114 | "original": { 115 | "owner": "divnix", 116 | "ref": "0.2.0", 117 | "repo": "dmerge", 118 | "type": "github" 119 | } 120 | }, 121 | "fenix": { 122 | "inputs": { 123 | "nixpkgs": "nixpkgs_2", 124 | "rust-analyzer-src": "rust-analyzer-src" 125 | }, 126 | "locked": { 127 | "lastModified": 1677306201, 128 | "narHash": "sha256-VZ9x7qdTosFvVsrpgFHrtYfT6PU3yMIs7NRYn9ELapI=", 129 | "owner": "nix-community", 130 | "repo": "fenix", 131 | "rev": "0923f0c162f65ae40261ec940406049726cfeab4", 132 | "type": "github" 133 | }, 134 | "original": { 135 | "owner": "nix-community", 136 | "repo": "fenix", 137 | "type": "github" 138 | } 139 | }, 140 | "flake-compat": { 141 | "flake": false, 142 | "locked": { 143 | "lastModified": 1673956053, 144 | "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", 145 | "owner": "edolstra", 146 | "repo": "flake-compat", 147 | "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", 148 | "type": "github" 149 | }, 150 | "original": { 151 | "owner": "edolstra", 152 | "repo": "flake-compat", 153 | "type": "github" 154 | } 155 | }, 156 | "flake-compat_2": { 157 | "flake": false, 158 | "locked": { 159 | "lastModified": 1673956053, 160 | "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", 161 | "owner": "edolstra", 162 | "repo": "flake-compat", 163 | "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", 164 | "type": "github" 165 | }, 166 | "original": { 167 | "owner": "edolstra", 168 | "repo": "flake-compat", 169 | "type": "github" 170 | } 171 | }, 172 | "flake-compat_3": { 173 | "flake": false, 174 | "locked": { 175 | "lastModified": 1696426674, 176 | "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", 177 | "owner": "edolstra", 178 | "repo": "flake-compat", 179 | "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", 180 | "type": "github" 181 | }, 182 | "original": { 183 | "owner": "edolstra", 184 | "repo": "flake-compat", 185 | "type": "github" 186 | } 187 | }, 188 | "flake-parts": { 189 | "inputs": { 190 | "nixpkgs-lib": "nixpkgs-lib" 191 | }, 192 | "locked": { 193 | "lastModified": 1738453229, 194 | "narHash": "sha256-7H9XgNiGLKN1G1CgRh0vUL4AheZSYzPm+zmZ7vxbJdo=", 195 | "owner": "hercules-ci", 196 | "repo": "flake-parts", 197 | "rev": "32ea77a06711b758da0ad9bd6a844c5740a87abd", 198 | "type": "github" 199 | }, 200 | "original": { 201 | "owner": "hercules-ci", 202 | "repo": "flake-parts", 203 | "type": "github" 204 | } 205 | }, 206 | "flake-utils": { 207 | "locked": { 208 | "lastModified": 1659877975, 209 | "narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=", 210 | "owner": "numtide", 211 | "repo": "flake-utils", 212 | "rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0", 213 | "type": "github" 214 | }, 215 | "original": { 216 | "owner": "numtide", 217 | "repo": "flake-utils", 218 | "type": "github" 219 | } 220 | }, 221 | "flake-utils_2": { 222 | "locked": { 223 | "lastModified": 1667395993, 224 | "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", 225 | "owner": "numtide", 226 | "repo": "flake-utils", 227 | "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", 228 | "type": "github" 229 | }, 230 | "original": { 231 | "owner": "numtide", 232 | "repo": "flake-utils", 233 | "type": "github" 234 | } 235 | }, 236 | "gitignore": { 237 | "inputs": { 238 | "nixpkgs": [ 239 | "pre-commit-hooks", 240 | "nixpkgs" 241 | ] 242 | }, 243 | "locked": { 244 | "lastModified": 1709087332, 245 | "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", 246 | "owner": "hercules-ci", 247 | "repo": "gitignore.nix", 248 | "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", 249 | "type": "github" 250 | }, 251 | "original": { 252 | "owner": "hercules-ci", 253 | "repo": "gitignore.nix", 254 | "type": "github" 255 | } 256 | }, 257 | "haumea": { 258 | "inputs": { 259 | "nixpkgs": [ 260 | "nixt", 261 | "std", 262 | "dmerge", 263 | "nixlib" 264 | ] 265 | }, 266 | "locked": { 267 | "lastModified": 1681176209, 268 | "narHash": "sha256-bJLDun6esIyWtwRVXcsgzGbh4UKu8wJDrPgykqPyzmg=", 269 | "owner": "nix-community", 270 | "repo": "haumea", 271 | "rev": "b915b66b27da3a595d77b139e945bb0a2fcac926", 272 | "type": "github" 273 | }, 274 | "original": { 275 | "owner": "nix-community", 276 | "repo": "haumea", 277 | "type": "github" 278 | } 279 | }, 280 | "incl": { 281 | "inputs": { 282 | "nixlib": [ 283 | "nixt", 284 | "std", 285 | "nixpkgs" 286 | ] 287 | }, 288 | "locked": { 289 | "lastModified": 1669263024, 290 | "narHash": "sha256-E/+23NKtxAqYG/0ydYgxlgarKnxmDbg6rCMWnOBqn9Q=", 291 | "owner": "divnix", 292 | "repo": "incl", 293 | "rev": "ce7bebaee048e4cd7ebdb4cee7885e00c4e2abca", 294 | "type": "github" 295 | }, 296 | "original": { 297 | "owner": "divnix", 298 | "repo": "incl", 299 | "type": "github" 300 | } 301 | }, 302 | "n2c": { 303 | "inputs": { 304 | "flake-utils": [ 305 | "nixt", 306 | "std", 307 | "flake-utils" 308 | ], 309 | "nixpkgs": [ 310 | "nixt", 311 | "std", 312 | "nixpkgs" 313 | ] 314 | }, 315 | "locked": { 316 | "lastModified": 1677330646, 317 | "narHash": "sha256-hUYCwJneMjnxTvj30Fjow6UMJUITqHlpUGpXMPXUJsU=", 318 | "owner": "nlewo", 319 | "repo": "nix2container", 320 | "rev": "ebca8f58d450cae1a19c07701a5a8ae40afc9efc", 321 | "type": "github" 322 | }, 323 | "original": { 324 | "owner": "nlewo", 325 | "repo": "nix2container", 326 | "type": "github" 327 | } 328 | }, 329 | "namaka": { 330 | "inputs": { 331 | "haumea": [ 332 | "nixt", 333 | "std", 334 | "dmerge", 335 | "haumea" 336 | ], 337 | "nixpkgs": [ 338 | "nixt", 339 | "std", 340 | "dmerge", 341 | "nixlib" 342 | ] 343 | }, 344 | "locked": { 345 | "lastModified": 1683059428, 346 | "narHash": "sha256-ZTMqleCWmuNWhZE375gtF1j1JRkaKEUFN1AM43e7h4Y=", 347 | "owner": "nix-community", 348 | "repo": "namaka", 349 | "rev": "2deba2f416454aec770bc1cc7365e39c73e6b1d7", 350 | "type": "github" 351 | }, 352 | "original": { 353 | "owner": "nix-community", 354 | "ref": "v0.1.1", 355 | "repo": "namaka", 356 | "type": "github" 357 | } 358 | }, 359 | "nixago": { 360 | "inputs": { 361 | "flake-utils": [ 362 | "nixt", 363 | "std", 364 | "flake-utils" 365 | ], 366 | "nixago-exts": [ 367 | "nixt", 368 | "std", 369 | "blank" 370 | ], 371 | "nixpkgs": [ 372 | "nixt", 373 | "std", 374 | "nixpkgs" 375 | ] 376 | }, 377 | "locked": { 378 | "lastModified": 1683210100, 379 | "narHash": "sha256-bhGDOlkWtlhVECpoOog4fWiFJmLCpVEg09a40aTjCbw=", 380 | "owner": "nix-community", 381 | "repo": "nixago", 382 | "rev": "1da60ad9412135f9ed7a004669fdcf3d378ec630", 383 | "type": "github" 384 | }, 385 | "original": { 386 | "owner": "nix-community", 387 | "repo": "nixago", 388 | "type": "github" 389 | } 390 | }, 391 | "nixpkgs": { 392 | "locked": { 393 | "lastModified": 1737885589, 394 | "narHash": "sha256-Zf0hSrtzaM1DEz8//+Xs51k/wdSajticVrATqDrfQjg=", 395 | "owner": "NixOS", 396 | "repo": "nixpkgs", 397 | "rev": "852ff1d9e153d8875a83602e03fdef8a63f0ecf8", 398 | "type": "github" 399 | }, 400 | "original": { 401 | "owner": "NixOS", 402 | "ref": "nixos-unstable", 403 | "repo": "nixpkgs", 404 | "type": "github" 405 | } 406 | }, 407 | "nixpkgs-lib": { 408 | "locked": { 409 | "lastModified": 1738452942, 410 | "narHash": "sha256-vJzFZGaCpnmo7I6i416HaBLpC+hvcURh/BQwROcGIp8=", 411 | "type": "tarball", 412 | "url": "https://github.com/NixOS/nixpkgs/archive/072a6db25e947df2f31aab9eccd0ab75d5b2da11.tar.gz" 413 | }, 414 | "original": { 415 | "type": "tarball", 416 | "url": "https://github.com/NixOS/nixpkgs/archive/072a6db25e947df2f31aab9eccd0ab75d5b2da11.tar.gz" 417 | } 418 | }, 419 | "nixpkgs_2": { 420 | "locked": { 421 | "lastModified": 1677063315, 422 | "narHash": "sha256-qiB4ajTeAOVnVSAwCNEEkoybrAlA+cpeiBxLobHndE8=", 423 | "owner": "nixos", 424 | "repo": "nixpkgs", 425 | "rev": "988cc958c57ce4350ec248d2d53087777f9e1949", 426 | "type": "github" 427 | }, 428 | "original": { 429 | "owner": "nixos", 430 | "ref": "nixos-unstable", 431 | "repo": "nixpkgs", 432 | "type": "github" 433 | } 434 | }, 435 | "nixt": { 436 | "inputs": { 437 | "flake-compat": "flake-compat", 438 | "nixpkgs": [ 439 | "nixpkgs" 440 | ], 441 | "std": "std", 442 | "std-data-collection": "std-data-collection" 443 | }, 444 | "locked": { 445 | "lastModified": 1729273076, 446 | "narHash": "sha256-h2Y+5bikSXS8MPYpxyZpd+VX9H5uuCS/csMMxZCoS3c=", 447 | "owner": "nix-community", 448 | "repo": "nixt", 449 | "rev": "ad8863c9f9e5a166d663f2f1f0eef74ab913a883", 450 | "type": "github" 451 | }, 452 | "original": { 453 | "owner": "nix-community", 454 | "repo": "nixt", 455 | "type": "github" 456 | } 457 | }, 458 | "nosys": { 459 | "locked": { 460 | "lastModified": 1668010795, 461 | "narHash": "sha256-JBDVBnos8g0toU7EhIIqQ1If5m/nyBqtHhL3sicdPwI=", 462 | "owner": "divnix", 463 | "repo": "nosys", 464 | "rev": "feade0141487801c71ff55623b421ed535dbdefa", 465 | "type": "github" 466 | }, 467 | "original": { 468 | "owner": "divnix", 469 | "repo": "nosys", 470 | "type": "github" 471 | } 472 | }, 473 | "paisano": { 474 | "inputs": { 475 | "nixpkgs": [ 476 | "nixt", 477 | "std", 478 | "nixpkgs" 479 | ], 480 | "nosys": "nosys", 481 | "yants": [ 482 | "nixt", 483 | "std", 484 | "yants" 485 | ] 486 | }, 487 | "locked": { 488 | "lastModified": 1678949904, 489 | "narHash": "sha256-oAoF66hYYz1RPh3lEwb9/4e4iyBAfTbQKZRRQ8gP0Ds=", 490 | "owner": "paisano-nix", 491 | "repo": "core", 492 | "rev": "88f2aff10a5064551d1d4cb86800d17084489ce3", 493 | "type": "github" 494 | }, 495 | "original": { 496 | "owner": "paisano-nix", 497 | "repo": "core", 498 | "type": "github" 499 | } 500 | }, 501 | "paisano-actions": { 502 | "inputs": { 503 | "nixpkgs": [ 504 | "nixt", 505 | "std", 506 | "paisano-mdbook-preprocessor", 507 | "nixpkgs" 508 | ] 509 | }, 510 | "locked": { 511 | "lastModified": 1677306424, 512 | "narHash": "sha256-H9/dI2rGEbKo4KEisqbRPHFG2ajF8Tm111NPdKGIf28=", 513 | "owner": "paisano-nix", 514 | "repo": "actions", 515 | "rev": "65ec4e080b3480167fc1a748c89a05901eea9a9b", 516 | "type": "github" 517 | }, 518 | "original": { 519 | "owner": "paisano-nix", 520 | "repo": "actions", 521 | "type": "github" 522 | } 523 | }, 524 | "paisano-mdbook-preprocessor": { 525 | "inputs": { 526 | "crane": "crane", 527 | "fenix": "fenix", 528 | "nixpkgs": [ 529 | "nixt", 530 | "std", 531 | "nixpkgs" 532 | ], 533 | "paisano-actions": "paisano-actions", 534 | "std": [ 535 | "nixt", 536 | "std" 537 | ] 538 | }, 539 | "locked": { 540 | "lastModified": 1680654400, 541 | "narHash": "sha256-Qdpio+ldhUK3zfl22Mhf8HUULdUOJXDWDdO7MIK69OU=", 542 | "owner": "paisano-nix", 543 | "repo": "mdbook-paisano-preprocessor", 544 | "rev": "11a8fc47f574f194a7ae7b8b98001f6143ba4cf1", 545 | "type": "github" 546 | }, 547 | "original": { 548 | "owner": "paisano-nix", 549 | "repo": "mdbook-paisano-preprocessor", 550 | "type": "github" 551 | } 552 | }, 553 | "paisano-tui": { 554 | "inputs": { 555 | "nixpkgs": [ 556 | "nixt", 557 | "std", 558 | "blank" 559 | ], 560 | "std": [ 561 | "nixt", 562 | "std" 563 | ] 564 | }, 565 | "locked": { 566 | "lastModified": 1681847764, 567 | "narHash": "sha256-mdd7PJW1BZvxy0cIKsPfAO+ohVl/V7heE5ZTAHzTdv8=", 568 | "owner": "paisano-nix", 569 | "repo": "tui", 570 | "rev": "3096bad91cae73ab8ab3367d31f8a143d248a244", 571 | "type": "github" 572 | }, 573 | "original": { 574 | "owner": "paisano-nix", 575 | "ref": "0.1.1", 576 | "repo": "tui", 577 | "type": "github" 578 | } 579 | }, 580 | "pre-commit-hooks": { 581 | "inputs": { 582 | "flake-compat": "flake-compat_3", 583 | "gitignore": "gitignore", 584 | "nixpkgs": [ 585 | "nixpkgs" 586 | ] 587 | }, 588 | "locked": { 589 | "lastModified": 1737465171, 590 | "narHash": "sha256-R10v2hoJRLq8jcL4syVFag7nIGE7m13qO48wRIukWNg=", 591 | "owner": "cachix", 592 | "repo": "pre-commit-hooks.nix", 593 | "rev": "9364dc02281ce2d37a1f55b6e51f7c0f65a75f17", 594 | "type": "github" 595 | }, 596 | "original": { 597 | "owner": "cachix", 598 | "repo": "pre-commit-hooks.nix", 599 | "type": "github" 600 | } 601 | }, 602 | "root": { 603 | "inputs": { 604 | "devshell": "devshell", 605 | "flake-parts": "flake-parts", 606 | "nixpkgs": "nixpkgs", 607 | "nixt": "nixt", 608 | "pre-commit-hooks": "pre-commit-hooks" 609 | } 610 | }, 611 | "rust-analyzer-src": { 612 | "flake": false, 613 | "locked": { 614 | "lastModified": 1677221702, 615 | "narHash": "sha256-1M+58rC4eTCWNmmX0hQVZP20t3tfYNunl9D/PrGUyGE=", 616 | "owner": "rust-lang", 617 | "repo": "rust-analyzer", 618 | "rev": "f5401f620699b26ed9d47a1d2e838143a18dbe3b", 619 | "type": "github" 620 | }, 621 | "original": { 622 | "owner": "rust-lang", 623 | "ref": "nightly", 624 | "repo": "rust-analyzer", 625 | "type": "github" 626 | } 627 | }, 628 | "rust-overlay": { 629 | "inputs": { 630 | "flake-utils": [ 631 | "nixt", 632 | "std", 633 | "paisano-mdbook-preprocessor", 634 | "crane", 635 | "flake-utils" 636 | ], 637 | "nixpkgs": [ 638 | "nixt", 639 | "std", 640 | "paisano-mdbook-preprocessor", 641 | "crane", 642 | "nixpkgs" 643 | ] 644 | }, 645 | "locked": { 646 | "lastModified": 1675391458, 647 | "narHash": "sha256-ukDKZw922BnK5ohL9LhwtaDAdCsJL7L6ScNEyF1lO9w=", 648 | "owner": "oxalica", 649 | "repo": "rust-overlay", 650 | "rev": "383a4acfd11d778d5c2efcf28376cbd845eeaedf", 651 | "type": "github" 652 | }, 653 | "original": { 654 | "owner": "oxalica", 655 | "repo": "rust-overlay", 656 | "type": "github" 657 | } 658 | }, 659 | "std": { 660 | "inputs": { 661 | "arion": [ 662 | "nixt", 663 | "std", 664 | "blank" 665 | ], 666 | "blank": "blank", 667 | "devshell": "devshell_2", 668 | "dmerge": "dmerge", 669 | "flake-utils": "flake-utils", 670 | "incl": "incl", 671 | "makes": [ 672 | "nixt", 673 | "std", 674 | "blank" 675 | ], 676 | "microvm": [ 677 | "nixt", 678 | "std", 679 | "blank" 680 | ], 681 | "n2c": "n2c", 682 | "nixago": "nixago", 683 | "nixpkgs": [ 684 | "nixt", 685 | "nixpkgs" 686 | ], 687 | "paisano": "paisano", 688 | "paisano-mdbook-preprocessor": "paisano-mdbook-preprocessor", 689 | "paisano-tui": "paisano-tui", 690 | "yants": "yants" 691 | }, 692 | "locked": { 693 | "lastModified": 1684180498, 694 | "narHash": "sha256-kA58ms4yunOVPhe3r7V0IIKeWUV+vl4r2GTcfFfYW5o=", 695 | "owner": "divnix", 696 | "repo": "std", 697 | "rev": "45b431ae09df98e046bcc8271aa209bdfc87444d", 698 | "type": "github" 699 | }, 700 | "original": { 701 | "owner": "divnix", 702 | "repo": "std", 703 | "type": "github" 704 | } 705 | }, 706 | "std-data-collection": { 707 | "inputs": { 708 | "nixpkgs": [ 709 | "nixt", 710 | "nixpkgs" 711 | ], 712 | "std": [ 713 | "nixt", 714 | "std" 715 | ] 716 | }, 717 | "locked": { 718 | "lastModified": 1676163535, 719 | "narHash": "sha256-xofkWLBqU4zj5vzJhWor2Z9CyPGKt7UGkTchsCT48Po=", 720 | "owner": "divnix", 721 | "repo": "std-data-collection", 722 | "rev": "f713d81a6197e1b0854fb201cc7acde5ef9e93d4", 723 | "type": "github" 724 | }, 725 | "original": { 726 | "owner": "divnix", 727 | "repo": "std-data-collection", 728 | "type": "github" 729 | } 730 | }, 731 | "yants": { 732 | "inputs": { 733 | "nixpkgs": [ 734 | "nixt", 735 | "std", 736 | "nixpkgs" 737 | ] 738 | }, 739 | "locked": { 740 | "lastModified": 1667096281, 741 | "narHash": "sha256-wRRec6ze0gJHmGn6m57/zhz/Kdvp9HS4Nl5fkQ+uIuA=", 742 | "owner": "divnix", 743 | "repo": "yants", 744 | "rev": "d18f356ec25cb94dc9c275870c3a7927a10f8c3c", 745 | "type": "github" 746 | }, 747 | "original": { 748 | "owner": "divnix", 749 | "repo": "yants", 750 | "type": "github" 751 | } 752 | } 753 | }, 754 | "root": "root", 755 | "version": 7 756 | } 757 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Extra modules that nobody needs."; 3 | 4 | inputs = { 5 | devshell = { 6 | url = "github:numtide/devshell"; 7 | inputs.nixpkgs.follows = "nixpkgs"; 8 | }; 9 | 10 | flake-parts.url = "github:hercules-ci/flake-parts"; 11 | 12 | nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 13 | 14 | pre-commit-hooks = { 15 | url = "github:cachix/pre-commit-hooks.nix"; 16 | inputs.nixpkgs.follows = "nixpkgs"; 17 | }; 18 | nixt = { 19 | url = "github:nix-community/nixt"; 20 | inputs.nixpkgs.follows = "nixpkgs"; 21 | }; 22 | }; 23 | 24 | outputs = 25 | { self, ... }@inputs: 26 | inputs.flake-parts.lib.mkFlake { inherit inputs; } { 27 | imports = [ 28 | inputs.devshell.flakeModule 29 | inputs.pre-commit-hooks.flakeModule 30 | ]; 31 | systems = [ 32 | "x86_64-linux" 33 | "aarch64-linux" 34 | ]; 35 | flake.__nixt = inputs.nixt.lib.grow { 36 | blocks = import ./tests { 37 | inherit (inputs) nixt; 38 | pkgs = import inputs.nixpkgs { 39 | system = "x86_64-linux"; 40 | config.allowUnfree = true; 41 | overlays = [ 42 | self.overlays.default 43 | ]; 44 | }; 45 | }; 46 | }; 47 | 48 | flake.modules = { 49 | flake = { 50 | nixos-extra-modules = import ./flake-modules; 51 | default = self.modules.flake.nixos-extra-modules; 52 | }; 53 | nixos = { 54 | nixos-extra-modules = import ./modules; 55 | default = self.modules.nixos.nixos-extra-modules; 56 | }; 57 | home-manager = { 58 | nixos-extra-modules = import ./hm-modules; 59 | default = self.modules.home-manager.nixos-extra-modules; 60 | }; 61 | }; 62 | flake.overlays = { 63 | nixos-extra-modules = import ./overlay.nix inputs; 64 | default = self.overlays.nixos-extra-modules; 65 | }; 66 | 67 | perSystem = 68 | { 69 | pkgs, 70 | system, 71 | config, 72 | ... 73 | }: 74 | { 75 | _module.args.pkgs = import inputs.nixpkgs { 76 | inherit system; 77 | config.allowUnfree = true; 78 | overlays = [ 79 | self.overlays.default 80 | ]; 81 | }; 82 | # `nix flake check` 83 | pre-commit.settings.hooks = { 84 | nixfmt-rfc-style.enable = true; 85 | deadnix.enable = true; 86 | statix.enable = true; 87 | }; 88 | formatter = pkgs.nixfmt-rfc-style; 89 | devshells.default = { 90 | commands = with pkgs; [ 91 | { 92 | package = statix; 93 | help = "Lint nix code"; 94 | } 95 | { 96 | package = inputs.nixt.packages.${system}.default; 97 | help = "Lint nix code"; 98 | } 99 | { 100 | package = deadnix; 101 | help = "Find unused expressions in nix code"; 102 | } 103 | ]; 104 | devshell.startup.pre-commit.text = config.pre-commit.installationScript; 105 | }; 106 | }; 107 | }; 108 | } 109 | -------------------------------------------------------------------------------- /hm-modules/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | imports = [ 3 | ./i3.nix 4 | ./wallpapers.nix 5 | ]; 6 | } 7 | -------------------------------------------------------------------------------- /hm-modules/i3.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | config, 4 | pkgs, 5 | ... 6 | }: { 7 | options.xsession.windowManager.i3.enableSystemdTarget = lib.mkEnableOption "i3 autostarting the systemd graphical user targets"; 8 | config = let 9 | cfg = config.xsession.windowManager.i3.enableSystemdTarget; 10 | in 11 | lib.mkIf cfg { 12 | systemd.user = { 13 | targets.i3-session = { 14 | Unit = { 15 | Description = "i3 session"; 16 | Documentation = ["man:systemd.special(7)"]; 17 | BindsTo = ["graphical-session.target"]; 18 | Wants = ["graphical-session-pre.target"]; 19 | After = ["graphical-session-pre.target"]; 20 | }; 21 | }; 22 | }; 23 | xsession.windowManager.i3.config.startup = lib.mkAfter [ 24 | { 25 | command = "${pkgs.systemd}/bin/systemctl --user start i3-session.target"; 26 | always = false; 27 | notification = false; 28 | } 29 | ]; 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /hm-modules/wallpapers.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | config, 4 | pkgs, 5 | ... 6 | }: let 7 | cfg = config.xsession.wallpapers; 8 | in { 9 | options.xsession.wallpapers = { 10 | enable = lib.mkEnableOption "automatically refreshing randomly selected wallpapers"; 11 | script = let 12 | exe = 13 | pkgs.writeShellScript "set-wallpaper" 14 | '' 15 | ${pkgs.feh}/bin/feh --no-fehbg --bg-fill --randomize \ 16 | $( ${pkgs.findutils}/bin/find ${config.home.homeDirectory}/${cfg.folder} \( -iname "*.png" -or -iname "*.jpg" \) ) 17 | ''; 18 | in 19 | lib.mkOption { 20 | description = "The script which will be called to set new wallpapers"; 21 | default = exe; 22 | type = lib.types.package; 23 | }; 24 | folder = lib.mkOption { 25 | description = "The folder from which the wallpapers are selected. Relative to home directory"; 26 | type = lib.types.str; 27 | default = ".local/share/wallpapers"; 28 | }; 29 | refreshInterval = lib.mkOption { 30 | description = "How often new wallpapers are drawn. Used as a Systemd timer interval."; 31 | type = lib.types.str; 32 | default = "3 min"; 33 | }; 34 | }; 35 | config = lib.mkIf cfg.enable { 36 | systemd.user = { 37 | timers = { 38 | set-wallpaper = { 39 | Unit = { 40 | Description = "Set a random wallpaper every 3 minutes"; 41 | }; 42 | Timer = { 43 | OnUnitActiveSec = cfg.refreshInterval; 44 | }; 45 | Install.WantedBy = [ 46 | "timers.target" 47 | ]; 48 | }; 49 | }; 50 | services = { 51 | set-wallpaper = { 52 | Unit = { 53 | Description = "Set a random wallpaper on all X displays"; 54 | }; 55 | Service = { 56 | Type = "oneshot"; 57 | ExecStart = 58 | cfg.script; 59 | }; 60 | Install.WantedBy = ["graphical-session.target"]; 61 | }; 62 | }; 63 | }; 64 | home.persistence."/state".directories = [cfg.folder]; 65 | }; 66 | } 67 | -------------------------------------------------------------------------------- /lib/disko.nix: -------------------------------------------------------------------------------- 1 | _inputs: final: prev: { 2 | lib = 3 | prev.lib 4 | // { 5 | disko = { 6 | content = { 7 | luksZfs = luksName: pool: { 8 | type = "luks"; 9 | name = "${pool}_${luksName}"; 10 | settings.allowDiscards = true; 11 | content = { 12 | type = "zfs"; 13 | inherit pool; 14 | }; 15 | }; 16 | }; 17 | gpt = rec { 18 | partGrub = { 19 | priority = 1; 20 | size = "1M"; 21 | type = "ef02"; 22 | }; 23 | partEfi = size: { 24 | inherit size; 25 | priority = 1000; 26 | type = "ef00"; 27 | content = { 28 | type = "filesystem"; 29 | format = "vfat"; 30 | mountpoint = "/boot"; 31 | }; 32 | }; 33 | partBoot = size: 34 | partEfi size 35 | // { 36 | hybrid.mbrBootableFlag = true; 37 | }; 38 | partSwap = size: { 39 | inherit size; 40 | priority = 2000; 41 | content = { 42 | type = "swap"; 43 | randomEncryption = true; 44 | }; 45 | }; 46 | partLuksZfs = luksName: pool: size: { 47 | inherit size; 48 | content = final.lib.disko.content.luksZfs luksName pool; 49 | }; 50 | }; 51 | zfs = rec { 52 | mkZpool = prev.lib.recursiveUpdate { 53 | type = "zpool"; 54 | rootFsOptions = { 55 | compression = "zstd"; 56 | acltype = "posix"; 57 | atime = "off"; 58 | xattr = "sa"; 59 | dnodesize = "auto"; 60 | mountpoint = "none"; 61 | canmount = "off"; 62 | devices = "off"; 63 | }; 64 | options.ashift = "12"; 65 | }; 66 | 67 | impermanenceZfsDatasets = { 68 | "local" = unmountable; 69 | "local/root" = 70 | filesystem "/" 71 | // { 72 | postCreateHook = "zfs snapshot rpool/local/root@blank"; 73 | }; 74 | "local/nix" = filesystem "/nix"; 75 | "local/state" = filesystem "/state"; 76 | "safe" = unmountable; 77 | "safe/persist" = filesystem "/persist"; 78 | }; 79 | 80 | unmountable = {type = "zfs_fs";}; 81 | filesystem = mountpoint: { 82 | type = "zfs_fs"; 83 | options = { 84 | canmount = "noauto"; 85 | inherit mountpoint; 86 | }; 87 | # Required to add dependencies for initrd 88 | inherit mountpoint; 89 | }; 90 | }; 91 | }; 92 | }; 93 | } 94 | -------------------------------------------------------------------------------- /lib/misc.nix: -------------------------------------------------------------------------------- 1 | _inputs: final: prev: 2 | let 3 | inherit (prev.lib) 4 | filter 5 | foldl' 6 | genAttrs 7 | mergeAttrs 8 | mkMerge 9 | stringToCharacters 10 | substring 11 | unique 12 | ; 13 | inherit (final.lib) 14 | bit 15 | ; 16 | 17 | # Counts how often each element occurrs in xs. 18 | # Elements must be strings. 19 | countOccurrences = foldl' (acc: x: acc // { ${x} = (acc.${x} or 0) + 1; }) { }; 20 | 21 | # Returns all elements in xs that occur at least twice 22 | duplicates = 23 | xs: 24 | let 25 | occurrences = countOccurrences xs; 26 | in 27 | unique (filter (x: occurrences.${x} > 1) xs); 28 | 29 | # Concatenates all given attrsets as if calling a // b in order. 30 | concatAttrs = foldl' mergeAttrs { }; 31 | 32 | # True if the path or string starts with / 33 | isAbsolutePath = x: substring 0 1 x == "/"; 34 | 35 | # Merges all given attributes from the given attrsets using mkMerge. 36 | # Useful to merge several top-level configs in a module. 37 | mergeToplevelConfigs = keys: attrs: genAttrs keys (attr: mkMerge (map (x: x.${attr} or { }) attrs)); 38 | 39 | hexLiteralValues = { 40 | "0" = 0; 41 | "1" = 1; 42 | "2" = 2; 43 | "3" = 3; 44 | "4" = 4; 45 | "5" = 5; 46 | "6" = 6; 47 | "7" = 7; 48 | "8" = 8; 49 | "9" = 9; 50 | "a" = 10; 51 | "b" = 11; 52 | "c" = 12; 53 | "d" = 13; 54 | "e" = 14; 55 | "f" = 15; 56 | "A" = 10; 57 | "B" = 11; 58 | "C" = 12; 59 | "D" = 13; 60 | "E" = 14; 61 | "F" = 15; 62 | }; 63 | 64 | # Converts the given hex string to an integer. Only reliable for inputs in [0, 2^63), 65 | # after that the sign bit will overflow. 66 | hexToDec = v: foldl' (acc: x: (bit.left acc 4) + hexLiteralValues.${x}) 0 (stringToCharacters v); 67 | in 68 | { 69 | lib = prev.lib // { 70 | inherit 71 | hexToDec 72 | concatAttrs 73 | countOccurrences 74 | duplicates 75 | isAbsolutePath 76 | mergeToplevelConfigs 77 | ; 78 | }; 79 | } 80 | -------------------------------------------------------------------------------- /lib/net.nix: -------------------------------------------------------------------------------- 1 | inputs: _final: prev: 2 | let 3 | inherit (prev.lib) 4 | all 5 | any 6 | assertMsg 7 | elem 8 | filter 9 | flip 10 | foldl' 11 | hasInfix 12 | head 13 | min 14 | partition 15 | range 16 | recursiveUpdate 17 | reverseList 18 | splitString 19 | substring 20 | unique 21 | warnIf 22 | ; 23 | 24 | # From misc.nix 25 | inherit (prev.lib) 26 | hexToDec 27 | ; 28 | 29 | # IP address math library 30 | # https://gist.github.com/duairc/5c9bb3c922e5d501a1edb9e7b3b845ba 31 | # Plus some extensions by us 32 | libNet = 33 | (import ./netu.nix { 34 | inherit (inputs.nixpkgs) lib; 35 | }).lib; 36 | in 37 | { 38 | lib = recursiveUpdate prev.lib { 39 | inherit (libNet) 40 | arithmetic 41 | typechecks 42 | bit 43 | implementations 44 | parsers 45 | ; 46 | 47 | net = recursiveUpdate (removeAttrs libNet.net [ "types" ]) { 48 | cidr = rec { 49 | # host :: (ip | mac | integer) -> cidr -> ip 50 | # 51 | # Wrapper that extends the original host function to 52 | # check whether the argument `n` is in-range for the given cidr. 53 | # 54 | # Examples: 55 | # 56 | # > net.cidr.host 255 "192.168.1.0/24" 57 | # "192.168.1.255" 58 | # > net.cidr.host (256) "192.168.1.0/24" 59 | # 60 | # > net.cidr.host (-1) "192.168.1.0/24" 61 | # "192.168.1.255" 62 | # > net.cidr.host (-256) "192.168.1.0/24" 63 | # "192.168.1.0" 64 | # > net.cidr.host (-257) "192.168.1.0/24" 65 | # 66 | host = 67 | i: n: 68 | let 69 | cap = libNet.net.cidr.capacity n; 70 | intCheck = builtins.isInt i -> (i >= (-cap) && i < cap); 71 | ipCheck = libNet.net.types.ip.check i -> ((libNet.net.types.ip-in n).check result); 72 | 73 | result = libNet.net.cidr.host i n; 74 | in 75 | assert assertMsg (ipCheck && intCheck) "The host ${toString i} lies outside of ${n}"; 76 | result; 77 | # hostCidr :: (ip | mac | integer) -> cidr -> cidr 78 | # 79 | # Returns the nth host in the given cidr range (like cidr.host) 80 | # but as a cidr that retains the original prefix length. 81 | # 82 | # Examples: 83 | # 84 | # > net.cidr.hostCidr 2 "192.168.1.0/24" 85 | # "192.168.1.2/24" 86 | hostCidr = n: x: "${libNet.net.cidr.host n x}/${toString (libNet.net.cidr.length x)}"; 87 | # ip :: (cidr | ip) -> ip 88 | # 89 | # Returns just the ip part of the cidr. 90 | # 91 | # Examples: 92 | # 93 | # > net.cidr.ip "192.168.1.100/24" 94 | # "192.168.1.100" 95 | # > net.cidr.ip "192.168.1.100" 96 | # "192.168.1.100" 97 | ip = x: head (splitString "/" x); 98 | # canonicalize :: cidr -> cidr 99 | # 100 | # Replaces the ip of the cidr with the canonical network address 101 | # (first contained address in range) 102 | # 103 | # Examples: 104 | # 105 | # > net.cidr.canonicalize "192.168.1.100/24" 106 | # "192.168.1.0/24" 107 | canonicalize = x: libNet.net.cidr.make (libNet.net.cidr.length x) (ip x); 108 | # mergev4 :: [cidrv4 | ipv4] -> (cidrv4 | null) 109 | # 110 | # Returns the smallest cidr network that includes all given networks. 111 | # If no cidr mask is given, /32 is assumed. 112 | # 113 | # Examples: 114 | # 115 | # > net.cidr.mergev4 ["192.168.1.1/24" "192.168.6.1/32"] 116 | # "192.168.0.0/21" 117 | mergev4 = 118 | addrs_: 119 | let 120 | # Append /32 if necessary 121 | addrs = map (x: if hasInfix "/" x then x else "${x}/32") addrs_; 122 | # The smallest occurring length is the first we need to start checking, since 123 | # any greater cidr length represents a smaller address range which 124 | # wouldn't contain all of the original addresses. 125 | startLength = foldl' min 32 (map libNet.net.cidr.length addrs); 126 | possibleLengths = reverseList (range 0 startLength); 127 | # The first ip address will be "expanded" in cidr length until it covers all other 128 | # used addresses. 129 | firstIp = ip (head addrs); 130 | # Return the first (i.e. greatest length -> smallest prefix) cidr length 131 | # in the list that covers all used addresses 132 | bestLength = head ( 133 | filter 134 | # All given addresses must be contained by the generated address. 135 | (len: all (x: libNet.net.cidr.contains (ip x) (libNet.net.cidr.make len firstIp)) addrs) 136 | possibleLengths 137 | ); 138 | in 139 | assert assertMsg (!any (hasInfix ":") addrs) "mergev4 cannot operate on ipv6 addresses"; 140 | if addrs == [ ] then null else libNet.net.cidr.make bestLength firstIp; 141 | # mergev6 :: [cidrv6 | ipv6] -> (cidrv6 | null) 142 | # 143 | # Returns the smallest cidr network that includes all given networks. 144 | # If no cidr mask is given, /128 is assumed. 145 | # 146 | # Examples: 147 | # 148 | # > net.cidr.mergev6 ["fd00:dead:cafe::/64" "fd00:fd12:3456:7890::/56"] 149 | # "fd00:c000::/18" 150 | mergev6 = 151 | addrs_: 152 | let 153 | # Append /128 if necessary 154 | addrs = map (x: if hasInfix "/" x then x else "${x}/128") addrs_; 155 | # The smallest occurring length is the first we need to start checking, since 156 | # any greater cidr length represents a smaller address range which 157 | # wouldn't contain all of the original addresses. 158 | startLength = foldl' min 128 (map libNet.net.cidr.length addrs); 159 | possibleLengths = reverseList (range 0 startLength); 160 | # The first ip address will be "expanded" in cidr length until it covers all other 161 | # used addresses. 162 | firstIp = ip (head addrs); 163 | # Return the first (i.e. greatest length -> smallest prefix) cidr length 164 | # in the list that covers all used addresses 165 | bestLength = head ( 166 | filter 167 | # All given addresses must be contained by the generated address. 168 | (len: all (x: libNet.net.cidr.contains (ip x) (libNet.net.cidr.make len firstIp)) addrs) 169 | possibleLengths 170 | ); 171 | in 172 | assert assertMsg (all (hasInfix ":") addrs) "mergev6 cannot operate on ipv4 addresses"; 173 | if addrs == [ ] then null else libNet.net.cidr.make bestLength firstIp; 174 | # merge :: [cidr] -> { cidrv4 = (cidrv4 | null); cidrv6 = (cidrv4 | null); } 175 | # 176 | # Returns the smallest cidr network that includes all given networks, 177 | # but yields two separate result for all given ipv4 and ipv6 addresses. 178 | # Equivalent to calling mergev4 and mergev6 on a partition individually. 179 | merge = 180 | addrs: 181 | let 182 | v4_and_v6 = partition (hasInfix ":") addrs; 183 | in 184 | { 185 | cidrv4 = mergev4 v4_and_v6.wrong; 186 | cidrv6 = mergev6 v4_and_v6.right; 187 | }; 188 | # assignIps :: cidr -> [int | ip] -> [string] -> [ip] 189 | # 190 | # Assigns a semi-stable ip address from the given cidr network to each hostname. 191 | # The algorithm is based on hashing (abusing sha256) with linear probing. 192 | # The order of hosts doesn't matter. No ip (or offset) from the reserved list 193 | # will be assigned. The network address and broadcast address will always be reserved 194 | # automatically. 195 | # 196 | # Examples: 197 | # 198 | # > net.cidr.assignIps "192.168.100.1/24" [] ["a" "b" "c"] 199 | # { a = "192.168.100.202"; b = "192.168.100.74"; c = "192.168.100.226"; } 200 | # 201 | # > net.cidr.assignIps "192.168.100.1/24" [] ["a" "b" "c" "a-new-elem"] 202 | # { a = "192.168.100.202"; a-new-elem = "192.168.100.88"; b = "192.168.100.74"; c = "192.168.100.226"; } 203 | # 204 | # > net.cidr.assignIps "192.168.100.1/24" [202 "192.168.100.74"] ["a" "b" "c"] 205 | # { a = "192.168.100.203"; b = "192.168.100.75"; c = "192.168.100.226"; } 206 | assignIps = 207 | net: reserved: hosts: 208 | let 209 | cidrSize = libNet.net.cidr.size net; 210 | capacity = libNet.net.cidr.capacity net; 211 | # The base address of the network. Used to convert ip-based reservations to offsets 212 | baseAddr = host 0 net; 213 | # Reserve some values for the network, host and broadcast address. 214 | # The network and broadcast address should never be used, and we 215 | # want to reserve the host address for the host. We also convert 216 | # any ips to offsets here. 217 | init = unique ( 218 | [ 219 | 0 220 | (capacity - 1) 221 | ] 222 | ++ flip map reserved (x: if builtins.typeOf x == "int" then x else -(libNet.net.ip.diff baseAddr x)) 223 | ); 224 | nHosts = builtins.length hosts; 225 | nInit = builtins.length init; 226 | # Pre-sort all hosts, to ensure ordering invariance 227 | sortedHosts = 228 | warnIf ((nInit + nHosts) > 0.3 * capacity) 229 | "assignIps: hash stability may be degraded since utilization is >30%" 230 | (builtins.sort builtins.lessThan hosts); 231 | # Generates a hash (i.e. offset value) for a given hostname 232 | hashElem = 233 | x: 234 | builtins.bitAnd (capacity - 1) ( 235 | hexToDec (builtins.substring 0 16 (builtins.hashString "sha256" x)) 236 | ); 237 | # Do linear probing. Returns the first unused value at or after the given value. 238 | probe = 239 | avoid: value: 240 | if 241 | elem value avoid 242 | # TODO lib.mod 243 | # Poor man's modulo, because nix has no modulo. Luckily we operate on a residue 244 | # class of x modulo 2^n, so we can use bitAnd instead. 245 | then 246 | probe avoid (builtins.bitAnd (capacity - 1) (value + 1)) 247 | else 248 | value; 249 | # Hash a new element and avoid assigning any existing values. 250 | assignOne = 251 | { 252 | assigned, 253 | used, 254 | }: 255 | x: 256 | let 257 | value = probe used (hashElem x); 258 | in 259 | { 260 | assigned = assigned // { 261 | ${x} = host value net; 262 | }; 263 | used = [ value ] ++ used; 264 | }; 265 | in 266 | assert assertMsg ( 267 | cidrSize >= 2 && cidrSize <= 62 268 | ) "assignIps: cidrSize=${toString cidrSize} is not in [2, 62]."; 269 | assert assertMsg (nHosts <= capacity - nInit) 270 | "assignIps: number of hosts (${toString nHosts}) must be <= capacity (${toString capacity}) - reserved (${toString nInit})"; 271 | # Assign an ip in the subnet to each element, in order 272 | (foldl' assignOne { 273 | assigned = { }; 274 | used = init; 275 | } sortedHosts).assigned; 276 | }; 277 | ip = rec { 278 | # Checks whether the given address (with or without cidr notation) is an ipv4 address. 279 | isv4 = x: !isv6 x; 280 | # Checks whether the given address (with or without cidr notation) is an ipv6 address. 281 | isv6 = hasInfix ":"; 282 | }; 283 | mac = { 284 | # Adds offset to the given base address and ensures the result is in 285 | # a locally administered range by replacing the second nibble with a 2. 286 | addPrivate = 287 | base: offset: 288 | let 289 | added = libNet.net.mac.add base offset; 290 | pre = substring 0 1 added; 291 | suf = substring 2 (-1) added; 292 | in 293 | "${pre}2${suf}"; 294 | # assignMacs :: mac (base) -> int (size) -> [int | mac] (reserved) -> [string] (hosts) -> [mac] 295 | # 296 | # Assigns a semi-stable MAC address starting in [base, base + 2^size) to each hostname. 297 | # The algorithm is based on hashing (abusing sha256) with linear probing. 298 | # The order of hosts doesn't matter. No mac (or offset) from the reserved list 299 | # will be assigned. 300 | # 301 | # Examples: 302 | # 303 | # > net.mac.assignMacs "11:22:33:00:00:00" 24 [] ["a" "b" "c"] 304 | # { a = "11:22:33:1b:bd:ca"; b = "11:22:33:39:59:4a"; c = "11:22:33:50:7a:e2"; } 305 | # 306 | # > net.mac.assignMacs "11:22:33:00:00:00" 24 [] ["a" "b" "c" "a-new-elem"] 307 | # { a = "11:22:33:1b:bd:ca"; a-new-elem = "11:22:33:d6:5d:58"; b = "11:22:33:39:59:4a"; c = "11:22:33:50:7a:e2"; } 308 | # 309 | # > net.mac.assignMacs "11:22:33:00:00:00" 24 ["11:22:33:1b:bd:ca"] ["a" "b" "c"] 310 | # { a = "11:22:33:1b:bd:cb"; b = "11:22:33:39:59:4a"; c = "11:22:33:50:7a:e2"; } 311 | assignMacs = 312 | base: size: reserved: hosts: 313 | let 314 | capacity = libNet.bit.left 1 size; 315 | baseAsInt = libNet.net.mac.diff base "00:00:00:00:00:00"; 316 | init = unique ( 317 | flip map reserved (x: if builtins.typeOf x == "int" then x else libNet.net.mac.diff x base) 318 | ); 319 | nHosts = builtins.length hosts; 320 | nInit = builtins.length init; 321 | # Pre-sort all hosts, to ensure ordering invariance 322 | sortedHosts = 323 | warnIf ((nInit + nHosts) > 0.3 * capacity) 324 | "assignMacs: hash stability may be degraded since utilization is >30%" 325 | (builtins.sort builtins.lessThan hosts); 326 | # Generates a hash (i.e. offset value) for a given hostname 327 | hashElem = 328 | x: builtins.bitAnd (capacity - 1) (hexToDec (substring 0 16 (builtins.hashString "sha256" x))); 329 | # Do linear probing. Returns the first unused value at or after the given value. 330 | probe = 331 | avoid: value: 332 | if 333 | elem value avoid 334 | # TODO lib.mod 335 | # Poor man's modulo, because nix has no modulo. Luckily we operate on a residue 336 | # class of x modulo 2^n, so we can use bitAnd instead. 337 | then 338 | probe avoid (builtins.bitAnd (capacity - 1) (value + 1)) 339 | else 340 | value; 341 | # Hash a new element and avoid assigning any existing values. 342 | assignOne = 343 | { 344 | assigned, 345 | used, 346 | }: 347 | x: 348 | let 349 | value = probe used (hashElem x); 350 | in 351 | { 352 | assigned = assigned // { 353 | ${x} = libNet.net.mac.add value base; 354 | }; 355 | used = [ value ] ++ used; 356 | }; 357 | in 358 | assert assertMsg (size >= 2 && size <= 62) "assignMacs: size=${toString size} is not in [2, 62]."; 359 | assert assertMsg ( 360 | builtins.bitAnd (capacity - 1) baseAsInt == 0 361 | ) "assignMacs: the size=${toString size} least significant bits of the base mac address must be 0."; 362 | assert assertMsg (nHosts <= capacity - nInit) 363 | "assignMacs: number of hosts (${toString nHosts}) must be <= capacity (${toString capacity}) - reserved (${toString nInit})"; 364 | # Assign an ip in the subnet to each element, in order 365 | (foldl' assignOne { 366 | assigned = { }; 367 | used = init; 368 | } sortedHosts).assigned; 369 | }; 370 | }; 371 | types.net = libNet.net.types; 372 | }; 373 | } 374 | -------------------------------------------------------------------------------- /lib/netu.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib ? null, 3 | ... 4 | }: 5 | 6 | let 7 | 8 | net = 9 | { 10 | ip = { 11 | 12 | # add :: (ip | mac | integer) -> ip -> ip 13 | # 14 | # Examples: 15 | # 16 | # Adding integer to IPv4: 17 | # > net.ip.add 100 "10.0.0.1" 18 | # "10.0.0.101" 19 | # 20 | # Adding IPv4 to IPv4: 21 | # > net.ip.add "127.0.0.1" "10.0.0.1" 22 | # "137.0.0.2" 23 | # 24 | # Adding IPv6 to IPv4: 25 | # > net.ip.add "::cafe:beef" "10.0.0.1" 26 | # "212.254.186.191" 27 | # 28 | # Adding MAC to IPv4 (overflows): 29 | # > net.ip.add "fe:ed:fa:ce:f0:0d" "10.0.0.1" 30 | # "4.206.240.14" 31 | # 32 | # Adding integer to IPv6: 33 | # > net.ip.add 100 "dead:cafe:beef::" 34 | # "dead:cafe:beef::64" 35 | # 36 | # Adding IPv4 to to IPv6: 37 | # > net.ip.add "127.0.0.1" "dead:cafe:beef::" 38 | # "dead:cafe:beef::7f00:1" 39 | # 40 | # Adding MAC to IPv6: 41 | # > net.ip.add "fe:ed:fa:ce:f0:0d" "dead:cafe:beef::" 42 | # "dead:cafe:beef::feed:face:f00d" 43 | add = 44 | delta: ip: 45 | let 46 | function = "net.ip.add"; 47 | delta' = typechecks.numeric function "delta" delta; 48 | ip' = typechecks.ip function "ip" ip; 49 | in 50 | builders.ip (implementations.ip.add delta' ip'); 51 | 52 | # diff :: ip -> ip -> (integer | ipv6) 53 | # 54 | # net.ip.diff is the reverse of net.ip.add: 55 | # 56 | # net.ip.diff (net.ip.add a b) a = b 57 | # net.ip.diff (net.ip.add a b) b = a 58 | # 59 | # The difference between net.ip.diff and net.ip.subtract is that 60 | # net.ip.diff will try its best to return an integer (falling back 61 | # to an IPv6 if the result is too big to fit in an integer). This is 62 | # useful if you have two hosts that you know are on the same network 63 | # and you just want to calculate the offset between them — a result 64 | # like "0.0.0.10" is not very useful (which is what you would get 65 | # from net.ip.subtract). 66 | diff = 67 | minuend: subtrahend: 68 | let 69 | function = "net.ip.diff"; 70 | minuend' = typechecks.ip function "minuend" minuend; 71 | subtrahend' = typechecks.ip function "subtrahend" subtrahend; 72 | result = implementations.ip.diff minuend' subtrahend'; 73 | in 74 | if result ? ipv6 then builders.ipv6 result else result; 75 | 76 | # subtract :: (ip | mac | integer) -> ip -> ip 77 | # 78 | # net.ip.subtract is also the reverse of net.ip.add: 79 | # 80 | # net.ip.subtract a (net.ip.add a b) = b 81 | # net.ip.subtract b (net.ip.add a b) = a 82 | # 83 | # The difference between net.ip.subtract and net.ip.diff is that 84 | # net.ip.subtract will always return the same type as its "ip" 85 | # parameter. Its implementation takes the "delta" parameter, 86 | # coerces it to be the same type as the "ip" paramter, negates it 87 | # (using two's complement), and then adds it to "ip". 88 | subtract = 89 | delta: ip: 90 | let 91 | function = "net.ip.subtract"; 92 | delta' = typechecks.numeric function "delta" delta; 93 | ip' = typechecks.ip function "ip" ip; 94 | in 95 | builders.ip (implementations.ip.subtract delta' ip'); 96 | }; 97 | 98 | mac = { 99 | 100 | # add :: (ip | mac | integer) -> mac -> mac 101 | # 102 | # Examples: 103 | # 104 | # Adding integer to MAC: 105 | # > net.mac.add 100 "fe:ed:fa:ce:f0:0d" 106 | # "fe:ed:fa:ce:f0:71" 107 | # 108 | # Adding IPv4 to MAC: 109 | # > net.mac.add "127.0.0.1" "fe:ed:fa:ce:f0:0d" 110 | # "fe:ee:79:ce:f0:0e" 111 | # 112 | # Adding IPv6 to MAC: 113 | # > net.mac.add "::cafe:beef" "fe:ed:fa:ce:f0:0d" 114 | # "fe:ee:c5:cd:aa:cb 115 | # 116 | # Adding MAC to MAC: 117 | # > net.mac.add "fe:ed:fa:00:00:00" "00:00:00:ce:f0:0d" 118 | # "fe:ed:fa:ce:f0:0d" 119 | add = 120 | delta: mac: 121 | let 122 | function = "net.mac.add"; 123 | delta' = typechecks.numeric function "delta" delta; 124 | mac' = typechecks.mac function "mac" mac; 125 | in 126 | builders.mac (arithmetic.add delta' mac'); 127 | 128 | # diff :: mac -> mac -> integer 129 | # 130 | # net.mac.diff is the reverse of net.mac.add: 131 | # 132 | # net.mac.diff (net.mac.add a b) a = b 133 | # net.mac.diff (net.mac.add a b) b = a 134 | # 135 | # The difference between net.mac.diff and net.mac.subtract is that 136 | # net.mac.diff will always return an integer. 137 | diff = 138 | minuend: subtrahend: 139 | let 140 | function = "net.mac.diff"; 141 | minuend' = typechecks.mac function "minuend" minuend; 142 | subtrahend' = typechecks.mac function "subtrahend" subtrahend; 143 | in 144 | arithmetic.diff minuend' subtrahend'; 145 | 146 | # subtract :: (ip | mac | integer) -> mac -> mac 147 | # 148 | # net.mac.subtract is also the reverse of net.ip.add: 149 | # 150 | # net.mac.subtract a (net.mac.add a b) = b 151 | # net.mac.subtract b (net.mac.add a b) = a 152 | # 153 | # The difference between net.mac.subtract and net.mac.diff is that 154 | # net.mac.subtract will always return a MAC address. 155 | subtract = 156 | delta: mac: 157 | let 158 | function = "net.mac.subtract"; 159 | delta' = typechecks.numeric function "delta" delta; 160 | mac' = typechecks.mac function "mac" mac; 161 | in 162 | builders.mac (arithmetic.subtract delta' mac'); 163 | }; 164 | 165 | cidr = { 166 | # add :: (ip | mac | integer) -> cidr -> cidr 167 | # 168 | # > net.cidr.add 2 "127.0.0.0/8" 169 | # "129.0.0.0/8" 170 | # 171 | # > net.cidr.add (-2) "127.0.0.0/8" 172 | # "125.0.0.0/8" 173 | add = 174 | delta: cidr: 175 | let 176 | function = "net.cidr.add"; 177 | delta' = typechecks.numeric function "delta" delta; 178 | cidr' = typechecks.cidr function "cidr" cidr; 179 | in 180 | builders.cidr (implementations.cidr.add delta' cidr'); 181 | 182 | # child :: cidr -> cidr -> bool 183 | # 184 | # > net.cidr.child "10.10.10.0/24" "10.0.0.0/8" 185 | # true 186 | # 187 | # > net.cidr.child "127.0.0.0/8" "10.0.0.0/8" 188 | # false 189 | child = 190 | subcidr: cidr: 191 | let 192 | function = "net.cidr.child"; 193 | subcidr' = typechecks.cidr function "subcidr" subcidr; 194 | cidr' = typechecks.cidr function "cidr" cidr; 195 | in 196 | implementations.cidr.child subcidr' cidr'; 197 | 198 | # contains :: ip -> cidr -> bool 199 | # 200 | # > net.cidr.contains "127.0.0.1" "127.0.0.0/8" 201 | # true 202 | # 203 | # > net.cidr.contains "127.0.0.1" "192.168.0.0/16" 204 | # false 205 | contains = 206 | ip: cidr: 207 | let 208 | function = "net.cidr.contains"; 209 | ip' = typechecks.ip function "ip" ip; 210 | cidr' = typechecks.cidr function "cidr" cidr; 211 | in 212 | implementations.cidr.contains ip' cidr'; 213 | 214 | # capacity :: cidr -> integer 215 | # 216 | # > net.cidr.capacity "172.16.0.0/12" 217 | # 1048576 218 | # 219 | # > net.cidr.capacity "dead:cafe:beef::/96" 220 | # 4294967296 221 | # 222 | # > net.cidr.capacity "dead:cafe:beef::/48" (saturates to maxBound) 223 | # 9223372036854775807 224 | capacity = 225 | cidr: 226 | let 227 | function = "net.cidr.capacity"; 228 | cidr' = typechecks.cidr function "cidr" cidr; 229 | in 230 | implementations.cidr.capacity cidr'; 231 | 232 | # host :: (ip | mac | integer) -> cidr -> ip 233 | # 234 | # > net.cidr.host 10000 "10.0.0.0/8" 235 | # 10.0.39.16 236 | # 237 | # > net.cidr.host 10000 "dead:cafe:beef::/64" 238 | # "dead:cafe:beef::2710" 239 | # 240 | # net.cidr.host "127.0.0.1" "dead:cafe:beef::/48" 241 | # > "dead:cafe:beef::7f00:1" 242 | # 243 | # Inpsired by: 244 | # https://www.terraform.io/docs/configuration/functions/cidrhost.html 245 | host = 246 | hostnum: cidr: 247 | let 248 | function = "net.cidr.host"; 249 | hostnum' = typechecks.numeric function "hostnum" hostnum; 250 | cidr' = typechecks.cidr function "cidr" cidr; 251 | in 252 | builders.ip (implementations.cidr.host hostnum' cidr'); 253 | 254 | # length :: cidr -> integer 255 | # 256 | # > net.cidr.prefix "127.0.0.0/8" 257 | # 8 258 | # 259 | # > net.cidr.prefix "dead:cafe:beef::/48" 260 | # 48 261 | length = 262 | cidr: 263 | let 264 | function = "net.cidr.length"; 265 | cidr' = typechecks.cidr function "cidr" cidr; 266 | in 267 | implementations.cidr.length cidr'; 268 | 269 | # make :: integer -> ip -> cidr 270 | # 271 | # > net.cidr.make 24 "192.168.0.150" 272 | # "192.168.0.0/24" 273 | # 274 | # > net.cidr.make 40 "dead:cafe:beef::feed:face:f00d" 275 | # "dead:cafe:be00::/40" 276 | make = 277 | length: base: 278 | let 279 | function = "net.cidr.make"; 280 | length' = typechecks.int function "length" length; 281 | base' = typechecks.ip function "base" base; 282 | in 283 | builders.cidr (implementations.cidr.make length' base'); 284 | 285 | # netmask :: cidr -> ip 286 | # 287 | # > net.cidr.netmask "192.168.0.0/24" 288 | # "255.255.255.0" 289 | # 290 | # > net.cidr.netmask "dead:cafe:beef::/64" 291 | # "ffff:ffff:ffff:ffff::" 292 | netmask = 293 | cidr: 294 | let 295 | function = "net.cidr.netmask"; 296 | cidr' = typechecks.cidr function "cidr" cidr; 297 | in 298 | builders.ip (implementations.cidr.netmask cidr'); 299 | 300 | # size :: cidr -> integer 301 | # 302 | # > net.cidr.prefix "127.0.0.0/8" 303 | # 24 304 | # 305 | # > net.cidr.prefix "dead:cafe:beef::/48" 306 | # 80 307 | size = 308 | cidr: 309 | let 310 | function = "net.cidr.size"; 311 | cidr' = typechecks.cidr function "cidr" cidr; 312 | in 313 | implementations.cidr.size cidr'; 314 | 315 | # subnet :: integer -> (ip | mac | integer) -> cidr -> cidr 316 | # 317 | # > net.cidr.subnet 4 2 "172.16.0.0/12" 318 | # "172.18.0.0/16" 319 | # 320 | # > net.cidr.subnet 4 15 "10.1.2.0/24" 321 | # "10.1.2.240/28" 322 | # 323 | # > net.cidr.subnet 16 162 "fd00:fd12:3456:7890::/56" 324 | # "fd00:fd12:3456:7800:a200::/72" 325 | # 326 | # Inspired by: 327 | # https://www.terraform.io/docs/configuration/functions/cidrsubnet.html 328 | subnet = 329 | length: netnum: cidr: 330 | let 331 | function = "net.cidr.subnet"; 332 | length' = typechecks.int function "length" length; 333 | netnum' = typechecks.numeric function "netnum" netnum; 334 | cidr' = typechecks.cidr function "cidr" cidr; 335 | in 336 | builders.cidr (implementations.cidr.subnet length' netnum' cidr'); 337 | 338 | }; 339 | } 340 | // ( 341 | if builtins.isNull lib then 342 | { } 343 | else 344 | { 345 | types = 346 | let 347 | 348 | mkParsedOptionType = 349 | { 350 | name, 351 | description, 352 | parser, 353 | builder, 354 | }: 355 | let 356 | normalize = 357 | def: 358 | def 359 | // { 360 | value = builder (parser def.value); 361 | }; 362 | in 363 | lib.mkOptionType { 364 | inherit name description; 365 | check = x: builtins.isString x && parser x != null; 366 | merge = loc: defs: lib.mergeEqualOption loc (map normalize defs); 367 | }; 368 | 369 | dependent-ip = 370 | type: cidr: 371 | let 372 | cidrs = if builtins.isList cidr then cidr else [ cidr ]; 373 | in 374 | lib.types.addCheck type (i: lib.any (net.cidr.contains i) cidrs) 375 | // { 376 | description = type.description + " in ${builtins.concatStringsSep " or " cidrs}"; 377 | }; 378 | 379 | dependent-cidr = 380 | type: cidr: 381 | let 382 | cidrs = if builtins.isList cidr then cidr else [ cidr ]; 383 | in 384 | lib.types.addCheck type (i: lib.any (net.cidr.child i) cidrs) 385 | // { 386 | description = type.description + " in ${builtins.concatStringsSep " or " cidrs}"; 387 | }; 388 | 389 | in 390 | rec { 391 | 392 | ip = mkParsedOptionType { 393 | name = "ip"; 394 | description = "IPv4 or IPv6 address"; 395 | parser = parsers.ip; 396 | builder = builders.ip; 397 | }; 398 | 399 | ip-in = dependent-ip ip; 400 | 401 | ipv4 = mkParsedOptionType { 402 | name = "ipv4"; 403 | description = "IPv4 address"; 404 | parser = parsers.ipv4; 405 | builder = builders.ipv4; 406 | }; 407 | 408 | ipv4-in = dependent-ip ipv4; 409 | 410 | ipv6 = mkParsedOptionType { 411 | name = "ipv6"; 412 | description = "IPv6 address"; 413 | parser = parsers.ipv6; 414 | builder = builders.ipv6; 415 | }; 416 | 417 | ipv6-in = dependent-ip ipv6; 418 | 419 | cidr = mkParsedOptionType { 420 | name = "cidr"; 421 | description = "IPv4 or IPv6 address range in CIDR notation"; 422 | parser = parsers.cidr; 423 | builder = builders.cidr; 424 | }; 425 | 426 | cidr-in = dependent-cidr cidr; 427 | 428 | cidrv4 = mkParsedOptionType { 429 | name = "cidrv4"; 430 | description = "IPv4 address range in CIDR notation"; 431 | parser = parsers.cidrv4; 432 | builder = builders.cidrv4; 433 | }; 434 | 435 | cidrv4-in = dependent-cidr cidrv4; 436 | 437 | cidrv6 = mkParsedOptionType { 438 | name = "cidrv6"; 439 | description = "IPv6 address range in CIDR notation"; 440 | parser = parsers.cidrv6; 441 | builder = builders.cidrv6; 442 | }; 443 | 444 | cidrv6-in = dependent-cidr cidrv6; 445 | 446 | mac = mkParsedOptionType { 447 | name = "mac"; 448 | description = "MAC address"; 449 | parser = parsers.mac; 450 | builder = builders.mac; 451 | }; 452 | 453 | }; 454 | } 455 | ); 456 | 457 | list = { 458 | cons = a: b: [ a ] ++ b; 459 | }; 460 | 461 | bit = 462 | let 463 | inherit (import ./shift.nix) left arithmeticRight logicalRight; 464 | leftOrArithRight = a: if a > 0 then left a else arithmeticRight (-a); 465 | arithRightOrLeft = a: if a > 0 then arithmeticRight a else left (-a); 466 | 467 | not = builtins.bitXor (-1); 468 | 469 | mask = n: builtins.bitAnd (left n 1 - 1); 470 | in 471 | { 472 | inherit 473 | left 474 | arithmeticRight 475 | logicalRight 476 | not 477 | mask 478 | leftOrArithRight 479 | arithRightOrLeft 480 | ; 481 | }; 482 | 483 | math = rec { 484 | max = a: b: if a > b then a else b; 485 | 486 | min = a: b: if a < b then a else b; 487 | 488 | clamp = 489 | a: b: c: 490 | max a (min b c); 491 | }; 492 | 493 | parsers = 494 | let 495 | 496 | # fmap :: (a -> b) -> parser a -> parser b 497 | fmap = f: ma: bind ma (a: pure (f a)); 498 | 499 | # pure :: a -> parser a 500 | pure = a: string: { 501 | leftovers = string; 502 | result = a; 503 | }; 504 | 505 | # liftA2 :: (a -> b -> c) -> parser a -> parser b -> parser c 506 | liftA2 = 507 | f: ma: mb: 508 | bind ma (a: bind mb (b: pure (f a b))); 509 | liftA3 = 510 | f: a: b: 511 | ap (liftA2 f a b); 512 | liftA4 = 513 | f: a: b: c: 514 | ap (liftA3 f a b c); 515 | liftA5 = 516 | f: a: b: c: d: 517 | ap (liftA4 f a b c d); 518 | liftA6 = 519 | f: a: b: c: d: e: 520 | ap (liftA5 f a b c d e); 521 | 522 | # ap :: parser (a -> b) -> parser a -> parser b 523 | ap = liftA2 (a: a); 524 | 525 | # then_ :: parser a -> parser b -> parser b 526 | then_ = liftA2 (_: b: b); 527 | 528 | # empty :: parser a 529 | empty = _: null; 530 | 531 | # alt :: parser a -> parser a -> parser a 532 | alt = 533 | left: right: string: 534 | let 535 | result = left string; 536 | in 537 | if builtins.isNull result then right string else result; 538 | 539 | # guard :: bool -> parser {} 540 | guard = condition: if condition then pure { } else empty; 541 | 542 | # mfilter :: (a -> bool) -> parser a -> parser a 543 | mfilter = f: parser: bind parser (a: then_ (guard (f a)) (pure a)); 544 | 545 | # some :: parser a -> parser [a] 546 | some = v: liftA2 list.cons v (many v); 547 | 548 | # many :: parser a -> parser [a] 549 | many = v: alt (some v) (pure [ ]); 550 | 551 | # bind :: parser a -> (a -> parser b) -> parser b 552 | bind = 553 | parser: f: string: 554 | let 555 | a = parser string; 556 | in 557 | if builtins.isNull a then null else f a.result a.leftovers; 558 | 559 | # run :: parser a -> string -> maybe a 560 | run = 561 | parser: string: 562 | let 563 | result = parser string; 564 | in 565 | if builtins.isNull result || result.leftovers != "" then null else result.result; 566 | 567 | next = 568 | string: 569 | if string == "" then 570 | null 571 | else 572 | { 573 | leftovers = builtins.substring 1 (-1) string; 574 | result = builtins.substring 0 1 string; 575 | }; 576 | 577 | # Count how many characters were consumed by a parser 578 | count = 579 | parser: string: 580 | let 581 | result = parser string; 582 | in 583 | if builtins.isNull result then 584 | null 585 | else 586 | result 587 | // { 588 | result = { 589 | inherit (result) result; 590 | count = with result; builtins.stringLength string - builtins.stringLength leftovers; 591 | }; 592 | }; 593 | 594 | # Limit the parser to n characters at most 595 | limit = n: parser: fmap (a: a.result) (mfilter (a: a.count <= n) (count parser)); 596 | 597 | # Ensure the parser consumes exactly n characters 598 | exactly = n: parser: fmap (a: a.result) (mfilter (a: a.count == n) (count parser)); 599 | 600 | char = c: bind next (c': guard (c == c')); 601 | 602 | string = 603 | css: 604 | if css == "" then 605 | pure { } 606 | else 607 | let 608 | c = builtins.substring 0 1 css; 609 | cs = builtins.substring 1 (-1) css; 610 | in 611 | then_ (char c) (string cs); 612 | 613 | digit = set: bind next (c: then_ (guard (builtins.hasAttr c set)) (pure (builtins.getAttr c set))); 614 | 615 | decimalDigits = { 616 | "0" = 0; 617 | "1" = 1; 618 | "2" = 2; 619 | "3" = 3; 620 | "4" = 4; 621 | "5" = 5; 622 | "6" = 6; 623 | "7" = 7; 624 | "8" = 8; 625 | "9" = 9; 626 | }; 627 | 628 | hexadecimalDigits = decimalDigits // { 629 | "a" = 10; 630 | "b" = 11; 631 | "c" = 12; 632 | "d" = 13; 633 | "e" = 14; 634 | "f" = 15; 635 | "A" = 10; 636 | "B" = 11; 637 | "C" = 12; 638 | "D" = 13; 639 | "E" = 14; 640 | "F" = 15; 641 | }; 642 | 643 | fromDecimalDigits = builtins.foldl' (a: c: a * 10 + c) 0; 644 | fromHexadecimalDigits = builtins.foldl' (a: builtins.bitOr (bit.left 4 a)) 0; 645 | 646 | # disallow leading zeros 647 | decimal = bind (digit decimalDigits) ( 648 | n: 649 | if n == 0 then 650 | pure 0 651 | else 652 | fmap (ns: fromDecimalDigits (list.cons n ns)) (many (digit decimalDigits)) 653 | ); 654 | 655 | hexadecimal = fmap fromHexadecimalDigits (some (digit hexadecimalDigits)); 656 | 657 | ipv4 = 658 | let 659 | dot = char "."; 660 | 661 | octet = mfilter (n: n < 256) decimal; 662 | 663 | octet' = then_ dot octet; 664 | 665 | fromOctets = a: b: c: d: { 666 | ipv4 = builtins.bitOr (bit.left 8 (builtins.bitOr (bit.left 8 (builtins.bitOr (bit.left 8 a) b)) c)) d; 667 | }; 668 | in 669 | liftA4 fromOctets octet octet' octet' octet'; 670 | 671 | # This is more or less a literal translation of 672 | # https://hackage.haskell.org/package/ip/docs/src/Net.IPv6.html#parser 673 | ipv6 = 674 | let 675 | colon = char ":"; 676 | 677 | hextet = limit 4 hexadecimal; 678 | 679 | fromHextets = 680 | hextets: 681 | if builtins.length hextets != 8 then 682 | empty 683 | else 684 | let 685 | a = builtins.elemAt hextets 0; 686 | b = builtins.elemAt hextets 1; 687 | c = builtins.elemAt hextets 2; 688 | d = builtins.elemAt hextets 3; 689 | e = builtins.elemAt hextets 4; 690 | f = builtins.elemAt hextets 5; 691 | g = builtins.elemAt hextets 6; 692 | h = builtins.elemAt hextets 7; 693 | in 694 | pure { 695 | ipv6 = { 696 | a = builtins.bitOr (bit.left 16 a) b; 697 | b = builtins.bitOr (bit.left 16 c) d; 698 | c = builtins.bitOr (bit.left 16 e) f; 699 | d = builtins.bitOr (bit.left 16 g) h; 700 | }; 701 | }; 702 | 703 | ipv4' = fmap ( 704 | address: 705 | let 706 | upper = bit.arithmeticRight 16 address.ipv4; 707 | lower = bit.mask 16 address.ipv4; 708 | in 709 | [ 710 | upper 711 | lower 712 | ] 713 | ) ipv4; 714 | 715 | part = 716 | n: 717 | let 718 | n' = n + 1; 719 | hex = liftA2 list.cons hextet (then_ colon (alt (then_ colon (doubleColon n')) (part n'))); 720 | in 721 | if n == 7 then 722 | fmap (a: [ a ]) hextet 723 | else if n == 6 then 724 | alt ipv4' hex 725 | else 726 | hex; 727 | 728 | doubleColon = 729 | n: 730 | bind (alt afterDoubleColon (pure [ ])) ( 731 | rest: 732 | let 733 | missing = 8 - n - builtins.length rest; 734 | in 735 | if missing < 0 then empty else pure (builtins.genList (_: 0) missing ++ rest) 736 | ); 737 | 738 | afterDoubleColon = alt ipv4' ( 739 | liftA2 list.cons hextet (alt (then_ colon afterDoubleColon) (pure [ ])) 740 | ); 741 | 742 | in 743 | bind (alt (then_ (string "::") (doubleColon 0)) (part 0)) fromHextets; 744 | 745 | cidrv4 = liftA2 (base: length: implementations.cidr.make length base) ipv4 ( 746 | then_ (char "/") (mfilter (n: n <= 32) decimal) 747 | ); 748 | 749 | cidrv6 = liftA2 (base: length: implementations.cidr.make length base) ipv6 ( 750 | then_ (char "/") (mfilter (n: n <= 128) decimal) 751 | ); 752 | 753 | mac = 754 | let 755 | colon = char ":"; 756 | 757 | octet = exactly 2 hexadecimal; 758 | 759 | octet' = then_ colon octet; 760 | 761 | fromOctets = a: b: c: d: e: f: { 762 | mac = builtins.bitOr (bit.left 8 (builtins.bitOr (bit.left 8 (builtins.bitOr (bit.left 8 (builtins.bitOr (bit.left 8 (builtins.bitOr (bit.left 8 a) b)) c)) d)) e)) f; 763 | }; 764 | in 765 | liftA6 fromOctets octet octet' octet' octet' octet' octet'; 766 | 767 | in 768 | { 769 | ipv4 = run ipv4; 770 | ipv6 = run ipv6; 771 | ip = run (alt ipv4 ipv6); 772 | cidrv4 = run cidrv4; 773 | cidrv6 = run cidrv6; 774 | cidr = run (alt cidrv4 cidrv6); 775 | mac = run mac; 776 | numeric = run (alt (alt ipv4 ipv6) mac); 777 | }; 778 | 779 | builders = 780 | let 781 | 782 | ipv4 = 783 | address: 784 | let 785 | abcd = address.ipv4; 786 | abc = bit.arithmeticRight 8 abcd; 787 | ab = bit.arithmeticRight 8 abc; 788 | a = bit.arithmeticRight 8 ab; 789 | b = bit.mask 8 ab; 790 | c = bit.mask 8 abc; 791 | d = bit.mask 8 abcd; 792 | in 793 | builtins.concatStringsSep "." ( 794 | map toString [ 795 | a 796 | b 797 | c 798 | d 799 | ] 800 | ); 801 | 802 | # This is more or less a literal translation of 803 | # https://hackage.haskell.org/package/ip/docs/src/Net.IPv6.html#encode 804 | ipv6 = 805 | address: 806 | let 807 | 808 | digits = "0123456789abcdef"; 809 | 810 | toHexString = 811 | n: 812 | let 813 | rest = bit.arithmeticRight 4 n; 814 | current = bit.mask 4 n; 815 | prefix = if rest == 0 then "" else toHexString rest; 816 | in 817 | "${prefix}${builtins.substring current 1 digits}"; 818 | 819 | in 820 | if (with address.ipv6; a == 0 && b == 0 && c == 0 && d > 65535) then 821 | "::${ipv4 { ipv4 = address.ipv6.d; }}" 822 | else if (with address.ipv6; a == 0 && b == 0 && c == 65535) then 823 | "::ffff:${ipv4 { ipv4 = address.ipv6.d; }}" 824 | else 825 | let 826 | 827 | a = bit.arithmeticRight 16 address.ipv6.a; 828 | b = bit.mask 16 address.ipv6.a; 829 | c = bit.arithmeticRight 16 address.ipv6.b; 830 | d = bit.mask 16 address.ipv6.b; 831 | e = bit.arithmeticRight 16 address.ipv6.c; 832 | f = bit.mask 16 address.ipv6.c; 833 | g = bit.arithmeticRight 16 address.ipv6.d; 834 | h = bit.mask 16 address.ipv6.d; 835 | 836 | hextets = [ 837 | a 838 | b 839 | c 840 | d 841 | e 842 | f 843 | g 844 | h 845 | ]; 846 | 847 | # calculate the position and size of the longest sequence of 848 | # zeroes within the list of hextets 849 | longest = 850 | let 851 | go = 852 | i: current: best: 853 | if i < builtins.length hextets then 854 | let 855 | n = builtins.elemAt hextets i; 856 | 857 | current' = 858 | if n == 0 then 859 | if builtins.isNull current then 860 | { 861 | size = 1; 862 | position = i; 863 | } 864 | else 865 | current 866 | // { 867 | size = current.size + 1; 868 | } 869 | else 870 | null; 871 | 872 | best' = 873 | if n == 0 then 874 | if builtins.isNull best then 875 | current' 876 | else if current'.size > best.size then 877 | current' 878 | else 879 | best 880 | else 881 | best; 882 | in 883 | go (i + 1) current' best' 884 | else 885 | best; 886 | in 887 | go 0 null null; 888 | 889 | format = hextets: builtins.concatStringsSep ":" (map toHexString hextets); 890 | in 891 | if builtins.isNull longest then 892 | format hextets 893 | else 894 | let 895 | sublist = 896 | i: length: xs: 897 | map (builtins.elemAt xs) (builtins.genList (x: x + i) length); 898 | 899 | end = longest.position + longest.size; 900 | 901 | before = sublist 0 longest.position hextets; 902 | 903 | after = sublist end (builtins.length hextets - end) hextets; 904 | in 905 | "${format before}::${format after}"; 906 | 907 | ip = address: if address ? ipv4 then ipv4 address else ipv6 address; 908 | 909 | cidrv4 = cidr: "${ipv4 cidr.base}/${toString cidr.length}"; 910 | 911 | cidrv6 = cidr: "${ipv6 cidr.base}/${toString cidr.length}"; 912 | 913 | cidr = cidr: "${ip cidr.base}/${toString cidr.length}"; 914 | 915 | mac = 916 | address: 917 | let 918 | digits = "0123456789abcdef"; 919 | octet = 920 | n: 921 | let 922 | upper = bit.arithmeticRight 4 n; 923 | lower = bit.mask 4 n; 924 | in 925 | "${builtins.substring upper 1 digits}${builtins.substring lower 1 digits}"; 926 | a = bit.mask 8 (bit.arithmeticRight 40 address.mac); 927 | b = bit.mask 8 (bit.arithmeticRight 32 address.mac); 928 | c = bit.mask 8 (bit.arithmeticRight 24 address.mac); 929 | d = bit.mask 8 (bit.arithmeticRight 16 address.mac); 930 | e = bit.mask 8 (bit.arithmeticRight 8 address.mac); 931 | f = bit.mask 8 (bit.arithmeticRight 0 address.mac); 932 | in 933 | "${octet a}:${octet b}:${octet c}:${octet d}:${octet e}:${octet f}"; 934 | 935 | in 936 | { 937 | inherit 938 | ipv4 939 | ipv6 940 | ip 941 | cidrv4 942 | cidrv6 943 | cidr 944 | mac 945 | ; 946 | }; 947 | 948 | arithmetic = rec { 949 | # or :: (ip | mac | integer) -> (ip | mac | integer) -> (ip | mac | integer) 950 | or = 951 | a_: b: 952 | let 953 | a = coerce b a_; 954 | in 955 | if a ? ipv6 then 956 | { 957 | ipv6 = { 958 | a = builtins.bitOr a.ipv6.a b.ipv6.a; 959 | b = builtins.bitOr a.ipv6.b b.ipv6.b; 960 | c = builtins.bitOr a.ipv6.c b.ipv6.c; 961 | d = builtins.bitOr a.ipv6.d b.ipv6.d; 962 | }; 963 | } 964 | else if a ? ipv4 then 965 | { 966 | ipv4 = builtins.bitOr a.ipv4 b.ipv4; 967 | } 968 | else if a ? mac then 969 | { 970 | mac = builtins.bitOr a.mac b.mac; 971 | } 972 | else 973 | builtins.bitOr a b; 974 | 975 | # and :: (ip | mac | integer) -> (ip | mac | integer) -> (ip | mac | integer) 976 | and = 977 | a_: b: 978 | let 979 | a = coerce b a_; 980 | in 981 | if a ? ipv6 then 982 | { 983 | ipv6 = { 984 | a = builtins.bitAnd a.ipv6.a b.ipv6.a; 985 | b = builtins.bitAnd a.ipv6.b b.ipv6.b; 986 | c = builtins.bitAnd a.ipv6.c b.ipv6.c; 987 | d = builtins.bitAnd a.ipv6.d b.ipv6.d; 988 | }; 989 | } 990 | else if a ? ipv4 then 991 | { 992 | ipv4 = builtins.bitAnd a.ipv4 b.ipv4; 993 | } 994 | else if a ? mac then 995 | { 996 | mac = builtins.bitAnd a.mac b.mac; 997 | } 998 | else 999 | builtins.bitAnd a b; 1000 | 1001 | # not :: (ip | mac | integer) -> (ip | mac | integer) 1002 | not = 1003 | a: 1004 | if a ? ipv6 then 1005 | { 1006 | ipv6 = { 1007 | a = bit.mask 32 (bit.not a.ipv6.a); 1008 | b = bit.mask 32 (bit.not a.ipv6.b); 1009 | c = bit.mask 32 (bit.not a.ipv6.c); 1010 | d = bit.mask 32 (bit.not a.ipv6.d); 1011 | }; 1012 | } 1013 | else if a ? ipv4 then 1014 | { 1015 | ipv4 = bit.mask 32 (bit.not a.ipv4); 1016 | } 1017 | else if a ? mac then 1018 | { 1019 | mac = bit.mask 48 (bit.not a.mac); 1020 | } 1021 | else 1022 | bit.not a; 1023 | 1024 | # add :: (ip | mac | integer) -> (ip | mac | integer) -> (ip | mac | integer) 1025 | add = 1026 | let 1027 | split = a: { 1028 | fst = bit.mask 32 (bit.arithmeticRight 32 a); 1029 | snd = bit.mask 32 a; 1030 | }; 1031 | in 1032 | a_: b: 1033 | let 1034 | a = coerce b a_; 1035 | in 1036 | if a ? ipv6 then 1037 | let 1038 | a' = split (a.ipv6.a + b.ipv6.a + b'.fst); 1039 | b' = split (a.ipv6.b + b.ipv6.b + c'.fst); 1040 | c' = split (a.ipv6.c + b.ipv6.c + d'.fst); 1041 | d' = split (a.ipv6.d + b.ipv6.d); 1042 | in 1043 | { 1044 | ipv6 = { 1045 | a = a'.snd; 1046 | b = b'.snd; 1047 | c = c'.snd; 1048 | d = d'.snd; 1049 | }; 1050 | } 1051 | else if a ? ipv4 then 1052 | { 1053 | ipv4 = bit.mask 32 (a.ipv4 + b.ipv4); 1054 | } 1055 | else if a ? mac then 1056 | { 1057 | mac = bit.mask 48 (a.mac + b.mac); 1058 | } 1059 | else 1060 | a + b; 1061 | 1062 | # subtract :: (ip | mac | integer) -> (ip | mac | integer) -> (ip | mac | integer) 1063 | subtract = a: b: add (add 1 (not (coerce b a))) b; 1064 | 1065 | # diff :: (ip | mac | integer) -> (ip | mac | integer) -> (ipv6 | integer) 1066 | diff = 1067 | a: b: 1068 | let 1069 | toIPv6 = coerce { ipv6.a = 0; }; 1070 | result = (subtract b (toIPv6 a)).ipv6; 1071 | max32 = bit.left 32 1 - 1; 1072 | in 1073 | if 1074 | result.a == 0 && result.b == 0 && bit.arithmeticRight 31 result.c == 0 1075 | || result.a == max32 && result.b == max32 && bit.arithmeticRight 31 result.c == 1 1076 | then 1077 | builtins.bitOr (bit.left 32 result.c) result.d 1078 | else 1079 | { 1080 | ipv6 = result; 1081 | }; 1082 | 1083 | # left :: integer -> (ip | mac | integer) -> (ip | mac | integer) 1084 | left = i: right (-i); 1085 | 1086 | # right :: integer -> (ip | mac | integer) -> (ip | mac | integer) 1087 | right = 1088 | let 1089 | step = i: x: { 1090 | _1 = bit.mask 32 (bit.arithRightOrLeft (i + 96) x); 1091 | _2 = bit.mask 32 (bit.arithRightOrLeft (i + 64) x); 1092 | _3 = bit.mask 32 (bit.arithRightOrLeft (i + 32) x); 1093 | _4 = bit.mask 32 (bit.arithRightOrLeft i x); 1094 | _5 = bit.mask 32 (bit.arithRightOrLeft (i - 32) x); 1095 | _6 = bit.mask 32 (bit.arithRightOrLeft (i - 64) x); 1096 | _7 = bit.mask 32 (bit.arithRightOrLeft (i - 96) x); 1097 | }; 1098 | ors = builtins.foldl' builtins.bitOr 0; 1099 | in 1100 | i: x: 1101 | if x ? ipv6 then 1102 | let 1103 | a' = step i x.ipv6.a; 1104 | b' = step i x.ipv6.b; 1105 | c' = step i x.ipv6.c; 1106 | d' = step i x.ipv6.d; 1107 | in 1108 | { 1109 | ipv6 = { 1110 | a = ors [ 1111 | a'._4 1112 | b'._3 1113 | c'._2 1114 | d'._1 1115 | ]; 1116 | b = ors [ 1117 | a'._5 1118 | b'._4 1119 | c'._3 1120 | d'._2 1121 | ]; 1122 | c = ors [ 1123 | a'._6 1124 | b'._5 1125 | c'._4 1126 | d'._3 1127 | ]; 1128 | d = ors [ 1129 | a'._7 1130 | b'._6 1131 | c'._5 1132 | d'._4 1133 | ]; 1134 | }; 1135 | } 1136 | else if x ? ipv4 then 1137 | { 1138 | ipv4 = bit.mask 32 (bit.arithRightOrLeft i x.ipv4); 1139 | } 1140 | else if x ? mac then 1141 | { 1142 | mac = bit.mask 48 (bit.arithRightOrLeft i x.mac); 1143 | } 1144 | else 1145 | bit.arithRightOrLeft i x; 1146 | 1147 | # shadow :: integer -> (ip | mac | integer) -> (ip | mac | integer) 1148 | shadow = n: a: and (right n (left n (coerce a (-1)))) a; 1149 | 1150 | # coshadow :: integer -> (ip | mac | integer) -> (ip | mac | integer) 1151 | coshadow = n: a: and (not (right n (left n (coerce a (-1))))) a; 1152 | 1153 | # coerce target value 1154 | # coerce value to the type of target 1155 | # coerce :: A -> (ip | mac | integer) -> A 1156 | # where A :: (ip | mac | integer) 1157 | coerce = 1158 | target: value: 1159 | if target ? ipv6 then 1160 | if value ? ipv6 then 1161 | value 1162 | else if value ? ipv4 then 1163 | { 1164 | ipv6 = { 1165 | a = 0; 1166 | b = 0; 1167 | c = 0; 1168 | d = value.ipv4; 1169 | }; 1170 | } 1171 | else if value ? mac then 1172 | { 1173 | ipv6 = { 1174 | a = 0; 1175 | b = 0; 1176 | c = bit.arithmeticRight 32 value.mac; 1177 | d = bit.mask 32 value.mac; 1178 | }; 1179 | } 1180 | else 1181 | { 1182 | ipv6 = { 1183 | a = bit.mask 32 (bit.arithmeticRight 96 value); 1184 | b = bit.mask 32 (bit.arithmeticRight 64 value); 1185 | c = bit.mask 32 (bit.arithmeticRight 32 value); 1186 | d = bit.mask 32 value; 1187 | }; 1188 | } 1189 | else if target ? ipv4 then 1190 | if value ? ipv6 then 1191 | { 1192 | ipv4 = value.ipv6.d; 1193 | } 1194 | else if value ? ipv4 then 1195 | value 1196 | else if value ? mac then 1197 | { 1198 | ipv4 = bit.mask 32 value.mac; 1199 | } 1200 | else 1201 | { 1202 | ipv4 = bit.mask 32 value; 1203 | } 1204 | else if target ? mac then 1205 | if value ? ipv6 then 1206 | { 1207 | mac = builtins.bitOr (bit.left 32 (bit.mask 16 value.ipv6.c)) value.ipv6.d; 1208 | } 1209 | else if value ? ipv4 then 1210 | { 1211 | mac = value.ipv4; 1212 | } 1213 | else if value ? mac then 1214 | value 1215 | else 1216 | { 1217 | mac = bit.mask 48 value; 1218 | } 1219 | else if value ? ipv6 then 1220 | builtins.foldl' builtins.bitOr 0 [ 1221 | (bit.left 96 value.ipv6.a) 1222 | (bit.left 64 value.ipv6.b) 1223 | (bit.left 32 value.ipv6.c) 1224 | value.ipv6.d 1225 | ] 1226 | else 1227 | value.ipv4 or value.mac or value; 1228 | }; 1229 | 1230 | implementations = { 1231 | cidr = rec { 1232 | # add :: (ip | mac | integer) -> cidr -> cidr 1233 | add = 1234 | delta: cidr: 1235 | let 1236 | size' = size cidr; 1237 | in 1238 | { 1239 | base = arithmetic.left size' (arithmetic.add delta (arithmetic.arithRightOrLeft size' cidr.base)); 1240 | inherit (cidr) length; 1241 | }; 1242 | 1243 | # capacity :: cidr -> integer 1244 | capacity = 1245 | cidr: 1246 | let 1247 | size' = size cidr; 1248 | in 1249 | if size' > 62 then 1250 | 9223372036854775807 # maxBound to prevent overflow 1251 | else 1252 | bit.left size' 1; 1253 | 1254 | # child :: cidr -> cidr -> bool 1255 | child = subcidr: cidr: length subcidr > length cidr && contains (host 0 subcidr) cidr; 1256 | 1257 | # contains :: ip -> cidr -> bool 1258 | contains = ip: cidr: host 0 (make cidr.length ip) == host 0 cidr; 1259 | 1260 | # host :: (ip | mac | integer) -> cidr -> ip 1261 | host = 1262 | index: cidr: 1263 | let 1264 | index' = arithmetic.coerce cidr.base index; 1265 | in 1266 | arithmetic.or (arithmetic.shadow cidr.length index') cidr.base; 1267 | 1268 | # length :: cidr -> integer 1269 | length = cidr: cidr.length; 1270 | 1271 | # netmask :: cidr -> ip 1272 | netmask = cidr: arithmetic.coshadow cidr.length (arithmetic.coerce cidr.base (-1)); 1273 | 1274 | # size :: cidr -> integer 1275 | size = cidr: (if cidr.base ? ipv6 then 128 else 32) - cidr.length; 1276 | 1277 | # subnet :: integer -> (ip | mac | integer) -> cidr -> cidr 1278 | subnet = 1279 | length: index: cidr: 1280 | let 1281 | length' = cidr.length + length; 1282 | index' = arithmetic.coerce cidr.base index; 1283 | size = (if cidr.base ? ipv6 then 128 else 32) - length'; 1284 | in 1285 | make length' (host (arithmetic.left size index') cidr); 1286 | 1287 | # make :: integer -> ip -> cidr 1288 | make = 1289 | length: base: 1290 | let 1291 | length' = math.clamp 0 (if base ? ipv6 then 128 else 32) length; 1292 | in 1293 | { 1294 | base = arithmetic.coshadow length' base; 1295 | length = length'; 1296 | }; 1297 | }; 1298 | }; 1299 | 1300 | typechecks = 1301 | let 1302 | 1303 | fail = 1304 | description: function: argument: 1305 | builtins.throw "${function}: ${argument} parameter must be ${description}"; 1306 | 1307 | meta = 1308 | parser: description: function: argument: input: 1309 | let 1310 | error = fail description function argument; 1311 | in 1312 | if !builtins.isString input then 1313 | error 1314 | else 1315 | let 1316 | result = parser input; 1317 | in 1318 | if builtins.isNull result then error else result; 1319 | 1320 | in 1321 | { 1322 | int = 1323 | function: argument: input: 1324 | if builtins.isInt input then input else fail "an integer" function argument; 1325 | ip = meta parsers.ip "an IPv4 or IPv6 address"; 1326 | cidr = meta parsers.cidr "an IPv4 or IPv6 address range in CIDR notation"; 1327 | mac = meta parsers.mac "a MAC address"; 1328 | numeric = 1329 | function: argument: input: 1330 | if builtins.isInt input then 1331 | input 1332 | else 1333 | meta parsers.numeric "an integer or IPv4, IPv6 or MAC address" function argument input; 1334 | }; 1335 | 1336 | in 1337 | 1338 | { 1339 | lib = { 1340 | inherit 1341 | arithmetic 1342 | net 1343 | typechecks 1344 | parsers 1345 | implementations 1346 | bit 1347 | ; 1348 | }; 1349 | } 1350 | -------------------------------------------------------------------------------- /lib/shift.nix: -------------------------------------------------------------------------------- 1 | let 2 | # lut = [ 3 | # 1 # 0 4 | # 2 # 1 5 | # 4 # 2 6 | # 8 # 3 7 | # 16 # 4 8 | # 32 # 5 9 | # 64 # 6 10 | # 128 # 7 11 | # 256 # 8 12 | # 512 # 9 13 | # 1024 # 10 14 | # 2048 # 11 15 | # 4096 # 12 16 | # 8192 # 13 17 | # 16384 # 14 18 | # 32768 # 15 19 | # 65536 # 16 20 | # 131072 # 17 21 | # 262144 # 18 22 | # 524288 # 19 23 | # 1048576 # 20 24 | # 2097152 # 21 25 | # 4194304 # 22 26 | # 8388608 # 23 27 | # 16777216 # 24 28 | # 33554432 # 25 29 | # 67108864 # 26 30 | # 134217728 # 27 31 | # 268435456 # 28 32 | # 536870912 # 29 33 | # 1073741824 # 30 34 | # 2147483648 # 31 35 | # 4294967296 # 32 36 | # 8589934592 # 33 37 | # 17179869184 # 34 38 | # 34359738368 # 35 39 | # 68719476736 # 36 40 | # 137438953472 # 37 41 | # 274877906944 # 38 42 | # 549755813888 # 39 43 | # 1099511627776 # 40 44 | # 2199023255552 # 41 45 | # 4398046511104 # 42 46 | # 8796093022208 # 43 47 | # 17592186044416 # 44 48 | # 35184372088832 # 45 49 | # 70368744177664 # 46 50 | # 140737488355328 # 47 51 | # 281474976710656 # 48 52 | # 562949953421312 # 49 53 | # 1125899906842624 # 50 54 | # 2251799813685248 # 51 55 | # 4503599627370496 # 52 56 | # 9007199254740992 # 53 57 | # 18014398509481984 # 54 58 | # 36028797018963968 # 55 59 | # 72057594037927936 # 56 60 | # 144115188075855872 # 57 61 | # 288230376151711744 # 58 62 | # 576460752303423488 # 59 63 | # 1152921504606846976 # 60 64 | # 2305843009213693952 # 61 65 | # 4611686018427387904 # 62 66 | # ]; 67 | lut = builtins.foldl' (l: n: l ++ [ (2 * builtins.elemAt l n) ]) [ 1 ] (builtins.genList (x: x) 62); 68 | intmin = (-9223372036854775807) - 1; 69 | intmax = 9223372036854775807; 70 | left = 71 | a: b: 72 | if a >= 64 then 73 | # It's allowed to shift out all bits 74 | 0 75 | else if a == 0 then 76 | b 77 | else if a < 0 then 78 | throw "Inverse Left Shift not supported" 79 | else 80 | let 81 | inv = 63 - a; 82 | mask = if inv == 63 then intmax else (builtins.elemAt lut inv) - 1; 83 | masked = builtins.bitAnd b mask; 84 | checker = if inv == 63 then intmin else builtins.elemAt lut inv; 85 | negate = (builtins.bitAnd b checker) != 0; 86 | mult = if a == 63 then intmin else builtins.elemAt lut a; 87 | result = masked * mult; 88 | in 89 | if !negate then result else intmin + result; 90 | logicalRight = 91 | a: b: 92 | if a >= 64 then 93 | 0 94 | else if a == 0 then 95 | b 96 | else if a < 0 then 97 | throw "Inverse right Shift not supported" 98 | else 99 | let 100 | masked = builtins.bitAnd b intmax; 101 | negate = b < 0; 102 | # Split division to prevent having to divide by a negative number for 103 | # shifts of 63 bit 104 | result = masked / 2 / (builtins.elemAt lut (a - 1)); 105 | inv = 63 - a; 106 | highest_bit = builtins.elemAt lut inv; 107 | in 108 | if !negate then result else result + highest_bit; 109 | arithmeticRight = 110 | a: b: 111 | if a >= 64 then 112 | if b < 0 then -1 else 0 113 | else if a == 0 then 114 | b 115 | else if a < 0 then 116 | throw "Inverse right Shift not supported" 117 | else 118 | let 119 | negate = b < 0; 120 | mask = if a == 63 then intmax else (builtins.elemAt lut a) - 1; 121 | round_down = negate && (builtins.bitAnd mask b != 0); 122 | result = b / 2 / (builtins.elemAt lut (a - 1)); 123 | in 124 | if round_down then result - 1 else result; 125 | in 126 | { 127 | inherit left logicalRight arithmeticRight; 128 | } 129 | -------------------------------------------------------------------------------- /lib/types.nix: -------------------------------------------------------------------------------- 1 | _inputs: _final: prev: let 2 | inherit 3 | (prev.lib) 4 | all 5 | assertMsg 6 | isAttrs 7 | mkOptionType 8 | recursiveUpdate 9 | showOption 10 | types 11 | ; 12 | 13 | # Checks whether the value is a lazy value without causing 14 | # it's value to be evaluated 15 | isLazyValue = x: isAttrs x && x ? _lazyValue; 16 | # Constructs a lazy value holding the given value. 17 | lazyValue = value: {_lazyValue = value;}; 18 | 19 | # Represents a lazy value of the given type, which 20 | # holds the actual value as an attrset like { _lazyValue = ; }. 21 | # This allows the option to be defined and filtered from a defintion 22 | # list without evaluating the value. 23 | lazyValueOf = type: 24 | mkOptionType rec { 25 | name = "lazyValueOf ${type.name}"; 26 | inherit (type) description descriptionClass emptyValue getSubOptions getSubModules; 27 | check = isLazyValue; 28 | merge = loc: defs: 29 | assert assertMsg 30 | (all (x: type.check x._lazyValue) defs) 31 | "The option `${showOption loc}` is defined with a lazy value holding an invalid type"; 32 | types.mergeOneOption loc defs; 33 | substSubModules = m: types.uniq (type.substSubModules m); 34 | functor = (types.defaultFunctor name) // {wrapped = type;}; 35 | nestedTypes.elemType = type; 36 | }; 37 | 38 | # Represents a value or lazy value of the given type that will 39 | # automatically be coerced to the given type when merged. 40 | lazyOf = type: types.coercedTo (lazyValueOf type) (x: x._lazyValue) type; 41 | in { 42 | lib = recursiveUpdate prev.lib { 43 | types = { 44 | inherit 45 | isLazyValue 46 | lazyValue 47 | lazyValueOf 48 | lazyOf 49 | ; 50 | }; 51 | }; 52 | } 53 | -------------------------------------------------------------------------------- /modules/boot.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | lib, 4 | ... 5 | }: { 6 | options.boot.mode = lib.mkOption { 7 | description = "Enable recommended Options for different boot modes"; 8 | type = lib.types.nullOr (lib.types.enum ["bios" "efi" "secureboot"]); 9 | default = null; 10 | }; 11 | config.boot.loader = let 12 | bios-conf = { 13 | grub = { 14 | enable = true; 15 | efiSupport = false; 16 | configurationLimit = 32; 17 | }; 18 | }; 19 | efi-conf = { 20 | # Use the systemd-boot EFI boot loader. 21 | systemd-boot.enable = true; 22 | systemd-boot.configurationLimit = 32; 23 | efi.canTouchEfiVariables = true; 24 | }; 25 | in 26 | lib.mkIf (config.boot.mode != null) 27 | { 28 | efi = efi-conf; 29 | bios = bios-conf; 30 | secureboot = throw "not yet implemented"; 31 | "" = {}; 32 | } 33 | .${ 34 | if config.boot.mode == null 35 | then "" 36 | else config.boot.mode 37 | }; 38 | } 39 | -------------------------------------------------------------------------------- /modules/default.nix: -------------------------------------------------------------------------------- 1 | { inputs, ... }: 2 | { 3 | imports = [ 4 | inputs.microvm.nixosModules.host 5 | 6 | ./boot.nix 7 | ./globals.nix 8 | ./guests/default.nix 9 | ./interface-naming.nix 10 | ./nginx.nix 11 | ./node.nix 12 | ./restic.nix 13 | #./topology-wireguard.nix 14 | ./wireguard.nix 15 | ]; 16 | 17 | nixpkgs.overlays = [ 18 | inputs.microvm.overlay 19 | ]; 20 | } 21 | -------------------------------------------------------------------------------- /modules/globals.nix: -------------------------------------------------------------------------------- 1 | { lib, options, ... }: 2 | { 3 | options._globalsDefs = lib.mkOption { 4 | type = lib.types.unspecified; 5 | default = options.globals.definitions; 6 | readOnly = true; 7 | internal = true; 8 | }; 9 | } 10 | -------------------------------------------------------------------------------- /modules/guests/common-guest-config.nix: -------------------------------------------------------------------------------- 1 | _guestName: guestCfg: 2 | { lib, ... }: 3 | let 4 | inherit (lib) 5 | mkForce 6 | nameValuePair 7 | listToAttrs 8 | flip 9 | ; 10 | in 11 | { 12 | node.name = guestCfg.nodeName; 13 | node.type = guestCfg.backend; 14 | 15 | nix = { 16 | settings.auto-optimise-store = mkForce false; 17 | optimise.automatic = mkForce false; 18 | gc.automatic = mkForce false; 19 | }; 20 | documentation.enable = mkForce false; 21 | 22 | systemd.network.networks = listToAttrs ( 23 | flip map guestCfg.networking.links ( 24 | name: 25 | nameValuePair "10-${name}" { 26 | matchConfig.Name = name; 27 | DHCP = "yes"; 28 | # XXX: Do we really want this? 29 | dhcpV4Config.UseDNS = false; 30 | dhcpV6Config.UseDNS = false; 31 | ipv6AcceptRAConfig.UseDNS = false; 32 | networkConfig = { 33 | IPv6PrivacyExtensions = "yes"; 34 | MulticastDNS = true; 35 | IPv6AcceptRA = true; 36 | }; 37 | linkConfig.RequiredForOnline = "routable"; 38 | } 39 | ) 40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /modules/guests/container.nix: -------------------------------------------------------------------------------- 1 | guestName: guestCfg: 2 | { 3 | config, 4 | inputs, 5 | lib, 6 | pkgs, 7 | extraModules, 8 | ... 9 | }: 10 | let 11 | inherit (lib) 12 | flip 13 | mapAttrs' 14 | nameValuePair 15 | ; 16 | in 17 | { 18 | inherit (guestCfg.container) macvlans; 19 | ephemeral = true; 20 | privateNetwork = true; 21 | autoStart = guestCfg.autostart; 22 | extraFlags = [ 23 | "--uuid=${builtins.substring 0 32 (builtins.hashString "sha256" guestName)}" 24 | ]; 25 | bindMounts = flip mapAttrs' guestCfg.zfs ( 26 | _: zfsCfg: 27 | nameValuePair zfsCfg.guestMountpoint { 28 | hostPath = zfsCfg.hostMountpoint; 29 | isReadOnly = false; 30 | } 31 | ); 32 | nixosConfiguration = (import "${inputs.nixpkgs}/nixos/lib/eval-config.nix") { 33 | specialArgs = guestCfg.extraSpecialArgs; 34 | prefix = [ 35 | "nodes" 36 | "${config.node.name}-${guestName}" 37 | "config" 38 | ]; 39 | system = null; 40 | modules = 41 | [ 42 | { 43 | boot.isContainer = true; 44 | networking.useHostResolvConf = false; 45 | 46 | # We cannot force the package set via nixpkgs.pkgs and 47 | # inputs.nixpkgs.nixosModules.readOnlyPkgs, since some nixosModules 48 | # like nixseparatedebuginfod depend on adding packages via nixpkgs.overlays. 49 | # So we just mimic the options and overlays defined by the passed pkgs set. 50 | nixpkgs.hostPlatform = config.nixpkgs.hostPlatform.system; 51 | nixpkgs.overlays = pkgs.overlays; 52 | nixpkgs.config = pkgs.config; 53 | 54 | # Bind the /guest/* paths from above so impermancence doesn't complain. 55 | # We bind-mount stuff from the host to itself, which is perfectly defined 56 | # and not recursive. This allows us to have a fileSystems entry for each 57 | # bindMount which other stuff can depend upon (impermanence adds dependencies 58 | # to the state fs). 59 | fileSystems = flip mapAttrs' guestCfg.zfs ( 60 | _: zfsCfg: 61 | nameValuePair zfsCfg.guestMountpoint { 62 | neededForBoot = true; 63 | fsType = "none"; 64 | device = zfsCfg.guestMountpoint; 65 | options = [ "bind" ]; 66 | } 67 | ); 68 | } 69 | (import ./common-guest-config.nix guestName guestCfg) 70 | ] 71 | ++ guestCfg.modules 72 | ++ extraModules; 73 | }; 74 | } 75 | -------------------------------------------------------------------------------- /modules/guests/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | lib, 4 | pkgs, 5 | utils, 6 | ... 7 | } @ attrs: let 8 | inherit 9 | (lib) 10 | attrNames 11 | attrValues 12 | attrsToList 13 | length 14 | splitString 15 | elemAt 16 | disko 17 | escapeShellArg 18 | flatten 19 | flip 20 | foldl' 21 | forEach 22 | groupBy 23 | hasInfix 24 | hasPrefix 25 | listToAttrs 26 | literalExpression 27 | makeBinPath 28 | mapAttrs 29 | mapAttrsToList 30 | mergeToplevelConfigs 31 | mkIf 32 | mkMerge 33 | mkOption 34 | net 35 | optional 36 | optionalAttrs 37 | types 38 | warnIf 39 | ; 40 | 41 | # All available backends 42 | backends = [ 43 | "microvm" 44 | "container" 45 | ]; 46 | 47 | guestsByBackend = 48 | lib.genAttrs backends (_: {}) 49 | // mapAttrs (_: listToAttrs) (groupBy (x: x.value.backend) (attrsToList config.guests)); 50 | 51 | # List the necessary mount units for the given guest 52 | fsMountUnitsFor = guestCfg: map (x: "${utils.escapeSystemdPath x.hostMountpoint}.mount") (attrValues guestCfg.zfs); 53 | 54 | # Configuration required on the host for a specific guest 55 | defineGuest = _guestName: guestCfg: { 56 | # Add the required datasets to the disko configuration of the machine 57 | disko.devices.zpool = mkMerge ( 58 | flip map (attrValues guestCfg.zfs) (zfsCfg: { 59 | ${zfsCfg.pool}.datasets.${zfsCfg.dataset} = 60 | # We generate the mountpoint fileSystems entries ourselfs to enable shared folders between guests 61 | disko.zfs.unmountable; 62 | }) 63 | ); 64 | 65 | # Ensure that the zfs dataset exists before it is mounted. 66 | systemd.services = mkMerge ( 67 | flip map (attrValues guestCfg.zfs) ( 68 | zfsCfg: let 69 | fsMountUnit = "${utils.escapeSystemdPath zfsCfg.hostMountpoint}.mount"; 70 | in { 71 | "zfs-ensure-${utils.escapeSystemdPath "${zfsCfg.pool}/${zfsCfg.dataset}"}" = { 72 | wantedBy = [fsMountUnit]; 73 | before = [fsMountUnit]; 74 | after = [ 75 | "zfs-import-${utils.escapeSystemdPath zfsCfg.pool}.service" 76 | "zfs-mount.target" 77 | ]; 78 | unitConfig.DefaultDependencies = "no"; 79 | serviceConfig.Type = "oneshot"; 80 | script = let 81 | poolDataset = "${zfsCfg.pool}/${zfsCfg.dataset}"; 82 | diskoDataset = config.disko.devices.zpool.${zfsCfg.pool}.datasets.${zfsCfg.dataset}; 83 | in '' 84 | export PATH=${makeBinPath [pkgs.zfs]}":$PATH" 85 | if ! zfs list -H -o type ${escapeShellArg poolDataset} &>/dev/null ; then 86 | ${diskoDataset._create} 87 | fi 88 | ''; 89 | }; 90 | } 91 | ) 92 | ); 93 | }; 94 | 95 | defineMicrovm = guestName: guestCfg: { 96 | # Ensure that the zfs dataset exists before it is mounted. 97 | systemd.services."microvm@${guestName}" = { 98 | requires = fsMountUnitsFor guestCfg; 99 | after = fsMountUnitsFor guestCfg; 100 | }; 101 | 102 | microvm.vms.${guestName} = import ./microvm.nix guestName guestCfg attrs; 103 | }; 104 | 105 | defineContainer = guestName: guestCfg: { 106 | # Ensure that the zfs dataset exists before it is mounted. 107 | systemd.services."container@${guestName}" = { 108 | requires = fsMountUnitsFor guestCfg; 109 | after = fsMountUnitsFor guestCfg; 110 | # Don't use the notify service type. Using exec will always consider containers 111 | # started immediately and donesn't wait until the container is fully booted. 112 | # Containers should behave like independent machines, and issues inside the container 113 | # will unnecessarily lock up the service on the host otherwise. 114 | # This causes issues on system activation or when containers take longer to start 115 | # than TimeoutStartSec. 116 | serviceConfig.Type = lib.mkForce "exec"; 117 | }; 118 | 119 | containers.${guestName} = import ./container.nix guestName guestCfg attrs; 120 | }; 121 | in { 122 | imports = [ 123 | { 124 | # This is opt-out, so we can't put this into the mkIf below 125 | microvm.host.enable = guestsByBackend.microvm != {}; 126 | } 127 | ]; 128 | 129 | options.node.type = mkOption { 130 | type = types.enum (["host"] ++ backends); 131 | description = "The type of this machine."; 132 | default = "host"; 133 | }; 134 | 135 | options.containers = mkOption { 136 | type = types.attrsOf ( 137 | types.submodule (submod: { 138 | options.nixosConfiguration = mkOption { 139 | type = types.unspecified; 140 | default = null; 141 | description = "Set this to the result of a `nixosSystem` invocation to use it as the guest system. This will set the `path` option for you."; 142 | }; 143 | config = mkIf (submod.config.nixosConfiguration != null) ( 144 | { 145 | path = submod.config.nixosConfiguration.config.system.build.toplevel; 146 | } 147 | // optionalAttrs (config ? topology) { 148 | _nix_topology_config = submod.config.nixosConfiguration.config; 149 | } 150 | ); 151 | }) 152 | ); 153 | }; 154 | 155 | options.guests = mkOption { 156 | default = {}; 157 | description = "Defines the actual vms and handles the necessary base setup for them."; 158 | type = types.attrsOf ( 159 | types.submodule (submod: { 160 | options = { 161 | nodeName = mkOption { 162 | type = types.str; 163 | default = "${config.node.name}-${submod.config._module.args.name}"; 164 | description = '' 165 | The name of the resulting node. By default this will be a compound name 166 | of the host's name and the guest's name to avoid name clashes. Can be 167 | overwritten to designate special names to specific guests. 168 | ''; 169 | }; 170 | 171 | backend = mkOption { 172 | type = types.enum backends; 173 | description = '' 174 | Determines how the guest will be hosted. You can currently choose 175 | between microvm based deployment, or nixos containers. 176 | ''; 177 | }; 178 | 179 | extraSpecialArgs = mkOption { 180 | type = types.attrs; 181 | default = {}; 182 | example = literalExpression "{ inherit inputs; }"; 183 | description = '' 184 | Extra `specialArgs` passed to each guest system definition. This 185 | option can be used to pass additional arguments to all modules. 186 | ''; 187 | }; 188 | 189 | # Options for the microvm backend 190 | microvm = { 191 | system = mkOption { 192 | type = types.str; 193 | description = "The system that this microvm should use"; 194 | }; 195 | 196 | baseMac = mkOption { 197 | type = types.net.mac; 198 | description = "The base mac address from which the guest's mac will be derived. Only the second and third byte are used, so for 02:XX:YY:ZZ:ZZ:ZZ, this specifies XX and YY, while Zs are generated automatically. Not used if the mac is set directly."; 199 | default = "02:01:27:00:00:00"; 200 | }; 201 | interfaces = mkOption { 202 | description = "An attrset correlating the host interface to which the microvm should be attached via macvtap, with its mac address"; 203 | type = types.attrsOf ( 204 | types.submodule (submod-iface: { 205 | options = { 206 | hostLink = mkOption { 207 | type = types.str; 208 | description = "The name of the host side link to which this interface will bind."; 209 | default = submod-iface.config._module.args.name; 210 | example = "lan"; 211 | }; 212 | mac = mkOption { 213 | type = types.net.mac; 214 | description = "The MAC address for the guest's macvtap interface"; 215 | default = let 216 | base = "02:${lib.substring 3 5 submod.config.microvm.baseMac}:00:00:00"; 217 | in 218 | (net.mac.assignMacs base 24 [] ( 219 | flatten ( 220 | flip mapAttrsToList config.guests ( 221 | name: value: forEach (attrNames value.microvm.interfaces) (iface: "${name}-${iface}") 222 | ) 223 | ) 224 | )) 225 | ."${submod.config._module.args.name}-${submod-iface.config._module.args.name}"; 226 | }; 227 | }; 228 | }) 229 | ); 230 | default = {}; 231 | }; 232 | }; 233 | 234 | # Options for the container backend 235 | container = { 236 | macvlans = mkOption { 237 | type = types.listOf types.str; 238 | description = '' 239 | The macvlans to be created for the container. 240 | Can be either an interface name in which case the container interface will be called mv- or a pair 241 | of :. 242 | ''; 243 | }; 244 | }; 245 | 246 | networking.links = mkOption { 247 | type = types.listOf types.str; 248 | description = "The ethernet links inside of the guest. For containers, these cannot be named similar to an existing interface on the host."; 249 | default = 250 | if submod.config.backend == "microvm" 251 | then (flip mapAttrsToList submod.config.microvm.interfaces (name: _: name)) 252 | else if submod.config.backend == "container" 253 | then 254 | (forEach submod.config.container.macvlans ( 255 | name: let 256 | split = splitString ":" name; 257 | in 258 | if length split > 1 259 | then elemAt split 1 260 | else "mv-${name}" 261 | )) 262 | else throw "Invalid backend"; 263 | }; 264 | 265 | zfs = mkOption { 266 | description = "zfs datasets to mount into the guest"; 267 | default = {}; 268 | type = types.attrsOf ( 269 | types.submodule (zfsSubmod: { 270 | options = { 271 | pool = mkOption { 272 | type = types.str; 273 | description = "The host's zfs pool on which the dataset resides"; 274 | }; 275 | 276 | dataset = mkOption { 277 | type = types.str; 278 | example = "safe/guests/mycontainer"; 279 | description = "The host's dataset that should be used for this mountpoint (will automatically be created, including parent datasets)"; 280 | }; 281 | 282 | hostMountpoint = mkOption { 283 | type = types.path; 284 | default = "/guests/${submod.config._module.args.name}${zfsSubmod.config.guestMountpoint}"; 285 | example = "/guests/mycontainer/persist"; 286 | description = "The host's mountpoint for the guest's dataset"; 287 | }; 288 | 289 | guestMountpoint = mkOption { 290 | type = types.path; 291 | default = zfsSubmod.config._module.args.name; 292 | example = "/persist"; 293 | description = "The mountpoint inside the guest."; 294 | }; 295 | }; 296 | }) 297 | ); 298 | }; 299 | 300 | autostart = mkOption { 301 | type = types.bool; 302 | default = false; 303 | description = "Whether this guest should be started automatically with the host"; 304 | }; 305 | 306 | modules = mkOption { 307 | type = types.listOf types.unspecified; 308 | default = []; 309 | description = "Additional modules to load"; 310 | }; 311 | }; 312 | }) 313 | ); 314 | }; 315 | 316 | config = mkIf (config.guests != {}) (mkMerge [ 317 | { 318 | systemd.tmpfiles.rules = [ 319 | "d /guests 0700 root root -" 320 | ]; 321 | 322 | # To enable shared folders we need to do all fileSystems entries ourselfs 323 | fileSystems = let 324 | zfsDefs = flatten ( 325 | flip mapAttrsToList config.guests ( 326 | _: guestCfg: 327 | flip mapAttrsToList guestCfg.zfs ( 328 | _: zfsCfg: { 329 | path = "${zfsCfg.pool}/${zfsCfg.dataset}"; 330 | inherit (zfsCfg) hostMountpoint; 331 | } 332 | ) 333 | ) 334 | ); 335 | # Due to limitations in zfs mounting we need to explicitly set an order in which 336 | # any dataset gets mounted 337 | zfsDefsByPath = flip groupBy zfsDefs (x: x.path); 338 | in 339 | mkMerge ( 340 | flip mapAttrsToList zfsDefsByPath ( 341 | _: defs: 342 | ( 343 | foldl' 344 | ( 345 | { 346 | prev, 347 | res, 348 | }: elem: { 349 | prev = elem; 350 | res = 351 | res 352 | // { 353 | ${elem.hostMountpoint} = { 354 | fsType = "zfs"; 355 | options = 356 | ["zfsutil"] 357 | ++ optional (prev != null) 358 | "x-systemd.requires-mounts-for=${ 359 | warnIf (hasInfix " " prev.hostMountpoint) 360 | "HostMountpoint ${prev.hostMountpoint} cannot contain a space" 361 | prev.hostMountpoint 362 | }"; 363 | device = elem.path; 364 | }; 365 | }; 366 | } 367 | ) 368 | { 369 | prev = null; 370 | res = {}; 371 | } 372 | defs 373 | ) 374 | .res 375 | ) 376 | ); 377 | 378 | assertions = flatten ( 379 | flip mapAttrsToList config.guests ( 380 | guestName: guestCfg: 381 | flip mapAttrsToList guestCfg.zfs ( 382 | zfsName: zfsCfg: { 383 | assertion = hasPrefix "/" zfsCfg.guestMountpoint; 384 | message = "guest ${guestName}: zfs ${zfsName}: the guestMountpoint must be an absolute path."; 385 | } 386 | ) 387 | ) 388 | ); 389 | } 390 | (mergeToplevelConfigs ["disko" "systemd" "fileSystems"] ( 391 | mapAttrsToList defineGuest config.guests 392 | )) 393 | (mergeToplevelConfigs ["containers" "systemd"] ( 394 | mapAttrsToList defineContainer guestsByBackend.container 395 | )) 396 | (mergeToplevelConfigs ["microvm" "systemd"] ( 397 | mapAttrsToList defineMicrovm guestsByBackend.microvm 398 | )) 399 | ]); 400 | } 401 | -------------------------------------------------------------------------------- /modules/guests/microvm.nix: -------------------------------------------------------------------------------- 1 | guestName: guestCfg: 2 | { 3 | inputs, 4 | lib, 5 | extraModules, 6 | ... 7 | }: 8 | let 9 | inherit (lib) 10 | concatMapAttrs 11 | flip 12 | mapAttrs 13 | mapAttrsToList 14 | mkDefault 15 | mkForce 16 | replaceStrings 17 | ; 18 | in 19 | { 20 | specialArgs = guestCfg.extraSpecialArgs; 21 | pkgs = inputs.self.pkgs.${guestCfg.microvm.system}; 22 | inherit (guestCfg) autostart; 23 | config = { 24 | imports = 25 | extraModules 26 | ++ guestCfg.modules 27 | ++ [ 28 | (import ./common-guest-config.nix guestName guestCfg) 29 | ( 30 | { config, ... }: 31 | { 32 | # Set early hostname too, so we can associate those logs to this host and don't get "localhost" entries in loki 33 | boot.kernelParams = [ "systemd.hostname=${config.networking.hostName}" ]; 34 | } 35 | ) 36 | ]; 37 | 38 | lib.microvm.interfaces = guestCfg.microvm.interfaces; 39 | 40 | microvm = { 41 | hypervisor = mkDefault "qemu"; 42 | 43 | # Give them some juice by default 44 | mem = mkDefault (1024 + 2048); 45 | # This causes QEMU rebuilds which would remove 200MB from the closure but 46 | # recompiling QEMU every deploy is worse. 47 | optimize.enable = false; 48 | 49 | # Add a writable store overlay, but since this is always ephemeral 50 | # disable any store optimization from nix. 51 | writableStoreOverlay = "/nix/.rw-store"; 52 | 53 | # MACVTAP bridge to the host's network 54 | interfaces = flip mapAttrsToList guestCfg.microvm.interfaces ( 55 | _: 56 | { 57 | mac, 58 | hostLink, 59 | ... 60 | }: 61 | { 62 | type = "macvtap"; 63 | id = "vm-${replaceStrings [ ":" ] [ "" ] mac}"; 64 | inherit mac; 65 | macvtap = { 66 | link = hostLink; 67 | mode = "bridge"; 68 | }; 69 | } 70 | ); 71 | 72 | shares = 73 | [ 74 | # Share the nix-store of the host 75 | { 76 | source = "/nix/store"; 77 | mountPoint = "/nix/.ro-store"; 78 | tag = "ro-store"; 79 | proto = "virtiofs"; 80 | } 81 | ] 82 | ++ flip mapAttrsToList guestCfg.zfs ( 83 | _: zfsCfg: { 84 | source = zfsCfg.hostMountpoint; 85 | mountPoint = zfsCfg.guestMountpoint; 86 | tag = builtins.substring 0 16 (builtins.hashString "sha256" zfsCfg.hostMountpoint); 87 | proto = "virtiofs"; 88 | } 89 | ); 90 | }; 91 | 92 | networking.renameInterfacesByMac = flip mapAttrs guestCfg.microvm.interfaces (_: { mac, ... }: mac); 93 | systemd.network.networks = flip concatMapAttrs guestCfg.microvm.interfaces ( 94 | name: 95 | { mac, ... }: 96 | { 97 | "10-${name}".matchConfig = mkForce { 98 | MACAddress = mac; 99 | }; 100 | } 101 | ); 102 | }; 103 | } 104 | -------------------------------------------------------------------------------- /modules/interface-naming.nix: -------------------------------------------------------------------------------- 1 | # Provides an option to easily rename interfaces by their mac addresses. 2 | { 3 | config, 4 | lib, 5 | pkgs, 6 | ... 7 | }: let 8 | inherit 9 | (lib) 10 | attrValues 11 | concatStringsSep 12 | duplicates 13 | flip 14 | mapAttrsToList 15 | mkOption 16 | types 17 | ; 18 | 19 | cfg = config.networking.renameInterfacesByMac; 20 | 21 | interfaceNamesUdevRules = pkgs.writeTextFile { 22 | name = "interface-names-udev-rules"; 23 | text = concatStringsSep "\n" ( 24 | flip mapAttrsToList cfg 25 | (name: mac: ''SUBSYSTEM=="net", ACTION=="add", ATTR{address}=="${mac}", ATTR{addr_assign_type}=="0", NAME:="${name}"'') 26 | ); 27 | destination = "/etc/udev/rules.d/01-interface-names.rules"; 28 | }; 29 | in { 30 | options.networking.renameInterfacesByMac = mkOption { 31 | default = {}; 32 | example = {lan = "11:22:33:44:55:66";}; 33 | description = "Allows naming of network interfaces based on their physical address"; 34 | type = types.attrsOf types.str; 35 | }; 36 | 37 | config = lib.mkIf (cfg != {}) { 38 | assertions = let 39 | duplicateMacs = duplicates (attrValues cfg); 40 | in [ 41 | { 42 | assertion = duplicateMacs == []; 43 | message = "Duplicate mac addresses found in network interface name assignment: ${concatStringsSep ", " duplicateMacs}"; 44 | } 45 | ]; 46 | 47 | services.udev.packages = [interfaceNamesUdevRules]; 48 | boot.initrd.services.udev.packages = [interfaceNamesUdevRules]; 49 | }; 50 | } 51 | -------------------------------------------------------------------------------- /modules/nginx.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | lib, 4 | ... 5 | }: 6 | let 7 | inherit (lib) 8 | mkBefore 9 | mkEnableOption 10 | mkIf 11 | mkOption 12 | types 13 | ; 14 | in 15 | { 16 | options.services.nginx = { 17 | recommendedSetup = mkEnableOption "recommended setup parameters."; 18 | recommendedSecurityHeaders = mkEnableOption "additional security headers by default in each location block. Can be overwritten in each location with `recommendedSecurityHeaders`."; 19 | virtualHosts = mkOption { 20 | type = types.attrsOf ( 21 | types.submodule { 22 | options.locations = mkOption { 23 | type = types.attrsOf ( 24 | types.submodule (submod: { 25 | options = { 26 | recommendedSecurityHeaders = mkOption { 27 | type = types.bool; 28 | default = config.services.nginx.recommendedSecurityHeaders; 29 | description = "Whether to add additional security headers to this location."; 30 | }; 31 | 32 | X-Frame-Options = mkOption { 33 | type = types.str; 34 | default = "DENY"; 35 | description = "The value to use for X-Frame-Options"; 36 | }; 37 | }; 38 | config = mkIf submod.config.recommendedSecurityHeaders { 39 | extraConfig = mkBefore '' 40 | # Enable HTTP Strict Transport Security (HSTS) 41 | add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"; 42 | 43 | # Minimize information leaked to other domains 44 | add_header Referrer-Policy "origin-when-cross-origin"; 45 | 46 | add_header X-XSS-Protection "1; mode=block"; 47 | add_header X-Frame-Options "${submod.config.X-Frame-Options}"; 48 | add_header X-Content-Type-Options "nosniff"; 49 | ''; 50 | }; 51 | }) 52 | ); 53 | }; 54 | } 55 | ); 56 | }; 57 | }; 58 | 59 | config = mkIf (config.services.nginx.enable && config.services.nginx.recommendedSetup) { 60 | age.secrets."dhparams.pem" = mkIf (config ? age) { 61 | generator.script = "dhparams"; 62 | mode = "440"; 63 | group = "nginx"; 64 | }; 65 | 66 | networking.firewall.allowedTCPPorts = [ 67 | 80 68 | 443 69 | ]; 70 | # QUIC 71 | networking.firewall.allowedUDPPorts = [ 443 ]; 72 | 73 | # Sensible defaults for nginx 74 | services.nginx = { 75 | recommendedBrotliSettings = true; 76 | recommendedGzipSettings = true; 77 | recommendedOptimisation = true; 78 | recommendedProxySettings = true; 79 | recommendedTlsSettings = true; 80 | recommendedSecurityHeaders = true; 81 | 82 | # SSL config 83 | sslCiphers = "EECDH+AESGCM:EDH+AESGCM:!aNULL"; 84 | sslDhparam = mkIf (config ? age) config.age.secrets."dhparams.pem".path; 85 | commonHttpConfig = '' 86 | log_format json_combined escape=json '{' 87 | '"time": $msec,' 88 | '"remote_addr":"$remote_addr",' 89 | '"status":$status,' 90 | '"method":"$request_method",' 91 | '"host":"$host",' 92 | '"uri":"$request_uri",' 93 | '"request_size":$request_length,' 94 | '"response_size":$body_bytes_sent,' 95 | '"response_time":$request_time,' 96 | '"referrer":"$http_referer",' 97 | '"user_agent":"$http_user_agent"' 98 | '}'; 99 | error_log syslog:server=unix:/dev/log,nohostname; 100 | access_log syslog:server=unix:/dev/log,nohostname json_combined; 101 | ssl_ecdh_curve secp384r1; 102 | ''; 103 | 104 | # Default host that rejects everything. 105 | # This is selected when no matching host is found for a request. 106 | virtualHosts.dummy = { 107 | default = true; 108 | rejectSSL = true; 109 | locations."/".extraConfig = '' 110 | deny all; 111 | ''; 112 | }; 113 | }; 114 | }; 115 | } 116 | -------------------------------------------------------------------------------- /modules/node.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | lib, 4 | ... 5 | }: let 6 | inherit 7 | (lib) 8 | mkOption 9 | types 10 | ; 11 | in { 12 | options.node = { 13 | name = mkOption { 14 | description = "A unique name for this node (host) in the repository. Defines the default hostname, but this can be overwritten."; 15 | type = types.str; 16 | }; 17 | }; 18 | 19 | config = { 20 | networking.hostName = config.node.name; 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /modules/restic.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | config, 4 | ... 5 | }: let 6 | inherit 7 | (lib) 8 | flatten 9 | flip 10 | mapAttrsToList 11 | mkEnableOption 12 | mkIf 13 | mkOption 14 | optional 15 | types 16 | unique 17 | ; 18 | in { 19 | options.services.restic.backups = mkOption { 20 | type = types.attrsOf (types.submodule (submod: { 21 | options.hetznerStorageBox = { 22 | enable = mkEnableOption "Automatically configure this backup to use the given hetzner storage box. Will use SFTP via SSH."; 23 | 24 | mainUser = mkOption { 25 | type = types.str; 26 | description = '' 27 | The main user. While not technically required for restic, we still use it to 28 | derive the subuser name and it is required for the automatic setup script 29 | that creates the users. 30 | ''; 31 | }; 32 | 33 | subUid = mkOption { 34 | type = types.int; 35 | description = "The id of the subuser that was allocated on the hetzner server for this backup."; 36 | }; 37 | 38 | path = mkOption { 39 | type = types.str; 40 | description = '' 41 | The remote path to backup into. While not technically required for restic 42 | (since the subuser is chrooted on the remote), it is required for the 43 | automatic setup script that creates the users. 44 | ''; 45 | }; 46 | 47 | sshAgeSecret = mkOption { 48 | type = types.str; 49 | description = "The name of the agenix secret containing the ssh private key for accesing the storage box."; 50 | }; 51 | }; 52 | 53 | config = let 54 | subuser = "${submod.config.hetznerStorageBox.mainUser}-sub${toString submod.config.hetznerStorageBox.subUid}"; 55 | url = "${subuser}@${submod.config.hetznerStorageBox.mainUser}.your-storagebox.de"; 56 | in 57 | mkIf submod.config.hetznerStorageBox.enable { 58 | repository = "rclone:"; 59 | extraOptions = [ 60 | "rclone.program='ssh -p23 ${url} -i ${config.age.secrets.${submod.config.hetznerStorageBox.sshAgeSecret}.path}'" 61 | ]; 62 | }; 63 | })); 64 | }; 65 | 66 | config.services.openssh.knownHosts.hetzner-storage-boxes = let 67 | hetznerStorageBoxHostnames = 68 | unique (flatten (flip mapAttrsToList config.services.restic.backups 69 | (_: backupCfg: optional backupCfg.hetznerStorageBox.enable "[${backupCfg.hetznerStorageBox.mainUser}.your-storagebox.de]:23"))); 70 | in 71 | mkIf (hetznerStorageBoxHostnames != []) { 72 | publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIICf9svRenC/PLKIL9nk6K/pxQgoiFC41wTNvoIncOxs"; 73 | hostNames = hetznerStorageBoxHostnames; 74 | }; 75 | } 76 | -------------------------------------------------------------------------------- /modules/topology-wireguard.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | lib, 4 | inputs ? {}, 5 | ... 6 | }: let 7 | inherit 8 | (lib) 9 | flip 10 | mapAttrsToList 11 | mkDefault 12 | mkEnableOption 13 | mkIf 14 | mkMerge 15 | filter 16 | ; 17 | 18 | headOrNull = xs: 19 | if xs == [] 20 | then null 21 | else builtins.head xs; 22 | 23 | networkId = wgName: "wireguard-${wgName}"; 24 | in { 25 | options.topology.extractors.wireguard.enable = mkEnableOption "topology wireguard extractor" // {default = true;}; 26 | 27 | config = mkIf (config.topology.extractors.wireguard.enable && config ? wireguard) { 28 | # Create networks (this will be duplicated by each node, 29 | # but it doesn't matter and will be merged anyway) 30 | topology.networks = mkMerge ( 31 | flip mapAttrsToList config.wireguard ( 32 | wgName: _: let 33 | inherit (lib.wireguard.getNetwork inputs wgName) networkCidrs; 34 | in { 35 | ${networkId wgName} = { 36 | name = mkDefault "Wireguard network '${wgName}'"; 37 | icon = "interfaces.wireguard"; 38 | cidrv4 = headOrNull (filter lib.net.ip.isv4 networkCidrs); 39 | cidrv6 = headOrNull (filter lib.net.ip.isv6 networkCidrs); 40 | }; 41 | } 42 | ) 43 | ); 44 | 45 | # Assign network and physical connections to related interfaces 46 | topology.self.interfaces = mkMerge ( 47 | flip mapAttrsToList config.wireguard ( 48 | wgName: wgCfg: let 49 | inherit 50 | (lib.wireguard.getNetwork inputs wgName) 51 | participatingServerNodes 52 | wgCfgOf 53 | ; 54 | 55 | isServer = wgCfg.server.host != null; 56 | filterSelf = filter (x: x != config.node.name); 57 | 58 | # The list of peers that are "physically" connected in the wireguard network, 59 | # meaning they communicate directly with each other. 60 | connectedPeers = 61 | if isServer 62 | then 63 | # Other servers in the same network 64 | filterSelf participatingServerNodes 65 | else [wgCfg.client.via]; 66 | in { 67 | ${wgCfg.linkName} = { 68 | network = networkId wgName; 69 | virtual = true; 70 | renderer.hidePhysicalConnections = true; 71 | physicalConnections = flip map connectedPeers (peer: { 72 | node = inputs.self.nodes.${peer}.config.topology.id; 73 | interface = (wgCfgOf peer).linkName; 74 | }); 75 | }; 76 | } 77 | ) 78 | ); 79 | }; 80 | } 81 | -------------------------------------------------------------------------------- /modules/wireguard.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | inputs, 4 | lib, 5 | globals, 6 | ... 7 | }: 8 | let 9 | inherit (lib) 10 | any 11 | attrNames 12 | concatMapAttrs 13 | count 14 | mkMerge 15 | filterAttrs 16 | flip 17 | mapAttrs' 18 | mapAttrsToList 19 | mkIf 20 | nameValuePair 21 | attrValues 22 | net 23 | optional 24 | optionalAttrs 25 | stringLength 26 | concatLists 27 | ; 28 | 29 | memberWG = filterAttrs ( 30 | _: cfg: any (x: x == config.node.name) (attrNames cfg.hosts) 31 | ) globals.wireguard; 32 | in 33 | 34 | { 35 | assertions = concatLists ( 36 | flip mapAttrsToList memberWG ( 37 | networkName: networkCfg: 38 | let 39 | assertionPrefix = "While evaluation the wireguard network ${networkName}:"; 40 | hostCfg = networkCfg.hosts.${config.node.name}; 41 | in 42 | [ 43 | { 44 | assertion = networkCfg.cidrv4 != null || networkCfg.cidrv6 != null; 45 | message = "${assertionPrefix}: At least one of cidrv4 or cidrv6 has to be set."; 46 | } 47 | { 48 | assertion = (count (x: x.server) (attrValues networkCfg.hosts)) == 1; 49 | message = "${assertionPrefix}: You have to declare exactly 1 server node."; 50 | } 51 | { 52 | assertion = (count (x: x.id == hostCfg.id) (attrValues networkCfg.hosts)) == 1; 53 | message = "${assertionPrefix}: More than one host with id ${toString hostCfg.id}"; 54 | } 55 | { 56 | assertion = stringLength hostCfg.linkName < 16; 57 | message = "${assertionPrefix}: The specified linkName '${hostCfg.linkName}' is too long (must be max 15 characters)."; 58 | } 59 | ] 60 | ) 61 | ); 62 | networking.firewall.allowedUDPPorts = mkMerge ( 63 | flip mapAttrsToList memberWG ( 64 | _: networkCfg: 65 | let 66 | hostCfg = networkCfg.hosts.${config.node.name}; 67 | in 68 | optional (hostCfg.server && networkCfg.openFirewall) networkCfg.port 69 | ) 70 | ); 71 | networking.nftables.firewall.zones = mkMerge ( 72 | flip mapAttrsToList memberWG ( 73 | _: networkCfg: 74 | let 75 | hostCfg = networkCfg.hosts.${config.node.name}; 76 | peers = filterAttrs (name: _: name != config.node.name) networkCfg.hosts; 77 | in 78 | { 79 | # Parent zone for the whole network 80 | "wg-${hostCfg.linkName}".interfaces = [ hostCfg.linkName ]; 81 | } 82 | // (flip mapAttrs' peers ( 83 | name: cfg: 84 | nameValuePair "wg-${hostCfg.linkName}-node-${name}" { 85 | parent = "wg-${hostCfg.linkName}"; 86 | ipv4Addresses = optional (cfg.ipv4 != null) cfg.ipv4; 87 | ipv6Addresses = optional (cfg.ipv6 != null) cfg.ipv6; 88 | } 89 | )) 90 | ) 91 | ); 92 | networking.nftables.firewall.rules = mkMerge ( 93 | flip mapAttrsToList memberWG ( 94 | _: networkCfg: 95 | let 96 | inherit (config.networking.nftables.firewall) localZoneName; 97 | hostCfg = networkCfg.hosts.${config.node.name}; 98 | peers = filterAttrs (name: _: name != config.node.name) networkCfg.hosts; 99 | in 100 | { 101 | "wg-${hostCfg.linkName}-to-${localZoneName}" = { 102 | from = [ "wg-${hostCfg.linkName}" ]; 103 | to = [ localZoneName ]; 104 | ignoreEmptyRule = true; 105 | 106 | inherit (hostCfg.firewallRuleForAll) 107 | allowedTCPPorts 108 | allowedUDPPorts 109 | ; 110 | }; 111 | } 112 | // (flip mapAttrs' peers ( 113 | name: _: 114 | nameValuePair "wg-${hostCfg.linkName}-node-${name}-to-${localZoneName}" ( 115 | mkIf (hostCfg.firewallRuleForNode ? ${name}) { 116 | from = [ "wg-${hostCfg.linkName}-node-${name}" ]; 117 | to = [ localZoneName ]; 118 | ignoreEmptyRule = true; 119 | 120 | inherit (hostCfg.firewallRuleForNode.${name}) 121 | allowedTCPPorts 122 | allowedUDPPorts 123 | ; 124 | 125 | } 126 | ) 127 | )) 128 | ) 129 | ); 130 | age.secrets = flip concatMapAttrs memberWG ( 131 | networkName: networkCfg: 132 | let 133 | serverNode = filterAttrs (_: cfg: cfg.server) networkCfg.hosts; 134 | connectedPeers = if hostCfg.server then peers else serverNode; 135 | hostCfg = networkCfg.hosts.${config.node.name}; 136 | peers = filterAttrs (name: _: name != config.node.name) networkCfg.hosts; 137 | sortedPeers = 138 | peerA: peerB: 139 | if peerA < peerB then 140 | { 141 | peer1 = peerA; 142 | peer2 = peerB; 143 | } 144 | else 145 | { 146 | peer1 = peerB; 147 | peer2 = peerA; 148 | }; 149 | 150 | peerPrivateKeyFile = peerName: "/secrets/wireguard/${networkName}/keys/${peerName}.age"; 151 | peerPrivateKeyPath = peerName: inputs.self.outPath + peerPrivateKeyFile peerName; 152 | peerPrivateKeySecret = peerName: "wireguard-${networkName}-priv-${peerName}"; 153 | peerPresharedKeyFile = 154 | peerA: peerB: 155 | let 156 | inherit (sortedPeers peerA peerB) peer1 peer2; 157 | in 158 | "/secrets/wireguard/${networkName}/psks/${peer1}+${peer2}.age"; 159 | peerPresharedKeyPath = peerA: peerB: inputs.self.outPath + peerPresharedKeyFile peerA peerB; 160 | peerPresharedKeySecret = 161 | peerA: peerB: 162 | let 163 | inherit (sortedPeers peerA peerB) peer1 peer2; 164 | in 165 | "wireguard-${networkName}-psks-${peer1}+${peer2}"; 166 | in 167 | flip mapAttrs' connectedPeers ( 168 | name: _: 169 | nameValuePair (peerPresharedKeySecret config.node.name name) { 170 | rekeyFile = peerPresharedKeyPath config.node.name name; 171 | owner = "systemd-network"; 172 | generator.script = { pkgs, ... }: "${pkgs.wireguard-tools}/bin/wg genpsk"; 173 | } 174 | ) 175 | // { 176 | ${peerPrivateKeySecret config.node.name} = { 177 | rekeyFile = peerPrivateKeyPath config.node.name; 178 | owner = "systemd-network"; 179 | generator.script = 180 | { 181 | pkgs, 182 | file, 183 | ... 184 | }: 185 | '' 186 | priv=$(${pkgs.wireguard-tools}/bin/wg genkey) 187 | ${pkgs.wireguard-tools}/bin/wg pubkey <<< "$priv" > ${ 188 | lib.escapeShellArg (lib.removeSuffix ".age" file + ".pub") 189 | } 190 | echo "$priv" 191 | ''; 192 | }; 193 | } 194 | ); 195 | systemd.network.netdevs = flip mapAttrs' memberWG ( 196 | networkName: networkCfg: 197 | let 198 | serverNode = filterAttrs (_: cfg: cfg.server) networkCfg.hosts; 199 | hostCfg = networkCfg.hosts.${config.node.name}; 200 | peers = filterAttrs (name: _: name != config.node.name) networkCfg.hosts; 201 | sortedPeers = 202 | peerA: peerB: 203 | if peerA < peerB then 204 | { 205 | peer1 = peerA; 206 | peer2 = peerB; 207 | } 208 | else 209 | { 210 | peer1 = peerB; 211 | peer2 = peerA; 212 | }; 213 | 214 | peerPublicKeyFile = peerName: "/secrets/wireguard/${networkName}/keys/${peerName}.pub"; 215 | peerPublicKeyPath = peerName: inputs.self.outPath + peerPublicKeyFile peerName; 216 | 217 | peerPrivateKeySecret = peerName: "wireguard-${networkName}-priv-${peerName}"; 218 | peerPresharedKeySecret = 219 | peerA: peerB: 220 | let 221 | inherit (sortedPeers peerA peerB) peer1 peer2; 222 | in 223 | "wireguard-${networkName}-psks-${peer1}+${peer2}"; 224 | in 225 | nameValuePair "${hostCfg.unitConfName}" { 226 | netdevConfig = { 227 | Kind = "wireguard"; 228 | Name = hostCfg.linkName; 229 | Description = "Wireguard network ${networkName}"; 230 | }; 231 | wireguardConfig = 232 | { 233 | PrivateKeyFile = config.age.secrets.${peerPrivateKeySecret config.node.name}.path; 234 | } 235 | // optionalAttrs hostCfg.server { 236 | ListenPort = networkCfg.port; 237 | }; 238 | wireguardPeers = 239 | if hostCfg.server then 240 | # All client nodes that have their via set to us. 241 | mapAttrsToList (clientName: clientCfg: { 242 | PublicKey = builtins.readFile (peerPublicKeyPath clientName); 243 | PresharedKeyFile = config.age.secrets.${peerPresharedKeySecret config.node.name clientName}.path; 244 | AllowedIPs = 245 | (optional (clientCfg.ipv4 != null) (net.cidr.make 32 clientCfg.ipv4)) 246 | ++ (optional (clientCfg.ipv6 != null) (net.cidr.make 128 clientCfg.ipv6)); 247 | }) peers 248 | else 249 | # We are a client node, so only include our via server. 250 | mapAttrsToList ( 251 | serverName: _: 252 | { 253 | PublicKey = builtins.readFile (peerPublicKeyPath serverName); 254 | PresharedKeyFile = config.age.secrets.${peerPresharedKeySecret config.node.name serverName}.path; 255 | Endpoint = "${networkCfg.host}:${toString networkCfg.port}"; 256 | # Access to the whole network is routed through our entry node. 257 | AllowedIPs = 258 | (optional (networkCfg.cidrv4 != null) networkCfg.cidrv4) 259 | ++ (optional (networkCfg.cidrv6 != null) networkCfg.cidrv6); 260 | } 261 | // optionalAttrs hostCfg.keepalive { 262 | PersistentKeepalive = 25; 263 | } 264 | ) serverNode; 265 | } 266 | ); 267 | systemd.network.networks = flip mapAttrs' memberWG ( 268 | _: networkCfg: 269 | let 270 | hostCfg = networkCfg.hosts.${config.node.name}; 271 | in 272 | nameValuePair hostCfg.unitConfName { 273 | matchConfig.Name = hostCfg.linkName; 274 | address = 275 | (optional (networkCfg.cidrv4 != null) (net.cidr.hostCidr hostCfg.id networkCfg.cidrv4)) 276 | ++ (optional (networkCfg.cidrv6 != null) (net.cidr.hostCidr hostCfg.id networkCfg.cidrv6)); 277 | } 278 | ); 279 | } 280 | -------------------------------------------------------------------------------- /modules/wireguardGlobals.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | let 3 | inherit (lib) 4 | mkOption 5 | types 6 | importJSON 7 | ; 8 | in 9 | { 10 | options.globals = mkOption { 11 | type = types.submodule { 12 | options = { 13 | wireguard = mkOption { 14 | default = { }; 15 | type = types.attrsOf ( 16 | types.submodule ( 17 | { name, config, ... }: 18 | let 19 | wgConf = config; 20 | wgName = name; 21 | in 22 | { 23 | options = { 24 | host = mkOption { 25 | type = types.str; 26 | description = "The host name or IP addresse for reaching the server node."; 27 | }; 28 | idFile = mkOption { 29 | type = types.nullOr types.path; 30 | default = null; 31 | description = "A json file containing a mapping from hostname to id."; 32 | }; 33 | cidrv4 = mkOption { 34 | type = types.nullOr types.net.cidrv4; 35 | default = null; 36 | description = "The server host of this wireguard"; 37 | }; 38 | cidrv6 = mkOption { 39 | type = types.nullOr types.net.cidrv6; 40 | default = null; 41 | description = "The server host of this wireguard"; 42 | }; 43 | port = mkOption { 44 | default = 51820; 45 | type = types.port; 46 | description = "The port the server listens on"; 47 | }; 48 | openFirewall = mkOption { 49 | default = false; 50 | type = types.bool; 51 | description = "Whether to open the servers firewall for the specified {option}`port`. Has no effect for client nodes."; 52 | }; 53 | 54 | hosts = mkOption { 55 | default = { }; 56 | description = "Attrset of hostName to host specific config"; 57 | type = types.attrsOf ( 58 | types.submodule ( 59 | { config, name, ... }: 60 | { 61 | config.id = 62 | let 63 | inherit (wgConf) idFile; 64 | in 65 | if (idFile == null) then 66 | null 67 | else 68 | ( 69 | let 70 | conf = importJSON idFile; 71 | in 72 | conf.${name} or null 73 | ); 74 | options = { 75 | server = mkOption { 76 | default = false; 77 | type = types.bool; 78 | description = "Whether this host acts as the server and relay for the network. Has to be set for exactly 1 host."; 79 | }; 80 | linkName = mkOption { 81 | default = wgName; 82 | type = types.str; 83 | description = "The name of the created network interface. Has to be less than 15 characters."; 84 | }; 85 | unitConfName = mkOption { 86 | default = "40-${config.linkName}"; 87 | type = types.str; 88 | description = "The name of the generated systemd unit configuration files."; 89 | readOnly = true; 90 | }; 91 | id = mkOption { 92 | type = types.int; 93 | description = "The unique id of this host. Used to derive its IP addresses. Has to be smaller than the size of the Subnet."; 94 | }; 95 | ipv4 = mkOption { 96 | type = types.nullOr types.net.ipv4; 97 | default = if (wgConf.cidrv4 == null) then null else lib.net.cidr.host config.id wgConf.cidrv4; 98 | readOnly = true; 99 | description = "The IPv4 of this host. Automatically computed from the {option}`id`"; 100 | }; 101 | ipv6 = mkOption { 102 | type = types.nullOr types.net.ipv6; 103 | default = if (wgConf.cidrv4 == null) then null else lib.net.cidr.host config.id wgConf.cidrv6; 104 | readOnly = true; 105 | description = "The IPv4 of this host. Automatically computed from the {option}`id`"; 106 | }; 107 | keepalive = mkOption { 108 | default = true; 109 | type = types.bool; 110 | description = "Whether to keep the connection alive using PersistentKeepalive. Has no effect for server nodes."; 111 | }; 112 | firewallRuleForAll = mkOption { 113 | default = { }; 114 | description = '' 115 | Allows you to set specific firewall rules for traffic originating from any participant in this 116 | wireguard network. A corresponding rule `wg--to-` will be created to easily expose 117 | services to the network. 118 | ''; 119 | type = types.submodule { 120 | options = { 121 | allowedTCPPorts = mkOption { 122 | type = types.listOf types.port; 123 | default = [ ]; 124 | description = "Convenience option to open specific TCP ports for traffic from the network."; 125 | }; 126 | allowedUDPPorts = mkOption { 127 | type = types.listOf types.port; 128 | default = [ ]; 129 | description = "Convenience option to open specific UDP ports for traffic from the network."; 130 | }; 131 | }; 132 | }; 133 | }; 134 | firewallRuleForNode = mkOption { 135 | default = { }; 136 | description = '' 137 | Allows you to set specific firewall rules just for traffic originating from another network node. 138 | A corresponding rule `wg--node--to-` will be created to easily expose 139 | services to that node. 140 | ''; 141 | type = types.attrsOf ( 142 | types.submodule { 143 | options = { 144 | allowedTCPPorts = mkOption { 145 | type = types.listOf types.port; 146 | default = [ ]; 147 | description = "Convenience option to open specific TCP ports for traffic from another node."; 148 | }; 149 | allowedUDPPorts = mkOption { 150 | type = types.listOf types.port; 151 | default = [ ]; 152 | description = "Convenience option to open specific UDP ports for traffic from another node."; 153 | }; 154 | }; 155 | } 156 | ); 157 | }; 158 | }; 159 | } 160 | ) 161 | ); 162 | }; 163 | }; 164 | } 165 | ) 166 | ); 167 | }; 168 | }; 169 | }; 170 | }; 171 | } 172 | -------------------------------------------------------------------------------- /overlay.nix: -------------------------------------------------------------------------------- 1 | inputs: final: prev: 2 | prev.lib.composeManyExtensions ( 3 | # Order is important to allow using prev instead of final in more places to 4 | # speed up evaluation. 5 | (map (x: import x inputs) [ 6 | # No dependencies 7 | ./lib/types.nix 8 | # No dependencies 9 | ./lib/misc.nix 10 | # No dependencies 11 | ./lib/disko.nix 12 | # Requires misc 13 | ./lib/net.nix 14 | ]) 15 | ++ [ 16 | (import ./pkgs) 17 | ] 18 | ) final prev 19 | -------------------------------------------------------------------------------- /pkgs/default.nix: -------------------------------------------------------------------------------- 1 | _final: prev: { 2 | home-assistant-custom-lovelace-modules = 3 | prev.home-assistant-custom-lovelace-modules 4 | // { 5 | bar-card = prev.callPackage ./home-assistant/bar-card.nix {}; 6 | }; 7 | } 8 | -------------------------------------------------------------------------------- /pkgs/home-assistant/bar-card.nix: -------------------------------------------------------------------------------- 1 | { 2 | stdenvNoCC, 3 | fetchFromGitHub, 4 | fetchYarnDeps, 5 | yarnConfigHook, 6 | yarnBuildHook, 7 | nodejs, 8 | }: 9 | stdenvNoCC.mkDerivation rec { 10 | pname = "bar-card"; 11 | version = "3.2.0"; 12 | 13 | src = fetchFromGitHub { 14 | owner = "patrickdag"; 15 | repo = "bar-card"; 16 | rev = "ad9b1e83f6cf75b699911ebc34a4782c707f254f"; 17 | hash = "sha256-1dX6HErKfhMgu9YQATsUk9jPFbCRRoQLhYISM+evVQM="; 18 | }; 19 | offlineCache = fetchYarnDeps { 20 | inherit src; 21 | hash = "sha256-f/kFCxIinW/9Po0pZM9V8i0ySqiGqz1rmEEFSvw1Gk4="; 22 | }; 23 | nativeBuildInputs = [ 24 | yarnConfigHook 25 | yarnBuildHook 26 | nodejs 27 | ]; 28 | installPhase = '' 29 | runHook preInstall 30 | 31 | mkdir -p $out 32 | cp dist/* $out 33 | 34 | runHook postInstall 35 | ''; 36 | } 37 | -------------------------------------------------------------------------------- /statix.toml: -------------------------------------------------------------------------------- 1 | disabled = [ 2 | "repeated_keys" 3 | ] 4 | -------------------------------------------------------------------------------- /tests/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | nixt, 3 | pkgs, 4 | ... 5 | }: 6 | [ 7 | (import ./shift.nix { inherit pkgs nixt; }) 8 | (import ./net.nix { inherit pkgs nixt; }) 9 | ] 10 | -------------------------------------------------------------------------------- /tests/net.nix: -------------------------------------------------------------------------------- 1 | { 2 | nixt, 3 | pkgs, 4 | ... 5 | }: 6 | let 7 | inherit (nixt.lib) block describe it; 8 | inherit (pkgs) lib; 9 | expected = { 10 | cidrv6 = { 11 | empty = { 12 | base = { 13 | ipv6 = { 14 | a = 0; 15 | b = 0; 16 | c = 0; 17 | d = 0; 18 | }; 19 | }; 20 | length = 0; 21 | }; 22 | base4 = { 23 | base = { 24 | ipv6 = { 25 | a = 65536; 26 | b = 0; 27 | c = 0; 28 | d = 0; 29 | }; 30 | }; 31 | length = 4; 32 | }; 33 | }; 34 | }; 35 | in 36 | block ./net.nix [ 37 | (describe "cidrv6" [ 38 | (it "child 1" (lib.net.cidr.child "1::/4" "0::/0")) 39 | (it "child 2" (lib.net.cidr.child "1::/4" "::/0")) 40 | (it "child 3" (!lib.net.cidr.child "::/0" "::/4")) 41 | (it "child 4" (lib.net.cidr.child "1100::/8" "1000::/4")) 42 | (it "child 5" (!lib.net.cidr.child "1100::/16" "1000::/8")) 43 | (it "child 6" (lib.net.cidr.child "0:1::/24" "0:1::/8")) 44 | 45 | (it "typechecks 1" (lib.typechecks.cidr "" "" "::/0" == expected.cidrv6.empty)) 46 | (it "typechecks 2" (lib.typechecks.cidr "" "" "0::/0" == expected.cidrv6.empty)) 47 | (it "typechecks 3" (lib.typechecks.cidr "" "" "1::/4" == expected.cidrv6.empty // { length = 4; })) 48 | 49 | (it "impl child" (lib.implementations.cidr.child expected.cidrv6.base4 expected.cidrv6.empty)) 50 | 51 | (it "cidr length 0" (lib.implementations.cidr.length expected.cidrv6.empty == 0)) 52 | (it "cidr length 4" (lib.implementations.cidr.length expected.cidrv6.base4 == 4)) 53 | 54 | (it "contains 4" ( 55 | lib.implementations.cidr.contains expected.cidrv6.base4.base expected.cidrv6.empty 56 | )) 57 | 58 | (it "host 4" (lib.implementations.cidr.host 0 expected.cidrv6.base4 == expected.cidrv6.base4.base)) 59 | (it "host 0" (lib.implementations.cidr.host 0 expected.cidrv6.empty == expected.cidrv6.empty.base)) 60 | 61 | (it "make len 0" ( 62 | lib.implementations.cidr.make 0 expected.cidrv6.base4.base == expected.cidrv6.empty 63 | )) 64 | (it "make base 4 len 0 length" ( 65 | (lib.implementations.cidr.make 0 expected.cidrv6.base4.base).length == 0 66 | )) 67 | (it "make base 4 len 0 base" ( 68 | (lib.implementations.cidr.make 0 expected.cidrv6.base4.base).base == expected.cidrv6.empty.base 69 | )) 70 | ]) 71 | (describe "cidrv4" [ 72 | (it "child 1" (lib.net.cidr.child "10.10.10.0/24" "10.0.0.0/8")) 73 | (it "child 2" (!lib.net.cidr.child "127.0.0.0/24" "10.0.0.0/8")) 74 | ]) 75 | (describe "cidr" [ 76 | (it "host 255" (lib.net.cidr.host 255 "192.168.1.0/24" == "192.168.1.255")) 77 | (it "host err" ((builtins.tryEval (lib.net.cidr.host 256 "192.168.1.0/24")).success == false)) 78 | (it "host -1" (lib.net.cidr.host (-1) "192.168.1.0/24" == "192.168.1.255")) 79 | (it "host -256" (lib.net.cidr.host (-256) "192.168.1.0/24" == "192.168.1.0")) 80 | (it "host -err" ((builtins.tryEval (lib.net.cidr.host (-257) "192.168.1.0/24")).success == false)) 81 | (it "host ip" (lib.net.cidr.host "0.0.0.1" "192.168.1.0/24" == "192.168.1.1")) 82 | ]) 83 | (describe "arithmetic" [ 84 | (it "coshadow 1" ( 85 | (lib.arithmetic.coshadow 0 expected.cidrv6.base4.base) == expected.cidrv6.empty.base 86 | )) 87 | (it "coerce 1" ( 88 | (lib.arithmetic.coerce expected.cidrv6.base4.base (-1)) == { 89 | ipv6 = { 90 | a = 4294967295; 91 | b = 4294967295; 92 | c = 4294967295; 93 | d = 4294967295; 94 | }; 95 | } 96 | )) 97 | ]) 98 | (describe "bit" [ 99 | (it "mask 32 " ((lib.bit.mask 32 (-1)) == 4294967295)) 100 | ]) 101 | ] 102 | -------------------------------------------------------------------------------- /tests/shift.nix: -------------------------------------------------------------------------------- 1 | { 2 | nixt, 3 | pkgs, 4 | ... 5 | }: 6 | let 7 | inherit (nixt.lib) block describe it; 8 | inherit (pkgs.lib.bit) left logicalRight arithmeticRight; 9 | in 10 | block ./shift.nix [ 11 | (describe "" [ 12 | (it "left shift 31065 << 63" (left 63 31065 == -9223372036854775807 - 1)) 13 | (it "negative left shift 31065 << 63" (left 63 (-31065) == -9223372036854775807 - 1)) 14 | (it "logical right shift 31065 >> 63" (logicalRight 63 31065 == 0)) 15 | (it "negative logical right shift 31065 >> 63" (logicalRight 63 (-31065) == 1)) 16 | (it "arithmetic right shift 31065 >> 63" (arithmeticRight 63 31065 == 0)) 17 | (it "negative arithmetic right shift 31065 >> 63" (arithmeticRight 63 (-31065) == -1)) 18 | (it "left shift 25624 << 62" (left 62 25624 == 0)) 19 | (it "negative left shift 25624 << 62" (left 62 (-25624) == 0)) 20 | (it "logical right shift 25624 >> 62" (logicalRight 62 25624 == 0)) 21 | (it "negative logical right shift 25624 >> 62" (logicalRight 62 (-25624) == 3)) 22 | (it "arithmetic right shift 25624 >> 62" (arithmeticRight 62 25624 == 0)) 23 | (it "negative arithmetic right shift 25624 >> 62" (arithmeticRight 62 (-25624) == -1)) 24 | (it "left shift 308 << 61" (left 61 308 == -9223372036854775807 - 1)) 25 | (it "negative left shift 308 << 61" (left 61 (-308) == -9223372036854775807 - 1)) 26 | (it "logical right shift 308 >> 61" (logicalRight 61 308 == 0)) 27 | (it "negative logical right shift 308 >> 61" (logicalRight 61 (-308) == 7)) 28 | (it "arithmetic right shift 308 >> 61" (arithmeticRight 61 308 == 0)) 29 | (it "negative arithmetic right shift 308 >> 61" (arithmeticRight 61 (-308) == -1)) 30 | (it "left shift 16712 << 60" (left 60 16712 == -9223372036854775807 - 1)) 31 | (it "negative left shift 16712 << 60" (left 60 (-16712) == -9223372036854775807 - 1)) 32 | (it "logical right shift 16712 >> 60" (logicalRight 60 16712 == 0)) 33 | (it "negative logical right shift 16712 >> 60" (logicalRight 60 (-16712) == 15)) 34 | (it "arithmetic right shift 16712 >> 60" (arithmeticRight 60 16712 == 0)) 35 | (it "negative arithmetic right shift 16712 >> 60" (arithmeticRight 60 (-16712) == -1)) 36 | (it "left shift 5852 << 59" (left 59 5852 == -2305843009213693952)) 37 | (it "negative left shift 5852 << 59" (left 59 (-5852) == 2305843009213693952)) 38 | (it "logical right shift 5852 >> 59" (logicalRight 59 5852 == 0)) 39 | (it "negative logical right shift 5852 >> 59" (logicalRight 59 (-5852) == 31)) 40 | (it "arithmetic right shift 5852 >> 59" (arithmeticRight 59 5852 == 0)) 41 | (it "negative arithmetic right shift 5852 >> 59" (arithmeticRight 59 (-5852) == -1)) 42 | (it "left shift 389 << 58" (left 58 389 == 1441151880758558720)) 43 | (it "negative left shift 389 << 58" (left 58 (-389) == -1441151880758558720)) 44 | (it "logical right shift 389 >> 58" (logicalRight 58 389 == 0)) 45 | (it "negative logical right shift 389 >> 58" (logicalRight 58 (-389) == 63)) 46 | (it "arithmetic right shift 389 >> 58" (arithmeticRight 58 389 == 0)) 47 | (it "negative arithmetic right shift 389 >> 58" (arithmeticRight 58 (-389) == -1)) 48 | (it "left shift 13663 << 57" (left 57 13663 == -4755801206503243776)) 49 | (it "negative left shift 13663 << 57" (left 57 (-13663) == 4755801206503243776)) 50 | (it "logical right shift 13663 >> 57" (logicalRight 57 13663 == 0)) 51 | (it "negative logical right shift 13663 >> 57" (logicalRight 57 (-13663) == 127)) 52 | (it "arithmetic right shift 13663 >> 57" (arithmeticRight 57 13663 == 0)) 53 | (it "negative arithmetic right shift 13663 >> 57" (arithmeticRight 57 (-13663) == -1)) 54 | (it "left shift 15349 << 56" (left 56 15349 == -792633534417207296)) 55 | (it "negative left shift 15349 << 56" (left 56 (-15349) == 792633534417207296)) 56 | (it "logical right shift 15349 >> 56" (logicalRight 56 15349 == 0)) 57 | (it "negative logical right shift 15349 >> 56" (logicalRight 56 (-15349) == 255)) 58 | (it "arithmetic right shift 15349 >> 56" (arithmeticRight 56 15349 == 0)) 59 | (it "negative arithmetic right shift 15349 >> 56" (arithmeticRight 56 (-15349) == -1)) 60 | (it "left shift 25817 << 55" (left 55 25817 == 7818248953115181056)) 61 | (it "negative left shift 25817 << 55" (left 55 (-25817) == -7818248953115181056)) 62 | (it "logical right shift 25817 >> 55" (logicalRight 55 25817 == 0)) 63 | (it "negative logical right shift 25817 >> 55" (logicalRight 55 (-25817) == 511)) 64 | (it "arithmetic right shift 25817 >> 55" (arithmeticRight 55 25817 == 0)) 65 | (it "negative arithmetic right shift 25817 >> 55" (arithmeticRight 55 (-25817) == -1)) 66 | (it "left shift 896 << 54" (left 54 896 == -2305843009213693952)) 67 | (it "negative left shift 896 << 54" (left 54 (-896) == 2305843009213693952)) 68 | (it "logical right shift 896 >> 54" (logicalRight 54 896 == 0)) 69 | (it "negative logical right shift 896 >> 54" (logicalRight 54 (-896) == 1023)) 70 | (it "arithmetic right shift 896 >> 54" (arithmeticRight 54 896 == 0)) 71 | (it "negative arithmetic right shift 896 >> 54" (arithmeticRight 54 (-896) == -1)) 72 | (it "left shift 14592 << 53" (left 53 14592 == 2305843009213693952)) 73 | (it "negative left shift 14592 << 53" (left 53 (-14592) == -2305843009213693952)) 74 | (it "logical right shift 14592 >> 53" (logicalRight 53 14592 == 0)) 75 | (it "negative logical right shift 14592 >> 53" (logicalRight 53 (-14592) == 2047)) 76 | (it "arithmetic right shift 14592 >> 53" (arithmeticRight 53 14592 == 0)) 77 | (it "negative arithmetic right shift 14592 >> 53" (arithmeticRight 53 (-14592) == -1)) 78 | (it "left shift 8533 << 52" (left 52 8533 == 1535727472933339136)) 79 | (it "negative left shift 8533 << 52" (left 52 (-8533) == -1535727472933339136)) 80 | (it "logical right shift 8533 >> 52" (logicalRight 52 8533 == 0)) 81 | (it "negative logical right shift 8533 >> 52" (logicalRight 52 (-8533) == 4095)) 82 | (it "arithmetic right shift 8533 >> 52" (arithmeticRight 52 8533 == 0)) 83 | (it "negative arithmetic right shift 8533 >> 52" (arithmeticRight 52 (-8533) == -1)) 84 | (it "left shift 30180 << 51" (left 51 30180 == -5827657917817421824)) 85 | (it "negative left shift 30180 << 51" (left 51 (-30180) == 5827657917817421824)) 86 | (it "logical right shift 30180 >> 51" (logicalRight 51 30180 == 0)) 87 | (it "negative logical right shift 30180 >> 51" (logicalRight 51 (-30180) == 8191)) 88 | (it "arithmetic right shift 30180 >> 51" (arithmeticRight 51 30180 == 0)) 89 | (it "negative arithmetic right shift 30180 >> 51" (arithmeticRight 51 (-30180) == -1)) 90 | (it "left shift 24903 << 50" (left 50 24903 == -8855202767317237760)) 91 | (it "negative left shift 24903 << 50" (left 50 (-24903) == 8855202767317237760)) 92 | (it "logical right shift 24903 >> 50" (logicalRight 50 24903 == 0)) 93 | (it "negative logical right shift 24903 >> 50" (logicalRight 50 (-24903) == 16383)) 94 | (it "arithmetic right shift 24903 >> 50" (arithmeticRight 50 24903 == 0)) 95 | (it "negative arithmetic right shift 24903 >> 50" (arithmeticRight 50 (-24903) == -1)) 96 | (it "left shift 14440 << 49" (left 49 14440 == 8128997327403745280)) 97 | (it "negative left shift 14440 << 49" (left 49 (-14440) == -8128997327403745280)) 98 | (it "logical right shift 14440 >> 49" (logicalRight 49 14440 == 0)) 99 | (it "negative logical right shift 14440 >> 49" (logicalRight 49 (-14440) == 32767)) 100 | (it "arithmetic right shift 14440 >> 49" (arithmeticRight 49 14440 == 0)) 101 | (it "negative arithmetic right shift 14440 >> 49" (arithmeticRight 49 (-14440) == -1)) 102 | (it "left shift 7398 << 48" (left 48 7398 == 2082351877705433088)) 103 | (it "negative left shift 7398 << 48" (left 48 (-7398) == -2082351877705433088)) 104 | (it "logical right shift 7398 >> 48" (logicalRight 48 7398 == 0)) 105 | (it "negative logical right shift 7398 >> 48" (logicalRight 48 (-7398) == 65535)) 106 | (it "arithmetic right shift 7398 >> 48" (arithmeticRight 48 7398 == 0)) 107 | (it "negative arithmetic right shift 7398 >> 48" (arithmeticRight 48 (-7398) == -1)) 108 | (it "left shift 10215 << 47" (left 47 10215 == 1437633443549675520)) 109 | (it "negative left shift 10215 << 47" (left 47 (-10215) == -1437633443549675520)) 110 | (it "logical right shift 10215 >> 47" (logicalRight 47 10215 == 0)) 111 | (it "negative logical right shift 10215 >> 47" (logicalRight 47 (-10215) == 131071)) 112 | (it "arithmetic right shift 10215 >> 47" (arithmeticRight 47 10215 == 0)) 113 | (it "negative arithmetic right shift 10215 >> 47" (arithmeticRight 47 (-10215) == -1)) 114 | (it "left shift 10172 << 46" (left 46 10172 == 715790865775198208)) 115 | (it "negative left shift 10172 << 46" (left 46 (-10172) == -715790865775198208)) 116 | (it "logical right shift 10172 >> 46" (logicalRight 46 10172 == 0)) 117 | (it "negative logical right shift 10172 >> 46" (logicalRight 46 (-10172) == 262143)) 118 | (it "arithmetic right shift 10172 >> 46" (arithmeticRight 46 10172 == 0)) 119 | (it "negative arithmetic right shift 10172 >> 46" (arithmeticRight 46 (-10172) == -1)) 120 | (it "left shift 15671 << 45" (left 45 15671 == 551374295004086272)) 121 | (it "negative left shift 15671 << 45" (left 45 (-15671) == -551374295004086272)) 122 | (it "logical right shift 15671 >> 45" (logicalRight 45 15671 == 0)) 123 | (it "negative logical right shift 15671 >> 45" (logicalRight 45 (-15671) == 524287)) 124 | (it "arithmetic right shift 15671 >> 45" (arithmeticRight 45 15671 == 0)) 125 | (it "negative arithmetic right shift 15671 >> 45" (arithmeticRight 45 (-15671) == -1)) 126 | (it "left shift 22286 << 44" (left 44 22286 == 392059458185854976)) 127 | (it "negative left shift 22286 << 44" (left 44 (-22286) == -392059458185854976)) 128 | (it "logical right shift 22286 >> 44" (logicalRight 44 22286 == 0)) 129 | (it "negative logical right shift 22286 >> 44" (logicalRight 44 (-22286) == 1048575)) 130 | (it "arithmetic right shift 22286 >> 44" (arithmeticRight 44 22286 == 0)) 131 | (it "negative arithmetic right shift 22286 >> 44" (arithmeticRight 44 (-22286) == -1)) 132 | (it "left shift 29742 << 43" (left 43 29742 == 261613398666510336)) 133 | (it "negative left shift 29742 << 43" (left 43 (-29742) == -261613398666510336)) 134 | (it "logical right shift 29742 >> 43" (logicalRight 43 29742 == 0)) 135 | (it "negative logical right shift 29742 >> 43" (logicalRight 43 (-29742) == 2097151)) 136 | (it "arithmetic right shift 29742 >> 43" (arithmeticRight 43 29742 == 0)) 137 | (it "negative arithmetic right shift 29742 >> 43" (arithmeticRight 43 (-29742) == -1)) 138 | (it "left shift 13223 << 42" (left 42 13223 == 58155369016328192)) 139 | (it "negative left shift 13223 << 42" (left 42 (-13223) == -58155369016328192)) 140 | (it "logical right shift 13223 >> 42" (logicalRight 42 13223 == 0)) 141 | (it "negative logical right shift 13223 >> 42" (logicalRight 42 (-13223) == 4194303)) 142 | (it "arithmetic right shift 13223 >> 42" (arithmeticRight 42 13223 == 0)) 143 | (it "negative arithmetic right shift 13223 >> 42" (arithmeticRight 42 (-13223) == -1)) 144 | (it "left shift 27362 << 41" (left 41 27362 == 60169674318413824)) 145 | (it "negative left shift 27362 << 41" (left 41 (-27362) == -60169674318413824)) 146 | (it "logical right shift 27362 >> 41" (logicalRight 41 27362 == 0)) 147 | (it "negative logical right shift 27362 >> 41" (logicalRight 41 (-27362) == 8388607)) 148 | (it "arithmetic right shift 27362 >> 41" (arithmeticRight 41 27362 == 0)) 149 | (it "negative arithmetic right shift 27362 >> 41" (arithmeticRight 41 (-27362) == -1)) 150 | (it "left shift 9358 << 40" (left 40 9358 == 10289229812727808)) 151 | (it "negative left shift 9358 << 40" (left 40 (-9358) == -10289229812727808)) 152 | (it "logical right shift 9358 >> 40" (logicalRight 40 9358 == 0)) 153 | (it "negative logical right shift 9358 >> 40" (logicalRight 40 (-9358) == 16777215)) 154 | (it "arithmetic right shift 9358 >> 40" (arithmeticRight 40 9358 == 0)) 155 | (it "negative arithmetic right shift 9358 >> 40" (arithmeticRight 40 (-9358) == -1)) 156 | (it "left shift 11691 << 39" (left 39 11691 == 6427195220164608)) 157 | (it "negative left shift 11691 << 39" (left 39 (-11691) == -6427195220164608)) 158 | (it "logical right shift 11691 >> 39" (logicalRight 39 11691 == 0)) 159 | (it "negative logical right shift 11691 >> 39" (logicalRight 39 (-11691) == 33554431)) 160 | (it "arithmetic right shift 11691 >> 39" (arithmeticRight 39 11691 == 0)) 161 | (it "negative arithmetic right shift 11691 >> 39" (arithmeticRight 39 (-11691) == -1)) 162 | (it "left shift 18608 << 38" (left 38 18608 == 5114928092413952)) 163 | (it "negative left shift 18608 << 38" (left 38 (-18608) == -5114928092413952)) 164 | (it "logical right shift 18608 >> 38" (logicalRight 38 18608 == 0)) 165 | (it "negative logical right shift 18608 >> 38" (logicalRight 38 (-18608) == 67108863)) 166 | (it "arithmetic right shift 18608 >> 38" (arithmeticRight 38 18608 == 0)) 167 | (it "negative arithmetic right shift 18608 >> 38" (arithmeticRight 38 (-18608) == -1)) 168 | (it "left shift 30802 << 37" (left 37 30802 == 4233394644844544)) 169 | (it "negative left shift 30802 << 37" (left 37 (-30802) == -4233394644844544)) 170 | (it "logical right shift 30802 >> 37" (logicalRight 37 30802 == 0)) 171 | (it "negative logical right shift 30802 >> 37" (logicalRight 37 (-30802) == 134217727)) 172 | (it "arithmetic right shift 30802 >> 37" (arithmeticRight 37 30802 == 0)) 173 | (it "negative arithmetic right shift 30802 >> 37" (arithmeticRight 37 (-30802) == -1)) 174 | (it "left shift 25321 << 36" (left 36 25321 == 1740045870432256)) 175 | (it "negative left shift 25321 << 36" (left 36 (-25321) == -1740045870432256)) 176 | (it "logical right shift 25321 >> 36" (logicalRight 36 25321 == 0)) 177 | (it "negative logical right shift 25321 >> 36" (logicalRight 36 (-25321) == 268435455)) 178 | (it "arithmetic right shift 25321 >> 36" (arithmeticRight 36 25321 == 0)) 179 | (it "negative arithmetic right shift 25321 >> 36" (arithmeticRight 36 (-25321) == -1)) 180 | (it "left shift 21777 << 35" (left 35 21777 == 748252022439936)) 181 | (it "negative left shift 21777 << 35" (left 35 (-21777) == -748252022439936)) 182 | (it "logical right shift 21777 >> 35" (logicalRight 35 21777 == 0)) 183 | (it "negative logical right shift 21777 >> 35" (logicalRight 35 (-21777) == 536870911)) 184 | (it "arithmetic right shift 21777 >> 35" (arithmeticRight 35 21777 == 0)) 185 | (it "negative arithmetic right shift 21777 >> 35" (arithmeticRight 35 (-21777) == -1)) 186 | (it "left shift 19037 << 34" (left 34 19037 == 327053169655808)) 187 | (it "negative left shift 19037 << 34" (left 34 (-19037) == -327053169655808)) 188 | (it "logical right shift 19037 >> 34" (logicalRight 34 19037 == 0)) 189 | (it "negative logical right shift 19037 >> 34" (logicalRight 34 (-19037) == 1073741823)) 190 | (it "arithmetic right shift 19037 >> 34" (arithmeticRight 34 19037 == 0)) 191 | (it "negative arithmetic right shift 19037 >> 34" (arithmeticRight 34 (-19037) == -1)) 192 | (it "left shift 23041 << 33" (left 33 23041 == 197920682934272)) 193 | (it "negative left shift 23041 << 33" (left 33 (-23041) == -197920682934272)) 194 | (it "logical right shift 23041 >> 33" (logicalRight 33 23041 == 0)) 195 | (it "negative logical right shift 23041 >> 33" (logicalRight 33 (-23041) == 2147483647)) 196 | (it "arithmetic right shift 23041 >> 33" (arithmeticRight 33 23041 == 0)) 197 | (it "negative arithmetic right shift 23041 >> 33" (arithmeticRight 33 (-23041) == -1)) 198 | (it "left shift 13483 << 32" (left 32 13483 == 57909044051968)) 199 | (it "negative left shift 13483 << 32" (left 32 (-13483) == -57909044051968)) 200 | (it "logical right shift 13483 >> 32" (logicalRight 32 13483 == 0)) 201 | (it "negative logical right shift 13483 >> 32" (logicalRight 32 (-13483) == 4294967295)) 202 | (it "arithmetic right shift 13483 >> 32" (arithmeticRight 32 13483 == 0)) 203 | (it "negative arithmetic right shift 13483 >> 32" (arithmeticRight 32 (-13483) == -1)) 204 | (it "left shift 12790 << 31" (left 31 12790 == 27466315857920)) 205 | (it "negative left shift 12790 << 31" (left 31 (-12790) == -27466315857920)) 206 | (it "logical right shift 12790 >> 31" (logicalRight 31 12790 == 0)) 207 | (it "negative logical right shift 12790 >> 31" (logicalRight 31 (-12790) == 8589934591)) 208 | (it "arithmetic right shift 12790 >> 31" (arithmeticRight 31 12790 == 0)) 209 | (it "negative arithmetic right shift 12790 >> 31" (arithmeticRight 31 (-12790) == -1)) 210 | (it "left shift 29648 << 30" (left 30 29648 == 31834297597952)) 211 | (it "negative left shift 29648 << 30" (left 30 (-29648) == -31834297597952)) 212 | (it "logical right shift 29648 >> 30" (logicalRight 30 29648 == 0)) 213 | (it "negative logical right shift 29648 >> 30" (logicalRight 30 (-29648) == 17179869183)) 214 | (it "arithmetic right shift 29648 >> 30" (arithmeticRight 30 29648 == 0)) 215 | (it "negative arithmetic right shift 29648 >> 30" (arithmeticRight 30 (-29648) == -1)) 216 | (it "left shift 31723 << 29" (left 29 31723 == 17031155941376)) 217 | (it "negative left shift 31723 << 29" (left 29 (-31723) == -17031155941376)) 218 | (it "logical right shift 31723 >> 29" (logicalRight 29 31723 == 0)) 219 | (it "negative logical right shift 31723 >> 29" (logicalRight 29 (-31723) == 34359738367)) 220 | (it "arithmetic right shift 31723 >> 29" (arithmeticRight 29 31723 == 0)) 221 | (it "negative arithmetic right shift 31723 >> 29" (arithmeticRight 29 (-31723) == -1)) 222 | (it "left shift 429 << 28" (left 28 429 == 115158810624)) 223 | (it "negative left shift 429 << 28" (left 28 (-429) == -115158810624)) 224 | (it "logical right shift 429 >> 28" (logicalRight 28 429 == 0)) 225 | (it "negative logical right shift 429 >> 28" (logicalRight 28 (-429) == 68719476735)) 226 | (it "arithmetic right shift 429 >> 28" (arithmeticRight 28 429 == 0)) 227 | (it "negative arithmetic right shift 429 >> 28" (arithmeticRight 28 (-429) == -1)) 228 | (it "left shift 3138 << 27" (left 27 3138 == 421175230464)) 229 | (it "negative left shift 3138 << 27" (left 27 (-3138) == -421175230464)) 230 | (it "logical right shift 3138 >> 27" (logicalRight 27 3138 == 0)) 231 | (it "negative logical right shift 3138 >> 27" (logicalRight 27 (-3138) == 137438953471)) 232 | (it "arithmetic right shift 3138 >> 27" (arithmeticRight 27 3138 == 0)) 233 | (it "negative arithmetic right shift 3138 >> 27" (arithmeticRight 27 (-3138) == -1)) 234 | (it "left shift 20136 << 26" (left 26 20136 == 1351304085504)) 235 | (it "negative left shift 20136 << 26" (left 26 (-20136) == -1351304085504)) 236 | (it "logical right shift 20136 >> 26" (logicalRight 26 20136 == 0)) 237 | (it "negative logical right shift 20136 >> 26" (logicalRight 26 (-20136) == 274877906943)) 238 | (it "arithmetic right shift 20136 >> 26" (arithmeticRight 26 20136 == 0)) 239 | (it "negative arithmetic right shift 20136 >> 26" (arithmeticRight 26 (-20136) == -1)) 240 | (it "left shift 27841 << 25" (left 25 27841 == 934188941312)) 241 | (it "negative left shift 27841 << 25" (left 25 (-27841) == -934188941312)) 242 | (it "logical right shift 27841 >> 25" (logicalRight 25 27841 == 0)) 243 | (it "negative logical right shift 27841 >> 25" (logicalRight 25 (-27841) == 549755813887)) 244 | (it "arithmetic right shift 27841 >> 25" (arithmeticRight 25 27841 == 0)) 245 | (it "negative arithmetic right shift 27841 >> 25" (arithmeticRight 25 (-27841) == -1)) 246 | (it "left shift 23965 << 24" (left 24 23965 == 402065981440)) 247 | (it "negative left shift 23965 << 24" (left 24 (-23965) == -402065981440)) 248 | (it "logical right shift 23965 >> 24" (logicalRight 24 23965 == 0)) 249 | (it "negative logical right shift 23965 >> 24" (logicalRight 24 (-23965) == 1099511627775)) 250 | (it "arithmetic right shift 23965 >> 24" (arithmeticRight 24 23965 == 0)) 251 | (it "negative arithmetic right shift 23965 >> 24" (arithmeticRight 24 (-23965) == -1)) 252 | (it "left shift 9647 << 23" (left 23 9647 == 80924901376)) 253 | (it "negative left shift 9647 << 23" (left 23 (-9647) == -80924901376)) 254 | (it "logical right shift 9647 >> 23" (logicalRight 23 9647 == 0)) 255 | (it "negative logical right shift 9647 >> 23" (logicalRight 23 (-9647) == 2199023255551)) 256 | (it "arithmetic right shift 9647 >> 23" (arithmeticRight 23 9647 == 0)) 257 | (it "negative arithmetic right shift 9647 >> 23" (arithmeticRight 23 (-9647) == -1)) 258 | (it "left shift 13329 << 22" (left 22 13329 == 55905878016)) 259 | (it "negative left shift 13329 << 22" (left 22 (-13329) == -55905878016)) 260 | (it "logical right shift 13329 >> 22" (logicalRight 22 13329 == 0)) 261 | (it "negative logical right shift 13329 >> 22" (logicalRight 22 (-13329) == 4398046511103)) 262 | (it "arithmetic right shift 13329 >> 22" (arithmeticRight 22 13329 == 0)) 263 | (it "negative arithmetic right shift 13329 >> 22" (arithmeticRight 22 (-13329) == -1)) 264 | (it "left shift 32694 << 21" (left 21 32694 == 68564287488)) 265 | (it "negative left shift 32694 << 21" (left 21 (-32694) == -68564287488)) 266 | (it "logical right shift 32694 >> 21" (logicalRight 21 32694 == 0)) 267 | (it "negative logical right shift 32694 >> 21" (logicalRight 21 (-32694) == 8796093022207)) 268 | (it "arithmetic right shift 32694 >> 21" (arithmeticRight 21 32694 == 0)) 269 | (it "negative arithmetic right shift 32694 >> 21" (arithmeticRight 21 (-32694) == -1)) 270 | (it "left shift 17805 << 20" (left 20 17805 == 18669895680)) 271 | (it "negative left shift 17805 << 20" (left 20 (-17805) == -18669895680)) 272 | (it "logical right shift 17805 >> 20" (logicalRight 20 17805 == 0)) 273 | (it "negative logical right shift 17805 >> 20" (logicalRight 20 (-17805) == 17592186044415)) 274 | (it "arithmetic right shift 17805 >> 20" (arithmeticRight 20 17805 == 0)) 275 | (it "negative arithmetic right shift 17805 >> 20" (arithmeticRight 20 (-17805) == -1)) 276 | (it "left shift 32292 << 19" (left 19 32292 == 16930308096)) 277 | (it "negative left shift 32292 << 19" (left 19 (-32292) == -16930308096)) 278 | (it "logical right shift 32292 >> 19" (logicalRight 19 32292 == 0)) 279 | (it "negative logical right shift 32292 >> 19" (logicalRight 19 (-32292) == 35184372088831)) 280 | (it "arithmetic right shift 32292 >> 19" (arithmeticRight 19 32292 == 0)) 281 | (it "negative arithmetic right shift 32292 >> 19" (arithmeticRight 19 (-32292) == -1)) 282 | (it "left shift 16841 << 18" (left 18 16841 == 4414767104)) 283 | (it "negative left shift 16841 << 18" (left 18 (-16841) == -4414767104)) 284 | (it "logical right shift 16841 >> 18" (logicalRight 18 16841 == 0)) 285 | (it "negative logical right shift 16841 >> 18" (logicalRight 18 (-16841) == 70368744177663)) 286 | (it "arithmetic right shift 16841 >> 18" (arithmeticRight 18 16841 == 0)) 287 | (it "negative arithmetic right shift 16841 >> 18" (arithmeticRight 18 (-16841) == -1)) 288 | (it "left shift 18798 << 17" (left 17 18798 == 2463891456)) 289 | (it "negative left shift 18798 << 17" (left 17 (-18798) == -2463891456)) 290 | (it "logical right shift 18798 >> 17" (logicalRight 17 18798 == 0)) 291 | (it "negative logical right shift 18798 >> 17" (logicalRight 17 (-18798) == 140737488355327)) 292 | (it "arithmetic right shift 18798 >> 17" (arithmeticRight 17 18798 == 0)) 293 | (it "negative arithmetic right shift 18798 >> 17" (arithmeticRight 17 (-18798) == -1)) 294 | (it "left shift 2511 << 16" (left 16 2511 == 164560896)) 295 | (it "negative left shift 2511 << 16" (left 16 (-2511) == -164560896)) 296 | (it "logical right shift 2511 >> 16" (logicalRight 16 2511 == 0)) 297 | (it "negative logical right shift 2511 >> 16" (logicalRight 16 (-2511) == 281474976710655)) 298 | (it "arithmetic right shift 2511 >> 16" (arithmeticRight 16 2511 == 0)) 299 | (it "negative arithmetic right shift 2511 >> 16" (arithmeticRight 16 (-2511) == -1)) 300 | (it "left shift 933 << 15" (left 15 933 == 30572544)) 301 | (it "negative left shift 933 << 15" (left 15 (-933) == -30572544)) 302 | (it "logical right shift 933 >> 15" (logicalRight 15 933 == 0)) 303 | (it "negative logical right shift 933 >> 15" (logicalRight 15 (-933) == 562949953421311)) 304 | (it "arithmetic right shift 933 >> 15" (arithmeticRight 15 933 == 0)) 305 | (it "negative arithmetic right shift 933 >> 15" (arithmeticRight 15 (-933) == -1)) 306 | (it "left shift 26861 << 14" (left 14 26861 == 440090624)) 307 | (it "negative left shift 26861 << 14" (left 14 (-26861) == -440090624)) 308 | (it "logical right shift 26861 >> 14" (logicalRight 14 26861 == 1)) 309 | (it "negative logical right shift 26861 >> 14" (logicalRight 14 (-26861) == 1125899906842622)) 310 | (it "arithmetic right shift 26861 >> 14" (arithmeticRight 14 26861 == 1)) 311 | (it "negative arithmetic right shift 26861 >> 14" (arithmeticRight 14 (-26861) == -2)) 312 | (it "left shift 13766 << 13" (left 13 13766 == 112771072)) 313 | (it "negative left shift 13766 << 13" (left 13 (-13766) == -112771072)) 314 | (it "logical right shift 13766 >> 13" (logicalRight 13 13766 == 1)) 315 | (it "negative logical right shift 13766 >> 13" (logicalRight 13 (-13766) == 2251799813685246)) 316 | (it "arithmetic right shift 13766 >> 13" (arithmeticRight 13 13766 == 1)) 317 | (it "negative arithmetic right shift 13766 >> 13" (arithmeticRight 13 (-13766) == -2)) 318 | (it "left shift 21344 << 12" (left 12 21344 == 87425024)) 319 | (it "negative left shift 21344 << 12" (left 12 (-21344) == -87425024)) 320 | (it "logical right shift 21344 >> 12" (logicalRight 12 21344 == 5)) 321 | (it "negative logical right shift 21344 >> 12" (logicalRight 12 (-21344) == 4503599627370490)) 322 | (it "arithmetic right shift 21344 >> 12" (arithmeticRight 12 21344 == 5)) 323 | (it "negative arithmetic right shift 21344 >> 12" (arithmeticRight 12 (-21344) == -6)) 324 | (it "left shift 8246 << 11" (left 11 8246 == 16887808)) 325 | (it "negative left shift 8246 << 11" (left 11 (-8246) == -16887808)) 326 | (it "logical right shift 8246 >> 11" (logicalRight 11 8246 == 4)) 327 | (it "negative logical right shift 8246 >> 11" (logicalRight 11 (-8246) == 9007199254740987)) 328 | (it "arithmetic right shift 8246 >> 11" (arithmeticRight 11 8246 == 4)) 329 | (it "negative arithmetic right shift 8246 >> 11" (arithmeticRight 11 (-8246) == -5)) 330 | (it "left shift 18044 << 10" (left 10 18044 == 18477056)) 331 | (it "negative left shift 18044 << 10" (left 10 (-18044) == -18477056)) 332 | (it "logical right shift 18044 >> 10" (logicalRight 10 18044 == 17)) 333 | (it "negative logical right shift 18044 >> 10" (logicalRight 10 (-18044) == 18014398509481966)) 334 | (it "arithmetic right shift 18044 >> 10" (arithmeticRight 10 18044 == 17)) 335 | (it "negative arithmetic right shift 18044 >> 10" (arithmeticRight 10 (-18044) == -18)) 336 | (it "left shift 11554 << 9" (left 9 11554 == 5915648)) 337 | (it "negative left shift 11554 << 9" (left 9 (-11554) == -5915648)) 338 | (it "logical right shift 11554 >> 9" (logicalRight 9 11554 == 22)) 339 | (it "negative logical right shift 11554 >> 9" (logicalRight 9 (-11554) == 36028797018963945)) 340 | (it "arithmetic right shift 11554 >> 9" (arithmeticRight 9 11554 == 22)) 341 | (it "negative arithmetic right shift 11554 >> 9" (arithmeticRight 9 (-11554) == -23)) 342 | (it "left shift 19651 << 8" (left 8 19651 == 5030656)) 343 | (it "negative left shift 19651 << 8" (left 8 (-19651) == -5030656)) 344 | (it "logical right shift 19651 >> 8" (logicalRight 8 19651 == 76)) 345 | (it "negative logical right shift 19651 >> 8" (logicalRight 8 (-19651) == 72057594037927859)) 346 | (it "arithmetic right shift 19651 >> 8" (arithmeticRight 8 19651 == 76)) 347 | (it "negative arithmetic right shift 19651 >> 8" (arithmeticRight 8 (-19651) == -77)) 348 | (it "left shift 32588 << 7" (left 7 32588 == 4171264)) 349 | (it "negative left shift 32588 << 7" (left 7 (-32588) == -4171264)) 350 | (it "logical right shift 32588 >> 7" (logicalRight 7 32588 == 254)) 351 | (it "negative logical right shift 32588 >> 7" (logicalRight 7 (-32588) == 144115188075855617)) 352 | (it "arithmetic right shift 32588 >> 7" (arithmeticRight 7 32588 == 254)) 353 | (it "negative arithmetic right shift 32588 >> 7" (arithmeticRight 7 (-32588) == -255)) 354 | (it "left shift 23333 << 6" (left 6 23333 == 1493312)) 355 | (it "negative left shift 23333 << 6" (left 6 (-23333) == -1493312)) 356 | (it "logical right shift 23333 >> 6" (logicalRight 6 23333 == 364)) 357 | (it "negative logical right shift 23333 >> 6" (logicalRight 6 (-23333) == 288230376151711379)) 358 | (it "arithmetic right shift 23333 >> 6" (arithmeticRight 6 23333 == 364)) 359 | (it "negative arithmetic right shift 23333 >> 6" (arithmeticRight 6 (-23333) == -365)) 360 | (it "left shift 13058 << 5" (left 5 13058 == 417856)) 361 | (it "negative left shift 13058 << 5" (left 5 (-13058) == -417856)) 362 | (it "logical right shift 13058 >> 5" (logicalRight 5 13058 == 408)) 363 | (it "negative logical right shift 13058 >> 5" (logicalRight 5 (-13058) == 576460752303423079)) 364 | (it "arithmetic right shift 13058 >> 5" (arithmeticRight 5 13058 == 408)) 365 | (it "negative arithmetic right shift 13058 >> 5" (arithmeticRight 5 (-13058) == -409)) 366 | (it "left shift 10754 << 4" (left 4 10754 == 172064)) 367 | (it "negative left shift 10754 << 4" (left 4 (-10754) == -172064)) 368 | (it "logical right shift 10754 >> 4" (logicalRight 4 10754 == 672)) 369 | (it "negative logical right shift 10754 >> 4" (logicalRight 4 (-10754) == 1152921504606846303)) 370 | (it "arithmetic right shift 10754 >> 4" (arithmeticRight 4 10754 == 672)) 371 | (it "negative arithmetic right shift 10754 >> 4" (arithmeticRight 4 (-10754) == -673)) 372 | (it "left shift 22538 << 3" (left 3 22538 == 180304)) 373 | (it "negative left shift 22538 << 3" (left 3 (-22538) == -180304)) 374 | (it "logical right shift 22538 >> 3" (logicalRight 3 22538 == 2817)) 375 | (it "negative logical right shift 22538 >> 3" (logicalRight 3 (-22538) == 2305843009213691134)) 376 | (it "arithmetic right shift 22538 >> 3" (arithmeticRight 3 22538 == 2817)) 377 | (it "negative arithmetic right shift 22538 >> 3" (arithmeticRight 3 (-22538) == -2818)) 378 | (it "left shift 1792 << 2" (left 2 1792 == 7168)) 379 | (it "negative left shift 1792 << 2" (left 2 (-1792) == -7168)) 380 | (it "logical right shift 1792 >> 2" (logicalRight 2 1792 == 448)) 381 | (it "negative logical right shift 1792 >> 2" (logicalRight 2 (-1792) == 4611686018427387456)) 382 | (it "arithmetic right shift 1792 >> 2" (arithmeticRight 2 1792 == 448)) 383 | (it "negative arithmetic right shift 1792 >> 2" (arithmeticRight 2 (-1792) == -448)) 384 | (it "left shift 29700 << 1" (left 1 29700 == 59400)) 385 | (it "negative left shift 29700 << 1" (left 1 (-29700) == -59400)) 386 | (it "logical right shift 29700 >> 1" (logicalRight 1 29700 == 14850)) 387 | (it "negative logical right shift 29700 >> 1" (logicalRight 1 (-29700) == 9223372036854760958)) 388 | (it "arithmetic right shift 29700 >> 1" (arithmeticRight 1 29700 == 14850)) 389 | (it "negative arithmetic right shift 29700 >> 1" (arithmeticRight 1 (-29700) == -14850)) 390 | (it "left shift 21068 << 0" (left 0 21068 == 21068)) 391 | (it "negative left shift 21068 << 0" (left 0 (-21068) == -21068)) 392 | (it "logical right shift 21068 >> 0" (logicalRight 0 21068 == 21068)) 393 | (it "negative logical right shift 21068 >> 0" (logicalRight 0 (-21068) == -21068)) 394 | (it "arithmetic right shift 21068 >> 0" (arithmeticRight 0 21068 == 21068)) 395 | (it "negative arithmetic right shift 21068 >> 0" (arithmeticRight 0 (-21068) == -21068)) 396 | (it "arithmetic right shift -1 >> 32" (arithmeticRight 32 (-1) == (-1))) 397 | (it "arithmetic right shift -1 >> 102" (arithmeticRight 102 (-1) == (-1))) 398 | ]) 399 | ] 400 | --------------------------------------------------------------------------------