├── .editorconfig ├── .github ├── dependabot.yml └── workflows │ └── check.yml ├── .gitmodules ├── COPYING ├── bors.toml ├── default.nix ├── devshellModule.nix ├── flake.lock ├── flake.nix ├── module.nix ├── nixosModule.nix ├── scripts ├── burn.bash ├── hm-home.bash ├── hosts-build.bash ├── hosts-install.bash ├── hosts-rebuild.bash ├── hosts-vm.bash ├── onboarding-up.bash ├── utils-repl │ ├── default.nix │ └── repl.nix ├── utils-ssh-show.bash └── utils-update.bash ├── shell.nix ├── stdProfile.nix ├── ufr-polyfills ├── UnofficialFlakesRoadmap.md ├── eachSystem.nix ├── flake.lock.nix └── ufrContract.nix └── writeBashWithPaths.nix /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | end_of_line = lf 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | charset = utf-8 9 | indent_style = space 10 | indent_size = 2 11 | 12 | # Ignore diffs/patches 13 | [*.{diff,patch}] 14 | end_of_line = unset 15 | insert_final_newline = unset 16 | trim_trailing_whitespace = unset 17 | indent_size = unset 18 | 19 | [{.*,secrets}/**] 20 | end_of_line = unset 21 | insert_final_newline = unset 22 | trim_trailing_whitespace = unset 23 | charset = unset 24 | indent_style = unset 25 | indent_size = unset 26 | 27 | [*.py] 28 | indent_size = 4 29 | 30 | [*.md] 31 | max_line_length = off 32 | trim_trailing_whitespace = false 33 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | name: "Check & Cachix" 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - trying 8 | - staging 9 | 10 | jobs: 11 | check: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2.3.4 15 | with: 16 | submodules: true 17 | - uses: cachix/install-nix-action@v14 18 | with: 19 | install_url: https://github.com/numtide/nix-flakes-installer/releases/download/nix-2.4pre20210823_af94b54/install 20 | extra_nix_config: | 21 | experimental-features = nix-command flakes 22 | system-features = nixos-test benchmark big-parallel kvm recursive-nix 23 | substituters = https://nrdxp.cachix.org https://nix-community.cachix.org https://cache.nixos.org 24 | trusted-public-keys = nrdxp.cachix.org-1:Fc5PSqY2Jm1TrWfm88l6cvGWwz3s93c6IOifQWnhNW4= nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs= cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= 25 | 26 | - uses: cachix/cachix-action@v10 27 | with: 28 | name: nrdxp 29 | signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}' 30 | authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' 31 | 32 | - name: Bash format check 33 | run: | 34 | nix -Lv develop -c -- fmt-bash --check 35 | 36 | # Check bud top level command 37 | - run: nix -Lv develop -c nix run . 38 | # TODO: possible testing strategies for ssh-show command 39 | # - uses: shimataro/ssh-key-action@v2 40 | # with: 41 | # key: ${{ secrets.SSH_KEY }} 42 | # known_hosts: | 43 | # : 44 | # - run: nix run --show-trace . -- ssh-show-ed25519 45 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "e2e/devos"] 2 | path = e2e/devos 3 | url = https://github.com/divnix/devos.git 4 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any person obtaining 2 | a copy of this software and associated documentation files (the 3 | "Software"), to deal in the Software without restriction, including 4 | without limitation the rights to use, copy, modify, merge, publish, 5 | distribute, sublicense, and/or sell copies of the Software, and to 6 | permit persons to whom the Software is furnished to do so, subject to 7 | the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be 10 | included in all copies or substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 13 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 14 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 15 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 16 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 17 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 18 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | -------------------------------------------------------------------------------- /bors.toml: -------------------------------------------------------------------------------- 1 | status = [ "check" ] 2 | 3 | required_approvals = 1 4 | 5 | up_to_date_approvals = true 6 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | { system ? builtins.currentSystem 2 | , inputs ? import ./ufr-polyfills/flake.lock.nix ./. 3 | 4 | # alternative 1 -------------------------------------------------- 5 | 6 | , pkgs ? import inputs.nixpkgs { 7 | inherit system; 8 | overlays = inputs.nixpkgs.lib.optionals 9 | ((inputs ? self) && (inputs.self ? overlays)) 10 | (builtins.attrValues inputs.self.overlays) 11 | ; 12 | config = { }; 13 | } 14 | 15 | # function config ------------------------------------------------ 16 | 17 | , budModules ? pkgs.lib.optionals 18 | ((inputs ? self) && (inputs.self ? budModules)) 19 | (builtins.attrValues inputs.self.budModules) 20 | 21 | # pass the host's config for inferring the reverse dns fqdn of this host 22 | # so that this script can accessor the _current_ host identifier in 23 | # self.nixosConfigurations. 24 | , hostConfig ? null 25 | 26 | # pass a string to the path where you hold a (writable) local copy of the flake repo 27 | # so that this script can execute operations on that flake, such as updates, etc. 28 | , editableFlakeRoot ? null 29 | 30 | }: 31 | let 32 | 33 | lib = pkgs.lib; 34 | name = "bud"; 35 | description = "Your highly customizable system ctl"; 36 | 37 | budModule = import ./module.nix; 38 | stdProfile = import ./stdProfile.nix; 39 | 40 | pkgsModule = { config, lib, ... }: { 41 | config = { 42 | _module.args.name = name; 43 | _module.args.baseModules = [ budModule ]; 44 | _module.args.pkgs = lib.mkDefault pkgs; 45 | _module.args.hostConfig = hostConfig; 46 | _module.args.editableFlakeRoot = editableFlakeRoot; 47 | }; 48 | }; 49 | 50 | budUtilsModule = { pkgs, lib, ... }: { 51 | config = { 52 | _module.args.budUtils = { 53 | writeBashWithPaths = import ./writeBashWithPaths.nix { 54 | inherit pkgs lib; 55 | }; 56 | }; 57 | }; 58 | }; 59 | 60 | evaled = lib.evalModules { 61 | modules = [ pkgsModule budUtilsModule budModule stdProfile ] ++ budModules; 62 | }; 63 | 64 | in 65 | evaled.config.bud.cmd // { 66 | meta = { inherit description; }; 67 | } 68 | -------------------------------------------------------------------------------- /devshellModule.nix: -------------------------------------------------------------------------------- 1 | # the bud function that still needs to be instantiated 2 | bud: 3 | { pkgs 4 | # we require a reference to the flake 5 | # in order to collect self.budModules 6 | , self 7 | , ... 8 | }: 9 | let 10 | reboudBud = bud self; 11 | in 12 | { 13 | _file = toString ./.; 14 | commands = [ 15 | { 16 | category = "devos"; 17 | package = reboudBud { inherit pkgs; }; 18 | } 19 | ]; 20 | } 21 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "beautysh": { 4 | "inputs": { 5 | "flake-utils": "flake-utils", 6 | "nixpkgs": "nixpkgs", 7 | "poetry2nix": "poetry2nix" 8 | }, 9 | "locked": { 10 | "lastModified": 1641830469, 11 | "narHash": "sha256-uhDmgNP/biOWe4FtOa6c2xZnREH+NP9rdrMm0LccRUk=", 12 | "owner": "lovesegfault", 13 | "repo": "beautysh", 14 | "rev": "e85d9736927c0fcf2abb05cb3a2d8d9b4502a2eb", 15 | "type": "github" 16 | }, 17 | "original": { 18 | "owner": "lovesegfault", 19 | "repo": "beautysh", 20 | "type": "github" 21 | } 22 | }, 23 | "devshell": { 24 | "locked": { 25 | "lastModified": 1640301433, 26 | "narHash": "sha256-eplae8ZNiEmxbOgwUn9IihaJfEUxoUilkwciRPGskYE=", 27 | "owner": "numtide", 28 | "repo": "devshell", 29 | "rev": "f87fb932740abe1c1b46f6edd8a36ade17881666", 30 | "type": "github" 31 | }, 32 | "original": { 33 | "owner": "numtide", 34 | "repo": "devshell", 35 | "type": "github" 36 | } 37 | }, 38 | "flake-utils": { 39 | "locked": { 40 | "lastModified": 1631561581, 41 | "narHash": "sha256-3VQMV5zvxaVLvqqUrNz3iJelLw30mIVSfZmAaauM3dA=", 42 | "owner": "numtide", 43 | "repo": "flake-utils", 44 | "rev": "7e5bf3925f6fbdfaf50a2a7ca0be2879c4261d19", 45 | "type": "github" 46 | }, 47 | "original": { 48 | "owner": "numtide", 49 | "repo": "flake-utils", 50 | "type": "github" 51 | } 52 | }, 53 | "nixpkgs": { 54 | "locked": { 55 | "lastModified": 1633971123, 56 | "narHash": "sha256-WmI4NbH1IPGFWVkuBkKoYgOnxgwSfWDgdZplJlQ93vA=", 57 | "owner": "NixOS", 58 | "repo": "nixpkgs", 59 | "rev": "e4ef597edfd8a0ba5f12362932fc9b1dd01a0aef", 60 | "type": "github" 61 | }, 62 | "original": { 63 | "owner": "NixOS", 64 | "ref": "nixos-unstable-small", 65 | "repo": "nixpkgs", 66 | "type": "github" 67 | } 68 | }, 69 | "nixpkgs_2": { 70 | "locked": { 71 | "lastModified": 1641947035, 72 | "narHash": "sha256-l4dnwWvh2Yte2I9sAwGhMG/qkvLkfV0v/ssgkZ5KNY4=", 73 | "owner": "nixos", 74 | "repo": "nixpkgs", 75 | "rev": "9acedfd7ef32253237311dc3c78286773dbcb0d6", 76 | "type": "github" 77 | }, 78 | "original": { 79 | "owner": "nixos", 80 | "ref": "release-21.11", 81 | "repo": "nixpkgs", 82 | "type": "github" 83 | } 84 | }, 85 | "poetry2nix": { 86 | "inputs": { 87 | "flake-utils": [ 88 | "beautysh", 89 | "flake-utils" 90 | ], 91 | "nixpkgs": [ 92 | "beautysh", 93 | "nixpkgs" 94 | ] 95 | }, 96 | "locked": { 97 | "lastModified": 1633382856, 98 | "narHash": "sha256-hYlet806M9xJj4yxf0g5fhDT2IEUVIMAl7sqIeZ8DUM=", 99 | "owner": "nix-community", 100 | "repo": "poetry2nix", 101 | "rev": "705cbfa10e3d9bfed2e59e0256844ae3704dbd7e", 102 | "type": "github" 103 | }, 104 | "original": { 105 | "owner": "nix-community", 106 | "repo": "poetry2nix", 107 | "type": "github" 108 | } 109 | }, 110 | "root": { 111 | "inputs": { 112 | "beautysh": "beautysh", 113 | "devshell": "devshell", 114 | "nixpkgs": "nixpkgs_2" 115 | } 116 | } 117 | }, 118 | "root": "root", 119 | "version": 7 120 | } 121 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Bud - a highly composable system ctl command"; 3 | 4 | nixConfig.extra-experimental-features = "nix-command flakes ca-references"; 5 | nixConfig.extra-substituters = "https://nrdxp.cachix.org https://nix-community.cachix.org"; 6 | nixConfig.extra-trusted-public-keys = "nrdxp.cachix.org-1:Fc5PSqY2Jm1TrWfm88l6cvGWwz3s93c6IOifQWnhNW4= nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="; 7 | 8 | inputs = { 9 | nixpkgs.url = "github:nixos/nixpkgs/release-21.11"; 10 | devshell.url = "github:numtide/devshell"; 11 | beautysh.url = "github:lovesegfault/beautysh"; 12 | }; 13 | 14 | outputs = { self, nixpkgs, devshell, beautysh, ... }: 15 | let 16 | 17 | # Unofficial Flakes Roadmap - Polyfills 18 | # .. see: https://demo.hedgedoc.org/s/_W6Ve03GK# 19 | # .. also: /ufr-polyfills 20 | 21 | # Super Stupid Flakes / System As an Input - Style: 22 | supportedSystems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" ]; 23 | ufrContract = import ./ufr-polyfills/ufrContract.nix; 24 | 25 | # Dependency Groups - Style 26 | budInputs = { inherit self nixpkgs; }; 27 | 28 | # repind this flake's functor to new self as part of the inputs 29 | # this helps to completely avoid invoking flake.lock.nix. 30 | # In a flake-only scenario, flake.lock.nix would disregard 31 | # inputs follows configurations. 32 | rebind = src: inpt: _: rebound: args: 33 | let 34 | inputs = inpt // { self = rebound; }; 35 | in 36 | import src ({ inherit inputs; } // args); 37 | 38 | # Dependency Groups - Style 39 | devShellInputs = { inherit nixpkgs devshell beautysh; }; 40 | 41 | # .. we hope you like this style. 42 | # .. it's adopted by a growing number of projects. 43 | # Please consider adopting it if you want to help to improve flakes. 44 | 45 | in 46 | { 47 | lib.writeBashWithPaths = import ./writeBashWithPaths.nix; 48 | nixosModules.bud = import ./nixosModule.nix self; 49 | devshellModules.bud = import ./devshellModule.nix self; 50 | 51 | defaultPackage = ufrContract supportedSystems ./. budInputs; 52 | 53 | # The flake's functor ... 54 | # ... knows how to consume the self.overlays it's currently bound to 55 | overlays = { }; 56 | 57 | # ... knows how to consume self.budModules it's currently bound to 58 | budModules = { }; 59 | 60 | # usage: inputs.bud newSelf { ... }; 61 | __functor = rebind ./. budInputs; 62 | 63 | # bud-local use 64 | devShell = ufrContract supportedSystems ./shell.nix devShellInputs; 65 | 66 | }; 67 | } 68 | -------------------------------------------------------------------------------- /module.nix: -------------------------------------------------------------------------------- 1 | { name, config, lib, pkgs, hostConfig, editableFlakeRoot, ... }: 2 | with lib; 3 | let 4 | cfg = config.bud; 5 | entryOptions = { 6 | enable = mkEnableOption "cmd" // { default = true; }; 7 | 8 | synopsis = mkOption { 9 | type = types.str; 10 | description = '' 11 | Synopsis. 12 | ''; 13 | }; 14 | help = mkOption { 15 | type = types.str; 16 | description = '' 17 | Short help. 18 | ''; 19 | }; 20 | description = mkOption { 21 | type = types.str; 22 | default = ""; 23 | description = '' 24 | Longer descriptions. 25 | ''; 26 | }; 27 | 28 | writer = mkOption { 29 | type = types.functionTo (types.functionTo types.package); 30 | description = '' 31 | Script to run. 32 | ''; 33 | }; 34 | 35 | script = mkOption { 36 | type = types.path; 37 | description = '' 38 | Script to run. 39 | ''; 40 | }; 41 | 42 | deps = mkOption { 43 | type = types.listOf types.str; 44 | default = [ ]; 45 | description = '' 46 | A list of other commands that this one should be cuaranteed to be placed after. 47 | ''; 48 | }; 49 | }; 50 | 51 | addUsage = 52 | mapAttrs (k: v: builtins.removeAttrs v [ "script" "enable" "synopsis" "help" "description" ] // { 53 | text = '' 54 | "${v.synopsis}" "${v.help}" \''; 55 | }); 56 | 57 | addCase = 58 | mapAttrs (k: v: builtins.removeAttrs v [ "script" "enable" "synopsis" "help" "description" ] // { 59 | text = 60 | let 61 | script' = v.writer (builtins.baseNameOf v.script) v.script; 62 | in 63 | '' 64 | # ${k} subcommand 65 | "${k}") 66 | shift 1; 67 | mkcmd "${v.synopsis}" "${v.help}" "${v.description}" "${script'}" "$@" 68 | ;; 69 | 70 | ''; 71 | }); 72 | 73 | arch = pkgs.system; 74 | 75 | host = 76 | let 77 | partitionString = sep: s: 78 | builtins.filter (v: builtins.isString v) (builtins.split "${sep}" s); 79 | reversePartition = s: lib.reverseList (partitionString "\\." s); 80 | rebake = l: builtins.concatStringsSep "." l; 81 | in 82 | if (hostConfig != null && hostConfig.networking.domain != null) then 83 | rebake (reversePartition hostConfig.networking.domain ++ [ hostConfig.networking.hostName ]) 84 | else if hostConfig != null then 85 | hostConfig.networking.hostName 86 | # fall back to reverse dns from hostname --fqdn command 87 | else "$(IFS='.'; parts=($(hostname --fqdn)); IFS=' '; HOST=$(for (( idx=\${#parts[@]}-1 ; idx>=0 ; idx-- )) ; do printf \"\${parts[idx]}.\"; done); echo \${HOST:: -1})" 88 | ; 89 | 90 | flakeRoot = 91 | if editableFlakeRoot != null 92 | then editableFlakeRoot 93 | else "$PRJ_ROOT" 94 | ; 95 | 96 | 97 | budCmd = pkgs.writeShellScriptBin name '' 98 | 99 | export PATH="${makeBinPath [ pkgs.coreutils pkgs.hostname ]}" 100 | 101 | shopt -s extglob 102 | 103 | FLAKEROOT="${flakeRoot}" # writable 104 | HOST="${host}" 105 | USER="$(logname)" 106 | ARCH="${arch}" 107 | 108 | # mocks: for testing onlye 109 | FLAKEROOT="''${TEST_FLAKEROOT:-$FLAKEROOT}" 110 | HOST="''${TEST_HOST:-$HOST}" 111 | USER="''${TEST_USER:-$USER}" 112 | ARCH="''${TEST_ARCH:-$ARCH}" 113 | 114 | # needs a FLAKEROOT 115 | [[ -d "$FLAKEROOT" ]] || 116 | { 117 | echo "This script must be run either from the flake's devshell or its root path must be specified" >&2 118 | exit 1 119 | } 120 | 121 | # FLAKEROOT must be writable (no store path) 122 | [[ -w "$FLAKEROOT" ]] || 123 | { 124 | echo "You canot use the flake's store path for reference." 125 | "This script requires a pointer to the writable flake root." >&2 126 | exit 1 127 | } 128 | 129 | 130 | mkcmd () { 131 | synopsis=$1 132 | help=$2 133 | description=$3 134 | script=$4 135 | shift 4; 136 | case "$1" in 137 | "-h"|"help"|"--help") 138 | printf "%b\n" \ 139 | "" \ 140 | "\e[4mUsage\e[0m: $synopsis $help\n" \ 141 | "\e[4mDescription\e[0m:\n$description" 142 | ;; 143 | *) 144 | FLAKEROOT="$FLAKEROOT" HOST="$HOST" USER="$USER" ARCH="$ARCH" exec $script "$@" 145 | ;; 146 | esac 147 | } 148 | 149 | usage () { 150 | printf "%b\n" \ 151 | "" \ 152 | "\e[4mUsage\e[0m: $(basename $0) COMMAND [ARGS]\n" \ 153 | "\e[4mCommands\e[0m:" 154 | 155 | printf " %-30s %s\n\n" \ 156 | ${textClosureMap id (addUsage cfg.cmds) (attrNames cfg.cmds)} 157 | 158 | } 159 | 160 | case "$1" in 161 | ""|"-h"|"help"|"--help") 162 | usage 163 | ;; 164 | 165 | ${textClosureMap id (addCase cfg.cmds) (attrNames cfg.cmds)} 166 | esac 167 | ''; 168 | in 169 | { 170 | options.bud = { 171 | cmds = mkOption { 172 | type = types.attrsOf (types.nullOr (types.submodule { options = entryOptions; })); 173 | default = { }; 174 | internal = true; 175 | apply = as: filterAttrs (_: v: v.enable == true) as; 176 | description = '' 177 | A list of sub commands appended to the `bud` case switch statement. 178 | ''; 179 | }; 180 | cmd = mkOption { 181 | internal = true; 182 | type = types.package; 183 | description = '' 184 | This package contains the fully resolved `bud` script. 185 | ''; 186 | }; 187 | }; 188 | 189 | config.bud = { 190 | cmd = budCmd; 191 | }; 192 | } 193 | -------------------------------------------------------------------------------- /nixosModule.nix: -------------------------------------------------------------------------------- 1 | # the bud function that still needs to be instantiated 2 | bud: 3 | { config 4 | , pkgs 5 | , lib 6 | # we require a reference to the flake 7 | # in order to collect self.budModules 8 | , self 9 | , ... 10 | }: 11 | let 12 | reboundBud = bud self; 13 | cfg = config.bud; 14 | in 15 | with lib; { 16 | options.bud = { 17 | enable = mkEnableOption "enable bud sysctl tool"; 18 | localFlakeClone = mkOption { 19 | type = types.path; 20 | description = '' 21 | A path reference to the local (editable) copy 22 | of your system flake. This is used as reference 23 | so that the scripts can do work on your flake, 24 | such as for example updating inputs. 25 | ''; 26 | }; 27 | }; 28 | 29 | config = mkIf cfg.enable { 30 | environment.systemPackages = [ 31 | (reboundBud { inherit pkgs; hostConfig = config; editableFlakeRoot = cfg.localFlakeClone; }) 32 | ]; 33 | }; 34 | 35 | } 36 | -------------------------------------------------------------------------------- /scripts/burn.bash: -------------------------------------------------------------------------------- 1 | 2 | set -e 3 | set -o pipefail 4 | 5 | iso="$FLAKEROOT/result/iso/" 6 | iso="$iso$(ls "$iso" | fzf)" 7 | dev=$(lsblk -d -n --output RM,NAME,FSTYPE,SIZE,LABEL,TYPE,VENDOR,UUID | awk '{ if ($1 == 1) { print } }' | fzf | awk '{print $2}') 8 | dev="/dev/$dev" 9 | 10 | pv -tpreb "$iso" | dd bs=4M of="$dev" iflag=fullblock conv=notrunc,noerror oflag=sync 11 | -------------------------------------------------------------------------------- /scripts/hm-home.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | if [[ "$1" == "switch" ]]; then 3 | shift 1 4 | switch=y 5 | fi 6 | 7 | portable= 8 | hostattr="$FLAKEROOT#homeConfigurations.\"$USER@$HOST\".activationPackage" 9 | portableattr="$FLAKEROOT#homeConfigurationsPortable.$ARCH.\"$USER\".activationPackage" 10 | 11 | if [[ "$1" == *"@"* ]]; then 12 | USER="$(cut -d '@' -f 1 <<<"$1")" 13 | HOST="$(cut -d '@' -f 2 <<<"$1")" 14 | 15 | # only user provided, intend to deploy the portable version 16 | elif [[ ! -z "$1" ]] && [[ -z "$2" ]]; then 17 | portable=y 18 | USER="${1}" 19 | 20 | # nothing or both provided 21 | else 22 | USER="${1:-$USER}" 23 | HOST="${2:-$HOST}" 24 | HOST="$( 25 | IFS='.' 26 | parts=($(echo $HOST)) 27 | IFS=' ' 28 | host=$(for ((idx = ${#parts[@]} - 1; idx >= 0; idx--)); do printf \"\${parts[idx]}.\"; done) 29 | echo \${host:: -1} 30 | )" 31 | fi 32 | 33 | if [[ "$portable" == "y" ]]; then 34 | attr="$portableattr" 35 | else 36 | attr="$hostattr" 37 | fi 38 | 39 | if [[ "$switch" == "y" ]]; then 40 | nix build "$attr" "${@:3}" && result/activate && unlink result 41 | else 42 | nix build "$attr" "${@:3}" 43 | fi 44 | -------------------------------------------------------------------------------- /scripts/hosts-build.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | output="${2:-$1}" 4 | 5 | if [[ ! "$output" == "$1" ]]; then 6 | HOST="${1}" 7 | fi 8 | 9 | attr="$FLAKEROOT#nixosConfigurations.\"$HOST\".config.system.build.$output" 10 | 11 | nix build "$attr" "${@:3}" 12 | -------------------------------------------------------------------------------- /scripts/hosts-install.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | HOST="${1:-$HOST}" 4 | 5 | attr="$FLAKEROOT#\"$HOST\"" 6 | nixos-install --flake "$attr" "${@:2}" 7 | -------------------------------------------------------------------------------- /scripts/hosts-rebuild.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | HOST="${1:-$HOST}" 4 | 5 | set -- "${@:2}" 6 | 7 | if [ -x /run/wrappers/bin/sudo ]; then 8 | export PATH=/run/wrappers/bin:$PATH 9 | set -- --use-remote-sudo "$@" 10 | fi 11 | 12 | nixos-rebuild --flake "$FLAKEROOT#$HOST" "$@" 13 | -------------------------------------------------------------------------------- /scripts/hosts-vm.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | HOST="${1:-$HOST}" 4 | 5 | attr="$FLAKEROOT#nixosConfigurations.\"$HOST\".config.system.build.vm" 6 | 7 | nix build "$attr" "${@:2}" 8 | 9 | exec $FLAKEROOT/result/bin/run-*-vm 10 | -------------------------------------------------------------------------------- /scripts/onboarding-up.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | targetdir="$FLAKEROOT/hosts/${HOST//\./\/}" 4 | mkdir -p "$targetdir" 5 | 6 | # `sudo` is necessary for `btrfs subvolume show` 7 | nixos-generate-config --dir "$targetdir" 8 | 9 | printf "%s\n" \ 10 | "{ suites, ... }:" \ 11 | "{" \ 12 | " imports = [" \ 13 | " ./configuration.nix" \ 14 | " ] ++ suites.base;" \ 15 | "" \ 16 | " bud.enable = true;" \ 17 | " bud.localFlakeClone = \"$FLAKEROOT\";" \ 18 | "}" > "$targetdir/default.nix" 19 | 20 | chown $USER:$(id -gn $USER) -R "$targetdir" 21 | 22 | git add -f "$targetdir" 23 | -------------------------------------------------------------------------------- /scripts/utils-repl/default.nix: -------------------------------------------------------------------------------- 1 | pkgs: 2 | pkgs.writeShellScript "repl" '' 3 | if [ -z "$1" ]; then 4 | nix repl --argstr host "$HOST" --argstr flakePath "$FLAKEROOT" ${./repl.nix} 5 | else 6 | nix repl --argstr host "$HOST" --argstr flakePath $(readlink -f $1 | sed 's|/flake.nix||') ${./repl.nix} 7 | fi 8 | '' 9 | -------------------------------------------------------------------------------- /scripts/utils-repl/repl.nix: -------------------------------------------------------------------------------- 1 | # Adapted on 3rd of July 2021 from 2 | # https://github.com/gytis-ivaskevicius/flake-utils-plus/blob/438316a7b7d798bff326c97da8e2b15a56c7657e/lib/repl.nix 3 | 4 | { flakePath, host }: 5 | 6 | let 7 | 8 | Flake = 9 | if builtins.pathExists flakePath 10 | then builtins.getFlake (toString flakePath) 11 | else { } 12 | ; 13 | 14 | Me = Flake.nixosConfigurations.${host} or { }; 15 | Channels = Flake.pkgs.${builtins.currentSystem} or { }; 16 | 17 | LoadFlake = path: builtins.getFlake (toString path); 18 | 19 | in 20 | { 21 | inherit 22 | Channels 23 | Flake 24 | LoadFlake 25 | Me 26 | ; 27 | } 28 | -------------------------------------------------------------------------------- /scripts/utils-ssh-show.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [[ "$1" == *"@"* ]]; then 4 | HOST="$(cut -d '@' -f 2 <<< "$1")" 5 | USER="$(cut -d '@' -f 1 <<< "$1")" 6 | else 7 | HOST="${1:-$HOST}" 8 | HOST="$(IFS='.'; parts=($(echo $HOST)); IFS=' '; host=$(for (( idx=\${#parts[@]}-1 ; idx>=0 ; idx-- )) ; do printf \"\${parts[idx]}.\"; done); echo \${host:: -1})" 9 | USER="${2:-$USER}" 10 | fi 11 | 12 | if [[ "$USER" == "root" ]]; then 13 | cmd="cat /etc/ssh/ssh_host_ed25519_key.pub" 14 | else 15 | cmd="cat ~/.ssh/id_ed25519.pub" 16 | fi 17 | 18 | ssh "$USER@$HOST" $cmd 19 | -------------------------------------------------------------------------------- /scripts/utils-update.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [[ -n "$1" ]]; then 4 | nix flake lock --update-input "$1" "$FLAKEROOT" 5 | else 6 | nix flake update "$FLAKEROOT" 7 | fi 8 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | { inputs, system ? builtins.currentSystem }: 2 | let 3 | 4 | pkgs = import inputs.nixpkgs { 5 | inherit system; config = { }; 6 | overlays = [ 7 | (final: prev: { 8 | beautysh = inputs.beautysh.defaultPackage."${final.system}"; 9 | }) 10 | ]; 11 | }; 12 | devshell = import inputs.devshell { inherit pkgs system; }; 13 | 14 | withCategory = category: attrset: attrset // { inherit category; }; 15 | util = withCategory "utils"; 16 | 17 | test = path: host: user: name: args: withCategory "tests" { 18 | name = "check-${name}"; 19 | help = "Checks ${name} subcommand"; 20 | command = '' 21 | set -e 22 | 23 | cd $PRJ_ROOT/${path} 24 | 25 | head=$(git rev-parse HEAD) 26 | 27 | trap_err() { 28 | local ret=$? 29 | echo -e \ 30 | "\033[1m\033[31m""exit $ret: \033[0m\033[1m""command [$BASH_COMMAND] failed""\033[0m" 31 | } 32 | 33 | trap 'trap_err' ERR 34 | TEST_FLAKEROOT="$PRJ_ROOT/${path}" TEST_HOST="${host}" TEST_USER="${user}" \ 35 | ${pkgs.nixUnstable}/bin/nix run $PRJ_ROOT -- ${name} ${args} 36 | 37 | git checkout -f "$head" 38 | ''; 39 | }; 40 | 41 | in 42 | devshell.mkShell { 43 | name = "bud"; 44 | packages = with pkgs; [ 45 | fd 46 | nixpkgs-fmt 47 | nixUnstable 48 | ]; 49 | 50 | # tempfix: remove when merged https://github.com/numtide/devshell/pull/123 51 | devshell.startup.load_profiles = pkgs.lib.mkForce (pkgs.lib.noDepEntry '' 52 | # PATH is devshell's exorbitant privilige: 53 | # fence against its pollution 54 | _PATH=''${PATH} 55 | # Load installed profiles 56 | for file in "$DEVSHELL_DIR/etc/profile.d/"*.sh; do 57 | # If that folder doesn't exist, bash loves to return the whole glob 58 | [[ -f "$file" ]] && source "$file" 59 | done 60 | # Exert exorbitant privilige and leave no trace 61 | export PATH=''${_PATH} 62 | unset _PATH 63 | ''); 64 | 65 | commands = [ 66 | { 67 | name = "fmt"; 68 | help = "Check Nix formatting"; 69 | command = "nixpkgs-fmt \${@} $PRJ_ROOT"; 70 | } 71 | { 72 | name = "evalnix"; 73 | help = "Check Nix parsing"; 74 | command = "fd --extension nix --exec nix-instantiate --parse --quiet {} >/dev/null"; 75 | } 76 | { 77 | name = "fmt-bash"; 78 | help = "Format bash scripts"; 79 | command = '' 80 | set -euo pipefail 81 | export PATH=${pkgs.beautysh}/bin:${pkgs.findutils}/bin:$PATH 82 | for path in $(find "$PRJ_ROOT/scripts" -name '*.bash') 83 | do 84 | beautysh "$@" "$path" 85 | done 86 | ''; 87 | } 88 | (test "e2e/devos" "NixOS" "nixos" "home" "") 89 | (test "e2e/devos" "NixOS" "" "build" "NixOS toplevel") 90 | (test "e2e/devos" "NixOS" "" "install" "-h") 91 | (test "e2e/devos" "NixOS" "" "rebuild" "-h") 92 | (test "e2e/devos" "NixOS" "" "vm" "") 93 | (test "e2e/devos" "NixOS.example.com" "" "up" "") 94 | (test "e2e/devos" "NixOS" "root" "ssh-show" "") 95 | (test "e2e/devos" "" "" "update" "") 96 | ]; 97 | } 98 | -------------------------------------------------------------------------------- /stdProfile.nix: -------------------------------------------------------------------------------- 1 | { stdProfilePath, pkgs, lib, budUtils, ... }: 2 | let 3 | # pkgs.nixos-install-tools does not build on darwin 4 | installPkgs = (import "${toString pkgs.path}/nixos/lib/eval-config.nix" { 5 | inherit (pkgs) system; 6 | modules = [ { nix.package = pkgs.nixUnstable; } ]; 7 | }).config.system.build; 8 | in 9 | { 10 | bud.cmds = with pkgs; { 11 | 12 | # Onboarding 13 | up = { 14 | writer = budUtils.writeBashWithPaths [ installPkgs.nixos-generate-config git mercurial coreutils ]; 15 | synopsis = "up"; 16 | help = "Generate $FLAKEROOT/hosts/\${HOST//\./\/}/default.nix"; 17 | script = ./scripts/onboarding-up.bash; 18 | }; 19 | 20 | # Utils 21 | update = { 22 | writer = budUtils.writeBashWithPaths [ nixUnstable git mercurial ]; 23 | synopsis = "update [INPUT]"; 24 | help = "Update and commit $FLAKEROOT/flake.lock file or specific input"; 25 | script = ./scripts/utils-update.bash; 26 | }; 27 | burn = { 28 | writer = budUtils.writeBashWithPaths [ nixUnstable git mercurial coreutils gawk util-linux fzf pv ]; 29 | synopsis = "burn"; 30 | help = "Burn an ISO on a removable device that you interactively choose (build the iso first)"; 31 | script = ./scripts/burn.bash; 32 | }; 33 | repl = { 34 | writer = budUtils.writeBashWithPaths [ nixUnstable gnused git mercurial coreutils ]; 35 | synopsis = "repl [FLAKE]"; 36 | help = "Enter a repl with the flake's outputs"; 37 | script = (import ./scripts/utils-repl pkgs).outPath; 38 | }; 39 | ssh-show = { 40 | writer = budUtils.writeBashWithPaths [ openssh coreutils ]; 41 | synopsis = "ssh-show HOST USER | USER@HOSTNAME"; 42 | help = "Show target host's SSH ed25519 key"; 43 | description = '' 44 | Use this script to quickly determine the target host 45 | key for which to encrypt an agenix secret. 46 | ''; 47 | script = ./scripts/utils-ssh-show.bash; 48 | }; 49 | 50 | # Home-Manager 51 | home = { 52 | writer = budUtils.writeBashWithPaths [ nixUnstable git mercurial coreutils ]; 53 | synopsis = "home [switch] (user@fqdn | USER HOST | USER)"; 54 | help = "Home-manager config of USER from HOST or host-less portable USER for current architecture"; 55 | script = ./scripts/hm-home.bash; 56 | }; 57 | 58 | # Hosts 59 | build = { 60 | writer = budUtils.writeBashWithPaths [ nixUnstable git mercurial coreutils ]; 61 | synopsis = "build HOST BUILD"; 62 | help = "Build a variant of your configuration from system.build"; 63 | script = ./scripts/hosts-build.bash; 64 | }; 65 | vm = { 66 | writer = budUtils.writeBashWithPaths [ nixUnstable git mercurial coreutils ]; 67 | synopsis = "vm HOST"; 68 | help = "Generate & run a one-shot vm for HOST"; 69 | script = ./scripts/hosts-vm.bash; 70 | }; 71 | install = { 72 | writer = budUtils.writeBashWithPaths [ installPkgs.nixos-install git mercurial coreutils ]; 73 | synopsis = "install HOST [ARGS]"; 74 | help = "Shortcut for nixos-install"; 75 | script = ./scripts/hosts-install.bash; 76 | }; 77 | rebuild = { 78 | writer = budUtils.writeBashWithPaths [ installPkgs.nixos-rebuild git mercurial coreutils ]; 79 | synopsis = "rebuild HOST (switch|boot|test)"; 80 | help = "Shortcut for nixos-rebuild"; 81 | script = ./scripts/hosts-rebuild.bash; 82 | }; 83 | 84 | }; 85 | } 86 | -------------------------------------------------------------------------------- /ufr-polyfills/UnofficialFlakesRoadmap.md: -------------------------------------------------------------------------------- 1 | # Hey! 2 | 3 | This project is commited to the [Unofficial Flakes Roadmap (ufr)](https://demo.hedgedoc.org/s/_W6Ve03GK#). 4 | 5 | 6 | _The future of flakes is bright again._ 7 | 8 | -------------------------------------------------------------------------------- /ufr-polyfills/eachSystem.nix: -------------------------------------------------------------------------------- 1 | # Builds a map from value to =value for each system. 2 | # .. adopted from: https://github.com/numtide/flake-utils 3 | # 4 | systems: f: 5 | let 6 | op = attrs: system: 7 | attrs // { 8 | ${system} = f system; 9 | }; 10 | in 11 | builtins.foldl' op { } systems 12 | 13 | -------------------------------------------------------------------------------- /ufr-polyfills/flake.lock.nix: -------------------------------------------------------------------------------- 1 | # Adapted from https://github.com/edolstra/flake-compat/blob/master/default.nix 2 | # 3 | # This version only gives back the inputs. In that mode, flake becomes little 4 | # more than a niv replacement. 5 | src: 6 | let 7 | lockFilePath = src + "/flake.lock"; 8 | 9 | lockFile = builtins.fromJSON (builtins.readFile lockFilePath); 10 | 11 | # Emulate builtins.fetchTree 12 | # 13 | # TODO: only implement polyfill if the builtin doesn't exist? 14 | fetchTree = 15 | info: 16 | if info.type == "github" then 17 | { 18 | outPath = fetchTarball { 19 | url = "https://api.${info.host or "github.com"}/repos/${info.owner}/${info.repo}/tarball/${info.rev}"; 20 | sha256 = info.narHash; 21 | }; 22 | rev = info.rev; 23 | shortRev = builtins.substring 0 7 info.rev; 24 | lastModified = info.lastModified; 25 | narHash = info.narHash; 26 | } 27 | else if info.type == "git" then 28 | { 29 | outPath = 30 | builtins.fetchGit 31 | ({ url = info.url; sha256 = info.narHash; } 32 | // (if info ? rev then { inherit (info) rev; } else { }) 33 | // (if info ? ref then { inherit (info) ref; } else { }) 34 | ); 35 | lastModified = info.lastModified; 36 | narHash = info.narHash; 37 | } // (if info ? rev then { 38 | rev = info.rev; 39 | shortRev = builtins.substring 0 7 info.rev; 40 | } else { }) 41 | else if info.type == "path" then 42 | { 43 | outPath = builtins.path { path = info.path; }; 44 | narHash = info.narHash; 45 | } 46 | else if info.type == "tarball" then 47 | { 48 | outPath = fetchTarball { 49 | url = info.url; 50 | sha256 = info.narHash; 51 | }; 52 | narHash = info.narHash; 53 | } 54 | else if info.type == "gitlab" then 55 | { 56 | inherit (info) rev narHash lastModified; 57 | outPath = fetchTarball { 58 | url = "https://${info.host or "gitlab.com"}/api/v4/projects/${info.owner}%2F${info.repo}/repository/archive.tar.gz?sha=${info.rev}"; 59 | sha256 = info.narHash; 60 | }; 61 | shortRev = builtins.substring 0 7 info.rev; 62 | } 63 | else 64 | # FIXME: add Mercurial, tarball inputs. 65 | throw "flake input has unsupported input type '${info.type}'"; 66 | 67 | allNodes = 68 | builtins.mapAttrs 69 | (key: node: 70 | let 71 | sourceInfo = 72 | if key == lockFile.root 73 | then { } 74 | else fetchTree (node.info or { } // removeAttrs node.locked [ "dir" ]); 75 | 76 | inputs = builtins.mapAttrs 77 | (inputName: inputSpec: allNodes.${resolveInput inputSpec}) 78 | (node.inputs or { }); 79 | 80 | # Resolve a input spec into a node name. An input spec is 81 | # either a node name, or a 'follows' path from the root 82 | # node. 83 | resolveInput = inputSpec: 84 | if builtins.isList inputSpec 85 | then getInputByPath lockFile.root inputSpec 86 | else inputSpec; 87 | 88 | # Follow an input path (e.g. ["dwarffs" "nixpkgs"]) from the 89 | # root node, returning the final node. 90 | getInputByPath = nodeName: path: 91 | if path == [ ] 92 | then nodeName 93 | else 94 | getInputByPath 95 | # Since this could be a 'follows' input, call resolveInput. 96 | (resolveInput lockFile.nodes.${nodeName}.inputs.${builtins.head path}) 97 | (builtins.tail path); 98 | 99 | result = sourceInfo // { inherit inputs; inherit sourceInfo; }; 100 | in 101 | if node.flake or true then 102 | result 103 | else 104 | sourceInfo 105 | ) 106 | lockFile.nodes; 107 | 108 | result = 109 | if lockFile.version >= 5 && lockFile.version <= 7 110 | then allNodes.${lockFile.root}.inputs 111 | else throw "lock file '${lockFilePath}' has unsupported version ${toString lockFile.version}"; 112 | 113 | in 114 | result 115 | 116 | -------------------------------------------------------------------------------- /ufr-polyfills/ufrContract.nix: -------------------------------------------------------------------------------- 1 | let 2 | eachSystem = import ./eachSystem.nix; 3 | in 4 | 5 | supportedSystems: 6 | imprt: inputs: 7 | eachSystem supportedSystems (system: 8 | import imprt { 9 | inherit inputs system; # The super stupid flakes contract `{ inputs, system }` 10 | } 11 | ) 12 | -------------------------------------------------------------------------------- /writeBashWithPaths.nix: -------------------------------------------------------------------------------- 1 | { pkgs, lib }: 2 | paths: name: script: 3 | pkgs.writers.writeBash name '' 4 | export PATH="${lib.makeBinPath paths}" 5 | source ${script} 6 | '' 7 | --------------------------------------------------------------------------------