From d29e6e9b5c9ae675bd0a0c28080d41a10c64319d Mon Sep 17 00:00:00 2001 From: _cry64 Date: Fri, 12 Dec 2025 10:33:04 +1000 Subject: [PATCH 001/338] init --- cerulean/CeruleanOS/cdesktop.nix | 136 ++++++++++++++ cerulean/CeruleanOS/csystem.nix | 309 +++++++++++++++++++++++++++++++ cerulean/CeruleanOS/cuser.nix | 89 +++++++++ notes.md | 285 ++++++++++++++++++++++++++++ 4 files changed, 819 insertions(+) create mode 100644 cerulean/CeruleanOS/cdesktop.nix create mode 100644 cerulean/CeruleanOS/csystem.nix create mode 100644 cerulean/CeruleanOS/cuser.nix create mode 100644 notes.md diff --git a/cerulean/CeruleanOS/cdesktop.nix b/cerulean/CeruleanOS/cdesktop.nix new file mode 100644 index 0000000..00b7439 --- /dev/null +++ b/cerulean/CeruleanOS/cdesktop.nix @@ -0,0 +1,136 @@ +{ + lib, + config, + pkgs, + pkgs-unstable, + ... +} @ args: let + getModule = name: "../modules/homemanager/${name}.nix"; + getModules = map (x: getModule x); +in { + imports = getModules [ + "term/foot" + "editor/vscode" + + "wm/hyprland" + "wm/hyprland/hyprlock" + + "dm/sddm" + "dm/sddm/themes/corners" + + "apps/firefox" + "apps/thunderbird" + "apps/obsidian" + "apps/rider" + "apps/winbox" + "apps/gitkraken" + "apps/thunar" + + "wm/kanshi" + "wm/mako" + ]; + + home = { + pointerCursor = { + gtk.enable = true; + # x11.enable = true # dont enable since im on hyprland + package = pkgs.bibata-cursors; + name = "Bibata-Modern-Ice"; + size = 16; + }; + + packages = with pkgs; [ + # for services.gnome-keyring + ( + if config.cerulean.isGraphical + then seahorse # gui + else null + ) + + fuzzel + ]; + }; + + gtk = { + enable = true; + font.name = "Victor Mono SemiBold 12"; + theme = { + name = "Dracula"; + package = pkgs.dracula-theme; + }; + iconTheme = { + name = "kora"; + package = pkgs.kora-icon-theme; + }; + # TODO: use a variable to mirror this cursor size + # with the `home.pointerCurser.size` + cursorTheme = { + package = pkgs.bibata-cursors; + name = "Bibata-Modern-Ice"; + size = 16; + }; + }; + + qt = { + enable = true; + platformTheme.name = "gtk2"; + style.name = "gtk2"; + }; + + services = { + # Set display manager (login screen) + displayManager = { + # sddm relies on pkgs.libsForQt5.qt5.qtgraphicaleffects + sddm = { + enable = true; + wayland.enable = true; # experimental + theme = "corners"; + }; + defaultSession = + "hyprland" + + ( + if config.programs.hyprland.withUWSM + then "-uwsm" + else null + ); + }; + + # Multimedia Framework + # With backwards compatability for alsa/pulseaudio/jack + pipewire = { + enable = true; + wireplumber.enable = true; + + alsa.enable = true; + alsa.support32Bit = true; + pulse.enable = true; + jack.enable = true; + }; + }; + + # ---- ENVIRONMENT ---- + environment = { + sessionVariables = { + # Hint Electron apps to use support Wayland + NIXOS_OZONE_WL = "1"; + }; + }; + + # ---- SYSTEM PACKAGES ---- + environment.systemPackages = with pkgs; [ + # User Environment + swww + helvum + easyeffects + pavucontrol + hyprpicker # colour picking utility + hyprshot # screenshot utility + qbittorrent + signal-desktop # MAKE THIS ONLY FOR THE DESKTOP FOR END USERS, NOT SERVERS + kdePackages.gwenview # image viewer + libreoffice + wl-clipboard # clipboard for wayland + ]; + + security.rtkit.enable = true; # I *think* this is for pipewire +} diff --git a/cerulean/CeruleanOS/csystem.nix b/cerulean/CeruleanOS/csystem.nix new file mode 100644 index 0000000..fc398f8 --- /dev/null +++ b/cerulean/CeruleanOS/csystem.nix @@ -0,0 +1,309 @@ +{ + inputs, + lib, + config, + pkgs, + pkgs-unstable, + homemanager, + cerulean, + ... +} @ args: let + getModule = name: "../modules/nixos/${name}.nix"; + getModules = map (x: getModule x); + + getHostModule = name: "TODO"; +in { + imports = getModules [ + (getHostModule "hardware-configuration") + (import "${homemanager}/nixos") + + "shell/bash" + "shell/bash/bashistrans.nix" + "shell/zsh" + "shell/fish" + + "cli/git" + "cli/bat" + "cli/btop" + "cli/tmux" + "cli/nvim" + + "lang/asm" + "lang/bash" # TODO: (YES THIS IS DIFFERENT TO shell/bash, this provides language support ie pkgs.shellcheck) + "lang/c-family" + "lang/dotnet" + # "lang/go" + # "lang/haskell" + # "lang/java" + # "lang/nim" + "lang/python" + # "lang/rust" + # "lang/sage" + + "editor/helix" + ]; + + nix.settings = { + experimental-features = [ + "nix-command" + "flakes" + ]; + + download-buffer-size = 524288000; # 500 MiB + + # making wheel group members "trusted users" allows + # them to import packages not signed by a trusted key + # (aka super duper easier to remote deploy) + trusted-users = ["root" "@wheel"]; + }; + + nixpkgs = { + overlays = cerulean.lib.importOverlaysNixOS; + + config = if config.cerulean.allowUnfreeWhitelist != [] + then { + allowUnfreePredicate = + pkg: builtins.elem + (lib.getName pkg) + config.cerulean.allowUnfreeWhitelist; + } + else { + allowUnfree = config.cerulean.allowUnfree; + }; + }; + + # colmena deployment configuration + deployment = { + targetHost = config.cerulean.domain ?? config.cerulean.ip; + targetUser = "cerulean"; + targetPort = "22"; + sshOptions = [ + "-A" # forward ssh-agent + ]; + buildOnTarget = false; # build locally then deploy + }; + + + time.timeZone = config.cerulean.timeZone; + i18n.defaultLocale = "en_US.UTF-8"; + + # Enable initrd hook for virtual console customisation + # aka cool colours when booting yay!! + console = { + enable = true; + earlySetup = true; # initrd pre hook + keyMap = "us"; + font = "Lat2-Terminus16"; + # ANSI 24-bit color definitions (theme: dracula) + colors = [ + "21222c" + "ff5555" + "50fa7b" + "f1fa8c" + "bd93f9" + "ff79c6" + "8be9fd" + "f8f8f2" + "6272a4" + "ff6e6e" + "69ff94" + "ffffa5" + "d6acff" + "ff92df" + "a4ffff" + "ffffff" + ]; + }; + + # super duper minimum grub2 config + boot.loader = { + efi = { + canTouchEfiVariables = true; + efiSysMountPoint = "/boot/efi"; + }; + + grub = { + enable = true; + device = "nodev"; + }; + + # GitHub: vinceliuice/grub2-themes + grub2-theme = { + enable = true; + theme = "whitesur"; # stylish, vimix, or whitesur + footer = true; + # TODO: switch my cables to switch default grub display + customResolution = "3840x2160"; + }; + }; + + networking = { + hostName = config.cerulean.hostname; + networkmanager.enable = true; + + firewall = { + enable = true; + allowedTCPPorts = [ + 22 # sshd + 80 # nginx (http) + 443 # nginx (https) + # 5678 # MikroTik WinBox + ]; + }; + }; + + # ------- USERS ------- + security.sudo.wheelNeedsPassword = true; + users = { + defaultUserShell = pkgs.bash; + + users = cerulean.lib.importUsersNixOS; + }; + + home-manager = { + users = cerulean.lib.importUsersHomeManager; + + extraSpecialArgs = { inherit inputs pkgs pkgs-unstable; }; + sharedModules = []; + }; + + # ---- ENVIRONMENT ---- + environment = { + # always install "dev"/"man" derivation outputs + extraOutputsToInstall = ["dev" "man"]; + + systemPackages = with pkgs; [ + # User Environment + bluetui + + # Shell + bash + fish + shellcheck + grc # colorise command outputs + moreutils + + # Systems Programming & Compilation + qemu # Fellice Bellard's Quick Emulator + # GNU Utils + gnumake + # Binaries + binutils + strace + ltrace + perf-tools # ftrace + perf + radare2 + gdb + # ASM + nasm + (callPackage ../packages/x86-manpages {}) + # C Family + gcc + clang + clang-tools + + # Rust + cargo + rustc + # Go + go + # Nim + nim + nimble + # Haskell + ghc + ghcid + haskell-language-server + ormolu + + # Python + python312 # I use 3.12 since it's in a pretty stable state now + python314 # also 3.14 for latest features + poetry + + openvpn + inetutils + + # security tools + nmap + + httpie + curlie + zoxide + doggo + tldr + btop + eza + yazi + lazygit + ripgrep + viddy # modern `watch` command + thefuck + + # TODO: once upgraded past Nix-24.07 this line won't be necessary (I think) + # helix will support nixd by default + # SOURCE: https://github.com/nix-community/nixd/blob/main/nixd/docs/editor-setup.md#Helix + # nixd # lsp for nix # DEBUG + + # Pretty necessary + nix-prefetch-git + brightnessctl + acpi + powertop + imagemagick + + # "Standard" Unix Commands + vim + file + wget + tree + pstree + unzip + unrar-free + lz4 + man-pages + man-pages-posix + + # Cryptography + gnupg + openssl + libargon2 + ]; + }; + + programs = { + nix-ld.enable = true; + }; + + documentation = { + enable = true; + doc.enable = true; # install /share/doc packages + man.enable = true; # install manpages + info.enable = true; # install GNU info + dev.enable = true; # install docs intended for developers + nixos = { + enable = true; # install NixOS documentation (ie man -k nix, & nixos-help) + options.splitBuild = true; + # includeAllModules = true; + }; + }; + + virtualisation.docker.enable = true; + + + hardware = { + graphics = { + enable = true; + enable32Bit = true; + }; + + bluetooth = let + btSupported = config.cerulean.bluetoothSupported; + in { + enable = btSupported; + powerOnBoot = btSupported; + }; + }; + + system.stateVersion = config.cerulean.stateVersion; # DO NOT MODIFY +} diff --git a/cerulean/CeruleanOS/cuser.nix b/cerulean/CeruleanOS/cuser.nix new file mode 100644 index 0000000..8b26c9f --- /dev/null +++ b/cerulean/CeruleanOS/cuser.nix @@ -0,0 +1,89 @@ +{ + lib, + config, + pkgs, + pkgs-unstable, + ... +} @ args: let + getModule = name: "../modules/homemanager/${name}.nix"; + getModules = map (x: getModule x); +in { + imports = getModules [ + "shell/fish" + + "cli/git" + "cli/bat" + "cli/btop" + "cli/tmux" + + "editor/helix" + ]; + + nixpkgs.config.allowUnfreePredicate = pkg: + builtins.elem (lib.GetName pkg) [ + "vscode-extension-ms-dotnettools-csharp" + ]; + + home = { + stateVersion = config.cerulean.stateVersion; # DO NOT MODIFY + + username = config.cerulean.username; + homeDirectory = "/home/${config.cerulean.username}"; + + shellAliases = { + rg = "batgrep"; # bat + ripgrep + man = "batman"; # bat + man + }; + + sessionVariables = { + NIX_SHELL_PRESERVE_PROMPT = 1; + }; + + packages = with pkgs; [ + # for services.gnome-keyring + gcr # provides org.gnome.keyring.SystemPrompter + speedtest-cli + ]; + }; + + programs = { + home-manager.enable = true; + + zsh = { + enable = true; + enableCompletion = true; + autosuggestion.enable = true; + syntaxHighlighting.enable = true; + + history = { + size = 10000; + ignoreAllDups = true; + path = "$HOME/.zsh_history"; + ignorePatterns = [ + "rm *" + ]; + }; + }; + + # set ssh profiles + # NOTE: (IMPORTANT) this DOES NOT start the ssh-agent + # for that you need to use `services.ssh-agent.enable` + ssh = { + enable = true; + forwardAgent = false; + addKeysToAgent = "no"; + }; + }; + + services = { + # enable OpenSSH private key agent + ssh-agent.enable = true; + + gnome-keyring.enable = true; + }; + + # the ssh-agent won't set this for itself... + systemd.user.sessionVariables.SSH_AUTH_SOCK = "$XDG_RUNTIME_DIR/ssh-agent"; + # Nicely reload system units when changing configs + systemd.user.startServices = "sd-switch"; +} diff --git a/notes.md b/notes.md new file mode 100644 index 0000000..9bc45e1 --- /dev/null +++ b/notes.md @@ -0,0 +1,285 @@ +use `rg -F 'Command::new'` within `~/sandbox/clones/colmena/` + +use `hx $(realpath $(which nixos-rebuild))` to access the bash script + +>[!NOTE] +> The option `--extra-experimental-features "flakes"` is only set +> if `expression.requires_flakes()` where `expression: NixExpression`. + +>[!TODO] +> Also search for nix-store by itself, nix-instantiate, etc. +> And also search for `make_privileged_command(...)` usages. +> or actually maybe `.stdin(...)` or `Stdio::piped(...)`. + + +## `Command::new()` Calls Translated +```rs +command/repl.rs: let mut repl_cmd = Command::new("nix"); +/* +nix repl \ + --experimental-features "nix-command flakes" \ + --file $REPL_EXPRESSION_FILE + */ + +nix/key.rs: let output = Command::new(pathname) +/* +$PATHNAME $ARGS < /dev/null |& ... + */ + +nix/info.rs: let version_cmd = Command::new("nix-instantiate") +/* +# `NixCheck::detect(...)` +# NOTE: Used to detect if Nix is present (and if so, what version) +# NOTE: The version number is used to detect if flakes are supported (NOT enabled though) +# NOTE: You can do the same with nix3 with `nix --version` +nix-instantiate --version + */ + +nix/info.rs: let flake_cmd = Command::new("nix-instantiate") +/* +# `NixCheck::detect(...)` +# NOTE: Used to detect if flakes are enabled +nix-instantiate \ + --eval -E builtins.getFlake \ + &> /dev/null + */ + +nix/flake.rs: let child = Command::new("nix") +/* +# `FlakeMetadata::resolve(flake: &str) -> ColmenaREsult` +nix flake metadata \ + --json \ + --extra-experimental-features "nix-command flakes" + */ + +nix/flake.rs: let status = Command::new("nix") +/* +# `lock_flake_quiet(uri: &str) -> ColmenaResult<()>` +# NOTE: "Quietly locks the dependencies of a flake." +nix flake lock \ + --extra-experimental-features "nix-command flakes" + */ + +nix/profile.rs: let mut command = Command::new("nix-store"); +/* +# `Profile::create_gc_root(&self, path: &Path) -> ColmenaResult<()>` +# NOTE: "Create a GC root for this profile." +# Each Profile struct contains a StorePath object +nix-store \ + --no-build-output \ + --indirect \ + --add-root \ + --realise $STORE_PATH \ + 1>/dev/null + */ + +nix/hive/mod.rs: let mut command = Command::new("nix-instantiate"); +/* +# `NixInstantiate::instantiate(&self) -> Command` +# NOTE: "Instantiation is not supported with DirectFlakeEval" +# NOTE: $EXPRESSION == `self.hive.get_base_expression() + self.expression` +# NOTE: --extra-experimental-features "flakes" only if `self.hive.is_flake()` +nix-instantiate -E $EXPRESSION \ + --no-gc-warning \ + --extra-experimental-features "flakes" + */ + +nix/hive/mod.rs: let mut command = Command::new("nix"); +/* +# `NixInstantiate::eval(self) -> Command` +# NOTE: $FLAGS == `self.hive.nix_flags()` + +# XXX: WARNING: if `self.hive.evaluation_method` is `EvaluationMethod::NixInstantiate` +# XXX: WARNING: then `NixInstantiate::instantiate(...)` (previous) is called +$NIX_INSTANTIATE_COMMAND + --eval + --json + --strict + --read-write-mode # ensures the derivations are instantiated, required for system profile evaluation and IFD + $FLAGS + +# XXX: WARNING: else if `self.hive.evaluation_method` is `EvaluationMethod::DirectFlakeEval` +# XXX: WARNING: then `NixInstantiate::instantiate(...)` (previous) is called +nix eval ${FLAKE_URI}#colmenaHive $FULL_EXPRESSION $FLAGS \ + --json \ + --apply \ + --extra-experimental-features "nix-command flakes" + */ + +nix/evaluator/nix_eval_jobs.rs: let mut command = Command::new(&self.executable); +/* +# `NixEvalJobs::evaluate(&self, expression: &dyn NixExpression, flags: NixFlags) -> ColmenaREsult>>>` +# NOTE: $EXECUTABLE defaults to EXECUTABLE='nix-eval-jobs' +$EXECUTABLE $FLAGS \ + --workers $WORKERS \ + --expr $EXPRESSION \ + --extra-experimental-features "flakes" \ + 2>&1 + */ + +nix/host/local.rs: let mut command = self.make_privileged_command(&["sh", "-c", &key_script]); +/* +# `Local::upload_key(&mut self, name: &str, key: &Key, require_ownership: bool) -> ColmenaResult<()>` +# XXX: NOTE: Same as `Ssh::upload_key()` +sh -c "$KEY_SCRIPT" + */ + + +nix/host/local.rs: let mut command = Command::new("nix-store"); +/* +# `Local::realize_remote(...)` +# XXX: NOTE: Same as `Ssh::realize_remote(...)` +nix-store + --no-gc-warning + --realise $DERIVATION_PATH + */ + +nix/host/local.rs: self.make_privileged_command(&["nix-env", "--profile", SYSTEM_PROFILE, "--set", path]) +/* +# `Local::activate(&mut self, profile: &Profile, goal: Goal) -> ColmenaResult<()>` +# XXX: NOTE: Same as `Ssh::activate(...)` + +# NOTE: This command runs if `goal.should_switch_profile()` +nix-env --profile $SYSTEM+PROFILE + --set $PROFILE_PATH + +# NOTE: Separate command (runs regardless of `goal.should_switch_profile()`) +$ACTIVATION_COMMAND*/ + +nix/host/local.rs: let paths = Command::new("readlink") +/* +# `Local::get_current_system_profile(&mut self) -> ColmenaResult` +# XXX: NOTE: Same as `Ssh::get_current_system_profile(...)` +readlink -e $CURRENT_PROFILE + */ + +nix/host/local.rs: let paths = Command::new("sh") +/* +# `Local::get_main_system_profile(&mut self) -> ColmenaResult` +# XXX: NOTE: Same as `Ssh::get_main_system_profile(...)` +sh -c 'readlink -e "$SYSTEM_PROFILE" | readlink -e "$CURRENT_PROFILE"' + */ + +nix/host/local.rs: let mut result = Command::new(full_command[0]); +/* +# `Local::make_privileged_command>(&self, command: &[S]) -> Command` +$COMMAND + */ + +nix/store.rs: let references = Command::new("nix-store") +/* +# `StorePath::references(...)` +nix-store + --query + --references $STORE_PATH + */ + +nix/host/ssh.rs: let mut cmd = Command::new("ssh"); +/* +# `Ssh::ssh(...)` +# $PRIVESC_COMMAND is privilege_escalation_command from Ssh struct +NIX_SSHOPTS="$SSH_OPTIONS" ssh $SSH_OPTIONS -- $PRIVESC_COMMAND $COMMAND + */ + +nix/host/ssh.rs: let mut command = Command::new("nix"); +/* +# `Ssh::nix_copy_closure(...)` - CONDITION: `if self.use_nix3_copy()` +# NOTE: --builders-use-substitutes "needed due to UX bug in ssh-ng://" +# --derivation is only added if the `path.file_extension() == "drv"` +# --to or --from is used depending on `CopyDirection` +# "?compress=true" is added after ${SSH_TARGET} if `options.gzip` is set +# WARNING: COMMAND EXECUTED OVER SSH (`self.ssh(...)`) +NIX_SSHOPTS="$SSH_OPTIONS" nix copy $CLOSURE_PATH + --no-check-sigs + --substitute-on-destination + --builders-use-substitutes + --derivation + [--to|--from] "ssh-ng://${SSH_TARGET}?compress=true" + --extra-experimental-features "nix-command" + */ + +nix/host/ssh.rs: let mut command = Command::new("nix-copy-closure"); +/* +# `Ssh::nix_copy_closure(...)` - CONDITION: `else` +# --include-outputs if `options.include_outputs` +# --use-substitues if `options.use_substitutes` +# --gzip if `options.gzip` +# WARNING: COMMAND EXECUTED OVER SSH (`self.ssh(...)`) +NIX_SSHOPTS="$SSH_OPTIONS" nix-copy-closure $SSH_TARGET $CLOSURE_PATH + [--to|--from] + --include-outputs + --use-substitues + --gzip + */ + + +nix/host/ssh.rs: let mut command = self.ssh(&["sh", "-c", &key_script]); +/* +# `Ssh::upload_key(...)` +# XXX: NOTE: Same as `Ssh::upload_key()` +# WARNING: COMMAND EXECUTED OVER SSH (`sel.ssh(...)`) +sh -c "$KEY_SCRIPT" + */ + + +nix/host/ssh.rs: let mut command = self.ssh("nix-store"); +/* +# `Ssh::realize_remote(...)` +# XXX: NOTE: Same as `Local::realize_remote(...)` +# WARNING: COMMAND EXECUTED OVER SSH (`self.ssh(...)`) +nix-store + --realise $DERIVATION + --no-gc-warning + */ + +nix/host/ssh.rs: let set_profile = self.ssh(&["nix-env", "--profile", SYSTEM_PROFILE, "--set", path]); +/* +# `Ssh::activate` - activate (switch to) a specific NixOS profile +# XXX: NOTE: Same as `Local::activate(...)` + +# NOTE: This command runs if `goal.should_switch_profile()` +# WARNING: COMMAND EXECUTED OVER SSH (`self.ssh(...)`) +nix-env --profile $SYSTEM+PROFILE + --set $PROFILE_PATH + +# NOTE: Separate command (runs regardless of `goal.should_switch_profile()`) +# WARNING: COMMAND EXECUTED OVER SSH (`self.ssh(...)`) +$ACTIVATION_COMMAND + */ + +nix/host/ssh.rs: let paths = self.ssh(&["readlink", "-e", CURRENT_PROFILE]) +/* +# `Ssh::get_current_system_profile(&mut self) -> ColmenaResult` +# XXX: NOTE: Same as `Local::get_current_system_profile(...)` +readlink -e $CURRENT_PROFILE + */ + +nix/hosts/ssh.rs: let paths = self.ssh(&["sh", "-c", &command]).capture_output().await?; +/* +# `Ssh::get_main_system_profile(...)` +# NOTE: the command executed is the same as in `/nix/host/local.rs` (except for SSH) +# XXX: NOTE: Same as `Local::get_main_system_profile(...)` +# WARNING: COMMAND EXECUTED OVER SSH (`self.ssh(...)`) +sh -c 'readlink -e "$SYSTEM_PROFILE" | readlink -e "$CURRENT_PROFILE"' + */ + +nix/hosts/ssh.rs: self.run_command(self.ssh(&["reboot"])).await +/* +# `Ssh::initiate_reboot` (`initate_reboot` spelt incorrectly... omg, line 408) +# WARNING: NOTE: `Local` doesn't seem to have an alternative to reboot (why?) +reboot + */ +``` + +#### SSH Options +```bash +# === Defined in /nix/host/ssh.rs Ssh::ssh_options() (line 345) === # + +-o StrictHostKeyChecking=accept-new +-o BatchMode=yes +-T +$EXTRA_SSH_OPTIONS # I assume these are set by the user? +-p $SSH_PORT # if self.port is Some +-F $SSH_CONFIG # if self.ssh_config is Some + +``` From 2b737e981b5ad5b604113366592c79054574de27 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Fri, 12 Dec 2025 10:33:04 +1000 Subject: [PATCH 002/338] init --- cerulean/CeruleanOS/cdesktop.nix | 136 ++++++++++++++ cerulean/CeruleanOS/csystem.nix | 309 +++++++++++++++++++++++++++++++ cerulean/CeruleanOS/cuser.nix | 89 +++++++++ notes.md | 285 ++++++++++++++++++++++++++++ 4 files changed, 819 insertions(+) create mode 100644 cerulean/CeruleanOS/cdesktop.nix create mode 100644 cerulean/CeruleanOS/csystem.nix create mode 100644 cerulean/CeruleanOS/cuser.nix create mode 100644 notes.md diff --git a/cerulean/CeruleanOS/cdesktop.nix b/cerulean/CeruleanOS/cdesktop.nix new file mode 100644 index 0000000..00b7439 --- /dev/null +++ b/cerulean/CeruleanOS/cdesktop.nix @@ -0,0 +1,136 @@ +{ + lib, + config, + pkgs, + pkgs-unstable, + ... +} @ args: let + getModule = name: "../modules/homemanager/${name}.nix"; + getModules = map (x: getModule x); +in { + imports = getModules [ + "term/foot" + "editor/vscode" + + "wm/hyprland" + "wm/hyprland/hyprlock" + + "dm/sddm" + "dm/sddm/themes/corners" + + "apps/firefox" + "apps/thunderbird" + "apps/obsidian" + "apps/rider" + "apps/winbox" + "apps/gitkraken" + "apps/thunar" + + "wm/kanshi" + "wm/mako" + ]; + + home = { + pointerCursor = { + gtk.enable = true; + # x11.enable = true # dont enable since im on hyprland + package = pkgs.bibata-cursors; + name = "Bibata-Modern-Ice"; + size = 16; + }; + + packages = with pkgs; [ + # for services.gnome-keyring + ( + if config.cerulean.isGraphical + then seahorse # gui + else null + ) + + fuzzel + ]; + }; + + gtk = { + enable = true; + font.name = "Victor Mono SemiBold 12"; + theme = { + name = "Dracula"; + package = pkgs.dracula-theme; + }; + iconTheme = { + name = "kora"; + package = pkgs.kora-icon-theme; + }; + # TODO: use a variable to mirror this cursor size + # with the `home.pointerCurser.size` + cursorTheme = { + package = pkgs.bibata-cursors; + name = "Bibata-Modern-Ice"; + size = 16; + }; + }; + + qt = { + enable = true; + platformTheme.name = "gtk2"; + style.name = "gtk2"; + }; + + services = { + # Set display manager (login screen) + displayManager = { + # sddm relies on pkgs.libsForQt5.qt5.qtgraphicaleffects + sddm = { + enable = true; + wayland.enable = true; # experimental + theme = "corners"; + }; + defaultSession = + "hyprland" + + ( + if config.programs.hyprland.withUWSM + then "-uwsm" + else null + ); + }; + + # Multimedia Framework + # With backwards compatability for alsa/pulseaudio/jack + pipewire = { + enable = true; + wireplumber.enable = true; + + alsa.enable = true; + alsa.support32Bit = true; + pulse.enable = true; + jack.enable = true; + }; + }; + + # ---- ENVIRONMENT ---- + environment = { + sessionVariables = { + # Hint Electron apps to use support Wayland + NIXOS_OZONE_WL = "1"; + }; + }; + + # ---- SYSTEM PACKAGES ---- + environment.systemPackages = with pkgs; [ + # User Environment + swww + helvum + easyeffects + pavucontrol + hyprpicker # colour picking utility + hyprshot # screenshot utility + qbittorrent + signal-desktop # MAKE THIS ONLY FOR THE DESKTOP FOR END USERS, NOT SERVERS + kdePackages.gwenview # image viewer + libreoffice + wl-clipboard # clipboard for wayland + ]; + + security.rtkit.enable = true; # I *think* this is for pipewire +} diff --git a/cerulean/CeruleanOS/csystem.nix b/cerulean/CeruleanOS/csystem.nix new file mode 100644 index 0000000..fc398f8 --- /dev/null +++ b/cerulean/CeruleanOS/csystem.nix @@ -0,0 +1,309 @@ +{ + inputs, + lib, + config, + pkgs, + pkgs-unstable, + homemanager, + cerulean, + ... +} @ args: let + getModule = name: "../modules/nixos/${name}.nix"; + getModules = map (x: getModule x); + + getHostModule = name: "TODO"; +in { + imports = getModules [ + (getHostModule "hardware-configuration") + (import "${homemanager}/nixos") + + "shell/bash" + "shell/bash/bashistrans.nix" + "shell/zsh" + "shell/fish" + + "cli/git" + "cli/bat" + "cli/btop" + "cli/tmux" + "cli/nvim" + + "lang/asm" + "lang/bash" # TODO: (YES THIS IS DIFFERENT TO shell/bash, this provides language support ie pkgs.shellcheck) + "lang/c-family" + "lang/dotnet" + # "lang/go" + # "lang/haskell" + # "lang/java" + # "lang/nim" + "lang/python" + # "lang/rust" + # "lang/sage" + + "editor/helix" + ]; + + nix.settings = { + experimental-features = [ + "nix-command" + "flakes" + ]; + + download-buffer-size = 524288000; # 500 MiB + + # making wheel group members "trusted users" allows + # them to import packages not signed by a trusted key + # (aka super duper easier to remote deploy) + trusted-users = ["root" "@wheel"]; + }; + + nixpkgs = { + overlays = cerulean.lib.importOverlaysNixOS; + + config = if config.cerulean.allowUnfreeWhitelist != [] + then { + allowUnfreePredicate = + pkg: builtins.elem + (lib.getName pkg) + config.cerulean.allowUnfreeWhitelist; + } + else { + allowUnfree = config.cerulean.allowUnfree; + }; + }; + + # colmena deployment configuration + deployment = { + targetHost = config.cerulean.domain ?? config.cerulean.ip; + targetUser = "cerulean"; + targetPort = "22"; + sshOptions = [ + "-A" # forward ssh-agent + ]; + buildOnTarget = false; # build locally then deploy + }; + + + time.timeZone = config.cerulean.timeZone; + i18n.defaultLocale = "en_US.UTF-8"; + + # Enable initrd hook for virtual console customisation + # aka cool colours when booting yay!! + console = { + enable = true; + earlySetup = true; # initrd pre hook + keyMap = "us"; + font = "Lat2-Terminus16"; + # ANSI 24-bit color definitions (theme: dracula) + colors = [ + "21222c" + "ff5555" + "50fa7b" + "f1fa8c" + "bd93f9" + "ff79c6" + "8be9fd" + "f8f8f2" + "6272a4" + "ff6e6e" + "69ff94" + "ffffa5" + "d6acff" + "ff92df" + "a4ffff" + "ffffff" + ]; + }; + + # super duper minimum grub2 config + boot.loader = { + efi = { + canTouchEfiVariables = true; + efiSysMountPoint = "/boot/efi"; + }; + + grub = { + enable = true; + device = "nodev"; + }; + + # GitHub: vinceliuice/grub2-themes + grub2-theme = { + enable = true; + theme = "whitesur"; # stylish, vimix, or whitesur + footer = true; + # TODO: switch my cables to switch default grub display + customResolution = "3840x2160"; + }; + }; + + networking = { + hostName = config.cerulean.hostname; + networkmanager.enable = true; + + firewall = { + enable = true; + allowedTCPPorts = [ + 22 # sshd + 80 # nginx (http) + 443 # nginx (https) + # 5678 # MikroTik WinBox + ]; + }; + }; + + # ------- USERS ------- + security.sudo.wheelNeedsPassword = true; + users = { + defaultUserShell = pkgs.bash; + + users = cerulean.lib.importUsersNixOS; + }; + + home-manager = { + users = cerulean.lib.importUsersHomeManager; + + extraSpecialArgs = { inherit inputs pkgs pkgs-unstable; }; + sharedModules = []; + }; + + # ---- ENVIRONMENT ---- + environment = { + # always install "dev"/"man" derivation outputs + extraOutputsToInstall = ["dev" "man"]; + + systemPackages = with pkgs; [ + # User Environment + bluetui + + # Shell + bash + fish + shellcheck + grc # colorise command outputs + moreutils + + # Systems Programming & Compilation + qemu # Fellice Bellard's Quick Emulator + # GNU Utils + gnumake + # Binaries + binutils + strace + ltrace + perf-tools # ftrace + perf + radare2 + gdb + # ASM + nasm + (callPackage ../packages/x86-manpages {}) + # C Family + gcc + clang + clang-tools + + # Rust + cargo + rustc + # Go + go + # Nim + nim + nimble + # Haskell + ghc + ghcid + haskell-language-server + ormolu + + # Python + python312 # I use 3.12 since it's in a pretty stable state now + python314 # also 3.14 for latest features + poetry + + openvpn + inetutils + + # security tools + nmap + + httpie + curlie + zoxide + doggo + tldr + btop + eza + yazi + lazygit + ripgrep + viddy # modern `watch` command + thefuck + + # TODO: once upgraded past Nix-24.07 this line won't be necessary (I think) + # helix will support nixd by default + # SOURCE: https://github.com/nix-community/nixd/blob/main/nixd/docs/editor-setup.md#Helix + # nixd # lsp for nix # DEBUG + + # Pretty necessary + nix-prefetch-git + brightnessctl + acpi + powertop + imagemagick + + # "Standard" Unix Commands + vim + file + wget + tree + pstree + unzip + unrar-free + lz4 + man-pages + man-pages-posix + + # Cryptography + gnupg + openssl + libargon2 + ]; + }; + + programs = { + nix-ld.enable = true; + }; + + documentation = { + enable = true; + doc.enable = true; # install /share/doc packages + man.enable = true; # install manpages + info.enable = true; # install GNU info + dev.enable = true; # install docs intended for developers + nixos = { + enable = true; # install NixOS documentation (ie man -k nix, & nixos-help) + options.splitBuild = true; + # includeAllModules = true; + }; + }; + + virtualisation.docker.enable = true; + + + hardware = { + graphics = { + enable = true; + enable32Bit = true; + }; + + bluetooth = let + btSupported = config.cerulean.bluetoothSupported; + in { + enable = btSupported; + powerOnBoot = btSupported; + }; + }; + + system.stateVersion = config.cerulean.stateVersion; # DO NOT MODIFY +} diff --git a/cerulean/CeruleanOS/cuser.nix b/cerulean/CeruleanOS/cuser.nix new file mode 100644 index 0000000..8b26c9f --- /dev/null +++ b/cerulean/CeruleanOS/cuser.nix @@ -0,0 +1,89 @@ +{ + lib, + config, + pkgs, + pkgs-unstable, + ... +} @ args: let + getModule = name: "../modules/homemanager/${name}.nix"; + getModules = map (x: getModule x); +in { + imports = getModules [ + "shell/fish" + + "cli/git" + "cli/bat" + "cli/btop" + "cli/tmux" + + "editor/helix" + ]; + + nixpkgs.config.allowUnfreePredicate = pkg: + builtins.elem (lib.GetName pkg) [ + "vscode-extension-ms-dotnettools-csharp" + ]; + + home = { + stateVersion = config.cerulean.stateVersion; # DO NOT MODIFY + + username = config.cerulean.username; + homeDirectory = "/home/${config.cerulean.username}"; + + shellAliases = { + rg = "batgrep"; # bat + ripgrep + man = "batman"; # bat + man + }; + + sessionVariables = { + NIX_SHELL_PRESERVE_PROMPT = 1; + }; + + packages = with pkgs; [ + # for services.gnome-keyring + gcr # provides org.gnome.keyring.SystemPrompter + speedtest-cli + ]; + }; + + programs = { + home-manager.enable = true; + + zsh = { + enable = true; + enableCompletion = true; + autosuggestion.enable = true; + syntaxHighlighting.enable = true; + + history = { + size = 10000; + ignoreAllDups = true; + path = "$HOME/.zsh_history"; + ignorePatterns = [ + "rm *" + ]; + }; + }; + + # set ssh profiles + # NOTE: (IMPORTANT) this DOES NOT start the ssh-agent + # for that you need to use `services.ssh-agent.enable` + ssh = { + enable = true; + forwardAgent = false; + addKeysToAgent = "no"; + }; + }; + + services = { + # enable OpenSSH private key agent + ssh-agent.enable = true; + + gnome-keyring.enable = true; + }; + + # the ssh-agent won't set this for itself... + systemd.user.sessionVariables.SSH_AUTH_SOCK = "$XDG_RUNTIME_DIR/ssh-agent"; + # Nicely reload system units when changing configs + systemd.user.startServices = "sd-switch"; +} diff --git a/notes.md b/notes.md new file mode 100644 index 0000000..9bc45e1 --- /dev/null +++ b/notes.md @@ -0,0 +1,285 @@ +use `rg -F 'Command::new'` within `~/sandbox/clones/colmena/` + +use `hx $(realpath $(which nixos-rebuild))` to access the bash script + +>[!NOTE] +> The option `--extra-experimental-features "flakes"` is only set +> if `expression.requires_flakes()` where `expression: NixExpression`. + +>[!TODO] +> Also search for nix-store by itself, nix-instantiate, etc. +> And also search for `make_privileged_command(...)` usages. +> or actually maybe `.stdin(...)` or `Stdio::piped(...)`. + + +## `Command::new()` Calls Translated +```rs +command/repl.rs: let mut repl_cmd = Command::new("nix"); +/* +nix repl \ + --experimental-features "nix-command flakes" \ + --file $REPL_EXPRESSION_FILE + */ + +nix/key.rs: let output = Command::new(pathname) +/* +$PATHNAME $ARGS < /dev/null |& ... + */ + +nix/info.rs: let version_cmd = Command::new("nix-instantiate") +/* +# `NixCheck::detect(...)` +# NOTE: Used to detect if Nix is present (and if so, what version) +# NOTE: The version number is used to detect if flakes are supported (NOT enabled though) +# NOTE: You can do the same with nix3 with `nix --version` +nix-instantiate --version + */ + +nix/info.rs: let flake_cmd = Command::new("nix-instantiate") +/* +# `NixCheck::detect(...)` +# NOTE: Used to detect if flakes are enabled +nix-instantiate \ + --eval -E builtins.getFlake \ + &> /dev/null + */ + +nix/flake.rs: let child = Command::new("nix") +/* +# `FlakeMetadata::resolve(flake: &str) -> ColmenaREsult` +nix flake metadata \ + --json \ + --extra-experimental-features "nix-command flakes" + */ + +nix/flake.rs: let status = Command::new("nix") +/* +# `lock_flake_quiet(uri: &str) -> ColmenaResult<()>` +# NOTE: "Quietly locks the dependencies of a flake." +nix flake lock \ + --extra-experimental-features "nix-command flakes" + */ + +nix/profile.rs: let mut command = Command::new("nix-store"); +/* +# `Profile::create_gc_root(&self, path: &Path) -> ColmenaResult<()>` +# NOTE: "Create a GC root for this profile." +# Each Profile struct contains a StorePath object +nix-store \ + --no-build-output \ + --indirect \ + --add-root \ + --realise $STORE_PATH \ + 1>/dev/null + */ + +nix/hive/mod.rs: let mut command = Command::new("nix-instantiate"); +/* +# `NixInstantiate::instantiate(&self) -> Command` +# NOTE: "Instantiation is not supported with DirectFlakeEval" +# NOTE: $EXPRESSION == `self.hive.get_base_expression() + self.expression` +# NOTE: --extra-experimental-features "flakes" only if `self.hive.is_flake()` +nix-instantiate -E $EXPRESSION \ + --no-gc-warning \ + --extra-experimental-features "flakes" + */ + +nix/hive/mod.rs: let mut command = Command::new("nix"); +/* +# `NixInstantiate::eval(self) -> Command` +# NOTE: $FLAGS == `self.hive.nix_flags()` + +# XXX: WARNING: if `self.hive.evaluation_method` is `EvaluationMethod::NixInstantiate` +# XXX: WARNING: then `NixInstantiate::instantiate(...)` (previous) is called +$NIX_INSTANTIATE_COMMAND + --eval + --json + --strict + --read-write-mode # ensures the derivations are instantiated, required for system profile evaluation and IFD + $FLAGS + +# XXX: WARNING: else if `self.hive.evaluation_method` is `EvaluationMethod::DirectFlakeEval` +# XXX: WARNING: then `NixInstantiate::instantiate(...)` (previous) is called +nix eval ${FLAKE_URI}#colmenaHive $FULL_EXPRESSION $FLAGS \ + --json \ + --apply \ + --extra-experimental-features "nix-command flakes" + */ + +nix/evaluator/nix_eval_jobs.rs: let mut command = Command::new(&self.executable); +/* +# `NixEvalJobs::evaluate(&self, expression: &dyn NixExpression, flags: NixFlags) -> ColmenaREsult>>>` +# NOTE: $EXECUTABLE defaults to EXECUTABLE='nix-eval-jobs' +$EXECUTABLE $FLAGS \ + --workers $WORKERS \ + --expr $EXPRESSION \ + --extra-experimental-features "flakes" \ + 2>&1 + */ + +nix/host/local.rs: let mut command = self.make_privileged_command(&["sh", "-c", &key_script]); +/* +# `Local::upload_key(&mut self, name: &str, key: &Key, require_ownership: bool) -> ColmenaResult<()>` +# XXX: NOTE: Same as `Ssh::upload_key()` +sh -c "$KEY_SCRIPT" + */ + + +nix/host/local.rs: let mut command = Command::new("nix-store"); +/* +# `Local::realize_remote(...)` +# XXX: NOTE: Same as `Ssh::realize_remote(...)` +nix-store + --no-gc-warning + --realise $DERIVATION_PATH + */ + +nix/host/local.rs: self.make_privileged_command(&["nix-env", "--profile", SYSTEM_PROFILE, "--set", path]) +/* +# `Local::activate(&mut self, profile: &Profile, goal: Goal) -> ColmenaResult<()>` +# XXX: NOTE: Same as `Ssh::activate(...)` + +# NOTE: This command runs if `goal.should_switch_profile()` +nix-env --profile $SYSTEM+PROFILE + --set $PROFILE_PATH + +# NOTE: Separate command (runs regardless of `goal.should_switch_profile()`) +$ACTIVATION_COMMAND*/ + +nix/host/local.rs: let paths = Command::new("readlink") +/* +# `Local::get_current_system_profile(&mut self) -> ColmenaResult` +# XXX: NOTE: Same as `Ssh::get_current_system_profile(...)` +readlink -e $CURRENT_PROFILE + */ + +nix/host/local.rs: let paths = Command::new("sh") +/* +# `Local::get_main_system_profile(&mut self) -> ColmenaResult` +# XXX: NOTE: Same as `Ssh::get_main_system_profile(...)` +sh -c 'readlink -e "$SYSTEM_PROFILE" | readlink -e "$CURRENT_PROFILE"' + */ + +nix/host/local.rs: let mut result = Command::new(full_command[0]); +/* +# `Local::make_privileged_command>(&self, command: &[S]) -> Command` +$COMMAND + */ + +nix/store.rs: let references = Command::new("nix-store") +/* +# `StorePath::references(...)` +nix-store + --query + --references $STORE_PATH + */ + +nix/host/ssh.rs: let mut cmd = Command::new("ssh"); +/* +# `Ssh::ssh(...)` +# $PRIVESC_COMMAND is privilege_escalation_command from Ssh struct +NIX_SSHOPTS="$SSH_OPTIONS" ssh $SSH_OPTIONS -- $PRIVESC_COMMAND $COMMAND + */ + +nix/host/ssh.rs: let mut command = Command::new("nix"); +/* +# `Ssh::nix_copy_closure(...)` - CONDITION: `if self.use_nix3_copy()` +# NOTE: --builders-use-substitutes "needed due to UX bug in ssh-ng://" +# --derivation is only added if the `path.file_extension() == "drv"` +# --to or --from is used depending on `CopyDirection` +# "?compress=true" is added after ${SSH_TARGET} if `options.gzip` is set +# WARNING: COMMAND EXECUTED OVER SSH (`self.ssh(...)`) +NIX_SSHOPTS="$SSH_OPTIONS" nix copy $CLOSURE_PATH + --no-check-sigs + --substitute-on-destination + --builders-use-substitutes + --derivation + [--to|--from] "ssh-ng://${SSH_TARGET}?compress=true" + --extra-experimental-features "nix-command" + */ + +nix/host/ssh.rs: let mut command = Command::new("nix-copy-closure"); +/* +# `Ssh::nix_copy_closure(...)` - CONDITION: `else` +# --include-outputs if `options.include_outputs` +# --use-substitues if `options.use_substitutes` +# --gzip if `options.gzip` +# WARNING: COMMAND EXECUTED OVER SSH (`self.ssh(...)`) +NIX_SSHOPTS="$SSH_OPTIONS" nix-copy-closure $SSH_TARGET $CLOSURE_PATH + [--to|--from] + --include-outputs + --use-substitues + --gzip + */ + + +nix/host/ssh.rs: let mut command = self.ssh(&["sh", "-c", &key_script]); +/* +# `Ssh::upload_key(...)` +# XXX: NOTE: Same as `Ssh::upload_key()` +# WARNING: COMMAND EXECUTED OVER SSH (`sel.ssh(...)`) +sh -c "$KEY_SCRIPT" + */ + + +nix/host/ssh.rs: let mut command = self.ssh("nix-store"); +/* +# `Ssh::realize_remote(...)` +# XXX: NOTE: Same as `Local::realize_remote(...)` +# WARNING: COMMAND EXECUTED OVER SSH (`self.ssh(...)`) +nix-store + --realise $DERIVATION + --no-gc-warning + */ + +nix/host/ssh.rs: let set_profile = self.ssh(&["nix-env", "--profile", SYSTEM_PROFILE, "--set", path]); +/* +# `Ssh::activate` - activate (switch to) a specific NixOS profile +# XXX: NOTE: Same as `Local::activate(...)` + +# NOTE: This command runs if `goal.should_switch_profile()` +# WARNING: COMMAND EXECUTED OVER SSH (`self.ssh(...)`) +nix-env --profile $SYSTEM+PROFILE + --set $PROFILE_PATH + +# NOTE: Separate command (runs regardless of `goal.should_switch_profile()`) +# WARNING: COMMAND EXECUTED OVER SSH (`self.ssh(...)`) +$ACTIVATION_COMMAND + */ + +nix/host/ssh.rs: let paths = self.ssh(&["readlink", "-e", CURRENT_PROFILE]) +/* +# `Ssh::get_current_system_profile(&mut self) -> ColmenaResult` +# XXX: NOTE: Same as `Local::get_current_system_profile(...)` +readlink -e $CURRENT_PROFILE + */ + +nix/hosts/ssh.rs: let paths = self.ssh(&["sh", "-c", &command]).capture_output().await?; +/* +# `Ssh::get_main_system_profile(...)` +# NOTE: the command executed is the same as in `/nix/host/local.rs` (except for SSH) +# XXX: NOTE: Same as `Local::get_main_system_profile(...)` +# WARNING: COMMAND EXECUTED OVER SSH (`self.ssh(...)`) +sh -c 'readlink -e "$SYSTEM_PROFILE" | readlink -e "$CURRENT_PROFILE"' + */ + +nix/hosts/ssh.rs: self.run_command(self.ssh(&["reboot"])).await +/* +# `Ssh::initiate_reboot` (`initate_reboot` spelt incorrectly... omg, line 408) +# WARNING: NOTE: `Local` doesn't seem to have an alternative to reboot (why?) +reboot + */ +``` + +#### SSH Options +```bash +# === Defined in /nix/host/ssh.rs Ssh::ssh_options() (line 345) === # + +-o StrictHostKeyChecking=accept-new +-o BatchMode=yes +-T +$EXTRA_SSH_OPTIONS # I assume these are set by the user? +-p $SSH_PORT # if self.port is Some +-F $SSH_CONFIG # if self.ssh_config is Some + +``` From decb028d1924d62cd03f7e57ceec0cb5b0598a46 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sat, 13 Dec 2025 09:30:20 +1000 Subject: [PATCH 003/338] add flake --- flake.lock | 27 ++++++++++++++++++ flake.nix | 34 +++++++++++++++++++++++ {cerulean/CeruleanOS => lib}/cdesktop.nix | 0 {cerulean/CeruleanOS => lib}/csystem.nix | 0 {cerulean/CeruleanOS => lib}/cuser.nix | 0 5 files changed, 61 insertions(+) create mode 100644 flake.lock create mode 100644 flake.nix rename {cerulean/CeruleanOS => lib}/cdesktop.nix (100%) rename {cerulean/CeruleanOS => lib}/csystem.nix (100%) rename {cerulean/CeruleanOS => lib}/cuser.nix (100%) diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..7666840 --- /dev/null +++ b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1756754095, + "narHash": "sha256-9Rsn9XEWINExosFkKEqdp8EI6Mujr1gmQiyrEcts2ls=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "7c815e513adbf03c9098b2bd230c1e0525c8a7f9", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-25.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..823fcda --- /dev/null +++ b/flake.nix @@ -0,0 +1,34 @@ +{ + description = "Your Nix Cloud Simplified"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"; + nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixos-unstable"; + + systems.url = "github:nix-systems/default"; + }; + + outputs = inputs @ { + self, + systems, + nixpkgs, + ... + }: let + defaultSystems = ["aarch64-darwin" "aarch64-linux" "i686-linux" "x86_64-darwin" "x86_64-linux"]; + + forAllSystems = f: + nixpkgs.lib.genAttrs defaultSystems (system: + f system (import nixpkgs { + inherit system; + overlays = builtins.attrValues self.overlays; + })); + in { + overlays.default = final: prev: { + }; + + checks = self.packages; + packages = + forAllSystems (system: pkgs: rec { + }); + }; +} diff --git a/cerulean/CeruleanOS/cdesktop.nix b/lib/cdesktop.nix similarity index 100% rename from cerulean/CeruleanOS/cdesktop.nix rename to lib/cdesktop.nix diff --git a/cerulean/CeruleanOS/csystem.nix b/lib/csystem.nix similarity index 100% rename from cerulean/CeruleanOS/csystem.nix rename to lib/csystem.nix diff --git a/cerulean/CeruleanOS/cuser.nix b/lib/cuser.nix similarity index 100% rename from cerulean/CeruleanOS/cuser.nix rename to lib/cuser.nix From bc2466b0382b6045461b763398cef4bddab12191 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Sat, 13 Dec 2025 09:30:20 +1000 Subject: [PATCH 004/338] add flake --- flake.lock | 27 ++++++++++++++++++ flake.nix | 34 +++++++++++++++++++++++ {cerulean/CeruleanOS => lib}/cdesktop.nix | 0 {cerulean/CeruleanOS => lib}/csystem.nix | 0 {cerulean/CeruleanOS => lib}/cuser.nix | 0 5 files changed, 61 insertions(+) create mode 100644 flake.lock create mode 100644 flake.nix rename {cerulean/CeruleanOS => lib}/cdesktop.nix (100%) rename {cerulean/CeruleanOS => lib}/csystem.nix (100%) rename {cerulean/CeruleanOS => lib}/cuser.nix (100%) diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..7666840 --- /dev/null +++ b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1756754095, + "narHash": "sha256-9Rsn9XEWINExosFkKEqdp8EI6Mujr1gmQiyrEcts2ls=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "7c815e513adbf03c9098b2bd230c1e0525c8a7f9", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-25.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..823fcda --- /dev/null +++ b/flake.nix @@ -0,0 +1,34 @@ +{ + description = "Your Nix Cloud Simplified"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"; + nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixos-unstable"; + + systems.url = "github:nix-systems/default"; + }; + + outputs = inputs @ { + self, + systems, + nixpkgs, + ... + }: let + defaultSystems = ["aarch64-darwin" "aarch64-linux" "i686-linux" "x86_64-darwin" "x86_64-linux"]; + + forAllSystems = f: + nixpkgs.lib.genAttrs defaultSystems (system: + f system (import nixpkgs { + inherit system; + overlays = builtins.attrValues self.overlays; + })); + in { + overlays.default = final: prev: { + }; + + checks = self.packages; + packages = + forAllSystems (system: pkgs: rec { + }); + }; +} diff --git a/cerulean/CeruleanOS/cdesktop.nix b/lib/cdesktop.nix similarity index 100% rename from cerulean/CeruleanOS/cdesktop.nix rename to lib/cdesktop.nix diff --git a/cerulean/CeruleanOS/csystem.nix b/lib/csystem.nix similarity index 100% rename from cerulean/CeruleanOS/csystem.nix rename to lib/csystem.nix diff --git a/cerulean/CeruleanOS/cuser.nix b/lib/cuser.nix similarity index 100% rename from cerulean/CeruleanOS/cuser.nix rename to lib/cuser.nix From bb362d9470716a1f94d4fac65269723e6c00c037 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sat, 13 Dec 2025 13:24:37 +1000 Subject: [PATCH 005/338] implement cerulean.nexus.nodes --- flake.lock | 27 --------------- flake.nix | 97 +++++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 74 insertions(+), 50 deletions(-) delete mode 100644 flake.lock diff --git a/flake.lock b/flake.lock deleted file mode 100644 index 7666840..0000000 --- a/flake.lock +++ /dev/null @@ -1,27 +0,0 @@ -{ - "nodes": { - "nixpkgs": { - "locked": { - "lastModified": 1756754095, - "narHash": "sha256-9Rsn9XEWINExosFkKEqdp8EI6Mujr1gmQiyrEcts2ls=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "7c815e513adbf03c9098b2bd230c1e0525c8a7f9", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixos-25.05", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "nixpkgs": "nixpkgs" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/flake.nix b/flake.nix index 823fcda..25d7357 100644 --- a/flake.nix +++ b/flake.nix @@ -1,34 +1,85 @@ { description = "Your Nix Cloud Simplified"; - inputs = { + inputs = let + follows = following: { + inputs = builtins.listToAttrs (builtins.map (x: { + name = x; + value = {follows = x;}; + }) + following); + }; + in { + systems.url = "github:nix-systems/default"; + nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"; nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixos-unstable"; - systems.url = "github:nix-systems/default"; - }; - - outputs = inputs @ { - self, - systems, - nixpkgs, - ... - }: let - defaultSystems = ["aarch64-darwin" "aarch64-linux" "i686-linux" "x86_64-darwin" "x86_64-linux"]; - - forAllSystems = f: - nixpkgs.lib.genAttrs defaultSystems (system: - f system (import nixpkgs { - inherit system; - overlays = builtins.attrValues self.overlays; - })); - in { - overlays.default = final: prev: { + nib = { + url = "github:emileclarkb/nib"; + inputs = follows ["systems"]; }; - checks = self.packages; - packages = - forAllSystems (system: pkgs: rec { + deploy-rs.url = "github:serokell/deploy-rs"; + }; + + outputs = { + self, + nixpkgs, + nixpkgs-unstable, + nib, + deploy-rs, + ... + } @ inputs: let + lib = nixpkgs.lib; + + sys = with nib; + mkUSys { + pkgs = withPkgs nixpkgs { + config.allowUnfree = false; + overlays = builtins.attrValues self.overlays; + }; + upkgs = withPkgs nixpkgs-unstable { + config.allowUnfree = false; + }; + }; + in rec { + # overlays.default = final: prev: { + # }; + + # checks = self.packages; + # packages = + # forAllSystems (system: pkgs: rec { + # }); + + mkNexusConfig = config: let + mapNodes = f: lib.mapAttrs f config.nexus.nodes; + in rec { + nixosConfigurations = mapNodes ( + name: node: + lib.nixosSystem { + system = node.system; + modules = node.modules; + } + ); + + deploy.nodes = mapNodes (name: node: { + hostname = name; + profiles.system = { + user = "root"; + path = let + system = node.system; + in + deploy-rs.lib.${system}.activate.nixos nixosConfigurations.${system}; + }; }); + + checks = builtins.mapAttrs (system: deployLib: deployLib.deployChecks deploy) deploy-rs.lib; + }; + + mkNexus = outputs: let + config = outputs.cerulean; + in + (mkNexusConfig config) // (builtins.removeAttrs outputs ["cerulean"]); }; } From 4b8e6504376b6b20e03443966b85eecffb4f5e45 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Sat, 13 Dec 2025 13:24:37 +1000 Subject: [PATCH 006/338] implement cerulean.nexus.nodes --- flake.lock | 27 --------------- flake.nix | 97 +++++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 74 insertions(+), 50 deletions(-) delete mode 100644 flake.lock diff --git a/flake.lock b/flake.lock deleted file mode 100644 index 7666840..0000000 --- a/flake.lock +++ /dev/null @@ -1,27 +0,0 @@ -{ - "nodes": { - "nixpkgs": { - "locked": { - "lastModified": 1756754095, - "narHash": "sha256-9Rsn9XEWINExosFkKEqdp8EI6Mujr1gmQiyrEcts2ls=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "7c815e513adbf03c9098b2bd230c1e0525c8a7f9", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixos-25.05", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "nixpkgs": "nixpkgs" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/flake.nix b/flake.nix index 823fcda..25d7357 100644 --- a/flake.nix +++ b/flake.nix @@ -1,34 +1,85 @@ { description = "Your Nix Cloud Simplified"; - inputs = { + inputs = let + follows = following: { + inputs = builtins.listToAttrs (builtins.map (x: { + name = x; + value = {follows = x;}; + }) + following); + }; + in { + systems.url = "github:nix-systems/default"; + nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"; nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixos-unstable"; - systems.url = "github:nix-systems/default"; - }; - - outputs = inputs @ { - self, - systems, - nixpkgs, - ... - }: let - defaultSystems = ["aarch64-darwin" "aarch64-linux" "i686-linux" "x86_64-darwin" "x86_64-linux"]; - - forAllSystems = f: - nixpkgs.lib.genAttrs defaultSystems (system: - f system (import nixpkgs { - inherit system; - overlays = builtins.attrValues self.overlays; - })); - in { - overlays.default = final: prev: { + nib = { + url = "github:emileclarkb/nib"; + inputs = follows ["systems"]; }; - checks = self.packages; - packages = - forAllSystems (system: pkgs: rec { + deploy-rs.url = "github:serokell/deploy-rs"; + }; + + outputs = { + self, + nixpkgs, + nixpkgs-unstable, + nib, + deploy-rs, + ... + } @ inputs: let + lib = nixpkgs.lib; + + sys = with nib; + mkUSys { + pkgs = withPkgs nixpkgs { + config.allowUnfree = false; + overlays = builtins.attrValues self.overlays; + }; + upkgs = withPkgs nixpkgs-unstable { + config.allowUnfree = false; + }; + }; + in rec { + # overlays.default = final: prev: { + # }; + + # checks = self.packages; + # packages = + # forAllSystems (system: pkgs: rec { + # }); + + mkNexusConfig = config: let + mapNodes = f: lib.mapAttrs f config.nexus.nodes; + in rec { + nixosConfigurations = mapNodes ( + name: node: + lib.nixosSystem { + system = node.system; + modules = node.modules; + } + ); + + deploy.nodes = mapNodes (name: node: { + hostname = name; + profiles.system = { + user = "root"; + path = let + system = node.system; + in + deploy-rs.lib.${system}.activate.nixos nixosConfigurations.${system}; + }; }); + + checks = builtins.mapAttrs (system: deployLib: deployLib.deployChecks deploy) deploy-rs.lib; + }; + + mkNexus = outputs: let + config = outputs.cerulean; + in + (mkNexusConfig config) // (builtins.removeAttrs outputs ["cerulean"]); }; } From 4b13f5e4fac63e65f11dbead61f4f381b7ec55e4 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sat, 13 Dec 2025 13:40:02 +1000 Subject: [PATCH 007/338] flakes aren't actually nix :( *fuck thunks* --- flake.nix | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/flake.nix b/flake.nix index 25d7357..0814581 100644 --- a/flake.nix +++ b/flake.nix @@ -1,23 +1,15 @@ { description = "Your Nix Cloud Simplified"; - inputs = let - follows = following: { - inputs = builtins.listToAttrs (builtins.map (x: { - name = x; - value = {follows = x;}; - }) - following); - }; - in { + inputs = { systems.url = "github:nix-systems/default"; nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"; nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixos-unstable"; nib = { - url = "github:emileclarkb/nib"; - inputs = follows ["systems"]; + url = "github:emilelcb/nib"; + inputs.systems.follows = "systems"; }; deploy-rs.url = "github:serokell/deploy-rs"; From ffecd5c2a734293d838408731e4b3018bee538da Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Sat, 13 Dec 2025 13:40:02 +1000 Subject: [PATCH 008/338] flakes aren't actually nix :( *fuck thunks* --- flake.nix | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/flake.nix b/flake.nix index 25d7357..0814581 100644 --- a/flake.nix +++ b/flake.nix @@ -1,23 +1,15 @@ { description = "Your Nix Cloud Simplified"; - inputs = let - follows = following: { - inputs = builtins.listToAttrs (builtins.map (x: { - name = x; - value = {follows = x;}; - }) - following); - }; - in { + inputs = { systems.url = "github:nix-systems/default"; nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"; nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixos-unstable"; nib = { - url = "github:emileclarkb/nib"; - inputs = follows ["systems"]; + url = "github:emilelcb/nib"; + inputs.systems.follows = "systems"; }; deploy-rs.url = "github:serokell/deploy-rs"; From eae4f89f60650b35a3631f2026300be3d15f60a8 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sat, 13 Dec 2025 13:40:08 +1000 Subject: [PATCH 009/338] progress flake.lock --- flake.lock | 167 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 flake.lock diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..709fb53 --- /dev/null +++ b/flake.lock @@ -0,0 +1,167 @@ +{ + "nodes": { + "deploy-rs": { + "inputs": { + "flake-compat": "flake-compat", + "nixpkgs": "nixpkgs", + "utils": "utils" + }, + "locked": { + "lastModified": 1762286984, + "narHash": "sha256-9I2H9x5We6Pl+DBYHjR1s3UT8wgwcpAH03kn9CqtdQc=", + "owner": "serokell", + "repo": "deploy-rs", + "rev": "9c870f63e28ec1e83305f7f6cb73c941e699f74f", + "type": "github" + }, + "original": { + "owner": "serokell", + "repo": "deploy-rs", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1733328505, + "narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "nib": { + "inputs": { + "systems": [ + "systems" + ] + }, + "locked": { + "lastModified": 1765596193, + "narHash": "sha256-mBtvOklNgeKgYRCjERNUpFP0ILNH5wlkkuV3tpNxeN4=", + "owner": "emilelcb", + "repo": "nib", + "rev": "b7a24b922f54c5ef31a660832d874869dfa0826e", + "type": "github" + }, + "original": { + "owner": "emilelcb", + "repo": "nib", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1743014863, + "narHash": "sha256-jAIUqsiN2r3hCuHji80U7NNEafpIMBXiwKlSrjWMlpg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "bd3bac8bfb542dbde7ffffb6987a1a1f9d41699f", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-unstable": { + "locked": { + "lastModified": 1765186076, + "narHash": "sha256-hM20uyap1a0M9d344I692r+ik4gTMyj60cQWO+hAYP8=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "addf7cf5f383a3101ecfba091b98d0a1263dc9b8", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1765311797, + "narHash": "sha256-mSD5Ob7a+T2RNjvPvOA1dkJHGVrNVl8ZOrAwBjKBDQo=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "09eb77e94fa25202af8f3e81ddc7353d9970ac1b", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-25.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "deploy-rs": "deploy-rs", + "nib": "nib", + "nixpkgs": "nixpkgs_2", + "nixpkgs-unstable": "nixpkgs-unstable", + "systems": "systems_2" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} From b392c9ab1b71ad22c81cdc61d2184c818b146088 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Sat, 13 Dec 2025 13:40:08 +1000 Subject: [PATCH 010/338] progress flake.lock --- flake.lock | 167 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 flake.lock diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..709fb53 --- /dev/null +++ b/flake.lock @@ -0,0 +1,167 @@ +{ + "nodes": { + "deploy-rs": { + "inputs": { + "flake-compat": "flake-compat", + "nixpkgs": "nixpkgs", + "utils": "utils" + }, + "locked": { + "lastModified": 1762286984, + "narHash": "sha256-9I2H9x5We6Pl+DBYHjR1s3UT8wgwcpAH03kn9CqtdQc=", + "owner": "serokell", + "repo": "deploy-rs", + "rev": "9c870f63e28ec1e83305f7f6cb73c941e699f74f", + "type": "github" + }, + "original": { + "owner": "serokell", + "repo": "deploy-rs", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1733328505, + "narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "nib": { + "inputs": { + "systems": [ + "systems" + ] + }, + "locked": { + "lastModified": 1765596193, + "narHash": "sha256-mBtvOklNgeKgYRCjERNUpFP0ILNH5wlkkuV3tpNxeN4=", + "owner": "emilelcb", + "repo": "nib", + "rev": "b7a24b922f54c5ef31a660832d874869dfa0826e", + "type": "github" + }, + "original": { + "owner": "emilelcb", + "repo": "nib", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1743014863, + "narHash": "sha256-jAIUqsiN2r3hCuHji80U7NNEafpIMBXiwKlSrjWMlpg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "bd3bac8bfb542dbde7ffffb6987a1a1f9d41699f", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-unstable": { + "locked": { + "lastModified": 1765186076, + "narHash": "sha256-hM20uyap1a0M9d344I692r+ik4gTMyj60cQWO+hAYP8=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "addf7cf5f383a3101ecfba091b98d0a1263dc9b8", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1765311797, + "narHash": "sha256-mSD5Ob7a+T2RNjvPvOA1dkJHGVrNVl8ZOrAwBjKBDQo=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "09eb77e94fa25202af8f3e81ddc7353d9970ac1b", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-25.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "deploy-rs": "deploy-rs", + "nib": "nib", + "nixpkgs": "nixpkgs_2", + "nixpkgs-unstable": "nixpkgs-unstable", + "systems": "systems_2" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} From ccc1e46dec619f519b3b315e127aa69332e0acc8 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sat, 13 Dec 2025 22:10:44 +1000 Subject: [PATCH 011/338] implement: mkNexus now configures NixOS and deploy-rs Deployment information is parsed via nib.parse.mergeTypedStruct. Overall much less boilerplate will be required for endusers. deploy-rs also now supports retrieval from a nix binary cache! --- flake.nix | 105 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 94 insertions(+), 11 deletions(-) diff --git a/flake.nix b/flake.nix index 0814581..2488ecd 100644 --- a/flake.nix +++ b/flake.nix @@ -36,8 +36,18 @@ }; }; in rec { - # overlays.default = final: prev: { - # }; + overlays = [ + # deploy-rs is built from the flake input, not from nixpkgs! + # To take advantage of the nixpkgs binary cache, + # the deploy-rs package can be overwritten: + deploy-rs.overlays.default + (self: super: { + deploy-rs = { + inherit (super) deploy-rs; + lib = super.deploy-rs.lib; + }; + }) + ]; # checks = self.packages; # packages = @@ -45,24 +55,97 @@ # }); mkNexusConfig = config: let - mapNodes = f: lib.mapAttrs f config.nexus.nodes; + # abstract node instance that stores all default values + templateNode = let + missing = msg: path: + builtins.abort '' + Each Cerulean Nexus node is required to specify ${msg}! + Ensure `cerulean.nexus.nodes.$${NODE}.${path}` exists under your call to `cerulean.mkNexus`. + ''; + in + system: { + system = missing "its system type" "system"; # intentionally left missing!! (to raise errors) + modules = missing "its required modules" "modules"; + specialArgs = { + inherit inputs; + pkgs = sys.pkgsFor system; + upkgs = sys.upkgsFor system; + }; + + deploy = { + user = "root"; + sudo = "sudo -u"; + interactiveSudo = false; + + remoteBuild = false; # prefer local builds for remote deploys + + autoRollback = true; # reactivate previous profile if activation fails + magicRollback = true; + + activationTimeout = 500; # timeout in seconds for profile activation + confirmTimeout = 30; # timeout in seconds for profile activation confirmation + + ssh = { + host = missing "an SSH hostname (domain name or ip address) for deployment" "deploy.ssh.host"; + user = missing "an SSH username for deployment" "deploy.ssh.user"; + port = 22; + opts = []; + }; + }; + }; + + parseNode = name: nodeAttrs: + if !(builtins.isAttrs nodeAttrs) + then + # fail if node is not an attribute set + builtins.abort '' + Cerulean Nexus nodes must be provided as an attribute set, got "${builtins.typeOf nodeAttrs}" instead! + Ensure all `cerulean.nexus.nodes.${name}` declarations are attribute sets under your call to `cerulean.mkNexus`. + '' + # TODO: nodeAttrs.system won't display any nice error messages!! + # TODO: will mergeTypedStruct give nice error messages? or should I use mergeStructErr directly? + else nib.parse.mergeTypedStruct (templateNode nodeAttrs.system) nodeAttrs; + + mapNodes = f: lib.mapAttrs f (parseNode config.nexus.nodes); in rec { nixosConfigurations = mapNodes ( name: node: lib.nixosSystem { system = node.system; modules = node.modules; + + # nix passes these to every single module + specialArgs = [] // node.modules.specialArgs; } ); - deploy.nodes = mapNodes (name: node: { - hostname = name; - profiles.system = { - user = "root"; - path = let - system = node.system; - in - deploy-rs.lib.${system}.activate.nixos nixosConfigurations.${system}; + deploy.nodes = mapNodes (nodeName: node: { + hostname = node.deploy.ssh.host; + + profilesOrder = ["default"]; # profiles priority + profiles.default = { + path = deploy-rs.lib.${node.system}.activate.nixos nixosConfigurations.${node.system}; + + user = node.deploy.user; + sudo = node.deploy.sudo; + interactiveSudo = node.deploy.interactiveSudo; + + fastConnection = false; + + autoRollback = node.deploy.autoRollback; + magicRollback = node.deploy.magicRollback; + activationTimeout = node.deploy.activationTimeout; + confirmTimeout = node.deploy.confirmTimeout; + + remoteBuild = node.deploy.remoteBuild; + sshUser = node.deploy.ssh.user; + sshOpts = + node.deploy.ssh.opts + ++ ( + if builtins.elem "-p" node.deploy.ssh.opts + then [] + else ["-p" (builtins.toString node.deploy.ssh.port)] + ); }; }); From bd7e49af6d90c7bb16aed1f9e68cf31f4934bc86 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Sat, 13 Dec 2025 22:10:44 +1000 Subject: [PATCH 012/338] implement: mkNexus now configures NixOS and deploy-rs Deployment information is parsed via nib.parse.mergeTypedStruct. Overall much less boilerplate will be required for endusers. deploy-rs also now supports retrieval from a nix binary cache! --- flake.nix | 105 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 94 insertions(+), 11 deletions(-) diff --git a/flake.nix b/flake.nix index 0814581..2488ecd 100644 --- a/flake.nix +++ b/flake.nix @@ -36,8 +36,18 @@ }; }; in rec { - # overlays.default = final: prev: { - # }; + overlays = [ + # deploy-rs is built from the flake input, not from nixpkgs! + # To take advantage of the nixpkgs binary cache, + # the deploy-rs package can be overwritten: + deploy-rs.overlays.default + (self: super: { + deploy-rs = { + inherit (super) deploy-rs; + lib = super.deploy-rs.lib; + }; + }) + ]; # checks = self.packages; # packages = @@ -45,24 +55,97 @@ # }); mkNexusConfig = config: let - mapNodes = f: lib.mapAttrs f config.nexus.nodes; + # abstract node instance that stores all default values + templateNode = let + missing = msg: path: + builtins.abort '' + Each Cerulean Nexus node is required to specify ${msg}! + Ensure `cerulean.nexus.nodes.$${NODE}.${path}` exists under your call to `cerulean.mkNexus`. + ''; + in + system: { + system = missing "its system type" "system"; # intentionally left missing!! (to raise errors) + modules = missing "its required modules" "modules"; + specialArgs = { + inherit inputs; + pkgs = sys.pkgsFor system; + upkgs = sys.upkgsFor system; + }; + + deploy = { + user = "root"; + sudo = "sudo -u"; + interactiveSudo = false; + + remoteBuild = false; # prefer local builds for remote deploys + + autoRollback = true; # reactivate previous profile if activation fails + magicRollback = true; + + activationTimeout = 500; # timeout in seconds for profile activation + confirmTimeout = 30; # timeout in seconds for profile activation confirmation + + ssh = { + host = missing "an SSH hostname (domain name or ip address) for deployment" "deploy.ssh.host"; + user = missing "an SSH username for deployment" "deploy.ssh.user"; + port = 22; + opts = []; + }; + }; + }; + + parseNode = name: nodeAttrs: + if !(builtins.isAttrs nodeAttrs) + then + # fail if node is not an attribute set + builtins.abort '' + Cerulean Nexus nodes must be provided as an attribute set, got "${builtins.typeOf nodeAttrs}" instead! + Ensure all `cerulean.nexus.nodes.${name}` declarations are attribute sets under your call to `cerulean.mkNexus`. + '' + # TODO: nodeAttrs.system won't display any nice error messages!! + # TODO: will mergeTypedStruct give nice error messages? or should I use mergeStructErr directly? + else nib.parse.mergeTypedStruct (templateNode nodeAttrs.system) nodeAttrs; + + mapNodes = f: lib.mapAttrs f (parseNode config.nexus.nodes); in rec { nixosConfigurations = mapNodes ( name: node: lib.nixosSystem { system = node.system; modules = node.modules; + + # nix passes these to every single module + specialArgs = [] // node.modules.specialArgs; } ); - deploy.nodes = mapNodes (name: node: { - hostname = name; - profiles.system = { - user = "root"; - path = let - system = node.system; - in - deploy-rs.lib.${system}.activate.nixos nixosConfigurations.${system}; + deploy.nodes = mapNodes (nodeName: node: { + hostname = node.deploy.ssh.host; + + profilesOrder = ["default"]; # profiles priority + profiles.default = { + path = deploy-rs.lib.${node.system}.activate.nixos nixosConfigurations.${node.system}; + + user = node.deploy.user; + sudo = node.deploy.sudo; + interactiveSudo = node.deploy.interactiveSudo; + + fastConnection = false; + + autoRollback = node.deploy.autoRollback; + magicRollback = node.deploy.magicRollback; + activationTimeout = node.deploy.activationTimeout; + confirmTimeout = node.deploy.confirmTimeout; + + remoteBuild = node.deploy.remoteBuild; + sshUser = node.deploy.ssh.user; + sshOpts = + node.deploy.ssh.opts + ++ ( + if builtins.elem "-p" node.deploy.ssh.opts + then [] + else ["-p" (builtins.toString node.deploy.ssh.port)] + ); }; }); From 666117d85bdfee6fbe046565140e4fa412c5514f Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 14 Dec 2025 08:56:27 +1000 Subject: [PATCH 013/338] progress flake.lock --- flake.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/flake.lock b/flake.lock index 709fb53..6daa4e2 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1765596193, - "narHash": "sha256-mBtvOklNgeKgYRCjERNUpFP0ILNH5wlkkuV3tpNxeN4=", + "lastModified": 1765627331, + "narHash": "sha256-EfLYy7ZvkQVHLt+L4NUVEJFuCQf9yQ/nuLZk+gqXMto=", "owner": "emilelcb", "repo": "nib", - "rev": "b7a24b922f54c5ef31a660832d874869dfa0826e", + "rev": "4cf710c1a4628d0317d79bdf08d217280cf90359", "type": "github" }, "original": { @@ -74,11 +74,11 @@ }, "nixpkgs-unstable": { "locked": { - "lastModified": 1765186076, - "narHash": "sha256-hM20uyap1a0M9d344I692r+ik4gTMyj60cQWO+hAYP8=", + "lastModified": 1765472234, + "narHash": "sha256-9VvC20PJPsleGMewwcWYKGzDIyjckEz8uWmT0vCDYK0=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "addf7cf5f383a3101ecfba091b98d0a1263dc9b8", + "rev": "2fbfb1d73d239d2402a8fe03963e37aab15abe8b", "type": "github" }, "original": { From f6d33efea76a478be4ce5f1179f89ff57eb9d0c3 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Sun, 14 Dec 2025 08:56:27 +1000 Subject: [PATCH 014/338] progress flake.lock --- flake.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/flake.lock b/flake.lock index 709fb53..6daa4e2 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1765596193, - "narHash": "sha256-mBtvOklNgeKgYRCjERNUpFP0ILNH5wlkkuV3tpNxeN4=", + "lastModified": 1765627331, + "narHash": "sha256-EfLYy7ZvkQVHLt+L4NUVEJFuCQf9yQ/nuLZk+gqXMto=", "owner": "emilelcb", "repo": "nib", - "rev": "b7a24b922f54c5ef31a660832d874869dfa0826e", + "rev": "4cf710c1a4628d0317d79bdf08d217280cf90359", "type": "github" }, "original": { @@ -74,11 +74,11 @@ }, "nixpkgs-unstable": { "locked": { - "lastModified": 1765186076, - "narHash": "sha256-hM20uyap1a0M9d344I692r+ik4gTMyj60cQWO+hAYP8=", + "lastModified": 1765472234, + "narHash": "sha256-9VvC20PJPsleGMewwcWYKGzDIyjckEz8uWmT0vCDYK0=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "addf7cf5f383a3101ecfba091b98d0a1263dc9b8", + "rev": "2fbfb1d73d239d2402a8fe03963e37aab15abe8b", "type": "github" }, "original": { From a7b5bb87e0b217ca5e32dbd24e36ddf18b7330a5 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 14 Dec 2025 09:12:05 +1000 Subject: [PATCH 015/338] progress flake.lock --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 6daa4e2..fc3998c 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1765627331, - "narHash": "sha256-EfLYy7ZvkQVHLt+L4NUVEJFuCQf9yQ/nuLZk+gqXMto=", + "lastModified": 1765667488, + "narHash": "sha256-ev+8p/+KiAnhDxqsvZikCrcJu9mFpBqItJqHzR9KKp4=", "owner": "emilelcb", "repo": "nib", - "rev": "4cf710c1a4628d0317d79bdf08d217280cf90359", + "rev": "0dd5a38bda82500b126688bc914508cd0339e444", "type": "github" }, "original": { From 4fcf479ff2f38dfe5c1ee6cc8bdcb9009882bec2 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Sun, 14 Dec 2025 09:12:05 +1000 Subject: [PATCH 016/338] progress flake.lock --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 6daa4e2..fc3998c 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1765627331, - "narHash": "sha256-EfLYy7ZvkQVHLt+L4NUVEJFuCQf9yQ/nuLZk+gqXMto=", + "lastModified": 1765667488, + "narHash": "sha256-ev+8p/+KiAnhDxqsvZikCrcJu9mFpBqItJqHzR9KKp4=", "owner": "emilelcb", "repo": "nib", - "rev": "4cf710c1a4628d0317d79bdf08d217280cf90359", + "rev": "0dd5a38bda82500b126688bc914508cd0339e444", "type": "github" }, "original": { From 3d4eec3dd3047c7ba50ed474c3010d6ef0387f2a Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 14 Dec 2025 09:27:19 +1000 Subject: [PATCH 017/338] progress flake.lock --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index fc3998c..841741c 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1765667488, - "narHash": "sha256-ev+8p/+KiAnhDxqsvZikCrcJu9mFpBqItJqHzR9KKp4=", + "lastModified": 1765668418, + "narHash": "sha256-oMpQ8MlWWb+SV1bqGaileJrbR/UMUtS18f0g1L6AMhs=", "owner": "emilelcb", "repo": "nib", - "rev": "0dd5a38bda82500b126688bc914508cd0339e444", + "rev": "027f4d0c620a82386699d9612b470db3b5a49b68", "type": "github" }, "original": { From 20b5f98d5549109d46323495573b1a1a8e49b735 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Sun, 14 Dec 2025 09:27:19 +1000 Subject: [PATCH 018/338] progress flake.lock --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index fc3998c..841741c 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1765667488, - "narHash": "sha256-ev+8p/+KiAnhDxqsvZikCrcJu9mFpBqItJqHzR9KKp4=", + "lastModified": 1765668418, + "narHash": "sha256-oMpQ8MlWWb+SV1bqGaileJrbR/UMUtS18f0g1L6AMhs=", "owner": "emilelcb", "repo": "nib", - "rev": "0dd5a38bda82500b126688bc914508cd0339e444", + "rev": "027f4d0c620a82386699d9612b470db3b5a49b68", "type": "github" }, "original": { From 6842e736504ec4eef585b4e95670fb0e00bfdd13 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 14 Dec 2025 09:29:30 +1000 Subject: [PATCH 019/338] progress flake.lock --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 841741c..4ebebf7 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1765668418, - "narHash": "sha256-oMpQ8MlWWb+SV1bqGaileJrbR/UMUtS18f0g1L6AMhs=", + "lastModified": 1765668556, + "narHash": "sha256-pU6o0WLiF14KmTYhiJactHi5tWxgibvRs9s+S16hOJY=", "owner": "emilelcb", "repo": "nib", - "rev": "027f4d0c620a82386699d9612b470db3b5a49b68", + "rev": "4f09ba4c69f0c4ac6fdb2d45f580f3b0bb38a68b", "type": "github" }, "original": { From 4838eef540ee2a9085a19d47a8a74c257e092fe0 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Sun, 14 Dec 2025 09:29:30 +1000 Subject: [PATCH 020/338] progress flake.lock --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 841741c..4ebebf7 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1765668418, - "narHash": "sha256-oMpQ8MlWWb+SV1bqGaileJrbR/UMUtS18f0g1L6AMhs=", + "lastModified": 1765668556, + "narHash": "sha256-pU6o0WLiF14KmTYhiJactHi5tWxgibvRs9s+S16hOJY=", "owner": "emilelcb", "repo": "nib", - "rev": "027f4d0c620a82386699d9612b470db3b5a49b68", + "rev": "4f09ba4c69f0c4ac6fdb2d45f580f3b0bb38a68b", "type": "github" }, "original": { From 85171f9de6973f8c0fd7471d783073b2eb018400 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 14 Dec 2025 09:35:13 +1000 Subject: [PATCH 021/338] progress flake.lock --- flake.lock | 6 +++--- flake.nix | 5 ----- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/flake.lock b/flake.lock index 4ebebf7..d322b22 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1765668556, - "narHash": "sha256-pU6o0WLiF14KmTYhiJactHi5tWxgibvRs9s+S16hOJY=", + "lastModified": 1765668856, + "narHash": "sha256-msPgyL5wJDfyrOVH8IsyIuWgTpwMkewhZgy7VoQKvzQ=", "owner": "emilelcb", "repo": "nib", - "rev": "4f09ba4c69f0c4ac6fdb2d45f580f3b0bb38a68b", + "rev": "f2972a24bf400cae8d43af4030d939d7c3d6cb05", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 2488ecd..591afd5 100644 --- a/flake.nix +++ b/flake.nix @@ -49,11 +49,6 @@ }) ]; - # checks = self.packages; - # packages = - # forAllSystems (system: pkgs: rec { - # }); - mkNexusConfig = config: let # abstract node instance that stores all default values templateNode = let From f5980ff9c16b5725b4397b4c126a63834c0b6424 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Sun, 14 Dec 2025 09:35:13 +1000 Subject: [PATCH 022/338] progress flake.lock --- flake.lock | 6 +++--- flake.nix | 5 ----- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/flake.lock b/flake.lock index 4ebebf7..d322b22 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1765668556, - "narHash": "sha256-pU6o0WLiF14KmTYhiJactHi5tWxgibvRs9s+S16hOJY=", + "lastModified": 1765668856, + "narHash": "sha256-msPgyL5wJDfyrOVH8IsyIuWgTpwMkewhZgy7VoQKvzQ=", "owner": "emilelcb", "repo": "nib", - "rev": "4f09ba4c69f0c4ac6fdb2d45f580f3b0bb38a68b", + "rev": "f2972a24bf400cae8d43af4030d939d7c3d6cb05", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 2488ecd..591afd5 100644 --- a/flake.nix +++ b/flake.nix @@ -49,11 +49,6 @@ }) ]; - # checks = self.packages; - # packages = - # forAllSystems (system: pkgs: rec { - # }); - mkNexusConfig = config: let # abstract node instance that stores all default values templateNode = let From 42090b8fd9a456fa137d83ac795a576e5aaed49b Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 14 Dec 2025 10:02:39 +1000 Subject: [PATCH 023/338] fix improper usage of mapAttrs --- flake.nix | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 591afd5..6c126ac 100644 --- a/flake.nix +++ b/flake.nix @@ -101,9 +101,11 @@ # TODO: will mergeTypedStruct give nice error messages? or should I use mergeStructErr directly? else nib.parse.mergeTypedStruct (templateNode nodeAttrs.system) nodeAttrs; - mapNodes = f: lib.mapAttrs f (parseNode config.nexus.nodes); + # TODO: mapNodes = f: builtins.mapAttrs (name: nodeAttrs: f name (parseNode name nodeAttrs)) config.nexus.nodes + mapNodes = f: builtins.mapAttrs f (builtins.mapAttrs parseNode config.nexus.nodes); in rec { nixosConfigurations = mapNodes ( + # TODO: _: node: name: node: lib.nixosSystem { system = node.system; @@ -114,6 +116,7 @@ } ); + # TODO: deploy.nodes = mapNodes (_: node: { deploy.nodes = mapNodes (nodeName: node: { hostname = node.deploy.ssh.host; From eaae5a48cee49aa51c8e50bc6718e28ac053c243 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Sun, 14 Dec 2025 10:02:39 +1000 Subject: [PATCH 024/338] fix improper usage of mapAttrs --- flake.nix | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 591afd5..6c126ac 100644 --- a/flake.nix +++ b/flake.nix @@ -101,9 +101,11 @@ # TODO: will mergeTypedStruct give nice error messages? or should I use mergeStructErr directly? else nib.parse.mergeTypedStruct (templateNode nodeAttrs.system) nodeAttrs; - mapNodes = f: lib.mapAttrs f (parseNode config.nexus.nodes); + # TODO: mapNodes = f: builtins.mapAttrs (name: nodeAttrs: f name (parseNode name nodeAttrs)) config.nexus.nodes + mapNodes = f: builtins.mapAttrs f (builtins.mapAttrs parseNode config.nexus.nodes); in rec { nixosConfigurations = mapNodes ( + # TODO: _: node: name: node: lib.nixosSystem { system = node.system; @@ -114,6 +116,7 @@ } ); + # TODO: deploy.nodes = mapNodes (_: node: { deploy.nodes = mapNodes (nodeName: node: { hostname = node.deploy.ssh.host; From 9c074af6cd40ad141a46f32a348508f6eb444794 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 14 Dec 2025 10:07:18 +1000 Subject: [PATCH 025/338] fix specialArgs should be {} not [] --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 6c126ac..b498dfc 100644 --- a/flake.nix +++ b/flake.nix @@ -112,7 +112,7 @@ modules = node.modules; # nix passes these to every single module - specialArgs = [] // node.modules.specialArgs; + specialArgs = {} // node.modules.specialArgs; } ); From 3e1a2f0e2adc606fc6ff38e7615c2a8dbc5685ec Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Sun, 14 Dec 2025 10:07:18 +1000 Subject: [PATCH 026/338] fix specialArgs should be {} not [] --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 6c126ac..b498dfc 100644 --- a/flake.nix +++ b/flake.nix @@ -112,7 +112,7 @@ modules = node.modules; # nix passes these to every single module - specialArgs = [] // node.modules.specialArgs; + specialArgs = {} // node.modules.specialArgs; } ); From 075969f228ef486674750efea4d0823a14ae7d97 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 14 Dec 2025 10:13:25 +1000 Subject: [PATCH 027/338] progress flake.lock --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index d322b22..d438b16 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1765668856, - "narHash": "sha256-msPgyL5wJDfyrOVH8IsyIuWgTpwMkewhZgy7VoQKvzQ=", + "lastModified": 1765671184, + "narHash": "sha256-f65WuXX/nbXvoX8MKBpSJ4T5c1KQGex6h3KdTAT22yM=", "owner": "emilelcb", "repo": "nib", - "rev": "f2972a24bf400cae8d43af4030d939d7c3d6cb05", + "rev": "30ca46a1e1e565e351d1adbb659d32809b97d789", "type": "github" }, "original": { From 6d769a1d258a5b6bc7750d91fad1fba38782ce1d Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Sun, 14 Dec 2025 10:13:25 +1000 Subject: [PATCH 028/338] progress flake.lock --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index d322b22..d438b16 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1765668856, - "narHash": "sha256-msPgyL5wJDfyrOVH8IsyIuWgTpwMkewhZgy7VoQKvzQ=", + "lastModified": 1765671184, + "narHash": "sha256-f65WuXX/nbXvoX8MKBpSJ4T5c1KQGex6h3KdTAT22yM=", "owner": "emilelcb", "repo": "nib", - "rev": "f2972a24bf400cae8d43af4030d939d7c3d6cb05", + "rev": "30ca46a1e1e565e351d1adbb659d32809b97d789", "type": "github" }, "original": { From d7f3dd5648452693f0f8929c98c3b0d7671ced77 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 14 Dec 2025 10:20:51 +1000 Subject: [PATCH 029/338] progress flake.lock --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index d438b16..cf6dbfd 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1765671184, - "narHash": "sha256-f65WuXX/nbXvoX8MKBpSJ4T5c1KQGex6h3KdTAT22yM=", + "lastModified": 1765671633, + "narHash": "sha256-tpJWuTZYba9DOJQY/y1uSWwb2KFmLH6bpufUtSo9GBQ=", "owner": "emilelcb", "repo": "nib", - "rev": "30ca46a1e1e565e351d1adbb659d32809b97d789", + "rev": "dc1f9f0953fbb42fe3cb9d0bfefef478297c61f7", "type": "github" }, "original": { From 01a058ede3b7e70dffc04700deb405e09d4522e2 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Sun, 14 Dec 2025 10:20:51 +1000 Subject: [PATCH 030/338] progress flake.lock --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index d438b16..cf6dbfd 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1765671184, - "narHash": "sha256-f65WuXX/nbXvoX8MKBpSJ4T5c1KQGex6h3KdTAT22yM=", + "lastModified": 1765671633, + "narHash": "sha256-tpJWuTZYba9DOJQY/y1uSWwb2KFmLH6bpufUtSo9GBQ=", "owner": "emilelcb", "repo": "nib", - "rev": "30ca46a1e1e565e351d1adbb659d32809b97d789", + "rev": "dc1f9f0953fbb42fe3cb9d0bfefef478297c61f7", "type": "github" }, "original": { From 52e3931a43c53a703677c212f9b281c3abdc7050 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 14 Dec 2025 10:22:48 +1000 Subject: [PATCH 031/338] progress flake.lock --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index cf6dbfd..525de41 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1765671633, - "narHash": "sha256-tpJWuTZYba9DOJQY/y1uSWwb2KFmLH6bpufUtSo9GBQ=", + "lastModified": 1765671754, + "narHash": "sha256-abWHhCRaQLTai9AUzQkFZ37SlMgf/vqDbuD5amqEC/Y=", "owner": "emilelcb", "repo": "nib", - "rev": "dc1f9f0953fbb42fe3cb9d0bfefef478297c61f7", + "rev": "1045a3f4273c475c5d8852dc5e855437a51d4174", "type": "github" }, "original": { From ae041eacef3b6d57e50470ca03878a226184d461 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Sun, 14 Dec 2025 10:22:48 +1000 Subject: [PATCH 032/338] progress flake.lock --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index cf6dbfd..525de41 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1765671633, - "narHash": "sha256-tpJWuTZYba9DOJQY/y1uSWwb2KFmLH6bpufUtSo9GBQ=", + "lastModified": 1765671754, + "narHash": "sha256-abWHhCRaQLTai9AUzQkFZ37SlMgf/vqDbuD5amqEC/Y=", "owner": "emilelcb", "repo": "nib", - "rev": "dc1f9f0953fbb42fe3cb9d0bfefef478297c61f7", + "rev": "1045a3f4273c475c5d8852dc5e855437a51d4174", "type": "github" }, "original": { From 7b11d1b7fa011abaf85b594ba921800b4c9dec21 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 14 Dec 2025 10:34:53 +1000 Subject: [PATCH 033/338] improve templateNode missing message --- flake.nix | 53 ++++++++++++++++++++++++++--------------------------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/flake.nix b/flake.nix index b498dfc..19cc0d9 100644 --- a/flake.nix +++ b/flake.nix @@ -51,43 +51,42 @@ mkNexusConfig = config: let # abstract node instance that stores all default values - templateNode = let + templateNode = name: system: let missing = msg: path: builtins.abort '' Each Cerulean Nexus node is required to specify ${msg}! - Ensure `cerulean.nexus.nodes.$${NODE}.${path}` exists under your call to `cerulean.mkNexus`. + Ensure `cerulean.nexus.nodes.${name}.${path}` exists under your call to `cerulean.mkNexus`. ''; - in - system: { - system = missing "its system type" "system"; # intentionally left missing!! (to raise errors) - modules = missing "its required modules" "modules"; - specialArgs = { - inherit inputs; - pkgs = sys.pkgsFor system; - upkgs = sys.upkgsFor system; - }; + in { + system = missing "its system type" "system"; # intentionally left missing!! (to raise errors) + modules = missing "its required modules" "modules"; + specialArgs = { + inherit inputs; + pkgs = sys.pkgsFor system; + upkgs = sys.upkgsFor system; + }; - deploy = { - user = "root"; - sudo = "sudo -u"; - interactiveSudo = false; + deploy = { + user = "root"; + sudo = "sudo -u"; + interactiveSudo = false; - remoteBuild = false; # prefer local builds for remote deploys + remoteBuild = false; # prefer local builds for remote deploys - autoRollback = true; # reactivate previous profile if activation fails - magicRollback = true; + autoRollback = true; # reactivate previous profile if activation fails + magicRollback = true; - activationTimeout = 500; # timeout in seconds for profile activation - confirmTimeout = 30; # timeout in seconds for profile activation confirmation + activationTimeout = 500; # timeout in seconds for profile activation + confirmTimeout = 30; # timeout in seconds for profile activation confirmation - ssh = { - host = missing "an SSH hostname (domain name or ip address) for deployment" "deploy.ssh.host"; - user = missing "an SSH username for deployment" "deploy.ssh.user"; - port = 22; - opts = []; - }; + ssh = { + host = missing "an SSH hostname (domain name or ip address) for deployment" "deploy.ssh.host"; + user = missing "an SSH username for deployment" "deploy.ssh.user"; + port = 22; + opts = []; }; }; + }; parseNode = name: nodeAttrs: if !(builtins.isAttrs nodeAttrs) @@ -99,7 +98,7 @@ '' # TODO: nodeAttrs.system won't display any nice error messages!! # TODO: will mergeTypedStruct give nice error messages? or should I use mergeStructErr directly? - else nib.parse.mergeTypedStruct (templateNode nodeAttrs.system) nodeAttrs; + else nib.parse.mergeTypedStruct (templateNode name nodeAttrs.system) nodeAttrs; # TODO: mapNodes = f: builtins.mapAttrs (name: nodeAttrs: f name (parseNode name nodeAttrs)) config.nexus.nodes mapNodes = f: builtins.mapAttrs f (builtins.mapAttrs parseNode config.nexus.nodes); From 31cb6bc1ec71b575b7de59145c0a5562c40989c7 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Sun, 14 Dec 2025 10:34:53 +1000 Subject: [PATCH 034/338] improve templateNode missing message --- flake.nix | 53 ++++++++++++++++++++++++++--------------------------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/flake.nix b/flake.nix index b498dfc..19cc0d9 100644 --- a/flake.nix +++ b/flake.nix @@ -51,43 +51,42 @@ mkNexusConfig = config: let # abstract node instance that stores all default values - templateNode = let + templateNode = name: system: let missing = msg: path: builtins.abort '' Each Cerulean Nexus node is required to specify ${msg}! - Ensure `cerulean.nexus.nodes.$${NODE}.${path}` exists under your call to `cerulean.mkNexus`. + Ensure `cerulean.nexus.nodes.${name}.${path}` exists under your call to `cerulean.mkNexus`. ''; - in - system: { - system = missing "its system type" "system"; # intentionally left missing!! (to raise errors) - modules = missing "its required modules" "modules"; - specialArgs = { - inherit inputs; - pkgs = sys.pkgsFor system; - upkgs = sys.upkgsFor system; - }; + in { + system = missing "its system type" "system"; # intentionally left missing!! (to raise errors) + modules = missing "its required modules" "modules"; + specialArgs = { + inherit inputs; + pkgs = sys.pkgsFor system; + upkgs = sys.upkgsFor system; + }; - deploy = { - user = "root"; - sudo = "sudo -u"; - interactiveSudo = false; + deploy = { + user = "root"; + sudo = "sudo -u"; + interactiveSudo = false; - remoteBuild = false; # prefer local builds for remote deploys + remoteBuild = false; # prefer local builds for remote deploys - autoRollback = true; # reactivate previous profile if activation fails - magicRollback = true; + autoRollback = true; # reactivate previous profile if activation fails + magicRollback = true; - activationTimeout = 500; # timeout in seconds for profile activation - confirmTimeout = 30; # timeout in seconds for profile activation confirmation + activationTimeout = 500; # timeout in seconds for profile activation + confirmTimeout = 30; # timeout in seconds for profile activation confirmation - ssh = { - host = missing "an SSH hostname (domain name or ip address) for deployment" "deploy.ssh.host"; - user = missing "an SSH username for deployment" "deploy.ssh.user"; - port = 22; - opts = []; - }; + ssh = { + host = missing "an SSH hostname (domain name or ip address) for deployment" "deploy.ssh.host"; + user = missing "an SSH username for deployment" "deploy.ssh.user"; + port = 22; + opts = []; }; }; + }; parseNode = name: nodeAttrs: if !(builtins.isAttrs nodeAttrs) @@ -99,7 +98,7 @@ '' # TODO: nodeAttrs.system won't display any nice error messages!! # TODO: will mergeTypedStruct give nice error messages? or should I use mergeStructErr directly? - else nib.parse.mergeTypedStruct (templateNode nodeAttrs.system) nodeAttrs; + else nib.parse.mergeTypedStruct (templateNode name nodeAttrs.system) nodeAttrs; # TODO: mapNodes = f: builtins.mapAttrs (name: nodeAttrs: f name (parseNode name nodeAttrs)) config.nexus.nodes mapNodes = f: builtins.mapAttrs f (builtins.mapAttrs parseNode config.nexus.nodes); From 8b50a98ffb7a44c8fc19340b1cb59c3088b7cb58 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 14 Dec 2025 10:55:36 +1000 Subject: [PATCH 035/338] use mergeStruct not mergeTypedStruct --- flake.nix | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/flake.nix b/flake.nix index 19cc0d9..c60e577 100644 --- a/flake.nix +++ b/flake.nix @@ -98,14 +98,13 @@ '' # TODO: nodeAttrs.system won't display any nice error messages!! # TODO: will mergeTypedStruct give nice error messages? or should I use mergeStructErr directly? - else nib.parse.mergeTypedStruct (templateNode name nodeAttrs.system) nodeAttrs; + else nib.parse.mergeStruct (templateNode name nodeAttrs.system) nodeAttrs; # TODO: mapNodes = f: builtins.mapAttrs (name: nodeAttrs: f name (parseNode name nodeAttrs)) config.nexus.nodes mapNodes = f: builtins.mapAttrs f (builtins.mapAttrs parseNode config.nexus.nodes); in rec { nixosConfigurations = mapNodes ( - # TODO: _: node: - name: node: + _: node: lib.nixosSystem { system = node.system; modules = node.modules; @@ -115,8 +114,7 @@ } ); - # TODO: deploy.nodes = mapNodes (_: node: { - deploy.nodes = mapNodes (nodeName: node: { + deploy.nodes = mapNodes (_: node: { hostname = node.deploy.ssh.host; profilesOrder = ["default"]; # profiles priority From 5ca11315fd2fc7c6d025ec472a7b860bd6f14956 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Sun, 14 Dec 2025 10:55:36 +1000 Subject: [PATCH 036/338] use mergeStruct not mergeTypedStruct --- flake.nix | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/flake.nix b/flake.nix index 19cc0d9..c60e577 100644 --- a/flake.nix +++ b/flake.nix @@ -98,14 +98,13 @@ '' # TODO: nodeAttrs.system won't display any nice error messages!! # TODO: will mergeTypedStruct give nice error messages? or should I use mergeStructErr directly? - else nib.parse.mergeTypedStruct (templateNode name nodeAttrs.system) nodeAttrs; + else nib.parse.mergeStruct (templateNode name nodeAttrs.system) nodeAttrs; # TODO: mapNodes = f: builtins.mapAttrs (name: nodeAttrs: f name (parseNode name nodeAttrs)) config.nexus.nodes mapNodes = f: builtins.mapAttrs f (builtins.mapAttrs parseNode config.nexus.nodes); in rec { nixosConfigurations = mapNodes ( - # TODO: _: node: - name: node: + _: node: lib.nixosSystem { system = node.system; modules = node.modules; @@ -115,8 +114,7 @@ } ); - # TODO: deploy.nodes = mapNodes (_: node: { - deploy.nodes = mapNodes (nodeName: node: { + deploy.nodes = mapNodes (_: node: { hostname = node.deploy.ssh.host; profilesOrder = ["default"]; # profiles priority From 36f164f171bbfea77ecf6daa650aff1356ec0ff9 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 14 Dec 2025 10:55:42 +1000 Subject: [PATCH 037/338] progress flake.lock --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 525de41..d52b526 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1765671754, - "narHash": "sha256-abWHhCRaQLTai9AUzQkFZ37SlMgf/vqDbuD5amqEC/Y=", + "lastModified": 1765673695, + "narHash": "sha256-/YOJuSKP1hjG54SJ1LqlsNE7glObKJ6NN84WEGRCS9c=", "owner": "emilelcb", "repo": "nib", - "rev": "1045a3f4273c475c5d8852dc5e855437a51d4174", + "rev": "d0c2429748254dc6475aafaf91e631b7033281d6", "type": "github" }, "original": { From 3978614e871ead72a4ac9411b31d0dcf055a878d Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Sun, 14 Dec 2025 10:55:42 +1000 Subject: [PATCH 038/338] progress flake.lock --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 525de41..d52b526 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1765671754, - "narHash": "sha256-abWHhCRaQLTai9AUzQkFZ37SlMgf/vqDbuD5amqEC/Y=", + "lastModified": 1765673695, + "narHash": "sha256-/YOJuSKP1hjG54SJ1LqlsNE7glObKJ6NN84WEGRCS9c=", "owner": "emilelcb", "repo": "nib", - "rev": "1045a3f4273c475c5d8852dc5e855437a51d4174", + "rev": "d0c2429748254dc6475aafaf91e631b7033281d6", "type": "github" }, "original": { From 029f849a4cee893f7cd3e79268af6d00ab34bc44 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 14 Dec 2025 11:18:29 +1000 Subject: [PATCH 039/338] fix mergeStruct result never unwrapped --- flake.nix | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index c60e577..1d033b4 100644 --- a/flake.nix +++ b/flake.nix @@ -98,7 +98,16 @@ '' # TODO: nodeAttrs.system won't display any nice error messages!! # TODO: will mergeTypedStruct give nice error messages? or should I use mergeStructErr directly? - else nib.parse.mergeStruct (templateNode name nodeAttrs.system) nodeAttrs; + else let + templateAttrs = templateNode name nodeAttrs.system; + r = nib.parse.mergeStruct templateAttrs nodeAttrs; + in + nib.result.unwrap (_: + builtins.abort '' + Cerulean failed to parse `cerulean.nexus.nodes.${name}`! + mergeStruct should never return `result.Err`... How are you here?!? + '') + r; # TODO: mapNodes = f: builtins.mapAttrs (name: nodeAttrs: f name (parseNode name nodeAttrs)) config.nexus.nodes mapNodes = f: builtins.mapAttrs f (builtins.mapAttrs parseNode config.nexus.nodes); From 69bc00805a364796c4a8a59c2b8d742b1e249196 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Sun, 14 Dec 2025 11:18:29 +1000 Subject: [PATCH 040/338] fix mergeStruct result never unwrapped --- flake.nix | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index c60e577..1d033b4 100644 --- a/flake.nix +++ b/flake.nix @@ -98,7 +98,16 @@ '' # TODO: nodeAttrs.system won't display any nice error messages!! # TODO: will mergeTypedStruct give nice error messages? or should I use mergeStructErr directly? - else nib.parse.mergeStruct (templateNode name nodeAttrs.system) nodeAttrs; + else let + templateAttrs = templateNode name nodeAttrs.system; + r = nib.parse.mergeStruct templateAttrs nodeAttrs; + in + nib.result.unwrap (_: + builtins.abort '' + Cerulean failed to parse `cerulean.nexus.nodes.${name}`! + mergeStruct should never return `result.Err`... How are you here?!? + '') + r; # TODO: mapNodes = f: builtins.mapAttrs (name: nodeAttrs: f name (parseNode name nodeAttrs)) config.nexus.nodes mapNodes = f: builtins.mapAttrs f (builtins.mapAttrs parseNode config.nexus.nodes); From 51af192b38eaad56c78fe74ad62e3296dc9e6499 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 14 Dec 2025 12:43:04 +1000 Subject: [PATCH 041/338] progress flake.lock (nib.result.unwrap -> nib.types.unwrapRes) --- flake.lock | 6 +++--- flake.nix | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/flake.lock b/flake.lock index d52b526..bfac313 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1765673695, - "narHash": "sha256-/YOJuSKP1hjG54SJ1LqlsNE7glObKJ6NN84WEGRCS9c=", + "lastModified": 1765680112, + "narHash": "sha256-BbZWXQJ8EX42HWCbQcJZHa4t8AbLpskxzWImbpyRqdk=", "owner": "emilelcb", "repo": "nib", - "rev": "d0c2429748254dc6475aafaf91e631b7033281d6", + "rev": "29b6643c0edecee1a22b8d2776e642acfe99a4db", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 1d033b4..ddb8562 100644 --- a/flake.nix +++ b/flake.nix @@ -102,7 +102,7 @@ templateAttrs = templateNode name nodeAttrs.system; r = nib.parse.mergeStruct templateAttrs nodeAttrs; in - nib.result.unwrap (_: + nib.result.unwrapRes (_: builtins.abort '' Cerulean failed to parse `cerulean.nexus.nodes.${name}`! mergeStruct should never return `result.Err`... How are you here?!? From 3b0134b511b7418e0ed311b1036a4f18616a793d Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Sun, 14 Dec 2025 12:43:04 +1000 Subject: [PATCH 042/338] progress flake.lock (nib.result.unwrap -> nib.types.unwrapRes) --- flake.lock | 6 +++--- flake.nix | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/flake.lock b/flake.lock index d52b526..bfac313 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1765673695, - "narHash": "sha256-/YOJuSKP1hjG54SJ1LqlsNE7glObKJ6NN84WEGRCS9c=", + "lastModified": 1765680112, + "narHash": "sha256-BbZWXQJ8EX42HWCbQcJZHa4t8AbLpskxzWImbpyRqdk=", "owner": "emilelcb", "repo": "nib", - "rev": "d0c2429748254dc6475aafaf91e631b7033281d6", + "rev": "29b6643c0edecee1a22b8d2776e642acfe99a4db", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 1d033b4..ddb8562 100644 --- a/flake.nix +++ b/flake.nix @@ -102,7 +102,7 @@ templateAttrs = templateNode name nodeAttrs.system; r = nib.parse.mergeStruct templateAttrs nodeAttrs; in - nib.result.unwrap (_: + nib.result.unwrapRes (_: builtins.abort '' Cerulean failed to parse `cerulean.nexus.nodes.${name}`! mergeStruct should never return `result.Err`... How are you here?!? From 6edd28566cd0ff82766d3b287fc96169033fedf8 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 14 Dec 2025 12:48:58 +1000 Subject: [PATCH 043/338] progress flake.lock --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index bfac313..296a45a 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1765680112, - "narHash": "sha256-BbZWXQJ8EX42HWCbQcJZHa4t8AbLpskxzWImbpyRqdk=", + "lastModified": 1765680507, + "narHash": "sha256-Hnzh65xjkivnQLQAkVK4yfZbDOlf4yM/is83adjSMLs=", "owner": "emilelcb", "repo": "nib", - "rev": "29b6643c0edecee1a22b8d2776e642acfe99a4db", + "rev": "be8f4005ce19e8bb7a8218d4b7d82445c7f3901b", "type": "github" }, "original": { From d1dee996bb1c8f6824287a858e393205a59b8c1c Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Sun, 14 Dec 2025 12:48:58 +1000 Subject: [PATCH 044/338] progress flake.lock --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index bfac313..296a45a 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1765680112, - "narHash": "sha256-BbZWXQJ8EX42HWCbQcJZHa4t8AbLpskxzWImbpyRqdk=", + "lastModified": 1765680507, + "narHash": "sha256-Hnzh65xjkivnQLQAkVK4yfZbDOlf4yM/is83adjSMLs=", "owner": "emilelcb", "repo": "nib", - "rev": "29b6643c0edecee1a22b8d2776e642acfe99a4db", + "rev": "be8f4005ce19e8bb7a8218d4b7d82445c7f3901b", "type": "github" }, "original": { From 5723fa803f909633fa5cb6e166e12215aeebfbae Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 14 Dec 2025 12:51:05 +1000 Subject: [PATCH 045/338] progress flake.lock --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 296a45a..c20eb28 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1765680507, - "narHash": "sha256-Hnzh65xjkivnQLQAkVK4yfZbDOlf4yM/is83adjSMLs=", + "lastModified": 1765680654, + "narHash": "sha256-WhOceuTSceRp6BiWfsUciTsFFIhXj8tXMcEqRz/n/lg=", "owner": "emilelcb", "repo": "nib", - "rev": "be8f4005ce19e8bb7a8218d4b7d82445c7f3901b", + "rev": "fd181864589e0352eb1c35dc965131706c9c440c", "type": "github" }, "original": { From bd49bf36bf46d5cc03580300d01be329188adcbe Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Sun, 14 Dec 2025 12:51:05 +1000 Subject: [PATCH 046/338] progress flake.lock --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 296a45a..c20eb28 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1765680507, - "narHash": "sha256-Hnzh65xjkivnQLQAkVK4yfZbDOlf4yM/is83adjSMLs=", + "lastModified": 1765680654, + "narHash": "sha256-WhOceuTSceRp6BiWfsUciTsFFIhXj8tXMcEqRz/n/lg=", "owner": "emilelcb", "repo": "nib", - "rev": "be8f4005ce19e8bb7a8218d4b7d82445c7f3901b", + "rev": "fd181864589e0352eb1c35dc965131706c9c440c", "type": "github" }, "original": { From 4e09dfb81cab19463356f04512b93dbbc062faca Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 14 Dec 2025 12:53:07 +1000 Subject: [PATCH 047/338] fix: nib.result undefined --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index ddb8562..76af524 100644 --- a/flake.nix +++ b/flake.nix @@ -102,7 +102,7 @@ templateAttrs = templateNode name nodeAttrs.system; r = nib.parse.mergeStruct templateAttrs nodeAttrs; in - nib.result.unwrapRes (_: + nib.types.unwrapRes (_: builtins.abort '' Cerulean failed to parse `cerulean.nexus.nodes.${name}`! mergeStruct should never return `result.Err`... How are you here?!? From d1e17d1ea0496254620a4699962e732b566fe22d Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Sun, 14 Dec 2025 12:53:07 +1000 Subject: [PATCH 048/338] fix: nib.result undefined --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index ddb8562..76af524 100644 --- a/flake.nix +++ b/flake.nix @@ -102,7 +102,7 @@ templateAttrs = templateNode name nodeAttrs.system; r = nib.parse.mergeStruct templateAttrs nodeAttrs; in - nib.result.unwrapRes (_: + nib.types.unwrapRes (_: builtins.abort '' Cerulean failed to parse `cerulean.nexus.nodes.${name}`! mergeStruct should never return `result.Err`... How are you here?!? From 86fe8f9a49ad7f24bc7c5908e294dff201b21755 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 14 Dec 2025 12:59:43 +1000 Subject: [PATCH 049/338] progress flake.lock --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index c20eb28..873c858 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1765680654, - "narHash": "sha256-WhOceuTSceRp6BiWfsUciTsFFIhXj8tXMcEqRz/n/lg=", + "lastModified": 1765681171, + "narHash": "sha256-IP1MMCVj/U9j5gJwyTqrCOw+ZzOZSVxZecpmw2UOsfg=", "owner": "emilelcb", "repo": "nib", - "rev": "fd181864589e0352eb1c35dc965131706c9c440c", + "rev": "8a8469962403d609a0f05f2c78ff17e06cb01e1c", "type": "github" }, "original": { From fe05a1c1c1f852a7e376ae7e2cefb7ab82268465 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Sun, 14 Dec 2025 12:59:43 +1000 Subject: [PATCH 050/338] progress flake.lock --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index c20eb28..873c858 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1765680654, - "narHash": "sha256-WhOceuTSceRp6BiWfsUciTsFFIhXj8tXMcEqRz/n/lg=", + "lastModified": 1765681171, + "narHash": "sha256-IP1MMCVj/U9j5gJwyTqrCOw+ZzOZSVxZecpmw2UOsfg=", "owner": "emilelcb", "repo": "nib", - "rev": "fd181864589e0352eb1c35dc965131706c9c440c", + "rev": "8a8469962403d609a0f05f2c78ff17e06cb01e1c", "type": "github" }, "original": { From d2de01644a3714c5c5aeb6538adcee44bdb2bb07 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 14 Dec 2025 13:25:00 +1000 Subject: [PATCH 051/338] use `with builtins` --- flake.lock | 6 +- flake.nix | 267 +++++++++++++++++++++++++++-------------------------- 2 files changed, 137 insertions(+), 136 deletions(-) diff --git a/flake.lock b/flake.lock index 873c858..27902dd 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1765681171, - "narHash": "sha256-IP1MMCVj/U9j5gJwyTqrCOw+ZzOZSVxZecpmw2UOsfg=", + "lastModified": 1765682506, + "narHash": "sha256-7ElrlSed9FGQHgcHMwnp7KbBzVmGKJ1m41oorhihxYg=", "owner": "emilelcb", "repo": "nib", - "rev": "8a8469962403d609a0f05f2c78ff17e06cb01e1c", + "rev": "223bb548ec08f1f7274c439b2354f83fdc7cea1c", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 76af524..da08945 100644 --- a/flake.nix +++ b/flake.nix @@ -22,143 +22,144 @@ nib, deploy-rs, ... - } @ inputs: let - lib = nixpkgs.lib; + } @ inputs: + with builtins; let + lib = nixpkgs.lib; - sys = with nib; - mkUSys { - pkgs = withPkgs nixpkgs { - config.allowUnfree = false; - overlays = builtins.attrValues self.overlays; - }; - upkgs = withPkgs nixpkgs-unstable { - config.allowUnfree = false; - }; - }; - in rec { - overlays = [ - # deploy-rs is built from the flake input, not from nixpkgs! - # To take advantage of the nixpkgs binary cache, - # the deploy-rs package can be overwritten: - deploy-rs.overlays.default - (self: super: { - deploy-rs = { - inherit (super) deploy-rs; - lib = super.deploy-rs.lib; - }; - }) - ]; - - mkNexusConfig = config: let - # abstract node instance that stores all default values - templateNode = name: system: let - missing = msg: path: - builtins.abort '' - Each Cerulean Nexus node is required to specify ${msg}! - Ensure `cerulean.nexus.nodes.${name}.${path}` exists under your call to `cerulean.mkNexus`. - ''; - in { - system = missing "its system type" "system"; # intentionally left missing!! (to raise errors) - modules = missing "its required modules" "modules"; - specialArgs = { - inherit inputs; - pkgs = sys.pkgsFor system; - upkgs = sys.upkgsFor system; - }; - - deploy = { - user = "root"; - sudo = "sudo -u"; - interactiveSudo = false; - - remoteBuild = false; # prefer local builds for remote deploys - - autoRollback = true; # reactivate previous profile if activation fails - magicRollback = true; - - activationTimeout = 500; # timeout in seconds for profile activation - confirmTimeout = 30; # timeout in seconds for profile activation confirmation - - ssh = { - host = missing "an SSH hostname (domain name or ip address) for deployment" "deploy.ssh.host"; - user = missing "an SSH username for deployment" "deploy.ssh.user"; - port = 22; - opts = []; + sys = with nib; + mkUSys { + pkgs = withPkgs nixpkgs { + config.allowUnfree = false; + overlays = attrValues self.overlays; + }; + upkgs = withPkgs nixpkgs-unstable { + config.allowUnfree = false; }; }; + in rec { + overlays = [ + # deploy-rs is built from the flake input, not from nixpkgs! + # To take advantage of the nixpkgs binary cache, + # the deploy-rs package can be overwritten: + deploy-rs.overlays.default + (self: super: { + deploy-rs = { + inherit (super) deploy-rs; + lib = super.deploy-rs.lib; + }; + }) + ]; + + mkNexusConfig = config: let + # abstract node instance that stores all default values + templateNode = name: system: let + missing = msg: path: + abort '' + Each Cerulean Nexus node is required to specify ${msg}! + Ensure `cerulean.nexus.nodes.${name}.${path}` exists under your call to `cerulean.mkNexus`. + ''; + in { + system = missing "its system type" "system"; # intentionally left missing!! (to raise errors) + modules = missing "its required modules" "modules"; + specialArgs = { + inherit inputs; + pkgs = sys.pkgsFor system; + upkgs = sys.upkgsFor system; + }; + + deploy = { + user = "root"; + sudo = "sudo -u"; + interactiveSudo = false; + + remoteBuild = false; # prefer local builds for remote deploys + + autoRollback = true; # reactivate previous profile if activation fails + magicRollback = true; + + activationTimeout = 500; # timeout in seconds for profile activation + confirmTimeout = 30; # timeout in seconds for profile activation confirmation + + ssh = { + host = missing "an SSH hostname (domain name or ip address) for deployment" "deploy.ssh.host"; + user = missing "an SSH username for deployment" "deploy.ssh.user"; + port = 22; + opts = []; + }; + }; + }; + + parseNode = name: nodeAttrs: + if !(isAttrs nodeAttrs) + then + # fail if node is not an attribute set + abort '' + Cerulean Nexus nodes must be provided as an attribute set, got "${typeOf nodeAttrs}" instead! + Ensure all `cerulean.nexus.nodes.${name}` declarations are attribute sets under your call to `cerulean.mkNexus`. + '' + # TODO: nodeAttrs.system won't display any nice error messages!! + # TODO: will mergeTypedStruct give nice error messages? or should I use mergeStructErr directly? + else let + templateAttrs = templateNode name nodeAttrs.system; + r = nib.parse.mergeStruct templateAttrs nodeAttrs; + in + nib.types.unwrapRes (_: + abort '' + Cerulean failed to parse `cerulean.nexus.nodes.${name}`! + mergeStruct should never return `result.Err`... How are you here?!? + '') + r; + + # TODO: mapNodes = f: mapAttrs (name: nodeAttrs: f name (parseNode name nodeAttrs)) config.nexus.nodes + mapNodes = f: mapAttrs f (mapAttrs parseNode config.nexus.nodes); + in rec { + nixosConfigurations = mapNodes ( + _: node: + lib.nixosSystem { + system = node.system; + modules = node.modules; + + # nix passes these to every single module + specialArgs = {} // node.modules.specialArgs; + } + ); + + deploy.nodes = mapNodes (_: node: { + hostname = node.deploy.ssh.host; + + profilesOrder = ["default"]; # profiles priority + profiles.default = { + path = deploy-rs.lib.${node.system}.activate.nixos nixosConfigurations.${node.system}; + + user = node.deploy.user; + sudo = node.deploy.sudo; + interactiveSudo = node.deploy.interactiveSudo; + + fastConnection = false; + + autoRollback = node.deploy.autoRollback; + magicRollback = node.deploy.magicRollback; + activationTimeout = node.deploy.activationTimeout; + confirmTimeout = node.deploy.confirmTimeout; + + remoteBuild = node.deploy.remoteBuild; + sshUser = node.deploy.ssh.user; + sshOpts = + node.deploy.ssh.opts + ++ ( + if elem "-p" node.deploy.ssh.opts + then [] + else ["-p" (toString node.deploy.ssh.port)] + ); + }; + }); + + checks = mapAttrs (system: deployLib: deployLib.deployChecks deploy) deploy-rs.lib; }; - parseNode = name: nodeAttrs: - if !(builtins.isAttrs nodeAttrs) - then - # fail if node is not an attribute set - builtins.abort '' - Cerulean Nexus nodes must be provided as an attribute set, got "${builtins.typeOf nodeAttrs}" instead! - Ensure all `cerulean.nexus.nodes.${name}` declarations are attribute sets under your call to `cerulean.mkNexus`. - '' - # TODO: nodeAttrs.system won't display any nice error messages!! - # TODO: will mergeTypedStruct give nice error messages? or should I use mergeStructErr directly? - else let - templateAttrs = templateNode name nodeAttrs.system; - r = nib.parse.mergeStruct templateAttrs nodeAttrs; - in - nib.types.unwrapRes (_: - builtins.abort '' - Cerulean failed to parse `cerulean.nexus.nodes.${name}`! - mergeStruct should never return `result.Err`... How are you here?!? - '') - r; - - # TODO: mapNodes = f: builtins.mapAttrs (name: nodeAttrs: f name (parseNode name nodeAttrs)) config.nexus.nodes - mapNodes = f: builtins.mapAttrs f (builtins.mapAttrs parseNode config.nexus.nodes); - in rec { - nixosConfigurations = mapNodes ( - _: node: - lib.nixosSystem { - system = node.system; - modules = node.modules; - - # nix passes these to every single module - specialArgs = {} // node.modules.specialArgs; - } - ); - - deploy.nodes = mapNodes (_: node: { - hostname = node.deploy.ssh.host; - - profilesOrder = ["default"]; # profiles priority - profiles.default = { - path = deploy-rs.lib.${node.system}.activate.nixos nixosConfigurations.${node.system}; - - user = node.deploy.user; - sudo = node.deploy.sudo; - interactiveSudo = node.deploy.interactiveSudo; - - fastConnection = false; - - autoRollback = node.deploy.autoRollback; - magicRollback = node.deploy.magicRollback; - activationTimeout = node.deploy.activationTimeout; - confirmTimeout = node.deploy.confirmTimeout; - - remoteBuild = node.deploy.remoteBuild; - sshUser = node.deploy.ssh.user; - sshOpts = - node.deploy.ssh.opts - ++ ( - if builtins.elem "-p" node.deploy.ssh.opts - then [] - else ["-p" (builtins.toString node.deploy.ssh.port)] - ); - }; - }); - - checks = builtins.mapAttrs (system: deployLib: deployLib.deployChecks deploy) deploy-rs.lib; + mkNexus = outputs: let + config = outputs.cerulean; + in + (mkNexusConfig config) // (removeAttrs outputs ["cerulean"]); }; - - mkNexus = outputs: let - config = outputs.cerulean; - in - (mkNexusConfig config) // (builtins.removeAttrs outputs ["cerulean"]); - }; } From 1efbda0f6bb89193ecf174c14ca1da1d16972141 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Sun, 14 Dec 2025 13:25:00 +1000 Subject: [PATCH 052/338] use `with builtins` --- flake.lock | 6 +- flake.nix | 267 +++++++++++++++++++++++++++-------------------------- 2 files changed, 137 insertions(+), 136 deletions(-) diff --git a/flake.lock b/flake.lock index 873c858..27902dd 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1765681171, - "narHash": "sha256-IP1MMCVj/U9j5gJwyTqrCOw+ZzOZSVxZecpmw2UOsfg=", + "lastModified": 1765682506, + "narHash": "sha256-7ElrlSed9FGQHgcHMwnp7KbBzVmGKJ1m41oorhihxYg=", "owner": "emilelcb", "repo": "nib", - "rev": "8a8469962403d609a0f05f2c78ff17e06cb01e1c", + "rev": "223bb548ec08f1f7274c439b2354f83fdc7cea1c", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 76af524..da08945 100644 --- a/flake.nix +++ b/flake.nix @@ -22,143 +22,144 @@ nib, deploy-rs, ... - } @ inputs: let - lib = nixpkgs.lib; + } @ inputs: + with builtins; let + lib = nixpkgs.lib; - sys = with nib; - mkUSys { - pkgs = withPkgs nixpkgs { - config.allowUnfree = false; - overlays = builtins.attrValues self.overlays; - }; - upkgs = withPkgs nixpkgs-unstable { - config.allowUnfree = false; - }; - }; - in rec { - overlays = [ - # deploy-rs is built from the flake input, not from nixpkgs! - # To take advantage of the nixpkgs binary cache, - # the deploy-rs package can be overwritten: - deploy-rs.overlays.default - (self: super: { - deploy-rs = { - inherit (super) deploy-rs; - lib = super.deploy-rs.lib; - }; - }) - ]; - - mkNexusConfig = config: let - # abstract node instance that stores all default values - templateNode = name: system: let - missing = msg: path: - builtins.abort '' - Each Cerulean Nexus node is required to specify ${msg}! - Ensure `cerulean.nexus.nodes.${name}.${path}` exists under your call to `cerulean.mkNexus`. - ''; - in { - system = missing "its system type" "system"; # intentionally left missing!! (to raise errors) - modules = missing "its required modules" "modules"; - specialArgs = { - inherit inputs; - pkgs = sys.pkgsFor system; - upkgs = sys.upkgsFor system; - }; - - deploy = { - user = "root"; - sudo = "sudo -u"; - interactiveSudo = false; - - remoteBuild = false; # prefer local builds for remote deploys - - autoRollback = true; # reactivate previous profile if activation fails - magicRollback = true; - - activationTimeout = 500; # timeout in seconds for profile activation - confirmTimeout = 30; # timeout in seconds for profile activation confirmation - - ssh = { - host = missing "an SSH hostname (domain name or ip address) for deployment" "deploy.ssh.host"; - user = missing "an SSH username for deployment" "deploy.ssh.user"; - port = 22; - opts = []; + sys = with nib; + mkUSys { + pkgs = withPkgs nixpkgs { + config.allowUnfree = false; + overlays = attrValues self.overlays; + }; + upkgs = withPkgs nixpkgs-unstable { + config.allowUnfree = false; }; }; + in rec { + overlays = [ + # deploy-rs is built from the flake input, not from nixpkgs! + # To take advantage of the nixpkgs binary cache, + # the deploy-rs package can be overwritten: + deploy-rs.overlays.default + (self: super: { + deploy-rs = { + inherit (super) deploy-rs; + lib = super.deploy-rs.lib; + }; + }) + ]; + + mkNexusConfig = config: let + # abstract node instance that stores all default values + templateNode = name: system: let + missing = msg: path: + abort '' + Each Cerulean Nexus node is required to specify ${msg}! + Ensure `cerulean.nexus.nodes.${name}.${path}` exists under your call to `cerulean.mkNexus`. + ''; + in { + system = missing "its system type" "system"; # intentionally left missing!! (to raise errors) + modules = missing "its required modules" "modules"; + specialArgs = { + inherit inputs; + pkgs = sys.pkgsFor system; + upkgs = sys.upkgsFor system; + }; + + deploy = { + user = "root"; + sudo = "sudo -u"; + interactiveSudo = false; + + remoteBuild = false; # prefer local builds for remote deploys + + autoRollback = true; # reactivate previous profile if activation fails + magicRollback = true; + + activationTimeout = 500; # timeout in seconds for profile activation + confirmTimeout = 30; # timeout in seconds for profile activation confirmation + + ssh = { + host = missing "an SSH hostname (domain name or ip address) for deployment" "deploy.ssh.host"; + user = missing "an SSH username for deployment" "deploy.ssh.user"; + port = 22; + opts = []; + }; + }; + }; + + parseNode = name: nodeAttrs: + if !(isAttrs nodeAttrs) + then + # fail if node is not an attribute set + abort '' + Cerulean Nexus nodes must be provided as an attribute set, got "${typeOf nodeAttrs}" instead! + Ensure all `cerulean.nexus.nodes.${name}` declarations are attribute sets under your call to `cerulean.mkNexus`. + '' + # TODO: nodeAttrs.system won't display any nice error messages!! + # TODO: will mergeTypedStruct give nice error messages? or should I use mergeStructErr directly? + else let + templateAttrs = templateNode name nodeAttrs.system; + r = nib.parse.mergeStruct templateAttrs nodeAttrs; + in + nib.types.unwrapRes (_: + abort '' + Cerulean failed to parse `cerulean.nexus.nodes.${name}`! + mergeStruct should never return `result.Err`... How are you here?!? + '') + r; + + # TODO: mapNodes = f: mapAttrs (name: nodeAttrs: f name (parseNode name nodeAttrs)) config.nexus.nodes + mapNodes = f: mapAttrs f (mapAttrs parseNode config.nexus.nodes); + in rec { + nixosConfigurations = mapNodes ( + _: node: + lib.nixosSystem { + system = node.system; + modules = node.modules; + + # nix passes these to every single module + specialArgs = {} // node.modules.specialArgs; + } + ); + + deploy.nodes = mapNodes (_: node: { + hostname = node.deploy.ssh.host; + + profilesOrder = ["default"]; # profiles priority + profiles.default = { + path = deploy-rs.lib.${node.system}.activate.nixos nixosConfigurations.${node.system}; + + user = node.deploy.user; + sudo = node.deploy.sudo; + interactiveSudo = node.deploy.interactiveSudo; + + fastConnection = false; + + autoRollback = node.deploy.autoRollback; + magicRollback = node.deploy.magicRollback; + activationTimeout = node.deploy.activationTimeout; + confirmTimeout = node.deploy.confirmTimeout; + + remoteBuild = node.deploy.remoteBuild; + sshUser = node.deploy.ssh.user; + sshOpts = + node.deploy.ssh.opts + ++ ( + if elem "-p" node.deploy.ssh.opts + then [] + else ["-p" (toString node.deploy.ssh.port)] + ); + }; + }); + + checks = mapAttrs (system: deployLib: deployLib.deployChecks deploy) deploy-rs.lib; }; - parseNode = name: nodeAttrs: - if !(builtins.isAttrs nodeAttrs) - then - # fail if node is not an attribute set - builtins.abort '' - Cerulean Nexus nodes must be provided as an attribute set, got "${builtins.typeOf nodeAttrs}" instead! - Ensure all `cerulean.nexus.nodes.${name}` declarations are attribute sets under your call to `cerulean.mkNexus`. - '' - # TODO: nodeAttrs.system won't display any nice error messages!! - # TODO: will mergeTypedStruct give nice error messages? or should I use mergeStructErr directly? - else let - templateAttrs = templateNode name nodeAttrs.system; - r = nib.parse.mergeStruct templateAttrs nodeAttrs; - in - nib.types.unwrapRes (_: - builtins.abort '' - Cerulean failed to parse `cerulean.nexus.nodes.${name}`! - mergeStruct should never return `result.Err`... How are you here?!? - '') - r; - - # TODO: mapNodes = f: builtins.mapAttrs (name: nodeAttrs: f name (parseNode name nodeAttrs)) config.nexus.nodes - mapNodes = f: builtins.mapAttrs f (builtins.mapAttrs parseNode config.nexus.nodes); - in rec { - nixosConfigurations = mapNodes ( - _: node: - lib.nixosSystem { - system = node.system; - modules = node.modules; - - # nix passes these to every single module - specialArgs = {} // node.modules.specialArgs; - } - ); - - deploy.nodes = mapNodes (_: node: { - hostname = node.deploy.ssh.host; - - profilesOrder = ["default"]; # profiles priority - profiles.default = { - path = deploy-rs.lib.${node.system}.activate.nixos nixosConfigurations.${node.system}; - - user = node.deploy.user; - sudo = node.deploy.sudo; - interactiveSudo = node.deploy.interactiveSudo; - - fastConnection = false; - - autoRollback = node.deploy.autoRollback; - magicRollback = node.deploy.magicRollback; - activationTimeout = node.deploy.activationTimeout; - confirmTimeout = node.deploy.confirmTimeout; - - remoteBuild = node.deploy.remoteBuild; - sshUser = node.deploy.ssh.user; - sshOpts = - node.deploy.ssh.opts - ++ ( - if builtins.elem "-p" node.deploy.ssh.opts - then [] - else ["-p" (builtins.toString node.deploy.ssh.port)] - ); - }; - }); - - checks = builtins.mapAttrs (system: deployLib: deployLib.deployChecks deploy) deploy-rs.lib; + mkNexus = outputs: let + config = outputs.cerulean; + in + (mkNexusConfig config) // (removeAttrs outputs ["cerulean"]); }; - - mkNexus = outputs: let - config = outputs.cerulean; - in - (mkNexusConfig config) // (builtins.removeAttrs outputs ["cerulean"]); - }; } From 0b24eb1246643c0e6c4ce703d48f9f55fdb399dd Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 14 Dec 2025 14:02:06 +1000 Subject: [PATCH 053/338] add LICENSE --- LICENSE | 201 +++++++++++++++++++++++++++++++++++++++++++++++ flake.nix | 13 +++ lib/cdesktop.nix | 13 +++ lib/csystem.nix | 13 +++ lib/cuser.nix | 13 +++ 5 files changed, 253 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/flake.nix b/flake.nix index da08945..777e37e 100644 --- a/flake.nix +++ b/flake.nix @@ -1,3 +1,16 @@ +# Copyright 2025 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. { description = "Your Nix Cloud Simplified"; diff --git a/lib/cdesktop.nix b/lib/cdesktop.nix index 00b7439..ef2b205 100644 --- a/lib/cdesktop.nix +++ b/lib/cdesktop.nix @@ -1,3 +1,16 @@ +# Copyright 2025 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. { lib, config, diff --git a/lib/csystem.nix b/lib/csystem.nix index fc398f8..7f4677c 100644 --- a/lib/csystem.nix +++ b/lib/csystem.nix @@ -1,3 +1,16 @@ +# Copyright 2025 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. { inputs, lib, diff --git a/lib/cuser.nix b/lib/cuser.nix index 8b26c9f..f602e03 100644 --- a/lib/cuser.nix +++ b/lib/cuser.nix @@ -1,3 +1,16 @@ +# Copyright 2025 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. { lib, config, From 21ed706c4a08c969e400842e45631fb588d075d3 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Sun, 14 Dec 2025 14:02:06 +1000 Subject: [PATCH 054/338] add LICENSE --- LICENSE | 201 +++++++++++++++++++++++++++++++++++++++++++++++ flake.nix | 13 +++ lib/cdesktop.nix | 13 +++ lib/csystem.nix | 13 +++ lib/cuser.nix | 13 +++ 5 files changed, 253 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/flake.nix b/flake.nix index da08945..777e37e 100644 --- a/flake.nix +++ b/flake.nix @@ -1,3 +1,16 @@ +# Copyright 2025 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. { description = "Your Nix Cloud Simplified"; diff --git a/lib/cdesktop.nix b/lib/cdesktop.nix index 00b7439..ef2b205 100644 --- a/lib/cdesktop.nix +++ b/lib/cdesktop.nix @@ -1,3 +1,16 @@ +# Copyright 2025 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. { lib, config, diff --git a/lib/csystem.nix b/lib/csystem.nix index fc398f8..7f4677c 100644 --- a/lib/csystem.nix +++ b/lib/csystem.nix @@ -1,3 +1,16 @@ +# Copyright 2025 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. { inputs, lib, diff --git a/lib/cuser.nix b/lib/cuser.nix index 8b26c9f..f602e03 100644 --- a/lib/cuser.nix +++ b/lib/cuser.nix @@ -1,3 +1,16 @@ +# Copyright 2025 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. { lib, config, From 607a0dee93aaad87aae8adc5dcc4a0f70bb786c7 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 14 Dec 2025 14:02:12 +1000 Subject: [PATCH 055/338] add README.md --- README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..b62396e --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +>[!WARNING] +> ✨ **Under Construction** ✨ +> Cerulean has lived rent free in my head for the last 12 months. +> I'm developing this project for personal use and especially +> for use at my workplace. **Be not afraid!** It's only a matter +> of time until Cerulean is ready for use! + +## 💙 Same Colour, More Control +Cerulean is what you wish Azure could be. An expansive collection of microservices, pre-configured systems, +and entirely self-hosted! Cerulean is built using NixOS as a foundation so you know it's never going to break randomly. +NixOS backing makes Cerulean **extremely scalable**! Just rent a new VPS and Cerulean will build an ISO of your configuration. + +No stress, no hassle! +Say goodbye to Azure! And say goodbye to Kubernetes! You're taking life into your own hands 💙 + +### 🌌 🚀 Nexus +Cerulean allows you to declare *"Nexus Networks"*, virtual networks of servers that Cerulean can manage. +Each Nexus is **very powerful**. Allowing for simple distributed computing, automatic construction of a wireguard +VPN, distributed DNS for local hostnames, and that's just scratching the surface... From e5127b0134154cfaa88d7117b9ac12aba71e6bd5 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Sun, 14 Dec 2025 14:02:12 +1000 Subject: [PATCH 056/338] add README.md --- README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..b62396e --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +>[!WARNING] +> ✨ **Under Construction** ✨ +> Cerulean has lived rent free in my head for the last 12 months. +> I'm developing this project for personal use and especially +> for use at my workplace. **Be not afraid!** It's only a matter +> of time until Cerulean is ready for use! + +## 💙 Same Colour, More Control +Cerulean is what you wish Azure could be. An expansive collection of microservices, pre-configured systems, +and entirely self-hosted! Cerulean is built using NixOS as a foundation so you know it's never going to break randomly. +NixOS backing makes Cerulean **extremely scalable**! Just rent a new VPS and Cerulean will build an ISO of your configuration. + +No stress, no hassle! +Say goodbye to Azure! And say goodbye to Kubernetes! You're taking life into your own hands 💙 + +### 🌌 🚀 Nexus +Cerulean allows you to declare *"Nexus Networks"*, virtual networks of servers that Cerulean can manage. +Each Nexus is **very powerful**. Allowing for simple distributed computing, automatic construction of a wireguard +VPN, distributed DNS for local hostnames, and that's just scratching the surface... From 26388007079678bbecb0bce2d7d4cbaaab6e6423 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 14 Dec 2025 14:02:22 +1000 Subject: [PATCH 057/338] progress flake.lock --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 27902dd..69362a4 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1765682506, - "narHash": "sha256-7ElrlSed9FGQHgcHMwnp7KbBzVmGKJ1m41oorhihxYg=", + "lastModified": 1765684362, + "narHash": "sha256-EVSCVPxeq8933uNDyllEzjvlX3eh/eivDSx6dWzcebM=", "owner": "emilelcb", "repo": "nib", - "rev": "223bb548ec08f1f7274c439b2354f83fdc7cea1c", + "rev": "862491247f8c43c36039ed89a59e37021db79c21", "type": "github" }, "original": { From 2f5d3221df55edb94ab6c3b607666f237b85434f Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Sun, 14 Dec 2025 14:02:22 +1000 Subject: [PATCH 058/338] progress flake.lock --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 27902dd..69362a4 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1765682506, - "narHash": "sha256-7ElrlSed9FGQHgcHMwnp7KbBzVmGKJ1m41oorhihxYg=", + "lastModified": 1765684362, + "narHash": "sha256-EVSCVPxeq8933uNDyllEzjvlX3eh/eivDSx6dWzcebM=", "owner": "emilelcb", "repo": "nib", - "rev": "223bb548ec08f1f7274c439b2354f83fdc7cea1c", + "rev": "862491247f8c43c36039ed89a59e37021db79c21", "type": "github" }, "original": { From 0b7fa9ab0da9ac197b13d618d83f7a1da10e617b Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 14 Dec 2025 18:56:29 +1000 Subject: [PATCH 059/338] support nib's new parsing schema --- flake.nix | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/flake.nix b/flake.nix index 777e37e..a18ba05 100644 --- a/flake.nix +++ b/flake.nix @@ -36,7 +36,8 @@ deploy-rs, ... } @ inputs: - with builtins; let + with builtins; + with nib.types; let lib = nixpkgs.lib; sys = with nib; @@ -67,17 +68,17 @@ # abstract node instance that stores all default values templateNode = name: system: let missing = msg: path: - abort '' + Terminal (abort '' Each Cerulean Nexus node is required to specify ${msg}! Ensure `cerulean.nexus.nodes.${name}.${path}` exists under your call to `cerulean.mkNexus`. - ''; + ''); in { system = missing "its system type" "system"; # intentionally left missing!! (to raise errors) modules = missing "its required modules" "modules"; specialArgs = { inherit inputs; - pkgs = sys.pkgsFor system; - upkgs = sys.upkgsFor system; + pkgs = Terminal (sys.pkgsFor system); + upkgs = Terminal (sys.upkgsFor system); }; deploy = { @@ -114,14 +115,14 @@ # TODO: will mergeTypedStruct give nice error messages? or should I use mergeStructErr directly? else let templateAttrs = templateNode name nodeAttrs.system; - r = nib.parse.mergeStruct templateAttrs nodeAttrs; + S = nib.parse.parseStructFor templateAttrs nodeAttrs; in - nib.types.unwrapRes (_: + unwrapRes (_: abort '' Cerulean failed to parse `cerulean.nexus.nodes.${name}`! mergeStruct should never return `result.Err`... How are you here?!? '') - r; + S; # TODO: mapNodes = f: mapAttrs (name: nodeAttrs: f name (parseNode name nodeAttrs)) config.nexus.nodes mapNodes = f: mapAttrs f (mapAttrs parseNode config.nexus.nodes); From 15ed0341b9517c240ecad1f08dd7369dc94ccc97 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Sun, 14 Dec 2025 18:56:29 +1000 Subject: [PATCH 060/338] support nib's new parsing schema --- flake.nix | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/flake.nix b/flake.nix index 777e37e..a18ba05 100644 --- a/flake.nix +++ b/flake.nix @@ -36,7 +36,8 @@ deploy-rs, ... } @ inputs: - with builtins; let + with builtins; + with nib.types; let lib = nixpkgs.lib; sys = with nib; @@ -67,17 +68,17 @@ # abstract node instance that stores all default values templateNode = name: system: let missing = msg: path: - abort '' + Terminal (abort '' Each Cerulean Nexus node is required to specify ${msg}! Ensure `cerulean.nexus.nodes.${name}.${path}` exists under your call to `cerulean.mkNexus`. - ''; + ''); in { system = missing "its system type" "system"; # intentionally left missing!! (to raise errors) modules = missing "its required modules" "modules"; specialArgs = { inherit inputs; - pkgs = sys.pkgsFor system; - upkgs = sys.upkgsFor system; + pkgs = Terminal (sys.pkgsFor system); + upkgs = Terminal (sys.upkgsFor system); }; deploy = { @@ -114,14 +115,14 @@ # TODO: will mergeTypedStruct give nice error messages? or should I use mergeStructErr directly? else let templateAttrs = templateNode name nodeAttrs.system; - r = nib.parse.mergeStruct templateAttrs nodeAttrs; + S = nib.parse.parseStructFor templateAttrs nodeAttrs; in - nib.types.unwrapRes (_: + unwrapRes (_: abort '' Cerulean failed to parse `cerulean.nexus.nodes.${name}`! mergeStruct should never return `result.Err`... How are you here?!? '') - r; + S; # TODO: mapNodes = f: mapAttrs (name: nodeAttrs: f name (parseNode name nodeAttrs)) config.nexus.nodes mapNodes = f: mapAttrs f (mapAttrs parseNode config.nexus.nodes); From c6cdd10f5b2087b8f0447d4f684494f156ac8eb4 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 14 Dec 2025 18:56:43 +1000 Subject: [PATCH 061/338] progress flake.lock --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 69362a4..e092c32 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1765684362, - "narHash": "sha256-EVSCVPxeq8933uNDyllEzjvlX3eh/eivDSx6dWzcebM=", + "lastModified": 1765702567, + "narHash": "sha256-ubhv2dEaoF/eLUxuc2eQee/JEyRuUSC4vCIZDzLkW38=", "owner": "emilelcb", "repo": "nib", - "rev": "862491247f8c43c36039ed89a59e37021db79c21", + "rev": "cfdee02789d860581e97d68827b82c10a689bc24", "type": "github" }, "original": { From 2c2444445bf50611a4a2bfeba964267751d4d841 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Sun, 14 Dec 2025 18:56:43 +1000 Subject: [PATCH 062/338] progress flake.lock --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 69362a4..e092c32 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1765684362, - "narHash": "sha256-EVSCVPxeq8933uNDyllEzjvlX3eh/eivDSx6dWzcebM=", + "lastModified": 1765702567, + "narHash": "sha256-ubhv2dEaoF/eLUxuc2eQee/JEyRuUSC4vCIZDzLkW38=", "owner": "emilelcb", "repo": "nib", - "rev": "862491247f8c43c36039ed89a59e37021db79c21", + "rev": "cfdee02789d860581e97d68827b82c10a689bc24", "type": "github" }, "original": { From 9c484553b3b2299981ff9175bd9bc328a77781d0 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 14 Dec 2025 20:48:04 +1000 Subject: [PATCH 063/338] progress flake.lock --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index e092c32..bdf80e4 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1765702567, - "narHash": "sha256-ubhv2dEaoF/eLUxuc2eQee/JEyRuUSC4vCIZDzLkW38=", + "lastModified": 1765709254, + "narHash": "sha256-WhVHUTFVmvK5+ZdGkQv6Xz4jABGBhxosjTL4czg0ocg=", "owner": "emilelcb", "repo": "nib", - "rev": "cfdee02789d860581e97d68827b82c10a689bc24", + "rev": "26f8275a1b7fc7f0708b8d4117973131349b439a", "type": "github" }, "original": { From 09e9782e377679b0f0288e7071a773fab98acb57 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Sun, 14 Dec 2025 20:48:04 +1000 Subject: [PATCH 064/338] progress flake.lock --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index e092c32..bdf80e4 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1765702567, - "narHash": "sha256-ubhv2dEaoF/eLUxuc2eQee/JEyRuUSC4vCIZDzLkW38=", + "lastModified": 1765709254, + "narHash": "sha256-WhVHUTFVmvK5+ZdGkQv6Xz4jABGBhxosjTL4czg0ocg=", "owner": "emilelcb", "repo": "nib", - "rev": "cfdee02789d860581e97d68827b82c10a689bc24", + "rev": "26f8275a1b7fc7f0708b8d4117973131349b439a", "type": "github" }, "original": { From 0b13998fd4c5d106886472ab72c463c727e8fab0 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 14 Dec 2025 20:50:07 +1000 Subject: [PATCH 065/338] specialArgs should be Terminal --- flake.nix | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.nix b/flake.nix index a18ba05..39f2932 100644 --- a/flake.nix +++ b/flake.nix @@ -75,10 +75,10 @@ in { system = missing "its system type" "system"; # intentionally left missing!! (to raise errors) modules = missing "its required modules" "modules"; - specialArgs = { + specialArgs = Terminal { inherit inputs; - pkgs = Terminal (sys.pkgsFor system); - upkgs = Terminal (sys.upkgsFor system); + pkgs = sys.pkgsFor system; + upkgs = sys.upkgsFor system; }; deploy = { From 4c69ed440735d2ba977d9f2adf6cab7a2839ea72 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Sun, 14 Dec 2025 20:50:07 +1000 Subject: [PATCH 066/338] specialArgs should be Terminal --- flake.nix | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.nix b/flake.nix index a18ba05..39f2932 100644 --- a/flake.nix +++ b/flake.nix @@ -75,10 +75,10 @@ in { system = missing "its system type" "system"; # intentionally left missing!! (to raise errors) modules = missing "its required modules" "modules"; - specialArgs = { + specialArgs = Terminal { inherit inputs; - pkgs = Terminal (sys.pkgsFor system); - upkgs = Terminal (sys.upkgsFor system); + pkgs = sys.pkgsFor system; + upkgs = sys.upkgsFor system; }; deploy = { From 08ceff458159794bab094d340b0bc1ae220e91ad Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 14 Dec 2025 21:37:20 +1000 Subject: [PATCH 067/338] avoid the with keyword --- flake.nix | 253 +++++++++++++++++++++++++++--------------------------- 1 file changed, 126 insertions(+), 127 deletions(-) diff --git a/flake.nix b/flake.nix index 39f2932..1007b79 100644 --- a/flake.nix +++ b/flake.nix @@ -35,145 +35,144 @@ nib, deploy-rs, ... - } @ inputs: - with builtins; - with nib.types; let - lib = nixpkgs.lib; + } @ inputs: let + lib = nixpkgs.lib; - sys = with nib; - mkUSys { - pkgs = withPkgs nixpkgs { - config.allowUnfree = false; - overlays = attrValues self.overlays; - }; - upkgs = withPkgs nixpkgs-unstable { - config.allowUnfree = false; - }; + sys = nib.mkUSys { + pkgs = nib.withPkgs nixpkgs { + config.allowUnfree = false; + overlays = builtins.attrValues self.overlays; + }; + upkgs = nib.withPkgs nixpkgs-unstable { + config.allowUnfree = false; + }; + }; + in rec { + overlays = [ + # deploy-rs is built from the flake input, not from nixpkgs! + # To take advantage of the nixpkgs binary cache, + # the deploy-rs package can be overwritten: + deploy-rs.overlays.default + (self: super: { + deploy-rs = { + inherit (super) deploy-rs; + lib = super.deploy-rs.lib; }; - in rec { - overlays = [ - # deploy-rs is built from the flake input, not from nixpkgs! - # To take advantage of the nixpkgs binary cache, - # the deploy-rs package can be overwritten: - deploy-rs.overlays.default - (self: super: { - deploy-rs = { - inherit (super) deploy-rs; - lib = super.deploy-rs.lib; - }; - }) - ]; + }) + ]; - mkNexusConfig = config: let - # abstract node instance that stores all default values - templateNode = name: system: let - missing = msg: path: - Terminal (abort '' - Each Cerulean Nexus node is required to specify ${msg}! - Ensure `cerulean.nexus.nodes.${name}.${path}` exists under your call to `cerulean.mkNexus`. - ''); - in { - system = missing "its system type" "system"; # intentionally left missing!! (to raise errors) - modules = missing "its required modules" "modules"; - specialArgs = Terminal { - inherit inputs; - pkgs = sys.pkgsFor system; - upkgs = sys.upkgsFor system; - }; + mkNexusConfig = config: let + # abstract node instance that stores all default values + templateNode = name: system: let + Terminal = nib.types.Terminal; - deploy = { - user = "root"; - sudo = "sudo -u"; - interactiveSudo = false; - - remoteBuild = false; # prefer local builds for remote deploys - - autoRollback = true; # reactivate previous profile if activation fails - magicRollback = true; - - activationTimeout = 500; # timeout in seconds for profile activation - confirmTimeout = 30; # timeout in seconds for profile activation confirmation - - ssh = { - host = missing "an SSH hostname (domain name or ip address) for deployment" "deploy.ssh.host"; - user = missing "an SSH username for deployment" "deploy.ssh.user"; - port = 22; - opts = []; - }; - }; + missing = msg: path: + Terminal (abort '' + Each Cerulean Nexus node is required to specify ${msg}! + Ensure `cerulean.nexus.nodes.${name}.${path}` exists under your call to `cerulean.mkNexus`. + ''); + in { + system = missing "its system type" "system"; # intentionally left missing!! (to raise errors) + modules = missing "its required modules" "modules"; + specialArgs = Terminal { + inherit inputs; + pkgs = sys.pkgsFor system; + upkgs = sys.upkgsFor system; }; - parseNode = name: nodeAttrs: - if !(isAttrs nodeAttrs) - then - # fail if node is not an attribute set - abort '' - Cerulean Nexus nodes must be provided as an attribute set, got "${typeOf nodeAttrs}" instead! - Ensure all `cerulean.nexus.nodes.${name}` declarations are attribute sets under your call to `cerulean.mkNexus`. - '' - # TODO: nodeAttrs.system won't display any nice error messages!! - # TODO: will mergeTypedStruct give nice error messages? or should I use mergeStructErr directly? - else let - templateAttrs = templateNode name nodeAttrs.system; - S = nib.parse.parseStructFor templateAttrs nodeAttrs; - in - unwrapRes (_: - abort '' - Cerulean failed to parse `cerulean.nexus.nodes.${name}`! - mergeStruct should never return `result.Err`... How are you here?!? - '') - S; + deploy = { + user = "root"; + sudo = "sudo -u"; + interactiveSudo = false; - # TODO: mapNodes = f: mapAttrs (name: nodeAttrs: f name (parseNode name nodeAttrs)) config.nexus.nodes - mapNodes = f: mapAttrs f (mapAttrs parseNode config.nexus.nodes); - in rec { - nixosConfigurations = mapNodes ( - _: node: - lib.nixosSystem { - system = node.system; - modules = node.modules; + remoteBuild = false; # prefer local builds for remote deploys - # nix passes these to every single module - specialArgs = {} // node.modules.specialArgs; - } - ); + autoRollback = true; # reactivate previous profile if activation fails + magicRollback = true; - deploy.nodes = mapNodes (_: node: { - hostname = node.deploy.ssh.host; + activationTimeout = 500; # timeout in seconds for profile activation + confirmTimeout = 30; # timeout in seconds for profile activation confirmation - profilesOrder = ["default"]; # profiles priority - profiles.default = { - path = deploy-rs.lib.${node.system}.activate.nixos nixosConfigurations.${node.system}; - - user = node.deploy.user; - sudo = node.deploy.sudo; - interactiveSudo = node.deploy.interactiveSudo; - - fastConnection = false; - - autoRollback = node.deploy.autoRollback; - magicRollback = node.deploy.magicRollback; - activationTimeout = node.deploy.activationTimeout; - confirmTimeout = node.deploy.confirmTimeout; - - remoteBuild = node.deploy.remoteBuild; - sshUser = node.deploy.ssh.user; - sshOpts = - node.deploy.ssh.opts - ++ ( - if elem "-p" node.deploy.ssh.opts - then [] - else ["-p" (toString node.deploy.ssh.port)] - ); + ssh = { + host = missing "an SSH hostname (domain name or ip address) for deployment" "deploy.ssh.host"; + user = missing "an SSH username for deployment" "deploy.ssh.user"; + port = 22; + opts = []; }; - }); - - checks = mapAttrs (system: deployLib: deployLib.deployChecks deploy) deploy-rs.lib; + }; }; - mkNexus = outputs: let - config = outputs.cerulean; - in - (mkNexusConfig config) // (removeAttrs outputs ["cerulean"]); + parseNode = name: nodeAttrs: + if !(builtins.isAttrs nodeAttrs) + then + # fail if node is not an attribute set + abort '' + Cerulean Nexus nodes must be provided as an attribute set, got "${builtins.typeOf nodeAttrs}" instead! + Ensure all `cerulean.nexus.nodes.${name}` declarations are attribute sets under your call to `cerulean.mkNexus`. + '' + # TODO: nodeAttrs.system won't display any nice error messages!! + # TODO: will mergeTypedStruct give nice error messages? or should I use mergeStructErr directly? + else let + templateAttrs = templateNode name nodeAttrs.system; + S = nib.parse.parseStructFor templateAttrs nodeAttrs; + in + nib.types.unwrapRes (_: + abort '' + Cerulean failed to parse `cerulean.nexus.nodes.${name}`! + mergeStruct should never return `result.Err`... How are you here?!? + '') + S; + + # TODO: mapNodes = f: mapAttrs (name: nodeAttrs: f name (parseNode name nodeAttrs)) config.nexus.nodes + mapNodes = f: builtins.mapAttrs f (builtins.mapAttrs parseNode config.nexus.nodes); + in rec { + nixosConfigurations = mapNodes ( + _: node: + lib.nixosSystem { + system = node.system; + modules = node.modules; + + # nix passes these to every single module + specialArgs = {} // node.modules.specialArgs; + } + ); + + deploy.nodes = mapNodes (_: node: { + hostname = node.deploy.ssh.host; + + profilesOrder = ["default"]; # profiles priority + profiles.default = { + path = deploy-rs.lib.${node.system}.activate.nixos nixosConfigurations.${node.system}; + + user = node.deploy.user; + sudo = node.deploy.sudo; + interactiveSudo = node.deploy.interactiveSudo; + + fastConnection = false; + + autoRollback = node.deploy.autoRollback; + magicRollback = node.deploy.magicRollback; + activationTimeout = node.deploy.activationTimeout; + confirmTimeout = node.deploy.confirmTimeout; + + remoteBuild = node.deploy.remoteBuild; + sshUser = node.deploy.ssh.user; + sshOpts = + node.deploy.ssh.opts + ++ ( + if builtins.elem "-p" node.deploy.ssh.opts + then [] + else ["-p" (toString node.deploy.ssh.port)] + ); + }; + }); + + checks = builtins.mapAttrs (system: deployLib: deployLib.deployChecks deploy) deploy-rs.lib; }; + + mkNexus = outputs: let + config = outputs.cerulean; + in + (mkNexusConfig config) // (removeAttrs outputs ["cerulean"]); + }; } From a82ce313a5fda91bf3ba44e3b98f0b1dbc8ae68b Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Sun, 14 Dec 2025 21:37:20 +1000 Subject: [PATCH 068/338] avoid the with keyword --- flake.nix | 253 +++++++++++++++++++++++++++--------------------------- 1 file changed, 126 insertions(+), 127 deletions(-) diff --git a/flake.nix b/flake.nix index 39f2932..1007b79 100644 --- a/flake.nix +++ b/flake.nix @@ -35,145 +35,144 @@ nib, deploy-rs, ... - } @ inputs: - with builtins; - with nib.types; let - lib = nixpkgs.lib; + } @ inputs: let + lib = nixpkgs.lib; - sys = with nib; - mkUSys { - pkgs = withPkgs nixpkgs { - config.allowUnfree = false; - overlays = attrValues self.overlays; - }; - upkgs = withPkgs nixpkgs-unstable { - config.allowUnfree = false; - }; + sys = nib.mkUSys { + pkgs = nib.withPkgs nixpkgs { + config.allowUnfree = false; + overlays = builtins.attrValues self.overlays; + }; + upkgs = nib.withPkgs nixpkgs-unstable { + config.allowUnfree = false; + }; + }; + in rec { + overlays = [ + # deploy-rs is built from the flake input, not from nixpkgs! + # To take advantage of the nixpkgs binary cache, + # the deploy-rs package can be overwritten: + deploy-rs.overlays.default + (self: super: { + deploy-rs = { + inherit (super) deploy-rs; + lib = super.deploy-rs.lib; }; - in rec { - overlays = [ - # deploy-rs is built from the flake input, not from nixpkgs! - # To take advantage of the nixpkgs binary cache, - # the deploy-rs package can be overwritten: - deploy-rs.overlays.default - (self: super: { - deploy-rs = { - inherit (super) deploy-rs; - lib = super.deploy-rs.lib; - }; - }) - ]; + }) + ]; - mkNexusConfig = config: let - # abstract node instance that stores all default values - templateNode = name: system: let - missing = msg: path: - Terminal (abort '' - Each Cerulean Nexus node is required to specify ${msg}! - Ensure `cerulean.nexus.nodes.${name}.${path}` exists under your call to `cerulean.mkNexus`. - ''); - in { - system = missing "its system type" "system"; # intentionally left missing!! (to raise errors) - modules = missing "its required modules" "modules"; - specialArgs = Terminal { - inherit inputs; - pkgs = sys.pkgsFor system; - upkgs = sys.upkgsFor system; - }; + mkNexusConfig = config: let + # abstract node instance that stores all default values + templateNode = name: system: let + Terminal = nib.types.Terminal; - deploy = { - user = "root"; - sudo = "sudo -u"; - interactiveSudo = false; - - remoteBuild = false; # prefer local builds for remote deploys - - autoRollback = true; # reactivate previous profile if activation fails - magicRollback = true; - - activationTimeout = 500; # timeout in seconds for profile activation - confirmTimeout = 30; # timeout in seconds for profile activation confirmation - - ssh = { - host = missing "an SSH hostname (domain name or ip address) for deployment" "deploy.ssh.host"; - user = missing "an SSH username for deployment" "deploy.ssh.user"; - port = 22; - opts = []; - }; - }; + missing = msg: path: + Terminal (abort '' + Each Cerulean Nexus node is required to specify ${msg}! + Ensure `cerulean.nexus.nodes.${name}.${path}` exists under your call to `cerulean.mkNexus`. + ''); + in { + system = missing "its system type" "system"; # intentionally left missing!! (to raise errors) + modules = missing "its required modules" "modules"; + specialArgs = Terminal { + inherit inputs; + pkgs = sys.pkgsFor system; + upkgs = sys.upkgsFor system; }; - parseNode = name: nodeAttrs: - if !(isAttrs nodeAttrs) - then - # fail if node is not an attribute set - abort '' - Cerulean Nexus nodes must be provided as an attribute set, got "${typeOf nodeAttrs}" instead! - Ensure all `cerulean.nexus.nodes.${name}` declarations are attribute sets under your call to `cerulean.mkNexus`. - '' - # TODO: nodeAttrs.system won't display any nice error messages!! - # TODO: will mergeTypedStruct give nice error messages? or should I use mergeStructErr directly? - else let - templateAttrs = templateNode name nodeAttrs.system; - S = nib.parse.parseStructFor templateAttrs nodeAttrs; - in - unwrapRes (_: - abort '' - Cerulean failed to parse `cerulean.nexus.nodes.${name}`! - mergeStruct should never return `result.Err`... How are you here?!? - '') - S; + deploy = { + user = "root"; + sudo = "sudo -u"; + interactiveSudo = false; - # TODO: mapNodes = f: mapAttrs (name: nodeAttrs: f name (parseNode name nodeAttrs)) config.nexus.nodes - mapNodes = f: mapAttrs f (mapAttrs parseNode config.nexus.nodes); - in rec { - nixosConfigurations = mapNodes ( - _: node: - lib.nixosSystem { - system = node.system; - modules = node.modules; + remoteBuild = false; # prefer local builds for remote deploys - # nix passes these to every single module - specialArgs = {} // node.modules.specialArgs; - } - ); + autoRollback = true; # reactivate previous profile if activation fails + magicRollback = true; - deploy.nodes = mapNodes (_: node: { - hostname = node.deploy.ssh.host; + activationTimeout = 500; # timeout in seconds for profile activation + confirmTimeout = 30; # timeout in seconds for profile activation confirmation - profilesOrder = ["default"]; # profiles priority - profiles.default = { - path = deploy-rs.lib.${node.system}.activate.nixos nixosConfigurations.${node.system}; - - user = node.deploy.user; - sudo = node.deploy.sudo; - interactiveSudo = node.deploy.interactiveSudo; - - fastConnection = false; - - autoRollback = node.deploy.autoRollback; - magicRollback = node.deploy.magicRollback; - activationTimeout = node.deploy.activationTimeout; - confirmTimeout = node.deploy.confirmTimeout; - - remoteBuild = node.deploy.remoteBuild; - sshUser = node.deploy.ssh.user; - sshOpts = - node.deploy.ssh.opts - ++ ( - if elem "-p" node.deploy.ssh.opts - then [] - else ["-p" (toString node.deploy.ssh.port)] - ); + ssh = { + host = missing "an SSH hostname (domain name or ip address) for deployment" "deploy.ssh.host"; + user = missing "an SSH username for deployment" "deploy.ssh.user"; + port = 22; + opts = []; }; - }); - - checks = mapAttrs (system: deployLib: deployLib.deployChecks deploy) deploy-rs.lib; + }; }; - mkNexus = outputs: let - config = outputs.cerulean; - in - (mkNexusConfig config) // (removeAttrs outputs ["cerulean"]); + parseNode = name: nodeAttrs: + if !(builtins.isAttrs nodeAttrs) + then + # fail if node is not an attribute set + abort '' + Cerulean Nexus nodes must be provided as an attribute set, got "${builtins.typeOf nodeAttrs}" instead! + Ensure all `cerulean.nexus.nodes.${name}` declarations are attribute sets under your call to `cerulean.mkNexus`. + '' + # TODO: nodeAttrs.system won't display any nice error messages!! + # TODO: will mergeTypedStruct give nice error messages? or should I use mergeStructErr directly? + else let + templateAttrs = templateNode name nodeAttrs.system; + S = nib.parse.parseStructFor templateAttrs nodeAttrs; + in + nib.types.unwrapRes (_: + abort '' + Cerulean failed to parse `cerulean.nexus.nodes.${name}`! + mergeStruct should never return `result.Err`... How are you here?!? + '') + S; + + # TODO: mapNodes = f: mapAttrs (name: nodeAttrs: f name (parseNode name nodeAttrs)) config.nexus.nodes + mapNodes = f: builtins.mapAttrs f (builtins.mapAttrs parseNode config.nexus.nodes); + in rec { + nixosConfigurations = mapNodes ( + _: node: + lib.nixosSystem { + system = node.system; + modules = node.modules; + + # nix passes these to every single module + specialArgs = {} // node.modules.specialArgs; + } + ); + + deploy.nodes = mapNodes (_: node: { + hostname = node.deploy.ssh.host; + + profilesOrder = ["default"]; # profiles priority + profiles.default = { + path = deploy-rs.lib.${node.system}.activate.nixos nixosConfigurations.${node.system}; + + user = node.deploy.user; + sudo = node.deploy.sudo; + interactiveSudo = node.deploy.interactiveSudo; + + fastConnection = false; + + autoRollback = node.deploy.autoRollback; + magicRollback = node.deploy.magicRollback; + activationTimeout = node.deploy.activationTimeout; + confirmTimeout = node.deploy.confirmTimeout; + + remoteBuild = node.deploy.remoteBuild; + sshUser = node.deploy.ssh.user; + sshOpts = + node.deploy.ssh.opts + ++ ( + if builtins.elem "-p" node.deploy.ssh.opts + then [] + else ["-p" (toString node.deploy.ssh.port)] + ); + }; + }); + + checks = builtins.mapAttrs (system: deployLib: deployLib.deployChecks deploy) deploy-rs.lib; }; + + mkNexus = outputs: let + config = outputs.cerulean; + in + (mkNexusConfig config) // (removeAttrs outputs ["cerulean"]); + }; } From 143a63e81ebea8b87608a9bf6a6da5f742391c7d Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 14 Dec 2025 21:46:47 +1000 Subject: [PATCH 069/338] fix: typo unwrapRes -> unwrapOk --- flake.nix | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/flake.nix b/flake.nix index 1007b79..b8c59d2 100644 --- a/flake.nix +++ b/flake.nix @@ -110,21 +110,23 @@ Cerulean Nexus nodes must be provided as an attribute set, got "${builtins.typeOf nodeAttrs}" instead! Ensure all `cerulean.nexus.nodes.${name}` declarations are attribute sets under your call to `cerulean.mkNexus`. '' - # TODO: nodeAttrs.system won't display any nice error messages!! - # TODO: will mergeTypedStruct give nice error messages? or should I use mergeStructErr directly? else let templateAttrs = templateNode name nodeAttrs.system; S = nib.parse.parseStructFor templateAttrs nodeAttrs; in - nib.types.unwrapRes (_: + nib.types.unwrapOk (_: abort '' Cerulean failed to parse `cerulean.nexus.nodes.${name}`! mergeStruct should never return `result.Err`... How are you here?!? '') S; - # TODO: mapNodes = f: mapAttrs (name: nodeAttrs: f name (parseNode name nodeAttrs)) config.nexus.nodes - mapNodes = f: builtins.mapAttrs f (builtins.mapAttrs parseNode config.nexus.nodes); + # mapNodes = f: builtins.mapAttrs f (builtins.mapAttrs parseNode config.nexus.nodes); + mapNodes = f: + builtins.mapAttrs ( + name: nodeAttrs: f name (parseNode name nodeAttrs) + ) + config.nexus.nodes; in rec { nixosConfigurations = mapNodes ( _: node: From ea6d9c435482d3334a25469e4cfc64efd6c063e2 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Sun, 14 Dec 2025 21:46:47 +1000 Subject: [PATCH 070/338] fix: typo unwrapRes -> unwrapOk --- flake.nix | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/flake.nix b/flake.nix index 1007b79..b8c59d2 100644 --- a/flake.nix +++ b/flake.nix @@ -110,21 +110,23 @@ Cerulean Nexus nodes must be provided as an attribute set, got "${builtins.typeOf nodeAttrs}" instead! Ensure all `cerulean.nexus.nodes.${name}` declarations are attribute sets under your call to `cerulean.mkNexus`. '' - # TODO: nodeAttrs.system won't display any nice error messages!! - # TODO: will mergeTypedStruct give nice error messages? or should I use mergeStructErr directly? else let templateAttrs = templateNode name nodeAttrs.system; S = nib.parse.parseStructFor templateAttrs nodeAttrs; in - nib.types.unwrapRes (_: + nib.types.unwrapOk (_: abort '' Cerulean failed to parse `cerulean.nexus.nodes.${name}`! mergeStruct should never return `result.Err`... How are you here?!? '') S; - # TODO: mapNodes = f: mapAttrs (name: nodeAttrs: f name (parseNode name nodeAttrs)) config.nexus.nodes - mapNodes = f: builtins.mapAttrs f (builtins.mapAttrs parseNode config.nexus.nodes); + # mapNodes = f: builtins.mapAttrs f (builtins.mapAttrs parseNode config.nexus.nodes); + mapNodes = f: + builtins.mapAttrs ( + name: nodeAttrs: f name (parseNode name nodeAttrs) + ) + config.nexus.nodes; in rec { nixosConfigurations = mapNodes ( _: node: From 9f88e9c86dab098e4a5ae1df7c307c5294ecb1c1 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 14 Dec 2025 21:56:10 +1000 Subject: [PATCH 071/338] god help me --- flake.nix | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/flake.nix b/flake.nix index b8c59d2..4c73bab 100644 --- a/flake.nix +++ b/flake.nix @@ -114,11 +114,14 @@ templateAttrs = templateNode name nodeAttrs.system; S = nib.parse.parseStructFor templateAttrs nodeAttrs; in - nib.types.unwrapOk (_: - abort '' - Cerulean failed to parse `cerulean.nexus.nodes.${name}`! - mergeStruct should never return `result.Err`... How are you here?!? - '') + nib.types.unwrapOk (res: + if nib.types.isRes res + then + abort '' + Cerulean failed to parse `cerulean.nexus.nodes.${name}`! + mergeStruct should never return `result.Err`... How are you here?!? + '' + else abort "huh wtf ${builtins.typeOf res} +AND-THEN+ ${builtins.toJSON res}") S; # mapNodes = f: builtins.mapAttrs f (builtins.mapAttrs parseNode config.nexus.nodes); From 13e4d244a3b6c899c4c1c5a22e777b43b20a7b21 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Sun, 14 Dec 2025 21:56:10 +1000 Subject: [PATCH 072/338] god help me --- flake.nix | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/flake.nix b/flake.nix index b8c59d2..4c73bab 100644 --- a/flake.nix +++ b/flake.nix @@ -114,11 +114,14 @@ templateAttrs = templateNode name nodeAttrs.system; S = nib.parse.parseStructFor templateAttrs nodeAttrs; in - nib.types.unwrapOk (_: - abort '' - Cerulean failed to parse `cerulean.nexus.nodes.${name}`! - mergeStruct should never return `result.Err`... How are you here?!? - '') + nib.types.unwrapOk (res: + if nib.types.isRes res + then + abort '' + Cerulean failed to parse `cerulean.nexus.nodes.${name}`! + mergeStruct should never return `result.Err`... How are you here?!? + '' + else abort "huh wtf ${builtins.typeOf res} +AND-THEN+ ${builtins.toJSON res}") S; # mapNodes = f: builtins.mapAttrs f (builtins.mapAttrs parseNode config.nexus.nodes); From dd33d02714dcbc2b3a9d926492834ca00d535e3f Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 14 Dec 2025 22:42:09 +1000 Subject: [PATCH 073/338] progress flake.nix --- flake.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/flake.lock b/flake.lock index bdf80e4..2bb6c60 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1765709254, - "narHash": "sha256-WhVHUTFVmvK5+ZdGkQv6Xz4jABGBhxosjTL4czg0ocg=", + "lastModified": 1765716077, + "narHash": "sha256-u9Zb2HTRAw8P4N5E+sqX4126UPIwumCSdfB1wzjfOj0=", "owner": "emilelcb", "repo": "nib", - "rev": "26f8275a1b7fc7f0708b8d4117973131349b439a", + "rev": "e8f4764794d0abeabb572efcc6f404b24f147a7f", "type": "github" }, "original": { @@ -90,11 +90,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1765311797, - "narHash": "sha256-mSD5Ob7a+T2RNjvPvOA1dkJHGVrNVl8ZOrAwBjKBDQo=", + "lastModified": 1765608474, + "narHash": "sha256-9Wx53UK0z8Di5iesJID0tS1dRKwGxI4i7tsSanOHhF0=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "09eb77e94fa25202af8f3e81ddc7353d9970ac1b", + "rev": "28bb483c11a1214a73f9fd2d9928a6e2ea86ec71", "type": "github" }, "original": { From 3b262bafceffd1683bd0eba6634fe0180740f103 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Sun, 14 Dec 2025 22:42:09 +1000 Subject: [PATCH 074/338] progress flake.nix --- flake.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/flake.lock b/flake.lock index bdf80e4..2bb6c60 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1765709254, - "narHash": "sha256-WhVHUTFVmvK5+ZdGkQv6Xz4jABGBhxosjTL4czg0ocg=", + "lastModified": 1765716077, + "narHash": "sha256-u9Zb2HTRAw8P4N5E+sqX4126UPIwumCSdfB1wzjfOj0=", "owner": "emilelcb", "repo": "nib", - "rev": "26f8275a1b7fc7f0708b8d4117973131349b439a", + "rev": "e8f4764794d0abeabb572efcc6f404b24f147a7f", "type": "github" }, "original": { @@ -90,11 +90,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1765311797, - "narHash": "sha256-mSD5Ob7a+T2RNjvPvOA1dkJHGVrNVl8ZOrAwBjKBDQo=", + "lastModified": 1765608474, + "narHash": "sha256-9Wx53UK0z8Di5iesJID0tS1dRKwGxI4i7tsSanOHhF0=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "09eb77e94fa25202af8f3e81ddc7353d9970ac1b", + "rev": "28bb483c11a1214a73f9fd2d9928a6e2ea86ec71", "type": "github" }, "original": { From 27e83ddb47c6bbf5f18aa129a8012bc2a2753b9f Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 14 Dec 2025 22:50:40 +1000 Subject: [PATCH 075/338] remove unnecessary unwrapRes call --- flake.nix | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/flake.nix b/flake.nix index 4c73bab..23557e1 100644 --- a/flake.nix +++ b/flake.nix @@ -112,17 +112,8 @@ '' else let templateAttrs = templateNode name nodeAttrs.system; - S = nib.parse.parseStructFor templateAttrs nodeAttrs; in - nib.types.unwrapOk (res: - if nib.types.isRes res - then - abort '' - Cerulean failed to parse `cerulean.nexus.nodes.${name}`! - mergeStruct should never return `result.Err`... How are you here?!? - '' - else abort "huh wtf ${builtins.typeOf res} +AND-THEN+ ${builtins.toJSON res}") - S; + nib.parse.parseStructFor templateAttrs nodeAttrs; # mapNodes = f: builtins.mapAttrs f (builtins.mapAttrs parseNode config.nexus.nodes); mapNodes = f: From 1bedcc404ccc996039290cb2f159cc37a41e8cb1 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Sun, 14 Dec 2025 22:50:40 +1000 Subject: [PATCH 076/338] remove unnecessary unwrapRes call --- flake.nix | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/flake.nix b/flake.nix index 4c73bab..23557e1 100644 --- a/flake.nix +++ b/flake.nix @@ -112,17 +112,8 @@ '' else let templateAttrs = templateNode name nodeAttrs.system; - S = nib.parse.parseStructFor templateAttrs nodeAttrs; in - nib.types.unwrapOk (res: - if nib.types.isRes res - then - abort '' - Cerulean failed to parse `cerulean.nexus.nodes.${name}`! - mergeStruct should never return `result.Err`... How are you here?!? - '' - else abort "huh wtf ${builtins.typeOf res} +AND-THEN+ ${builtins.toJSON res}") - S; + nib.parse.parseStructFor templateAttrs nodeAttrs; # mapNodes = f: builtins.mapAttrs f (builtins.mapAttrs parseNode config.nexus.nodes); mapNodes = f: From 2751abad6c5919e64ea6526bac8db8d24a17590d Mon Sep 17 00:00:00 2001 From: _cry64 Date: Mon, 15 Dec 2025 00:14:32 +1000 Subject: [PATCH 077/338] progress flack.lock --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 2bb6c60..4abd95c 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1765716077, - "narHash": "sha256-u9Zb2HTRAw8P4N5E+sqX4126UPIwumCSdfB1wzjfOj0=", + "lastModified": 1765721644, + "narHash": "sha256-l4uoz89w+CaaY7vXWMGlxGPavEf/dNh6+hMIwPA3Mxk=", "owner": "emilelcb", "repo": "nib", - "rev": "e8f4764794d0abeabb572efcc6f404b24f147a7f", + "rev": "1ae1301a144ac1f74c8cc2fc5a75803132e7858f", "type": "github" }, "original": { From 76f483c6320c6c816186756329565418e8d57402 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Mon, 15 Dec 2025 00:14:32 +1000 Subject: [PATCH 078/338] progress flack.lock --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 2bb6c60..4abd95c 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1765716077, - "narHash": "sha256-u9Zb2HTRAw8P4N5E+sqX4126UPIwumCSdfB1wzjfOj0=", + "lastModified": 1765721644, + "narHash": "sha256-l4uoz89w+CaaY7vXWMGlxGPavEf/dNh6+hMIwPA3Mxk=", "owner": "emilelcb", "repo": "nib", - "rev": "e8f4764794d0abeabb572efcc6f404b24f147a7f", + "rev": "1ae1301a144ac1f74c8cc2fc5a75803132e7858f", "type": "github" }, "original": { From 93d8640c35344db81a08f69e7cafe028d2387c27 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Mon, 15 Dec 2025 00:24:28 +1000 Subject: [PATCH 079/338] progress flack.lock --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 4abd95c..8a4e69c 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1765721644, - "narHash": "sha256-l4uoz89w+CaaY7vXWMGlxGPavEf/dNh6+hMIwPA3Mxk=", + "lastModified": 1765722258, + "narHash": "sha256-qvH1BMbwLRhbWE9pxDZvA/1G/4cO8v0dGZXSF4M8fW0=", "owner": "emilelcb", "repo": "nib", - "rev": "1ae1301a144ac1f74c8cc2fc5a75803132e7858f", + "rev": "c1cb233c8f37dd555cfcd3e6b5a18256fb9cd06b", "type": "github" }, "original": { From d72f6581ccdfbf13b0fbe11d9b8d5e609cd72679 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Mon, 15 Dec 2025 00:24:28 +1000 Subject: [PATCH 080/338] progress flack.lock --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 4abd95c..8a4e69c 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1765721644, - "narHash": "sha256-l4uoz89w+CaaY7vXWMGlxGPavEf/dNh6+hMIwPA3Mxk=", + "lastModified": 1765722258, + "narHash": "sha256-qvH1BMbwLRhbWE9pxDZvA/1G/4cO8v0dGZXSF4M8fW0=", "owner": "emilelcb", "repo": "nib", - "rev": "1ae1301a144ac1f74c8cc2fc5a75803132e7858f", + "rev": "c1cb233c8f37dd555cfcd3e6b5a18256fb9cd06b", "type": "github" }, "original": { From 64d13428d062a27b0e71537aec1bd0ef4e79f4e9 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Thu, 18 Dec 2025 11:28:58 +1000 Subject: [PATCH 081/338] enable additional experimental-features --- lib/csystem.nix | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/csystem.nix b/lib/csystem.nix index 7f4677c..64e3aa3 100644 --- a/lib/csystem.nix +++ b/lib/csystem.nix @@ -57,9 +57,17 @@ in { ]; nix.settings = { + # REF: https://nix.dev/manual/nix/2.24/development/experimental-features experimental-features = [ - "nix-command" + # Significant "flakes" + "nix-command" + "pipe-operators" + + # Minor + "no-url-literals" + "parse-toml-timestamps" + "recursive-nix" ]; download-buffer-size = 524288000; # 500 MiB From c9451510e5cb193ac5fb3041865519596b0856b5 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Thu, 18 Dec 2025 11:28:58 +1000 Subject: [PATCH 082/338] enable additional experimental-features --- lib/csystem.nix | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/csystem.nix b/lib/csystem.nix index 7f4677c..64e3aa3 100644 --- a/lib/csystem.nix +++ b/lib/csystem.nix @@ -57,9 +57,17 @@ in { ]; nix.settings = { + # REF: https://nix.dev/manual/nix/2.24/development/experimental-features experimental-features = [ - "nix-command" + # Significant "flakes" + "nix-command" + "pipe-operators" + + # Minor + "no-url-literals" + "parse-toml-timestamps" + "recursive-nix" ]; download-buffer-size = 524288000; # 500 MiB From 8734ca8835ed74e77a2e5e6814d7a42716fed43d Mon Sep 17 00:00:00 2001 From: _cry64 Date: Thu, 18 Dec 2025 11:41:40 +1000 Subject: [PATCH 083/338] fix specialArg handling + use mergeStructs --- flake.nix | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/flake.nix b/flake.nix index 23557e1..557764f 100644 --- a/flake.nix +++ b/flake.nix @@ -74,11 +74,7 @@ in { system = missing "its system type" "system"; # intentionally left missing!! (to raise errors) modules = missing "its required modules" "modules"; - specialArgs = Terminal { - inherit inputs; - pkgs = sys.pkgsFor system; - upkgs = sys.upkgsFor system; - }; + specialArgs = Terminal {}; deploy = { user = "root"; @@ -113,32 +109,39 @@ else let templateAttrs = templateNode name nodeAttrs.system; in - nib.parse.parseStructFor templateAttrs nodeAttrs; + nib.parse.mergeStructs templateAttrs nodeAttrs; # mapNodes = f: builtins.mapAttrs f (builtins.mapAttrs parseNode config.nexus.nodes); mapNodes = f: - builtins.mapAttrs ( - name: nodeAttrs: f name (parseNode name nodeAttrs) - ) + builtins.mapAttrs + (nodeName: nodeAttrs: f nodeName (parseNode nodeName nodeAttrs)) config.nexus.nodes; in rec { nixosConfigurations = mapNodes ( - _: node: + nodeName: node: lib.nixosSystem { system = node.system; modules = node.modules; # nix passes these to every single module - specialArgs = {} // node.modules.specialArgs; + specialArgs = + node.modules.specialArgs + // { + inherit inputs; + pkgs = sys.pkgsFor node.system; + upkgs = sys.upkgsFor node.system; + }; } ); - deploy.nodes = mapNodes (_: node: { + deploy.nodes = mapNodes (nodeName: node: let + nixosFor = system: deploy-rs.lib.${system}.activate.nixos; + in { hostname = node.deploy.ssh.host; profilesOrder = ["default"]; # profiles priority profiles.default = { - path = deploy-rs.lib.${node.system}.activate.nixos nixosConfigurations.${node.system}; + path = nixosFor node.system nixosConfigurations.${nodeName}; user = node.deploy.user; sudo = node.deploy.sudo; From 73664536807fad5e2c8d0bf649f9aa648cd1d97c Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Thu, 18 Dec 2025 11:41:40 +1000 Subject: [PATCH 084/338] fix specialArg handling + use mergeStructs --- flake.nix | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/flake.nix b/flake.nix index 23557e1..557764f 100644 --- a/flake.nix +++ b/flake.nix @@ -74,11 +74,7 @@ in { system = missing "its system type" "system"; # intentionally left missing!! (to raise errors) modules = missing "its required modules" "modules"; - specialArgs = Terminal { - inherit inputs; - pkgs = sys.pkgsFor system; - upkgs = sys.upkgsFor system; - }; + specialArgs = Terminal {}; deploy = { user = "root"; @@ -113,32 +109,39 @@ else let templateAttrs = templateNode name nodeAttrs.system; in - nib.parse.parseStructFor templateAttrs nodeAttrs; + nib.parse.mergeStructs templateAttrs nodeAttrs; # mapNodes = f: builtins.mapAttrs f (builtins.mapAttrs parseNode config.nexus.nodes); mapNodes = f: - builtins.mapAttrs ( - name: nodeAttrs: f name (parseNode name nodeAttrs) - ) + builtins.mapAttrs + (nodeName: nodeAttrs: f nodeName (parseNode nodeName nodeAttrs)) config.nexus.nodes; in rec { nixosConfigurations = mapNodes ( - _: node: + nodeName: node: lib.nixosSystem { system = node.system; modules = node.modules; # nix passes these to every single module - specialArgs = {} // node.modules.specialArgs; + specialArgs = + node.modules.specialArgs + // { + inherit inputs; + pkgs = sys.pkgsFor node.system; + upkgs = sys.upkgsFor node.system; + }; } ); - deploy.nodes = mapNodes (_: node: { + deploy.nodes = mapNodes (nodeName: node: let + nixosFor = system: deploy-rs.lib.${system}.activate.nixos; + in { hostname = node.deploy.ssh.host; profilesOrder = ["default"]; # profiles priority profiles.default = { - path = deploy-rs.lib.${node.system}.activate.nixos nixosConfigurations.${node.system}; + path = nixosFor node.system nixosConfigurations.${nodeName}; user = node.deploy.user; sudo = node.deploy.sudo; From ba5b40f1e7f929bb573ba3ac2b78f6fa2b5fd6d8 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Thu, 18 Dec 2025 11:41:44 +1000 Subject: [PATCH 085/338] progress flake.lock --- flake.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/flake.lock b/flake.lock index 8a4e69c..26e5ad1 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1765722258, - "narHash": "sha256-qvH1BMbwLRhbWE9pxDZvA/1G/4cO8v0dGZXSF4M8fW0=", + "lastModified": 1766021230, + "narHash": "sha256-sC1pjYhurb7Dc6f6Di7CFxyk1sn+tsn++BRuin0r7tY=", "owner": "emilelcb", "repo": "nib", - "rev": "c1cb233c8f37dd555cfcd3e6b5a18256fb9cd06b", + "rev": "d0fceb7721aa316797a4632cdadea33daa2dbec3", "type": "github" }, "original": { @@ -74,11 +74,11 @@ }, "nixpkgs-unstable": { "locked": { - "lastModified": 1765472234, - "narHash": "sha256-9VvC20PJPsleGMewwcWYKGzDIyjckEz8uWmT0vCDYK0=", + "lastModified": 1765779637, + "narHash": "sha256-KJ2wa/BLSrTqDjbfyNx70ov/HdgNBCBBSQP3BIzKnv4=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "2fbfb1d73d239d2402a8fe03963e37aab15abe8b", + "rev": "1306659b587dc277866c7b69eb97e5f07864d8c4", "type": "github" }, "original": { @@ -90,11 +90,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1765608474, - "narHash": "sha256-9Wx53UK0z8Di5iesJID0tS1dRKwGxI4i7tsSanOHhF0=", + "lastModified": 1765838191, + "narHash": "sha256-m5KWt1nOm76ILk/JSCxBM4MfK3rYY7Wq9/TZIIeGnT8=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "28bb483c11a1214a73f9fd2d9928a6e2ea86ec71", + "rev": "c6f52ebd45e5925c188d1a20119978aa4ffd5ef6", "type": "github" }, "original": { From ff46349ccbbadd87c35f7ee1f8a8f43e4a1d992e Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Thu, 18 Dec 2025 11:41:44 +1000 Subject: [PATCH 086/338] progress flake.lock --- flake.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/flake.lock b/flake.lock index 8a4e69c..26e5ad1 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1765722258, - "narHash": "sha256-qvH1BMbwLRhbWE9pxDZvA/1G/4cO8v0dGZXSF4M8fW0=", + "lastModified": 1766021230, + "narHash": "sha256-sC1pjYhurb7Dc6f6Di7CFxyk1sn+tsn++BRuin0r7tY=", "owner": "emilelcb", "repo": "nib", - "rev": "c1cb233c8f37dd555cfcd3e6b5a18256fb9cd06b", + "rev": "d0fceb7721aa316797a4632cdadea33daa2dbec3", "type": "github" }, "original": { @@ -74,11 +74,11 @@ }, "nixpkgs-unstable": { "locked": { - "lastModified": 1765472234, - "narHash": "sha256-9VvC20PJPsleGMewwcWYKGzDIyjckEz8uWmT0vCDYK0=", + "lastModified": 1765779637, + "narHash": "sha256-KJ2wa/BLSrTqDjbfyNx70ov/HdgNBCBBSQP3BIzKnv4=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "2fbfb1d73d239d2402a8fe03963e37aab15abe8b", + "rev": "1306659b587dc277866c7b69eb97e5f07864d8c4", "type": "github" }, "original": { @@ -90,11 +90,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1765608474, - "narHash": "sha256-9Wx53UK0z8Di5iesJID0tS1dRKwGxI4i7tsSanOHhF0=", + "lastModified": 1765838191, + "narHash": "sha256-m5KWt1nOm76ILk/JSCxBM4MfK3rYY7Wq9/TZIIeGnT8=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "28bb483c11a1214a73f9fd2d9928a6e2ea86ec71", + "rev": "c6f52ebd45e5925c188d1a20119978aa4ffd5ef6", "type": "github" }, "original": { From 1fa92c90d8ed7de17b558b4ccff95be6b75fe248 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Thu, 18 Dec 2025 11:54:47 +1000 Subject: [PATCH 087/338] progress flake.lock --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 26e5ad1..38ca47a 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1766021230, - "narHash": "sha256-sC1pjYhurb7Dc6f6Di7CFxyk1sn+tsn++BRuin0r7tY=", + "lastModified": 1766022866, + "narHash": "sha256-hQqqHIuGfhPWae/gDdC0xuPvR2jzOHphkiYmsgcBxrE=", "owner": "emilelcb", "repo": "nib", - "rev": "d0fceb7721aa316797a4632cdadea33daa2dbec3", + "rev": "9e3e2e4715eac54ea0ef9116266138409a7603b6", "type": "github" }, "original": { From 1016635230432f613df308a965fe42f1ccef8473 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Thu, 18 Dec 2025 11:54:47 +1000 Subject: [PATCH 088/338] progress flake.lock --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 26e5ad1..38ca47a 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1766021230, - "narHash": "sha256-sC1pjYhurb7Dc6f6Di7CFxyk1sn+tsn++BRuin0r7tY=", + "lastModified": 1766022866, + "narHash": "sha256-hQqqHIuGfhPWae/gDdC0xuPvR2jzOHphkiYmsgcBxrE=", "owner": "emilelcb", "repo": "nib", - "rev": "d0fceb7721aa316797a4632cdadea33daa2dbec3", + "rev": "9e3e2e4715eac54ea0ef9116266138409a7603b6", "type": "github" }, "original": { From 8466b5daebd4c9f1428762d1308fb3ac3f359411 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Thu, 18 Dec 2025 11:57:21 +1000 Subject: [PATCH 089/338] progress flake.lock --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 38ca47a..901254f 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1766022866, - "narHash": "sha256-hQqqHIuGfhPWae/gDdC0xuPvR2jzOHphkiYmsgcBxrE=", + "lastModified": 1766023030, + "narHash": "sha256-xW5oemq0Lv6FLeqM4j7Ou4gg9qWGLcStlln+uJFeJE0=", "owner": "emilelcb", "repo": "nib", - "rev": "9e3e2e4715eac54ea0ef9116266138409a7603b6", + "rev": "8199b2d4ff733c4f76df7345646e50faedb1d4ff", "type": "github" }, "original": { From 1b228a8dd0663f952deee78fbd282412dff7d50d Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Thu, 18 Dec 2025 11:57:21 +1000 Subject: [PATCH 090/338] progress flake.lock --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 38ca47a..901254f 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1766022866, - "narHash": "sha256-hQqqHIuGfhPWae/gDdC0xuPvR2jzOHphkiYmsgcBxrE=", + "lastModified": 1766023030, + "narHash": "sha256-xW5oemq0Lv6FLeqM4j7Ou4gg9qWGLcStlln+uJFeJE0=", "owner": "emilelcb", "repo": "nib", - "rev": "9e3e2e4715eac54ea0ef9116266138409a7603b6", + "rev": "8199b2d4ff733c4f76df7345646e50faedb1d4ff", "type": "github" }, "original": { From 557f86119190d698af23b4ba611911b919fbb627 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Thu, 18 Dec 2025 12:10:51 +1000 Subject: [PATCH 091/338] progress flake.lock --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 901254f..721cdd1 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1766023030, - "narHash": "sha256-xW5oemq0Lv6FLeqM4j7Ou4gg9qWGLcStlln+uJFeJE0=", + "lastModified": 1766023838, + "narHash": "sha256-3uNPuYnjSsuOeBzN0YtkP4f+HiKgKPjC86eet8MelnQ=", "owner": "emilelcb", "repo": "nib", - "rev": "8199b2d4ff733c4f76df7345646e50faedb1d4ff", + "rev": "58733245b936bc7428bcbda09a59e0b85fffa8d2", "type": "github" }, "original": { From fca705beed98c220c9270b84e9e28b6ac3ddf00d Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Thu, 18 Dec 2025 12:10:51 +1000 Subject: [PATCH 092/338] progress flake.lock --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 901254f..721cdd1 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1766023030, - "narHash": "sha256-xW5oemq0Lv6FLeqM4j7Ou4gg9qWGLcStlln+uJFeJE0=", + "lastModified": 1766023838, + "narHash": "sha256-3uNPuYnjSsuOeBzN0YtkP4f+HiKgKPjC86eet8MelnQ=", "owner": "emilelcb", "repo": "nib", - "rev": "8199b2d4ff733c4f76df7345646e50faedb1d4ff", + "rev": "58733245b936bc7428bcbda09a59e0b85fffa8d2", "type": "github" }, "original": { From f263f597a07bc0ed704b8fc3024104fe9ad62fb5 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Thu, 18 Dec 2025 12:23:12 +1000 Subject: [PATCH 093/338] DEBUG: why is node supposedly a list?? --- flake.nix | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/flake.nix b/flake.nix index 557764f..3170329 100644 --- a/flake.nix +++ b/flake.nix @@ -119,19 +119,20 @@ in rec { nixosConfigurations = mapNodes ( nodeName: node: - lib.nixosSystem { - system = node.system; - modules = node.modules; + assert false || abort "DEBUG: node has type ${builtins.typeOf node}"; + lib.nixosSystem { + system = node.system; + modules = node.modules; - # nix passes these to every single module - specialArgs = - node.modules.specialArgs - // { - inherit inputs; - pkgs = sys.pkgsFor node.system; - upkgs = sys.upkgsFor node.system; - }; - } + # nix passes these to every single module + specialArgs = + node.modules.specialArgs + // { + inherit inputs; + pkgs = sys.pkgsFor node.system; + upkgs = sys.upkgsFor node.system; + }; + } ); deploy.nodes = mapNodes (nodeName: node: let From e43545b3dd9b93ab7ffd190162d0447bd7a3a374 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Thu, 18 Dec 2025 12:23:12 +1000 Subject: [PATCH 094/338] DEBUG: why is node supposedly a list?? --- flake.nix | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/flake.nix b/flake.nix index 557764f..3170329 100644 --- a/flake.nix +++ b/flake.nix @@ -119,19 +119,20 @@ in rec { nixosConfigurations = mapNodes ( nodeName: node: - lib.nixosSystem { - system = node.system; - modules = node.modules; + assert false || abort "DEBUG: node has type ${builtins.typeOf node}"; + lib.nixosSystem { + system = node.system; + modules = node.modules; - # nix passes these to every single module - specialArgs = - node.modules.specialArgs - // { - inherit inputs; - pkgs = sys.pkgsFor node.system; - upkgs = sys.upkgsFor node.system; - }; - } + # nix passes these to every single module + specialArgs = + node.modules.specialArgs + // { + inherit inputs; + pkgs = sys.pkgsFor node.system; + upkgs = sys.upkgsFor node.system; + }; + } ); deploy.nodes = mapNodes (nodeName: node: let From 4cac8bc1282b1a4cda46f31b98e09546be4dc4b9 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Thu, 18 Dec 2025 12:23:17 +1000 Subject: [PATCH 095/338] progress flake.lock --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 721cdd1..e49a3a3 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1766023838, - "narHash": "sha256-3uNPuYnjSsuOeBzN0YtkP4f+HiKgKPjC86eet8MelnQ=", + "lastModified": 1766024569, + "narHash": "sha256-XXUmy+y6K0sR71qf2GRYKObeZLXZIHZU0vuqkF5muhY=", "owner": "emilelcb", "repo": "nib", - "rev": "58733245b936bc7428bcbda09a59e0b85fffa8d2", + "rev": "d4a7591a0c264cd54afb8af3b3b07064d4297d70", "type": "github" }, "original": { From 71b0498e0d7fa952fc5ea62a250a604321920d28 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Thu, 18 Dec 2025 12:23:17 +1000 Subject: [PATCH 096/338] progress flake.lock --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 721cdd1..e49a3a3 100644 --- a/flake.lock +++ b/flake.lock @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1766023838, - "narHash": "sha256-3uNPuYnjSsuOeBzN0YtkP4f+HiKgKPjC86eet8MelnQ=", + "lastModified": 1766024569, + "narHash": "sha256-XXUmy+y6K0sR71qf2GRYKObeZLXZIHZU0vuqkF5muhY=", "owner": "emilelcb", "repo": "nib", - "rev": "58733245b936bc7428bcbda09a59e0b85fffa8d2", + "rev": "d4a7591a0c264cd54afb8af3b3b07064d4297d70", "type": "github" }, "original": { From 77d09f1a87e898b8c7b82f808325eeef85b43b35 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Thu, 18 Dec 2025 12:25:59 +1000 Subject: [PATCH 097/338] nevermind it is a set --- flake.nix | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/flake.nix b/flake.nix index 3170329..557764f 100644 --- a/flake.nix +++ b/flake.nix @@ -119,20 +119,19 @@ in rec { nixosConfigurations = mapNodes ( nodeName: node: - assert false || abort "DEBUG: node has type ${builtins.typeOf node}"; - lib.nixosSystem { - system = node.system; - modules = node.modules; + lib.nixosSystem { + system = node.system; + modules = node.modules; - # nix passes these to every single module - specialArgs = - node.modules.specialArgs - // { - inherit inputs; - pkgs = sys.pkgsFor node.system; - upkgs = sys.upkgsFor node.system; - }; - } + # nix passes these to every single module + specialArgs = + node.modules.specialArgs + // { + inherit inputs; + pkgs = sys.pkgsFor node.system; + upkgs = sys.upkgsFor node.system; + }; + } ); deploy.nodes = mapNodes (nodeName: node: let From f3e7b6364128c36848f67deb508f49d4b179c5b8 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Thu, 18 Dec 2025 12:25:59 +1000 Subject: [PATCH 098/338] nevermind it is a set --- flake.nix | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/flake.nix b/flake.nix index 3170329..557764f 100644 --- a/flake.nix +++ b/flake.nix @@ -119,20 +119,19 @@ in rec { nixosConfigurations = mapNodes ( nodeName: node: - assert false || abort "DEBUG: node has type ${builtins.typeOf node}"; - lib.nixosSystem { - system = node.system; - modules = node.modules; + lib.nixosSystem { + system = node.system; + modules = node.modules; - # nix passes these to every single module - specialArgs = - node.modules.specialArgs - // { - inherit inputs; - pkgs = sys.pkgsFor node.system; - upkgs = sys.upkgsFor node.system; - }; - } + # nix passes these to every single module + specialArgs = + node.modules.specialArgs + // { + inherit inputs; + pkgs = sys.pkgsFor node.system; + upkgs = sys.upkgsFor node.system; + }; + } ); deploy.nodes = mapNodes (nodeName: node: let From a7e506ced94de39b08bba95c37e4bceca28e4266 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Thu, 18 Dec 2025 12:37:32 +1000 Subject: [PATCH 099/338] DEBUG: use local flakes --- flake.lock | 15 ++++++--------- flake.nix | 2 +- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/flake.lock b/flake.lock index e49a3a3..44792d2 100644 --- a/flake.lock +++ b/flake.lock @@ -43,17 +43,14 @@ ] }, "locked": { - "lastModified": 1766024569, - "narHash": "sha256-XXUmy+y6K0sR71qf2GRYKObeZLXZIHZU0vuqkF5muhY=", - "owner": "emilelcb", - "repo": "nib", - "rev": "d4a7591a0c264cd54afb8af3b3b07064d4297d70", - "type": "github" + "lastModified": 1766024923, + "narHash": "sha256-wmxmsx1RmVEUTa9Gs3pXn26Sm7avpIQuKdXZVEqhnLA=", + "path": "/home/emile/nib", + "type": "path" }, "original": { - "owner": "emilelcb", - "repo": "nib", - "type": "github" + "path": "/home/emile/nib", + "type": "path" } }, "nixpkgs": { diff --git a/flake.nix b/flake.nix index 557764f..8b59c38 100644 --- a/flake.nix +++ b/flake.nix @@ -21,7 +21,7 @@ nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixos-unstable"; nib = { - url = "github:emilelcb/nib"; + url = "path:/home/emile/nib"; inputs.systems.follows = "systems"; }; From cd859856d933f37cb2a3658cf46c2f77269bfae6 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Thu, 18 Dec 2025 12:37:32 +1000 Subject: [PATCH 100/338] DEBUG: use local flakes --- flake.lock | 15 ++++++--------- flake.nix | 2 +- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/flake.lock b/flake.lock index e49a3a3..44792d2 100644 --- a/flake.lock +++ b/flake.lock @@ -43,17 +43,14 @@ ] }, "locked": { - "lastModified": 1766024569, - "narHash": "sha256-XXUmy+y6K0sR71qf2GRYKObeZLXZIHZU0vuqkF5muhY=", - "owner": "emilelcb", - "repo": "nib", - "rev": "d4a7591a0c264cd54afb8af3b3b07064d4297d70", - "type": "github" + "lastModified": 1766024923, + "narHash": "sha256-wmxmsx1RmVEUTa9Gs3pXn26Sm7avpIQuKdXZVEqhnLA=", + "path": "/home/emile/nib", + "type": "path" }, "original": { - "owner": "emilelcb", - "repo": "nib", - "type": "github" + "path": "/home/emile/nib", + "type": "path" } }, "nixpkgs": { diff --git a/flake.nix b/flake.nix index 557764f..8b59c38 100644 --- a/flake.nix +++ b/flake.nix @@ -21,7 +21,7 @@ nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixos-unstable"; nib = { - url = "github:emilelcb/nib"; + url = "path:/home/emile/nib"; inputs.systems.follows = "systems"; }; From 4784dddffb70c54e02b3e135a0a9bda2698fc2cf Mon Sep 17 00:00:00 2001 From: _cry64 Date: Thu, 18 Dec 2025 12:42:46 +1000 Subject: [PATCH 101/338] type: "node.modules.specialArgs" --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 8b59c38..8ab6e22 100644 --- a/flake.nix +++ b/flake.nix @@ -125,7 +125,7 @@ # nix passes these to every single module specialArgs = - node.modules.specialArgs + node.specialArgs // { inherit inputs; pkgs = sys.pkgsFor node.system; From 796fbc88e9fd3816abf2ebc79c580aaea4b92a61 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Thu, 18 Dec 2025 12:42:46 +1000 Subject: [PATCH 102/338] type: "node.modules.specialArgs" --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 8b59c38..8ab6e22 100644 --- a/flake.nix +++ b/flake.nix @@ -125,7 +125,7 @@ # nix passes these to every single module specialArgs = - node.modules.specialArgs + node.specialArgs // { inherit inputs; pkgs = sys.pkgsFor node.system; From 4692bcb12bf0a16088887663fa1beaeacd9a83fe Mon Sep 17 00:00:00 2001 From: _cry64 Date: Thu, 18 Dec 2025 12:48:49 +1000 Subject: [PATCH 103/338] progress flake.lock --- flake.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flake.lock b/flake.lock index 44792d2..115ca7b 100644 --- a/flake.lock +++ b/flake.lock @@ -43,8 +43,8 @@ ] }, "locked": { - "lastModified": 1766024923, - "narHash": "sha256-wmxmsx1RmVEUTa9Gs3pXn26Sm7avpIQuKdXZVEqhnLA=", + "lastModified": 1766026109, + "narHash": "sha256-z4u9D+rWnvzEQRjUmxoGS1wA0/+hEU09Qk6H1WpFn64=", "path": "/home/emile/nib", "type": "path" }, From 26c2051fa4d401fff297325af3af512129be9a71 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Thu, 18 Dec 2025 12:48:49 +1000 Subject: [PATCH 104/338] progress flake.lock --- flake.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flake.lock b/flake.lock index 44792d2..115ca7b 100644 --- a/flake.lock +++ b/flake.lock @@ -43,8 +43,8 @@ ] }, "locked": { - "lastModified": 1766024923, - "narHash": "sha256-wmxmsx1RmVEUTa9Gs3pXn26Sm7avpIQuKdXZVEqhnLA=", + "lastModified": 1766026109, + "narHash": "sha256-z4u9D+rWnvzEQRjUmxoGS1wA0/+hEU09Qk6H1WpFn64=", "path": "/home/emile/nib", "type": "path" }, From 31b8c6f163c2b793e38778bab148dc8eab6489b4 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Thu, 18 Dec 2025 13:12:16 +1000 Subject: [PATCH 105/338] return to remote flake inputs --- flake.lock | 13 ++++++++----- flake.nix | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/flake.lock b/flake.lock index 115ca7b..100da43 100644 --- a/flake.lock +++ b/flake.lock @@ -44,13 +44,16 @@ }, "locked": { "lastModified": 1766026109, - "narHash": "sha256-z4u9D+rWnvzEQRjUmxoGS1wA0/+hEU09Qk6H1WpFn64=", - "path": "/home/emile/nib", - "type": "path" + "narHash": "sha256-CcaDBnaHtFApR66IeFzB41+ENfVZzey32DAMLMrBvKU=", + "owner": "emilelcb", + "repo": "nib", + "rev": "df8f10c468a532676fbb5099e7c4f61368385bd8", + "type": "github" }, "original": { - "path": "/home/emile/nib", - "type": "path" + "owner": "emilelcb", + "repo": "nib", + "type": "github" } }, "nixpkgs": { diff --git a/flake.nix b/flake.nix index 8ab6e22..f56d078 100644 --- a/flake.nix +++ b/flake.nix @@ -21,7 +21,7 @@ nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixos-unstable"; nib = { - url = "path:/home/emile/nib"; + url = "github:emilelcb/nib"; inputs.systems.follows = "systems"; }; From 0f49bbbd6e23fe8b0e798d04d61902c47950438e Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Thu, 18 Dec 2025 13:12:16 +1000 Subject: [PATCH 106/338] return to remote flake inputs --- flake.lock | 13 ++++++++----- flake.nix | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/flake.lock b/flake.lock index 115ca7b..100da43 100644 --- a/flake.lock +++ b/flake.lock @@ -44,13 +44,16 @@ }, "locked": { "lastModified": 1766026109, - "narHash": "sha256-z4u9D+rWnvzEQRjUmxoGS1wA0/+hEU09Qk6H1WpFn64=", - "path": "/home/emile/nib", - "type": "path" + "narHash": "sha256-CcaDBnaHtFApR66IeFzB41+ENfVZzey32DAMLMrBvKU=", + "owner": "emilelcb", + "repo": "nib", + "rev": "df8f10c468a532676fbb5099e7c4f61368385bd8", + "type": "github" }, "original": { - "path": "/home/emile/nib", - "type": "path" + "owner": "emilelcb", + "repo": "nib", + "type": "github" } }, "nixpkgs": { diff --git a/flake.nix b/flake.nix index 8ab6e22..f56d078 100644 --- a/flake.nix +++ b/flake.nix @@ -21,7 +21,7 @@ nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixos-unstable"; nib = { - url = "path:/home/emile/nib"; + url = "github:emilelcb/nib"; inputs.systems.follows = "systems"; }; From cf5a6bbc00864e88bf9c3e33824872f5efd577f3 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Thu, 18 Dec 2025 13:21:03 +1000 Subject: [PATCH 107/338] default to ssh-agent propagation --- flake.nix | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/flake.nix b/flake.nix index f56d078..4aaa4a2 100644 --- a/flake.nix +++ b/flake.nix @@ -162,6 +162,11 @@ if builtins.elem "-p" node.deploy.ssh.opts then [] else ["-p" (toString node.deploy.ssh.port)] + ) + ++ ( + if builtins.elem "-A" node.deploy.ssh.opts + then [] + else ["-A"] ); }; }); From aef76779ed38cc6aaa2b2709b650e0fef198fede Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Thu, 18 Dec 2025 13:21:03 +1000 Subject: [PATCH 108/338] default to ssh-agent propagation --- flake.nix | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/flake.nix b/flake.nix index f56d078..4aaa4a2 100644 --- a/flake.nix +++ b/flake.nix @@ -162,6 +162,11 @@ if builtins.elem "-p" node.deploy.ssh.opts then [] else ["-p" (toString node.deploy.ssh.port)] + ) + ++ ( + if builtins.elem "-A" node.deploy.ssh.opts + then [] + else ["-A"] ); }; }); From c52ea75e205c8ea948c1dd886da5077cde16864d Mon Sep 17 00:00:00 2001 From: _cry64 Date: Wed, 7 Jan 2026 21:40:40 +1000 Subject: [PATCH 109/338] add bash ceru tool --- ceru/ceru | 44 +++++++++++++++ ceru/libceru.sh | 105 ++++++++++++++++++++++++++++++++++++ ceru/subcmds/new/default.sh | 34 ++++++++++++ ceru/subcmds/new/key | 100 ++++++++++++++++++++++++++++++++++ 4 files changed, 283 insertions(+) create mode 100755 ceru/ceru create mode 100755 ceru/libceru.sh create mode 100755 ceru/subcmds/new/default.sh create mode 100755 ceru/subcmds/new/key diff --git a/ceru/ceru b/ceru/ceru new file mode 100755 index 0000000..bc4a4b1 --- /dev/null +++ b/ceru/ceru @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +set -euo pipefail + +# ======== CONFIGURATION ======== +CERU_USE_NIX3=true +# ======== CONFIGURATION ======== + +# libceru env vars +THIS=$(basename "$0") +source libceru.sh + +USAGE="${BOLD}${UNDERLINE}${RED}Usage${RESET} + ${BOLD}${GREEN}$THIS [option...] subcommand${RESET} + +${BOLD}${UNDERLINE}${RED}Options${RESET} + ${BOLD}${MAGENTA}-h, --help${RESET} Show this message (^_^) + +${BOLD}${UNDERLINE}${RED}Subcommands${RESET} + ${BOLD}${CYAN}deploy${RESET} Deploy your Cerulean network + ${BOLD}${CYAN}new${RESET} Init new instances of various components (see ${BOLD}${GREEN}\`$THIS new --help\`${RESET})" + +# parse all args +SUBCMD=false # whether a subcommand was specified +while [[ $# -gt 0 ]]; do + ARG=$1 + case $ARG in + -h|--help) + throw-usage 0 ;; + -*) + echo "[!] Unknown option \"$ARG\"" + exit 1 ;; + *) + SUBCMD=true + break ;; + esac +done; unset -v ARG + +# invalid usage occurs if no args or subcommand given +if [[ $# = 0 || "$SUBCMD" = false ]]; then + throw-usage 1 +fi; unset -v SUBCMD + +# run provided subcommand +run-subcmd "$@" diff --git a/ceru/libceru.sh b/ceru/libceru.sh new file mode 100755 index 0000000..92d21d7 --- /dev/null +++ b/ceru/libceru.sh @@ -0,0 +1,105 @@ +#!/usr/bin/env bash + +# libceru relies on the script that sources +# it to set the following environment variables: +# CMD_NAME USAGE +set -u + +# ======== INTERNAL STATE ======== +SUBCMDS="$PWD/subcmds" # XXX: TODO: don't hardcode as relative +CMD_ABS="$SUBCMDS" # (initial value) CMD_ABS stores the current cmd's absolute path +CMD_MAJ="$THIS" # (initial value) CMD_MAJ stores the current cmd's parent's name +CMD_MIN="" # (initial value) CMD_MIN stores the current cmd's name +# ======== INTERNAL STATE ======== + +# ANSI Coloring +BLACK='\033[30m' +RED='\033[31m' +GREEN='\033[32m' +YELLOW='\033[33m' +BLUE='\033[34m' +MAGENTA='\033[35m' +CYAN='\033[36m' +WHITE='\033[37m' +DEFCOL='\033[39m' # default colour + +# ANSI Styling +RESET='\033[0m' +BOLD='\033[1m' +DIM='\033[2m' +ITALIC='\033[3m' +UNDERLINE='\033[4m' +BLINKSLOW='\033[5m' +BLINKFAST='\033[6m' +REVERSE='\033[7m' +INVISIBLE='\033[8m' + +# Error Messages +function perr { echo -e "${BOLD}${RED}error:${RESET} $@\nTry ${BOLD}${GREEN}'--help'${RESET} for more information." >&2; } +function perr-usage { echo -e "$USAGE" >&2; } +function perr-badflag { perr "unrecognised flag ${BOLD}${MAGENTA}'$1'${RESET}"; } +function perr-noflagval { perr "flag ${BOLD}${MAGENTA}'$1'${RESET} requires ${BOLD}${MAGENTA}${2}${RESET} argument(s), but only ${BOLD}${MAGENTA}${3}${RESET} were given"; } +function perr-badarg { perr "unrecognised arg ${BOLD}${MAGENTA}'$1'${RESET}"; } +function perr-noarg { perr "required argument ${BOLD}${MAGENTA}'$1'${RESET} is missing"; } +# Failures +function throw { echo -e "${@:2}" >&2; if [[ "$1" -ge 0 ]]; then exit "$1"; fi; } +function throw-usage { throw "$1" "$(perr-usage 2>&1)"; } +function throw-badflag { throw "$1" "$(perr-badflag "${@:2}" 2>&1)"; } +function throw-noflagval { throw "$1" "$(perr-noflagval "${@:2}" 2>&1)"; } +function throw-badarg { throw "$1" "$(perr-badarg "${@:2}" 2>&1)"; } +function throw-noarg { throw "$1" "$(perr-noarg "${@:2}" 2>&1)"; } +# Parsing/Validation +function required { [[ -n "$1" ]] || throw-noarg 1 "${@:2}"; } + +# Other +function confirm-action { + local CHAR + while :; do + echo -e "$1" + read -n1 CHAR + case $CHAR in + [yY]) + return 0 ;; + [nN]) + return 1 ;; + esac + done +} +function confirm { confirm-action ":: Proceed? [Y/n] "; } + +function confirm-file-overwrite { + echo -e "${BOLD}${UNDERLINE}${BLINKFAST}${RED}WARNING!${RESET} ${YELLOW}The following files will be overwritten:${RESET}" + local ARG="" + for ARG in "$@"; do + echo -e "${BOLD} • ${GREEN}${ARG}${RESET}" + done + confirm +} + +# ====== Core ====== +function run-subcmd { + # if CMD_MIN is empty, then CMD_MAJ is the root cmd (ie ceru) + # and hence CMD_MAJ shouldn't yet be overridden + if [[ ! "$CMD_MIN" = "" ]]; then + CMD_MAJ="$CMD_MIN" # swapsies! + fi + # we shift here so $@ is passed correctly when we `source "$TARGET"` + CMD_MIN="$1"; shift + + # ensure the current command can take subcommands + if [[ -f "$CMD_ABS" ]]; then + # XXX: INTERNAL ERROR + throw 2 "Subcommand \"$CMD_MIN\" cannot exist, as $CMD_MAJ has no subcommands!" + fi + + CMD_ABS="$CMD_ABS/$CMD_MIN" + + # attempt to find the script corresponding to CMD_ABS + TARGET="$CMD_ABS" + if [[ -d "$CMD_ABS" ]]; then + TARGET="$TARGET/default.sh" + elif [[ ! -f "$CMD_ABS" ]]; then + throw 1 "Command \"$CMD_MAJ\" does not provide subcommand \"$CMD_MIN\"!" + fi + source "$TARGET" +} diff --git a/ceru/subcmds/new/default.sh b/ceru/subcmds/new/default.sh new file mode 100755 index 0000000..8cfce8f --- /dev/null +++ b/ceru/subcmds/new/default.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +set -euo pipefail +USAGE="${BOLD}${UNDERLINE}${RED}Usage${RESET} + ${BOLD}${GREEN}$THIS new [option...] subcommand${RESET} + +${BOLD}${UNDERLINE}${RED}Options${RESET} + ${BOLD}${MAGENTA}-h, --help${RESET} Show this message (^_^) + +${BOLD}${UNDERLINE}${RED}Subcommands${RESET} + ${BOLD}${CYAN}key${RESET} Generate a new binary-cache signing keypair" + +# parse all args +SUBCMD=false # where a subcommand was specified +while [[ $# -gt 0 ]]; do + ARG=$1 + case $ARG in + -h|--help) + throw-usage 0 ;; + -*) + echo "[!] Unknown option \"$ARG\"" + exit 1 ;; + *) + SUBCMD=true + break ;; + esac +done; unset -v ARG + +# invalid usage occurs if no args or subcommand given +if [[ $# = 0 || "$SUBCMD" = false ]]; then + throw-usage 1 +fi; unset -v SUBCMD + +# run provided subcommand +run-subcmd "$@" diff --git a/ceru/subcmds/new/key b/ceru/subcmds/new/key new file mode 100755 index 0000000..41b4916 --- /dev/null +++ b/ceru/subcmds/new/key @@ -0,0 +1,100 @@ +#!/usr/bin/env bash +set -euo pipefail +USAGE="${BOLD}${UNDERLINE}${RED}Usage${RESET} + ${BOLD}${GREEN}$THIS new key [option...]${RESET} + +${BOLD}${UNDERLINE}${RED}Options${RESET} + ${BOLD}${MAGENTA}-h, --help${RESET} Show this message (^_^) + ${BOLD}${MAGENTA}-f, --force${RESET} Ignores all warnings!! + ${BOLD}${MAGENTA}-j, --json${RESET} Output in JSON format + ${BOLD}${MAGENTA}-n, --name${RESET} Identifier of the key (e.g. ${BOLD}${MAGENTA}cache.example.org-1${RESET}) + ${BOLD}${MAGENTA}-o, --out${RESET} Private key file name to write to (the public key is named identically but ends with ${BOLD}${MAGENTA}.pub${RESET}) + ${BOLD}${MAGENTA}-P, --private-only${RESET} Only generate the private key, not the entire keypair" + +# ==== Argument Values ==== +FORCE=false +PRIV_ONLY=false +JSON=false +NAME="" +SINK="" +# ==== Argument Values ==== + +# parse all args +while [[ $# -gt 0 ]]; do + ARG="$1" + case "$ARG" in + -h|--help) + throw-usage 0 + ;; + -n|--name) + shift + # XXX: NOTE: do I need to safe shift (shift || true) since -e is set? + NAME="$1"; shift + ;; + -P|--private-only) + shift + PRIV_ONLY=true + ;; + -o|--out) + shift + # XXX: NOTE: do I need to safe shift (shift || true) since -e is set? + SINK="$1"; shift + ;; + -j|--json) + shift + JSON=true + ;; + -f|--force) + shift + FORCE=true + ;; + + -*) + throw-badflag 1 "$ARG" + ;; + *) + throw-badarg 1 "$ARG" + ;; + esac +done; unset -v ARG + +# fail if NAME not provided +required "$NAME" --name + + +# generate our keypair +PRIV_KEY=$(nix key generate-secret --key-name "$NAME") +PUB_KEY=$(nix key convert-secret-to-public <<<"$PRIV_KEY") + +# result defaults to unset (only stays unset if we intend on writing to a file) +RESULT="" +# JSON formatting +if [[ "$JSON" = true ]]; then + RESULT="{ + \"privateKey\": \"${PRIV_KEY}\"$( + [[ "$PRIV_ONLY" = true ]] \ + || echo ",\n \"publicKey\": \"${PUB_KEY}\"") +}" + if [[ -n "$SINK" ]]; then + # confirm the user understands files will be overwritten + [[ "$FORCE" = true ]] || confirm-file-overwrite "$SINK" || exit 0 + echo -e "$RESULT" > "$SINK" + else + echo -e "$RESULT" + fi +# standard formatting (stdout) +elif [[ -z "$SINK" ]]; then + echo -e "${BOLD}${UNDERLINE}${RED}Private Key${RESET} + ${BOLD}${GREEN}${PRIV_KEY}${RESET}$( + [[ "$PRIV_ONLY" = true ]] \ + || echo "\n${BOLD}${UNDERLINE}${RED}Public Key${RESET}\n ${BOLD}${GREEN}${PUB_KEY}${RESET}")" +# standard formatting (files) +else + PRIV_SINK="$SINK" + PUB_SINK="$SINK.pub" + # confirm the user understands files will be overwritten + [[ "$FORCE" = true ]] || confirm-file-overwrite "$PRIV_SINK" "$PUB_SINK" || exit 0 + echo "$PRIV_KEY" > "$PRIV_SINK" + echo "$PUB_KEY" > "$PUB_SINK" +fi; +unset -v PRIV_SINK PUB_SINK PRIV_KEY PUB_KEY RESULT From b366820ddff7ceffa098ea9b54af77ca32b38401 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Wed, 7 Jan 2026 21:40:40 +1000 Subject: [PATCH 110/338] add bash ceru tool --- ceru/ceru | 44 +++++++++++++++ ceru/libceru.sh | 105 ++++++++++++++++++++++++++++++++++++ ceru/subcmds/new/default.sh | 34 ++++++++++++ ceru/subcmds/new/key | 100 ++++++++++++++++++++++++++++++++++ 4 files changed, 283 insertions(+) create mode 100755 ceru/ceru create mode 100755 ceru/libceru.sh create mode 100755 ceru/subcmds/new/default.sh create mode 100755 ceru/subcmds/new/key diff --git a/ceru/ceru b/ceru/ceru new file mode 100755 index 0000000..bc4a4b1 --- /dev/null +++ b/ceru/ceru @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +set -euo pipefail + +# ======== CONFIGURATION ======== +CERU_USE_NIX3=true +# ======== CONFIGURATION ======== + +# libceru env vars +THIS=$(basename "$0") +source libceru.sh + +USAGE="${BOLD}${UNDERLINE}${RED}Usage${RESET} + ${BOLD}${GREEN}$THIS [option...] subcommand${RESET} + +${BOLD}${UNDERLINE}${RED}Options${RESET} + ${BOLD}${MAGENTA}-h, --help${RESET} Show this message (^_^) + +${BOLD}${UNDERLINE}${RED}Subcommands${RESET} + ${BOLD}${CYAN}deploy${RESET} Deploy your Cerulean network + ${BOLD}${CYAN}new${RESET} Init new instances of various components (see ${BOLD}${GREEN}\`$THIS new --help\`${RESET})" + +# parse all args +SUBCMD=false # whether a subcommand was specified +while [[ $# -gt 0 ]]; do + ARG=$1 + case $ARG in + -h|--help) + throw-usage 0 ;; + -*) + echo "[!] Unknown option \"$ARG\"" + exit 1 ;; + *) + SUBCMD=true + break ;; + esac +done; unset -v ARG + +# invalid usage occurs if no args or subcommand given +if [[ $# = 0 || "$SUBCMD" = false ]]; then + throw-usage 1 +fi; unset -v SUBCMD + +# run provided subcommand +run-subcmd "$@" diff --git a/ceru/libceru.sh b/ceru/libceru.sh new file mode 100755 index 0000000..92d21d7 --- /dev/null +++ b/ceru/libceru.sh @@ -0,0 +1,105 @@ +#!/usr/bin/env bash + +# libceru relies on the script that sources +# it to set the following environment variables: +# CMD_NAME USAGE +set -u + +# ======== INTERNAL STATE ======== +SUBCMDS="$PWD/subcmds" # XXX: TODO: don't hardcode as relative +CMD_ABS="$SUBCMDS" # (initial value) CMD_ABS stores the current cmd's absolute path +CMD_MAJ="$THIS" # (initial value) CMD_MAJ stores the current cmd's parent's name +CMD_MIN="" # (initial value) CMD_MIN stores the current cmd's name +# ======== INTERNAL STATE ======== + +# ANSI Coloring +BLACK='\033[30m' +RED='\033[31m' +GREEN='\033[32m' +YELLOW='\033[33m' +BLUE='\033[34m' +MAGENTA='\033[35m' +CYAN='\033[36m' +WHITE='\033[37m' +DEFCOL='\033[39m' # default colour + +# ANSI Styling +RESET='\033[0m' +BOLD='\033[1m' +DIM='\033[2m' +ITALIC='\033[3m' +UNDERLINE='\033[4m' +BLINKSLOW='\033[5m' +BLINKFAST='\033[6m' +REVERSE='\033[7m' +INVISIBLE='\033[8m' + +# Error Messages +function perr { echo -e "${BOLD}${RED}error:${RESET} $@\nTry ${BOLD}${GREEN}'--help'${RESET} for more information." >&2; } +function perr-usage { echo -e "$USAGE" >&2; } +function perr-badflag { perr "unrecognised flag ${BOLD}${MAGENTA}'$1'${RESET}"; } +function perr-noflagval { perr "flag ${BOLD}${MAGENTA}'$1'${RESET} requires ${BOLD}${MAGENTA}${2}${RESET} argument(s), but only ${BOLD}${MAGENTA}${3}${RESET} were given"; } +function perr-badarg { perr "unrecognised arg ${BOLD}${MAGENTA}'$1'${RESET}"; } +function perr-noarg { perr "required argument ${BOLD}${MAGENTA}'$1'${RESET} is missing"; } +# Failures +function throw { echo -e "${@:2}" >&2; if [[ "$1" -ge 0 ]]; then exit "$1"; fi; } +function throw-usage { throw "$1" "$(perr-usage 2>&1)"; } +function throw-badflag { throw "$1" "$(perr-badflag "${@:2}" 2>&1)"; } +function throw-noflagval { throw "$1" "$(perr-noflagval "${@:2}" 2>&1)"; } +function throw-badarg { throw "$1" "$(perr-badarg "${@:2}" 2>&1)"; } +function throw-noarg { throw "$1" "$(perr-noarg "${@:2}" 2>&1)"; } +# Parsing/Validation +function required { [[ -n "$1" ]] || throw-noarg 1 "${@:2}"; } + +# Other +function confirm-action { + local CHAR + while :; do + echo -e "$1" + read -n1 CHAR + case $CHAR in + [yY]) + return 0 ;; + [nN]) + return 1 ;; + esac + done +} +function confirm { confirm-action ":: Proceed? [Y/n] "; } + +function confirm-file-overwrite { + echo -e "${BOLD}${UNDERLINE}${BLINKFAST}${RED}WARNING!${RESET} ${YELLOW}The following files will be overwritten:${RESET}" + local ARG="" + for ARG in "$@"; do + echo -e "${BOLD} • ${GREEN}${ARG}${RESET}" + done + confirm +} + +# ====== Core ====== +function run-subcmd { + # if CMD_MIN is empty, then CMD_MAJ is the root cmd (ie ceru) + # and hence CMD_MAJ shouldn't yet be overridden + if [[ ! "$CMD_MIN" = "" ]]; then + CMD_MAJ="$CMD_MIN" # swapsies! + fi + # we shift here so $@ is passed correctly when we `source "$TARGET"` + CMD_MIN="$1"; shift + + # ensure the current command can take subcommands + if [[ -f "$CMD_ABS" ]]; then + # XXX: INTERNAL ERROR + throw 2 "Subcommand \"$CMD_MIN\" cannot exist, as $CMD_MAJ has no subcommands!" + fi + + CMD_ABS="$CMD_ABS/$CMD_MIN" + + # attempt to find the script corresponding to CMD_ABS + TARGET="$CMD_ABS" + if [[ -d "$CMD_ABS" ]]; then + TARGET="$TARGET/default.sh" + elif [[ ! -f "$CMD_ABS" ]]; then + throw 1 "Command \"$CMD_MAJ\" does not provide subcommand \"$CMD_MIN\"!" + fi + source "$TARGET" +} diff --git a/ceru/subcmds/new/default.sh b/ceru/subcmds/new/default.sh new file mode 100755 index 0000000..8cfce8f --- /dev/null +++ b/ceru/subcmds/new/default.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +set -euo pipefail +USAGE="${BOLD}${UNDERLINE}${RED}Usage${RESET} + ${BOLD}${GREEN}$THIS new [option...] subcommand${RESET} + +${BOLD}${UNDERLINE}${RED}Options${RESET} + ${BOLD}${MAGENTA}-h, --help${RESET} Show this message (^_^) + +${BOLD}${UNDERLINE}${RED}Subcommands${RESET} + ${BOLD}${CYAN}key${RESET} Generate a new binary-cache signing keypair" + +# parse all args +SUBCMD=false # where a subcommand was specified +while [[ $# -gt 0 ]]; do + ARG=$1 + case $ARG in + -h|--help) + throw-usage 0 ;; + -*) + echo "[!] Unknown option \"$ARG\"" + exit 1 ;; + *) + SUBCMD=true + break ;; + esac +done; unset -v ARG + +# invalid usage occurs if no args or subcommand given +if [[ $# = 0 || "$SUBCMD" = false ]]; then + throw-usage 1 +fi; unset -v SUBCMD + +# run provided subcommand +run-subcmd "$@" diff --git a/ceru/subcmds/new/key b/ceru/subcmds/new/key new file mode 100755 index 0000000..41b4916 --- /dev/null +++ b/ceru/subcmds/new/key @@ -0,0 +1,100 @@ +#!/usr/bin/env bash +set -euo pipefail +USAGE="${BOLD}${UNDERLINE}${RED}Usage${RESET} + ${BOLD}${GREEN}$THIS new key [option...]${RESET} + +${BOLD}${UNDERLINE}${RED}Options${RESET} + ${BOLD}${MAGENTA}-h, --help${RESET} Show this message (^_^) + ${BOLD}${MAGENTA}-f, --force${RESET} Ignores all warnings!! + ${BOLD}${MAGENTA}-j, --json${RESET} Output in JSON format + ${BOLD}${MAGENTA}-n, --name${RESET} Identifier of the key (e.g. ${BOLD}${MAGENTA}cache.example.org-1${RESET}) + ${BOLD}${MAGENTA}-o, --out${RESET} Private key file name to write to (the public key is named identically but ends with ${BOLD}${MAGENTA}.pub${RESET}) + ${BOLD}${MAGENTA}-P, --private-only${RESET} Only generate the private key, not the entire keypair" + +# ==== Argument Values ==== +FORCE=false +PRIV_ONLY=false +JSON=false +NAME="" +SINK="" +# ==== Argument Values ==== + +# parse all args +while [[ $# -gt 0 ]]; do + ARG="$1" + case "$ARG" in + -h|--help) + throw-usage 0 + ;; + -n|--name) + shift + # XXX: NOTE: do I need to safe shift (shift || true) since -e is set? + NAME="$1"; shift + ;; + -P|--private-only) + shift + PRIV_ONLY=true + ;; + -o|--out) + shift + # XXX: NOTE: do I need to safe shift (shift || true) since -e is set? + SINK="$1"; shift + ;; + -j|--json) + shift + JSON=true + ;; + -f|--force) + shift + FORCE=true + ;; + + -*) + throw-badflag 1 "$ARG" + ;; + *) + throw-badarg 1 "$ARG" + ;; + esac +done; unset -v ARG + +# fail if NAME not provided +required "$NAME" --name + + +# generate our keypair +PRIV_KEY=$(nix key generate-secret --key-name "$NAME") +PUB_KEY=$(nix key convert-secret-to-public <<<"$PRIV_KEY") + +# result defaults to unset (only stays unset if we intend on writing to a file) +RESULT="" +# JSON formatting +if [[ "$JSON" = true ]]; then + RESULT="{ + \"privateKey\": \"${PRIV_KEY}\"$( + [[ "$PRIV_ONLY" = true ]] \ + || echo ",\n \"publicKey\": \"${PUB_KEY}\"") +}" + if [[ -n "$SINK" ]]; then + # confirm the user understands files will be overwritten + [[ "$FORCE" = true ]] || confirm-file-overwrite "$SINK" || exit 0 + echo -e "$RESULT" > "$SINK" + else + echo -e "$RESULT" + fi +# standard formatting (stdout) +elif [[ -z "$SINK" ]]; then + echo -e "${BOLD}${UNDERLINE}${RED}Private Key${RESET} + ${BOLD}${GREEN}${PRIV_KEY}${RESET}$( + [[ "$PRIV_ONLY" = true ]] \ + || echo "\n${BOLD}${UNDERLINE}${RED}Public Key${RESET}\n ${BOLD}${GREEN}${PUB_KEY}${RESET}")" +# standard formatting (files) +else + PRIV_SINK="$SINK" + PUB_SINK="$SINK.pub" + # confirm the user understands files will be overwritten + [[ "$FORCE" = true ]] || confirm-file-overwrite "$PRIV_SINK" "$PUB_SINK" || exit 0 + echo "$PRIV_KEY" > "$PRIV_SINK" + echo "$PUB_KEY" > "$PUB_SINK" +fi; +unset -v PRIV_SINK PUB_SINK PRIV_KEY PUB_KEY RESULT From 05848057209e62e58e11dca76f27f9aea154c13d Mon Sep 17 00:00:00 2001 From: _cry64 Date: Wed, 7 Jan 2026 22:04:16 +1000 Subject: [PATCH 111/338] clean confirmation logic most confirmations previously were fictious --- ceru/libceru.sh | 16 ++++++++++++---- ceru/subcmds/new/key | 1 - 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/ceru/libceru.sh b/ceru/libceru.sh index 92d21d7..f3ff6d5 100755 --- a/ceru/libceru.sh +++ b/ceru/libceru.sh @@ -68,12 +68,20 @@ function confirm-action { function confirm { confirm-action ":: Proceed? [Y/n] "; } function confirm-file-overwrite { - echo -e "${BOLD}${UNDERLINE}${BLINKFAST}${RED}WARNING!${RESET} ${YELLOW}The following files will be overwritten:${RESET}" - local ARG="" + local OVERWRITE=false + local ARG for ARG in "$@"; do - echo -e "${BOLD} • ${GREEN}${ARG}${RESET}" + if [[ -f "$ARG" ]]; then + # write info (initial) lines on first overwritable file found + if [[ "$OVERWRITE" = false ]]; then + echo -e "${BOLD}${UNDERLINE}${BLINKFAST}${RED}WARNING!${RESET} ${YELLOW}The following files will be overwritten:${RESET}" + OVERWRITE=true + fi + # list all files that require overwriting + echo -e "${BOLD} • ${GREEN}${ARG}${RESET}" + fi done - confirm + [[ "$OVERWRITE" = false ]] || confirm } # ====== Core ====== diff --git a/ceru/subcmds/new/key b/ceru/subcmds/new/key index 41b4916..e37570b 100755 --- a/ceru/subcmds/new/key +++ b/ceru/subcmds/new/key @@ -48,7 +48,6 @@ while [[ $# -gt 0 ]]; do shift FORCE=true ;; - -*) throw-badflag 1 "$ARG" ;; From e807e396765bd4098d6b2c702d25200e00815111 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Wed, 7 Jan 2026 22:04:16 +1000 Subject: [PATCH 112/338] clean confirmation logic most confirmations previously were fictious --- ceru/libceru.sh | 16 ++++++++++++---- ceru/subcmds/new/key | 1 - 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/ceru/libceru.sh b/ceru/libceru.sh index 92d21d7..f3ff6d5 100755 --- a/ceru/libceru.sh +++ b/ceru/libceru.sh @@ -68,12 +68,20 @@ function confirm-action { function confirm { confirm-action ":: Proceed? [Y/n] "; } function confirm-file-overwrite { - echo -e "${BOLD}${UNDERLINE}${BLINKFAST}${RED}WARNING!${RESET} ${YELLOW}The following files will be overwritten:${RESET}" - local ARG="" + local OVERWRITE=false + local ARG for ARG in "$@"; do - echo -e "${BOLD} • ${GREEN}${ARG}${RESET}" + if [[ -f "$ARG" ]]; then + # write info (initial) lines on first overwritable file found + if [[ "$OVERWRITE" = false ]]; then + echo -e "${BOLD}${UNDERLINE}${BLINKFAST}${RED}WARNING!${RESET} ${YELLOW}The following files will be overwritten:${RESET}" + OVERWRITE=true + fi + # list all files that require overwriting + echo -e "${BOLD} • ${GREEN}${ARG}${RESET}" + fi done - confirm + [[ "$OVERWRITE" = false ]] || confirm } # ====== Core ====== diff --git a/ceru/subcmds/new/key b/ceru/subcmds/new/key index 41b4916..e37570b 100755 --- a/ceru/subcmds/new/key +++ b/ceru/subcmds/new/key @@ -48,7 +48,6 @@ while [[ $# -gt 0 ]]; do shift FORCE=true ;; - -*) throw-badflag 1 "$ARG" ;; From 19389d97e9419909bd52f4d1a5eb9359e7cf1648 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Thu, 8 Jan 2026 14:03:59 +1000 Subject: [PATCH 113/338] add missing Apache2 statement to ceru --- ceru/ceru | 14 ++++++++++++++ ceru/libceru.sh | 16 +++++++++++++++- ceru/subcmds/new/default.sh | 15 +++++++++++++++ ceru/subcmds/new/key | 15 +++++++++++++++ 4 files changed, 59 insertions(+), 1 deletion(-) diff --git a/ceru/ceru b/ceru/ceru index bc4a4b1..f407960 100755 --- a/ceru/ceru +++ b/ceru/ceru @@ -1,4 +1,18 @@ #!/usr/bin/env bash +# Copyright 2025 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + set -euo pipefail # ======== CONFIGURATION ======== diff --git a/ceru/libceru.sh b/ceru/libceru.sh index f3ff6d5..d08551c 100755 --- a/ceru/libceru.sh +++ b/ceru/libceru.sh @@ -1,9 +1,23 @@ #!/usr/bin/env bash +# Copyright 2025 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. # libceru relies on the script that sources # it to set the following environment variables: # CMD_NAME USAGE -set -u + +set -u # ======== INTERNAL STATE ======== SUBCMDS="$PWD/subcmds" # XXX: TODO: don't hardcode as relative diff --git a/ceru/subcmds/new/default.sh b/ceru/subcmds/new/default.sh index 8cfce8f..ed0d15a 100755 --- a/ceru/subcmds/new/default.sh +++ b/ceru/subcmds/new/default.sh @@ -1,5 +1,20 @@ #!/usr/bin/env bash +# Copyright 2025 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + set -euo pipefail + USAGE="${BOLD}${UNDERLINE}${RED}Usage${RESET} ${BOLD}${GREEN}$THIS new [option...] subcommand${RESET} diff --git a/ceru/subcmds/new/key b/ceru/subcmds/new/key index e37570b..599762e 100755 --- a/ceru/subcmds/new/key +++ b/ceru/subcmds/new/key @@ -1,5 +1,20 @@ #!/usr/bin/env bash +# Copyright 2025 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + set -euo pipefail + USAGE="${BOLD}${UNDERLINE}${RED}Usage${RESET} ${BOLD}${GREEN}$THIS new key [option...]${RESET} From 33effeffa8c2e269568aaffe22b102a9ac62e745 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Thu, 8 Jan 2026 14:03:59 +1000 Subject: [PATCH 114/338] add missing Apache2 statement to ceru --- ceru/ceru | 14 ++++++++++++++ ceru/libceru.sh | 16 +++++++++++++++- ceru/subcmds/new/default.sh | 15 +++++++++++++++ ceru/subcmds/new/key | 15 +++++++++++++++ 4 files changed, 59 insertions(+), 1 deletion(-) diff --git a/ceru/ceru b/ceru/ceru index bc4a4b1..f407960 100755 --- a/ceru/ceru +++ b/ceru/ceru @@ -1,4 +1,18 @@ #!/usr/bin/env bash +# Copyright 2025 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + set -euo pipefail # ======== CONFIGURATION ======== diff --git a/ceru/libceru.sh b/ceru/libceru.sh index f3ff6d5..d08551c 100755 --- a/ceru/libceru.sh +++ b/ceru/libceru.sh @@ -1,9 +1,23 @@ #!/usr/bin/env bash +# Copyright 2025 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. # libceru relies on the script that sources # it to set the following environment variables: # CMD_NAME USAGE -set -u + +set -u # ======== INTERNAL STATE ======== SUBCMDS="$PWD/subcmds" # XXX: TODO: don't hardcode as relative diff --git a/ceru/subcmds/new/default.sh b/ceru/subcmds/new/default.sh index 8cfce8f..ed0d15a 100755 --- a/ceru/subcmds/new/default.sh +++ b/ceru/subcmds/new/default.sh @@ -1,5 +1,20 @@ #!/usr/bin/env bash +# Copyright 2025 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + set -euo pipefail + USAGE="${BOLD}${UNDERLINE}${RED}Usage${RESET} ${BOLD}${GREEN}$THIS new [option...] subcommand${RESET} diff --git a/ceru/subcmds/new/key b/ceru/subcmds/new/key index e37570b..599762e 100755 --- a/ceru/subcmds/new/key +++ b/ceru/subcmds/new/key @@ -1,5 +1,20 @@ #!/usr/bin/env bash +# Copyright 2025 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + set -euo pipefail + USAGE="${BOLD}${UNDERLINE}${RED}Usage${RESET} ${BOLD}${GREEN}$THIS new key [option...]${RESET} From 15fdfb71a781838a230f2eb15d7aaa7a6b7e2a33 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Thu, 15 Jan 2026 12:11:04 +1000 Subject: [PATCH 115/338] use mix module system --- cerulean/default.nix | 20 +++++ cerulean/flake-config/default.nix | 25 ++++++ cerulean/flake-config/nexus.nix | 90 +++++++++++++++++++++ cerulean/flake-config/nodes.nix | 67 ++++++++++++++++ flake.nix | 127 ++---------------------------- 5 files changed, 207 insertions(+), 122 deletions(-) create mode 100644 cerulean/default.nix create mode 100644 cerulean/flake-config/default.nix create mode 100644 cerulean/flake-config/nexus.nix create mode 100644 cerulean/flake-config/nodes.nix diff --git a/cerulean/default.nix b/cerulean/default.nix new file mode 100644 index 0000000..e72374e --- /dev/null +++ b/cerulean/default.nix @@ -0,0 +1,20 @@ +# Copyright 2025 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +{mix, ...} @ inputs: +mix.mkMod (mix.newMixture {specialArgs = inputs;}) +(mixture: { + includes.public = [ + ./flake-config + ]; +}) diff --git a/cerulean/flake-config/default.nix b/cerulean/flake-config/default.nix new file mode 100644 index 0000000..1a4e885 --- /dev/null +++ b/cerulean/flake-config/default.nix @@ -0,0 +1,25 @@ +# Copyright 2025 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +{ + mix, + mixture, + ... +}: +mix.mkMod mixture +(mixture: { + includes.public = [ + ./nodes + ./nexus + ]; +}) diff --git a/cerulean/flake-config/nexus.nix b/cerulean/flake-config/nexus.nix new file mode 100644 index 0000000..58c1a99 --- /dev/null +++ b/cerulean/flake-config/nexus.nix @@ -0,0 +1,90 @@ +# Copyright 2025 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +{ + mixture, + sys, + lib, + deploy-rs, + ... +}: let + inherit + (mixture.nodes) + mapNodes + ; + + inherit + (lib) + nixosSystem + ; + + mkNexus' = config: rec { + nixosConfigurations = mapNodes ( + nodeName: node: + nixosSystem { + system = node.system; + modules = node.modules; + + # nix passes these to every single module + specialArgs = + node.specialArgs + // { + pkgs = sys.pkgsFor node.system; + upkgs = sys.upkgsFor node.system; + }; + } + ); + + deploy.nodes = mapNodes (nodeName: node: let + nixosFor = system: deploy-rs.lib.${system}.activate.nixos; + in { + hostname = node.deploy.ssh.host; + + profilesOrder = ["default"]; # profiles priority + profiles.default = { + path = nixosFor node.system nixosConfigurations.${nodeName}; + + user = node.deploy.user; + sudo = node.deploy.sudo; + interactiveSudo = node.deploy.interactiveSudo; + + fastConnection = false; + + autoRollback = node.deploy.autoRollback; + magicRollback = node.deploy.magicRollback; + activationTimeout = node.deploy.activationTimeout; + confirmTimeout = node.deploy.confirmTimeout; + + remoteBuild = node.deploy.remoteBuild; + sshUser = node.deploy.ssh.user; + sshOpts = + node.deploy.ssh.opts + ++ ( + if builtins.elem "-p" node.deploy.ssh.opts + then [] + else ["-p" (toString node.deploy.ssh.port)] + ) + ++ ( + if builtins.elem "-A" node.deploy.ssh.opts + then [] + else ["-A"] + ); + }; + }); + + checks = builtins.mapAttrs (system: deployLib: deployLib.deployChecks deploy) deploy-rs.lib; + }; +in { + mkNexus = outputs: + (mkNexus' outputs.cerulean) // (removeAttrs outputs ["cerulean"]); +} diff --git a/cerulean/flake-config/nodes.nix b/cerulean/flake-config/nodes.nix new file mode 100644 index 0000000..f6f95fc --- /dev/null +++ b/cerulean/flake-config/nodes.nix @@ -0,0 +1,67 @@ +# Copyright 2025 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +{nib, ...}: rec { + # abstract node instance that stores all default values + templateNode = name: system: let + Terminal = nib.types.Terminal; + + missing = msg: path: + Terminal (abort '' + Each Cerulean Nexus node is required to specify ${msg}! + Ensure `cerulean.nexus.nodes.${name}.${path}` exists under your call to `cerulean.mkNexus`. + ''); + in { + system = missing "its system type" "system"; # intentionally left missing!! (to raise errors) + modules = missing "its required modules" "modules"; + specialArgs = Terminal {}; + + deploy = { + user = "root"; + sudo = "sudo -u"; + interactiveSudo = false; + + remoteBuild = false; # prefer local builds for remote deploys + + autoRollback = true; # reactivate previous profile if activation fails + magicRollback = true; + + activationTimeout = 500; # timeout in seconds for profile activation + confirmTimeout = 30; # timeout in seconds for profile activation confirmation + + ssh = { + host = missing "an SSH hostname (domain name or ip address) for deployment" "deploy.ssh.host"; + user = missing "an SSH username for deployment" "deploy.ssh.user"; + port = 22; + opts = []; + }; + }; + }; + + parseNode = name: nodeAttrs: + if !(builtins.isAttrs nodeAttrs) + then + # fail if node is not an attribute set + abort '' + Cerulean Nexus nodes must be provided as an attribute set, got "${builtins.typeOf nodeAttrs}" instead! + Ensure all `cerulean.nexus.nodes.${name}` declarations are attribute sets under your call to `cerulean.mkNexus`. + '' + else let + templateAttrs = templateNode name nodeAttrs.system; + in + nib.parse.mergeStructs templateAttrs nodeAttrs; + + mapNodes' = f: + builtins.mapAttrs + (nodeName: nodeAttrs: f nodeName (parseNode nodeName nodeAttrs)); +} diff --git a/flake.nix b/flake.nix index 4aaa4a2..be08ecc 100644 --- a/flake.nix +++ b/flake.nix @@ -47,11 +47,12 @@ config.allowUnfree = false; }; }; - in rec { + + cerulean = import ./cerulean {inherit inputs lib sys;}; + in { overlays = [ - # deploy-rs is built from the flake input, not from nixpkgs! - # To take advantage of the nixpkgs binary cache, - # the deploy-rs package can be overwritten: + # build deploy-rs as a package not from the flake input, + # hence we can rely on a nixpkg binary cache. deploy-rs.overlays.default (self: super: { deploy-rs = { @@ -60,123 +61,5 @@ }; }) ]; - - mkNexusConfig = config: let - # abstract node instance that stores all default values - templateNode = name: system: let - Terminal = nib.types.Terminal; - - missing = msg: path: - Terminal (abort '' - Each Cerulean Nexus node is required to specify ${msg}! - Ensure `cerulean.nexus.nodes.${name}.${path}` exists under your call to `cerulean.mkNexus`. - ''); - in { - system = missing "its system type" "system"; # intentionally left missing!! (to raise errors) - modules = missing "its required modules" "modules"; - specialArgs = Terminal {}; - - deploy = { - user = "root"; - sudo = "sudo -u"; - interactiveSudo = false; - - remoteBuild = false; # prefer local builds for remote deploys - - autoRollback = true; # reactivate previous profile if activation fails - magicRollback = true; - - activationTimeout = 500; # timeout in seconds for profile activation - confirmTimeout = 30; # timeout in seconds for profile activation confirmation - - ssh = { - host = missing "an SSH hostname (domain name or ip address) for deployment" "deploy.ssh.host"; - user = missing "an SSH username for deployment" "deploy.ssh.user"; - port = 22; - opts = []; - }; - }; - }; - - parseNode = name: nodeAttrs: - if !(builtins.isAttrs nodeAttrs) - then - # fail if node is not an attribute set - abort '' - Cerulean Nexus nodes must be provided as an attribute set, got "${builtins.typeOf nodeAttrs}" instead! - Ensure all `cerulean.nexus.nodes.${name}` declarations are attribute sets under your call to `cerulean.mkNexus`. - '' - else let - templateAttrs = templateNode name nodeAttrs.system; - in - nib.parse.mergeStructs templateAttrs nodeAttrs; - - # mapNodes = f: builtins.mapAttrs f (builtins.mapAttrs parseNode config.nexus.nodes); - mapNodes = f: - builtins.mapAttrs - (nodeName: nodeAttrs: f nodeName (parseNode nodeName nodeAttrs)) - config.nexus.nodes; - in rec { - nixosConfigurations = mapNodes ( - nodeName: node: - lib.nixosSystem { - system = node.system; - modules = node.modules; - - # nix passes these to every single module - specialArgs = - node.specialArgs - // { - inherit inputs; - pkgs = sys.pkgsFor node.system; - upkgs = sys.upkgsFor node.system; - }; - } - ); - - deploy.nodes = mapNodes (nodeName: node: let - nixosFor = system: deploy-rs.lib.${system}.activate.nixos; - in { - hostname = node.deploy.ssh.host; - - profilesOrder = ["default"]; # profiles priority - profiles.default = { - path = nixosFor node.system nixosConfigurations.${nodeName}; - - user = node.deploy.user; - sudo = node.deploy.sudo; - interactiveSudo = node.deploy.interactiveSudo; - - fastConnection = false; - - autoRollback = node.deploy.autoRollback; - magicRollback = node.deploy.magicRollback; - activationTimeout = node.deploy.activationTimeout; - confirmTimeout = node.deploy.confirmTimeout; - - remoteBuild = node.deploy.remoteBuild; - sshUser = node.deploy.ssh.user; - sshOpts = - node.deploy.ssh.opts - ++ ( - if builtins.elem "-p" node.deploy.ssh.opts - then [] - else ["-p" (toString node.deploy.ssh.port)] - ) - ++ ( - if builtins.elem "-A" node.deploy.ssh.opts - then [] - else ["-A"] - ); - }; - }); - - checks = builtins.mapAttrs (system: deployLib: deployLib.deployChecks deploy) deploy-rs.lib; - }; - - mkNexus = outputs: let - config = outputs.cerulean; - in - (mkNexusConfig config) // (removeAttrs outputs ["cerulean"]); }; } From 728e608bda4219c5b5a65548186f9ffa69d74401 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Thu, 15 Jan 2026 12:11:04 +1000 Subject: [PATCH 116/338] use mix module system --- cerulean/default.nix | 20 +++++ cerulean/flake-config/default.nix | 25 ++++++ cerulean/flake-config/nexus.nix | 90 +++++++++++++++++++++ cerulean/flake-config/nodes.nix | 67 ++++++++++++++++ flake.nix | 127 ++---------------------------- 5 files changed, 207 insertions(+), 122 deletions(-) create mode 100644 cerulean/default.nix create mode 100644 cerulean/flake-config/default.nix create mode 100644 cerulean/flake-config/nexus.nix create mode 100644 cerulean/flake-config/nodes.nix diff --git a/cerulean/default.nix b/cerulean/default.nix new file mode 100644 index 0000000..e72374e --- /dev/null +++ b/cerulean/default.nix @@ -0,0 +1,20 @@ +# Copyright 2025 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +{mix, ...} @ inputs: +mix.mkMod (mix.newMixture {specialArgs = inputs;}) +(mixture: { + includes.public = [ + ./flake-config + ]; +}) diff --git a/cerulean/flake-config/default.nix b/cerulean/flake-config/default.nix new file mode 100644 index 0000000..1a4e885 --- /dev/null +++ b/cerulean/flake-config/default.nix @@ -0,0 +1,25 @@ +# Copyright 2025 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +{ + mix, + mixture, + ... +}: +mix.mkMod mixture +(mixture: { + includes.public = [ + ./nodes + ./nexus + ]; +}) diff --git a/cerulean/flake-config/nexus.nix b/cerulean/flake-config/nexus.nix new file mode 100644 index 0000000..58c1a99 --- /dev/null +++ b/cerulean/flake-config/nexus.nix @@ -0,0 +1,90 @@ +# Copyright 2025 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +{ + mixture, + sys, + lib, + deploy-rs, + ... +}: let + inherit + (mixture.nodes) + mapNodes + ; + + inherit + (lib) + nixosSystem + ; + + mkNexus' = config: rec { + nixosConfigurations = mapNodes ( + nodeName: node: + nixosSystem { + system = node.system; + modules = node.modules; + + # nix passes these to every single module + specialArgs = + node.specialArgs + // { + pkgs = sys.pkgsFor node.system; + upkgs = sys.upkgsFor node.system; + }; + } + ); + + deploy.nodes = mapNodes (nodeName: node: let + nixosFor = system: deploy-rs.lib.${system}.activate.nixos; + in { + hostname = node.deploy.ssh.host; + + profilesOrder = ["default"]; # profiles priority + profiles.default = { + path = nixosFor node.system nixosConfigurations.${nodeName}; + + user = node.deploy.user; + sudo = node.deploy.sudo; + interactiveSudo = node.deploy.interactiveSudo; + + fastConnection = false; + + autoRollback = node.deploy.autoRollback; + magicRollback = node.deploy.magicRollback; + activationTimeout = node.deploy.activationTimeout; + confirmTimeout = node.deploy.confirmTimeout; + + remoteBuild = node.deploy.remoteBuild; + sshUser = node.deploy.ssh.user; + sshOpts = + node.deploy.ssh.opts + ++ ( + if builtins.elem "-p" node.deploy.ssh.opts + then [] + else ["-p" (toString node.deploy.ssh.port)] + ) + ++ ( + if builtins.elem "-A" node.deploy.ssh.opts + then [] + else ["-A"] + ); + }; + }); + + checks = builtins.mapAttrs (system: deployLib: deployLib.deployChecks deploy) deploy-rs.lib; + }; +in { + mkNexus = outputs: + (mkNexus' outputs.cerulean) // (removeAttrs outputs ["cerulean"]); +} diff --git a/cerulean/flake-config/nodes.nix b/cerulean/flake-config/nodes.nix new file mode 100644 index 0000000..f6f95fc --- /dev/null +++ b/cerulean/flake-config/nodes.nix @@ -0,0 +1,67 @@ +# Copyright 2025 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +{nib, ...}: rec { + # abstract node instance that stores all default values + templateNode = name: system: let + Terminal = nib.types.Terminal; + + missing = msg: path: + Terminal (abort '' + Each Cerulean Nexus node is required to specify ${msg}! + Ensure `cerulean.nexus.nodes.${name}.${path}` exists under your call to `cerulean.mkNexus`. + ''); + in { + system = missing "its system type" "system"; # intentionally left missing!! (to raise errors) + modules = missing "its required modules" "modules"; + specialArgs = Terminal {}; + + deploy = { + user = "root"; + sudo = "sudo -u"; + interactiveSudo = false; + + remoteBuild = false; # prefer local builds for remote deploys + + autoRollback = true; # reactivate previous profile if activation fails + magicRollback = true; + + activationTimeout = 500; # timeout in seconds for profile activation + confirmTimeout = 30; # timeout in seconds for profile activation confirmation + + ssh = { + host = missing "an SSH hostname (domain name or ip address) for deployment" "deploy.ssh.host"; + user = missing "an SSH username for deployment" "deploy.ssh.user"; + port = 22; + opts = []; + }; + }; + }; + + parseNode = name: nodeAttrs: + if !(builtins.isAttrs nodeAttrs) + then + # fail if node is not an attribute set + abort '' + Cerulean Nexus nodes must be provided as an attribute set, got "${builtins.typeOf nodeAttrs}" instead! + Ensure all `cerulean.nexus.nodes.${name}` declarations are attribute sets under your call to `cerulean.mkNexus`. + '' + else let + templateAttrs = templateNode name nodeAttrs.system; + in + nib.parse.mergeStructs templateAttrs nodeAttrs; + + mapNodes' = f: + builtins.mapAttrs + (nodeName: nodeAttrs: f nodeName (parseNode nodeName nodeAttrs)); +} diff --git a/flake.nix b/flake.nix index 4aaa4a2..be08ecc 100644 --- a/flake.nix +++ b/flake.nix @@ -47,11 +47,12 @@ config.allowUnfree = false; }; }; - in rec { + + cerulean = import ./cerulean {inherit inputs lib sys;}; + in { overlays = [ - # deploy-rs is built from the flake input, not from nixpkgs! - # To take advantage of the nixpkgs binary cache, - # the deploy-rs package can be overwritten: + # build deploy-rs as a package not from the flake input, + # hence we can rely on a nixpkg binary cache. deploy-rs.overlays.default (self: super: { deploy-rs = { @@ -60,123 +61,5 @@ }; }) ]; - - mkNexusConfig = config: let - # abstract node instance that stores all default values - templateNode = name: system: let - Terminal = nib.types.Terminal; - - missing = msg: path: - Terminal (abort '' - Each Cerulean Nexus node is required to specify ${msg}! - Ensure `cerulean.nexus.nodes.${name}.${path}` exists under your call to `cerulean.mkNexus`. - ''); - in { - system = missing "its system type" "system"; # intentionally left missing!! (to raise errors) - modules = missing "its required modules" "modules"; - specialArgs = Terminal {}; - - deploy = { - user = "root"; - sudo = "sudo -u"; - interactiveSudo = false; - - remoteBuild = false; # prefer local builds for remote deploys - - autoRollback = true; # reactivate previous profile if activation fails - magicRollback = true; - - activationTimeout = 500; # timeout in seconds for profile activation - confirmTimeout = 30; # timeout in seconds for profile activation confirmation - - ssh = { - host = missing "an SSH hostname (domain name or ip address) for deployment" "deploy.ssh.host"; - user = missing "an SSH username for deployment" "deploy.ssh.user"; - port = 22; - opts = []; - }; - }; - }; - - parseNode = name: nodeAttrs: - if !(builtins.isAttrs nodeAttrs) - then - # fail if node is not an attribute set - abort '' - Cerulean Nexus nodes must be provided as an attribute set, got "${builtins.typeOf nodeAttrs}" instead! - Ensure all `cerulean.nexus.nodes.${name}` declarations are attribute sets under your call to `cerulean.mkNexus`. - '' - else let - templateAttrs = templateNode name nodeAttrs.system; - in - nib.parse.mergeStructs templateAttrs nodeAttrs; - - # mapNodes = f: builtins.mapAttrs f (builtins.mapAttrs parseNode config.nexus.nodes); - mapNodes = f: - builtins.mapAttrs - (nodeName: nodeAttrs: f nodeName (parseNode nodeName nodeAttrs)) - config.nexus.nodes; - in rec { - nixosConfigurations = mapNodes ( - nodeName: node: - lib.nixosSystem { - system = node.system; - modules = node.modules; - - # nix passes these to every single module - specialArgs = - node.specialArgs - // { - inherit inputs; - pkgs = sys.pkgsFor node.system; - upkgs = sys.upkgsFor node.system; - }; - } - ); - - deploy.nodes = mapNodes (nodeName: node: let - nixosFor = system: deploy-rs.lib.${system}.activate.nixos; - in { - hostname = node.deploy.ssh.host; - - profilesOrder = ["default"]; # profiles priority - profiles.default = { - path = nixosFor node.system nixosConfigurations.${nodeName}; - - user = node.deploy.user; - sudo = node.deploy.sudo; - interactiveSudo = node.deploy.interactiveSudo; - - fastConnection = false; - - autoRollback = node.deploy.autoRollback; - magicRollback = node.deploy.magicRollback; - activationTimeout = node.deploy.activationTimeout; - confirmTimeout = node.deploy.confirmTimeout; - - remoteBuild = node.deploy.remoteBuild; - sshUser = node.deploy.ssh.user; - sshOpts = - node.deploy.ssh.opts - ++ ( - if builtins.elem "-p" node.deploy.ssh.opts - then [] - else ["-p" (toString node.deploy.ssh.port)] - ) - ++ ( - if builtins.elem "-A" node.deploy.ssh.opts - then [] - else ["-A"] - ); - }; - }); - - checks = builtins.mapAttrs (system: deployLib: deployLib.deployChecks deploy) deploy-rs.lib; - }; - - mkNexus = outputs: let - config = outputs.cerulean; - in - (mkNexusConfig config) // (removeAttrs outputs ["cerulean"]); }; } From dc387401295804aee677510282e36ba99099aeed Mon Sep 17 00:00:00 2001 From: _cry64 Date: Thu, 15 Jan 2026 12:16:47 +1000 Subject: [PATCH 117/338] progress flake.lock --- flake.lock | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/flake.lock b/flake.lock index 100da43..4c21514 100644 --- a/flake.lock +++ b/flake.lock @@ -7,11 +7,11 @@ "utils": "utils" }, "locked": { - "lastModified": 1762286984, - "narHash": "sha256-9I2H9x5We6Pl+DBYHjR1s3UT8wgwcpAH03kn9CqtdQc=", + "lastModified": 1766051518, + "narHash": "sha256-znKOwPXQnt3o7lDb3hdf19oDo0BLP4MfBOYiWkEHoik=", "owner": "serokell", "repo": "deploy-rs", - "rev": "9c870f63e28ec1e83305f7f6cb73c941e699f74f", + "rev": "d5eff7f948535b9c723d60cd8239f8f11ddc90fa", "type": "github" }, "original": { @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1766026109, - "narHash": "sha256-CcaDBnaHtFApR66IeFzB41+ENfVZzey32DAMLMrBvKU=", + "lastModified": 1768442186, + "narHash": "sha256-PMhz5gpLDBve8lxNX9eDLDC2+Y2mdPy8e8Pflzp6YYc=", "owner": "emilelcb", "repo": "nib", - "rev": "df8f10c468a532676fbb5099e7c4f61368385bd8", + "rev": "f3089ca0161e2925d24e9206b0a315319a1ad105", "type": "github" }, "original": { @@ -74,11 +74,11 @@ }, "nixpkgs-unstable": { "locked": { - "lastModified": 1765779637, - "narHash": "sha256-KJ2wa/BLSrTqDjbfyNx70ov/HdgNBCBBSQP3BIzKnv4=", + "lastModified": 1768127708, + "narHash": "sha256-1Sm77VfZh3mU0F5OqKABNLWxOuDeHIlcFjsXeeiPazs=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "1306659b587dc277866c7b69eb97e5f07864d8c4", + "rev": "ffbc9f8cbaacfb331b6017d5a5abb21a492c9a38", "type": "github" }, "original": { @@ -90,11 +90,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1765838191, - "narHash": "sha256-m5KWt1nOm76ILk/JSCxBM4MfK3rYY7Wq9/TZIIeGnT8=", + "lastModified": 1768323494, + "narHash": "sha256-yBXJLE6WCtrGo7LKiB6NOt6nisBEEkguC/lq/rP3zRQ=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "c6f52ebd45e5925c188d1a20119978aa4ffd5ef6", + "rev": "2c3e5ec5df46d3aeee2a1da0bfedd74e21f4bf3a", "type": "github" }, "original": { From bfc63c74b2b1b0d113df135bfaf034c0b3c11f9a Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Thu, 15 Jan 2026 12:16:47 +1000 Subject: [PATCH 118/338] progress flake.lock --- flake.lock | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/flake.lock b/flake.lock index 100da43..4c21514 100644 --- a/flake.lock +++ b/flake.lock @@ -7,11 +7,11 @@ "utils": "utils" }, "locked": { - "lastModified": 1762286984, - "narHash": "sha256-9I2H9x5We6Pl+DBYHjR1s3UT8wgwcpAH03kn9CqtdQc=", + "lastModified": 1766051518, + "narHash": "sha256-znKOwPXQnt3o7lDb3hdf19oDo0BLP4MfBOYiWkEHoik=", "owner": "serokell", "repo": "deploy-rs", - "rev": "9c870f63e28ec1e83305f7f6cb73c941e699f74f", + "rev": "d5eff7f948535b9c723d60cd8239f8f11ddc90fa", "type": "github" }, "original": { @@ -43,11 +43,11 @@ ] }, "locked": { - "lastModified": 1766026109, - "narHash": "sha256-CcaDBnaHtFApR66IeFzB41+ENfVZzey32DAMLMrBvKU=", + "lastModified": 1768442186, + "narHash": "sha256-PMhz5gpLDBve8lxNX9eDLDC2+Y2mdPy8e8Pflzp6YYc=", "owner": "emilelcb", "repo": "nib", - "rev": "df8f10c468a532676fbb5099e7c4f61368385bd8", + "rev": "f3089ca0161e2925d24e9206b0a315319a1ad105", "type": "github" }, "original": { @@ -74,11 +74,11 @@ }, "nixpkgs-unstable": { "locked": { - "lastModified": 1765779637, - "narHash": "sha256-KJ2wa/BLSrTqDjbfyNx70ov/HdgNBCBBSQP3BIzKnv4=", + "lastModified": 1768127708, + "narHash": "sha256-1Sm77VfZh3mU0F5OqKABNLWxOuDeHIlcFjsXeeiPazs=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "1306659b587dc277866c7b69eb97e5f07864d8c4", + "rev": "ffbc9f8cbaacfb331b6017d5a5abb21a492c9a38", "type": "github" }, "original": { @@ -90,11 +90,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1765838191, - "narHash": "sha256-m5KWt1nOm76ILk/JSCxBM4MfK3rYY7Wq9/TZIIeGnT8=", + "lastModified": 1768323494, + "narHash": "sha256-yBXJLE6WCtrGo7LKiB6NOt6nisBEEkguC/lq/rP3zRQ=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "c6f52ebd45e5925c188d1a20119978aa4ffd5ef6", + "rev": "2c3e5ec5df46d3aeee2a1da0bfedd74e21f4bf3a", "type": "github" }, "original": { From 0fe6007716d5d5d26557c5ea072bdc75195f1759 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Thu, 15 Jan 2026 12:16:57 +1000 Subject: [PATCH 119/338] simplify via inherit --- cerulean/flake-config/nexus.nix | 41 ++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/cerulean/flake-config/nexus.nix b/cerulean/flake-config/nexus.nix index 58c1a99..a9d0b02 100644 --- a/cerulean/flake-config/nexus.nix +++ b/cerulean/flake-config/nexus.nix @@ -46,36 +46,49 @@ ); deploy.nodes = mapNodes (nodeName: node: let + inherit + (node.deploy) + activationTimeout + autoRollback + confirmTimeout + interactiveSudo + magicRollback + remoteBuild + ssh + sudo + user + ; + nixosFor = system: deploy-rs.lib.${system}.activate.nixos; in { - hostname = node.deploy.ssh.host; + hostname = ssh.host; profilesOrder = ["default"]; # profiles priority profiles.default = { path = nixosFor node.system nixosConfigurations.${nodeName}; - user = node.deploy.user; - sudo = node.deploy.sudo; - interactiveSudo = node.deploy.interactiveSudo; + user = user; + sudo = sudo; + interactiveSudo = interactiveSudo; fastConnection = false; - autoRollback = node.deploy.autoRollback; - magicRollback = node.deploy.magicRollback; - activationTimeout = node.deploy.activationTimeout; - confirmTimeout = node.deploy.confirmTimeout; + autoRollback = autoRollback; + magicRollback = magicRollback; + activationTimeout = activationTimeout; + confirmTimeout = confirmTimeout; - remoteBuild = node.deploy.remoteBuild; - sshUser = node.deploy.ssh.user; + remoteBuild = remoteBuild; + sshUser = ssh.user; sshOpts = - node.deploy.ssh.opts + ssh.opts ++ ( - if builtins.elem "-p" node.deploy.ssh.opts + if builtins.elem "-p" ssh.opts then [] - else ["-p" (toString node.deploy.ssh.port)] + else ["-p" (toString ssh.port)] ) ++ ( - if builtins.elem "-A" node.deploy.ssh.opts + if builtins.elem "-A" ssh.opts then [] else ["-A"] ); From 02d278fade2bdd1f5ef05ba9bbdfbf49c041b5cb Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Thu, 15 Jan 2026 12:16:57 +1000 Subject: [PATCH 120/338] simplify via inherit --- cerulean/flake-config/nexus.nix | 41 ++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/cerulean/flake-config/nexus.nix b/cerulean/flake-config/nexus.nix index 58c1a99..a9d0b02 100644 --- a/cerulean/flake-config/nexus.nix +++ b/cerulean/flake-config/nexus.nix @@ -46,36 +46,49 @@ ); deploy.nodes = mapNodes (nodeName: node: let + inherit + (node.deploy) + activationTimeout + autoRollback + confirmTimeout + interactiveSudo + magicRollback + remoteBuild + ssh + sudo + user + ; + nixosFor = system: deploy-rs.lib.${system}.activate.nixos; in { - hostname = node.deploy.ssh.host; + hostname = ssh.host; profilesOrder = ["default"]; # profiles priority profiles.default = { path = nixosFor node.system nixosConfigurations.${nodeName}; - user = node.deploy.user; - sudo = node.deploy.sudo; - interactiveSudo = node.deploy.interactiveSudo; + user = user; + sudo = sudo; + interactiveSudo = interactiveSudo; fastConnection = false; - autoRollback = node.deploy.autoRollback; - magicRollback = node.deploy.magicRollback; - activationTimeout = node.deploy.activationTimeout; - confirmTimeout = node.deploy.confirmTimeout; + autoRollback = autoRollback; + magicRollback = magicRollback; + activationTimeout = activationTimeout; + confirmTimeout = confirmTimeout; - remoteBuild = node.deploy.remoteBuild; - sshUser = node.deploy.ssh.user; + remoteBuild = remoteBuild; + sshUser = ssh.user; sshOpts = - node.deploy.ssh.opts + ssh.opts ++ ( - if builtins.elem "-p" node.deploy.ssh.opts + if builtins.elem "-p" ssh.opts then [] - else ["-p" (toString node.deploy.ssh.port)] + else ["-p" (toString ssh.port)] ) ++ ( - if builtins.elem "-A" node.deploy.ssh.opts + if builtins.elem "-A" ssh.opts then [] else ["-A"] ); From da6cac75117fc0449c4d3ae72622b4e076ee9ed4 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Thu, 15 Jan 2026 12:45:47 +1000 Subject: [PATCH 121/338] extend node defaults use x86_64-linux as the default system, and ceru-build as the default ssh user --- cerulean/flake-config/nexus.nix | 9 ++------- cerulean/flake-config/nodes.nix | 7 ++++--- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/cerulean/flake-config/nexus.nix b/cerulean/flake-config/nexus.nix index a9d0b02..2e02119 100644 --- a/cerulean/flake-config/nexus.nix +++ b/cerulean/flake-config/nexus.nix @@ -23,17 +23,12 @@ mapNodes ; - inherit - (lib) - nixosSystem - ; - mkNexus' = config: rec { nixosConfigurations = mapNodes ( nodeName: node: - nixosSystem { + lib.nixosSystem { system = node.system; - modules = node.modules; + modules = [./hosts/${nodeName}] ++ node.extraModules; # nix passes these to every single module specialArgs = diff --git a/cerulean/flake-config/nodes.nix b/cerulean/flake-config/nodes.nix index f6f95fc..fdc32a4 100644 --- a/cerulean/flake-config/nodes.nix +++ b/cerulean/flake-config/nodes.nix @@ -22,8 +22,9 @@ Ensure `cerulean.nexus.nodes.${name}.${path}` exists under your call to `cerulean.mkNexus`. ''); in { - system = missing "its system type" "system"; # intentionally left missing!! (to raise errors) - modules = missing "its required modules" "modules"; + # system = missing "its system type" "system"; # intentionally left missing!! (to raise errors) + system = "x86_64-linux"; # sane default (i hope...) + extraModules = []; specialArgs = Terminal {}; deploy = { @@ -41,7 +42,7 @@ ssh = { host = missing "an SSH hostname (domain name or ip address) for deployment" "deploy.ssh.host"; - user = missing "an SSH username for deployment" "deploy.ssh.user"; + user = "ceru-build"; # ceru-build is the default connection user port = 22; opts = []; }; From 7120d87ac791b3de23cf4b644dd84a7652ab0d33 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Thu, 15 Jan 2026 12:45:47 +1000 Subject: [PATCH 122/338] extend node defaults use x86_64-linux as the default system, and ceru-build as the default ssh user --- cerulean/flake-config/nexus.nix | 9 ++------- cerulean/flake-config/nodes.nix | 7 ++++--- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/cerulean/flake-config/nexus.nix b/cerulean/flake-config/nexus.nix index a9d0b02..2e02119 100644 --- a/cerulean/flake-config/nexus.nix +++ b/cerulean/flake-config/nexus.nix @@ -23,17 +23,12 @@ mapNodes ; - inherit - (lib) - nixosSystem - ; - mkNexus' = config: rec { nixosConfigurations = mapNodes ( nodeName: node: - nixosSystem { + lib.nixosSystem { system = node.system; - modules = node.modules; + modules = [./hosts/${nodeName}] ++ node.extraModules; # nix passes these to every single module specialArgs = diff --git a/cerulean/flake-config/nodes.nix b/cerulean/flake-config/nodes.nix index f6f95fc..fdc32a4 100644 --- a/cerulean/flake-config/nodes.nix +++ b/cerulean/flake-config/nodes.nix @@ -22,8 +22,9 @@ Ensure `cerulean.nexus.nodes.${name}.${path}` exists under your call to `cerulean.mkNexus`. ''); in { - system = missing "its system type" "system"; # intentionally left missing!! (to raise errors) - modules = missing "its required modules" "modules"; + # system = missing "its system type" "system"; # intentionally left missing!! (to raise errors) + system = "x86_64-linux"; # sane default (i hope...) + extraModules = []; specialArgs = Terminal {}; deploy = { @@ -41,7 +42,7 @@ ssh = { host = missing "an SSH hostname (domain name or ip address) for deployment" "deploy.ssh.host"; - user = missing "an SSH username for deployment" "deploy.ssh.user"; + user = "ceru-build"; # ceru-build is the default connection user port = 22; opts = []; }; From dc69b17c29998da62ee944a1d840266c787eee2c Mon Sep 17 00:00:00 2001 From: _cry64 Date: Thu, 15 Jan 2026 12:49:41 +1000 Subject: [PATCH 123/338] add missing input "mix" --- flake.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/flake.nix b/flake.nix index be08ecc..94a4094 100644 --- a/flake.nix +++ b/flake.nix @@ -25,6 +25,8 @@ inputs.systems.follows = "systems"; }; + mix.url = "github:emilelcb/mix"; + deploy-rs.url = "github:serokell/deploy-rs"; }; From 927f77a8f272bb50ffc48397ffef3770ce5f06a2 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Thu, 15 Jan 2026 12:49:41 +1000 Subject: [PATCH 124/338] add missing input "mix" --- flake.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/flake.nix b/flake.nix index be08ecc..94a4094 100644 --- a/flake.nix +++ b/flake.nix @@ -25,6 +25,8 @@ inputs.systems.follows = "systems"; }; + mix.url = "github:emilelcb/mix"; + deploy-rs.url = "github:serokell/deploy-rs"; }; From 99e03effcefb9902f8d9f7153674bf42a5b45cac Mon Sep 17 00:00:00 2001 From: _cry64 Date: Thu, 15 Jan 2026 13:08:57 +1000 Subject: [PATCH 125/338] progress flake.lock --- flake.lock | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/flake.lock b/flake.lock index 4c21514..9271e7a 100644 --- a/flake.lock +++ b/flake.lock @@ -36,6 +36,18 @@ "type": "github" } }, + "mix": { + "locked": { + "lastModified": 1768446394, + "narHash": "sha256-3mDswdUKnDSY571nstThXDSZG5wB4agkiOuJUV4YKgM=", + "path": "/home/me/agribit/nexus/mix", + "type": "path" + }, + "original": { + "path": "/home/me/agribit/nexus/mix", + "type": "path" + } + }, "nib": { "inputs": { "systems": [ @@ -43,17 +55,14 @@ ] }, "locked": { - "lastModified": 1768442186, - "narHash": "sha256-PMhz5gpLDBve8lxNX9eDLDC2+Y2mdPy8e8Pflzp6YYc=", - "owner": "emilelcb", - "repo": "nib", - "rev": "f3089ca0161e2925d24e9206b0a315319a1ad105", - "type": "github" + "lastModified": 1768446390, + "narHash": "sha256-Cp8dd4A3HsXUYCaQqCqb+VKwUdK0FAe8HyibVqZV/pA=", + "path": "/home/me/agribit/nexus/nib", + "type": "path" }, "original": { - "owner": "emilelcb", - "repo": "nib", - "type": "github" + "path": "/home/me/agribit/nexus/nib", + "type": "path" } }, "nixpkgs": { @@ -74,11 +83,11 @@ }, "nixpkgs-unstable": { "locked": { - "lastModified": 1768127708, - "narHash": "sha256-1Sm77VfZh3mU0F5OqKABNLWxOuDeHIlcFjsXeeiPazs=", + "lastModified": 1768305791, + "narHash": "sha256-AIdl6WAn9aymeaH/NvBj0H9qM+XuAuYbGMZaP0zcXAQ=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "ffbc9f8cbaacfb331b6017d5a5abb21a492c9a38", + "rev": "1412caf7bf9e660f2f962917c14b1ea1c3bc695e", "type": "github" }, "original": { @@ -107,6 +116,7 @@ "root": { "inputs": { "deploy-rs": "deploy-rs", + "mix": "mix", "nib": "nib", "nixpkgs": "nixpkgs_2", "nixpkgs-unstable": "nixpkgs-unstable", From a645ac38ef70343c8c9a0a066d0f9534b5db2510 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Thu, 15 Jan 2026 13:08:57 +1000 Subject: [PATCH 126/338] progress flake.lock --- flake.lock | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/flake.lock b/flake.lock index 4c21514..9271e7a 100644 --- a/flake.lock +++ b/flake.lock @@ -36,6 +36,18 @@ "type": "github" } }, + "mix": { + "locked": { + "lastModified": 1768446394, + "narHash": "sha256-3mDswdUKnDSY571nstThXDSZG5wB4agkiOuJUV4YKgM=", + "path": "/home/me/agribit/nexus/mix", + "type": "path" + }, + "original": { + "path": "/home/me/agribit/nexus/mix", + "type": "path" + } + }, "nib": { "inputs": { "systems": [ @@ -43,17 +55,14 @@ ] }, "locked": { - "lastModified": 1768442186, - "narHash": "sha256-PMhz5gpLDBve8lxNX9eDLDC2+Y2mdPy8e8Pflzp6YYc=", - "owner": "emilelcb", - "repo": "nib", - "rev": "f3089ca0161e2925d24e9206b0a315319a1ad105", - "type": "github" + "lastModified": 1768446390, + "narHash": "sha256-Cp8dd4A3HsXUYCaQqCqb+VKwUdK0FAe8HyibVqZV/pA=", + "path": "/home/me/agribit/nexus/nib", + "type": "path" }, "original": { - "owner": "emilelcb", - "repo": "nib", - "type": "github" + "path": "/home/me/agribit/nexus/nib", + "type": "path" } }, "nixpkgs": { @@ -74,11 +83,11 @@ }, "nixpkgs-unstable": { "locked": { - "lastModified": 1768127708, - "narHash": "sha256-1Sm77VfZh3mU0F5OqKABNLWxOuDeHIlcFjsXeeiPazs=", + "lastModified": 1768305791, + "narHash": "sha256-AIdl6WAn9aymeaH/NvBj0H9qM+XuAuYbGMZaP0zcXAQ=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "ffbc9f8cbaacfb331b6017d5a5abb21a492c9a38", + "rev": "1412caf7bf9e660f2f962917c14b1ea1c3bc695e", "type": "github" }, "original": { @@ -107,6 +116,7 @@ "root": { "inputs": { "deploy-rs": "deploy-rs", + "mix": "mix", "nib": "nib", "nixpkgs": "nixpkgs_2", "nixpkgs-unstable": "nixpkgs-unstable", From 9202fa86789db5415c1fe0e03e7a89c0977b21b4 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Thu, 15 Jan 2026 13:09:28 +1000 Subject: [PATCH 127/338] move overlays from flake.nix -> cerulean/default.nix --- cerulean/default.nix | 18 +++++++++++++++++- flake.nix | 23 ++++++----------------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/cerulean/default.nix b/cerulean/default.nix index e72374e..d100652 100644 --- a/cerulean/default.nix +++ b/cerulean/default.nix @@ -11,10 +11,26 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -{mix, ...} @ inputs: +{ + mix, + deploy-rs, + ... +} @ inputs: mix.mkMod (mix.newMixture {specialArgs = inputs;}) (mixture: { includes.public = [ ./flake-config ]; + + overlays = [ + # build deploy-rs as a package not from the flake input, + # hence we can rely on a nixpkg binary cache. + deploy-rs.overlays.default + (self: super: { + deploy-rs = { + inherit (super) deploy-rs; + lib = super.deploy-rs.lib; + }; + }) + ]; }) diff --git a/flake.nix b/flake.nix index 94a4094..6de0367 100644 --- a/flake.nix +++ b/flake.nix @@ -35,10 +35,12 @@ nixpkgs, nixpkgs-unstable, nib, - deploy-rs, ... } @ inputs: let - lib = nixpkgs.lib; + inherit + (nixpkgs) + lib + ; sys = nib.mkUSys { pkgs = nib.withPkgs nixpkgs { @@ -49,19 +51,6 @@ config.allowUnfree = false; }; }; - - cerulean = import ./cerulean {inherit inputs lib sys;}; - in { - overlays = [ - # build deploy-rs as a package not from the flake input, - # hence we can rely on a nixpkg binary cache. - deploy-rs.overlays.default - (self: super: { - deploy-rs = { - inherit (super) deploy-rs; - lib = super.deploy-rs.lib; - }; - }) - ]; - }; + in + import ./cerulean <| inputs // {inherit lib sys;}; } From 4b6b7a31a4d1120bf7896bea83465a12669d2686 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Thu, 15 Jan 2026 13:09:28 +1000 Subject: [PATCH 128/338] move overlays from flake.nix -> cerulean/default.nix --- cerulean/default.nix | 18 +++++++++++++++++- flake.nix | 23 ++++++----------------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/cerulean/default.nix b/cerulean/default.nix index e72374e..d100652 100644 --- a/cerulean/default.nix +++ b/cerulean/default.nix @@ -11,10 +11,26 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -{mix, ...} @ inputs: +{ + mix, + deploy-rs, + ... +} @ inputs: mix.mkMod (mix.newMixture {specialArgs = inputs;}) (mixture: { includes.public = [ ./flake-config ]; + + overlays = [ + # build deploy-rs as a package not from the flake input, + # hence we can rely on a nixpkg binary cache. + deploy-rs.overlays.default + (self: super: { + deploy-rs = { + inherit (super) deploy-rs; + lib = super.deploy-rs.lib; + }; + }) + ]; }) diff --git a/flake.nix b/flake.nix index 94a4094..6de0367 100644 --- a/flake.nix +++ b/flake.nix @@ -35,10 +35,12 @@ nixpkgs, nixpkgs-unstable, nib, - deploy-rs, ... } @ inputs: let - lib = nixpkgs.lib; + inherit + (nixpkgs) + lib + ; sys = nib.mkUSys { pkgs = nib.withPkgs nixpkgs { @@ -49,19 +51,6 @@ config.allowUnfree = false; }; }; - - cerulean = import ./cerulean {inherit inputs lib sys;}; - in { - overlays = [ - # build deploy-rs as a package not from the flake input, - # hence we can rely on a nixpkg binary cache. - deploy-rs.overlays.default - (self: super: { - deploy-rs = { - inherit (super) deploy-rs; - lib = super.deploy-rs.lib; - }; - }) - ]; - }; + in + import ./cerulean <| inputs // {inherit lib sys;}; } From 0321be73372697046d57910822de4003d07d08cd Mon Sep 17 00:00:00 2001 From: _cry64 Date: Thu, 15 Jan 2026 13:10:18 +1000 Subject: [PATCH 129/338] DEBUG: use local flakes --- flake.lock | 8 ++++---- flake.nix | 6 ++++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/flake.lock b/flake.lock index 9271e7a..a38a1aa 100644 --- a/flake.lock +++ b/flake.lock @@ -38,8 +38,8 @@ }, "mix": { "locked": { - "lastModified": 1768446394, - "narHash": "sha256-3mDswdUKnDSY571nstThXDSZG5wB4agkiOuJUV4YKgM=", + "lastModified": 1768446523, + "narHash": "sha256-qnCAzSd9Mx2vTgoDDN8YaIfzebjbBffbqaPG+G97dWM=", "path": "/home/me/agribit/nexus/mix", "type": "path" }, @@ -55,8 +55,8 @@ ] }, "locked": { - "lastModified": 1768446390, - "narHash": "sha256-Cp8dd4A3HsXUYCaQqCqb+VKwUdK0FAe8HyibVqZV/pA=", + "lastModified": 1768446552, + "narHash": "sha256-2SrC9Jw/SytCT0mOjdu/u3F/DW0AUlBRWgHQ5rRWdKY=", "path": "/home/me/agribit/nexus/nib", "type": "path" }, diff --git a/flake.nix b/flake.nix index 6de0367..7e341d2 100644 --- a/flake.nix +++ b/flake.nix @@ -21,11 +21,13 @@ nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixos-unstable"; nib = { - url = "github:emilelcb/nib"; + # url = "github:emilelcb/nib"; + url = "/home/me/agribit/nexus/nib"; inputs.systems.follows = "systems"; }; - mix.url = "github:emilelcb/mix"; + # mix.url = "github:emilelcb/mix"; + mix.url = "/home/me/agribit/nexus/mix"; deploy-rs.url = "github:serokell/deploy-rs"; }; From ccf9fbebc93c7d75d274457ea51842002d0b960d Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Thu, 15 Jan 2026 13:10:18 +1000 Subject: [PATCH 130/338] DEBUG: use local flakes --- flake.lock | 8 ++++---- flake.nix | 6 ++++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/flake.lock b/flake.lock index 9271e7a..a38a1aa 100644 --- a/flake.lock +++ b/flake.lock @@ -38,8 +38,8 @@ }, "mix": { "locked": { - "lastModified": 1768446394, - "narHash": "sha256-3mDswdUKnDSY571nstThXDSZG5wB4agkiOuJUV4YKgM=", + "lastModified": 1768446523, + "narHash": "sha256-qnCAzSd9Mx2vTgoDDN8YaIfzebjbBffbqaPG+G97dWM=", "path": "/home/me/agribit/nexus/mix", "type": "path" }, @@ -55,8 +55,8 @@ ] }, "locked": { - "lastModified": 1768446390, - "narHash": "sha256-Cp8dd4A3HsXUYCaQqCqb+VKwUdK0FAe8HyibVqZV/pA=", + "lastModified": 1768446552, + "narHash": "sha256-2SrC9Jw/SytCT0mOjdu/u3F/DW0AUlBRWgHQ5rRWdKY=", "path": "/home/me/agribit/nexus/nib", "type": "path" }, diff --git a/flake.nix b/flake.nix index 6de0367..7e341d2 100644 --- a/flake.nix +++ b/flake.nix @@ -21,11 +21,13 @@ nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixos-unstable"; nib = { - url = "github:emilelcb/nib"; + # url = "github:emilelcb/nib"; + url = "/home/me/agribit/nexus/nib"; inputs.systems.follows = "systems"; }; - mix.url = "github:emilelcb/mix"; + # mix.url = "github:emilelcb/mix"; + mix.url = "/home/me/agribit/nexus/mix"; deploy-rs.url = "github:serokell/deploy-rs"; }; From b1cc88115ef2b5cc26b1378cedb0569e5f6404e9 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Thu, 15 Jan 2026 13:15:32 +1000 Subject: [PATCH 131/338] inputs.mix.nib follow inputs.nib --- flake.lock | 13 +++++++++---- flake.nix | 7 +++++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/flake.lock b/flake.lock index a38a1aa..3ec945d 100644 --- a/flake.lock +++ b/flake.lock @@ -37,9 +37,14 @@ } }, "mix": { + "inputs": { + "nib": [ + "nib" + ] + }, "locked": { - "lastModified": 1768446523, - "narHash": "sha256-qnCAzSd9Mx2vTgoDDN8YaIfzebjbBffbqaPG+G97dWM=", + "lastModified": 1768446884, + "narHash": "sha256-LinPc6fsP8j5hczHSMqKLsyj0ZUqKdoVOsESa2q0WTE=", "path": "/home/me/agribit/nexus/mix", "type": "path" }, @@ -55,8 +60,8 @@ ] }, "locked": { - "lastModified": 1768446552, - "narHash": "sha256-2SrC9Jw/SytCT0mOjdu/u3F/DW0AUlBRWgHQ5rRWdKY=", + "lastModified": 1768446852, + "narHash": "sha256-n+MiQNdY9ySvKstU549Iwmn1ZVh3pwO9ILH0vMcvF/U=", "path": "/home/me/agribit/nexus/nib", "type": "path" }, diff --git a/flake.nix b/flake.nix index 7e341d2..fc9e7ea 100644 --- a/flake.nix +++ b/flake.nix @@ -26,8 +26,11 @@ inputs.systems.follows = "systems"; }; - # mix.url = "github:emilelcb/mix"; - mix.url = "/home/me/agribit/nexus/mix"; + mix = { + # url = "github:emilelcb/mix"; + url = "/home/me/agribit/nexus/mix"; + inputs.nib.follows = "nib"; + }; deploy-rs.url = "github:serokell/deploy-rs"; }; From b0f2fe9b7f9b0f7bb5c2b13b29c391fafbbf44ee Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Thu, 15 Jan 2026 13:15:32 +1000 Subject: [PATCH 132/338] inputs.mix.nib follow inputs.nib --- flake.lock | 13 +++++++++---- flake.nix | 7 +++++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/flake.lock b/flake.lock index a38a1aa..3ec945d 100644 --- a/flake.lock +++ b/flake.lock @@ -37,9 +37,14 @@ } }, "mix": { + "inputs": { + "nib": [ + "nib" + ] + }, "locked": { - "lastModified": 1768446523, - "narHash": "sha256-qnCAzSd9Mx2vTgoDDN8YaIfzebjbBffbqaPG+G97dWM=", + "lastModified": 1768446884, + "narHash": "sha256-LinPc6fsP8j5hczHSMqKLsyj0ZUqKdoVOsESa2q0WTE=", "path": "/home/me/agribit/nexus/mix", "type": "path" }, @@ -55,8 +60,8 @@ ] }, "locked": { - "lastModified": 1768446552, - "narHash": "sha256-2SrC9Jw/SytCT0mOjdu/u3F/DW0AUlBRWgHQ5rRWdKY=", + "lastModified": 1768446852, + "narHash": "sha256-n+MiQNdY9ySvKstU549Iwmn1ZVh3pwO9ILH0vMcvF/U=", "path": "/home/me/agribit/nexus/nib", "type": "path" }, diff --git a/flake.nix b/flake.nix index 7e341d2..fc9e7ea 100644 --- a/flake.nix +++ b/flake.nix @@ -26,8 +26,11 @@ inputs.systems.follows = "systems"; }; - # mix.url = "github:emilelcb/mix"; - mix.url = "/home/me/agribit/nexus/mix"; + mix = { + # url = "github:emilelcb/mix"; + url = "/home/me/agribit/nexus/mix"; + inputs.nib.follows = "nib"; + }; deploy-rs.url = "github:serokell/deploy-rs"; }; From 3b8852b85edd3ae7c5997cd3e9fe2d3be9f87794 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Thu, 15 Jan 2026 13:18:35 +1000 Subject: [PATCH 133/338] fix mergeStructs renamed -> overrideStruct --- cerulean/flake-config/nodes.nix | 2 +- flake.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cerulean/flake-config/nodes.nix b/cerulean/flake-config/nodes.nix index fdc32a4..6fc5cf1 100644 --- a/cerulean/flake-config/nodes.nix +++ b/cerulean/flake-config/nodes.nix @@ -60,7 +60,7 @@ else let templateAttrs = templateNode name nodeAttrs.system; in - nib.parse.mergeStructs templateAttrs nodeAttrs; + nib.parse.overrideStruct templateAttrs nodeAttrs; mapNodes' = f: builtins.mapAttrs diff --git a/flake.lock b/flake.lock index 3ec945d..2c723c3 100644 --- a/flake.lock +++ b/flake.lock @@ -43,8 +43,8 @@ ] }, "locked": { - "lastModified": 1768446884, - "narHash": "sha256-LinPc6fsP8j5hczHSMqKLsyj0ZUqKdoVOsESa2q0WTE=", + "lastModified": 1768447123, + "narHash": "sha256-5OVX+fA3qmbpr0vmMnJyLrz6TlJGv4hnpWG4lPcpFNU=", "path": "/home/me/agribit/nexus/mix", "type": "path" }, @@ -60,8 +60,8 @@ ] }, "locked": { - "lastModified": 1768446852, - "narHash": "sha256-n+MiQNdY9ySvKstU549Iwmn1ZVh3pwO9ILH0vMcvF/U=", + "lastModified": 1768447092, + "narHash": "sha256-yqW9rUQE8dLKHBZ3RyrENWijc40sODPEIO6nTogoItg=", "path": "/home/me/agribit/nexus/nib", "type": "path" }, From d0a103a32951c79b8a6c99dbcb883d1542304e05 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Thu, 15 Jan 2026 13:18:35 +1000 Subject: [PATCH 134/338] fix mergeStructs renamed -> overrideStruct --- cerulean/flake-config/nodes.nix | 2 +- flake.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cerulean/flake-config/nodes.nix b/cerulean/flake-config/nodes.nix index fdc32a4..6fc5cf1 100644 --- a/cerulean/flake-config/nodes.nix +++ b/cerulean/flake-config/nodes.nix @@ -60,7 +60,7 @@ else let templateAttrs = templateNode name nodeAttrs.system; in - nib.parse.mergeStructs templateAttrs nodeAttrs; + nib.parse.overrideStruct templateAttrs nodeAttrs; mapNodes' = f: builtins.mapAttrs diff --git a/flake.lock b/flake.lock index 3ec945d..2c723c3 100644 --- a/flake.lock +++ b/flake.lock @@ -43,8 +43,8 @@ ] }, "locked": { - "lastModified": 1768446884, - "narHash": "sha256-LinPc6fsP8j5hczHSMqKLsyj0ZUqKdoVOsESa2q0WTE=", + "lastModified": 1768447123, + "narHash": "sha256-5OVX+fA3qmbpr0vmMnJyLrz6TlJGv4hnpWG4lPcpFNU=", "path": "/home/me/agribit/nexus/mix", "type": "path" }, @@ -60,8 +60,8 @@ ] }, "locked": { - "lastModified": 1768446852, - "narHash": "sha256-n+MiQNdY9ySvKstU549Iwmn1ZVh3pwO9ILH0vMcvF/U=", + "lastModified": 1768447092, + "narHash": "sha256-yqW9rUQE8dLKHBZ3RyrENWijc40sODPEIO6nTogoItg=", "path": "/home/me/agribit/nexus/nib", "type": "path" }, From 3a1c1be48f32f9d85ad2a323e57acada321850ac Mon Sep 17 00:00:00 2001 From: _cry64 Date: Thu, 15 Jan 2026 14:11:56 +1000 Subject: [PATCH 135/338] DEBUG: progress flake.lock --- flake.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/flake.lock b/flake.lock index 2c723c3..301e190 100644 --- a/flake.lock +++ b/flake.lock @@ -43,8 +43,8 @@ ] }, "locked": { - "lastModified": 1768447123, - "narHash": "sha256-5OVX+fA3qmbpr0vmMnJyLrz6TlJGv4hnpWG4lPcpFNU=", + "lastModified": 1768450300, + "narHash": "sha256-dmy3lOlkYYXOwQZI73Wvu6N6HXkw3y9QqaKsbcbU/Jk=", "path": "/home/me/agribit/nexus/mix", "type": "path" }, @@ -60,8 +60,8 @@ ] }, "locked": { - "lastModified": 1768447092, - "narHash": "sha256-yqW9rUQE8dLKHBZ3RyrENWijc40sODPEIO6nTogoItg=", + "lastModified": 1768450257, + "narHash": "sha256-Xjrq9tofKIXPomr+vLquFlJhPcmID+ivYEdizoSxGb8=", "path": "/home/me/agribit/nexus/nib", "type": "path" }, From dfc4cce236adff14ec20f5a0b2c24fb8b83da8e6 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Thu, 15 Jan 2026 14:11:56 +1000 Subject: [PATCH 136/338] DEBUG: progress flake.lock --- flake.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/flake.lock b/flake.lock index 2c723c3..301e190 100644 --- a/flake.lock +++ b/flake.lock @@ -43,8 +43,8 @@ ] }, "locked": { - "lastModified": 1768447123, - "narHash": "sha256-5OVX+fA3qmbpr0vmMnJyLrz6TlJGv4hnpWG4lPcpFNU=", + "lastModified": 1768450300, + "narHash": "sha256-dmy3lOlkYYXOwQZI73Wvu6N6HXkw3y9QqaKsbcbU/Jk=", "path": "/home/me/agribit/nexus/mix", "type": "path" }, @@ -60,8 +60,8 @@ ] }, "locked": { - "lastModified": 1768447092, - "narHash": "sha256-yqW9rUQE8dLKHBZ3RyrENWijc40sODPEIO6nTogoItg=", + "lastModified": 1768450257, + "narHash": "sha256-Xjrq9tofKIXPomr+vLquFlJhPcmID+ivYEdizoSxGb8=", "path": "/home/me/agribit/nexus/nib", "type": "path" }, From ec82962a8f9f466ae3b0e860be21b06cb78c4ec7 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Thu, 15 Jan 2026 15:08:21 +1000 Subject: [PATCH 137/338] attempt patch via new mix changes --- cerulean/default.nix | 3 +-- cerulean/flake-config/default.nix | 9 ++------- flake.lock | 8 ++++---- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/cerulean/default.nix b/cerulean/default.nix index d100652..2755f4c 100644 --- a/cerulean/default.nix +++ b/cerulean/default.nix @@ -16,8 +16,7 @@ deploy-rs, ... } @ inputs: -mix.mkMod (mix.newMixture {specialArgs = inputs;}) -(mixture: { +mix.newMixture inputs (mixture: { includes.public = [ ./flake-config ]; diff --git a/cerulean/flake-config/default.nix b/cerulean/flake-config/default.nix index 1a4e885..f31d0be 100644 --- a/cerulean/flake-config/default.nix +++ b/cerulean/flake-config/default.nix @@ -11,13 +11,8 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -{ - mix, - mixture, - ... -}: -mix.mkMod mixture -(mixture: { +{mix, ...} @ inputs: +mix.newMixture inputs (mixture: { includes.public = [ ./nodes ./nexus diff --git a/flake.lock b/flake.lock index 301e190..22753d2 100644 --- a/flake.lock +++ b/flake.lock @@ -43,8 +43,8 @@ ] }, "locked": { - "lastModified": 1768450300, - "narHash": "sha256-dmy3lOlkYYXOwQZI73Wvu6N6HXkw3y9QqaKsbcbU/Jk=", + "lastModified": 1768453675, + "narHash": "sha256-KirWmGJS37nZ8Ub64gUb/iHGMiwrp83DHYgRIujFOgw=", "path": "/home/me/agribit/nexus/mix", "type": "path" }, @@ -60,8 +60,8 @@ ] }, "locked": { - "lastModified": 1768450257, - "narHash": "sha256-Xjrq9tofKIXPomr+vLquFlJhPcmID+ivYEdizoSxGb8=", + "lastModified": 1768453677, + "narHash": "sha256-PccWYqS05ZPEG2tA+u0CxgQK1ikt9FKX515yr2LljG0=", "path": "/home/me/agribit/nexus/nib", "type": "path" }, From f5d0b6aa1a762fd45c553b88aa6135a59d025521 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Thu, 15 Jan 2026 15:08:21 +1000 Subject: [PATCH 138/338] attempt patch via new mix changes --- cerulean/default.nix | 3 +-- cerulean/flake-config/default.nix | 9 ++------- flake.lock | 8 ++++---- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/cerulean/default.nix b/cerulean/default.nix index d100652..2755f4c 100644 --- a/cerulean/default.nix +++ b/cerulean/default.nix @@ -16,8 +16,7 @@ deploy-rs, ... } @ inputs: -mix.mkMod (mix.newMixture {specialArgs = inputs;}) -(mixture: { +mix.newMixture inputs (mixture: { includes.public = [ ./flake-config ]; diff --git a/cerulean/flake-config/default.nix b/cerulean/flake-config/default.nix index 1a4e885..f31d0be 100644 --- a/cerulean/flake-config/default.nix +++ b/cerulean/flake-config/default.nix @@ -11,13 +11,8 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -{ - mix, - mixture, - ... -}: -mix.mkMod mixture -(mixture: { +{mix, ...} @ inputs: +mix.newMixture inputs (mixture: { includes.public = [ ./nodes ./nexus diff --git a/flake.lock b/flake.lock index 301e190..22753d2 100644 --- a/flake.lock +++ b/flake.lock @@ -43,8 +43,8 @@ ] }, "locked": { - "lastModified": 1768450300, - "narHash": "sha256-dmy3lOlkYYXOwQZI73Wvu6N6HXkw3y9QqaKsbcbU/Jk=", + "lastModified": 1768453675, + "narHash": "sha256-KirWmGJS37nZ8Ub64gUb/iHGMiwrp83DHYgRIujFOgw=", "path": "/home/me/agribit/nexus/mix", "type": "path" }, @@ -60,8 +60,8 @@ ] }, "locked": { - "lastModified": 1768450257, - "narHash": "sha256-Xjrq9tofKIXPomr+vLquFlJhPcmID+ivYEdizoSxGb8=", + "lastModified": 1768453677, + "narHash": "sha256-PccWYqS05ZPEG2tA+u0CxgQK1ikt9FKX515yr2LljG0=", "path": "/home/me/agribit/nexus/nib", "type": "path" }, From 843342d9bd02ca89951ceb6146824e6a3ed33cb2 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Thu, 15 Jan 2026 15:16:46 +1000 Subject: [PATCH 139/338] chat am I stupid? --- cerulean/flake-config/default.nix | 4 ++-- flake.lock | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cerulean/flake-config/default.nix b/cerulean/flake-config/default.nix index f31d0be..d8e0f7c 100644 --- a/cerulean/flake-config/default.nix +++ b/cerulean/flake-config/default.nix @@ -14,7 +14,7 @@ {mix, ...} @ inputs: mix.newMixture inputs (mixture: { includes.public = [ - ./nodes - ./nexus + ./nodes.nix + ./nexus.nix ]; }) diff --git a/flake.lock b/flake.lock index 22753d2..f063672 100644 --- a/flake.lock +++ b/flake.lock @@ -43,8 +43,8 @@ ] }, "locked": { - "lastModified": 1768453675, - "narHash": "sha256-KirWmGJS37nZ8Ub64gUb/iHGMiwrp83DHYgRIujFOgw=", + "lastModified": 1768454163, + "narHash": "sha256-slX94aaVtdt6bFWpxAizjxM09LMtgXyMM04MYb5Ep/o=", "path": "/home/me/agribit/nexus/mix", "type": "path" }, @@ -60,8 +60,8 @@ ] }, "locked": { - "lastModified": 1768453677, - "narHash": "sha256-PccWYqS05ZPEG2tA+u0CxgQK1ikt9FKX515yr2LljG0=", + "lastModified": 1768454172, + "narHash": "sha256-i5f2nUwui50Ryo43gNtN1OXzp9YtZ3guT8VnbzmF18Y=", "path": "/home/me/agribit/nexus/nib", "type": "path" }, From fc9d79f4669935dc10b400e52cce669be54ba051 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Thu, 15 Jan 2026 15:16:46 +1000 Subject: [PATCH 140/338] chat am I stupid? --- cerulean/flake-config/default.nix | 4 ++-- flake.lock | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cerulean/flake-config/default.nix b/cerulean/flake-config/default.nix index f31d0be..d8e0f7c 100644 --- a/cerulean/flake-config/default.nix +++ b/cerulean/flake-config/default.nix @@ -14,7 +14,7 @@ {mix, ...} @ inputs: mix.newMixture inputs (mixture: { includes.public = [ - ./nodes - ./nexus + ./nodes.nix + ./nexus.nix ]; }) diff --git a/flake.lock b/flake.lock index 22753d2..f063672 100644 --- a/flake.lock +++ b/flake.lock @@ -43,8 +43,8 @@ ] }, "locked": { - "lastModified": 1768453675, - "narHash": "sha256-KirWmGJS37nZ8Ub64gUb/iHGMiwrp83DHYgRIujFOgw=", + "lastModified": 1768454163, + "narHash": "sha256-slX94aaVtdt6bFWpxAizjxM09LMtgXyMM04MYb5Ep/o=", "path": "/home/me/agribit/nexus/mix", "type": "path" }, @@ -60,8 +60,8 @@ ] }, "locked": { - "lastModified": 1768453677, - "narHash": "sha256-PccWYqS05ZPEG2tA+u0CxgQK1ikt9FKX515yr2LljG0=", + "lastModified": 1768454172, + "narHash": "sha256-i5f2nUwui50Ryo43gNtN1OXzp9YtZ3guT8VnbzmF18Y=", "path": "/home/me/agribit/nexus/nib", "type": "path" }, From 2b44d27e2f35471f8c16518a95e1f701f8a2b17f Mon Sep 17 00:00:00 2001 From: _cry64 Date: Thu, 15 Jan 2026 15:21:48 +1000 Subject: [PATCH 141/338] use this instead of mixture --- cerulean/flake-config/nexus.nix | 4 ++-- flake.lock | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cerulean/flake-config/nexus.nix b/cerulean/flake-config/nexus.nix index 2e02119..3f1094f 100644 --- a/cerulean/flake-config/nexus.nix +++ b/cerulean/flake-config/nexus.nix @@ -12,14 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. { - mixture, + this, sys, lib, deploy-rs, ... }: let inherit - (mixture.nodes) + (this) mapNodes ; diff --git a/flake.lock b/flake.lock index f063672..6e7f15d 100644 --- a/flake.lock +++ b/flake.lock @@ -43,8 +43,8 @@ ] }, "locked": { - "lastModified": 1768454163, - "narHash": "sha256-slX94aaVtdt6bFWpxAizjxM09LMtgXyMM04MYb5Ep/o=", + "lastModified": 1768454486, + "narHash": "sha256-ktzDBTuKNx/B7rcdSttik3rkzVs/nhF2BO/8R6qx81A=", "path": "/home/me/agribit/nexus/mix", "type": "path" }, @@ -60,8 +60,8 @@ ] }, "locked": { - "lastModified": 1768454172, - "narHash": "sha256-i5f2nUwui50Ryo43gNtN1OXzp9YtZ3guT8VnbzmF18Y=", + "lastModified": 1768454457, + "narHash": "sha256-9BTy1Plb49Bfu2Ki3sciIMtcWPrIrBTThkszB/TkQA8=", "path": "/home/me/agribit/nexus/nib", "type": "path" }, From 78d412956da518040346a302efacedf5b7ca372e Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Thu, 15 Jan 2026 15:21:48 +1000 Subject: [PATCH 142/338] use this instead of mixture --- cerulean/flake-config/nexus.nix | 4 ++-- flake.lock | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cerulean/flake-config/nexus.nix b/cerulean/flake-config/nexus.nix index 2e02119..3f1094f 100644 --- a/cerulean/flake-config/nexus.nix +++ b/cerulean/flake-config/nexus.nix @@ -12,14 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. { - mixture, + this, sys, lib, deploy-rs, ... }: let inherit - (mixture.nodes) + (this) mapNodes ; diff --git a/flake.lock b/flake.lock index f063672..6e7f15d 100644 --- a/flake.lock +++ b/flake.lock @@ -43,8 +43,8 @@ ] }, "locked": { - "lastModified": 1768454163, - "narHash": "sha256-slX94aaVtdt6bFWpxAizjxM09LMtgXyMM04MYb5Ep/o=", + "lastModified": 1768454486, + "narHash": "sha256-ktzDBTuKNx/B7rcdSttik3rkzVs/nhF2BO/8R6qx81A=", "path": "/home/me/agribit/nexus/mix", "type": "path" }, @@ -60,8 +60,8 @@ ] }, "locked": { - "lastModified": 1768454172, - "narHash": "sha256-i5f2nUwui50Ryo43gNtN1OXzp9YtZ3guT8VnbzmF18Y=", + "lastModified": 1768454457, + "narHash": "sha256-9BTy1Plb49Bfu2Ki3sciIMtcWPrIrBTThkszB/TkQA8=", "path": "/home/me/agribit/nexus/nib", "type": "path" }, From f0b2eb0fa3071ebcf4dd69e9d71bad0c7521403b Mon Sep 17 00:00:00 2001 From: _cry64 Date: Thu, 15 Jan 2026 15:24:28 +1000 Subject: [PATCH 143/338] fix type mapNodes' -> mapNodes --- cerulean/flake-config/nodes.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cerulean/flake-config/nodes.nix b/cerulean/flake-config/nodes.nix index 6fc5cf1..bf4669f 100644 --- a/cerulean/flake-config/nodes.nix +++ b/cerulean/flake-config/nodes.nix @@ -62,7 +62,7 @@ in nib.parse.overrideStruct templateAttrs nodeAttrs; - mapNodes' = f: + mapNodes = f: builtins.mapAttrs (nodeName: nodeAttrs: f nodeName (parseNode nodeName nodeAttrs)); } From a6ef376c3075cafac8aaffaf6b5e0b5e1b42654c Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Thu, 15 Jan 2026 15:24:28 +1000 Subject: [PATCH 144/338] fix type mapNodes' -> mapNodes --- cerulean/flake-config/nodes.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cerulean/flake-config/nodes.nix b/cerulean/flake-config/nodes.nix index 6fc5cf1..bf4669f 100644 --- a/cerulean/flake-config/nodes.nix +++ b/cerulean/flake-config/nodes.nix @@ -62,7 +62,7 @@ in nib.parse.overrideStruct templateAttrs nodeAttrs; - mapNodes' = f: + mapNodes = f: builtins.mapAttrs (nodeName: nodeAttrs: f nodeName (parseNode nodeName nodeAttrs)); } From 7225b9a84410dcf5314419a9c048bb283daf1534 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Thu, 15 Jan 2026 15:26:47 +1000 Subject: [PATCH 145/338] inherit builtins --- cerulean/flake-config/nodes.nix | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/cerulean/flake-config/nodes.nix b/cerulean/flake-config/nodes.nix index bf4669f..5f7b877 100644 --- a/cerulean/flake-config/nodes.nix +++ b/cerulean/flake-config/nodes.nix @@ -11,7 +11,14 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -{nib, ...}: rec { +{nib, ...}: let + inherit + (builtins) + isAttrs + mapAttrs + typeOf + ; +in rec { # abstract node instance that stores all default values templateNode = name: system: let Terminal = nib.types.Terminal; @@ -50,11 +57,11 @@ }; parseNode = name: nodeAttrs: - if !(builtins.isAttrs nodeAttrs) + if !(isAttrs nodeAttrs) then # fail if node is not an attribute set abort '' - Cerulean Nexus nodes must be provided as an attribute set, got "${builtins.typeOf nodeAttrs}" instead! + Cerulean Nexus nodes must be provided as an attribute set, got "${typeOf nodeAttrs}" instead! Ensure all `cerulean.nexus.nodes.${name}` declarations are attribute sets under your call to `cerulean.mkNexus`. '' else let @@ -63,6 +70,6 @@ nib.parse.overrideStruct templateAttrs nodeAttrs; mapNodes = f: - builtins.mapAttrs + mapAttrs (nodeName: nodeAttrs: f nodeName (parseNode nodeName nodeAttrs)); } From 1fb847918dbef133f83fdd0dfd147ab95a322d6a Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Thu, 15 Jan 2026 15:26:47 +1000 Subject: [PATCH 146/338] inherit builtins --- cerulean/flake-config/nodes.nix | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/cerulean/flake-config/nodes.nix b/cerulean/flake-config/nodes.nix index bf4669f..5f7b877 100644 --- a/cerulean/flake-config/nodes.nix +++ b/cerulean/flake-config/nodes.nix @@ -11,7 +11,14 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -{nib, ...}: rec { +{nib, ...}: let + inherit + (builtins) + isAttrs + mapAttrs + typeOf + ; +in rec { # abstract node instance that stores all default values templateNode = name: system: let Terminal = nib.types.Terminal; @@ -50,11 +57,11 @@ }; parseNode = name: nodeAttrs: - if !(builtins.isAttrs nodeAttrs) + if !(isAttrs nodeAttrs) then # fail if node is not an attribute set abort '' - Cerulean Nexus nodes must be provided as an attribute set, got "${builtins.typeOf nodeAttrs}" instead! + Cerulean Nexus nodes must be provided as an attribute set, got "${typeOf nodeAttrs}" instead! Ensure all `cerulean.nexus.nodes.${name}` declarations are attribute sets under your call to `cerulean.mkNexus`. '' else let @@ -63,6 +70,6 @@ nib.parse.overrideStruct templateAttrs nodeAttrs; mapNodes = f: - builtins.mapAttrs + mapAttrs (nodeName: nodeAttrs: f nodeName (parseNode nodeName nodeAttrs)); } From cc2d7f58e906c0eaf26d5f7ab92a2e77809427ee Mon Sep 17 00:00:00 2001 From: _cry64 Date: Thu, 15 Jan 2026 15:28:45 +1000 Subject: [PATCH 147/338] flip mapNodes currying --- cerulean/flake-config/nodes.nix | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cerulean/flake-config/nodes.nix b/cerulean/flake-config/nodes.nix index 5f7b877..5c560fa 100644 --- a/cerulean/flake-config/nodes.nix +++ b/cerulean/flake-config/nodes.nix @@ -69,7 +69,7 @@ in rec { in nib.parse.overrideStruct templateAttrs nodeAttrs; - mapNodes = f: - mapAttrs - (nodeName: nodeAttrs: f nodeName (parseNode nodeName nodeAttrs)); + mapNodes = nodes: f: + nodes + |> mapAttrs (nodeName: nodeAttrs: f nodeName (parseNode nodeName nodeAttrs)); } From d5fe62a27c98ea719d42551005e856ea5492bfee Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Thu, 15 Jan 2026 15:28:45 +1000 Subject: [PATCH 148/338] flip mapNodes currying --- cerulean/flake-config/nodes.nix | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cerulean/flake-config/nodes.nix b/cerulean/flake-config/nodes.nix index 5f7b877..5c560fa 100644 --- a/cerulean/flake-config/nodes.nix +++ b/cerulean/flake-config/nodes.nix @@ -69,7 +69,7 @@ in rec { in nib.parse.overrideStruct templateAttrs nodeAttrs; - mapNodes = f: - mapAttrs - (nodeName: nodeAttrs: f nodeName (parseNode nodeName nodeAttrs)); + mapNodes = nodes: f: + nodes + |> mapAttrs (nodeName: nodeAttrs: f nodeName (parseNode nodeName nodeAttrs)); } From 7735f61670ad864f01691f5653f5d3ea27df4725 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Thu, 15 Jan 2026 15:30:54 +1000 Subject: [PATCH 149/338] fix mapNodes never provided nodes list --- cerulean/flake-config/nexus.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cerulean/flake-config/nexus.nix b/cerulean/flake-config/nexus.nix index 3f1094f..bdc1ba5 100644 --- a/cerulean/flake-config/nexus.nix +++ b/cerulean/flake-config/nexus.nix @@ -24,7 +24,7 @@ ; mkNexus' = config: rec { - nixosConfigurations = mapNodes ( + nixosConfigurations = mapNodes config.nexus.nodes ( nodeName: node: lib.nixosSystem { system = node.system; @@ -40,7 +40,7 @@ } ); - deploy.nodes = mapNodes (nodeName: node: let + deploy.nodes = mapNodes config.nexus.nodes (nodeName: node: let inherit (node.deploy) activationTimeout From e2e31b661e2124080d94ac39b4e962991ddf2cb3 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Thu, 15 Jan 2026 15:30:54 +1000 Subject: [PATCH 150/338] fix mapNodes never provided nodes list --- cerulean/flake-config/nexus.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cerulean/flake-config/nexus.nix b/cerulean/flake-config/nexus.nix index 3f1094f..bdc1ba5 100644 --- a/cerulean/flake-config/nexus.nix +++ b/cerulean/flake-config/nexus.nix @@ -24,7 +24,7 @@ ; mkNexus' = config: rec { - nixosConfigurations = mapNodes ( + nixosConfigurations = mapNodes config.nexus.nodes ( nodeName: node: lib.nixosSystem { system = node.system; @@ -40,7 +40,7 @@ } ); - deploy.nodes = mapNodes (nodeName: node: let + deploy.nodes = mapNodes config.nexus.nodes (nodeName: node: let inherit (node.deploy) activationTimeout From e21bd210c9f8658b3b800d4aec0c74de6ade9893 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Thu, 15 Jan 2026 15:47:02 +1000 Subject: [PATCH 151/338] fix impossible relative import --- cerulean/flake-config/nexus.nix | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/cerulean/flake-config/nexus.nix b/cerulean/flake-config/nexus.nix index bdc1ba5..f97d3e5 100644 --- a/cerulean/flake-config/nexus.nix +++ b/cerulean/flake-config/nexus.nix @@ -18,6 +18,13 @@ deploy-rs, ... }: let + inherit + (builtins) + elem + mapAttrs + pathExists + ; + inherit (this) mapNodes @@ -28,7 +35,14 @@ nodeName: node: lib.nixosSystem { system = node.system; - modules = [./hosts/${nodeName}] ++ node.extraModules; + modules = let + core' = config.root + "/hosts/${nodeName}"; + core = + if pathExists core' + then core' + else core' + ".nix"; + in + [core] ++ node.extraModules; # nix passes these to every single module specialArgs = @@ -78,19 +92,19 @@ sshOpts = ssh.opts ++ ( - if builtins.elem "-p" ssh.opts + if elem "-p" ssh.opts then [] else ["-p" (toString ssh.port)] ) ++ ( - if builtins.elem "-A" ssh.opts + if elem "-A" ssh.opts then [] else ["-A"] ); }; }); - checks = builtins.mapAttrs (system: deployLib: deployLib.deployChecks deploy) deploy-rs.lib; + checks = mapAttrs (system: deployLib: deployLib.deployChecks deploy) deploy-rs.lib; }; in { mkNexus = outputs: From 807ff2e560acb7f29ae385ebd510541dbbb16908 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Thu, 15 Jan 2026 15:47:02 +1000 Subject: [PATCH 152/338] fix impossible relative import --- cerulean/flake-config/nexus.nix | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/cerulean/flake-config/nexus.nix b/cerulean/flake-config/nexus.nix index bdc1ba5..f97d3e5 100644 --- a/cerulean/flake-config/nexus.nix +++ b/cerulean/flake-config/nexus.nix @@ -18,6 +18,13 @@ deploy-rs, ... }: let + inherit + (builtins) + elem + mapAttrs + pathExists + ; + inherit (this) mapNodes @@ -28,7 +35,14 @@ nodeName: node: lib.nixosSystem { system = node.system; - modules = [./hosts/${nodeName}] ++ node.extraModules; + modules = let + core' = config.root + "/hosts/${nodeName}"; + core = + if pathExists core' + then core' + else core' + ".nix"; + in + [core] ++ node.extraModules; # nix passes these to every single module specialArgs = @@ -78,19 +92,19 @@ sshOpts = ssh.opts ++ ( - if builtins.elem "-p" ssh.opts + if elem "-p" ssh.opts then [] else ["-p" (toString ssh.port)] ) ++ ( - if builtins.elem "-A" ssh.opts + if elem "-A" ssh.opts then [] else ["-A"] ); }; }); - checks = builtins.mapAttrs (system: deployLib: deployLib.deployChecks deploy) deploy-rs.lib; + checks = mapAttrs (system: deployLib: deployLib.deployChecks deploy) deploy-rs.lib; }; in { mkNexus = outputs: From 57963b9c1610cf4e315afd5f49f6665f4ddf44d0 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Thu, 15 Jan 2026 20:10:16 +1000 Subject: [PATCH 153/338] introduce nexus templating via nib --- cerulean/{flake-config => nexus}/default.nix | 0 cerulean/{flake-config => nexus}/nexus.nix | 53 +++++++++++++++++--- cerulean/{flake-config => nexus}/nodes.nix | 5 +- 3 files changed, 50 insertions(+), 8 deletions(-) rename cerulean/{flake-config => nexus}/default.nix (100%) rename cerulean/{flake-config => nexus}/nexus.nix (63%) rename cerulean/{flake-config => nexus}/nodes.nix (97%) diff --git a/cerulean/flake-config/default.nix b/cerulean/nexus/default.nix similarity index 100% rename from cerulean/flake-config/default.nix rename to cerulean/nexus/default.nix diff --git a/cerulean/flake-config/nexus.nix b/cerulean/nexus/nexus.nix similarity index 63% rename from cerulean/flake-config/nexus.nix rename to cerulean/nexus/nexus.nix index f97d3e5..433be8b 100644 --- a/cerulean/flake-config/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -14,6 +14,7 @@ { this, sys, + nib, lib, deploy-rs, ... @@ -21,8 +22,10 @@ inherit (builtins) elem + isAttrs mapAttrs pathExists + typeOf ; inherit @@ -30,19 +33,52 @@ mapNodes ; - mkNexus' = config: rec { - nixosConfigurations = mapNodes config.nexus.nodes ( + inherit + (nib.std) + getAttrOr + ; + + templateNexus = let + inherit + (nib.types) + Terminal + ; + + missing = msg: path: + Terminal (abort '' + Each Cerulean Nexus node is required to specify ${msg}! + Ensure `nexus.${path}` exists under your call to `cerulean.mkNexus`. + ''); + in { + root = missing "the root directory for all cerulean nix modules." "root"; + groups = missing "an list of all valid node group names." "groups"; + nodes = Terminal {}; + }; + + parseNexus = nexus: + if !(isAttrs nexus) + then + abort '' + Cerulean Nexus config must be provided as an attribute set, got "${typeOf nexus}" instead! + Ensure all the `nexus` declaration is an attribute set under your call to `cerulean.mkNexus`. + '' + else nib.parse.overrideStruct templateNexus nexus; + + mkNexus' = nexus': let + nexus = parseNexus nexus'; + in rec { + nixosConfigurations = mapNodes nexus.nodes ( nodeName: node: lib.nixosSystem { system = node.system; modules = let - core' = config.root + "/hosts/${nodeName}"; + core' = nexus.root + "/hosts/${nodeName}"; core = if pathExists core' then core' else core' + ".nix"; in - [core] ++ node.extraModules; + [core ../nixos-module] ++ node.extraModules; # nix passes these to every single module specialArgs = @@ -54,7 +90,7 @@ } ); - deploy.nodes = mapNodes config.nexus.nodes (nodeName: node: let + deploy.nodes = mapNodes nexus.nodes (nodeName: node: let inherit (node.deploy) activationTimeout @@ -107,6 +143,9 @@ checks = mapAttrs (system: deployLib: deployLib.deployChecks deploy) deploy-rs.lib; }; in { - mkNexus = outputs: - (mkNexus' outputs.cerulean) // (removeAttrs outputs ["cerulean"]); + mkNexus = outputs': let + autogen = mkNexus' <| getAttrOr "nexus" outputs' {}; + outputs = removeAttrs outputs' ["nexus"]; + in + autogen // outputs; # XXX: TODO: replace this with a deep merge } diff --git a/cerulean/flake-config/nodes.nix b/cerulean/nexus/nodes.nix similarity index 97% rename from cerulean/flake-config/nodes.nix rename to cerulean/nexus/nodes.nix index 5c560fa..781a1fa 100644 --- a/cerulean/flake-config/nodes.nix +++ b/cerulean/nexus/nodes.nix @@ -21,7 +21,10 @@ in rec { # abstract node instance that stores all default values templateNode = name: system: let - Terminal = nib.types.Terminal; + inherit + (nib.types) + Terminal + ; missing = msg: path: Terminal (abort '' From 908255d012ea5d5b7403c399fa78a2114cde7d5d Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Thu, 15 Jan 2026 20:10:16 +1000 Subject: [PATCH 154/338] introduce nexus templating via nib --- cerulean/{flake-config => nexus}/default.nix | 0 cerulean/{flake-config => nexus}/nexus.nix | 53 +++++++++++++++++--- cerulean/{flake-config => nexus}/nodes.nix | 5 +- 3 files changed, 50 insertions(+), 8 deletions(-) rename cerulean/{flake-config => nexus}/default.nix (100%) rename cerulean/{flake-config => nexus}/nexus.nix (63%) rename cerulean/{flake-config => nexus}/nodes.nix (97%) diff --git a/cerulean/flake-config/default.nix b/cerulean/nexus/default.nix similarity index 100% rename from cerulean/flake-config/default.nix rename to cerulean/nexus/default.nix diff --git a/cerulean/flake-config/nexus.nix b/cerulean/nexus/nexus.nix similarity index 63% rename from cerulean/flake-config/nexus.nix rename to cerulean/nexus/nexus.nix index f97d3e5..433be8b 100644 --- a/cerulean/flake-config/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -14,6 +14,7 @@ { this, sys, + nib, lib, deploy-rs, ... @@ -21,8 +22,10 @@ inherit (builtins) elem + isAttrs mapAttrs pathExists + typeOf ; inherit @@ -30,19 +33,52 @@ mapNodes ; - mkNexus' = config: rec { - nixosConfigurations = mapNodes config.nexus.nodes ( + inherit + (nib.std) + getAttrOr + ; + + templateNexus = let + inherit + (nib.types) + Terminal + ; + + missing = msg: path: + Terminal (abort '' + Each Cerulean Nexus node is required to specify ${msg}! + Ensure `nexus.${path}` exists under your call to `cerulean.mkNexus`. + ''); + in { + root = missing "the root directory for all cerulean nix modules." "root"; + groups = missing "an list of all valid node group names." "groups"; + nodes = Terminal {}; + }; + + parseNexus = nexus: + if !(isAttrs nexus) + then + abort '' + Cerulean Nexus config must be provided as an attribute set, got "${typeOf nexus}" instead! + Ensure all the `nexus` declaration is an attribute set under your call to `cerulean.mkNexus`. + '' + else nib.parse.overrideStruct templateNexus nexus; + + mkNexus' = nexus': let + nexus = parseNexus nexus'; + in rec { + nixosConfigurations = mapNodes nexus.nodes ( nodeName: node: lib.nixosSystem { system = node.system; modules = let - core' = config.root + "/hosts/${nodeName}"; + core' = nexus.root + "/hosts/${nodeName}"; core = if pathExists core' then core' else core' + ".nix"; in - [core] ++ node.extraModules; + [core ../nixos-module] ++ node.extraModules; # nix passes these to every single module specialArgs = @@ -54,7 +90,7 @@ } ); - deploy.nodes = mapNodes config.nexus.nodes (nodeName: node: let + deploy.nodes = mapNodes nexus.nodes (nodeName: node: let inherit (node.deploy) activationTimeout @@ -107,6 +143,9 @@ checks = mapAttrs (system: deployLib: deployLib.deployChecks deploy) deploy-rs.lib; }; in { - mkNexus = outputs: - (mkNexus' outputs.cerulean) // (removeAttrs outputs ["cerulean"]); + mkNexus = outputs': let + autogen = mkNexus' <| getAttrOr "nexus" outputs' {}; + outputs = removeAttrs outputs' ["nexus"]; + in + autogen // outputs; # XXX: TODO: replace this with a deep merge } diff --git a/cerulean/flake-config/nodes.nix b/cerulean/nexus/nodes.nix similarity index 97% rename from cerulean/flake-config/nodes.nix rename to cerulean/nexus/nodes.nix index 5c560fa..781a1fa 100644 --- a/cerulean/flake-config/nodes.nix +++ b/cerulean/nexus/nodes.nix @@ -21,7 +21,10 @@ in rec { # abstract node instance that stores all default values templateNode = name: system: let - Terminal = nib.types.Terminal; + inherit + (nib.types) + Terminal + ; missing = msg: path: Terminal (abort '' From f83d87e0baa7fd809411c772a12da98aa32c4f9a Mon Sep 17 00:00:00 2001 From: _cry64 Date: Thu, 15 Jan 2026 20:10:31 +1000 Subject: [PATCH 155/338] begin development on nixos cerulean module --- cerulean/nixos-module/config.nix | 14 ++++++++++++++ cerulean/nixos-module/default.nix | 18 ++++++++++++++++++ cerulean/nixos-module/options.nix | 20 ++++++++++++++++++++ 3 files changed, 52 insertions(+) create mode 100644 cerulean/nixos-module/config.nix create mode 100644 cerulean/nixos-module/default.nix create mode 100644 cerulean/nixos-module/options.nix diff --git a/cerulean/nixos-module/config.nix b/cerulean/nixos-module/config.nix new file mode 100644 index 0000000..26d459c --- /dev/null +++ b/cerulean/nixos-module/config.nix @@ -0,0 +1,14 @@ +# Copyright 2026 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +{...}: {} diff --git a/cerulean/nixos-module/default.nix b/cerulean/nixos-module/default.nix new file mode 100644 index 0000000..cc92212 --- /dev/null +++ b/cerulean/nixos-module/default.nix @@ -0,0 +1,18 @@ +# Copyright 2026 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +{...} @ inputs: { + options = import ./options.nix inputs; + + config = import ./config.nix inputs; +} diff --git a/cerulean/nixos-module/options.nix b/cerulean/nixos-module/options.nix new file mode 100644 index 0000000..8fb6a46 --- /dev/null +++ b/cerulean/nixos-module/options.nix @@ -0,0 +1,20 @@ +# Copyright 2026 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +{lib, ...}: { + cerulean.nexus.node = { + group = lib.mkOption { + type = lib.types.enum; + }; + }; +} From d69d228e655e337d68f72a0276c2704632548414 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Thu, 15 Jan 2026 20:10:31 +1000 Subject: [PATCH 156/338] begin development on nixos cerulean module --- cerulean/nixos-module/config.nix | 14 ++++++++++++++ cerulean/nixos-module/default.nix | 18 ++++++++++++++++++ cerulean/nixos-module/options.nix | 20 ++++++++++++++++++++ 3 files changed, 52 insertions(+) create mode 100644 cerulean/nixos-module/config.nix create mode 100644 cerulean/nixos-module/default.nix create mode 100644 cerulean/nixos-module/options.nix diff --git a/cerulean/nixos-module/config.nix b/cerulean/nixos-module/config.nix new file mode 100644 index 0000000..26d459c --- /dev/null +++ b/cerulean/nixos-module/config.nix @@ -0,0 +1,14 @@ +# Copyright 2026 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +{...}: {} diff --git a/cerulean/nixos-module/default.nix b/cerulean/nixos-module/default.nix new file mode 100644 index 0000000..cc92212 --- /dev/null +++ b/cerulean/nixos-module/default.nix @@ -0,0 +1,18 @@ +# Copyright 2026 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +{...} @ inputs: { + options = import ./options.nix inputs; + + config = import ./config.nix inputs; +} diff --git a/cerulean/nixos-module/options.nix b/cerulean/nixos-module/options.nix new file mode 100644 index 0000000..8fb6a46 --- /dev/null +++ b/cerulean/nixos-module/options.nix @@ -0,0 +1,20 @@ +# Copyright 2026 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +{lib, ...}: { + cerulean.nexus.node = { + group = lib.mkOption { + type = lib.types.enum; + }; + }; +} From 284ae8c94e3ba37dc53aa7630955b8ec24940935 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Thu, 15 Jan 2026 20:10:41 +1000 Subject: [PATCH 157/338] switch to nexus (from flake-config) --- cerulean/default.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cerulean/default.nix b/cerulean/default.nix index 2755f4c..4241570 100644 --- a/cerulean/default.nix +++ b/cerulean/default.nix @@ -18,7 +18,7 @@ } @ inputs: mix.newMixture inputs (mixture: { includes.public = [ - ./flake-config + ./nexus ]; overlays = [ From 3551145604c94ff6bba456021b2640f36d3e22af Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Thu, 15 Jan 2026 20:10:41 +1000 Subject: [PATCH 158/338] switch to nexus (from flake-config) --- cerulean/default.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cerulean/default.nix b/cerulean/default.nix index 2755f4c..4241570 100644 --- a/cerulean/default.nix +++ b/cerulean/default.nix @@ -18,7 +18,7 @@ } @ inputs: mix.newMixture inputs (mixture: { includes.public = [ - ./flake-config + ./nexus ]; overlays = [ From 167ab269e5f0c10f1a67fb7deaae45a2112d8440 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Thu, 15 Jan 2026 20:10:46 +1000 Subject: [PATCH 159/338] progress flake.lock --- cerulean/nexus/nexus.nix | 2 +- flake.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index 433be8b..f22daa0 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -56,7 +56,7 @@ }; parseNexus = nexus: - if !(isAttrs nexus) + if ! isAttrs nexus then abort '' Cerulean Nexus config must be provided as an attribute set, got "${typeOf nexus}" instead! diff --git a/flake.lock b/flake.lock index 6e7f15d..acf6af9 100644 --- a/flake.lock +++ b/flake.lock @@ -43,7 +43,7 @@ ] }, "locked": { - "lastModified": 1768454486, + "lastModified": 1768458501, "narHash": "sha256-ktzDBTuKNx/B7rcdSttik3rkzVs/nhF2BO/8R6qx81A=", "path": "/home/me/agribit/nexus/mix", "type": "path" @@ -60,8 +60,8 @@ ] }, "locked": { - "lastModified": 1768454457, - "narHash": "sha256-9BTy1Plb49Bfu2Ki3sciIMtcWPrIrBTThkszB/TkQA8=", + "lastModified": 1768472081, + "narHash": "sha256-pgrZsMcnH5VbUEdwkjejCtRk5e4eyy7xf6rMIxvG8AQ=", "path": "/home/me/agribit/nexus/nib", "type": "path" }, From e58cd70a82851e52dd0d68f65750ecde3981256a Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Thu, 15 Jan 2026 20:10:46 +1000 Subject: [PATCH 160/338] progress flake.lock --- cerulean/nexus/nexus.nix | 2 +- flake.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index 433be8b..f22daa0 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -56,7 +56,7 @@ }; parseNexus = nexus: - if !(isAttrs nexus) + if ! isAttrs nexus then abort '' Cerulean Nexus config must be provided as an attribute set, got "${typeOf nexus}" instead! diff --git a/flake.lock b/flake.lock index 6e7f15d..acf6af9 100644 --- a/flake.lock +++ b/flake.lock @@ -43,7 +43,7 @@ ] }, "locked": { - "lastModified": 1768454486, + "lastModified": 1768458501, "narHash": "sha256-ktzDBTuKNx/B7rcdSttik3rkzVs/nhF2BO/8R6qx81A=", "path": "/home/me/agribit/nexus/mix", "type": "path" @@ -60,8 +60,8 @@ ] }, "locked": { - "lastModified": 1768454457, - "narHash": "sha256-9BTy1Plb49Bfu2Ki3sciIMtcWPrIrBTThkszB/TkQA8=", + "lastModified": 1768472081, + "narHash": "sha256-pgrZsMcnH5VbUEdwkjejCtRk5e4eyy7xf6rMIxvG8AQ=", "path": "/home/me/agribit/nexus/nib", "type": "path" }, From f4e91552369502d0803a62105b46523115c34850 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Fri, 16 Jan 2026 09:57:43 +1000 Subject: [PATCH 161/338] progress flake.lock --- flake.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/flake.lock b/flake.lock index acf6af9..9305adb 100644 --- a/flake.lock +++ b/flake.lock @@ -43,8 +43,8 @@ ] }, "locked": { - "lastModified": 1768458501, - "narHash": "sha256-ktzDBTuKNx/B7rcdSttik3rkzVs/nhF2BO/8R6qx81A=", + "lastModified": 1768521445, + "narHash": "sha256-gluJUJKtd0Gch5Li6TNw9xYU1iFYSVDd8++qYwdajlY=", "path": "/home/me/agribit/nexus/mix", "type": "path" }, @@ -60,8 +60,8 @@ ] }, "locked": { - "lastModified": 1768472081, - "narHash": "sha256-pgrZsMcnH5VbUEdwkjejCtRk5e4eyy7xf6rMIxvG8AQ=", + "lastModified": 1768520505, + "narHash": "sha256-W5O2JzaMONE8NrDRL1XpQn0XEgZkfrozTs6Wv9yMJAk=", "path": "/home/me/agribit/nexus/nib", "type": "path" }, From 38587a327d5840409129546b802c7c22dd3f314f Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Fri, 16 Jan 2026 09:57:43 +1000 Subject: [PATCH 162/338] progress flake.lock --- flake.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/flake.lock b/flake.lock index acf6af9..9305adb 100644 --- a/flake.lock +++ b/flake.lock @@ -43,8 +43,8 @@ ] }, "locked": { - "lastModified": 1768458501, - "narHash": "sha256-ktzDBTuKNx/B7rcdSttik3rkzVs/nhF2BO/8R6qx81A=", + "lastModified": 1768521445, + "narHash": "sha256-gluJUJKtd0Gch5Li6TNw9xYU1iFYSVDd8++qYwdajlY=", "path": "/home/me/agribit/nexus/mix", "type": "path" }, @@ -60,8 +60,8 @@ ] }, "locked": { - "lastModified": 1768472081, - "narHash": "sha256-pgrZsMcnH5VbUEdwkjejCtRk5e4eyy7xf6rMIxvG8AQ=", + "lastModified": 1768520505, + "narHash": "sha256-W5O2JzaMONE8NrDRL1XpQn0XEgZkfrozTs6Wv9yMJAk=", "path": "/home/me/agribit/nexus/nib", "type": "path" }, From 52c04697f3c4a35846e3e5026926b8b7451641db Mon Sep 17 00:00:00 2001 From: _cry64 Date: Fri, 16 Jan 2026 11:10:53 +1000 Subject: [PATCH 163/338] return to upstream --- flake.lock | 30 ++++++++++++++++++------------ flake.nix | 8 ++++---- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/flake.lock b/flake.lock index 9305adb..e9a0074 100644 --- a/flake.lock +++ b/flake.lock @@ -43,14 +43,17 @@ ] }, "locked": { - "lastModified": 1768521445, - "narHash": "sha256-gluJUJKtd0Gch5Li6TNw9xYU1iFYSVDd8++qYwdajlY=", - "path": "/home/me/agribit/nexus/mix", - "type": "path" + "lastModified": 1768525804, + "narHash": "sha256-jlpNb7Utqfdq2HESAB1mtddWHOsxKlTjPiLFRLd35r8=", + "owner": "emilelcb", + "repo": "mix", + "rev": "617d8915a6518a3d4e375b87c50ae34d9daee6c6", + "type": "github" }, "original": { - "path": "/home/me/agribit/nexus/mix", - "type": "path" + "owner": "emilelcb", + "repo": "mix", + "type": "github" } }, "nib": { @@ -60,14 +63,17 @@ ] }, "locked": { - "lastModified": 1768520505, - "narHash": "sha256-W5O2JzaMONE8NrDRL1XpQn0XEgZkfrozTs6Wv9yMJAk=", - "path": "/home/me/agribit/nexus/nib", - "type": "path" + "lastModified": 1768472076, + "narHash": "sha256-bdVRCDy6oJx/CZiyxkke783FgtBW//wDuOAITUsQcNc=", + "owner": "emilelcb", + "repo": "nib", + "rev": "42ac66dfc180a13af1cc8850397db66ec5556991", + "type": "github" }, "original": { - "path": "/home/me/agribit/nexus/nib", - "type": "path" + "owner": "emilelcb", + "repo": "nib", + "type": "github" } }, "nixpkgs": { diff --git a/flake.nix b/flake.nix index fc9e7ea..b31ea1d 100644 --- a/flake.nix +++ b/flake.nix @@ -21,14 +21,14 @@ nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixos-unstable"; nib = { - # url = "github:emilelcb/nib"; - url = "/home/me/agribit/nexus/nib"; + url = "github:emilelcb/nib"; + # url = "/home/me/agribit/nexus/nib"; inputs.systems.follows = "systems"; }; mix = { - # url = "github:emilelcb/mix"; - url = "/home/me/agribit/nexus/mix"; + url = "github:emilelcb/mix"; + # url = "/home/me/agribit/nexus/mix"; inputs.nib.follows = "nib"; }; From 839b80f9404b985cfd185b95b41853b7b9db658b Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Fri, 16 Jan 2026 11:10:53 +1000 Subject: [PATCH 164/338] return to upstream --- flake.lock | 30 ++++++++++++++++++------------ flake.nix | 8 ++++---- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/flake.lock b/flake.lock index 9305adb..e9a0074 100644 --- a/flake.lock +++ b/flake.lock @@ -43,14 +43,17 @@ ] }, "locked": { - "lastModified": 1768521445, - "narHash": "sha256-gluJUJKtd0Gch5Li6TNw9xYU1iFYSVDd8++qYwdajlY=", - "path": "/home/me/agribit/nexus/mix", - "type": "path" + "lastModified": 1768525804, + "narHash": "sha256-jlpNb7Utqfdq2HESAB1mtddWHOsxKlTjPiLFRLd35r8=", + "owner": "emilelcb", + "repo": "mix", + "rev": "617d8915a6518a3d4e375b87c50ae34d9daee6c6", + "type": "github" }, "original": { - "path": "/home/me/agribit/nexus/mix", - "type": "path" + "owner": "emilelcb", + "repo": "mix", + "type": "github" } }, "nib": { @@ -60,14 +63,17 @@ ] }, "locked": { - "lastModified": 1768520505, - "narHash": "sha256-W5O2JzaMONE8NrDRL1XpQn0XEgZkfrozTs6Wv9yMJAk=", - "path": "/home/me/agribit/nexus/nib", - "type": "path" + "lastModified": 1768472076, + "narHash": "sha256-bdVRCDy6oJx/CZiyxkke783FgtBW//wDuOAITUsQcNc=", + "owner": "emilelcb", + "repo": "nib", + "rev": "42ac66dfc180a13af1cc8850397db66ec5556991", + "type": "github" }, "original": { - "path": "/home/me/agribit/nexus/nib", - "type": "path" + "owner": "emilelcb", + "repo": "nib", + "type": "github" } }, "nixpkgs": { diff --git a/flake.nix b/flake.nix index fc9e7ea..b31ea1d 100644 --- a/flake.nix +++ b/flake.nix @@ -21,14 +21,14 @@ nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixos-unstable"; nib = { - # url = "github:emilelcb/nib"; - url = "/home/me/agribit/nexus/nib"; + url = "github:emilelcb/nib"; + # url = "/home/me/agribit/nexus/nib"; inputs.systems.follows = "systems"; }; mix = { - # url = "github:emilelcb/mix"; - url = "/home/me/agribit/nexus/mix"; + url = "github:emilelcb/mix"; + # url = "/home/me/agribit/nexus/mix"; inputs.nib.follows = "nib"; }; From 3fd30e1506565369013ad9ca0762945f65b2cc9d Mon Sep 17 00:00:00 2001 From: _cry64 Date: Tue, 20 Jan 2026 12:24:41 +1000 Subject: [PATCH 165/338] rename module core -> host --- cerulean/nexus/nexus.nix | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index f22daa0..5342080 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -72,13 +72,13 @@ lib.nixosSystem { system = node.system; modules = let - core' = nexus.root + "/hosts/${nodeName}"; - core = - if pathExists core' - then core' - else core' + ".nix"; + host' = nexus.root + "/hosts/${nodeName}"; + host = + if pathExists host' + then host' + else host' + ".nix"; in - [core ../nixos-module] ++ node.extraModules; + [../nixos-module host] ++ node.extraModules; # nix passes these to every single module specialArgs = From 0c37a6d19cbe5a8ebc8250f3616960dc9f9cbadd Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Tue, 20 Jan 2026 12:24:41 +1000 Subject: [PATCH 166/338] rename module core -> host --- cerulean/nexus/nexus.nix | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index f22daa0..5342080 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -72,13 +72,13 @@ lib.nixosSystem { system = node.system; modules = let - core' = nexus.root + "/hosts/${nodeName}"; - core = - if pathExists core' - then core' - else core' + ".nix"; + host' = nexus.root + "/hosts/${nodeName}"; + host = + if pathExists host' + then host' + else host' + ".nix"; in - [core ../nixos-module] ++ node.extraModules; + [../nixos-module host] ++ node.extraModules; # nix passes these to every single module specialArgs = From 7b3e7a24735657b5fe087670902abc103251b90f Mon Sep 17 00:00:00 2001 From: _cry64 Date: Tue, 20 Jan 2026 14:12:52 +1000 Subject: [PATCH 167/338] rename subcmds/new/key -> subcmds/new/cache-key --- ceru/subcmds/new/{key => cache-key} | 2 +- ceru/subcmds/new/default.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename ceru/subcmds/new/{key => cache-key} (98%) diff --git a/ceru/subcmds/new/key b/ceru/subcmds/new/cache-key similarity index 98% rename from ceru/subcmds/new/key rename to ceru/subcmds/new/cache-key index 599762e..9431a0b 100755 --- a/ceru/subcmds/new/key +++ b/ceru/subcmds/new/cache-key @@ -16,7 +16,7 @@ set -euo pipefail USAGE="${BOLD}${UNDERLINE}${RED}Usage${RESET} - ${BOLD}${GREEN}$THIS new key [option...]${RESET} + ${BOLD}${GREEN}$THIS new cache-key [option...]${RESET} ${BOLD}${UNDERLINE}${RED}Options${RESET} ${BOLD}${MAGENTA}-h, --help${RESET} Show this message (^_^) diff --git a/ceru/subcmds/new/default.sh b/ceru/subcmds/new/default.sh index ed0d15a..aaaef81 100755 --- a/ceru/subcmds/new/default.sh +++ b/ceru/subcmds/new/default.sh @@ -22,7 +22,7 @@ ${BOLD}${UNDERLINE}${RED}Options${RESET} ${BOLD}${MAGENTA}-h, --help${RESET} Show this message (^_^) ${BOLD}${UNDERLINE}${RED}Subcommands${RESET} - ${BOLD}${CYAN}key${RESET} Generate a new binary-cache signing keypair" + ${BOLD}${CYAN}cache-key${RESET} Generate a new binary-cache signing keypair # parse all args SUBCMD=false # where a subcommand was specified From 6cb8d3fb0017f5ff9b8c2451993b260a7b410143 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Tue, 20 Jan 2026 14:12:52 +1000 Subject: [PATCH 168/338] rename subcmds/new/key -> subcmds/new/cache-key --- ceru/subcmds/new/{key => cache-key} | 2 +- ceru/subcmds/new/default.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename ceru/subcmds/new/{key => cache-key} (98%) diff --git a/ceru/subcmds/new/key b/ceru/subcmds/new/cache-key similarity index 98% rename from ceru/subcmds/new/key rename to ceru/subcmds/new/cache-key index 599762e..9431a0b 100755 --- a/ceru/subcmds/new/key +++ b/ceru/subcmds/new/cache-key @@ -16,7 +16,7 @@ set -euo pipefail USAGE="${BOLD}${UNDERLINE}${RED}Usage${RESET} - ${BOLD}${GREEN}$THIS new key [option...]${RESET} + ${BOLD}${GREEN}$THIS new cache-key [option...]${RESET} ${BOLD}${UNDERLINE}${RED}Options${RESET} ${BOLD}${MAGENTA}-h, --help${RESET} Show this message (^_^) diff --git a/ceru/subcmds/new/default.sh b/ceru/subcmds/new/default.sh index ed0d15a..aaaef81 100755 --- a/ceru/subcmds/new/default.sh +++ b/ceru/subcmds/new/default.sh @@ -22,7 +22,7 @@ ${BOLD}${UNDERLINE}${RED}Options${RESET} ${BOLD}${MAGENTA}-h, --help${RESET} Show this message (^_^) ${BOLD}${UNDERLINE}${RED}Subcommands${RESET} - ${BOLD}${CYAN}key${RESET} Generate a new binary-cache signing keypair" + ${BOLD}${CYAN}cache-key${RESET} Generate a new binary-cache signing keypair # parse all args SUBCMD=false # where a subcommand was specified From b108aa394213b1921eadbc12dce99d3ca165f01e Mon Sep 17 00:00:00 2001 From: _cry64 Date: Tue, 20 Jan 2026 14:13:06 +1000 Subject: [PATCH 169/338] add subcmds/new/ssh-key --- ceru/subcmds/new/default.sh | 1 + ceru/subcmds/new/ssh-key | 121 ++++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100755 ceru/subcmds/new/ssh-key diff --git a/ceru/subcmds/new/default.sh b/ceru/subcmds/new/default.sh index aaaef81..b5f7b46 100755 --- a/ceru/subcmds/new/default.sh +++ b/ceru/subcmds/new/default.sh @@ -23,6 +23,7 @@ ${BOLD}${UNDERLINE}${RED}Options${RESET} ${BOLD}${UNDERLINE}${RED}Subcommands${RESET} ${BOLD}${CYAN}cache-key${RESET} Generate a new binary-cache signing keypair + ${BOLD}${CYAN}ssh-key${RESET} Generate a new SSH RSA-4096 keypair" # parse all args SUBCMD=false # where a subcommand was specified diff --git a/ceru/subcmds/new/ssh-key b/ceru/subcmds/new/ssh-key new file mode 100755 index 0000000..abf0c1d --- /dev/null +++ b/ceru/subcmds/new/ssh-key @@ -0,0 +1,121 @@ +#!/usr/bin/env bash +# Copyright 2025 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -euo pipefail + +USAGE="${BOLD}${UNDERLINE}${RED}Usage${RESET} + ${BOLD}${GREEN}$THIS new ssh-key [option...]${RESET} + +${BOLD}${UNDERLINE}${RED}Description${RESET} + Generates a new SSH keypair with secure defaults. + For more advanced usage run the \`ssh-keygen\` utility directly. + +${BOLD}${UNDERLINE}${RED}Options${RESET} + ${BOLD}${MAGENTA}-h, --help${RESET} Show this message (^_^) + ${BOLD}${MAGENTA}-o, --out${RESET} Private key file name to write to (the public key is named identically but ends with ${BOLD}${MAGENTA}.pub${RESET}) + ${BOLD}${MAGENTA}-c, --comment${RESET} A comment or email address to write on the key + ${BOLD}${MAGENTA}-t, --type${RESET} The cryptographic algorithm to use: ${BOLD}${MAGENTA}ed25519${RESET} or ${BOLD}${MAGENTA}rsa${RESET} ${BOLD}${CYAN}(default: rsa)${RESET} + ${BOLD}${MAGENTA}-r, --rounds${RESET} The number of key derivation function rounds to apply ${BOLD}${CYAN}(default: 100)${RESET} + ${BOLD}${MAGENTA}-N, --nopasswd${RESET} Do not encrypt the private key with a password + ${BOLD}${MAGENTA}-H, --hardware-key${RESET} Enable the use of a secure hardware key peripheral device (ie YubiKey)" + +# ==== Argument Values ==== +TYPE='rsa' +ROUNDS='100' +COMMENT='' +OUT='' +NOPASSWD=false +HWKEY=false +# ==== Argument Values ==== + +# parse all args +while [[ $# -gt 0 ]]; do + ARG="$1" + case "$ARG" in + -h|--help) + throw-usage 0 + ;; + -o|--out) + shift + # XXX: NOTE: do I need to safe shift (shift || true) since -e is set? + OUT="$1"; shift + ;; + -c|--comment) + shift + # XXX: NOTE: do I need to safe shift (shift || true) since -e is set? + COMMENT="$1"; shift + ;; + -t|--type) + shift + TYPE="$1"; shift + ;; + -r|--rounds) + shift + # XXX: NOTE: do I need to safe shift (shift || true) since -e is set? + ROUNDS="$1"; shift + ;; + -N|--nopasswd) + shift + NOPASSWD=true + ;; + -H|--hardware-key) + shift + HWKEY=true + ;; + -*) + throw-badflag 1 "$ARG" + ;; + *) + throw-badarg 1 "$ARG" + ;; + esac +done; unset -v ARG + +EXTRA='' + +case "$TYPE" in + ed25519) + ;; + rsa) + EXTRA="$EXTRA -b 4096" + ;; + *) + throw-badvalue 1 "$TYPE" '-t|--type' + ;; +esac + +if [[ -n "$COMMENT" ]]; then + EXTRA="$EXTRA -C '$COMMENT'" +fi + +if [[ -n "$OUT" ]]; then + EXTRA="$EXTRA -f '$OUT'" +fi + +if [[ "$NOPASSWD" == true ]]; then + EXTRA="$EXTRA -N ''" +fi + +if [[ "$HWKEY" == true ]]; then + TYPE="$TYPE-sk" +fi + +# permit error during key generation +set +e +ssh-keygen -t "$TYPE" -a "$ROUNDS" $EXTRA + +# reset state +set -e +unset TYPE ROUNDS COMMENT OUT NOPASSWD EXTRA From 368151fdbfa486a1176ce8879c246b159834c665 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Tue, 20 Jan 2026 14:13:06 +1000 Subject: [PATCH 170/338] add subcmds/new/ssh-key --- ceru/subcmds/new/default.sh | 1 + ceru/subcmds/new/ssh-key | 121 ++++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100755 ceru/subcmds/new/ssh-key diff --git a/ceru/subcmds/new/default.sh b/ceru/subcmds/new/default.sh index aaaef81..b5f7b46 100755 --- a/ceru/subcmds/new/default.sh +++ b/ceru/subcmds/new/default.sh @@ -23,6 +23,7 @@ ${BOLD}${UNDERLINE}${RED}Options${RESET} ${BOLD}${UNDERLINE}${RED}Subcommands${RESET} ${BOLD}${CYAN}cache-key${RESET} Generate a new binary-cache signing keypair + ${BOLD}${CYAN}ssh-key${RESET} Generate a new SSH RSA-4096 keypair" # parse all args SUBCMD=false # where a subcommand was specified diff --git a/ceru/subcmds/new/ssh-key b/ceru/subcmds/new/ssh-key new file mode 100755 index 0000000..abf0c1d --- /dev/null +++ b/ceru/subcmds/new/ssh-key @@ -0,0 +1,121 @@ +#!/usr/bin/env bash +# Copyright 2025 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -euo pipefail + +USAGE="${BOLD}${UNDERLINE}${RED}Usage${RESET} + ${BOLD}${GREEN}$THIS new ssh-key [option...]${RESET} + +${BOLD}${UNDERLINE}${RED}Description${RESET} + Generates a new SSH keypair with secure defaults. + For more advanced usage run the \`ssh-keygen\` utility directly. + +${BOLD}${UNDERLINE}${RED}Options${RESET} + ${BOLD}${MAGENTA}-h, --help${RESET} Show this message (^_^) + ${BOLD}${MAGENTA}-o, --out${RESET} Private key file name to write to (the public key is named identically but ends with ${BOLD}${MAGENTA}.pub${RESET}) + ${BOLD}${MAGENTA}-c, --comment${RESET} A comment or email address to write on the key + ${BOLD}${MAGENTA}-t, --type${RESET} The cryptographic algorithm to use: ${BOLD}${MAGENTA}ed25519${RESET} or ${BOLD}${MAGENTA}rsa${RESET} ${BOLD}${CYAN}(default: rsa)${RESET} + ${BOLD}${MAGENTA}-r, --rounds${RESET} The number of key derivation function rounds to apply ${BOLD}${CYAN}(default: 100)${RESET} + ${BOLD}${MAGENTA}-N, --nopasswd${RESET} Do not encrypt the private key with a password + ${BOLD}${MAGENTA}-H, --hardware-key${RESET} Enable the use of a secure hardware key peripheral device (ie YubiKey)" + +# ==== Argument Values ==== +TYPE='rsa' +ROUNDS='100' +COMMENT='' +OUT='' +NOPASSWD=false +HWKEY=false +# ==== Argument Values ==== + +# parse all args +while [[ $# -gt 0 ]]; do + ARG="$1" + case "$ARG" in + -h|--help) + throw-usage 0 + ;; + -o|--out) + shift + # XXX: NOTE: do I need to safe shift (shift || true) since -e is set? + OUT="$1"; shift + ;; + -c|--comment) + shift + # XXX: NOTE: do I need to safe shift (shift || true) since -e is set? + COMMENT="$1"; shift + ;; + -t|--type) + shift + TYPE="$1"; shift + ;; + -r|--rounds) + shift + # XXX: NOTE: do I need to safe shift (shift || true) since -e is set? + ROUNDS="$1"; shift + ;; + -N|--nopasswd) + shift + NOPASSWD=true + ;; + -H|--hardware-key) + shift + HWKEY=true + ;; + -*) + throw-badflag 1 "$ARG" + ;; + *) + throw-badarg 1 "$ARG" + ;; + esac +done; unset -v ARG + +EXTRA='' + +case "$TYPE" in + ed25519) + ;; + rsa) + EXTRA="$EXTRA -b 4096" + ;; + *) + throw-badvalue 1 "$TYPE" '-t|--type' + ;; +esac + +if [[ -n "$COMMENT" ]]; then + EXTRA="$EXTRA -C '$COMMENT'" +fi + +if [[ -n "$OUT" ]]; then + EXTRA="$EXTRA -f '$OUT'" +fi + +if [[ "$NOPASSWD" == true ]]; then + EXTRA="$EXTRA -N ''" +fi + +if [[ "$HWKEY" == true ]]; then + TYPE="$TYPE-sk" +fi + +# permit error during key generation +set +e +ssh-keygen -t "$TYPE" -a "$ROUNDS" $EXTRA + +# reset state +set -e +unset TYPE ROUNDS COMMENT OUT NOPASSWD EXTRA From a6ed57d6c554c81169d89ad11464b1cff2e5ff3a Mon Sep 17 00:00:00 2001 From: _cry64 Date: Tue, 20 Jan 2026 14:30:32 +1000 Subject: [PATCH 171/338] fix ssh-key subcmd implementation --- ceru/subcmds/new/ssh-key | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/ceru/subcmds/new/ssh-key b/ceru/subcmds/new/ssh-key index abf0c1d..e4ce324 100755 --- a/ceru/subcmds/new/ssh-key +++ b/ceru/subcmds/new/ssh-key @@ -100,8 +100,9 @@ if [[ -n "$COMMENT" ]]; then EXTRA="$EXTRA -C '$COMMENT'" fi +# BUG: WARNING: $OUT permits arbitrary command injection if [[ -n "$OUT" ]]; then - EXTRA="$EXTRA -f '$OUT'" + EXTRA="$EXTRA -f $OUT" fi if [[ "$NOPASSWD" == true ]]; then @@ -109,13 +110,18 @@ if [[ "$NOPASSWD" == true ]]; then fi if [[ "$HWKEY" == true ]]; then + if [[ "$TYPE" == "rsa" ]]; then + echo -e "${BOLD}${RED}-H|--hardware-key${RESET} flag is not valid for ${BOLD}${MAGENTA}rsa${RESET} keys ${BOLD}${CYAN}(use ed25519 instead)${RESET}" + exit 1 + fi TYPE="$TYPE-sk" fi # permit error during key generation set +e -ssh-keygen -t "$TYPE" -a "$ROUNDS" $EXTRA +echo -e "${BOLD}${GREEN}[+] ssh-keygen -t $TYPE -a '$ROUNDS' $EXTRA${RESET}" +ssh-keygen -t $TYPE -a "$ROUNDS" $EXTRA # reset state set -e -unset TYPE ROUNDS COMMENT OUT NOPASSWD EXTRA +unset TYPE ROUNDS COMMENT OUT NOPASSWD HWKEY EXTRA From 2d7f99424b4f3a9a85da93d2ca6fc0f2750eedff Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Tue, 20 Jan 2026 14:30:32 +1000 Subject: [PATCH 172/338] fix ssh-key subcmd implementation --- ceru/subcmds/new/ssh-key | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/ceru/subcmds/new/ssh-key b/ceru/subcmds/new/ssh-key index abf0c1d..e4ce324 100755 --- a/ceru/subcmds/new/ssh-key +++ b/ceru/subcmds/new/ssh-key @@ -100,8 +100,9 @@ if [[ -n "$COMMENT" ]]; then EXTRA="$EXTRA -C '$COMMENT'" fi +# BUG: WARNING: $OUT permits arbitrary command injection if [[ -n "$OUT" ]]; then - EXTRA="$EXTRA -f '$OUT'" + EXTRA="$EXTRA -f $OUT" fi if [[ "$NOPASSWD" == true ]]; then @@ -109,13 +110,18 @@ if [[ "$NOPASSWD" == true ]]; then fi if [[ "$HWKEY" == true ]]; then + if [[ "$TYPE" == "rsa" ]]; then + echo -e "${BOLD}${RED}-H|--hardware-key${RESET} flag is not valid for ${BOLD}${MAGENTA}rsa${RESET} keys ${BOLD}${CYAN}(use ed25519 instead)${RESET}" + exit 1 + fi TYPE="$TYPE-sk" fi # permit error during key generation set +e -ssh-keygen -t "$TYPE" -a "$ROUNDS" $EXTRA +echo -e "${BOLD}${GREEN}[+] ssh-keygen -t $TYPE -a '$ROUNDS' $EXTRA${RESET}" +ssh-keygen -t $TYPE -a "$ROUNDS" $EXTRA # reset state set -e -unset TYPE ROUNDS COMMENT OUT NOPASSWD EXTRA +unset TYPE ROUNDS COMMENT OUT NOPASSWD HWKEY EXTRA From a7bc2b6d74a9fbd108b733b4f077675349a26eae Mon Sep 17 00:00:00 2001 From: _cry64 Date: Tue, 20 Jan 2026 14:38:46 +1000 Subject: [PATCH 173/338] ensure default umasks are applied post-keygen --- ceru/subcmds/new/ssh-key | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/ceru/subcmds/new/ssh-key b/ceru/subcmds/new/ssh-key index e4ce324..1ee9474 100755 --- a/ceru/subcmds/new/ssh-key +++ b/ceru/subcmds/new/ssh-key @@ -96,18 +96,6 @@ case "$TYPE" in ;; esac -if [[ -n "$COMMENT" ]]; then - EXTRA="$EXTRA -C '$COMMENT'" -fi - -# BUG: WARNING: $OUT permits arbitrary command injection -if [[ -n "$OUT" ]]; then - EXTRA="$EXTRA -f $OUT" -fi - -if [[ "$NOPASSWD" == true ]]; then - EXTRA="$EXTRA -N ''" -fi if [[ "$HWKEY" == true ]]; then if [[ "$TYPE" == "rsa" ]]; then @@ -117,10 +105,27 @@ if [[ "$HWKEY" == true ]]; then TYPE="$TYPE-sk" fi +if [[ -n "$COMMENT" ]]; then + EXTRA="$EXTRA -C '$COMMENT'" +fi + +# BUG: WARNING: $OUT permits arbitrary command injection +if [[ -n "$OUT" ]]; then + EXTRA="$EXTRA -f $OUT" +else + # fallback to ssh-keygen's default file (for chmod later) + OUT="~/.ssh/id_$TYPE" +fi + +if [[ "$NOPASSWD" == true ]]; then + EXTRA="$EXTRA -N ''" +fi # permit error during key generation set +e echo -e "${BOLD}${GREEN}[+] ssh-keygen -t $TYPE -a '$ROUNDS' $EXTRA${RESET}" ssh-keygen -t $TYPE -a "$ROUNDS" $EXTRA +chmod 600 $OUT +chmod 644 $OUT.pub # reset state set -e From 9b45d98c48197e9059ffbce4e0634565901df67d Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Tue, 20 Jan 2026 14:38:46 +1000 Subject: [PATCH 174/338] ensure default umasks are applied post-keygen --- ceru/subcmds/new/ssh-key | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/ceru/subcmds/new/ssh-key b/ceru/subcmds/new/ssh-key index e4ce324..1ee9474 100755 --- a/ceru/subcmds/new/ssh-key +++ b/ceru/subcmds/new/ssh-key @@ -96,18 +96,6 @@ case "$TYPE" in ;; esac -if [[ -n "$COMMENT" ]]; then - EXTRA="$EXTRA -C '$COMMENT'" -fi - -# BUG: WARNING: $OUT permits arbitrary command injection -if [[ -n "$OUT" ]]; then - EXTRA="$EXTRA -f $OUT" -fi - -if [[ "$NOPASSWD" == true ]]; then - EXTRA="$EXTRA -N ''" -fi if [[ "$HWKEY" == true ]]; then if [[ "$TYPE" == "rsa" ]]; then @@ -117,10 +105,27 @@ if [[ "$HWKEY" == true ]]; then TYPE="$TYPE-sk" fi +if [[ -n "$COMMENT" ]]; then + EXTRA="$EXTRA -C '$COMMENT'" +fi + +# BUG: WARNING: $OUT permits arbitrary command injection +if [[ -n "$OUT" ]]; then + EXTRA="$EXTRA -f $OUT" +else + # fallback to ssh-keygen's default file (for chmod later) + OUT="~/.ssh/id_$TYPE" +fi + +if [[ "$NOPASSWD" == true ]]; then + EXTRA="$EXTRA -N ''" +fi # permit error during key generation set +e echo -e "${BOLD}${GREEN}[+] ssh-keygen -t $TYPE -a '$ROUNDS' $EXTRA${RESET}" ssh-keygen -t $TYPE -a "$ROUNDS" $EXTRA +chmod 600 $OUT +chmod 644 $OUT.pub # reset state set -e From c2bd334156ed3b6bfd4213e819afbb821b962ff2 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Tue, 20 Jan 2026 14:48:30 +1000 Subject: [PATCH 175/338] add subcmds/new/wg-key --- ceru/subcmds/new/default.sh | 3 +- ceru/subcmds/new/wg-key | 97 +++++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 1 deletion(-) create mode 100755 ceru/subcmds/new/wg-key diff --git a/ceru/subcmds/new/default.sh b/ceru/subcmds/new/default.sh index b5f7b46..8731446 100755 --- a/ceru/subcmds/new/default.sh +++ b/ceru/subcmds/new/default.sh @@ -23,7 +23,8 @@ ${BOLD}${UNDERLINE}${RED}Options${RESET} ${BOLD}${UNDERLINE}${RED}Subcommands${RESET} ${BOLD}${CYAN}cache-key${RESET} Generate a new binary-cache signing keypair - ${BOLD}${CYAN}ssh-key${RESET} Generate a new SSH RSA-4096 keypair" + ${BOLD}${CYAN}ssh-key${RESET} Generate a new SSH RSA-4096 keypair + ${BOLD}${CYAN}wg-key${RESET} Generate a new Wireguard keypair" # parse all args SUBCMD=false # where a subcommand was specified diff --git a/ceru/subcmds/new/wg-key b/ceru/subcmds/new/wg-key new file mode 100755 index 0000000..3a008a7 --- /dev/null +++ b/ceru/subcmds/new/wg-key @@ -0,0 +1,97 @@ +#!/usr/bin/env bash +# Copyright 2025 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -euo pipefail + +USAGE="${BOLD}${UNDERLINE}${RED}Usage${RESET} + ${BOLD}${GREEN}$THIS new cache-key [option...]${RESET} + +${BOLD}${UNDERLINE}${RED}Options${RESET} + ${BOLD}${MAGENTA}-h, --help${RESET} Show this message (^_^) + ${BOLD}${MAGENTA}-o, --out${RESET} Private key file name to write to (the public key is named identically but ends with ${BOLD}${MAGENTA}.pub${RESET}) + ${BOLD}${MAGENTA}-j, --json${RESET} Output in JSON format + ${BOLD}${MAGENTA}-f, --force${RESET} Ignores all warnings!!" + +# ==== Argument Values ==== +OUT='' +JSON=false +FORCE=false +# ==== Argument Values ==== + +# parse all args +while [[ $# -gt 0 ]]; do + ARG="$1" + case "$ARG" in + -h|--help) + throw-usage 0 + ;; + -o|--out) + shift + # XXX: NOTE: do I need to safe shift (shift || true) since -e is set? + OUT="$1"; shift + ;; + -j|--json) + shift + JSON=true + ;; + -f|--force) + shift + FORCE=true + ;; + -*) + throw-badflag 1 "$ARG" + ;; + *) + throw-badarg 1 "$ARG" + ;; + esac +done; unset -v ARG + + + +# generate our keypair +PRIV_KEY=$(wg genkey) +PUB_KEY=$(wg pubkey <<<"$PRIV_KEY") + +# NOTE: same logic as `subcmds/new/cache-key` +# result defaults to unset (only stays unset if we intend on writing to a file) +RESULT="" +# JSON formatting +if [[ "$JSON" = true ]]; then + RESULT="{ + \"privateKey\": \"${PRIV_KEY}\" + \"publicKey\": \"${PUB_KEY}\" +}" + if [[ -n "$OUT" ]]; then + # confirm the user understands files will be overwritten + [[ "$FORCE" = true ]] || confirm-file-overwrite "$OUT" || exit 0 + echo -e "$RESULT" > "$OUT" + else + echo -e "$RESULT" + fi +# standard formatting (stdout) +elif [[ -z "$OUT" ]]; then + echo -e "${BOLD}${UNDERLINE}${RED}Private Key${RESET} + ${BOLD}${GREEN}${PRIV_KEY}${RESET} +${BOLD}${UNDERLINE}${RED}Public Key${RESET} + ${BOLD}${GREEN}${PUB_KEY}${RESET}" +# standard formatting (files) +else + # confirm the user understands files will be overwritten + [[ "$FORCE" = true ]] || confirm-file-overwrite "$OUT" "$OUT.pub" || exit 0 + echo "$PRIV_KEY" > "$OUT" + echo "$PUB_KEY" > "$OUT.pub" +fi; +unset -v OUT PRIV_KEY PUB_KEY RESULT From 8386599de3d771c2e34c0d6b7b3b0c72691c6f28 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Tue, 20 Jan 2026 14:48:30 +1000 Subject: [PATCH 176/338] add subcmds/new/wg-key --- ceru/subcmds/new/default.sh | 3 +- ceru/subcmds/new/wg-key | 97 +++++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 1 deletion(-) create mode 100755 ceru/subcmds/new/wg-key diff --git a/ceru/subcmds/new/default.sh b/ceru/subcmds/new/default.sh index b5f7b46..8731446 100755 --- a/ceru/subcmds/new/default.sh +++ b/ceru/subcmds/new/default.sh @@ -23,7 +23,8 @@ ${BOLD}${UNDERLINE}${RED}Options${RESET} ${BOLD}${UNDERLINE}${RED}Subcommands${RESET} ${BOLD}${CYAN}cache-key${RESET} Generate a new binary-cache signing keypair - ${BOLD}${CYAN}ssh-key${RESET} Generate a new SSH RSA-4096 keypair" + ${BOLD}${CYAN}ssh-key${RESET} Generate a new SSH RSA-4096 keypair + ${BOLD}${CYAN}wg-key${RESET} Generate a new Wireguard keypair" # parse all args SUBCMD=false # where a subcommand was specified diff --git a/ceru/subcmds/new/wg-key b/ceru/subcmds/new/wg-key new file mode 100755 index 0000000..3a008a7 --- /dev/null +++ b/ceru/subcmds/new/wg-key @@ -0,0 +1,97 @@ +#!/usr/bin/env bash +# Copyright 2025 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -euo pipefail + +USAGE="${BOLD}${UNDERLINE}${RED}Usage${RESET} + ${BOLD}${GREEN}$THIS new cache-key [option...]${RESET} + +${BOLD}${UNDERLINE}${RED}Options${RESET} + ${BOLD}${MAGENTA}-h, --help${RESET} Show this message (^_^) + ${BOLD}${MAGENTA}-o, --out${RESET} Private key file name to write to (the public key is named identically but ends with ${BOLD}${MAGENTA}.pub${RESET}) + ${BOLD}${MAGENTA}-j, --json${RESET} Output in JSON format + ${BOLD}${MAGENTA}-f, --force${RESET} Ignores all warnings!!" + +# ==== Argument Values ==== +OUT='' +JSON=false +FORCE=false +# ==== Argument Values ==== + +# parse all args +while [[ $# -gt 0 ]]; do + ARG="$1" + case "$ARG" in + -h|--help) + throw-usage 0 + ;; + -o|--out) + shift + # XXX: NOTE: do I need to safe shift (shift || true) since -e is set? + OUT="$1"; shift + ;; + -j|--json) + shift + JSON=true + ;; + -f|--force) + shift + FORCE=true + ;; + -*) + throw-badflag 1 "$ARG" + ;; + *) + throw-badarg 1 "$ARG" + ;; + esac +done; unset -v ARG + + + +# generate our keypair +PRIV_KEY=$(wg genkey) +PUB_KEY=$(wg pubkey <<<"$PRIV_KEY") + +# NOTE: same logic as `subcmds/new/cache-key` +# result defaults to unset (only stays unset if we intend on writing to a file) +RESULT="" +# JSON formatting +if [[ "$JSON" = true ]]; then + RESULT="{ + \"privateKey\": \"${PRIV_KEY}\" + \"publicKey\": \"${PUB_KEY}\" +}" + if [[ -n "$OUT" ]]; then + # confirm the user understands files will be overwritten + [[ "$FORCE" = true ]] || confirm-file-overwrite "$OUT" || exit 0 + echo -e "$RESULT" > "$OUT" + else + echo -e "$RESULT" + fi +# standard formatting (stdout) +elif [[ -z "$OUT" ]]; then + echo -e "${BOLD}${UNDERLINE}${RED}Private Key${RESET} + ${BOLD}${GREEN}${PRIV_KEY}${RESET} +${BOLD}${UNDERLINE}${RED}Public Key${RESET} + ${BOLD}${GREEN}${PUB_KEY}${RESET}" +# standard formatting (files) +else + # confirm the user understands files will be overwritten + [[ "$FORCE" = true ]] || confirm-file-overwrite "$OUT" "$OUT.pub" || exit 0 + echo "$PRIV_KEY" > "$OUT" + echo "$PUB_KEY" > "$OUT.pub" +fi; +unset -v OUT PRIV_KEY PUB_KEY RESULT From e80db5e349af4d167fb97614f018954c6305ad89 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Wed, 21 Jan 2026 12:50:26 +1000 Subject: [PATCH 177/338] change perr default styling --- ceru/libceru.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ceru/libceru.sh b/ceru/libceru.sh index d08551c..90db4ba 100755 --- a/ceru/libceru.sh +++ b/ceru/libceru.sh @@ -49,7 +49,7 @@ REVERSE='\033[7m' INVISIBLE='\033[8m' # Error Messages -function perr { echo -e "${BOLD}${RED}error:${RESET} $@\nTry ${BOLD}${GREEN}'--help'${RESET} for more information." >&2; } +function perr { echo -e "${BOLD}${RED}error:${WHITE} $@\n${BOLD}${GREEN}[+]${WHITE} Try ${GREEN}'--help'${WHITE} for more information." >&2; } function perr-usage { echo -e "$USAGE" >&2; } function perr-badflag { perr "unrecognised flag ${BOLD}${MAGENTA}'$1'${RESET}"; } function perr-noflagval { perr "flag ${BOLD}${MAGENTA}'$1'${RESET} requires ${BOLD}${MAGENTA}${2}${RESET} argument(s), but only ${BOLD}${MAGENTA}${3}${RESET} were given"; } From 97d8cea7ba4da97896ec2d9c382e8418042b1055 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Wed, 21 Jan 2026 12:50:26 +1000 Subject: [PATCH 178/338] change perr default styling --- ceru/libceru.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ceru/libceru.sh b/ceru/libceru.sh index d08551c..90db4ba 100755 --- a/ceru/libceru.sh +++ b/ceru/libceru.sh @@ -49,7 +49,7 @@ REVERSE='\033[7m' INVISIBLE='\033[8m' # Error Messages -function perr { echo -e "${BOLD}${RED}error:${RESET} $@\nTry ${BOLD}${GREEN}'--help'${RESET} for more information." >&2; } +function perr { echo -e "${BOLD}${RED}error:${WHITE} $@\n${BOLD}${GREEN}[+]${WHITE} Try ${GREEN}'--help'${WHITE} for more information." >&2; } function perr-usage { echo -e "$USAGE" >&2; } function perr-badflag { perr "unrecognised flag ${BOLD}${MAGENTA}'$1'${RESET}"; } function perr-noflagval { perr "flag ${BOLD}${MAGENTA}'$1'${RESET} requires ${BOLD}${MAGENTA}${2}${RESET} argument(s), but only ${BOLD}${MAGENTA}${3}${RESET} were given"; } From 24924908b12b3bd667866b90dd3164592c7b032f Mon Sep 17 00:00:00 2001 From: _cry64 Date: Wed, 21 Jan 2026 12:50:46 +1000 Subject: [PATCH 179/338] add perr-badval & throw-badval --- ceru/libceru.sh | 2 ++ ceru/subcmds/new/ssh-key | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ceru/libceru.sh b/ceru/libceru.sh index 90db4ba..f5070df 100755 --- a/ceru/libceru.sh +++ b/ceru/libceru.sh @@ -55,6 +55,7 @@ function perr-badflag { perr "unrecognised flag ${BOLD}${MAGENTA}'$1'${RESET} function perr-noflagval { perr "flag ${BOLD}${MAGENTA}'$1'${RESET} requires ${BOLD}${MAGENTA}${2}${RESET} argument(s), but only ${BOLD}${MAGENTA}${3}${RESET} were given"; } function perr-badarg { perr "unrecognised arg ${BOLD}${MAGENTA}'$1'${RESET}"; } function perr-noarg { perr "required argument ${BOLD}${MAGENTA}'$1'${RESET} is missing"; } +function perr-badval { perr "value ${MAGENTA}\"$1\"${WHITE} is not valid for flag ${CYAN}$2${RESET}"} # Failures function throw { echo -e "${@:2}" >&2; if [[ "$1" -ge 0 ]]; then exit "$1"; fi; } function throw-usage { throw "$1" "$(perr-usage 2>&1)"; } @@ -62,6 +63,7 @@ function throw-badflag { throw "$1" "$(perr-badflag "${@:2}" 2>&1)"; } function throw-noflagval { throw "$1" "$(perr-noflagval "${@:2}" 2>&1)"; } function throw-badarg { throw "$1" "$(perr-badarg "${@:2}" 2>&1)"; } function throw-noarg { throw "$1" "$(perr-noarg "${@:2}" 2>&1)"; } +function throw-badval { throw "$1" "$(perr-badval "$2" "$3" 2>&1)"; } # Parsing/Validation function required { [[ -n "$1" ]] || throw-noarg 1 "${@:2}"; } diff --git a/ceru/subcmds/new/ssh-key b/ceru/subcmds/new/ssh-key index 1ee9474..82260aa 100755 --- a/ceru/subcmds/new/ssh-key +++ b/ceru/subcmds/new/ssh-key @@ -92,7 +92,7 @@ case "$TYPE" in EXTRA="$EXTRA -b 4096" ;; *) - throw-badvalue 1 "$TYPE" '-t|--type' + throw-badval 1 "$TYPE" '-t|--type' ;; esac From ac516152f890b7f2ca339eaa21483d11f4dd2750 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Wed, 21 Jan 2026 12:50:46 +1000 Subject: [PATCH 180/338] add perr-badval & throw-badval --- ceru/libceru.sh | 2 ++ ceru/subcmds/new/ssh-key | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ceru/libceru.sh b/ceru/libceru.sh index 90db4ba..f5070df 100755 --- a/ceru/libceru.sh +++ b/ceru/libceru.sh @@ -55,6 +55,7 @@ function perr-badflag { perr "unrecognised flag ${BOLD}${MAGENTA}'$1'${RESET} function perr-noflagval { perr "flag ${BOLD}${MAGENTA}'$1'${RESET} requires ${BOLD}${MAGENTA}${2}${RESET} argument(s), but only ${BOLD}${MAGENTA}${3}${RESET} were given"; } function perr-badarg { perr "unrecognised arg ${BOLD}${MAGENTA}'$1'${RESET}"; } function perr-noarg { perr "required argument ${BOLD}${MAGENTA}'$1'${RESET} is missing"; } +function perr-badval { perr "value ${MAGENTA}\"$1\"${WHITE} is not valid for flag ${CYAN}$2${RESET}"} # Failures function throw { echo -e "${@:2}" >&2; if [[ "$1" -ge 0 ]]; then exit "$1"; fi; } function throw-usage { throw "$1" "$(perr-usage 2>&1)"; } @@ -62,6 +63,7 @@ function throw-badflag { throw "$1" "$(perr-badflag "${@:2}" 2>&1)"; } function throw-noflagval { throw "$1" "$(perr-noflagval "${@:2}" 2>&1)"; } function throw-badarg { throw "$1" "$(perr-badarg "${@:2}" 2>&1)"; } function throw-noarg { throw "$1" "$(perr-noarg "${@:2}" 2>&1)"; } +function throw-badval { throw "$1" "$(perr-badval "$2" "$3" 2>&1)"; } # Parsing/Validation function required { [[ -n "$1" ]] || throw-noarg 1 "${@:2}"; } diff --git a/ceru/subcmds/new/ssh-key b/ceru/subcmds/new/ssh-key index 1ee9474..82260aa 100755 --- a/ceru/subcmds/new/ssh-key +++ b/ceru/subcmds/new/ssh-key @@ -92,7 +92,7 @@ case "$TYPE" in EXTRA="$EXTRA -b 4096" ;; *) - throw-badvalue 1 "$TYPE" '-t|--type' + throw-badval 1 "$TYPE" '-t|--type' ;; esac From 8681d0e658ef238e27db37ac89c08a5d303ff080 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Wed, 21 Jan 2026 12:51:23 +1000 Subject: [PATCH 181/338] format throw family --- ceru/libceru.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ceru/libceru.sh b/ceru/libceru.sh index f5070df..09bf956 100755 --- a/ceru/libceru.sh +++ b/ceru/libceru.sh @@ -58,11 +58,11 @@ function perr-noarg { perr "required argument ${BOLD}${MAGENTA}'$1'${RESET} function perr-badval { perr "value ${MAGENTA}\"$1\"${WHITE} is not valid for flag ${CYAN}$2${RESET}"} # Failures function throw { echo -e "${@:2}" >&2; if [[ "$1" -ge 0 ]]; then exit "$1"; fi; } -function throw-usage { throw "$1" "$(perr-usage 2>&1)"; } -function throw-badflag { throw "$1" "$(perr-badflag "${@:2}" 2>&1)"; } -function throw-noflagval { throw "$1" "$(perr-noflagval "${@:2}" 2>&1)"; } -function throw-badarg { throw "$1" "$(perr-badarg "${@:2}" 2>&1)"; } -function throw-noarg { throw "$1" "$(perr-noarg "${@:2}" 2>&1)"; } +function throw-usage { throw "$1" "$(perr-usage 2>&1)"; } +function throw-badflag { throw "$1" "$(perr-badflag "${@:2}" 2>&1)"; } +function throw-noflagval { throw "$1" "$(perr-noflagval "${@:2}" 2>&1)"; } +function throw-badarg { throw "$1" "$(perr-badarg "${@:2}" 2>&1)"; } +function throw-noarg { throw "$1" "$(perr-noarg "${@:2}" 2>&1)"; } function throw-badval { throw "$1" "$(perr-badval "$2" "$3" 2>&1)"; } # Parsing/Validation function required { [[ -n "$1" ]] || throw-noarg 1 "${@:2}"; } From 0398ab5d81521747d242a384bbcdfb50bbc4f61a Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Wed, 21 Jan 2026 12:51:23 +1000 Subject: [PATCH 182/338] format throw family --- ceru/libceru.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ceru/libceru.sh b/ceru/libceru.sh index f5070df..09bf956 100755 --- a/ceru/libceru.sh +++ b/ceru/libceru.sh @@ -58,11 +58,11 @@ function perr-noarg { perr "required argument ${BOLD}${MAGENTA}'$1'${RESET} function perr-badval { perr "value ${MAGENTA}\"$1\"${WHITE} is not valid for flag ${CYAN}$2${RESET}"} # Failures function throw { echo -e "${@:2}" >&2; if [[ "$1" -ge 0 ]]; then exit "$1"; fi; } -function throw-usage { throw "$1" "$(perr-usage 2>&1)"; } -function throw-badflag { throw "$1" "$(perr-badflag "${@:2}" 2>&1)"; } -function throw-noflagval { throw "$1" "$(perr-noflagval "${@:2}" 2>&1)"; } -function throw-badarg { throw "$1" "$(perr-badarg "${@:2}" 2>&1)"; } -function throw-noarg { throw "$1" "$(perr-noarg "${@:2}" 2>&1)"; } +function throw-usage { throw "$1" "$(perr-usage 2>&1)"; } +function throw-badflag { throw "$1" "$(perr-badflag "${@:2}" 2>&1)"; } +function throw-noflagval { throw "$1" "$(perr-noflagval "${@:2}" 2>&1)"; } +function throw-badarg { throw "$1" "$(perr-badarg "${@:2}" 2>&1)"; } +function throw-noarg { throw "$1" "$(perr-noarg "${@:2}" 2>&1)"; } function throw-badval { throw "$1" "$(perr-badval "$2" "$3" 2>&1)"; } # Parsing/Validation function required { [[ -n "$1" ]] || throw-noarg 1 "${@:2}"; } From d3463b5343a695e4499b633ce51981cd6546b43c Mon Sep 17 00:00:00 2001 From: _cry64 Date: Wed, 21 Jan 2026 12:51:50 +1000 Subject: [PATCH 183/338] safe shifting is unnecessary --- ceru/subcmds/new/cache-key | 2 -- ceru/subcmds/new/ssh-key | 3 --- ceru/subcmds/new/wg-key | 1 - 3 files changed, 6 deletions(-) diff --git a/ceru/subcmds/new/cache-key b/ceru/subcmds/new/cache-key index 9431a0b..0b5aa13 100755 --- a/ceru/subcmds/new/cache-key +++ b/ceru/subcmds/new/cache-key @@ -43,7 +43,6 @@ while [[ $# -gt 0 ]]; do ;; -n|--name) shift - # XXX: NOTE: do I need to safe shift (shift || true) since -e is set? NAME="$1"; shift ;; -P|--private-only) @@ -52,7 +51,6 @@ while [[ $# -gt 0 ]]; do ;; -o|--out) shift - # XXX: NOTE: do I need to safe shift (shift || true) since -e is set? SINK="$1"; shift ;; -j|--json) diff --git a/ceru/subcmds/new/ssh-key b/ceru/subcmds/new/ssh-key index 82260aa..2bf700a 100755 --- a/ceru/subcmds/new/ssh-key +++ b/ceru/subcmds/new/ssh-key @@ -49,12 +49,10 @@ while [[ $# -gt 0 ]]; do ;; -o|--out) shift - # XXX: NOTE: do I need to safe shift (shift || true) since -e is set? OUT="$1"; shift ;; -c|--comment) shift - # XXX: NOTE: do I need to safe shift (shift || true) since -e is set? COMMENT="$1"; shift ;; -t|--type) @@ -63,7 +61,6 @@ while [[ $# -gt 0 ]]; do ;; -r|--rounds) shift - # XXX: NOTE: do I need to safe shift (shift || true) since -e is set? ROUNDS="$1"; shift ;; -N|--nopasswd) diff --git a/ceru/subcmds/new/wg-key b/ceru/subcmds/new/wg-key index 3a008a7..2efa85c 100755 --- a/ceru/subcmds/new/wg-key +++ b/ceru/subcmds/new/wg-key @@ -39,7 +39,6 @@ while [[ $# -gt 0 ]]; do ;; -o|--out) shift - # XXX: NOTE: do I need to safe shift (shift || true) since -e is set? OUT="$1"; shift ;; -j|--json) From 284d06d55e6477b372f97f878d7c60d88a290998 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Wed, 21 Jan 2026 12:51:50 +1000 Subject: [PATCH 184/338] safe shifting is unnecessary --- ceru/subcmds/new/cache-key | 2 -- ceru/subcmds/new/ssh-key | 3 --- ceru/subcmds/new/wg-key | 1 - 3 files changed, 6 deletions(-) diff --git a/ceru/subcmds/new/cache-key b/ceru/subcmds/new/cache-key index 9431a0b..0b5aa13 100755 --- a/ceru/subcmds/new/cache-key +++ b/ceru/subcmds/new/cache-key @@ -43,7 +43,6 @@ while [[ $# -gt 0 ]]; do ;; -n|--name) shift - # XXX: NOTE: do I need to safe shift (shift || true) since -e is set? NAME="$1"; shift ;; -P|--private-only) @@ -52,7 +51,6 @@ while [[ $# -gt 0 ]]; do ;; -o|--out) shift - # XXX: NOTE: do I need to safe shift (shift || true) since -e is set? SINK="$1"; shift ;; -j|--json) diff --git a/ceru/subcmds/new/ssh-key b/ceru/subcmds/new/ssh-key index 82260aa..2bf700a 100755 --- a/ceru/subcmds/new/ssh-key +++ b/ceru/subcmds/new/ssh-key @@ -49,12 +49,10 @@ while [[ $# -gt 0 ]]; do ;; -o|--out) shift - # XXX: NOTE: do I need to safe shift (shift || true) since -e is set? OUT="$1"; shift ;; -c|--comment) shift - # XXX: NOTE: do I need to safe shift (shift || true) since -e is set? COMMENT="$1"; shift ;; -t|--type) @@ -63,7 +61,6 @@ while [[ $# -gt 0 ]]; do ;; -r|--rounds) shift - # XXX: NOTE: do I need to safe shift (shift || true) since -e is set? ROUNDS="$1"; shift ;; -N|--nopasswd) diff --git a/ceru/subcmds/new/wg-key b/ceru/subcmds/new/wg-key index 3a008a7..2efa85c 100755 --- a/ceru/subcmds/new/wg-key +++ b/ceru/subcmds/new/wg-key @@ -39,7 +39,6 @@ while [[ $# -gt 0 ]]; do ;; -o|--out) shift - # XXX: NOTE: do I need to safe shift (shift || true) since -e is set? OUT="$1"; shift ;; -j|--json) From b49e1d6de315650386185ed5a720ef008f36959b Mon Sep 17 00:00:00 2001 From: _cry64 Date: Wed, 21 Jan 2026 12:52:33 +1000 Subject: [PATCH 185/338] minor ssh-key styling --- ceru/subcmds/new/ssh-key | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ceru/subcmds/new/ssh-key b/ceru/subcmds/new/ssh-key index 2bf700a..1e27ac4 100755 --- a/ceru/subcmds/new/ssh-key +++ b/ceru/subcmds/new/ssh-key @@ -96,7 +96,7 @@ esac if [[ "$HWKEY" == true ]]; then if [[ "$TYPE" == "rsa" ]]; then - echo -e "${BOLD}${RED}-H|--hardware-key${RESET} flag is not valid for ${BOLD}${MAGENTA}rsa${RESET} keys ${BOLD}${CYAN}(use ed25519 instead)${RESET}" + echo -e "${BOLD}${MAGENTA}-H|--hardware-key${RESET} flag is not valid for ${BOLD}${MAGE}rsa${RESET} keys ${BOLD}${CYAN}(use ed25519 instead)${RESET}" exit 1 fi TYPE="$TYPE-sk" @@ -106,7 +106,7 @@ if [[ -n "$COMMENT" ]]; then EXTRA="$EXTRA -C '$COMMENT'" fi -# BUG: WARNING: $OUT permits arbitrary command injection +# BUG: WARNING: TODO: $OUT permits arbitrary command injection if [[ -n "$OUT" ]]; then EXTRA="$EXTRA -f $OUT" else From dd77921dc46cd3dc8fe64606267c28a471917f5d Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Wed, 21 Jan 2026 12:52:33 +1000 Subject: [PATCH 186/338] minor ssh-key styling --- ceru/subcmds/new/ssh-key | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ceru/subcmds/new/ssh-key b/ceru/subcmds/new/ssh-key index 2bf700a..1e27ac4 100755 --- a/ceru/subcmds/new/ssh-key +++ b/ceru/subcmds/new/ssh-key @@ -96,7 +96,7 @@ esac if [[ "$HWKEY" == true ]]; then if [[ "$TYPE" == "rsa" ]]; then - echo -e "${BOLD}${RED}-H|--hardware-key${RESET} flag is not valid for ${BOLD}${MAGENTA}rsa${RESET} keys ${BOLD}${CYAN}(use ed25519 instead)${RESET}" + echo -e "${BOLD}${MAGENTA}-H|--hardware-key${RESET} flag is not valid for ${BOLD}${MAGE}rsa${RESET} keys ${BOLD}${CYAN}(use ed25519 instead)${RESET}" exit 1 fi TYPE="$TYPE-sk" @@ -106,7 +106,7 @@ if [[ -n "$COMMENT" ]]; then EXTRA="$EXTRA -C '$COMMENT'" fi -# BUG: WARNING: $OUT permits arbitrary command injection +# BUG: WARNING: TODO: $OUT permits arbitrary command injection if [[ -n "$OUT" ]]; then EXTRA="$EXTRA -f $OUT" else From cd337e8f2bcf57750ad5de8ccc65773baaa0d078 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Wed, 21 Jan 2026 14:14:25 +1000 Subject: [PATCH 187/338] style ssh-key description --- ceru/subcmds/new/ssh-key | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ceru/subcmds/new/ssh-key b/ceru/subcmds/new/ssh-key index 1e27ac4..7e8605b 100755 --- a/ceru/subcmds/new/ssh-key +++ b/ceru/subcmds/new/ssh-key @@ -20,7 +20,7 @@ USAGE="${BOLD}${UNDERLINE}${RED}Usage${RESET} ${BOLD}${UNDERLINE}${RED}Description${RESET} Generates a new SSH keypair with secure defaults. - For more advanced usage run the \`ssh-keygen\` utility directly. + For more advanced usage run the ${BOLD}${MAGENTA}\`ssh-keygen\`${RESET} utility directly. ${BOLD}${UNDERLINE}${RED}Options${RESET} ${BOLD}${MAGENTA}-h, --help${RESET} Show this message (^_^) From 55d0b92c3c409593ea01e17443c2c1f46f33153d Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Wed, 21 Jan 2026 14:14:25 +1000 Subject: [PATCH 188/338] style ssh-key description --- ceru/subcmds/new/ssh-key | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ceru/subcmds/new/ssh-key b/ceru/subcmds/new/ssh-key index 1e27ac4..7e8605b 100755 --- a/ceru/subcmds/new/ssh-key +++ b/ceru/subcmds/new/ssh-key @@ -20,7 +20,7 @@ USAGE="${BOLD}${UNDERLINE}${RED}Usage${RESET} ${BOLD}${UNDERLINE}${RED}Description${RESET} Generates a new SSH keypair with secure defaults. - For more advanced usage run the \`ssh-keygen\` utility directly. + For more advanced usage run the ${BOLD}${MAGENTA}\`ssh-keygen\`${RESET} utility directly. ${BOLD}${UNDERLINE}${RED}Options${RESET} ${BOLD}${MAGENTA}-h, --help${RESET} Show this message (^_^) From c99f859659bcc577a7652dc55e908b07e960eb2f Mon Sep 17 00:00:00 2001 From: _cry64 Date: Wed, 21 Jan 2026 14:14:54 +1000 Subject: [PATCH 189/338] fix ssh-key can do more than just rsa4096 --- ceru/subcmds/new/default.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ceru/subcmds/new/default.sh b/ceru/subcmds/new/default.sh index 8731446..4a5bc52 100755 --- a/ceru/subcmds/new/default.sh +++ b/ceru/subcmds/new/default.sh @@ -23,7 +23,7 @@ ${BOLD}${UNDERLINE}${RED}Options${RESET} ${BOLD}${UNDERLINE}${RED}Subcommands${RESET} ${BOLD}${CYAN}cache-key${RESET} Generate a new binary-cache signing keypair - ${BOLD}${CYAN}ssh-key${RESET} Generate a new SSH RSA-4096 keypair + ${BOLD}${CYAN}ssh-key${RESET} Generate a new SSH keypair ${BOLD}${CYAN}wg-key${RESET} Generate a new Wireguard keypair" # parse all args From c19acc924eb528ddc1da328ab5c7bc9c3c0bb59b Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Wed, 21 Jan 2026 14:14:54 +1000 Subject: [PATCH 190/338] fix ssh-key can do more than just rsa4096 --- ceru/subcmds/new/default.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ceru/subcmds/new/default.sh b/ceru/subcmds/new/default.sh index 8731446..4a5bc52 100755 --- a/ceru/subcmds/new/default.sh +++ b/ceru/subcmds/new/default.sh @@ -23,7 +23,7 @@ ${BOLD}${UNDERLINE}${RED}Options${RESET} ${BOLD}${UNDERLINE}${RED}Subcommands${RESET} ${BOLD}${CYAN}cache-key${RESET} Generate a new binary-cache signing keypair - ${BOLD}${CYAN}ssh-key${RESET} Generate a new SSH RSA-4096 keypair + ${BOLD}${CYAN}ssh-key${RESET} Generate a new SSH keypair ${BOLD}${CYAN}wg-key${RESET} Generate a new Wireguard keypair" # parse all args From 4daab330cb47316b141f7cc3039c005094455e52 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Wed, 21 Jan 2026 14:15:16 +1000 Subject: [PATCH 191/338] fix perr-badval missing semi-colon --- ceru/libceru.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ceru/libceru.sh b/ceru/libceru.sh index 09bf956..24dca94 100755 --- a/ceru/libceru.sh +++ b/ceru/libceru.sh @@ -55,7 +55,7 @@ function perr-badflag { perr "unrecognised flag ${BOLD}${MAGENTA}'$1'${RESET} function perr-noflagval { perr "flag ${BOLD}${MAGENTA}'$1'${RESET} requires ${BOLD}${MAGENTA}${2}${RESET} argument(s), but only ${BOLD}${MAGENTA}${3}${RESET} were given"; } function perr-badarg { perr "unrecognised arg ${BOLD}${MAGENTA}'$1'${RESET}"; } function perr-noarg { perr "required argument ${BOLD}${MAGENTA}'$1'${RESET} is missing"; } -function perr-badval { perr "value ${MAGENTA}\"$1\"${WHITE} is not valid for flag ${CYAN}$2${RESET}"} +function perr-badval { perr "value ${MAGENTA}\"$1\"${WHITE} is not valid for flag ${CYAN}$2${RESET}"; } # Failures function throw { echo -e "${@:2}" >&2; if [[ "$1" -ge 0 ]]; then exit "$1"; fi; } function throw-usage { throw "$1" "$(perr-usage 2>&1)"; } From da4d6c8aed7c1b1a10aeddb75eda393baefbcaee Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Wed, 21 Jan 2026 14:15:16 +1000 Subject: [PATCH 192/338] fix perr-badval missing semi-colon --- ceru/libceru.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ceru/libceru.sh b/ceru/libceru.sh index 09bf956..24dca94 100755 --- a/ceru/libceru.sh +++ b/ceru/libceru.sh @@ -55,7 +55,7 @@ function perr-badflag { perr "unrecognised flag ${BOLD}${MAGENTA}'$1'${RESET} function perr-noflagval { perr "flag ${BOLD}${MAGENTA}'$1'${RESET} requires ${BOLD}${MAGENTA}${2}${RESET} argument(s), but only ${BOLD}${MAGENTA}${3}${RESET} were given"; } function perr-badarg { perr "unrecognised arg ${BOLD}${MAGENTA}'$1'${RESET}"; } function perr-noarg { perr "required argument ${BOLD}${MAGENTA}'$1'${RESET} is missing"; } -function perr-badval { perr "value ${MAGENTA}\"$1\"${WHITE} is not valid for flag ${CYAN}$2${RESET}"} +function perr-badval { perr "value ${MAGENTA}\"$1\"${WHITE} is not valid for flag ${CYAN}$2${RESET}"; } # Failures function throw { echo -e "${@:2}" >&2; if [[ "$1" -ge 0 ]]; then exit "$1"; fi; } function throw-usage { throw "$1" "$(perr-usage 2>&1)"; } From 97c9dfb98610b0b4cbfbeb9cb630eb48b91546d3 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Wed, 21 Jan 2026 14:15:31 +1000 Subject: [PATCH 193/338] add subcmds/new/password --- ceru/subcmds/new/default.sh | 1 + ceru/subcmds/new/password | 260 ++++++++++++++++++++++++++++++++++++ 2 files changed, 261 insertions(+) create mode 100755 ceru/subcmds/new/password diff --git a/ceru/subcmds/new/default.sh b/ceru/subcmds/new/default.sh index 4a5bc52..af34920 100755 --- a/ceru/subcmds/new/default.sh +++ b/ceru/subcmds/new/default.sh @@ -23,6 +23,7 @@ ${BOLD}${UNDERLINE}${RED}Options${RESET} ${BOLD}${UNDERLINE}${RED}Subcommands${RESET} ${BOLD}${CYAN}cache-key${RESET} Generate a new binary-cache signing keypair + ${BOLD}${CYAN}password${RESET} Generate a new hashed Unix user password ${BOLD}${CYAN}ssh-key${RESET} Generate a new SSH keypair ${BOLD}${CYAN}wg-key${RESET} Generate a new Wireguard keypair" diff --git a/ceru/subcmds/new/password b/ceru/subcmds/new/password new file mode 100755 index 0000000..ae640e7 --- /dev/null +++ b/ceru/subcmds/new/password @@ -0,0 +1,260 @@ +#!/usr/bin/env bash +# Copyright 2026 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -euo pipefail + +USAGE="${BOLD}${UNDERLINE}${RED}Usage${RESET} + ${BOLD}${GREEN}$THIS new password [option...]${RESET} + +${BOLD}${UNDERLINE}${RED}Description${RESET} + Generates a new password hash in libxcrypt format with secure defaults. + For more advanced usage run the ${BOLD}${MAGENTA}\`mkpasswd\`${RESET} utility directly. + +${BOLD}${UNDERLINE}${RED}Options${RESET} + ${BOLD}${MAGENTA}-h, --help${RESET} Show this message (^_^) + ${BOLD}${MAGENTA}-o, --out${RESET} Private key file name to write to (the public key is named identically but ends with ${BOLD}${CYAN}.pub${RESET}) + ${BOLD}${MAGENTA}-j, --json${RESET} Output in JSON format + ${BOLD}${MAGENTA}-i, --stdin${RESET} Read the password to hash from stdin ${BOLD}$CYAN(single line only)${RESET} + ${BOLD}${MAGENTA}-t, --type${RESET} The hash algorithm to use: ${BOLD}${MAGENTA}yescrypt, scrypt, bcrypt ${CYAN}(default: yescrypt)${RESET} + ${BOLD}${MAGENTA}-r, --rounds${RESET} The number of key derivation function rounds to apply ${BOLD}${MAGENTA}(format: ^[0-9]+\$) ${CYAN}(defaults: yescrypt=11, scrypt=10, bcrypt=14)${RESET} + ${BOLD}${MAGENTA}-s, --salt${RESET} Specify the hash's salt directly ${BOLD}${RED}(not recommended)${RESET} ${BOLD}${CYAN}(default: libxcrypt secure default)${RESET}" + +# ==== Argument Values ==== +TYPE='yescrypt' +ROUNDS='' +SALTED=false +OUT='' +JSON=false +STDIN=false +EXTRA='' +# ==== Argument Values ==== + +# parse all args +while [[ $# -gt 0 ]]; do + ARG="$1" + case "$ARG" in + -h|--help) + throw-usage 0 + ;; + -o|--out) + shift + OUT="$1"; shift + ;; + -j|--json) + shift + JSON=true + ;; + -i|--stdin) + shift + STDIN=true + ;; + -t|--type) + shift + TYPE="$1"; shift + ;; + -r|--rounds) + shift + ROUNDS="$1"; shift + ;; + -s|--salt) + shift + if [[ "$SALTED" == false ]]; then + SALTED=true + EXTRA="$EXTRA --salt=\'$1\'" + fi + shift + ;; + -*) + throw-badflag 1 "$ARG" + ;; + *) + throw-badarg 1 "$ARG" + ;; + esac +done; unset -v ARG + +# NOTE: Available password hashing methods for /etc/passwd & /etc/shadow +# NOTE: Read the manual pages via `man 3 crypt` and `man 5 crypt` (if available) +# NOTE: Available online via https://man.archlinux.org/man/crypt.5 +# WARNING: Due to modern developments in cryptography most of these methods +# WARNING: are no longer recommended however some distrobutions still use them. +# WARNING: Cerulean intentionally restricts access to only secure algorithms. +# $ mkpasswd -m help +## Available methods: +## yescrypt Yescrypt +## gost-yescrypt GOST Yescrypt +## scrypt scrypt +## bcrypt bcrypt +## bcrypt-a bcrypt (obsolete $2a$ version) +## sha512crypt SHA-512 +## sha256crypt SHA-256 +## sunmd5 SunMD5 +## md5crypt MD5 +## bsdicrypt BSDI extended DES-based crypt(3) +## descrypt standard 56 bit DES-based crypt(3) +## nt NT-Hash +function perr-unsupportedhash { + local ALGO="$1" + echo -e "${BOLD}${CYAN}$THIS${RED} does not support the ${MAGENTA}$ALGO${RED} hashing algorithm${RESET}" >&2 +} +function perr-forbiddenhash { + local ALGO="$1" + echo -e "${BOLD}${CYAN}Cerulean${RED} intentionally forbids ${MAGENTA}$ALGO-based${RED} hashes.${RESET}" >&2 +} +function perr-recommendhash { + local ALGO="$1" + echo -e "${BOLD}${CYAN}Cerulean${WHITE} recommends the ${MAGENTA}$ALGO${WHITE} algorithm ${GREEN}(embrace modernity loser)${RESET}" >&2 +} + +# ensure $TYPE is a valid hash algorithm +case "$TYPE" in + # ========= PERMITTED HASH ALGORITHMS ========= + yescrypt) + if [[ -z "$ROUNDS" ]]; then + ROUNDS='11' + fi + ;; + scrypt) + if [[ -z "$ROUNDS" ]]; then + ROUNDS='10' + fi + ;; + bcrypt) + if [[ -z "$ROUNDS" ]]; then + ROUNDS='14' + fi + ;; + + # ========= FORBIDDEN HASH ALGORITHMS ========= + gost-yescrypt) + perr-unsupportedhash "$TYPE" + perr-recommendhash 'yescrypt' + echo -e "┏ +┃ Dear Comrade, +┃ It is with a heavy heart I must inform you that \"GOST Algorithms +┃ Considered Harmful\" - Edsger Wybe Dijkstra (probably). Alas +┃ GOST is considered broken... It is no longer 1970, please grow up :( +┃ Слава Родине! - Glory to the Motherland! +┗" >&2 + exit 1 + ;; + bcrypt-a) + perr-unsupportedhash "$TYPE" + perr-recommendhash 'bcrypt' + echo -e "┏ +┃ The alternative prefix \"\$2y$\" is equivalent to \"\$2b$\". +┃ It exists for historical reasons only. The alternative prefixes +┃ \"\$2a$\" and \"\$2x$\" provide bug-compatibility with +┃ crypt_blowfish 1.0.4 and earlier, which incorrectly processed +┃ characters with the 8th bit set. +┗" >&2 + exit 1 + ;; + sha512crypt|sha256crypt) + perr-unsupportedhash "$TYPE" + perr-forbiddenhash 'SHA' + echo -e "┏ +┃ SHA-based hashes are considered outdated and generally insecure +┃ due to their vulnerabilit to brute-force and collision attacks. +┃ Modern algorithms such as yescrypt, scrypt, and bcrypt are recommended. +┗" >&2 + exit 1 + ;; + sunmd5|md5crypt) + perr-unsupportedhash "$TYPE" + perr-forbiddenhash 'MD5' + echo -e "┏ +┃ Not as weak as the DES-based hashes, but MD5 is so cheap +┃ on modern hardware that it should not be used for new hashes. +┗" >&2 + exit 1 + ;; + bsdicrypt|descrypt) + perr-unsupportedhash "$TYPE" + perr-forbiddenhash 'DES' + echo -e "┏ +┃ The DES block cipher is cheap on modern hardware. Because there are only +┃ 4096 possible salts and 2**56 distinct passphrases, which it +┃ truncates to 8 characters, it is feasible to discover any passphrase +┃ hashed with this method. It should only be used if you absolutely have to +┃ generate hashes that will work on an old operating system that supports nothing else. +┗" >&2 + exit 1 + ;; + nt) + perr-unsupportedhash "$TYPE" + echo -e "${BOLD}Please ${RED}repent${WHITE} for your filthy sins ${RED}you disgusting human...${RESET}" >&2 + echo -e "┏ +┃ Available for cross-compatibility's sake on FreeBSD. Based on MD4. +┃ Has no salt or tunable cost parameter. It is so weak that +┃ almost any human-chosen passphrase hashed with this method is guessable. +┃ It should only be used if you absolutely have to generate hashes that +┃ will work on an old operating system that supports nothing else. +┗" >&2 + exit 1 + ;; + *) + echo -e "${BOLD}${RED}Unrecognised hash algorithm ${MAGENTA}\"$TYPE\"${RESET}" >&2 + echo -e "${BOLD}${GREEN}Supported algorithms: ${MAGENTA}yescrypt, scrypt, bcrypt${RESET}" >&2 + exit 1 + ;; +esac +unset -f perr-unsupportedhash perr-forbiddenhash perr-recommendhash + +# ensure $ROUNDS is a valid numeric +re='^[0-9]+$' +if ! [[ "$ROUNDS" =~ ^[0-9]+$ ]] ; then + throw-badval 1 "$ROUNDS" '-r|--rounds' +fi +unset -v re + +# Acquire password from stdin +if [[ "$STDIN" == true ]]; then + read -s PASS +else + read -sp "$(echo -e "${BOLD}${GREEN}Password:${RESET} ")" PASS + echo # \n + read -sp "$(echo -e "${BOLD}${GREEN}Retype Password:${RESET} ")" PASS2 + echo # \n + if [[ "$PASS" != "$PASS2" ]]; then + echo -e "${BOLD}${RED}Sorry, passwords do not match${RESET}" >&2 + exit 1 + fi + unset -v PASS2 +fi + +# Compute hash of password +RESULT=$(mkpasswd -sm "$TYPE" -R "$ROUNDS" $EXTRA <<<"$PASS") +unset -v PASS +# Format as JSON if necessary +if [[ "$JSON" == true ]]; then + RESULT="{ + \"type\": \"${TYPE}\", + \"rounds\": ${ROUNDS}, + \"hash\": \"${RESULT}\" +}" +fi + +# Display hash result +if [[ -n "$OUT" ]]; then + echo "$RESULT" > "$OUT" +elif [[ "$JSON" == true ]]; then + echo "$RESULT" +else + echo -e "${BOLD}${GREEN}Hash:${WHITE} $RESULT${RESET}" +fi +unset -v RESULT + +unset -v TYPE ROUNDS SALTED OUT JSON STDIN EXTRA From 5c3c08828a567e16205bc03ba4e0731e746714a5 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Wed, 21 Jan 2026 14:15:31 +1000 Subject: [PATCH 194/338] add subcmds/new/password --- ceru/subcmds/new/default.sh | 1 + ceru/subcmds/new/password | 260 ++++++++++++++++++++++++++++++++++++ 2 files changed, 261 insertions(+) create mode 100755 ceru/subcmds/new/password diff --git a/ceru/subcmds/new/default.sh b/ceru/subcmds/new/default.sh index 4a5bc52..af34920 100755 --- a/ceru/subcmds/new/default.sh +++ b/ceru/subcmds/new/default.sh @@ -23,6 +23,7 @@ ${BOLD}${UNDERLINE}${RED}Options${RESET} ${BOLD}${UNDERLINE}${RED}Subcommands${RESET} ${BOLD}${CYAN}cache-key${RESET} Generate a new binary-cache signing keypair + ${BOLD}${CYAN}password${RESET} Generate a new hashed Unix user password ${BOLD}${CYAN}ssh-key${RESET} Generate a new SSH keypair ${BOLD}${CYAN}wg-key${RESET} Generate a new Wireguard keypair" diff --git a/ceru/subcmds/new/password b/ceru/subcmds/new/password new file mode 100755 index 0000000..ae640e7 --- /dev/null +++ b/ceru/subcmds/new/password @@ -0,0 +1,260 @@ +#!/usr/bin/env bash +# Copyright 2026 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -euo pipefail + +USAGE="${BOLD}${UNDERLINE}${RED}Usage${RESET} + ${BOLD}${GREEN}$THIS new password [option...]${RESET} + +${BOLD}${UNDERLINE}${RED}Description${RESET} + Generates a new password hash in libxcrypt format with secure defaults. + For more advanced usage run the ${BOLD}${MAGENTA}\`mkpasswd\`${RESET} utility directly. + +${BOLD}${UNDERLINE}${RED}Options${RESET} + ${BOLD}${MAGENTA}-h, --help${RESET} Show this message (^_^) + ${BOLD}${MAGENTA}-o, --out${RESET} Private key file name to write to (the public key is named identically but ends with ${BOLD}${CYAN}.pub${RESET}) + ${BOLD}${MAGENTA}-j, --json${RESET} Output in JSON format + ${BOLD}${MAGENTA}-i, --stdin${RESET} Read the password to hash from stdin ${BOLD}$CYAN(single line only)${RESET} + ${BOLD}${MAGENTA}-t, --type${RESET} The hash algorithm to use: ${BOLD}${MAGENTA}yescrypt, scrypt, bcrypt ${CYAN}(default: yescrypt)${RESET} + ${BOLD}${MAGENTA}-r, --rounds${RESET} The number of key derivation function rounds to apply ${BOLD}${MAGENTA}(format: ^[0-9]+\$) ${CYAN}(defaults: yescrypt=11, scrypt=10, bcrypt=14)${RESET} + ${BOLD}${MAGENTA}-s, --salt${RESET} Specify the hash's salt directly ${BOLD}${RED}(not recommended)${RESET} ${BOLD}${CYAN}(default: libxcrypt secure default)${RESET}" + +# ==== Argument Values ==== +TYPE='yescrypt' +ROUNDS='' +SALTED=false +OUT='' +JSON=false +STDIN=false +EXTRA='' +# ==== Argument Values ==== + +# parse all args +while [[ $# -gt 0 ]]; do + ARG="$1" + case "$ARG" in + -h|--help) + throw-usage 0 + ;; + -o|--out) + shift + OUT="$1"; shift + ;; + -j|--json) + shift + JSON=true + ;; + -i|--stdin) + shift + STDIN=true + ;; + -t|--type) + shift + TYPE="$1"; shift + ;; + -r|--rounds) + shift + ROUNDS="$1"; shift + ;; + -s|--salt) + shift + if [[ "$SALTED" == false ]]; then + SALTED=true + EXTRA="$EXTRA --salt=\'$1\'" + fi + shift + ;; + -*) + throw-badflag 1 "$ARG" + ;; + *) + throw-badarg 1 "$ARG" + ;; + esac +done; unset -v ARG + +# NOTE: Available password hashing methods for /etc/passwd & /etc/shadow +# NOTE: Read the manual pages via `man 3 crypt` and `man 5 crypt` (if available) +# NOTE: Available online via https://man.archlinux.org/man/crypt.5 +# WARNING: Due to modern developments in cryptography most of these methods +# WARNING: are no longer recommended however some distrobutions still use them. +# WARNING: Cerulean intentionally restricts access to only secure algorithms. +# $ mkpasswd -m help +## Available methods: +## yescrypt Yescrypt +## gost-yescrypt GOST Yescrypt +## scrypt scrypt +## bcrypt bcrypt +## bcrypt-a bcrypt (obsolete $2a$ version) +## sha512crypt SHA-512 +## sha256crypt SHA-256 +## sunmd5 SunMD5 +## md5crypt MD5 +## bsdicrypt BSDI extended DES-based crypt(3) +## descrypt standard 56 bit DES-based crypt(3) +## nt NT-Hash +function perr-unsupportedhash { + local ALGO="$1" + echo -e "${BOLD}${CYAN}$THIS${RED} does not support the ${MAGENTA}$ALGO${RED} hashing algorithm${RESET}" >&2 +} +function perr-forbiddenhash { + local ALGO="$1" + echo -e "${BOLD}${CYAN}Cerulean${RED} intentionally forbids ${MAGENTA}$ALGO-based${RED} hashes.${RESET}" >&2 +} +function perr-recommendhash { + local ALGO="$1" + echo -e "${BOLD}${CYAN}Cerulean${WHITE} recommends the ${MAGENTA}$ALGO${WHITE} algorithm ${GREEN}(embrace modernity loser)${RESET}" >&2 +} + +# ensure $TYPE is a valid hash algorithm +case "$TYPE" in + # ========= PERMITTED HASH ALGORITHMS ========= + yescrypt) + if [[ -z "$ROUNDS" ]]; then + ROUNDS='11' + fi + ;; + scrypt) + if [[ -z "$ROUNDS" ]]; then + ROUNDS='10' + fi + ;; + bcrypt) + if [[ -z "$ROUNDS" ]]; then + ROUNDS='14' + fi + ;; + + # ========= FORBIDDEN HASH ALGORITHMS ========= + gost-yescrypt) + perr-unsupportedhash "$TYPE" + perr-recommendhash 'yescrypt' + echo -e "┏ +┃ Dear Comrade, +┃ It is with a heavy heart I must inform you that \"GOST Algorithms +┃ Considered Harmful\" - Edsger Wybe Dijkstra (probably). Alas +┃ GOST is considered broken... It is no longer 1970, please grow up :( +┃ Слава Родине! - Glory to the Motherland! +┗" >&2 + exit 1 + ;; + bcrypt-a) + perr-unsupportedhash "$TYPE" + perr-recommendhash 'bcrypt' + echo -e "┏ +┃ The alternative prefix \"\$2y$\" is equivalent to \"\$2b$\". +┃ It exists for historical reasons only. The alternative prefixes +┃ \"\$2a$\" and \"\$2x$\" provide bug-compatibility with +┃ crypt_blowfish 1.0.4 and earlier, which incorrectly processed +┃ characters with the 8th bit set. +┗" >&2 + exit 1 + ;; + sha512crypt|sha256crypt) + perr-unsupportedhash "$TYPE" + perr-forbiddenhash 'SHA' + echo -e "┏ +┃ SHA-based hashes are considered outdated and generally insecure +┃ due to their vulnerabilit to brute-force and collision attacks. +┃ Modern algorithms such as yescrypt, scrypt, and bcrypt are recommended. +┗" >&2 + exit 1 + ;; + sunmd5|md5crypt) + perr-unsupportedhash "$TYPE" + perr-forbiddenhash 'MD5' + echo -e "┏ +┃ Not as weak as the DES-based hashes, but MD5 is so cheap +┃ on modern hardware that it should not be used for new hashes. +┗" >&2 + exit 1 + ;; + bsdicrypt|descrypt) + perr-unsupportedhash "$TYPE" + perr-forbiddenhash 'DES' + echo -e "┏ +┃ The DES block cipher is cheap on modern hardware. Because there are only +┃ 4096 possible salts and 2**56 distinct passphrases, which it +┃ truncates to 8 characters, it is feasible to discover any passphrase +┃ hashed with this method. It should only be used if you absolutely have to +┃ generate hashes that will work on an old operating system that supports nothing else. +┗" >&2 + exit 1 + ;; + nt) + perr-unsupportedhash "$TYPE" + echo -e "${BOLD}Please ${RED}repent${WHITE} for your filthy sins ${RED}you disgusting human...${RESET}" >&2 + echo -e "┏ +┃ Available for cross-compatibility's sake on FreeBSD. Based on MD4. +┃ Has no salt or tunable cost parameter. It is so weak that +┃ almost any human-chosen passphrase hashed with this method is guessable. +┃ It should only be used if you absolutely have to generate hashes that +┃ will work on an old operating system that supports nothing else. +┗" >&2 + exit 1 + ;; + *) + echo -e "${BOLD}${RED}Unrecognised hash algorithm ${MAGENTA}\"$TYPE\"${RESET}" >&2 + echo -e "${BOLD}${GREEN}Supported algorithms: ${MAGENTA}yescrypt, scrypt, bcrypt${RESET}" >&2 + exit 1 + ;; +esac +unset -f perr-unsupportedhash perr-forbiddenhash perr-recommendhash + +# ensure $ROUNDS is a valid numeric +re='^[0-9]+$' +if ! [[ "$ROUNDS" =~ ^[0-9]+$ ]] ; then + throw-badval 1 "$ROUNDS" '-r|--rounds' +fi +unset -v re + +# Acquire password from stdin +if [[ "$STDIN" == true ]]; then + read -s PASS +else + read -sp "$(echo -e "${BOLD}${GREEN}Password:${RESET} ")" PASS + echo # \n + read -sp "$(echo -e "${BOLD}${GREEN}Retype Password:${RESET} ")" PASS2 + echo # \n + if [[ "$PASS" != "$PASS2" ]]; then + echo -e "${BOLD}${RED}Sorry, passwords do not match${RESET}" >&2 + exit 1 + fi + unset -v PASS2 +fi + +# Compute hash of password +RESULT=$(mkpasswd -sm "$TYPE" -R "$ROUNDS" $EXTRA <<<"$PASS") +unset -v PASS +# Format as JSON if necessary +if [[ "$JSON" == true ]]; then + RESULT="{ + \"type\": \"${TYPE}\", + \"rounds\": ${ROUNDS}, + \"hash\": \"${RESULT}\" +}" +fi + +# Display hash result +if [[ -n "$OUT" ]]; then + echo "$RESULT" > "$OUT" +elif [[ "$JSON" == true ]]; then + echo "$RESULT" +else + echo -e "${BOLD}${GREEN}Hash:${WHITE} $RESULT${RESET}" +fi +unset -v RESULT + +unset -v TYPE ROUNDS SALTED OUT JSON STDIN EXTRA From f9d0b56f97f65cb8b1a84a078192f9142ec0d1d1 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Wed, 21 Jan 2026 15:35:11 +1000 Subject: [PATCH 195/338] introduce isnumeric function --- ceru/libceru.sh | 2 ++ ceru/subcmds/new/password | 4 +--- ceru/subcmds/new/ssh-key | 7 +++++++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/ceru/libceru.sh b/ceru/libceru.sh index 24dca94..05e945d 100755 --- a/ceru/libceru.sh +++ b/ceru/libceru.sh @@ -100,6 +100,8 @@ function confirm-file-overwrite { [[ "$OVERWRITE" = false ]] || confirm } +function isnumeric { [[ "$1" =~ ^[0-9]+$ ]]; } + # ====== Core ====== function run-subcmd { # if CMD_MIN is empty, then CMD_MAJ is the root cmd (ie ceru) diff --git a/ceru/subcmds/new/password b/ceru/subcmds/new/password index ae640e7..12cc871 100755 --- a/ceru/subcmds/new/password +++ b/ceru/subcmds/new/password @@ -214,11 +214,9 @@ esac unset -f perr-unsupportedhash perr-forbiddenhash perr-recommendhash # ensure $ROUNDS is a valid numeric -re='^[0-9]+$' -if ! [[ "$ROUNDS" =~ ^[0-9]+$ ]] ; then +if ! isnumeric "$ROUNDS"; then throw-badval 1 "$ROUNDS" '-r|--rounds' fi -unset -v re # Acquire password from stdin if [[ "$STDIN" == true ]]; then diff --git a/ceru/subcmds/new/ssh-key b/ceru/subcmds/new/ssh-key index 7e8605b..eeda49d 100755 --- a/ceru/subcmds/new/ssh-key +++ b/ceru/subcmds/new/ssh-key @@ -81,6 +81,10 @@ while [[ $# -gt 0 ]]; do done; unset -v ARG EXTRA='' +# ensure $ROUNDS is a valid numeric +if ! isnumeric "$ROUNDS"; then + throw-badval 1 "$ROUNDS" '-r|--rounds' +fi case "$TYPE" in ed25519) @@ -93,6 +97,9 @@ case "$TYPE" in ;; esac +if ! isnumeric "$BITS"; then + throw-badval 1 "$BITS" '-b|--bits' +fi if [[ "$HWKEY" == true ]]; then if [[ "$TYPE" == "rsa" ]]; then From 1989d28cc2e2de680ec57ec1b20a1cfc04f8881e Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Wed, 21 Jan 2026 15:35:11 +1000 Subject: [PATCH 196/338] introduce isnumeric function --- ceru/libceru.sh | 2 ++ ceru/subcmds/new/password | 4 +--- ceru/subcmds/new/ssh-key | 7 +++++++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/ceru/libceru.sh b/ceru/libceru.sh index 24dca94..05e945d 100755 --- a/ceru/libceru.sh +++ b/ceru/libceru.sh @@ -100,6 +100,8 @@ function confirm-file-overwrite { [[ "$OVERWRITE" = false ]] || confirm } +function isnumeric { [[ "$1" =~ ^[0-9]+$ ]]; } + # ====== Core ====== function run-subcmd { # if CMD_MIN is empty, then CMD_MAJ is the root cmd (ie ceru) diff --git a/ceru/subcmds/new/password b/ceru/subcmds/new/password index ae640e7..12cc871 100755 --- a/ceru/subcmds/new/password +++ b/ceru/subcmds/new/password @@ -214,11 +214,9 @@ esac unset -f perr-unsupportedhash perr-forbiddenhash perr-recommendhash # ensure $ROUNDS is a valid numeric -re='^[0-9]+$' -if ! [[ "$ROUNDS" =~ ^[0-9]+$ ]] ; then +if ! isnumeric "$ROUNDS"; then throw-badval 1 "$ROUNDS" '-r|--rounds' fi -unset -v re # Acquire password from stdin if [[ "$STDIN" == true ]]; then diff --git a/ceru/subcmds/new/ssh-key b/ceru/subcmds/new/ssh-key index 7e8605b..eeda49d 100755 --- a/ceru/subcmds/new/ssh-key +++ b/ceru/subcmds/new/ssh-key @@ -81,6 +81,10 @@ while [[ $# -gt 0 ]]; do done; unset -v ARG EXTRA='' +# ensure $ROUNDS is a valid numeric +if ! isnumeric "$ROUNDS"; then + throw-badval 1 "$ROUNDS" '-r|--rounds' +fi case "$TYPE" in ed25519) @@ -93,6 +97,9 @@ case "$TYPE" in ;; esac +if ! isnumeric "$BITS"; then + throw-badval 1 "$BITS" '-b|--bits' +fi if [[ "$HWKEY" == true ]]; then if [[ "$TYPE" == "rsa" ]]; then From 8004efa6064464048d0526bd39deac51a1647ed8 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Wed, 21 Jan 2026 15:36:42 +1000 Subject: [PATCH 197/338] add -b|--bits flag to ssh-key --- ceru/subcmds/new/ssh-key | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/ceru/subcmds/new/ssh-key b/ceru/subcmds/new/ssh-key index eeda49d..be1ae85 100755 --- a/ceru/subcmds/new/ssh-key +++ b/ceru/subcmds/new/ssh-key @@ -34,6 +34,7 @@ ${BOLD}${UNDERLINE}${RED}Options${RESET} # ==== Argument Values ==== TYPE='rsa' ROUNDS='100' +BITS='' COMMENT='' OUT='' NOPASSWD=false @@ -63,6 +64,10 @@ while [[ $# -gt 0 ]]; do shift ROUNDS="$1"; shift ;; + -b|--bits) + shift + BITS="$1"; shift + ;; -N|--nopasswd) shift NOPASSWD=true @@ -88,9 +93,29 @@ fi case "$TYPE" in ed25519) + # NOTE: the value of BITS does not matter for Ed25519 + # NOTE: as it operates on a fixed size elliptic curve + if [[ -n "$BITS" ]]; then + BITS='256' + fi ;; rsa) - EXTRA="$EXTRA -b 4096" + if [[ -n "$BITS" ]]; then + BITS='4096' + else + case "$BITS" in + 2048) + echo -e "${BOLD}${UNDERLINE}${YELLOW}WARNING${RESET}${BOLD}: Although ${MAGENTA}2048-bit RSA keys${YELLOW} are considered secure,${RESET}" >&2 + echo -e "${BOLD}${UNDERLINE}${YELLOW}WARNING${RESET}${BOLD}: it is the growing opinion that these will not be soon.${RESET}" >&2 + echo -e "${BOLD}${UNDERLINE}${YELLOW}WARNING${RESET}${BOLD}: ${GREEN}Consider using a minimum of ${MAGENTA}3072-bit${YELLOW}, or ideally ${MAGENTA}4096-bit.${RESET}" >&2 + ;; + 3072|4096|8192) true + ;; + *) + throw-badval 1 "$BITS" '-b|--bits' + ;; + esac + fi ;; *) throw-badval 1 "$TYPE" '-t|--type' From e5e27858b3fe9eb1897b644c23b1313f29969538 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Wed, 21 Jan 2026 15:36:42 +1000 Subject: [PATCH 198/338] add -b|--bits flag to ssh-key --- ceru/subcmds/new/ssh-key | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/ceru/subcmds/new/ssh-key b/ceru/subcmds/new/ssh-key index eeda49d..be1ae85 100755 --- a/ceru/subcmds/new/ssh-key +++ b/ceru/subcmds/new/ssh-key @@ -34,6 +34,7 @@ ${BOLD}${UNDERLINE}${RED}Options${RESET} # ==== Argument Values ==== TYPE='rsa' ROUNDS='100' +BITS='' COMMENT='' OUT='' NOPASSWD=false @@ -63,6 +64,10 @@ while [[ $# -gt 0 ]]; do shift ROUNDS="$1"; shift ;; + -b|--bits) + shift + BITS="$1"; shift + ;; -N|--nopasswd) shift NOPASSWD=true @@ -88,9 +93,29 @@ fi case "$TYPE" in ed25519) + # NOTE: the value of BITS does not matter for Ed25519 + # NOTE: as it operates on a fixed size elliptic curve + if [[ -n "$BITS" ]]; then + BITS='256' + fi ;; rsa) - EXTRA="$EXTRA -b 4096" + if [[ -n "$BITS" ]]; then + BITS='4096' + else + case "$BITS" in + 2048) + echo -e "${BOLD}${UNDERLINE}${YELLOW}WARNING${RESET}${BOLD}: Although ${MAGENTA}2048-bit RSA keys${YELLOW} are considered secure,${RESET}" >&2 + echo -e "${BOLD}${UNDERLINE}${YELLOW}WARNING${RESET}${BOLD}: it is the growing opinion that these will not be soon.${RESET}" >&2 + echo -e "${BOLD}${UNDERLINE}${YELLOW}WARNING${RESET}${BOLD}: ${GREEN}Consider using a minimum of ${MAGENTA}3072-bit${YELLOW}, or ideally ${MAGENTA}4096-bit.${RESET}" >&2 + ;; + 3072|4096|8192) true + ;; + *) + throw-badval 1 "$BITS" '-b|--bits' + ;; + esac + fi ;; *) throw-badval 1 "$TYPE" '-t|--type' From 4ae45d312a1db9cc64a03070a3fec445a19fde2b Mon Sep 17 00:00:00 2001 From: _cry64 Date: Wed, 21 Jan 2026 15:37:39 +1000 Subject: [PATCH 199/338] remove EXTRA variable --- ceru/subcmds/new/ssh-key | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/ceru/subcmds/new/ssh-key b/ceru/subcmds/new/ssh-key index be1ae85..5eabaca 100755 --- a/ceru/subcmds/new/ssh-key +++ b/ceru/subcmds/new/ssh-key @@ -37,7 +37,7 @@ ROUNDS='100' BITS='' COMMENT='' OUT='' -NOPASSWD=false +NOPASSWD='' HWKEY=false # ==== Argument Values ==== @@ -70,7 +70,7 @@ while [[ $# -gt 0 ]]; do ;; -N|--nopasswd) shift - NOPASSWD=true + NOPASSWD="-N ''" ;; -H|--hardware-key) shift @@ -85,7 +85,6 @@ while [[ $# -gt 0 ]]; do esac done; unset -v ARG -EXTRA='' # ensure $ROUNDS is a valid numeric if ! isnumeric "$ROUNDS"; then throw-badval 1 "$ROUNDS" '-r|--rounds' @@ -134,28 +133,18 @@ if [[ "$HWKEY" == true ]]; then TYPE="$TYPE-sk" fi -if [[ -n "$COMMENT" ]]; then - EXTRA="$EXTRA -C '$COMMENT'" -fi - -# BUG: WARNING: TODO: $OUT permits arbitrary command injection -if [[ -n "$OUT" ]]; then - EXTRA="$EXTRA -f $OUT" -else +if [[ -z "$OUT" ]]; then # fallback to ssh-keygen's default file (for chmod later) OUT="~/.ssh/id_$TYPE" fi -if [[ "$NOPASSWD" == true ]]; then - EXTRA="$EXTRA -N ''" -fi # permit error during key generation set +e -echo -e "${BOLD}${GREEN}[+] ssh-keygen -t $TYPE -a '$ROUNDS' $EXTRA${RESET}" -ssh-keygen -t $TYPE -a "$ROUNDS" $EXTRA +echo -e "${BOLD}${GREEN}[+] ssh-keygen -t $TYPE -a$ROUNDS -b$BITS -C '$COMMENT' -f '$OUT' $NOPASSWD${RESET}" +ssh-keygen -t $TYPE -a "$ROUNDS" -b "$BITS" -C "$COMMENT" -f "$OUT" $NOPASSWD chmod 600 $OUT chmod 644 $OUT.pub - # reset state set -e -unset TYPE ROUNDS COMMENT OUT NOPASSWD HWKEY EXTRA + +unset TYPE ROUNDS BITS COMMENT OUT NOPASSWD HWKEY From b241bc1bffa8136cff98d736db339cf354d41f90 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Wed, 21 Jan 2026 15:37:39 +1000 Subject: [PATCH 200/338] remove EXTRA variable --- ceru/subcmds/new/ssh-key | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/ceru/subcmds/new/ssh-key b/ceru/subcmds/new/ssh-key index be1ae85..5eabaca 100755 --- a/ceru/subcmds/new/ssh-key +++ b/ceru/subcmds/new/ssh-key @@ -37,7 +37,7 @@ ROUNDS='100' BITS='' COMMENT='' OUT='' -NOPASSWD=false +NOPASSWD='' HWKEY=false # ==== Argument Values ==== @@ -70,7 +70,7 @@ while [[ $# -gt 0 ]]; do ;; -N|--nopasswd) shift - NOPASSWD=true + NOPASSWD="-N ''" ;; -H|--hardware-key) shift @@ -85,7 +85,6 @@ while [[ $# -gt 0 ]]; do esac done; unset -v ARG -EXTRA='' # ensure $ROUNDS is a valid numeric if ! isnumeric "$ROUNDS"; then throw-badval 1 "$ROUNDS" '-r|--rounds' @@ -134,28 +133,18 @@ if [[ "$HWKEY" == true ]]; then TYPE="$TYPE-sk" fi -if [[ -n "$COMMENT" ]]; then - EXTRA="$EXTRA -C '$COMMENT'" -fi - -# BUG: WARNING: TODO: $OUT permits arbitrary command injection -if [[ -n "$OUT" ]]; then - EXTRA="$EXTRA -f $OUT" -else +if [[ -z "$OUT" ]]; then # fallback to ssh-keygen's default file (for chmod later) OUT="~/.ssh/id_$TYPE" fi -if [[ "$NOPASSWD" == true ]]; then - EXTRA="$EXTRA -N ''" -fi # permit error during key generation set +e -echo -e "${BOLD}${GREEN}[+] ssh-keygen -t $TYPE -a '$ROUNDS' $EXTRA${RESET}" -ssh-keygen -t $TYPE -a "$ROUNDS" $EXTRA +echo -e "${BOLD}${GREEN}[+] ssh-keygen -t $TYPE -a$ROUNDS -b$BITS -C '$COMMENT' -f '$OUT' $NOPASSWD${RESET}" +ssh-keygen -t $TYPE -a "$ROUNDS" -b "$BITS" -C "$COMMENT" -f "$OUT" $NOPASSWD chmod 600 $OUT chmod 644 $OUT.pub - # reset state set -e -unset TYPE ROUNDS COMMENT OUT NOPASSWD HWKEY EXTRA + +unset TYPE ROUNDS BITS COMMENT OUT NOPASSWD HWKEY From 2740822c2c6ffe54eec15f0ad5d38df6e48c999f Mon Sep 17 00:00:00 2001 From: _cry64 Date: Wed, 21 Jan 2026 15:38:14 +1000 Subject: [PATCH 201/338] add missing -b|--bits doc --- ceru/subcmds/new/ssh-key | 1 + 1 file changed, 1 insertion(+) diff --git a/ceru/subcmds/new/ssh-key b/ceru/subcmds/new/ssh-key index 5eabaca..765b2a5 100755 --- a/ceru/subcmds/new/ssh-key +++ b/ceru/subcmds/new/ssh-key @@ -28,6 +28,7 @@ ${BOLD}${UNDERLINE}${RED}Options${RESET} ${BOLD}${MAGENTA}-c, --comment${RESET} A comment or email address to write on the key ${BOLD}${MAGENTA}-t, --type${RESET} The cryptographic algorithm to use: ${BOLD}${MAGENTA}ed25519${RESET} or ${BOLD}${MAGENTA}rsa${RESET} ${BOLD}${CYAN}(default: rsa)${RESET} ${BOLD}${MAGENTA}-r, --rounds${RESET} The number of key derivation function rounds to apply ${BOLD}${CYAN}(default: 100)${RESET} + ${BOLD}${MAGENTA}-b, --bits${RESET} The key size in bits ${BOLD}${MAGENTA}(see the \"Key Sizes\" section above) ${CYAN}(defaults: ecdsa=521, rsa=4096, ed25519=NOT-APPLICABLE)${RESET} ${BOLD}${MAGENTA}-N, --nopasswd${RESET} Do not encrypt the private key with a password ${BOLD}${MAGENTA}-H, --hardware-key${RESET} Enable the use of a secure hardware key peripheral device (ie YubiKey)" From fc6af7e8406f1d3e7384108f4b92dec6f103236a Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Wed, 21 Jan 2026 15:38:14 +1000 Subject: [PATCH 202/338] add missing -b|--bits doc --- ceru/subcmds/new/ssh-key | 1 + 1 file changed, 1 insertion(+) diff --git a/ceru/subcmds/new/ssh-key b/ceru/subcmds/new/ssh-key index 5eabaca..765b2a5 100755 --- a/ceru/subcmds/new/ssh-key +++ b/ceru/subcmds/new/ssh-key @@ -28,6 +28,7 @@ ${BOLD}${UNDERLINE}${RED}Options${RESET} ${BOLD}${MAGENTA}-c, --comment${RESET} A comment or email address to write on the key ${BOLD}${MAGENTA}-t, --type${RESET} The cryptographic algorithm to use: ${BOLD}${MAGENTA}ed25519${RESET} or ${BOLD}${MAGENTA}rsa${RESET} ${BOLD}${CYAN}(default: rsa)${RESET} ${BOLD}${MAGENTA}-r, --rounds${RESET} The number of key derivation function rounds to apply ${BOLD}${CYAN}(default: 100)${RESET} + ${BOLD}${MAGENTA}-b, --bits${RESET} The key size in bits ${BOLD}${MAGENTA}(see the \"Key Sizes\" section above) ${CYAN}(defaults: ecdsa=521, rsa=4096, ed25519=NOT-APPLICABLE)${RESET} ${BOLD}${MAGENTA}-N, --nopasswd${RESET} Do not encrypt the private key with a password ${BOLD}${MAGENTA}-H, --hardware-key${RESET} Enable the use of a secure hardware key peripheral device (ie YubiKey)" From 094f74f9c2d41911f45d90fab93ccc480f01dc1d Mon Sep 17 00:00:00 2001 From: _cry64 Date: Wed, 21 Jan 2026 15:38:50 +1000 Subject: [PATCH 203/338] support ECDSA --- ceru/subcmds/new/ssh-key | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/ceru/subcmds/new/ssh-key b/ceru/subcmds/new/ssh-key index 765b2a5..0f615f9 100755 --- a/ceru/subcmds/new/ssh-key +++ b/ceru/subcmds/new/ssh-key @@ -22,12 +22,18 @@ ${BOLD}${UNDERLINE}${RED}Description${RESET} Generates a new SSH keypair with secure defaults. For more advanced usage run the ${BOLD}${MAGENTA}\`ssh-keygen\`${RESET} utility directly. +${BOLD}${UNDERLINE}${RED}Key Sizes${RESET} + Key sizes are specified in bits via the ${BOLD}${MAGENTA}-b|--bits${RESET} flag. + • ${BOLD}${CYAN}ECDSA keys${RESET} can only operate on elliptic curves of size: ${BOLD}${CYAN}256, 384, or 521 bits${RESET} + • ${BOLD}${CYAN}Ed25519 keys${RESET} have a ${BOLD}${MAGENTA}fixed length${RESET} so the key size is ignored + • ${BOLD}${CYAN}RSA keys${RESET} have been intentionally restricted for Cerulean to: ${BOLD}${CYAN}2048, 3072, 4096, or 8192 bits${RESET} + ${BOLD}${UNDERLINE}${RED}Options${RESET} ${BOLD}${MAGENTA}-h, --help${RESET} Show this message (^_^) ${BOLD}${MAGENTA}-o, --out${RESET} Private key file name to write to (the public key is named identically but ends with ${BOLD}${MAGENTA}.pub${RESET}) ${BOLD}${MAGENTA}-c, --comment${RESET} A comment or email address to write on the key - ${BOLD}${MAGENTA}-t, --type${RESET} The cryptographic algorithm to use: ${BOLD}${MAGENTA}ed25519${RESET} or ${BOLD}${MAGENTA}rsa${RESET} ${BOLD}${CYAN}(default: rsa)${RESET} - ${BOLD}${MAGENTA}-r, --rounds${RESET} The number of key derivation function rounds to apply ${BOLD}${CYAN}(default: 100)${RESET} + ${BOLD}${MAGENTA}-t, --type${RESET} The cryptographic algorithm to use: ${BOLD}${MAGENTA}ecdsa, ed25519, rsa${RESET} ${BOLD}${CYAN}(default: ecdsa)${RESET} + ${BOLD}${MAGENTA}-r, --rounds${RESET} The number of KDF rounds to apply ${BOLD}${CYAN}(default: 100)${RESET} ${BOLD}${MAGENTA}-b, --bits${RESET} The key size in bits ${BOLD}${MAGENTA}(see the \"Key Sizes\" section above) ${CYAN}(defaults: ecdsa=521, rsa=4096, ed25519=NOT-APPLICABLE)${RESET} ${BOLD}${MAGENTA}-N, --nopasswd${RESET} Do not encrypt the private key with a password ${BOLD}${MAGENTA}-H, --hardware-key${RESET} Enable the use of a secure hardware key peripheral device (ie YubiKey)" @@ -92,6 +98,21 @@ if ! isnumeric "$ROUNDS"; then fi case "$TYPE" in + ecdsa) + if [[ -n "$BITS" ]]; then + BITS='521' + else + # NOTE: ECDSA keys can only operate on elliptic curves + # NOTE: of sizes: 256, 384 or 521 bits + case "$BITS" in + 256|384|512) true + ;; + *) + throw-badval 1 "$BITS" '-b|--bits' + ;; + esac + fi + ;; ed25519) # NOTE: the value of BITS does not matter for Ed25519 # NOTE: as it operates on a fixed size elliptic curve @@ -128,7 +149,7 @@ fi if [[ "$HWKEY" == true ]]; then if [[ "$TYPE" == "rsa" ]]; then - echo -e "${BOLD}${MAGENTA}-H|--hardware-key${RESET} flag is not valid for ${BOLD}${MAGE}rsa${RESET} keys ${BOLD}${CYAN}(use ed25519 instead)${RESET}" + echo -e "${BOLD}${MAGENTA}-H|--hardware-key${RESET} flag is not valid for ${BOLD}${MAGENTA}RSA keys${RESET} ${BOLD}${CYAN}(use Ed25519 instead)${RESET}" exit 1 fi TYPE="$TYPE-sk" From 645c1ac958b388c98ee4be58947d3408f9019946 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Wed, 21 Jan 2026 15:38:50 +1000 Subject: [PATCH 204/338] support ECDSA --- ceru/subcmds/new/ssh-key | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/ceru/subcmds/new/ssh-key b/ceru/subcmds/new/ssh-key index 765b2a5..0f615f9 100755 --- a/ceru/subcmds/new/ssh-key +++ b/ceru/subcmds/new/ssh-key @@ -22,12 +22,18 @@ ${BOLD}${UNDERLINE}${RED}Description${RESET} Generates a new SSH keypair with secure defaults. For more advanced usage run the ${BOLD}${MAGENTA}\`ssh-keygen\`${RESET} utility directly. +${BOLD}${UNDERLINE}${RED}Key Sizes${RESET} + Key sizes are specified in bits via the ${BOLD}${MAGENTA}-b|--bits${RESET} flag. + • ${BOLD}${CYAN}ECDSA keys${RESET} can only operate on elliptic curves of size: ${BOLD}${CYAN}256, 384, or 521 bits${RESET} + • ${BOLD}${CYAN}Ed25519 keys${RESET} have a ${BOLD}${MAGENTA}fixed length${RESET} so the key size is ignored + • ${BOLD}${CYAN}RSA keys${RESET} have been intentionally restricted for Cerulean to: ${BOLD}${CYAN}2048, 3072, 4096, or 8192 bits${RESET} + ${BOLD}${UNDERLINE}${RED}Options${RESET} ${BOLD}${MAGENTA}-h, --help${RESET} Show this message (^_^) ${BOLD}${MAGENTA}-o, --out${RESET} Private key file name to write to (the public key is named identically but ends with ${BOLD}${MAGENTA}.pub${RESET}) ${BOLD}${MAGENTA}-c, --comment${RESET} A comment or email address to write on the key - ${BOLD}${MAGENTA}-t, --type${RESET} The cryptographic algorithm to use: ${BOLD}${MAGENTA}ed25519${RESET} or ${BOLD}${MAGENTA}rsa${RESET} ${BOLD}${CYAN}(default: rsa)${RESET} - ${BOLD}${MAGENTA}-r, --rounds${RESET} The number of key derivation function rounds to apply ${BOLD}${CYAN}(default: 100)${RESET} + ${BOLD}${MAGENTA}-t, --type${RESET} The cryptographic algorithm to use: ${BOLD}${MAGENTA}ecdsa, ed25519, rsa${RESET} ${BOLD}${CYAN}(default: ecdsa)${RESET} + ${BOLD}${MAGENTA}-r, --rounds${RESET} The number of KDF rounds to apply ${BOLD}${CYAN}(default: 100)${RESET} ${BOLD}${MAGENTA}-b, --bits${RESET} The key size in bits ${BOLD}${MAGENTA}(see the \"Key Sizes\" section above) ${CYAN}(defaults: ecdsa=521, rsa=4096, ed25519=NOT-APPLICABLE)${RESET} ${BOLD}${MAGENTA}-N, --nopasswd${RESET} Do not encrypt the private key with a password ${BOLD}${MAGENTA}-H, --hardware-key${RESET} Enable the use of a secure hardware key peripheral device (ie YubiKey)" @@ -92,6 +98,21 @@ if ! isnumeric "$ROUNDS"; then fi case "$TYPE" in + ecdsa) + if [[ -n "$BITS" ]]; then + BITS='521' + else + # NOTE: ECDSA keys can only operate on elliptic curves + # NOTE: of sizes: 256, 384 or 521 bits + case "$BITS" in + 256|384|512) true + ;; + *) + throw-badval 1 "$BITS" '-b|--bits' + ;; + esac + fi + ;; ed25519) # NOTE: the value of BITS does not matter for Ed25519 # NOTE: as it operates on a fixed size elliptic curve @@ -128,7 +149,7 @@ fi if [[ "$HWKEY" == true ]]; then if [[ "$TYPE" == "rsa" ]]; then - echo -e "${BOLD}${MAGENTA}-H|--hardware-key${RESET} flag is not valid for ${BOLD}${MAGE}rsa${RESET} keys ${BOLD}${CYAN}(use ed25519 instead)${RESET}" + echo -e "${BOLD}${MAGENTA}-H|--hardware-key${RESET} flag is not valid for ${BOLD}${MAGENTA}RSA keys${RESET} ${BOLD}${CYAN}(use Ed25519 instead)${RESET}" exit 1 fi TYPE="$TYPE-sk" From 1e7567ec2b708d3a48803679d1694db8c2774caf Mon Sep 17 00:00:00 2001 From: _cry64 Date: Wed, 21 Jan 2026 15:39:15 +1000 Subject: [PATCH 205/338] use KDF acronym to shorten text --- ceru/subcmds/new/password | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ceru/subcmds/new/password b/ceru/subcmds/new/password index 12cc871..1b18eb7 100755 --- a/ceru/subcmds/new/password +++ b/ceru/subcmds/new/password @@ -28,7 +28,7 @@ ${BOLD}${UNDERLINE}${RED}Options${RESET} ${BOLD}${MAGENTA}-j, --json${RESET} Output in JSON format ${BOLD}${MAGENTA}-i, --stdin${RESET} Read the password to hash from stdin ${BOLD}$CYAN(single line only)${RESET} ${BOLD}${MAGENTA}-t, --type${RESET} The hash algorithm to use: ${BOLD}${MAGENTA}yescrypt, scrypt, bcrypt ${CYAN}(default: yescrypt)${RESET} - ${BOLD}${MAGENTA}-r, --rounds${RESET} The number of key derivation function rounds to apply ${BOLD}${MAGENTA}(format: ^[0-9]+\$) ${CYAN}(defaults: yescrypt=11, scrypt=10, bcrypt=14)${RESET} + ${BOLD}${MAGENTA}-r, --rounds${RESET} The number of KDF to apply ${BOLD}${MAGENTA}(format: ^[0-9]+\$) ${CYAN}(defaults: yescrypt=11, scrypt=10, bcrypt=14)${RESET} ${BOLD}${MAGENTA}-s, --salt${RESET} Specify the hash's salt directly ${BOLD}${RED}(not recommended)${RESET} ${BOLD}${CYAN}(default: libxcrypt secure default)${RESET}" # ==== Argument Values ==== From 33aaf5617817f132ed39cba12878e260952986a5 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Wed, 21 Jan 2026 15:39:15 +1000 Subject: [PATCH 206/338] use KDF acronym to shorten text --- ceru/subcmds/new/password | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ceru/subcmds/new/password b/ceru/subcmds/new/password index 12cc871..1b18eb7 100755 --- a/ceru/subcmds/new/password +++ b/ceru/subcmds/new/password @@ -28,7 +28,7 @@ ${BOLD}${UNDERLINE}${RED}Options${RESET} ${BOLD}${MAGENTA}-j, --json${RESET} Output in JSON format ${BOLD}${MAGENTA}-i, --stdin${RESET} Read the password to hash from stdin ${BOLD}$CYAN(single line only)${RESET} ${BOLD}${MAGENTA}-t, --type${RESET} The hash algorithm to use: ${BOLD}${MAGENTA}yescrypt, scrypt, bcrypt ${CYAN}(default: yescrypt)${RESET} - ${BOLD}${MAGENTA}-r, --rounds${RESET} The number of key derivation function rounds to apply ${BOLD}${MAGENTA}(format: ^[0-9]+\$) ${CYAN}(defaults: yescrypt=11, scrypt=10, bcrypt=14)${RESET} + ${BOLD}${MAGENTA}-r, --rounds${RESET} The number of KDF to apply ${BOLD}${MAGENTA}(format: ^[0-9]+\$) ${CYAN}(defaults: yescrypt=11, scrypt=10, bcrypt=14)${RESET} ${BOLD}${MAGENTA}-s, --salt${RESET} Specify the hash's salt directly ${BOLD}${RED}(not recommended)${RESET} ${BOLD}${CYAN}(default: libxcrypt secure default)${RESET}" # ==== Argument Values ==== From bf3c7040761137551557c8099fe30b055096daee Mon Sep 17 00:00:00 2001 From: _cry64 Date: Wed, 21 Jan 2026 15:48:28 +1000 Subject: [PATCH 207/338] shorten -b|--bits ssh-key doc --- ceru/subcmds/new/ssh-key | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ceru/subcmds/new/ssh-key b/ceru/subcmds/new/ssh-key index 0f615f9..cf0d9fb 100755 --- a/ceru/subcmds/new/ssh-key +++ b/ceru/subcmds/new/ssh-key @@ -34,7 +34,7 @@ ${BOLD}${UNDERLINE}${RED}Options${RESET} ${BOLD}${MAGENTA}-c, --comment${RESET} A comment or email address to write on the key ${BOLD}${MAGENTA}-t, --type${RESET} The cryptographic algorithm to use: ${BOLD}${MAGENTA}ecdsa, ed25519, rsa${RESET} ${BOLD}${CYAN}(default: ecdsa)${RESET} ${BOLD}${MAGENTA}-r, --rounds${RESET} The number of KDF rounds to apply ${BOLD}${CYAN}(default: 100)${RESET} - ${BOLD}${MAGENTA}-b, --bits${RESET} The key size in bits ${BOLD}${MAGENTA}(see the \"Key Sizes\" section above) ${CYAN}(defaults: ecdsa=521, rsa=4096, ed25519=NOT-APPLICABLE)${RESET} + ${BOLD}${MAGENTA}-b, --bits${RESET} The key size in bits ${BOLD}${MAGENTA}(see \"Key Sizes\" above) ${CYAN}(defaults: ecdsa=521, rsa=4096, ed25519=NULL)${RESET} ${BOLD}${MAGENTA}-N, --nopasswd${RESET} Do not encrypt the private key with a password ${BOLD}${MAGENTA}-H, --hardware-key${RESET} Enable the use of a secure hardware key peripheral device (ie YubiKey)" From 9106cd72849d998e53de54b6a096df87cffef392 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Wed, 21 Jan 2026 15:48:28 +1000 Subject: [PATCH 208/338] shorten -b|--bits ssh-key doc --- ceru/subcmds/new/ssh-key | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ceru/subcmds/new/ssh-key b/ceru/subcmds/new/ssh-key index 0f615f9..cf0d9fb 100755 --- a/ceru/subcmds/new/ssh-key +++ b/ceru/subcmds/new/ssh-key @@ -34,7 +34,7 @@ ${BOLD}${UNDERLINE}${RED}Options${RESET} ${BOLD}${MAGENTA}-c, --comment${RESET} A comment or email address to write on the key ${BOLD}${MAGENTA}-t, --type${RESET} The cryptographic algorithm to use: ${BOLD}${MAGENTA}ecdsa, ed25519, rsa${RESET} ${BOLD}${CYAN}(default: ecdsa)${RESET} ${BOLD}${MAGENTA}-r, --rounds${RESET} The number of KDF rounds to apply ${BOLD}${CYAN}(default: 100)${RESET} - ${BOLD}${MAGENTA}-b, --bits${RESET} The key size in bits ${BOLD}${MAGENTA}(see the \"Key Sizes\" section above) ${CYAN}(defaults: ecdsa=521, rsa=4096, ed25519=NOT-APPLICABLE)${RESET} + ${BOLD}${MAGENTA}-b, --bits${RESET} The key size in bits ${BOLD}${MAGENTA}(see \"Key Sizes\" above) ${CYAN}(defaults: ecdsa=521, rsa=4096, ed25519=NULL)${RESET} ${BOLD}${MAGENTA}-N, --nopasswd${RESET} Do not encrypt the private key with a password ${BOLD}${MAGENTA}-H, --hardware-key${RESET} Enable the use of a secure hardware key peripheral device (ie YubiKey)" From 0fa8372a34f61796e2e71ffab43189343e533fcc Mon Sep 17 00:00:00 2001 From: _cry64 Date: Wed, 21 Jan 2026 15:57:35 +1000 Subject: [PATCH 209/338] add \n after USAGE --- ceru/libceru.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ceru/libceru.sh b/ceru/libceru.sh index 05e945d..95aba51 100755 --- a/ceru/libceru.sh +++ b/ceru/libceru.sh @@ -50,7 +50,7 @@ INVISIBLE='\033[8m' # Error Messages function perr { echo -e "${BOLD}${RED}error:${WHITE} $@\n${BOLD}${GREEN}[+]${WHITE} Try ${GREEN}'--help'${WHITE} for more information." >&2; } -function perr-usage { echo -e "$USAGE" >&2; } +function perr-usage { echo -e "$USAGE\n" >&2; } function perr-badflag { perr "unrecognised flag ${BOLD}${MAGENTA}'$1'${RESET}"; } function perr-noflagval { perr "flag ${BOLD}${MAGENTA}'$1'${RESET} requires ${BOLD}${MAGENTA}${2}${RESET} argument(s), but only ${BOLD}${MAGENTA}${3}${RESET} were given"; } function perr-badarg { perr "unrecognised arg ${BOLD}${MAGENTA}'$1'${RESET}"; } @@ -58,7 +58,7 @@ function perr-noarg { perr "required argument ${BOLD}${MAGENTA}'$1'${RESET} function perr-badval { perr "value ${MAGENTA}\"$1\"${WHITE} is not valid for flag ${CYAN}$2${RESET}"; } # Failures function throw { echo -e "${@:2}" >&2; if [[ "$1" -ge 0 ]]; then exit "$1"; fi; } -function throw-usage { throw "$1" "$(perr-usage 2>&1)"; } +function throw-usage { throw "$1" "$(perr-usage 2>&1)\n"; } function throw-badflag { throw "$1" "$(perr-badflag "${@:2}" 2>&1)"; } function throw-noflagval { throw "$1" "$(perr-noflagval "${@:2}" 2>&1)"; } function throw-badarg { throw "$1" "$(perr-badarg "${@:2}" 2>&1)"; } From 20c3060a180753463eb2ff346ffdae7c514a56f8 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Wed, 21 Jan 2026 15:57:35 +1000 Subject: [PATCH 210/338] add \n after USAGE --- ceru/libceru.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ceru/libceru.sh b/ceru/libceru.sh index 05e945d..95aba51 100755 --- a/ceru/libceru.sh +++ b/ceru/libceru.sh @@ -50,7 +50,7 @@ INVISIBLE='\033[8m' # Error Messages function perr { echo -e "${BOLD}${RED}error:${WHITE} $@\n${BOLD}${GREEN}[+]${WHITE} Try ${GREEN}'--help'${WHITE} for more information." >&2; } -function perr-usage { echo -e "$USAGE" >&2; } +function perr-usage { echo -e "$USAGE\n" >&2; } function perr-badflag { perr "unrecognised flag ${BOLD}${MAGENTA}'$1'${RESET}"; } function perr-noflagval { perr "flag ${BOLD}${MAGENTA}'$1'${RESET} requires ${BOLD}${MAGENTA}${2}${RESET} argument(s), but only ${BOLD}${MAGENTA}${3}${RESET} were given"; } function perr-badarg { perr "unrecognised arg ${BOLD}${MAGENTA}'$1'${RESET}"; } @@ -58,7 +58,7 @@ function perr-noarg { perr "required argument ${BOLD}${MAGENTA}'$1'${RESET} function perr-badval { perr "value ${MAGENTA}\"$1\"${WHITE} is not valid for flag ${CYAN}$2${RESET}"; } # Failures function throw { echo -e "${@:2}" >&2; if [[ "$1" -ge 0 ]]; then exit "$1"; fi; } -function throw-usage { throw "$1" "$(perr-usage 2>&1)"; } +function throw-usage { throw "$1" "$(perr-usage 2>&1)\n"; } function throw-badflag { throw "$1" "$(perr-badflag "${@:2}" 2>&1)"; } function throw-noflagval { throw "$1" "$(perr-noflagval "${@:2}" 2>&1)"; } function throw-badarg { throw "$1" "$(perr-badarg "${@:2}" 2>&1)"; } From 8ae7c44e06f31b167b81a07c357d665cb1a11ff8 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Wed, 21 Jan 2026 16:00:07 +1000 Subject: [PATCH 211/338] fix bad test for BITS defaulting --- ceru/subcmds/new/ssh-key | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ceru/subcmds/new/ssh-key b/ceru/subcmds/new/ssh-key index cf0d9fb..740031d 100755 --- a/ceru/subcmds/new/ssh-key +++ b/ceru/subcmds/new/ssh-key @@ -99,7 +99,7 @@ fi case "$TYPE" in ecdsa) - if [[ -n "$BITS" ]]; then + if [[ -z "$BITS" ]]; then BITS='521' else # NOTE: ECDSA keys can only operate on elliptic curves @@ -116,12 +116,12 @@ case "$TYPE" in ed25519) # NOTE: the value of BITS does not matter for Ed25519 # NOTE: as it operates on a fixed size elliptic curve - if [[ -n "$BITS" ]]; then + if [[ -z "$BITS" ]]; then BITS='256' fi ;; rsa) - if [[ -n "$BITS" ]]; then + if [[ -z "$BITS" ]]; then BITS='4096' else case "$BITS" in From fe3cf55aa24b0bc141b9d1e911a7c675183848bd Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Wed, 21 Jan 2026 16:00:07 +1000 Subject: [PATCH 212/338] fix bad test for BITS defaulting --- ceru/subcmds/new/ssh-key | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ceru/subcmds/new/ssh-key b/ceru/subcmds/new/ssh-key index cf0d9fb..740031d 100755 --- a/ceru/subcmds/new/ssh-key +++ b/ceru/subcmds/new/ssh-key @@ -99,7 +99,7 @@ fi case "$TYPE" in ecdsa) - if [[ -n "$BITS" ]]; then + if [[ -z "$BITS" ]]; then BITS='521' else # NOTE: ECDSA keys can only operate on elliptic curves @@ -116,12 +116,12 @@ case "$TYPE" in ed25519) # NOTE: the value of BITS does not matter for Ed25519 # NOTE: as it operates on a fixed size elliptic curve - if [[ -n "$BITS" ]]; then + if [[ -z "$BITS" ]]; then BITS='256' fi ;; rsa) - if [[ -n "$BITS" ]]; then + if [[ -z "$BITS" ]]; then BITS='4096' else case "$BITS" in From ae7bc7306b6709ff3f1b1d01b5cdfbb24821668f Mon Sep 17 00:00:00 2001 From: _cry64 Date: Wed, 21 Jan 2026 16:00:29 +1000 Subject: [PATCH 213/338] fix: rsa used as default (now ecdsa) --- ceru/subcmds/new/ssh-key | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ceru/subcmds/new/ssh-key b/ceru/subcmds/new/ssh-key index 740031d..50078c8 100755 --- a/ceru/subcmds/new/ssh-key +++ b/ceru/subcmds/new/ssh-key @@ -26,7 +26,7 @@ ${BOLD}${UNDERLINE}${RED}Key Sizes${RESET} Key sizes are specified in bits via the ${BOLD}${MAGENTA}-b|--bits${RESET} flag. • ${BOLD}${CYAN}ECDSA keys${RESET} can only operate on elliptic curves of size: ${BOLD}${CYAN}256, 384, or 521 bits${RESET} • ${BOLD}${CYAN}Ed25519 keys${RESET} have a ${BOLD}${MAGENTA}fixed length${RESET} so the key size is ignored - • ${BOLD}${CYAN}RSA keys${RESET} have been intentionally restricted for Cerulean to: ${BOLD}${CYAN}2048, 3072, 4096, or 8192 bits${RESET} + • ${BOLD}${CYAN}RSA keys${RESET} have been intentionally restricted to: ${BOLD}${CYAN}2048, 3072, 4096, or 8192 bits${RESET} ${BOLD}${UNDERLINE}${RED}Options${RESET} ${BOLD}${MAGENTA}-h, --help${RESET} Show this message (^_^) @@ -39,7 +39,7 @@ ${BOLD}${UNDERLINE}${RED}Options${RESET} ${BOLD}${MAGENTA}-H, --hardware-key${RESET} Enable the use of a secure hardware key peripheral device (ie YubiKey)" # ==== Argument Values ==== -TYPE='rsa' +TYPE='ecdsa' ROUNDS='100' BITS='' COMMENT='' From 2a504fcdac8132a7034e19a90e53670699ed6967 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Wed, 21 Jan 2026 16:00:29 +1000 Subject: [PATCH 214/338] fix: rsa used as default (now ecdsa) --- ceru/subcmds/new/ssh-key | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ceru/subcmds/new/ssh-key b/ceru/subcmds/new/ssh-key index 740031d..50078c8 100755 --- a/ceru/subcmds/new/ssh-key +++ b/ceru/subcmds/new/ssh-key @@ -26,7 +26,7 @@ ${BOLD}${UNDERLINE}${RED}Key Sizes${RESET} Key sizes are specified in bits via the ${BOLD}${MAGENTA}-b|--bits${RESET} flag. • ${BOLD}${CYAN}ECDSA keys${RESET} can only operate on elliptic curves of size: ${BOLD}${CYAN}256, 384, or 521 bits${RESET} • ${BOLD}${CYAN}Ed25519 keys${RESET} have a ${BOLD}${MAGENTA}fixed length${RESET} so the key size is ignored - • ${BOLD}${CYAN}RSA keys${RESET} have been intentionally restricted for Cerulean to: ${BOLD}${CYAN}2048, 3072, 4096, or 8192 bits${RESET} + • ${BOLD}${CYAN}RSA keys${RESET} have been intentionally restricted to: ${BOLD}${CYAN}2048, 3072, 4096, or 8192 bits${RESET} ${BOLD}${UNDERLINE}${RED}Options${RESET} ${BOLD}${MAGENTA}-h, --help${RESET} Show this message (^_^) @@ -39,7 +39,7 @@ ${BOLD}${UNDERLINE}${RED}Options${RESET} ${BOLD}${MAGENTA}-H, --hardware-key${RESET} Enable the use of a secure hardware key peripheral device (ie YubiKey)" # ==== Argument Values ==== -TYPE='rsa' +TYPE='ecdsa' ROUNDS='100' BITS='' COMMENT='' From 11f3f31d5d51663987be5fb2bd4fc64eb72149f0 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Wed, 21 Jan 2026 16:05:09 +1000 Subject: [PATCH 215/338] fix prefer $HOME over tilde expansion --- ceru/subcmds/new/ssh-key | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ceru/subcmds/new/ssh-key b/ceru/subcmds/new/ssh-key index 50078c8..974a5f5 100755 --- a/ceru/subcmds/new/ssh-key +++ b/ceru/subcmds/new/ssh-key @@ -157,7 +157,7 @@ fi if [[ -z "$OUT" ]]; then # fallback to ssh-keygen's default file (for chmod later) - OUT="~/.ssh/id_$TYPE" + OUT="$HOME/.ssh/id_$TYPE" fi # permit error during key generation From 3599400b237b491ff2326647277e96183ac4e7aa Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Wed, 21 Jan 2026 16:05:09 +1000 Subject: [PATCH 216/338] fix prefer $HOME over tilde expansion --- ceru/subcmds/new/ssh-key | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ceru/subcmds/new/ssh-key b/ceru/subcmds/new/ssh-key index 50078c8..974a5f5 100755 --- a/ceru/subcmds/new/ssh-key +++ b/ceru/subcmds/new/ssh-key @@ -157,7 +157,7 @@ fi if [[ -z "$OUT" ]]; then # fallback to ssh-keygen's default file (for chmod later) - OUT="~/.ssh/id_$TYPE" + OUT="$HOME/.ssh/id_$TYPE" fi # permit error during key generation From fbdf824475da5892648953a7e2f867dc605418f0 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Wed, 21 Jan 2026 16:20:20 +1000 Subject: [PATCH 217/338] prefer DEFAULT_* over hardcoding --- ceru/subcmds/new/ssh-key | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/ceru/subcmds/new/ssh-key b/ceru/subcmds/new/ssh-key index 974a5f5..7256fc1 100755 --- a/ceru/subcmds/new/ssh-key +++ b/ceru/subcmds/new/ssh-key @@ -15,6 +15,14 @@ set -euo pipefail +# ===== CONFIGURATION ===== +DEFAULT_TYPE='ecdsa' +DEFAULT_ROUNDS='100' +DEFAULT_BITS_ECDSA='521' +DEFAULT_BITS_RSA='4096' +DEFAULT_BITS_ED25519='NULL' +# ===== CONFIGURATION ===== + USAGE="${BOLD}${UNDERLINE}${RED}Usage${RESET} ${BOLD}${GREEN}$THIS new ssh-key [option...]${RESET} @@ -32,15 +40,15 @@ ${BOLD}${UNDERLINE}${RED}Options${RESET} ${BOLD}${MAGENTA}-h, --help${RESET} Show this message (^_^) ${BOLD}${MAGENTA}-o, --out${RESET} Private key file name to write to (the public key is named identically but ends with ${BOLD}${MAGENTA}.pub${RESET}) ${BOLD}${MAGENTA}-c, --comment${RESET} A comment or email address to write on the key - ${BOLD}${MAGENTA}-t, --type${RESET} The cryptographic algorithm to use: ${BOLD}${MAGENTA}ecdsa, ed25519, rsa${RESET} ${BOLD}${CYAN}(default: ecdsa)${RESET} - ${BOLD}${MAGENTA}-r, --rounds${RESET} The number of KDF rounds to apply ${BOLD}${CYAN}(default: 100)${RESET} - ${BOLD}${MAGENTA}-b, --bits${RESET} The key size in bits ${BOLD}${MAGENTA}(see \"Key Sizes\" above) ${CYAN}(defaults: ecdsa=521, rsa=4096, ed25519=NULL)${RESET} + ${BOLD}${MAGENTA}-t, --type${RESET} The cryptographic algorithm to use: ${BOLD}${MAGENTA}ecdsa, ed25519, rsa${RESET} ${BOLD}${CYAN}(default: $DEFAULT_TYPE)${RESET} + ${BOLD}${MAGENTA}-r, --rounds${RESET} The number of KDF rounds to apply ${BOLD}${CYAN}(default: $DEFAULT_ROUNDS)${RESET} + ${BOLD}${MAGENTA}-b, --bits${RESET} The key size in bits ${BOLD}${MAGENTA}(see \"Key Sizes\" above) ${CYAN}(defaults: ecdsa=$DEFAULT_BITS_ECDSA, rsa=$DEFAULT_BITS_RSA, ed25519=$DEFAULT_BITS_ED25519)${RESET} ${BOLD}${MAGENTA}-N, --nopasswd${RESET} Do not encrypt the private key with a password ${BOLD}${MAGENTA}-H, --hardware-key${RESET} Enable the use of a secure hardware key peripheral device (ie YubiKey)" # ==== Argument Values ==== -TYPE='ecdsa' -ROUNDS='100' +TYPE="$DEFAULT_TYPE" +ROUNDS="$DEFAULT_ROUNDS" BITS='' COMMENT='' OUT='' @@ -169,4 +177,5 @@ chmod 644 $OUT.pub # reset state set -e -unset TYPE ROUNDS BITS COMMENT OUT NOPASSWD HWKEY +unset TYPE ROUNDS BITS COMMENT OUT NOPASSWD HWKEY \ + DEFAULT_TYPE DEFAULT_ROUNDS DEFAULT_BITS_ECDSA DEFAULT_BITS_RSA DEFAULT_BITS_ED25519 From ec068cab7a740ccf05212aebbf98d08012296552 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Wed, 21 Jan 2026 16:20:20 +1000 Subject: [PATCH 218/338] prefer DEFAULT_* over hardcoding --- ceru/subcmds/new/ssh-key | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/ceru/subcmds/new/ssh-key b/ceru/subcmds/new/ssh-key index 974a5f5..7256fc1 100755 --- a/ceru/subcmds/new/ssh-key +++ b/ceru/subcmds/new/ssh-key @@ -15,6 +15,14 @@ set -euo pipefail +# ===== CONFIGURATION ===== +DEFAULT_TYPE='ecdsa' +DEFAULT_ROUNDS='100' +DEFAULT_BITS_ECDSA='521' +DEFAULT_BITS_RSA='4096' +DEFAULT_BITS_ED25519='NULL' +# ===== CONFIGURATION ===== + USAGE="${BOLD}${UNDERLINE}${RED}Usage${RESET} ${BOLD}${GREEN}$THIS new ssh-key [option...]${RESET} @@ -32,15 +40,15 @@ ${BOLD}${UNDERLINE}${RED}Options${RESET} ${BOLD}${MAGENTA}-h, --help${RESET} Show this message (^_^) ${BOLD}${MAGENTA}-o, --out${RESET} Private key file name to write to (the public key is named identically but ends with ${BOLD}${MAGENTA}.pub${RESET}) ${BOLD}${MAGENTA}-c, --comment${RESET} A comment or email address to write on the key - ${BOLD}${MAGENTA}-t, --type${RESET} The cryptographic algorithm to use: ${BOLD}${MAGENTA}ecdsa, ed25519, rsa${RESET} ${BOLD}${CYAN}(default: ecdsa)${RESET} - ${BOLD}${MAGENTA}-r, --rounds${RESET} The number of KDF rounds to apply ${BOLD}${CYAN}(default: 100)${RESET} - ${BOLD}${MAGENTA}-b, --bits${RESET} The key size in bits ${BOLD}${MAGENTA}(see \"Key Sizes\" above) ${CYAN}(defaults: ecdsa=521, rsa=4096, ed25519=NULL)${RESET} + ${BOLD}${MAGENTA}-t, --type${RESET} The cryptographic algorithm to use: ${BOLD}${MAGENTA}ecdsa, ed25519, rsa${RESET} ${BOLD}${CYAN}(default: $DEFAULT_TYPE)${RESET} + ${BOLD}${MAGENTA}-r, --rounds${RESET} The number of KDF rounds to apply ${BOLD}${CYAN}(default: $DEFAULT_ROUNDS)${RESET} + ${BOLD}${MAGENTA}-b, --bits${RESET} The key size in bits ${BOLD}${MAGENTA}(see \"Key Sizes\" above) ${CYAN}(defaults: ecdsa=$DEFAULT_BITS_ECDSA, rsa=$DEFAULT_BITS_RSA, ed25519=$DEFAULT_BITS_ED25519)${RESET} ${BOLD}${MAGENTA}-N, --nopasswd${RESET} Do not encrypt the private key with a password ${BOLD}${MAGENTA}-H, --hardware-key${RESET} Enable the use of a secure hardware key peripheral device (ie YubiKey)" # ==== Argument Values ==== -TYPE='ecdsa' -ROUNDS='100' +TYPE="$DEFAULT_TYPE" +ROUNDS="$DEFAULT_ROUNDS" BITS='' COMMENT='' OUT='' @@ -169,4 +177,5 @@ chmod 644 $OUT.pub # reset state set -e -unset TYPE ROUNDS BITS COMMENT OUT NOPASSWD HWKEY +unset TYPE ROUNDS BITS COMMENT OUT NOPASSWD HWKEY \ + DEFAULT_TYPE DEFAULT_ROUNDS DEFAULT_BITS_ECDSA DEFAULT_BITS_RSA DEFAULT_BITS_ED25519 From 37afb36c9787dd92bf904b266e139d8f51a768b6 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Wed, 21 Jan 2026 17:02:20 +1000 Subject: [PATCH 219/338] convert crypto names to lowercase --- ceru/subcmds/new/password | 3 ++- ceru/subcmds/new/ssh-key | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/ceru/subcmds/new/password b/ceru/subcmds/new/password index 1b18eb7..60b983e 100755 --- a/ceru/subcmds/new/password +++ b/ceru/subcmds/new/password @@ -62,7 +62,8 @@ while [[ $# -gt 0 ]]; do ;; -t|--type) shift - TYPE="$1"; shift + # NOTE: ${1,,} is $1 in lowercase + TYPE="${1,,}"; shift ;; -r|--rounds) shift diff --git a/ceru/subcmds/new/ssh-key b/ceru/subcmds/new/ssh-key index 7256fc1..d0aa524 100755 --- a/ceru/subcmds/new/ssh-key +++ b/ceru/subcmds/new/ssh-key @@ -73,7 +73,8 @@ while [[ $# -gt 0 ]]; do ;; -t|--type) shift - TYPE="$1"; shift + # NOTE: ${1,,} is $1 in lowercase + TYPE="${1,,}"; shift ;; -r|--rounds) shift From 40a8280d73c7b26d913baae3ba69738d52312e1c Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Wed, 21 Jan 2026 17:02:20 +1000 Subject: [PATCH 220/338] convert crypto names to lowercase --- ceru/subcmds/new/password | 3 ++- ceru/subcmds/new/ssh-key | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/ceru/subcmds/new/password b/ceru/subcmds/new/password index 1b18eb7..60b983e 100755 --- a/ceru/subcmds/new/password +++ b/ceru/subcmds/new/password @@ -62,7 +62,8 @@ while [[ $# -gt 0 ]]; do ;; -t|--type) shift - TYPE="$1"; shift + # NOTE: ${1,,} is $1 in lowercase + TYPE="${1,,}"; shift ;; -r|--rounds) shift diff --git a/ceru/subcmds/new/ssh-key b/ceru/subcmds/new/ssh-key index 7256fc1..d0aa524 100755 --- a/ceru/subcmds/new/ssh-key +++ b/ceru/subcmds/new/ssh-key @@ -73,7 +73,8 @@ while [[ $# -gt 0 ]]; do ;; -t|--type) shift - TYPE="$1"; shift + # NOTE: ${1,,} is $1 in lowercase + TYPE="${1,,}"; shift ;; -r|--rounds) shift From 8e33eda8535c88012c1c6d106f9b3b009202fa18 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Wed, 21 Jan 2026 17:02:27 +1000 Subject: [PATCH 221/338] fix invisible tabs --- ceru/subcmds/new/password | 54 +++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/ceru/subcmds/new/password b/ceru/subcmds/new/password index 60b983e..d5cd795 100755 --- a/ceru/subcmds/new/password +++ b/ceru/subcmds/new/password @@ -108,7 +108,7 @@ done; unset -v ARG ## nt NT-Hash function perr-unsupportedhash { local ALGO="$1" - echo -e "${BOLD}${CYAN}$THIS${RED} does not support the ${MAGENTA}$ALGO${RED} hashing algorithm${RESET}" >&2 + echo -e "${BOLD}${RED}The ${MAGENTA}$ALGO${RED} hash algorithm is unsupported!${RESET}" >&2 } function perr-forbiddenhash { local ALGO="$1" @@ -142,68 +142,68 @@ case "$TYPE" in gost-yescrypt) perr-unsupportedhash "$TYPE" perr-recommendhash 'yescrypt' - echo -e "┏ -┃ Dear Comrade, + echo -e "${BOLD}${YELLOW}┏ +┃ Dear Comrade, ┃ It is with a heavy heart I must inform you that \"GOST Algorithms ┃ Considered Harmful\" - Edsger Wybe Dijkstra (probably). Alas -┃ GOST is considered broken... It is no longer 1970, please grow up :( -┃ Слава Родине! - Glory to the Motherland! -┗" >&2 +┃ GOST is considered broken... It is no longer 1970, please grow up :( +┃ Слава Родине! - Glory to the Motherland! +┗${RESET}" >&2 exit 1 ;; bcrypt-a) perr-unsupportedhash "$TYPE" perr-recommendhash 'bcrypt' - echo -e "┏ + echo -e "${BOLD}${YELLOW}┏ ┃ The alternative prefix \"\$2y$\" is equivalent to \"\$2b$\". -┃ It exists for historical reasons only. The alternative prefixes -┃ \"\$2a$\" and \"\$2x$\" provide bug-compatibility with -┃ crypt_blowfish 1.0.4 and earlier, which incorrectly processed -┃ characters with the 8th bit set. -┗" >&2 +┃ It exists for historical reasons only. The alternative prefixes +┃ \"\$2a$\" and \"\$2x$\" provide bug-compatibility with +┃ crypt_blowfish 1.0.4 and earlier, which incorrectly processed +┃ characters with the 8th bit set. +┗${RESET}" >&2 exit 1 ;; sha512crypt|sha256crypt) perr-unsupportedhash "$TYPE" perr-forbiddenhash 'SHA' - echo -e "┏ + echo -e "${BOLD}${YELLOW}┏ ┃ SHA-based hashes are considered outdated and generally insecure -┃ due to their vulnerabilit to brute-force and collision attacks. -┃ Modern algorithms such as yescrypt, scrypt, and bcrypt are recommended. -┗" >&2 +┃ due to their vulnerabilit to brute-force and collision attacks. +┃ Modern algorithms such as yescrypt, scrypt, and bcrypt are recommended. +┗${RESET}" >&2 exit 1 ;; sunmd5|md5crypt) perr-unsupportedhash "$TYPE" perr-forbiddenhash 'MD5' - echo -e "┏ + echo -e "${BOLD}${YELLOW}┏ ┃ Not as weak as the DES-based hashes, but MD5 is so cheap -┃ on modern hardware that it should not be used for new hashes. -┗" >&2 +┃ on modern hardware that it should not be used for new hashes. +┗${RESET}" >&2 exit 1 ;; bsdicrypt|descrypt) perr-unsupportedhash "$TYPE" perr-forbiddenhash 'DES' - echo -e "┏ + echo -e "${BOLD}${YELLOW}┏ ┃ The DES block cipher is cheap on modern hardware. Because there are only ┃ 4096 possible salts and 2**56 distinct passphrases, which it ┃ truncates to 8 characters, it is feasible to discover any passphrase ┃ hashed with this method. It should only be used if you absolutely have to ┃ generate hashes that will work on an old operating system that supports nothing else. -┗" >&2 +┗${RESET}" >&2 exit 1 ;; nt) perr-unsupportedhash "$TYPE" echo -e "${BOLD}Please ${RED}repent${WHITE} for your filthy sins ${RED}you disgusting human...${RESET}" >&2 - echo -e "┏ + echo -e "${BOLD}${YELLOW}┏ ┃ Available for cross-compatibility's sake on FreeBSD. Based on MD4. -┃ Has no salt or tunable cost parameter. It is so weak that -┃ almost any human-chosen passphrase hashed with this method is guessable. -┃ It should only be used if you absolutely have to generate hashes that -┃ will work on an old operating system that supports nothing else. -┗" >&2 +┃ Has no salt or tunable cost parameter. It is so weak that +┃ almost any human-chosen passphrase hashed with this method is guessable. +┃ It should only be used if you absolutely have to generate hashes that +┃ will work on an old operating system that supports nothing else. +┗${RESET}" >&2 exit 1 ;; *) From bc0be78367a62018e10edc9f24f3aeb6b2129631 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Wed, 21 Jan 2026 17:02:27 +1000 Subject: [PATCH 222/338] fix invisible tabs --- ceru/subcmds/new/password | 54 +++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/ceru/subcmds/new/password b/ceru/subcmds/new/password index 60b983e..d5cd795 100755 --- a/ceru/subcmds/new/password +++ b/ceru/subcmds/new/password @@ -108,7 +108,7 @@ done; unset -v ARG ## nt NT-Hash function perr-unsupportedhash { local ALGO="$1" - echo -e "${BOLD}${CYAN}$THIS${RED} does not support the ${MAGENTA}$ALGO${RED} hashing algorithm${RESET}" >&2 + echo -e "${BOLD}${RED}The ${MAGENTA}$ALGO${RED} hash algorithm is unsupported!${RESET}" >&2 } function perr-forbiddenhash { local ALGO="$1" @@ -142,68 +142,68 @@ case "$TYPE" in gost-yescrypt) perr-unsupportedhash "$TYPE" perr-recommendhash 'yescrypt' - echo -e "┏ -┃ Dear Comrade, + echo -e "${BOLD}${YELLOW}┏ +┃ Dear Comrade, ┃ It is with a heavy heart I must inform you that \"GOST Algorithms ┃ Considered Harmful\" - Edsger Wybe Dijkstra (probably). Alas -┃ GOST is considered broken... It is no longer 1970, please grow up :( -┃ Слава Родине! - Glory to the Motherland! -┗" >&2 +┃ GOST is considered broken... It is no longer 1970, please grow up :( +┃ Слава Родине! - Glory to the Motherland! +┗${RESET}" >&2 exit 1 ;; bcrypt-a) perr-unsupportedhash "$TYPE" perr-recommendhash 'bcrypt' - echo -e "┏ + echo -e "${BOLD}${YELLOW}┏ ┃ The alternative prefix \"\$2y$\" is equivalent to \"\$2b$\". -┃ It exists for historical reasons only. The alternative prefixes -┃ \"\$2a$\" and \"\$2x$\" provide bug-compatibility with -┃ crypt_blowfish 1.0.4 and earlier, which incorrectly processed -┃ characters with the 8th bit set. -┗" >&2 +┃ It exists for historical reasons only. The alternative prefixes +┃ \"\$2a$\" and \"\$2x$\" provide bug-compatibility with +┃ crypt_blowfish 1.0.4 and earlier, which incorrectly processed +┃ characters with the 8th bit set. +┗${RESET}" >&2 exit 1 ;; sha512crypt|sha256crypt) perr-unsupportedhash "$TYPE" perr-forbiddenhash 'SHA' - echo -e "┏ + echo -e "${BOLD}${YELLOW}┏ ┃ SHA-based hashes are considered outdated and generally insecure -┃ due to their vulnerabilit to brute-force and collision attacks. -┃ Modern algorithms such as yescrypt, scrypt, and bcrypt are recommended. -┗" >&2 +┃ due to their vulnerabilit to brute-force and collision attacks. +┃ Modern algorithms such as yescrypt, scrypt, and bcrypt are recommended. +┗${RESET}" >&2 exit 1 ;; sunmd5|md5crypt) perr-unsupportedhash "$TYPE" perr-forbiddenhash 'MD5' - echo -e "┏ + echo -e "${BOLD}${YELLOW}┏ ┃ Not as weak as the DES-based hashes, but MD5 is so cheap -┃ on modern hardware that it should not be used for new hashes. -┗" >&2 +┃ on modern hardware that it should not be used for new hashes. +┗${RESET}" >&2 exit 1 ;; bsdicrypt|descrypt) perr-unsupportedhash "$TYPE" perr-forbiddenhash 'DES' - echo -e "┏ + echo -e "${BOLD}${YELLOW}┏ ┃ The DES block cipher is cheap on modern hardware. Because there are only ┃ 4096 possible salts and 2**56 distinct passphrases, which it ┃ truncates to 8 characters, it is feasible to discover any passphrase ┃ hashed with this method. It should only be used if you absolutely have to ┃ generate hashes that will work on an old operating system that supports nothing else. -┗" >&2 +┗${RESET}" >&2 exit 1 ;; nt) perr-unsupportedhash "$TYPE" echo -e "${BOLD}Please ${RED}repent${WHITE} for your filthy sins ${RED}you disgusting human...${RESET}" >&2 - echo -e "┏ + echo -e "${BOLD}${YELLOW}┏ ┃ Available for cross-compatibility's sake on FreeBSD. Based on MD4. -┃ Has no salt or tunable cost parameter. It is so weak that -┃ almost any human-chosen passphrase hashed with this method is guessable. -┃ It should only be used if you absolutely have to generate hashes that -┃ will work on an old operating system that supports nothing else. -┗" >&2 +┃ Has no salt or tunable cost parameter. It is so weak that +┃ almost any human-chosen passphrase hashed with this method is guessable. +┃ It should only be used if you absolutely have to generate hashes that +┃ will work on an old operating system that supports nothing else. +┗${RESET}" >&2 exit 1 ;; *) From ca65318170e7cb93a14da77cf59dcffc3e690389 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Thu, 22 Jan 2026 10:29:26 +1000 Subject: [PATCH 223/338] add root as direct arg of mkNexus --- cerulean/nexus/nexus.nix | 9 ++++----- notes.md | 4 ++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index 5342080..5fb4866 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -50,7 +50,6 @@ Ensure `nexus.${path}` exists under your call to `cerulean.mkNexus`. ''); in { - root = missing "the root directory for all cerulean nix modules." "root"; groups = missing "an list of all valid node group names." "groups"; nodes = Terminal {}; }; @@ -64,7 +63,7 @@ '' else nib.parse.overrideStruct templateNexus nexus; - mkNexus' = nexus': let + mkNexus' = root: nexus': let nexus = parseNexus nexus'; in rec { nixosConfigurations = mapNodes nexus.nodes ( @@ -72,7 +71,7 @@ lib.nixosSystem { system = node.system; modules = let - host' = nexus.root + "/hosts/${nodeName}"; + host' = root + "/hosts/${nodeName}"; host = if pathExists host' then host' @@ -143,8 +142,8 @@ checks = mapAttrs (system: deployLib: deployLib.deployChecks deploy) deploy-rs.lib; }; in { - mkNexus = outputs': let - autogen = mkNexus' <| getAttrOr "nexus" outputs' {}; + mkNexus = root: outputs': let + autogen = mkNexus' root <| getAttrOr "nexus" outputs' {}; outputs = removeAttrs outputs' ["nexus"]; in autogen // outputs; # XXX: TODO: replace this with a deep merge diff --git a/notes.md b/notes.md index 9bc45e1..18cd20d 100644 --- a/notes.md +++ b/notes.md @@ -46,7 +46,7 @@ nix-instantiate \ nix/flake.rs: let child = Command::new("nix") /* -# `FlakeMetadata::resolve(flake: &str) -> ColmenaREsult` +# `FlakeMetadata::resolve(flake: &str) -> ColmenaResult` nix flake metadata \ --json \ --extra-experimental-features "nix-command flakes" @@ -140,7 +140,7 @@ nix/host/local.rs: self.make_privileged_command(&["nix-env", "--pro # XXX: NOTE: Same as `Ssh::activate(...)` # NOTE: This command runs if `goal.should_switch_profile()` -nix-env --profile $SYSTEM+PROFILE +nix-env --profile $SYSTEM_PROFILE --set $PROFILE_PATH # NOTE: Separate command (runs regardless of `goal.should_switch_profile()`) From 201d26fa589e45bd6d249a90d0bd153b5986cc03 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Thu, 22 Jan 2026 10:29:26 +1000 Subject: [PATCH 224/338] add root as direct arg of mkNexus --- cerulean/nexus/nexus.nix | 9 ++++----- notes.md | 4 ++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index 5342080..5fb4866 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -50,7 +50,6 @@ Ensure `nexus.${path}` exists under your call to `cerulean.mkNexus`. ''); in { - root = missing "the root directory for all cerulean nix modules." "root"; groups = missing "an list of all valid node group names." "groups"; nodes = Terminal {}; }; @@ -64,7 +63,7 @@ '' else nib.parse.overrideStruct templateNexus nexus; - mkNexus' = nexus': let + mkNexus' = root: nexus': let nexus = parseNexus nexus'; in rec { nixosConfigurations = mapNodes nexus.nodes ( @@ -72,7 +71,7 @@ lib.nixosSystem { system = node.system; modules = let - host' = nexus.root + "/hosts/${nodeName}"; + host' = root + "/hosts/${nodeName}"; host = if pathExists host' then host' @@ -143,8 +142,8 @@ checks = mapAttrs (system: deployLib: deployLib.deployChecks deploy) deploy-rs.lib; }; in { - mkNexus = outputs': let - autogen = mkNexus' <| getAttrOr "nexus" outputs' {}; + mkNexus = root: outputs': let + autogen = mkNexus' root <| getAttrOr "nexus" outputs' {}; outputs = removeAttrs outputs' ["nexus"]; in autogen // outputs; # XXX: TODO: replace this with a deep merge diff --git a/notes.md b/notes.md index 9bc45e1..18cd20d 100644 --- a/notes.md +++ b/notes.md @@ -46,7 +46,7 @@ nix-instantiate \ nix/flake.rs: let child = Command::new("nix") /* -# `FlakeMetadata::resolve(flake: &str) -> ColmenaREsult` +# `FlakeMetadata::resolve(flake: &str) -> ColmenaResult` nix flake metadata \ --json \ --extra-experimental-features "nix-command flakes" @@ -140,7 +140,7 @@ nix/host/local.rs: self.make_privileged_command(&["nix-env", "--pro # XXX: NOTE: Same as `Ssh::activate(...)` # NOTE: This command runs if `goal.should_switch_profile()` -nix-env --profile $SYSTEM+PROFILE +nix-env --profile $SYSTEM_PROFILE --set $PROFILE_PATH # NOTE: Separate command (runs regardless of `goal.should_switch_profile()`) From 52231b1a4d54807c4d71cecb1399275fe6379438 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Thu, 22 Jan 2026 10:30:41 +1000 Subject: [PATCH 225/338] use nib's new getAttrOr --- cerulean/nexus/nexus.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index 5fb4866..9f1f81c 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -143,7 +143,7 @@ }; in { mkNexus = root: outputs': let - autogen = mkNexus' root <| getAttrOr "nexus" outputs' {}; + autogen = mkNexus' root <| getAttrOr {} "nexus" outputs'; outputs = removeAttrs outputs' ["nexus"]; in autogen // outputs; # XXX: TODO: replace this with a deep merge From 1988e4961b2b14ba7e3700c422081b6787f0a7fd Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Thu, 22 Jan 2026 10:30:41 +1000 Subject: [PATCH 226/338] use nib's new getAttrOr --- cerulean/nexus/nexus.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index 5fb4866..9f1f81c 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -143,7 +143,7 @@ }; in { mkNexus = root: outputs': let - autogen = mkNexus' root <| getAttrOr "nexus" outputs' {}; + autogen = mkNexus' root <| getAttrOr {} "nexus" outputs'; outputs = removeAttrs outputs' ["nexus"]; in autogen // outputs; # XXX: TODO: replace this with a deep merge From 4a96aba7e38bdddc29bc80a5db1f6213614d7042 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 25 Jan 2026 13:29:36 +1000 Subject: [PATCH 227/338] add TODO.md --- TODO.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100755 TODO.md diff --git a/TODO.md b/TODO.md new file mode 100755 index 0000000..c43e8c6 --- /dev/null +++ b/TODO.md @@ -0,0 +1,5 @@ +Allow `Cerulean.mkNexus` to be an alias for `flake-parts.lib.mkFlake` +also rename `Cerulean` to `cerulean` in Nix to maintain the naming convention. + +Using `flake-parts` ensures Cerulean is usable without restricting +yourself only to the Cerulean ecosystem. From 7a384dd462199b558d1d8c2c13794d8c1d35fb72 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Sun, 25 Jan 2026 13:29:36 +1000 Subject: [PATCH 228/338] add TODO.md --- TODO.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100755 TODO.md diff --git a/TODO.md b/TODO.md new file mode 100755 index 0000000..c43e8c6 --- /dev/null +++ b/TODO.md @@ -0,0 +1,5 @@ +Allow `Cerulean.mkNexus` to be an alias for `flake-parts.lib.mkFlake` +also rename `Cerulean` to `cerulean` in Nix to maintain the naming convention. + +Using `flake-parts` ensures Cerulean is usable without restricting +yourself only to the Cerulean ecosystem. From c3ac86e6f9eecf3dd703ab153839707470f3967d Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 25 Jan 2026 13:29:39 +1000 Subject: [PATCH 229/338] mailmap --- .mailmap | 1 + 1 file changed, 1 insertion(+) create mode 100644 .mailmap diff --git a/.mailmap b/.mailmap new file mode 100644 index 0000000..ab42b58 --- /dev/null +++ b/.mailmap @@ -0,0 +1 @@ +Emile Clark-Boman From 0e1986209cf3d49e55b8946965181ebb5693f0e6 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Sun, 25 Jan 2026 13:29:39 +1000 Subject: [PATCH 230/338] mailmap --- .mailmap | 1 + 1 file changed, 1 insertion(+) create mode 100644 .mailmap diff --git a/.mailmap b/.mailmap new file mode 100644 index 0000000..ab42b58 --- /dev/null +++ b/.mailmap @@ -0,0 +1 @@ +Emile Clark-Boman From e28d06e346190542c0e9d1e0fd7dc7b03e72c29a Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 25 Jan 2026 13:30:43 +1000 Subject: [PATCH 231/338] git filter-repo --use-mailmap --- .mailmap | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .mailmap diff --git a/.mailmap b/.mailmap deleted file mode 100644 index ab42b58..0000000 --- a/.mailmap +++ /dev/null @@ -1 +0,0 @@ -Emile Clark-Boman From 718d4272ef2d6be409c900cba756d3189a749f87 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Sun, 25 Jan 2026 13:30:43 +1000 Subject: [PATCH 232/338] git filter-repo --use-mailmap --- .mailmap | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .mailmap diff --git a/.mailmap b/.mailmap deleted file mode 100644 index ab42b58..0000000 --- a/.mailmap +++ /dev/null @@ -1 +0,0 @@ -Emile Clark-Boman From a538a7f9bc95c8dd11958ebb7777e01890efa489 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 8 Feb 2026 20:25:04 +1000 Subject: [PATCH 233/338] fix bad getAttrOr call --- cerulean/nexus/nexus.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index 9f1f81c..dc153d5 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -143,7 +143,7 @@ }; in { mkNexus = root: outputs': let - autogen = mkNexus' root <| getAttrOr {} "nexus" outputs'; + autogen = mkNexus' root <| getAttrOr "nexus" {} outputs'; outputs = removeAttrs outputs' ["nexus"]; in autogen // outputs; # XXX: TODO: replace this with a deep merge From c5bdef5fcc8e901034989757fc67c020d926e747 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Sun, 8 Feb 2026 20:25:04 +1000 Subject: [PATCH 234/338] fix bad getAttrOr call --- cerulean/nexus/nexus.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index 9f1f81c..dc153d5 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -143,7 +143,7 @@ }; in { mkNexus = root: outputs': let - autogen = mkNexus' root <| getAttrOr {} "nexus" outputs'; + autogen = mkNexus' root <| getAttrOr "nexus" {} outputs'; outputs = removeAttrs outputs' ["nexus"]; in autogen // outputs; # XXX: TODO: replace this with a deep merge From 151f093378df4986c573929991c6614d194a8d29 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Mon, 9 Feb 2026 09:34:30 +1000 Subject: [PATCH 235/338] switch to NixTypes system --- cerulean/nexus/nexus.nix | 6 +++--- cerulean/nexus/nodes.nix | 6 +++--- flake.nix | 42 +++++++++++++--------------------------- 3 files changed, 19 insertions(+), 35 deletions(-) diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index dc153d5..e8c2c95 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -14,7 +14,7 @@ { this, sys, - nib, + nt, lib, deploy-rs, ... @@ -40,7 +40,7 @@ templateNexus = let inherit - (nib.types) + (nt.types) Terminal ; @@ -61,7 +61,7 @@ Cerulean Nexus config must be provided as an attribute set, got "${typeOf nexus}" instead! Ensure all the `nexus` declaration is an attribute set under your call to `cerulean.mkNexus`. '' - else nib.parse.overrideStruct templateNexus nexus; + else nt.projectOnto templateNexus nexus; mkNexus' = root: nexus': let nexus = parseNexus nexus'; diff --git a/cerulean/nexus/nodes.nix b/cerulean/nexus/nodes.nix index 781a1fa..62436b3 100644 --- a/cerulean/nexus/nodes.nix +++ b/cerulean/nexus/nodes.nix @@ -11,7 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -{nib, ...}: let +{nt, ...}: let inherit (builtins) isAttrs @@ -22,7 +22,7 @@ in rec { # abstract node instance that stores all default values templateNode = name: system: let inherit - (nib.types) + (nt.types) Terminal ; @@ -70,7 +70,7 @@ in rec { else let templateAttrs = templateNode name nodeAttrs.system; in - nib.parse.overrideStruct templateAttrs nodeAttrs; + nt.projectOnto templateAttrs nodeAttrs; mapNodes = nodes: f: nodes diff --git a/flake.nix b/flake.nix index b31ea1d..e05292c 100644 --- a/flake.nix +++ b/flake.nix @@ -20,42 +20,26 @@ nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"; nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixos-unstable"; - nib = { - url = "github:emilelcb/nib"; - # url = "/home/me/agribit/nexus/nib"; - inputs.systems.follows = "systems"; - }; - - mix = { - url = "github:emilelcb/mix"; - # url = "/home/me/agribit/nexus/mix"; - inputs.nib.follows = "nib"; + nt = { + url = "github:emilelcb/nt"; + inputs = { + systems.follows = "systems"; + nixpkgs.follows = "nixpkgs"; + }; }; deploy-rs.url = "github:serokell/deploy-rs"; }; outputs = { - self, nixpkgs, - nixpkgs-unstable, - nib, + nt, ... - } @ inputs: let - inherit - (nixpkgs) - lib - ; - - sys = nib.mkUSys { - pkgs = nib.withPkgs nixpkgs { - config.allowUnfree = false; - overlays = builtins.attrValues self.overlays; - }; - upkgs = nib.withPkgs nixpkgs-unstable { - config.allowUnfree = false; - }; + } @ inputs: + import ./cerulean + <| inputs + // { + inherit (nixpkgs) lib; + inherit (nt) mix; }; - in - import ./cerulean <| inputs // {inherit lib sys;}; } From 67ced96bef500e7d6b424a2b6e74e1399ba071db Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Mon, 9 Feb 2026 09:34:30 +1000 Subject: [PATCH 236/338] switch to NixTypes system --- cerulean/nexus/nexus.nix | 6 +++--- cerulean/nexus/nodes.nix | 6 +++--- flake.nix | 42 +++++++++++++--------------------------- 3 files changed, 19 insertions(+), 35 deletions(-) diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index dc153d5..e8c2c95 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -14,7 +14,7 @@ { this, sys, - nib, + nt, lib, deploy-rs, ... @@ -40,7 +40,7 @@ templateNexus = let inherit - (nib.types) + (nt.types) Terminal ; @@ -61,7 +61,7 @@ Cerulean Nexus config must be provided as an attribute set, got "${typeOf nexus}" instead! Ensure all the `nexus` declaration is an attribute set under your call to `cerulean.mkNexus`. '' - else nib.parse.overrideStruct templateNexus nexus; + else nt.projectOnto templateNexus nexus; mkNexus' = root: nexus': let nexus = parseNexus nexus'; diff --git a/cerulean/nexus/nodes.nix b/cerulean/nexus/nodes.nix index 781a1fa..62436b3 100644 --- a/cerulean/nexus/nodes.nix +++ b/cerulean/nexus/nodes.nix @@ -11,7 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -{nib, ...}: let +{nt, ...}: let inherit (builtins) isAttrs @@ -22,7 +22,7 @@ in rec { # abstract node instance that stores all default values templateNode = name: system: let inherit - (nib.types) + (nt.types) Terminal ; @@ -70,7 +70,7 @@ in rec { else let templateAttrs = templateNode name nodeAttrs.system; in - nib.parse.overrideStruct templateAttrs nodeAttrs; + nt.projectOnto templateAttrs nodeAttrs; mapNodes = nodes: f: nodes diff --git a/flake.nix b/flake.nix index b31ea1d..e05292c 100644 --- a/flake.nix +++ b/flake.nix @@ -20,42 +20,26 @@ nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"; nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixos-unstable"; - nib = { - url = "github:emilelcb/nib"; - # url = "/home/me/agribit/nexus/nib"; - inputs.systems.follows = "systems"; - }; - - mix = { - url = "github:emilelcb/mix"; - # url = "/home/me/agribit/nexus/mix"; - inputs.nib.follows = "nib"; + nt = { + url = "github:emilelcb/nt"; + inputs = { + systems.follows = "systems"; + nixpkgs.follows = "nixpkgs"; + }; }; deploy-rs.url = "github:serokell/deploy-rs"; }; outputs = { - self, nixpkgs, - nixpkgs-unstable, - nib, + nt, ... - } @ inputs: let - inherit - (nixpkgs) - lib - ; - - sys = nib.mkUSys { - pkgs = nib.withPkgs nixpkgs { - config.allowUnfree = false; - overlays = builtins.attrValues self.overlays; - }; - upkgs = nib.withPkgs nixpkgs-unstable { - config.allowUnfree = false; - }; + } @ inputs: + import ./cerulean + <| inputs + // { + inherit (nixpkgs) lib; + inherit (nt) mix; }; - in - import ./cerulean <| inputs // {inherit lib sys;}; } From c4612e73ec9c2833b62f381f099b3584b49798a1 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Mon, 9 Feb 2026 09:34:45 +1000 Subject: [PATCH 237/338] i dont think this is needed --- cerulean/default.nix | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cerulean/default.nix b/cerulean/default.nix index 4241570..68846bf 100644 --- a/cerulean/default.nix +++ b/cerulean/default.nix @@ -25,11 +25,11 @@ mix.newMixture inputs (mixture: { # build deploy-rs as a package not from the flake input, # hence we can rely on a nixpkg binary cache. deploy-rs.overlays.default - (self: super: { - deploy-rs = { - inherit (super) deploy-rs; - lib = super.deploy-rs.lib; - }; - }) + # (self: super: { + # deploy-rs = { + # inherit (super) deploy-rs; + # lib = super.deploy-rs.lib; + # }; + # }) ]; }) From f9a1b1083a100c4ff11094976e4f11b28b5e4df0 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Mon, 9 Feb 2026 09:34:45 +1000 Subject: [PATCH 238/338] i dont think this is needed --- cerulean/default.nix | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cerulean/default.nix b/cerulean/default.nix index 4241570..68846bf 100644 --- a/cerulean/default.nix +++ b/cerulean/default.nix @@ -25,11 +25,11 @@ mix.newMixture inputs (mixture: { # build deploy-rs as a package not from the flake input, # hence we can rely on a nixpkg binary cache. deploy-rs.overlays.default - (self: super: { - deploy-rs = { - inherit (super) deploy-rs; - lib = super.deploy-rs.lib; - }; - }) + # (self: super: { + # deploy-rs = { + # inherit (super) deploy-rs; + # lib = super.deploy-rs.lib; + # }; + # }) ]; }) From 0a74fb6ac2b0ac312ab7a91184e3efde8d344f1d Mon Sep 17 00:00:00 2001 From: _cry64 Date: Mon, 9 Feb 2026 09:35:10 +1000 Subject: [PATCH 239/338] provide default hostname --- cerulean/nexus/nodes.nix | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/cerulean/nexus/nodes.nix b/cerulean/nexus/nodes.nix index 62436b3..d98e1da 100644 --- a/cerulean/nexus/nodes.nix +++ b/cerulean/nexus/nodes.nix @@ -25,14 +25,7 @@ in rec { (nt.types) Terminal ; - - missing = msg: path: - Terminal (abort '' - Each Cerulean Nexus node is required to specify ${msg}! - Ensure `cerulean.nexus.nodes.${name}.${path}` exists under your call to `cerulean.mkNexus`. - ''); in { - # system = missing "its system type" "system"; # intentionally left missing!! (to raise errors) system = "x86_64-linux"; # sane default (i hope...) extraModules = []; specialArgs = Terminal {}; @@ -51,7 +44,7 @@ in rec { confirmTimeout = 30; # timeout in seconds for profile activation confirmation ssh = { - host = missing "an SSH hostname (domain name or ip address) for deployment" "deploy.ssh.host"; + host = name; user = "ceru-build"; # ceru-build is the default connection user port = 22; opts = []; From a7c9c21a2a91270691693a8fa0745b91f58e0170 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Mon, 9 Feb 2026 09:35:10 +1000 Subject: [PATCH 240/338] provide default hostname --- cerulean/nexus/nodes.nix | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/cerulean/nexus/nodes.nix b/cerulean/nexus/nodes.nix index 62436b3..d98e1da 100644 --- a/cerulean/nexus/nodes.nix +++ b/cerulean/nexus/nodes.nix @@ -25,14 +25,7 @@ in rec { (nt.types) Terminal ; - - missing = msg: path: - Terminal (abort '' - Each Cerulean Nexus node is required to specify ${msg}! - Ensure `cerulean.nexus.nodes.${name}.${path}` exists under your call to `cerulean.mkNexus`. - ''); in { - # system = missing "its system type" "system"; # intentionally left missing!! (to raise errors) system = "x86_64-linux"; # sane default (i hope...) extraModules = []; specialArgs = Terminal {}; @@ -51,7 +44,7 @@ in rec { confirmTimeout = 30; # timeout in seconds for profile activation confirmation ssh = { - host = missing "an SSH hostname (domain name or ip address) for deployment" "deploy.ssh.host"; + host = name; user = "ceru-build"; # ceru-build is the default connection user port = 22; opts = []; From c233cb88e40ae85a4d7c4501692e84c671328684 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Mon, 9 Feb 2026 09:35:24 +1000 Subject: [PATCH 241/338] add overlays and extraPkgConfig support --- cerulean/nexus/nexus.nix | 28 ++++++++++++++++++---------- cerulean/nexus/nodes.nix | 3 +++ 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index e8c2c95..29c7ad4 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -12,8 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. { + self, this, - sys, + nixpkgs, + nixpkgs-unstable, nt, lib, deploy-rs, @@ -33,11 +35,6 @@ mapNodes ; - inherit - (nib.std) - getAttrOr - ; - templateNexus = let inherit (nt.types) @@ -51,6 +48,7 @@ ''); in { groups = missing "an list of all valid node group names." "groups"; + overlays = []; nodes = Terminal {}; }; @@ -80,11 +78,21 @@ [../nixos-module host] ++ node.extraModules; # nix passes these to every single module - specialArgs = + specialArgs = let + pkgConfig = + { + inherit (node) system; + # XXX: WARNING: TODO: i've stopped caring + # XXX: WARNING: TODO: just figure out a better solution to pkgConfig + config.allowUnfree = true; + overlays = self.overlays ++ nexus.overlays ++ node.overlays; + } + // node.extraPkgConfig; + in node.specialArgs // { - pkgs = sys.pkgsFor node.system; - upkgs = sys.upkgsFor node.system; + pkgs = import nixpkgs pkgConfig; + upkgs = import nixpkgs-unstable pkgConfig; }; } ); @@ -143,7 +151,7 @@ }; in { mkNexus = root: outputs': let - autogen = mkNexus' root <| getAttrOr "nexus" {} outputs'; + autogen = mkNexus' root (outputs'.nexus or {}); outputs = removeAttrs outputs' ["nexus"]; in autogen // outputs; # XXX: TODO: replace this with a deep merge diff --git a/cerulean/nexus/nodes.nix b/cerulean/nexus/nodes.nix index d98e1da..b5432de 100644 --- a/cerulean/nexus/nodes.nix +++ b/cerulean/nexus/nodes.nix @@ -29,6 +29,9 @@ in rec { system = "x86_64-linux"; # sane default (i hope...) extraModules = []; specialArgs = Terminal {}; + overlays = []; + # XXX: WARNING: extraPkgConfig is a terrible solution (but im lazy for now) + extraPkgConfig = Terminal {}; deploy = { user = "root"; From 44b3e834aad0b18b9c716a65d06885d765e4d12c Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Mon, 9 Feb 2026 09:35:24 +1000 Subject: [PATCH 242/338] add overlays and extraPkgConfig support --- cerulean/nexus/nexus.nix | 28 ++++++++++++++++++---------- cerulean/nexus/nodes.nix | 3 +++ 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index e8c2c95..29c7ad4 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -12,8 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. { + self, this, - sys, + nixpkgs, + nixpkgs-unstable, nt, lib, deploy-rs, @@ -33,11 +35,6 @@ mapNodes ; - inherit - (nib.std) - getAttrOr - ; - templateNexus = let inherit (nt.types) @@ -51,6 +48,7 @@ ''); in { groups = missing "an list of all valid node group names." "groups"; + overlays = []; nodes = Terminal {}; }; @@ -80,11 +78,21 @@ [../nixos-module host] ++ node.extraModules; # nix passes these to every single module - specialArgs = + specialArgs = let + pkgConfig = + { + inherit (node) system; + # XXX: WARNING: TODO: i've stopped caring + # XXX: WARNING: TODO: just figure out a better solution to pkgConfig + config.allowUnfree = true; + overlays = self.overlays ++ nexus.overlays ++ node.overlays; + } + // node.extraPkgConfig; + in node.specialArgs // { - pkgs = sys.pkgsFor node.system; - upkgs = sys.upkgsFor node.system; + pkgs = import nixpkgs pkgConfig; + upkgs = import nixpkgs-unstable pkgConfig; }; } ); @@ -143,7 +151,7 @@ }; in { mkNexus = root: outputs': let - autogen = mkNexus' root <| getAttrOr "nexus" {} outputs'; + autogen = mkNexus' root (outputs'.nexus or {}); outputs = removeAttrs outputs' ["nexus"]; in autogen // outputs; # XXX: TODO: replace this with a deep merge diff --git a/cerulean/nexus/nodes.nix b/cerulean/nexus/nodes.nix index d98e1da..b5432de 100644 --- a/cerulean/nexus/nodes.nix +++ b/cerulean/nexus/nodes.nix @@ -29,6 +29,9 @@ in rec { system = "x86_64-linux"; # sane default (i hope...) extraModules = []; specialArgs = Terminal {}; + overlays = []; + # XXX: WARNING: extraPkgConfig is a terrible solution (but im lazy for now) + extraPkgConfig = Terminal {}; deploy = { user = "root"; From 03bee2a2761b3f3ba46abedd64a3afd46d640a73 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Mon, 9 Feb 2026 09:38:58 +1000 Subject: [PATCH 243/338] notes not needed just yet --- .gitignore | 1 + notes.md | 285 ----------------------------------------------------- 2 files changed, 1 insertion(+), 285 deletions(-) create mode 100644 .gitignore delete mode 100644 notes.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3828286 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/hidden diff --git a/notes.md b/notes.md deleted file mode 100644 index 18cd20d..0000000 --- a/notes.md +++ /dev/null @@ -1,285 +0,0 @@ -use `rg -F 'Command::new'` within `~/sandbox/clones/colmena/` - -use `hx $(realpath $(which nixos-rebuild))` to access the bash script - ->[!NOTE] -> The option `--extra-experimental-features "flakes"` is only set -> if `expression.requires_flakes()` where `expression: NixExpression`. - ->[!TODO] -> Also search for nix-store by itself, nix-instantiate, etc. -> And also search for `make_privileged_command(...)` usages. -> or actually maybe `.stdin(...)` or `Stdio::piped(...)`. - - -## `Command::new()` Calls Translated -```rs -command/repl.rs: let mut repl_cmd = Command::new("nix"); -/* -nix repl \ - --experimental-features "nix-command flakes" \ - --file $REPL_EXPRESSION_FILE - */ - -nix/key.rs: let output = Command::new(pathname) -/* -$PATHNAME $ARGS < /dev/null |& ... - */ - -nix/info.rs: let version_cmd = Command::new("nix-instantiate") -/* -# `NixCheck::detect(...)` -# NOTE: Used to detect if Nix is present (and if so, what version) -# NOTE: The version number is used to detect if flakes are supported (NOT enabled though) -# NOTE: You can do the same with nix3 with `nix --version` -nix-instantiate --version - */ - -nix/info.rs: let flake_cmd = Command::new("nix-instantiate") -/* -# `NixCheck::detect(...)` -# NOTE: Used to detect if flakes are enabled -nix-instantiate \ - --eval -E builtins.getFlake \ - &> /dev/null - */ - -nix/flake.rs: let child = Command::new("nix") -/* -# `FlakeMetadata::resolve(flake: &str) -> ColmenaResult` -nix flake metadata \ - --json \ - --extra-experimental-features "nix-command flakes" - */ - -nix/flake.rs: let status = Command::new("nix") -/* -# `lock_flake_quiet(uri: &str) -> ColmenaResult<()>` -# NOTE: "Quietly locks the dependencies of a flake." -nix flake lock \ - --extra-experimental-features "nix-command flakes" - */ - -nix/profile.rs: let mut command = Command::new("nix-store"); -/* -# `Profile::create_gc_root(&self, path: &Path) -> ColmenaResult<()>` -# NOTE: "Create a GC root for this profile." -# Each Profile struct contains a StorePath object -nix-store \ - --no-build-output \ - --indirect \ - --add-root \ - --realise $STORE_PATH \ - 1>/dev/null - */ - -nix/hive/mod.rs: let mut command = Command::new("nix-instantiate"); -/* -# `NixInstantiate::instantiate(&self) -> Command` -# NOTE: "Instantiation is not supported with DirectFlakeEval" -# NOTE: $EXPRESSION == `self.hive.get_base_expression() + self.expression` -# NOTE: --extra-experimental-features "flakes" only if `self.hive.is_flake()` -nix-instantiate -E $EXPRESSION \ - --no-gc-warning \ - --extra-experimental-features "flakes" - */ - -nix/hive/mod.rs: let mut command = Command::new("nix"); -/* -# `NixInstantiate::eval(self) -> Command` -# NOTE: $FLAGS == `self.hive.nix_flags()` - -# XXX: WARNING: if `self.hive.evaluation_method` is `EvaluationMethod::NixInstantiate` -# XXX: WARNING: then `NixInstantiate::instantiate(...)` (previous) is called -$NIX_INSTANTIATE_COMMAND - --eval - --json - --strict - --read-write-mode # ensures the derivations are instantiated, required for system profile evaluation and IFD - $FLAGS - -# XXX: WARNING: else if `self.hive.evaluation_method` is `EvaluationMethod::DirectFlakeEval` -# XXX: WARNING: then `NixInstantiate::instantiate(...)` (previous) is called -nix eval ${FLAKE_URI}#colmenaHive $FULL_EXPRESSION $FLAGS \ - --json \ - --apply \ - --extra-experimental-features "nix-command flakes" - */ - -nix/evaluator/nix_eval_jobs.rs: let mut command = Command::new(&self.executable); -/* -# `NixEvalJobs::evaluate(&self, expression: &dyn NixExpression, flags: NixFlags) -> ColmenaREsult>>>` -# NOTE: $EXECUTABLE defaults to EXECUTABLE='nix-eval-jobs' -$EXECUTABLE $FLAGS \ - --workers $WORKERS \ - --expr $EXPRESSION \ - --extra-experimental-features "flakes" \ - 2>&1 - */ - -nix/host/local.rs: let mut command = self.make_privileged_command(&["sh", "-c", &key_script]); -/* -# `Local::upload_key(&mut self, name: &str, key: &Key, require_ownership: bool) -> ColmenaResult<()>` -# XXX: NOTE: Same as `Ssh::upload_key()` -sh -c "$KEY_SCRIPT" - */ - - -nix/host/local.rs: let mut command = Command::new("nix-store"); -/* -# `Local::realize_remote(...)` -# XXX: NOTE: Same as `Ssh::realize_remote(...)` -nix-store - --no-gc-warning - --realise $DERIVATION_PATH - */ - -nix/host/local.rs: self.make_privileged_command(&["nix-env", "--profile", SYSTEM_PROFILE, "--set", path]) -/* -# `Local::activate(&mut self, profile: &Profile, goal: Goal) -> ColmenaResult<()>` -# XXX: NOTE: Same as `Ssh::activate(...)` - -# NOTE: This command runs if `goal.should_switch_profile()` -nix-env --profile $SYSTEM_PROFILE - --set $PROFILE_PATH - -# NOTE: Separate command (runs regardless of `goal.should_switch_profile()`) -$ACTIVATION_COMMAND*/ - -nix/host/local.rs: let paths = Command::new("readlink") -/* -# `Local::get_current_system_profile(&mut self) -> ColmenaResult` -# XXX: NOTE: Same as `Ssh::get_current_system_profile(...)` -readlink -e $CURRENT_PROFILE - */ - -nix/host/local.rs: let paths = Command::new("sh") -/* -# `Local::get_main_system_profile(&mut self) -> ColmenaResult` -# XXX: NOTE: Same as `Ssh::get_main_system_profile(...)` -sh -c 'readlink -e "$SYSTEM_PROFILE" | readlink -e "$CURRENT_PROFILE"' - */ - -nix/host/local.rs: let mut result = Command::new(full_command[0]); -/* -# `Local::make_privileged_command>(&self, command: &[S]) -> Command` -$COMMAND - */ - -nix/store.rs: let references = Command::new("nix-store") -/* -# `StorePath::references(...)` -nix-store - --query - --references $STORE_PATH - */ - -nix/host/ssh.rs: let mut cmd = Command::new("ssh"); -/* -# `Ssh::ssh(...)` -# $PRIVESC_COMMAND is privilege_escalation_command from Ssh struct -NIX_SSHOPTS="$SSH_OPTIONS" ssh $SSH_OPTIONS -- $PRIVESC_COMMAND $COMMAND - */ - -nix/host/ssh.rs: let mut command = Command::new("nix"); -/* -# `Ssh::nix_copy_closure(...)` - CONDITION: `if self.use_nix3_copy()` -# NOTE: --builders-use-substitutes "needed due to UX bug in ssh-ng://" -# --derivation is only added if the `path.file_extension() == "drv"` -# --to or --from is used depending on `CopyDirection` -# "?compress=true" is added after ${SSH_TARGET} if `options.gzip` is set -# WARNING: COMMAND EXECUTED OVER SSH (`self.ssh(...)`) -NIX_SSHOPTS="$SSH_OPTIONS" nix copy $CLOSURE_PATH - --no-check-sigs - --substitute-on-destination - --builders-use-substitutes - --derivation - [--to|--from] "ssh-ng://${SSH_TARGET}?compress=true" - --extra-experimental-features "nix-command" - */ - -nix/host/ssh.rs: let mut command = Command::new("nix-copy-closure"); -/* -# `Ssh::nix_copy_closure(...)` - CONDITION: `else` -# --include-outputs if `options.include_outputs` -# --use-substitues if `options.use_substitutes` -# --gzip if `options.gzip` -# WARNING: COMMAND EXECUTED OVER SSH (`self.ssh(...)`) -NIX_SSHOPTS="$SSH_OPTIONS" nix-copy-closure $SSH_TARGET $CLOSURE_PATH - [--to|--from] - --include-outputs - --use-substitues - --gzip - */ - - -nix/host/ssh.rs: let mut command = self.ssh(&["sh", "-c", &key_script]); -/* -# `Ssh::upload_key(...)` -# XXX: NOTE: Same as `Ssh::upload_key()` -# WARNING: COMMAND EXECUTED OVER SSH (`sel.ssh(...)`) -sh -c "$KEY_SCRIPT" - */ - - -nix/host/ssh.rs: let mut command = self.ssh("nix-store"); -/* -# `Ssh::realize_remote(...)` -# XXX: NOTE: Same as `Local::realize_remote(...)` -# WARNING: COMMAND EXECUTED OVER SSH (`self.ssh(...)`) -nix-store - --realise $DERIVATION - --no-gc-warning - */ - -nix/host/ssh.rs: let set_profile = self.ssh(&["nix-env", "--profile", SYSTEM_PROFILE, "--set", path]); -/* -# `Ssh::activate` - activate (switch to) a specific NixOS profile -# XXX: NOTE: Same as `Local::activate(...)` - -# NOTE: This command runs if `goal.should_switch_profile()` -# WARNING: COMMAND EXECUTED OVER SSH (`self.ssh(...)`) -nix-env --profile $SYSTEM+PROFILE - --set $PROFILE_PATH - -# NOTE: Separate command (runs regardless of `goal.should_switch_profile()`) -# WARNING: COMMAND EXECUTED OVER SSH (`self.ssh(...)`) -$ACTIVATION_COMMAND - */ - -nix/host/ssh.rs: let paths = self.ssh(&["readlink", "-e", CURRENT_PROFILE]) -/* -# `Ssh::get_current_system_profile(&mut self) -> ColmenaResult` -# XXX: NOTE: Same as `Local::get_current_system_profile(...)` -readlink -e $CURRENT_PROFILE - */ - -nix/hosts/ssh.rs: let paths = self.ssh(&["sh", "-c", &command]).capture_output().await?; -/* -# `Ssh::get_main_system_profile(...)` -# NOTE: the command executed is the same as in `/nix/host/local.rs` (except for SSH) -# XXX: NOTE: Same as `Local::get_main_system_profile(...)` -# WARNING: COMMAND EXECUTED OVER SSH (`self.ssh(...)`) -sh -c 'readlink -e "$SYSTEM_PROFILE" | readlink -e "$CURRENT_PROFILE"' - */ - -nix/hosts/ssh.rs: self.run_command(self.ssh(&["reboot"])).await -/* -# `Ssh::initiate_reboot` (`initate_reboot` spelt incorrectly... omg, line 408) -# WARNING: NOTE: `Local` doesn't seem to have an alternative to reboot (why?) -reboot - */ -``` - -#### SSH Options -```bash -# === Defined in /nix/host/ssh.rs Ssh::ssh_options() (line 345) === # - --o StrictHostKeyChecking=accept-new --o BatchMode=yes --T -$EXTRA_SSH_OPTIONS # I assume these are set by the user? --p $SSH_PORT # if self.port is Some --F $SSH_CONFIG # if self.ssh_config is Some - -``` From 8e49c266f9cda6f8ace1ab1992865a03c64a586c Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Mon, 9 Feb 2026 09:38:58 +1000 Subject: [PATCH 244/338] notes not needed just yet --- .gitignore | 1 + notes.md | 285 ----------------------------------------------------- 2 files changed, 1 insertion(+), 285 deletions(-) create mode 100644 .gitignore delete mode 100644 notes.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3828286 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/hidden diff --git a/notes.md b/notes.md deleted file mode 100644 index 18cd20d..0000000 --- a/notes.md +++ /dev/null @@ -1,285 +0,0 @@ -use `rg -F 'Command::new'` within `~/sandbox/clones/colmena/` - -use `hx $(realpath $(which nixos-rebuild))` to access the bash script - ->[!NOTE] -> The option `--extra-experimental-features "flakes"` is only set -> if `expression.requires_flakes()` where `expression: NixExpression`. - ->[!TODO] -> Also search for nix-store by itself, nix-instantiate, etc. -> And also search for `make_privileged_command(...)` usages. -> or actually maybe `.stdin(...)` or `Stdio::piped(...)`. - - -## `Command::new()` Calls Translated -```rs -command/repl.rs: let mut repl_cmd = Command::new("nix"); -/* -nix repl \ - --experimental-features "nix-command flakes" \ - --file $REPL_EXPRESSION_FILE - */ - -nix/key.rs: let output = Command::new(pathname) -/* -$PATHNAME $ARGS < /dev/null |& ... - */ - -nix/info.rs: let version_cmd = Command::new("nix-instantiate") -/* -# `NixCheck::detect(...)` -# NOTE: Used to detect if Nix is present (and if so, what version) -# NOTE: The version number is used to detect if flakes are supported (NOT enabled though) -# NOTE: You can do the same with nix3 with `nix --version` -nix-instantiate --version - */ - -nix/info.rs: let flake_cmd = Command::new("nix-instantiate") -/* -# `NixCheck::detect(...)` -# NOTE: Used to detect if flakes are enabled -nix-instantiate \ - --eval -E builtins.getFlake \ - &> /dev/null - */ - -nix/flake.rs: let child = Command::new("nix") -/* -# `FlakeMetadata::resolve(flake: &str) -> ColmenaResult` -nix flake metadata \ - --json \ - --extra-experimental-features "nix-command flakes" - */ - -nix/flake.rs: let status = Command::new("nix") -/* -# `lock_flake_quiet(uri: &str) -> ColmenaResult<()>` -# NOTE: "Quietly locks the dependencies of a flake." -nix flake lock \ - --extra-experimental-features "nix-command flakes" - */ - -nix/profile.rs: let mut command = Command::new("nix-store"); -/* -# `Profile::create_gc_root(&self, path: &Path) -> ColmenaResult<()>` -# NOTE: "Create a GC root for this profile." -# Each Profile struct contains a StorePath object -nix-store \ - --no-build-output \ - --indirect \ - --add-root \ - --realise $STORE_PATH \ - 1>/dev/null - */ - -nix/hive/mod.rs: let mut command = Command::new("nix-instantiate"); -/* -# `NixInstantiate::instantiate(&self) -> Command` -# NOTE: "Instantiation is not supported with DirectFlakeEval" -# NOTE: $EXPRESSION == `self.hive.get_base_expression() + self.expression` -# NOTE: --extra-experimental-features "flakes" only if `self.hive.is_flake()` -nix-instantiate -E $EXPRESSION \ - --no-gc-warning \ - --extra-experimental-features "flakes" - */ - -nix/hive/mod.rs: let mut command = Command::new("nix"); -/* -# `NixInstantiate::eval(self) -> Command` -# NOTE: $FLAGS == `self.hive.nix_flags()` - -# XXX: WARNING: if `self.hive.evaluation_method` is `EvaluationMethod::NixInstantiate` -# XXX: WARNING: then `NixInstantiate::instantiate(...)` (previous) is called -$NIX_INSTANTIATE_COMMAND - --eval - --json - --strict - --read-write-mode # ensures the derivations are instantiated, required for system profile evaluation and IFD - $FLAGS - -# XXX: WARNING: else if `self.hive.evaluation_method` is `EvaluationMethod::DirectFlakeEval` -# XXX: WARNING: then `NixInstantiate::instantiate(...)` (previous) is called -nix eval ${FLAKE_URI}#colmenaHive $FULL_EXPRESSION $FLAGS \ - --json \ - --apply \ - --extra-experimental-features "nix-command flakes" - */ - -nix/evaluator/nix_eval_jobs.rs: let mut command = Command::new(&self.executable); -/* -# `NixEvalJobs::evaluate(&self, expression: &dyn NixExpression, flags: NixFlags) -> ColmenaREsult>>>` -# NOTE: $EXECUTABLE defaults to EXECUTABLE='nix-eval-jobs' -$EXECUTABLE $FLAGS \ - --workers $WORKERS \ - --expr $EXPRESSION \ - --extra-experimental-features "flakes" \ - 2>&1 - */ - -nix/host/local.rs: let mut command = self.make_privileged_command(&["sh", "-c", &key_script]); -/* -# `Local::upload_key(&mut self, name: &str, key: &Key, require_ownership: bool) -> ColmenaResult<()>` -# XXX: NOTE: Same as `Ssh::upload_key()` -sh -c "$KEY_SCRIPT" - */ - - -nix/host/local.rs: let mut command = Command::new("nix-store"); -/* -# `Local::realize_remote(...)` -# XXX: NOTE: Same as `Ssh::realize_remote(...)` -nix-store - --no-gc-warning - --realise $DERIVATION_PATH - */ - -nix/host/local.rs: self.make_privileged_command(&["nix-env", "--profile", SYSTEM_PROFILE, "--set", path]) -/* -# `Local::activate(&mut self, profile: &Profile, goal: Goal) -> ColmenaResult<()>` -# XXX: NOTE: Same as `Ssh::activate(...)` - -# NOTE: This command runs if `goal.should_switch_profile()` -nix-env --profile $SYSTEM_PROFILE - --set $PROFILE_PATH - -# NOTE: Separate command (runs regardless of `goal.should_switch_profile()`) -$ACTIVATION_COMMAND*/ - -nix/host/local.rs: let paths = Command::new("readlink") -/* -# `Local::get_current_system_profile(&mut self) -> ColmenaResult` -# XXX: NOTE: Same as `Ssh::get_current_system_profile(...)` -readlink -e $CURRENT_PROFILE - */ - -nix/host/local.rs: let paths = Command::new("sh") -/* -# `Local::get_main_system_profile(&mut self) -> ColmenaResult` -# XXX: NOTE: Same as `Ssh::get_main_system_profile(...)` -sh -c 'readlink -e "$SYSTEM_PROFILE" | readlink -e "$CURRENT_PROFILE"' - */ - -nix/host/local.rs: let mut result = Command::new(full_command[0]); -/* -# `Local::make_privileged_command>(&self, command: &[S]) -> Command` -$COMMAND - */ - -nix/store.rs: let references = Command::new("nix-store") -/* -# `StorePath::references(...)` -nix-store - --query - --references $STORE_PATH - */ - -nix/host/ssh.rs: let mut cmd = Command::new("ssh"); -/* -# `Ssh::ssh(...)` -# $PRIVESC_COMMAND is privilege_escalation_command from Ssh struct -NIX_SSHOPTS="$SSH_OPTIONS" ssh $SSH_OPTIONS -- $PRIVESC_COMMAND $COMMAND - */ - -nix/host/ssh.rs: let mut command = Command::new("nix"); -/* -# `Ssh::nix_copy_closure(...)` - CONDITION: `if self.use_nix3_copy()` -# NOTE: --builders-use-substitutes "needed due to UX bug in ssh-ng://" -# --derivation is only added if the `path.file_extension() == "drv"` -# --to or --from is used depending on `CopyDirection` -# "?compress=true" is added after ${SSH_TARGET} if `options.gzip` is set -# WARNING: COMMAND EXECUTED OVER SSH (`self.ssh(...)`) -NIX_SSHOPTS="$SSH_OPTIONS" nix copy $CLOSURE_PATH - --no-check-sigs - --substitute-on-destination - --builders-use-substitutes - --derivation - [--to|--from] "ssh-ng://${SSH_TARGET}?compress=true" - --extra-experimental-features "nix-command" - */ - -nix/host/ssh.rs: let mut command = Command::new("nix-copy-closure"); -/* -# `Ssh::nix_copy_closure(...)` - CONDITION: `else` -# --include-outputs if `options.include_outputs` -# --use-substitues if `options.use_substitutes` -# --gzip if `options.gzip` -# WARNING: COMMAND EXECUTED OVER SSH (`self.ssh(...)`) -NIX_SSHOPTS="$SSH_OPTIONS" nix-copy-closure $SSH_TARGET $CLOSURE_PATH - [--to|--from] - --include-outputs - --use-substitues - --gzip - */ - - -nix/host/ssh.rs: let mut command = self.ssh(&["sh", "-c", &key_script]); -/* -# `Ssh::upload_key(...)` -# XXX: NOTE: Same as `Ssh::upload_key()` -# WARNING: COMMAND EXECUTED OVER SSH (`sel.ssh(...)`) -sh -c "$KEY_SCRIPT" - */ - - -nix/host/ssh.rs: let mut command = self.ssh("nix-store"); -/* -# `Ssh::realize_remote(...)` -# XXX: NOTE: Same as `Local::realize_remote(...)` -# WARNING: COMMAND EXECUTED OVER SSH (`self.ssh(...)`) -nix-store - --realise $DERIVATION - --no-gc-warning - */ - -nix/host/ssh.rs: let set_profile = self.ssh(&["nix-env", "--profile", SYSTEM_PROFILE, "--set", path]); -/* -# `Ssh::activate` - activate (switch to) a specific NixOS profile -# XXX: NOTE: Same as `Local::activate(...)` - -# NOTE: This command runs if `goal.should_switch_profile()` -# WARNING: COMMAND EXECUTED OVER SSH (`self.ssh(...)`) -nix-env --profile $SYSTEM+PROFILE - --set $PROFILE_PATH - -# NOTE: Separate command (runs regardless of `goal.should_switch_profile()`) -# WARNING: COMMAND EXECUTED OVER SSH (`self.ssh(...)`) -$ACTIVATION_COMMAND - */ - -nix/host/ssh.rs: let paths = self.ssh(&["readlink", "-e", CURRENT_PROFILE]) -/* -# `Ssh::get_current_system_profile(&mut self) -> ColmenaResult` -# XXX: NOTE: Same as `Local::get_current_system_profile(...)` -readlink -e $CURRENT_PROFILE - */ - -nix/hosts/ssh.rs: let paths = self.ssh(&["sh", "-c", &command]).capture_output().await?; -/* -# `Ssh::get_main_system_profile(...)` -# NOTE: the command executed is the same as in `/nix/host/local.rs` (except for SSH) -# XXX: NOTE: Same as `Local::get_main_system_profile(...)` -# WARNING: COMMAND EXECUTED OVER SSH (`self.ssh(...)`) -sh -c 'readlink -e "$SYSTEM_PROFILE" | readlink -e "$CURRENT_PROFILE"' - */ - -nix/hosts/ssh.rs: self.run_command(self.ssh(&["reboot"])).await -/* -# `Ssh::initiate_reboot` (`initate_reboot` spelt incorrectly... omg, line 408) -# WARNING: NOTE: `Local` doesn't seem to have an alternative to reboot (why?) -reboot - */ -``` - -#### SSH Options -```bash -# === Defined in /nix/host/ssh.rs Ssh::ssh_options() (line 345) === # - --o StrictHostKeyChecking=accept-new --o BatchMode=yes --T -$EXTRA_SSH_OPTIONS # I assume these are set by the user? --p $SSH_PORT # if self.port is Some --F $SSH_CONFIG # if self.ssh_config is Some - -``` From be916c86745d2b828ec3b3dc973ae336926cd003 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Mon, 9 Feb 2026 09:40:52 +1000 Subject: [PATCH 245/338] nt never uses flake inputs in this context --- flake.nix | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/flake.nix b/flake.nix index e05292c..99d7f68 100644 --- a/flake.nix +++ b/flake.nix @@ -20,13 +20,7 @@ nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"; nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixos-unstable"; - nt = { - url = "github:emilelcb/nt"; - inputs = { - systems.follows = "systems"; - nixpkgs.follows = "nixpkgs"; - }; - }; + nt.url = "github:emilelcb/nt"; deploy-rs.url = "github:serokell/deploy-rs"; }; From 4c83ab50d50d2a590907bfab60b9c68eb66dbce4 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Mon, 9 Feb 2026 09:40:52 +1000 Subject: [PATCH 246/338] nt never uses flake inputs in this context --- flake.nix | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/flake.nix b/flake.nix index e05292c..99d7f68 100644 --- a/flake.nix +++ b/flake.nix @@ -20,13 +20,7 @@ nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"; nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixos-unstable"; - nt = { - url = "github:emilelcb/nt"; - inputs = { - systems.follows = "systems"; - nixpkgs.follows = "nixpkgs"; - }; - }; + nt.url = "github:emilelcb/nt"; deploy-rs.url = "github:serokell/deploy-rs"; }; From 5c5f3fb65e847a7452fcffac8ee3e8f66bec61d6 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Thu, 12 Feb 2026 11:04:02 +1000 Subject: [PATCH 247/338] major refactoring argument self is now provided via recursion a naive implementation of host groups is added --- cerulean/nexus/nexus.nix | 275 +++++++++++++++++++++++++-------------- cerulean/nexus/nodes.nix | 3 +- flake.nix | 10 +- 3 files changed, 183 insertions(+), 105 deletions(-) diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index 29c7ad4..e322a57 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -23,8 +23,12 @@ }: let inherit (builtins) + attrNames + concatStringsSep elem + getAttr isAttrs + isFunction mapAttrs pathExists typeOf @@ -37,7 +41,7 @@ templateNexus = let inherit - (nt.types) + (nt.naive.terminal) Terminal ; @@ -47,112 +51,185 @@ Ensure `nexus.${path}` exists under your call to `cerulean.mkNexus`. ''); in { - groups = missing "an list of all valid node group names." "groups"; + groups = Terminal {}; overlays = []; nodes = Terminal {}; }; + parseGroups = groups: let + validGroup = g: + isAttrs g + || throw '' + Cerulean Nexus groups must be provided as attribute sets, got "${typeOf g}" instead! + Ensure all the `groups` definitions are attribute sets under your call to `cerulean.mkNexus`. + ''; + delegate = parent: g: + g + |> mapAttrs (name: value: + assert validGroup value; + (delegate g value) + // { + _name = name; + _parent = parent; + }); + in + assert validGroup groups; + delegate null groups; + parseNexus = nexus: - if ! isAttrs nexus - then - abort '' - Cerulean Nexus config must be provided as an attribute set, got "${typeOf nexus}" instead! - Ensure all the `nexus` declaration is an attribute set under your call to `cerulean.mkNexus`. - '' - else nt.projectOnto templateNexus nexus; + assert isAttrs nexus + || abort '' + Cerulean Nexus config must be provided as an attribute set, got "${typeOf nexus}" instead! + Ensure all the `nexus` declaration is an attribute set under your call to `cerulean.mkNexus`. + ''; let + base = nt.projectOnto templateNexus nexus; + in + # XXX: TODO: create a different version of nt.projectOnto that can actually + # XXX: TODO: handle applying a transformation to the result of each datapoint + base + // { + groups = parseGroups base.groups; + }; - mkNexus' = root: nexus': let - nexus = parseNexus nexus'; - in rec { - nixosConfigurations = mapNodes nexus.nodes ( - nodeName: node: - lib.nixosSystem { - system = node.system; - modules = let - host' = root + "/hosts/${nodeName}"; - host = - if pathExists host' - then host' - else host' + ".nix"; - in - [../nixos-module host] ++ node.extraModules; - - # nix passes these to every single module - specialArgs = let - pkgConfig = - { - inherit (node) system; - # XXX: WARNING: TODO: i've stopped caring - # XXX: WARNING: TODO: just figure out a better solution to pkgConfig - config.allowUnfree = true; - overlays = self.overlays ++ nexus.overlays ++ node.overlays; - } - // node.extraPkgConfig; - in - node.specialArgs - // { - pkgs = import nixpkgs pkgConfig; - upkgs = import nixpkgs-unstable pkgConfig; - }; - } + parseDecl = outputsBuilder: let + decl = ( + if isFunction outputsBuilder + then outputsBuilder final # provide `self` + else + assert (isAttrs outputsBuilder) + || abort '' + Cerulean declaration must be provided as an attribute set, got "${typeOf outputsBuilder}" instead! + Ensure your declaration is an attribute set or function under your call to `cerulean.mkNexus`. + ''; outputsBuilder ); - deploy.nodes = mapNodes nexus.nodes (nodeName: node: let - inherit - (node.deploy) - activationTimeout - autoRollback - confirmTimeout - interactiveSudo - magicRollback - remoteBuild - ssh - sudo - user - ; - - nixosFor = system: deploy-rs.lib.${system}.activate.nixos; - in { - hostname = ssh.host; - - profilesOrder = ["default"]; # profiles priority - profiles.default = { - path = nixosFor node.system nixosConfigurations.${nodeName}; - - user = user; - sudo = sudo; - interactiveSudo = interactiveSudo; - - fastConnection = false; - - autoRollback = autoRollback; - magicRollback = magicRollback; - activationTimeout = activationTimeout; - confirmTimeout = confirmTimeout; - - remoteBuild = remoteBuild; - sshUser = ssh.user; - sshOpts = - ssh.opts - ++ ( - if elem "-p" ssh.opts - then [] - else ["-p" (toString ssh.port)] - ) - ++ ( - if elem "-A" ssh.opts - then [] - else ["-A"] - ); + final = + decl + // { + nexus = parseNexus (decl.nexus or {}); }; - }); - - checks = mapAttrs (system: deployLib: deployLib.deployChecks deploy) deploy-rs.lib; - }; -in { - mkNexus = root: outputs': let - autogen = mkNexus' root (outputs'.nexus or {}); - outputs = removeAttrs outputs' ["nexus"]; in - autogen // outputs; # XXX: TODO: replace this with a deep merge + final; + + # XXX: TODO: create a function in NixTypes that handles this instead + findImport = path: + if pathExists path + then path + else path + ".nix"; +in { + mkNexus = root: outputsBuilder: let + decl = parseDecl outputsBuilder; + + inherit + (decl) + nexus + ; + customOutputs = removeAttrs decl ["nexus"]; + + outputs = rec { + nixosConfigurations = mapNodes nexus.nodes ( + nodeName: node: + lib.nixosSystem { + system = node.system; + modules = let + host = findImport (root + "/hosts/${nodeName}"); + # XXX: TODO: don't use a naive type for this (ie _name property) + # XXX: TODO: i really need NixTypes to be stable and use that instead + groups = + node.groups + |> map (group: + assert group ? _name + || throw (let + got = + if ! isAttrs group + then toString group + else + group + |> attrNames + |> map (name: "${name} = <${typeOf (getAttr name group)}>;") + |> concatStringsSep " " + |> (x: "{ ${x} }"); + in '' + Cerulean Nexus node "${nodeName}" is a member of a nonexistent group. + Got "${got}" of primitive type "${typeOf group}". + NOTE: Groups can be accessed via `self.groups.PATH.TO.YOUR.GROUP` + ''); + findImport (root + "/groups/${group._name}")); + in + [../nixos-module host] ++ groups ++ node.extraModules; + + # nix passes these to every single module + specialArgs = let + pkgConfig = + { + inherit (node) system; + # XXX: WARNING: TODO: i've stopped caring + # XXX: WARNING: TODO: just figure out a better solution to pkgConfig + config.allowUnfree = true; + overlays = self.overlays ++ nexus.overlays ++ node.overlays; + } + // node.extraPkgConfig; + in + node.specialArgs + // { + pkgs = import nixpkgs pkgConfig; + upkgs = import nixpkgs-unstable pkgConfig; + }; + } + ); + + deploy.nodes = mapNodes nexus.nodes (nodeName: node: let + inherit + (node.deploy) + activationTimeout + autoRollback + confirmTimeout + interactiveSudo + magicRollback + remoteBuild + ssh + sudo + user + ; + + nixosFor = system: deploy-rs.lib.${system}.activate.nixos; + in { + hostname = ssh.host; + + profilesOrder = ["default"]; # profiles priority + profiles.default = { + path = nixosFor node.system nixosConfigurations.${nodeName}; + + user = user; + sudo = sudo; + interactiveSudo = interactiveSudo; + + fastConnection = false; + + autoRollback = autoRollback; + magicRollback = magicRollback; + activationTimeout = activationTimeout; + confirmTimeout = confirmTimeout; + + remoteBuild = remoteBuild; + sshUser = ssh.user; + sshOpts = + ssh.opts + ++ ( + if elem "-p" ssh.opts + then [] + else ["-p" (toString ssh.port)] + ) + ++ ( + if elem "-A" ssh.opts + then [] + else ["-A"] + ); + }; + }); + + checks = mapAttrs (system: deployLib: deployLib.deployChecks deploy) deploy-rs.lib; + }; + in + outputs // customOutputs; } diff --git a/cerulean/nexus/nodes.nix b/cerulean/nexus/nodes.nix index b5432de..25a1d02 100644 --- a/cerulean/nexus/nodes.nix +++ b/cerulean/nexus/nodes.nix @@ -22,11 +22,12 @@ in rec { # abstract node instance that stores all default values templateNode = name: system: let inherit - (nt.types) + (nt.naive.terminal) Terminal ; in { system = "x86_64-linux"; # sane default (i hope...) + groups = []; extraModules = []; specialArgs = Terminal {}; overlays = []; diff --git a/flake.nix b/flake.nix index 99d7f68..94f4b33 100644 --- a/flake.nix +++ b/flake.nix @@ -31,9 +31,9 @@ ... } @ inputs: import ./cerulean - <| inputs - // { - inherit (nixpkgs) lib; - inherit (nt) mix; - }; + (inputs + // { + inherit (nixpkgs) lib; + inherit (nt) mix; + }); } From c4f98bf9d8a180cb2509b7afb36952883c6ececc Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Thu, 12 Feb 2026 11:04:02 +1000 Subject: [PATCH 248/338] major refactoring argument self is now provided via recursion a naive implementation of host groups is added --- cerulean/nexus/nexus.nix | 275 +++++++++++++++++++++++++-------------- cerulean/nexus/nodes.nix | 3 +- flake.nix | 10 +- 3 files changed, 183 insertions(+), 105 deletions(-) diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index 29c7ad4..e322a57 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -23,8 +23,12 @@ }: let inherit (builtins) + attrNames + concatStringsSep elem + getAttr isAttrs + isFunction mapAttrs pathExists typeOf @@ -37,7 +41,7 @@ templateNexus = let inherit - (nt.types) + (nt.naive.terminal) Terminal ; @@ -47,112 +51,185 @@ Ensure `nexus.${path}` exists under your call to `cerulean.mkNexus`. ''); in { - groups = missing "an list of all valid node group names." "groups"; + groups = Terminal {}; overlays = []; nodes = Terminal {}; }; + parseGroups = groups: let + validGroup = g: + isAttrs g + || throw '' + Cerulean Nexus groups must be provided as attribute sets, got "${typeOf g}" instead! + Ensure all the `groups` definitions are attribute sets under your call to `cerulean.mkNexus`. + ''; + delegate = parent: g: + g + |> mapAttrs (name: value: + assert validGroup value; + (delegate g value) + // { + _name = name; + _parent = parent; + }); + in + assert validGroup groups; + delegate null groups; + parseNexus = nexus: - if ! isAttrs nexus - then - abort '' - Cerulean Nexus config must be provided as an attribute set, got "${typeOf nexus}" instead! - Ensure all the `nexus` declaration is an attribute set under your call to `cerulean.mkNexus`. - '' - else nt.projectOnto templateNexus nexus; + assert isAttrs nexus + || abort '' + Cerulean Nexus config must be provided as an attribute set, got "${typeOf nexus}" instead! + Ensure all the `nexus` declaration is an attribute set under your call to `cerulean.mkNexus`. + ''; let + base = nt.projectOnto templateNexus nexus; + in + # XXX: TODO: create a different version of nt.projectOnto that can actually + # XXX: TODO: handle applying a transformation to the result of each datapoint + base + // { + groups = parseGroups base.groups; + }; - mkNexus' = root: nexus': let - nexus = parseNexus nexus'; - in rec { - nixosConfigurations = mapNodes nexus.nodes ( - nodeName: node: - lib.nixosSystem { - system = node.system; - modules = let - host' = root + "/hosts/${nodeName}"; - host = - if pathExists host' - then host' - else host' + ".nix"; - in - [../nixos-module host] ++ node.extraModules; - - # nix passes these to every single module - specialArgs = let - pkgConfig = - { - inherit (node) system; - # XXX: WARNING: TODO: i've stopped caring - # XXX: WARNING: TODO: just figure out a better solution to pkgConfig - config.allowUnfree = true; - overlays = self.overlays ++ nexus.overlays ++ node.overlays; - } - // node.extraPkgConfig; - in - node.specialArgs - // { - pkgs = import nixpkgs pkgConfig; - upkgs = import nixpkgs-unstable pkgConfig; - }; - } + parseDecl = outputsBuilder: let + decl = ( + if isFunction outputsBuilder + then outputsBuilder final # provide `self` + else + assert (isAttrs outputsBuilder) + || abort '' + Cerulean declaration must be provided as an attribute set, got "${typeOf outputsBuilder}" instead! + Ensure your declaration is an attribute set or function under your call to `cerulean.mkNexus`. + ''; outputsBuilder ); - deploy.nodes = mapNodes nexus.nodes (nodeName: node: let - inherit - (node.deploy) - activationTimeout - autoRollback - confirmTimeout - interactiveSudo - magicRollback - remoteBuild - ssh - sudo - user - ; - - nixosFor = system: deploy-rs.lib.${system}.activate.nixos; - in { - hostname = ssh.host; - - profilesOrder = ["default"]; # profiles priority - profiles.default = { - path = nixosFor node.system nixosConfigurations.${nodeName}; - - user = user; - sudo = sudo; - interactiveSudo = interactiveSudo; - - fastConnection = false; - - autoRollback = autoRollback; - magicRollback = magicRollback; - activationTimeout = activationTimeout; - confirmTimeout = confirmTimeout; - - remoteBuild = remoteBuild; - sshUser = ssh.user; - sshOpts = - ssh.opts - ++ ( - if elem "-p" ssh.opts - then [] - else ["-p" (toString ssh.port)] - ) - ++ ( - if elem "-A" ssh.opts - then [] - else ["-A"] - ); + final = + decl + // { + nexus = parseNexus (decl.nexus or {}); }; - }); - - checks = mapAttrs (system: deployLib: deployLib.deployChecks deploy) deploy-rs.lib; - }; -in { - mkNexus = root: outputs': let - autogen = mkNexus' root (outputs'.nexus or {}); - outputs = removeAttrs outputs' ["nexus"]; in - autogen // outputs; # XXX: TODO: replace this with a deep merge + final; + + # XXX: TODO: create a function in NixTypes that handles this instead + findImport = path: + if pathExists path + then path + else path + ".nix"; +in { + mkNexus = root: outputsBuilder: let + decl = parseDecl outputsBuilder; + + inherit + (decl) + nexus + ; + customOutputs = removeAttrs decl ["nexus"]; + + outputs = rec { + nixosConfigurations = mapNodes nexus.nodes ( + nodeName: node: + lib.nixosSystem { + system = node.system; + modules = let + host = findImport (root + "/hosts/${nodeName}"); + # XXX: TODO: don't use a naive type for this (ie _name property) + # XXX: TODO: i really need NixTypes to be stable and use that instead + groups = + node.groups + |> map (group: + assert group ? _name + || throw (let + got = + if ! isAttrs group + then toString group + else + group + |> attrNames + |> map (name: "${name} = <${typeOf (getAttr name group)}>;") + |> concatStringsSep " " + |> (x: "{ ${x} }"); + in '' + Cerulean Nexus node "${nodeName}" is a member of a nonexistent group. + Got "${got}" of primitive type "${typeOf group}". + NOTE: Groups can be accessed via `self.groups.PATH.TO.YOUR.GROUP` + ''); + findImport (root + "/groups/${group._name}")); + in + [../nixos-module host] ++ groups ++ node.extraModules; + + # nix passes these to every single module + specialArgs = let + pkgConfig = + { + inherit (node) system; + # XXX: WARNING: TODO: i've stopped caring + # XXX: WARNING: TODO: just figure out a better solution to pkgConfig + config.allowUnfree = true; + overlays = self.overlays ++ nexus.overlays ++ node.overlays; + } + // node.extraPkgConfig; + in + node.specialArgs + // { + pkgs = import nixpkgs pkgConfig; + upkgs = import nixpkgs-unstable pkgConfig; + }; + } + ); + + deploy.nodes = mapNodes nexus.nodes (nodeName: node: let + inherit + (node.deploy) + activationTimeout + autoRollback + confirmTimeout + interactiveSudo + magicRollback + remoteBuild + ssh + sudo + user + ; + + nixosFor = system: deploy-rs.lib.${system}.activate.nixos; + in { + hostname = ssh.host; + + profilesOrder = ["default"]; # profiles priority + profiles.default = { + path = nixosFor node.system nixosConfigurations.${nodeName}; + + user = user; + sudo = sudo; + interactiveSudo = interactiveSudo; + + fastConnection = false; + + autoRollback = autoRollback; + magicRollback = magicRollback; + activationTimeout = activationTimeout; + confirmTimeout = confirmTimeout; + + remoteBuild = remoteBuild; + sshUser = ssh.user; + sshOpts = + ssh.opts + ++ ( + if elem "-p" ssh.opts + then [] + else ["-p" (toString ssh.port)] + ) + ++ ( + if elem "-A" ssh.opts + then [] + else ["-A"] + ); + }; + }); + + checks = mapAttrs (system: deployLib: deployLib.deployChecks deploy) deploy-rs.lib; + }; + in + outputs // customOutputs; } diff --git a/cerulean/nexus/nodes.nix b/cerulean/nexus/nodes.nix index b5432de..25a1d02 100644 --- a/cerulean/nexus/nodes.nix +++ b/cerulean/nexus/nodes.nix @@ -22,11 +22,12 @@ in rec { # abstract node instance that stores all default values templateNode = name: system: let inherit - (nt.types) + (nt.naive.terminal) Terminal ; in { system = "x86_64-linux"; # sane default (i hope...) + groups = []; extraModules = []; specialArgs = Terminal {}; overlays = []; diff --git a/flake.nix b/flake.nix index 99d7f68..94f4b33 100644 --- a/flake.nix +++ b/flake.nix @@ -31,9 +31,9 @@ ... } @ inputs: import ./cerulean - <| inputs - // { - inherit (nixpkgs) lib; - inherit (nt) mix; - }; + (inputs + // { + inherit (nixpkgs) lib; + inherit (nt) mix; + }); } From 67cb277b1783a85457577129911dc6033ceaf59b Mon Sep 17 00:00:00 2001 From: _cry64 Date: Thu, 12 Feb 2026 13:24:33 +1000 Subject: [PATCH 249/338] add group inheritance --- cerulean/nexus/nexus.nix | 117 +++++++++++++++++++++++++++++++-------- 1 file changed, 93 insertions(+), 24 deletions(-) diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index e322a57..860546a 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -24,11 +24,14 @@ inherit (builtins) attrNames + concatLists concatStringsSep elem + filter getAttr isAttrs isFunction + isList mapAttrs pathExists typeOf @@ -56,25 +59,32 @@ nodes = Terminal {}; }; - parseGroups = groups: let + ROOT_GROUP_NAME = "all"; + + parseGroupDecl = groups: let validGroup = g: isAttrs g || throw '' Cerulean Nexus groups must be provided as attribute sets, got "${typeOf g}" instead! Ensure all the `groups` definitions are attribute sets under your call to `cerulean.mkNexus`. ''; - delegate = parent: g: - g - |> mapAttrs (name: value: - assert validGroup value; - (delegate g value) + delegate = parent: gName: g: let + result = + (g // { - _name = name; + _name = gName; _parent = parent; - }); + }) + |> mapAttrs (name: value: + if elem name ["_name" "_parent"] + # ignore metadata fields + then value + else assert validGroup value; (delegate result name value)); + in + result; in assert validGroup groups; - delegate null groups; + delegate null ROOT_GROUP_NAME groups; parseNexus = nexus: assert isAttrs nexus @@ -88,7 +98,7 @@ # XXX: TODO: handle applying a transformation to the result of each datapoint base // { - groups = parseGroups base.groups; + groups = parseGroupDecl base.groups; }; parseDecl = outputsBuilder: let @@ -135,26 +145,85 @@ in { host = findImport (root + "/hosts/${nodeName}"); # XXX: TODO: don't use a naive type for this (ie _name property) # XXX: TODO: i really need NixTypes to be stable and use that instead - groups = + # groups = + # node.groups + # |> map (group: + # assert group ? _name + # || throw (let + # got = + # if ! isAttrs group + # then toString group + # else + # group + # |> attrNames + # |> map (name: "${name} = <${typeOf (getAttr name group)}>;") + # |> concatStringsSep " " + # |> (x: "{ ${x} }"); + # in '' + # Cerulean Nexus node "${nodeName}" is a member of a nonexistent group. + # Got "${got}" of primitive type "${typeOf group}". + # NOTE: Groups can be accessed via `self.groups.PATH.TO.YOUR.GROUP` + # ''); + # findImport (root + "/groups/${group._name}")) + # |> nt.prim.unique; # filter by uniqueness + groups = assert isList node.groups + || throw '' + Cerulean Nexus node "${nodeName}" does not declare group membership as a list, got "${typeOf node.groups}" instead! + Ensure `nexus.nodes.${nodeName}.groups` is a list under your call to `cerulean.mkNexus`. + ''; node.groups - |> map (group: - assert group ? _name - || throw (let + # ensure all members are actually groups + |> map (group: let + got = + if ! isAttrs group + then toString group + else + group + |> attrNames + |> map (name: "${name} = <${typeOf (getAttr name group)}>;") + |> concatStringsSep " " + |> (x: "{ ${x} }"); + in + if group ? _name + then group + else + throw '' + Cerulean Nexus node "${nodeName}" is a member of an incorrectly structured group. + Got "${got}" of primitive type "${typeOf group}". + NOTE: Groups can be accessed via `self.groups.PATH.TO.YOUR.GROUP` + '') + # add all inherited groups via _parent + |> map (let + delegate = g: let got = - if ! isAttrs group - then toString group + if ! isAttrs g + then toString g else - group + g |> attrNames - |> map (name: "${name} = <${typeOf (getAttr name group)}>;") + |> map (name: "${name} = <${typeOf (getAttr name g)}>;") |> concatStringsSep " " |> (x: "{ ${x} }"); - in '' - Cerulean Nexus node "${nodeName}" is a member of a nonexistent group. - Got "${got}" of primitive type "${typeOf group}". - NOTE: Groups can be accessed via `self.groups.PATH.TO.YOUR.GROUP` - ''); - findImport (root + "/groups/${group._name}")); + in + assert g ? _parent + || throw '' + Cerulean Nexus node "${nodeName}" is a member of an incorrectly structured group. + Got "${got}" of primitive type "${typeOf g}". + NOTE: Groups can be accessed via `self.groups.PATH.TO.YOUR.GROUP` + ''; + if g._parent == null + then [g] + else [g] ++ delegate (g._parent); + in + delegate) + # flatten recursion result + |> concatLists + # find import location + |> map (group: findImport (root + "/groups/${group._name}")) + # filter by uniqueness + |> nt.prim.unique + # ignore missing groups + |> filter pathExists; in [../nixos-module host] ++ groups ++ node.extraModules; From 4cef14b835814d0560070433d5c388188ff20c82 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Thu, 12 Feb 2026 13:24:33 +1000 Subject: [PATCH 250/338] add group inheritance --- cerulean/nexus/nexus.nix | 117 +++++++++++++++++++++++++++++++-------- 1 file changed, 93 insertions(+), 24 deletions(-) diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index e322a57..860546a 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -24,11 +24,14 @@ inherit (builtins) attrNames + concatLists concatStringsSep elem + filter getAttr isAttrs isFunction + isList mapAttrs pathExists typeOf @@ -56,25 +59,32 @@ nodes = Terminal {}; }; - parseGroups = groups: let + ROOT_GROUP_NAME = "all"; + + parseGroupDecl = groups: let validGroup = g: isAttrs g || throw '' Cerulean Nexus groups must be provided as attribute sets, got "${typeOf g}" instead! Ensure all the `groups` definitions are attribute sets under your call to `cerulean.mkNexus`. ''; - delegate = parent: g: - g - |> mapAttrs (name: value: - assert validGroup value; - (delegate g value) + delegate = parent: gName: g: let + result = + (g // { - _name = name; + _name = gName; _parent = parent; - }); + }) + |> mapAttrs (name: value: + if elem name ["_name" "_parent"] + # ignore metadata fields + then value + else assert validGroup value; (delegate result name value)); + in + result; in assert validGroup groups; - delegate null groups; + delegate null ROOT_GROUP_NAME groups; parseNexus = nexus: assert isAttrs nexus @@ -88,7 +98,7 @@ # XXX: TODO: handle applying a transformation to the result of each datapoint base // { - groups = parseGroups base.groups; + groups = parseGroupDecl base.groups; }; parseDecl = outputsBuilder: let @@ -135,26 +145,85 @@ in { host = findImport (root + "/hosts/${nodeName}"); # XXX: TODO: don't use a naive type for this (ie _name property) # XXX: TODO: i really need NixTypes to be stable and use that instead - groups = + # groups = + # node.groups + # |> map (group: + # assert group ? _name + # || throw (let + # got = + # if ! isAttrs group + # then toString group + # else + # group + # |> attrNames + # |> map (name: "${name} = <${typeOf (getAttr name group)}>;") + # |> concatStringsSep " " + # |> (x: "{ ${x} }"); + # in '' + # Cerulean Nexus node "${nodeName}" is a member of a nonexistent group. + # Got "${got}" of primitive type "${typeOf group}". + # NOTE: Groups can be accessed via `self.groups.PATH.TO.YOUR.GROUP` + # ''); + # findImport (root + "/groups/${group._name}")) + # |> nt.prim.unique; # filter by uniqueness + groups = assert isList node.groups + || throw '' + Cerulean Nexus node "${nodeName}" does not declare group membership as a list, got "${typeOf node.groups}" instead! + Ensure `nexus.nodes.${nodeName}.groups` is a list under your call to `cerulean.mkNexus`. + ''; node.groups - |> map (group: - assert group ? _name - || throw (let + # ensure all members are actually groups + |> map (group: let + got = + if ! isAttrs group + then toString group + else + group + |> attrNames + |> map (name: "${name} = <${typeOf (getAttr name group)}>;") + |> concatStringsSep " " + |> (x: "{ ${x} }"); + in + if group ? _name + then group + else + throw '' + Cerulean Nexus node "${nodeName}" is a member of an incorrectly structured group. + Got "${got}" of primitive type "${typeOf group}". + NOTE: Groups can be accessed via `self.groups.PATH.TO.YOUR.GROUP` + '') + # add all inherited groups via _parent + |> map (let + delegate = g: let got = - if ! isAttrs group - then toString group + if ! isAttrs g + then toString g else - group + g |> attrNames - |> map (name: "${name} = <${typeOf (getAttr name group)}>;") + |> map (name: "${name} = <${typeOf (getAttr name g)}>;") |> concatStringsSep " " |> (x: "{ ${x} }"); - in '' - Cerulean Nexus node "${nodeName}" is a member of a nonexistent group. - Got "${got}" of primitive type "${typeOf group}". - NOTE: Groups can be accessed via `self.groups.PATH.TO.YOUR.GROUP` - ''); - findImport (root + "/groups/${group._name}")); + in + assert g ? _parent + || throw '' + Cerulean Nexus node "${nodeName}" is a member of an incorrectly structured group. + Got "${got}" of primitive type "${typeOf g}". + NOTE: Groups can be accessed via `self.groups.PATH.TO.YOUR.GROUP` + ''; + if g._parent == null + then [g] + else [g] ++ delegate (g._parent); + in + delegate) + # flatten recursion result + |> concatLists + # find import location + |> map (group: findImport (root + "/groups/${group._name}")) + # filter by uniqueness + |> nt.prim.unique + # ignore missing groups + |> filter pathExists; in [../nixos-module host] ++ groups ++ node.extraModules; From b2c23ad3621d34871cb50e163d91df7fc1306128 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Thu, 12 Feb 2026 14:20:33 +1000 Subject: [PATCH 251/338] fix root not propagate --- cerulean/nexus/nexus.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index 860546a..8b65fdd 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -241,6 +241,7 @@ in { in node.specialArgs // { + inherit root; pkgs = import nixpkgs pkgConfig; upkgs = import nixpkgs-unstable pkgConfig; }; From deb7738abcd797e6e5fc9e4dac098e1c05b30e09 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Thu, 12 Feb 2026 14:20:33 +1000 Subject: [PATCH 252/338] fix root not propagate --- cerulean/nexus/nexus.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index 860546a..8b65fdd 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -241,6 +241,7 @@ in { in node.specialArgs // { + inherit root; pkgs = import nixpkgs pkgConfig; upkgs = import nixpkgs-unstable pkgConfig; }; From 37edddc2eef8ddc29bb3f69e77c475f9ea16b687 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Fri, 13 Feb 2026 01:46:09 +1000 Subject: [PATCH 253/338] add importOverlays --- cerulean/nexus/nexus.nix | 74 +++++++++++++++------------------------- 1 file changed, 27 insertions(+), 47 deletions(-) diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index 8b65fdd..12ad933 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -54,8 +54,11 @@ Ensure `nexus.${path}` exists under your call to `cerulean.mkNexus`. ''); in { - groups = Terminal {}; overlays = []; + extraModules = []; + specialArgs = Terminal {}; + + groups = Terminal {}; nodes = Terminal {}; }; @@ -121,10 +124,19 @@ in final; + importOverlays = root: let + path = findImport (root + "/overlay"); + in + if pathExists path + then import path + else []; + # XXX: TODO: create a function in NixTypes that handles this instead findImport = path: if pathExists path then path + else if pathExists (path + "default.nix") + then path + "/default.nix" else path + ".nix"; in { mkNexus = root: outputsBuilder: let @@ -143,29 +155,6 @@ in { system = node.system; modules = let host = findImport (root + "/hosts/${nodeName}"); - # XXX: TODO: don't use a naive type for this (ie _name property) - # XXX: TODO: i really need NixTypes to be stable and use that instead - # groups = - # node.groups - # |> map (group: - # assert group ? _name - # || throw (let - # got = - # if ! isAttrs group - # then toString group - # else - # group - # |> attrNames - # |> map (name: "${name} = <${typeOf (getAttr name group)}>;") - # |> concatStringsSep " " - # |> (x: "{ ${x} }"); - # in '' - # Cerulean Nexus node "${nodeName}" is a member of a nonexistent group. - # Got "${got}" of primitive type "${typeOf group}". - # NOTE: Groups can be accessed via `self.groups.PATH.TO.YOUR.GROUP` - # ''); - # findImport (root + "/groups/${group._name}")) - # |> nt.prim.unique; # filter by uniqueness groups = assert isList node.groups || throw '' Cerulean Nexus node "${nodeName}" does not declare group membership as a list, got "${typeOf node.groups}" instead! @@ -194,26 +183,10 @@ in { '') # add all inherited groups via _parent |> map (let - delegate = g: let - got = - if ! isAttrs g - then toString g - else - g - |> attrNames - |> map (name: "${name} = <${typeOf (getAttr name g)}>;") - |> concatStringsSep " " - |> (x: "{ ${x} }"); - in - assert g ? _parent - || throw '' - Cerulean Nexus node "${nodeName}" is a member of an incorrectly structured group. - Got "${got}" of primitive type "${typeOf g}". - NOTE: Groups can be accessed via `self.groups.PATH.TO.YOUR.GROUP` - ''; - if g._parent == null - then [g] - else [g] ++ delegate (g._parent); + delegate = g: + if g._parent == null + then [g] + else [g] ++ delegate (g._parent); in delegate) # flatten recursion result @@ -225,7 +198,9 @@ in { # ignore missing groups |> filter pathExists; in - [../nixos-module host] ++ groups ++ node.extraModules; + [../nixos-module host] + ++ groups + ++ node.extraModules; # nix passes these to every single module specialArgs = let @@ -234,8 +209,13 @@ in { inherit (node) system; # XXX: WARNING: TODO: i've stopped caring # XXX: WARNING: TODO: just figure out a better solution to pkgConfig - config.allowUnfree = true; - overlays = self.overlays ++ nexus.overlays ++ node.overlays; + config.allowUnfree = false; + config.allowBroken = false; + overlays = + self.overlays + ++ nexus.overlays + ++ node.overlays + ++ importOverlays root; } // node.extraPkgConfig; in From 2e6bef9ad2a7c2a60a8ceb25c3a60bdf7bd52c31 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Fri, 13 Feb 2026 01:46:09 +1000 Subject: [PATCH 254/338] add importOverlays --- cerulean/nexus/nexus.nix | 74 +++++++++++++++------------------------- 1 file changed, 27 insertions(+), 47 deletions(-) diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index 8b65fdd..12ad933 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -54,8 +54,11 @@ Ensure `nexus.${path}` exists under your call to `cerulean.mkNexus`. ''); in { - groups = Terminal {}; overlays = []; + extraModules = []; + specialArgs = Terminal {}; + + groups = Terminal {}; nodes = Terminal {}; }; @@ -121,10 +124,19 @@ in final; + importOverlays = root: let + path = findImport (root + "/overlay"); + in + if pathExists path + then import path + else []; + # XXX: TODO: create a function in NixTypes that handles this instead findImport = path: if pathExists path then path + else if pathExists (path + "default.nix") + then path + "/default.nix" else path + ".nix"; in { mkNexus = root: outputsBuilder: let @@ -143,29 +155,6 @@ in { system = node.system; modules = let host = findImport (root + "/hosts/${nodeName}"); - # XXX: TODO: don't use a naive type for this (ie _name property) - # XXX: TODO: i really need NixTypes to be stable and use that instead - # groups = - # node.groups - # |> map (group: - # assert group ? _name - # || throw (let - # got = - # if ! isAttrs group - # then toString group - # else - # group - # |> attrNames - # |> map (name: "${name} = <${typeOf (getAttr name group)}>;") - # |> concatStringsSep " " - # |> (x: "{ ${x} }"); - # in '' - # Cerulean Nexus node "${nodeName}" is a member of a nonexistent group. - # Got "${got}" of primitive type "${typeOf group}". - # NOTE: Groups can be accessed via `self.groups.PATH.TO.YOUR.GROUP` - # ''); - # findImport (root + "/groups/${group._name}")) - # |> nt.prim.unique; # filter by uniqueness groups = assert isList node.groups || throw '' Cerulean Nexus node "${nodeName}" does not declare group membership as a list, got "${typeOf node.groups}" instead! @@ -194,26 +183,10 @@ in { '') # add all inherited groups via _parent |> map (let - delegate = g: let - got = - if ! isAttrs g - then toString g - else - g - |> attrNames - |> map (name: "${name} = <${typeOf (getAttr name g)}>;") - |> concatStringsSep " " - |> (x: "{ ${x} }"); - in - assert g ? _parent - || throw '' - Cerulean Nexus node "${nodeName}" is a member of an incorrectly structured group. - Got "${got}" of primitive type "${typeOf g}". - NOTE: Groups can be accessed via `self.groups.PATH.TO.YOUR.GROUP` - ''; - if g._parent == null - then [g] - else [g] ++ delegate (g._parent); + delegate = g: + if g._parent == null + then [g] + else [g] ++ delegate (g._parent); in delegate) # flatten recursion result @@ -225,7 +198,9 @@ in { # ignore missing groups |> filter pathExists; in - [../nixos-module host] ++ groups ++ node.extraModules; + [../nixos-module host] + ++ groups + ++ node.extraModules; # nix passes these to every single module specialArgs = let @@ -234,8 +209,13 @@ in { inherit (node) system; # XXX: WARNING: TODO: i've stopped caring # XXX: WARNING: TODO: just figure out a better solution to pkgConfig - config.allowUnfree = true; - overlays = self.overlays ++ nexus.overlays ++ node.overlays; + config.allowUnfree = false; + config.allowBroken = false; + overlays = + self.overlays + ++ nexus.overlays + ++ node.overlays + ++ importOverlays root; } // node.extraPkgConfig; in From d760d757bcda5790f3dc983ff008585b4bcf1436 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Fri, 13 Feb 2026 01:46:16 +1000 Subject: [PATCH 255/338] remove old lib --- lib/cdesktop.nix | 149 --------------------- lib/csystem.nix | 330 ----------------------------------------------- lib/cuser.nix | 102 --------------- 3 files changed, 581 deletions(-) delete mode 100644 lib/cdesktop.nix delete mode 100644 lib/csystem.nix delete mode 100644 lib/cuser.nix diff --git a/lib/cdesktop.nix b/lib/cdesktop.nix deleted file mode 100644 index ef2b205..0000000 --- a/lib/cdesktop.nix +++ /dev/null @@ -1,149 +0,0 @@ -# Copyright 2025 Emile Clark-Boman -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -{ - lib, - config, - pkgs, - pkgs-unstable, - ... -} @ args: let - getModule = name: "../modules/homemanager/${name}.nix"; - getModules = map (x: getModule x); -in { - imports = getModules [ - "term/foot" - "editor/vscode" - - "wm/hyprland" - "wm/hyprland/hyprlock" - - "dm/sddm" - "dm/sddm/themes/corners" - - "apps/firefox" - "apps/thunderbird" - "apps/obsidian" - "apps/rider" - "apps/winbox" - "apps/gitkraken" - "apps/thunar" - - "wm/kanshi" - "wm/mako" - ]; - - home = { - pointerCursor = { - gtk.enable = true; - # x11.enable = true # dont enable since im on hyprland - package = pkgs.bibata-cursors; - name = "Bibata-Modern-Ice"; - size = 16; - }; - - packages = with pkgs; [ - # for services.gnome-keyring - ( - if config.cerulean.isGraphical - then seahorse # gui - else null - ) - - fuzzel - ]; - }; - - gtk = { - enable = true; - font.name = "Victor Mono SemiBold 12"; - theme = { - name = "Dracula"; - package = pkgs.dracula-theme; - }; - iconTheme = { - name = "kora"; - package = pkgs.kora-icon-theme; - }; - # TODO: use a variable to mirror this cursor size - # with the `home.pointerCurser.size` - cursorTheme = { - package = pkgs.bibata-cursors; - name = "Bibata-Modern-Ice"; - size = 16; - }; - }; - - qt = { - enable = true; - platformTheme.name = "gtk2"; - style.name = "gtk2"; - }; - - services = { - # Set display manager (login screen) - displayManager = { - # sddm relies on pkgs.libsForQt5.qt5.qtgraphicaleffects - sddm = { - enable = true; - wayland.enable = true; # experimental - theme = "corners"; - }; - defaultSession = - "hyprland" - + ( - if config.programs.hyprland.withUWSM - then "-uwsm" - else null - ); - }; - - # Multimedia Framework - # With backwards compatability for alsa/pulseaudio/jack - pipewire = { - enable = true; - wireplumber.enable = true; - - alsa.enable = true; - alsa.support32Bit = true; - pulse.enable = true; - jack.enable = true; - }; - }; - - # ---- ENVIRONMENT ---- - environment = { - sessionVariables = { - # Hint Electron apps to use support Wayland - NIXOS_OZONE_WL = "1"; - }; - }; - - # ---- SYSTEM PACKAGES ---- - environment.systemPackages = with pkgs; [ - # User Environment - swww - helvum - easyeffects - pavucontrol - hyprpicker # colour picking utility - hyprshot # screenshot utility - qbittorrent - signal-desktop # MAKE THIS ONLY FOR THE DESKTOP FOR END USERS, NOT SERVERS - kdePackages.gwenview # image viewer - libreoffice - wl-clipboard # clipboard for wayland - ]; - - security.rtkit.enable = true; # I *think* this is for pipewire -} diff --git a/lib/csystem.nix b/lib/csystem.nix deleted file mode 100644 index 64e3aa3..0000000 --- a/lib/csystem.nix +++ /dev/null @@ -1,330 +0,0 @@ -# Copyright 2025 Emile Clark-Boman -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -{ - inputs, - lib, - config, - pkgs, - pkgs-unstable, - homemanager, - cerulean, - ... -} @ args: let - getModule = name: "../modules/nixos/${name}.nix"; - getModules = map (x: getModule x); - - getHostModule = name: "TODO"; -in { - imports = getModules [ - (getHostModule "hardware-configuration") - (import "${homemanager}/nixos") - - "shell/bash" - "shell/bash/bashistrans.nix" - "shell/zsh" - "shell/fish" - - "cli/git" - "cli/bat" - "cli/btop" - "cli/tmux" - "cli/nvim" - - "lang/asm" - "lang/bash" # TODO: (YES THIS IS DIFFERENT TO shell/bash, this provides language support ie pkgs.shellcheck) - "lang/c-family" - "lang/dotnet" - # "lang/go" - # "lang/haskell" - # "lang/java" - # "lang/nim" - "lang/python" - # "lang/rust" - # "lang/sage" - - "editor/helix" - ]; - - nix.settings = { - # REF: https://nix.dev/manual/nix/2.24/development/experimental-features - experimental-features = [ - # Significant - "flakes" - "nix-command" - "pipe-operators" - - # Minor - "no-url-literals" - "parse-toml-timestamps" - "recursive-nix" - ]; - - download-buffer-size = 524288000; # 500 MiB - - # making wheel group members "trusted users" allows - # them to import packages not signed by a trusted key - # (aka super duper easier to remote deploy) - trusted-users = ["root" "@wheel"]; - }; - - nixpkgs = { - overlays = cerulean.lib.importOverlaysNixOS; - - config = if config.cerulean.allowUnfreeWhitelist != [] - then { - allowUnfreePredicate = - pkg: builtins.elem - (lib.getName pkg) - config.cerulean.allowUnfreeWhitelist; - } - else { - allowUnfree = config.cerulean.allowUnfree; - }; - }; - - # colmena deployment configuration - deployment = { - targetHost = config.cerulean.domain ?? config.cerulean.ip; - targetUser = "cerulean"; - targetPort = "22"; - sshOptions = [ - "-A" # forward ssh-agent - ]; - buildOnTarget = false; # build locally then deploy - }; - - - time.timeZone = config.cerulean.timeZone; - i18n.defaultLocale = "en_US.UTF-8"; - - # Enable initrd hook for virtual console customisation - # aka cool colours when booting yay!! - console = { - enable = true; - earlySetup = true; # initrd pre hook - keyMap = "us"; - font = "Lat2-Terminus16"; - # ANSI 24-bit color definitions (theme: dracula) - colors = [ - "21222c" - "ff5555" - "50fa7b" - "f1fa8c" - "bd93f9" - "ff79c6" - "8be9fd" - "f8f8f2" - "6272a4" - "ff6e6e" - "69ff94" - "ffffa5" - "d6acff" - "ff92df" - "a4ffff" - "ffffff" - ]; - }; - - # super duper minimum grub2 config - boot.loader = { - efi = { - canTouchEfiVariables = true; - efiSysMountPoint = "/boot/efi"; - }; - - grub = { - enable = true; - device = "nodev"; - }; - - # GitHub: vinceliuice/grub2-themes - grub2-theme = { - enable = true; - theme = "whitesur"; # stylish, vimix, or whitesur - footer = true; - # TODO: switch my cables to switch default grub display - customResolution = "3840x2160"; - }; - }; - - networking = { - hostName = config.cerulean.hostname; - networkmanager.enable = true; - - firewall = { - enable = true; - allowedTCPPorts = [ - 22 # sshd - 80 # nginx (http) - 443 # nginx (https) - # 5678 # MikroTik WinBox - ]; - }; - }; - - # ------- USERS ------- - security.sudo.wheelNeedsPassword = true; - users = { - defaultUserShell = pkgs.bash; - - users = cerulean.lib.importUsersNixOS; - }; - - home-manager = { - users = cerulean.lib.importUsersHomeManager; - - extraSpecialArgs = { inherit inputs pkgs pkgs-unstable; }; - sharedModules = []; - }; - - # ---- ENVIRONMENT ---- - environment = { - # always install "dev"/"man" derivation outputs - extraOutputsToInstall = ["dev" "man"]; - - systemPackages = with pkgs; [ - # User Environment - bluetui - - # Shell - bash - fish - shellcheck - grc # colorise command outputs - moreutils - - # Systems Programming & Compilation - qemu # Fellice Bellard's Quick Emulator - # GNU Utils - gnumake - # Binaries - binutils - strace - ltrace - perf-tools # ftrace + perf - radare2 - gdb - # ASM - nasm - (callPackage ../packages/x86-manpages {}) - # C Family - gcc - clang - clang-tools - - # Rust - cargo - rustc - # Go - go - # Nim - nim - nimble - # Haskell - ghc - ghcid - haskell-language-server - ormolu - - # Python - python312 # I use 3.12 since it's in a pretty stable state now - python314 # also 3.14 for latest features - poetry - - openvpn - inetutils - - # security tools - nmap - - httpie - curlie - zoxide - doggo - tldr - btop - eza - yazi - lazygit - ripgrep - viddy # modern `watch` command - thefuck - - # TODO: once upgraded past Nix-24.07 this line won't be necessary (I think) - # helix will support nixd by default - # SOURCE: https://github.com/nix-community/nixd/blob/main/nixd/docs/editor-setup.md#Helix - # nixd # lsp for nix # DEBUG - - # Pretty necessary - nix-prefetch-git - brightnessctl - acpi - powertop - imagemagick - - # "Standard" Unix Commands - vim - file - wget - tree - pstree - unzip - unrar-free - lz4 - man-pages - man-pages-posix - - # Cryptography - gnupg - openssl - libargon2 - ]; - }; - - programs = { - nix-ld.enable = true; - }; - - documentation = { - enable = true; - doc.enable = true; # install /share/doc packages - man.enable = true; # install manpages - info.enable = true; # install GNU info - dev.enable = true; # install docs intended for developers - nixos = { - enable = true; # install NixOS documentation (ie man -k nix, & nixos-help) - options.splitBuild = true; - # includeAllModules = true; - }; - }; - - virtualisation.docker.enable = true; - - - hardware = { - graphics = { - enable = true; - enable32Bit = true; - }; - - bluetooth = let - btSupported = config.cerulean.bluetoothSupported; - in { - enable = btSupported; - powerOnBoot = btSupported; - }; - }; - - system.stateVersion = config.cerulean.stateVersion; # DO NOT MODIFY -} diff --git a/lib/cuser.nix b/lib/cuser.nix deleted file mode 100644 index f602e03..0000000 --- a/lib/cuser.nix +++ /dev/null @@ -1,102 +0,0 @@ -# Copyright 2025 Emile Clark-Boman -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -{ - lib, - config, - pkgs, - pkgs-unstable, - ... -} @ args: let - getModule = name: "../modules/homemanager/${name}.nix"; - getModules = map (x: getModule x); -in { - imports = getModules [ - "shell/fish" - - "cli/git" - "cli/bat" - "cli/btop" - "cli/tmux" - - "editor/helix" - ]; - - nixpkgs.config.allowUnfreePredicate = pkg: - builtins.elem (lib.GetName pkg) [ - "vscode-extension-ms-dotnettools-csharp" - ]; - - home = { - stateVersion = config.cerulean.stateVersion; # DO NOT MODIFY - - username = config.cerulean.username; - homeDirectory = "/home/${config.cerulean.username}"; - - shellAliases = { - rg = "batgrep"; # bat + ripgrep - man = "batman"; # bat + man - }; - - sessionVariables = { - NIX_SHELL_PRESERVE_PROMPT = 1; - }; - - packages = with pkgs; [ - # for services.gnome-keyring - gcr # provides org.gnome.keyring.SystemPrompter - speedtest-cli - ]; - }; - - programs = { - home-manager.enable = true; - - zsh = { - enable = true; - enableCompletion = true; - autosuggestion.enable = true; - syntaxHighlighting.enable = true; - - history = { - size = 10000; - ignoreAllDups = true; - path = "$HOME/.zsh_history"; - ignorePatterns = [ - "rm *" - ]; - }; - }; - - # set ssh profiles - # NOTE: (IMPORTANT) this DOES NOT start the ssh-agent - # for that you need to use `services.ssh-agent.enable` - ssh = { - enable = true; - forwardAgent = false; - addKeysToAgent = "no"; - }; - }; - - services = { - # enable OpenSSH private key agent - ssh-agent.enable = true; - - gnome-keyring.enable = true; - }; - - # the ssh-agent won't set this for itself... - systemd.user.sessionVariables.SSH_AUTH_SOCK = "$XDG_RUNTIME_DIR/ssh-agent"; - # Nicely reload system units when changing configs - systemd.user.startServices = "sd-switch"; -} From 22d02d49f06cf99f66e5e981c396dc740603c809 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Fri, 13 Feb 2026 01:46:16 +1000 Subject: [PATCH 256/338] remove old lib --- lib/cdesktop.nix | 149 --------------------- lib/csystem.nix | 330 ----------------------------------------------- lib/cuser.nix | 102 --------------- 3 files changed, 581 deletions(-) delete mode 100644 lib/cdesktop.nix delete mode 100644 lib/csystem.nix delete mode 100644 lib/cuser.nix diff --git a/lib/cdesktop.nix b/lib/cdesktop.nix deleted file mode 100644 index ef2b205..0000000 --- a/lib/cdesktop.nix +++ /dev/null @@ -1,149 +0,0 @@ -# Copyright 2025 Emile Clark-Boman -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -{ - lib, - config, - pkgs, - pkgs-unstable, - ... -} @ args: let - getModule = name: "../modules/homemanager/${name}.nix"; - getModules = map (x: getModule x); -in { - imports = getModules [ - "term/foot" - "editor/vscode" - - "wm/hyprland" - "wm/hyprland/hyprlock" - - "dm/sddm" - "dm/sddm/themes/corners" - - "apps/firefox" - "apps/thunderbird" - "apps/obsidian" - "apps/rider" - "apps/winbox" - "apps/gitkraken" - "apps/thunar" - - "wm/kanshi" - "wm/mako" - ]; - - home = { - pointerCursor = { - gtk.enable = true; - # x11.enable = true # dont enable since im on hyprland - package = pkgs.bibata-cursors; - name = "Bibata-Modern-Ice"; - size = 16; - }; - - packages = with pkgs; [ - # for services.gnome-keyring - ( - if config.cerulean.isGraphical - then seahorse # gui - else null - ) - - fuzzel - ]; - }; - - gtk = { - enable = true; - font.name = "Victor Mono SemiBold 12"; - theme = { - name = "Dracula"; - package = pkgs.dracula-theme; - }; - iconTheme = { - name = "kora"; - package = pkgs.kora-icon-theme; - }; - # TODO: use a variable to mirror this cursor size - # with the `home.pointerCurser.size` - cursorTheme = { - package = pkgs.bibata-cursors; - name = "Bibata-Modern-Ice"; - size = 16; - }; - }; - - qt = { - enable = true; - platformTheme.name = "gtk2"; - style.name = "gtk2"; - }; - - services = { - # Set display manager (login screen) - displayManager = { - # sddm relies on pkgs.libsForQt5.qt5.qtgraphicaleffects - sddm = { - enable = true; - wayland.enable = true; # experimental - theme = "corners"; - }; - defaultSession = - "hyprland" - + ( - if config.programs.hyprland.withUWSM - then "-uwsm" - else null - ); - }; - - # Multimedia Framework - # With backwards compatability for alsa/pulseaudio/jack - pipewire = { - enable = true; - wireplumber.enable = true; - - alsa.enable = true; - alsa.support32Bit = true; - pulse.enable = true; - jack.enable = true; - }; - }; - - # ---- ENVIRONMENT ---- - environment = { - sessionVariables = { - # Hint Electron apps to use support Wayland - NIXOS_OZONE_WL = "1"; - }; - }; - - # ---- SYSTEM PACKAGES ---- - environment.systemPackages = with pkgs; [ - # User Environment - swww - helvum - easyeffects - pavucontrol - hyprpicker # colour picking utility - hyprshot # screenshot utility - qbittorrent - signal-desktop # MAKE THIS ONLY FOR THE DESKTOP FOR END USERS, NOT SERVERS - kdePackages.gwenview # image viewer - libreoffice - wl-clipboard # clipboard for wayland - ]; - - security.rtkit.enable = true; # I *think* this is for pipewire -} diff --git a/lib/csystem.nix b/lib/csystem.nix deleted file mode 100644 index 64e3aa3..0000000 --- a/lib/csystem.nix +++ /dev/null @@ -1,330 +0,0 @@ -# Copyright 2025 Emile Clark-Boman -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -{ - inputs, - lib, - config, - pkgs, - pkgs-unstable, - homemanager, - cerulean, - ... -} @ args: let - getModule = name: "../modules/nixos/${name}.nix"; - getModules = map (x: getModule x); - - getHostModule = name: "TODO"; -in { - imports = getModules [ - (getHostModule "hardware-configuration") - (import "${homemanager}/nixos") - - "shell/bash" - "shell/bash/bashistrans.nix" - "shell/zsh" - "shell/fish" - - "cli/git" - "cli/bat" - "cli/btop" - "cli/tmux" - "cli/nvim" - - "lang/asm" - "lang/bash" # TODO: (YES THIS IS DIFFERENT TO shell/bash, this provides language support ie pkgs.shellcheck) - "lang/c-family" - "lang/dotnet" - # "lang/go" - # "lang/haskell" - # "lang/java" - # "lang/nim" - "lang/python" - # "lang/rust" - # "lang/sage" - - "editor/helix" - ]; - - nix.settings = { - # REF: https://nix.dev/manual/nix/2.24/development/experimental-features - experimental-features = [ - # Significant - "flakes" - "nix-command" - "pipe-operators" - - # Minor - "no-url-literals" - "parse-toml-timestamps" - "recursive-nix" - ]; - - download-buffer-size = 524288000; # 500 MiB - - # making wheel group members "trusted users" allows - # them to import packages not signed by a trusted key - # (aka super duper easier to remote deploy) - trusted-users = ["root" "@wheel"]; - }; - - nixpkgs = { - overlays = cerulean.lib.importOverlaysNixOS; - - config = if config.cerulean.allowUnfreeWhitelist != [] - then { - allowUnfreePredicate = - pkg: builtins.elem - (lib.getName pkg) - config.cerulean.allowUnfreeWhitelist; - } - else { - allowUnfree = config.cerulean.allowUnfree; - }; - }; - - # colmena deployment configuration - deployment = { - targetHost = config.cerulean.domain ?? config.cerulean.ip; - targetUser = "cerulean"; - targetPort = "22"; - sshOptions = [ - "-A" # forward ssh-agent - ]; - buildOnTarget = false; # build locally then deploy - }; - - - time.timeZone = config.cerulean.timeZone; - i18n.defaultLocale = "en_US.UTF-8"; - - # Enable initrd hook for virtual console customisation - # aka cool colours when booting yay!! - console = { - enable = true; - earlySetup = true; # initrd pre hook - keyMap = "us"; - font = "Lat2-Terminus16"; - # ANSI 24-bit color definitions (theme: dracula) - colors = [ - "21222c" - "ff5555" - "50fa7b" - "f1fa8c" - "bd93f9" - "ff79c6" - "8be9fd" - "f8f8f2" - "6272a4" - "ff6e6e" - "69ff94" - "ffffa5" - "d6acff" - "ff92df" - "a4ffff" - "ffffff" - ]; - }; - - # super duper minimum grub2 config - boot.loader = { - efi = { - canTouchEfiVariables = true; - efiSysMountPoint = "/boot/efi"; - }; - - grub = { - enable = true; - device = "nodev"; - }; - - # GitHub: vinceliuice/grub2-themes - grub2-theme = { - enable = true; - theme = "whitesur"; # stylish, vimix, or whitesur - footer = true; - # TODO: switch my cables to switch default grub display - customResolution = "3840x2160"; - }; - }; - - networking = { - hostName = config.cerulean.hostname; - networkmanager.enable = true; - - firewall = { - enable = true; - allowedTCPPorts = [ - 22 # sshd - 80 # nginx (http) - 443 # nginx (https) - # 5678 # MikroTik WinBox - ]; - }; - }; - - # ------- USERS ------- - security.sudo.wheelNeedsPassword = true; - users = { - defaultUserShell = pkgs.bash; - - users = cerulean.lib.importUsersNixOS; - }; - - home-manager = { - users = cerulean.lib.importUsersHomeManager; - - extraSpecialArgs = { inherit inputs pkgs pkgs-unstable; }; - sharedModules = []; - }; - - # ---- ENVIRONMENT ---- - environment = { - # always install "dev"/"man" derivation outputs - extraOutputsToInstall = ["dev" "man"]; - - systemPackages = with pkgs; [ - # User Environment - bluetui - - # Shell - bash - fish - shellcheck - grc # colorise command outputs - moreutils - - # Systems Programming & Compilation - qemu # Fellice Bellard's Quick Emulator - # GNU Utils - gnumake - # Binaries - binutils - strace - ltrace - perf-tools # ftrace + perf - radare2 - gdb - # ASM - nasm - (callPackage ../packages/x86-manpages {}) - # C Family - gcc - clang - clang-tools - - # Rust - cargo - rustc - # Go - go - # Nim - nim - nimble - # Haskell - ghc - ghcid - haskell-language-server - ormolu - - # Python - python312 # I use 3.12 since it's in a pretty stable state now - python314 # also 3.14 for latest features - poetry - - openvpn - inetutils - - # security tools - nmap - - httpie - curlie - zoxide - doggo - tldr - btop - eza - yazi - lazygit - ripgrep - viddy # modern `watch` command - thefuck - - # TODO: once upgraded past Nix-24.07 this line won't be necessary (I think) - # helix will support nixd by default - # SOURCE: https://github.com/nix-community/nixd/blob/main/nixd/docs/editor-setup.md#Helix - # nixd # lsp for nix # DEBUG - - # Pretty necessary - nix-prefetch-git - brightnessctl - acpi - powertop - imagemagick - - # "Standard" Unix Commands - vim - file - wget - tree - pstree - unzip - unrar-free - lz4 - man-pages - man-pages-posix - - # Cryptography - gnupg - openssl - libargon2 - ]; - }; - - programs = { - nix-ld.enable = true; - }; - - documentation = { - enable = true; - doc.enable = true; # install /share/doc packages - man.enable = true; # install manpages - info.enable = true; # install GNU info - dev.enable = true; # install docs intended for developers - nixos = { - enable = true; # install NixOS documentation (ie man -k nix, & nixos-help) - options.splitBuild = true; - # includeAllModules = true; - }; - }; - - virtualisation.docker.enable = true; - - - hardware = { - graphics = { - enable = true; - enable32Bit = true; - }; - - bluetooth = let - btSupported = config.cerulean.bluetoothSupported; - in { - enable = btSupported; - powerOnBoot = btSupported; - }; - }; - - system.stateVersion = config.cerulean.stateVersion; # DO NOT MODIFY -} diff --git a/lib/cuser.nix b/lib/cuser.nix deleted file mode 100644 index f602e03..0000000 --- a/lib/cuser.nix +++ /dev/null @@ -1,102 +0,0 @@ -# Copyright 2025 Emile Clark-Boman -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -{ - lib, - config, - pkgs, - pkgs-unstable, - ... -} @ args: let - getModule = name: "../modules/homemanager/${name}.nix"; - getModules = map (x: getModule x); -in { - imports = getModules [ - "shell/fish" - - "cli/git" - "cli/bat" - "cli/btop" - "cli/tmux" - - "editor/helix" - ]; - - nixpkgs.config.allowUnfreePredicate = pkg: - builtins.elem (lib.GetName pkg) [ - "vscode-extension-ms-dotnettools-csharp" - ]; - - home = { - stateVersion = config.cerulean.stateVersion; # DO NOT MODIFY - - username = config.cerulean.username; - homeDirectory = "/home/${config.cerulean.username}"; - - shellAliases = { - rg = "batgrep"; # bat + ripgrep - man = "batman"; # bat + man - }; - - sessionVariables = { - NIX_SHELL_PRESERVE_PROMPT = 1; - }; - - packages = with pkgs; [ - # for services.gnome-keyring - gcr # provides org.gnome.keyring.SystemPrompter - speedtest-cli - ]; - }; - - programs = { - home-manager.enable = true; - - zsh = { - enable = true; - enableCompletion = true; - autosuggestion.enable = true; - syntaxHighlighting.enable = true; - - history = { - size = 10000; - ignoreAllDups = true; - path = "$HOME/.zsh_history"; - ignorePatterns = [ - "rm *" - ]; - }; - }; - - # set ssh profiles - # NOTE: (IMPORTANT) this DOES NOT start the ssh-agent - # for that you need to use `services.ssh-agent.enable` - ssh = { - enable = true; - forwardAgent = false; - addKeysToAgent = "no"; - }; - }; - - services = { - # enable OpenSSH private key agent - ssh-agent.enable = true; - - gnome-keyring.enable = true; - }; - - # the ssh-agent won't set this for itself... - systemd.user.sessionVariables.SSH_AUTH_SOCK = "$XDG_RUNTIME_DIR/ssh-agent"; - # Nicely reload system units when changing configs - systemd.user.startServices = "sd-switch"; -} From 918840fe9d056418cbac602d5b438a345bf526ec Mon Sep 17 00:00:00 2001 From: _cry64 Date: Fri, 13 Feb 2026 01:46:25 +1000 Subject: [PATCH 257/338] progress flake.lock --- flake.lock | 146 ++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 123 insertions(+), 23 deletions(-) diff --git a/flake.lock b/flake.lock index e9a0074..e0b0735 100644 --- a/flake.lock +++ b/flake.lock @@ -36,43 +36,71 @@ "type": "github" } }, - "mix": { + "flake-parts": { "inputs": { - "nib": [ - "nib" + "nixpkgs-lib": [ + "nt", + "nix-unit", + "nixpkgs" ] }, "locked": { - "lastModified": 1768525804, - "narHash": "sha256-jlpNb7Utqfdq2HESAB1mtddWHOsxKlTjPiLFRLd35r8=", - "owner": "emilelcb", - "repo": "mix", - "rev": "617d8915a6518a3d4e375b87c50ae34d9daee6c6", + "lastModified": 1762440070, + "narHash": "sha256-xxdepIcb39UJ94+YydGP221rjnpkDZUlykKuF54PsqI=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "26d05891e14c88eb4a5d5bee659c0db5afb609d8", "type": "github" }, "original": { - "owner": "emilelcb", - "repo": "mix", + "owner": "hercules-ci", + "repo": "flake-parts", "type": "github" } }, - "nib": { + "nix-github-actions": { "inputs": { - "systems": [ - "systems" + "nixpkgs": [ + "nt", + "nix-unit", + "nixpkgs" ] }, "locked": { - "lastModified": 1768472076, - "narHash": "sha256-bdVRCDy6oJx/CZiyxkke783FgtBW//wDuOAITUsQcNc=", - "owner": "emilelcb", - "repo": "nib", - "rev": "42ac66dfc180a13af1cc8850397db66ec5556991", + "lastModified": 1737420293, + "narHash": "sha256-F1G5ifvqTpJq7fdkT34e/Jy9VCyzd5XfJ9TO8fHhJWE=", + "owner": "nix-community", + "repo": "nix-github-actions", + "rev": "f4158fa080ef4503c8f4c820967d946c2af31ec9", "type": "github" }, "original": { - "owner": "emilelcb", - "repo": "nib", + "owner": "nix-community", + "repo": "nix-github-actions", + "type": "github" + } + }, + "nix-unit": { + "inputs": { + "flake-parts": "flake-parts", + "nix-github-actions": "nix-github-actions", + "nixpkgs": [ + "nt", + "nixpkgs" + ], + "treefmt-nix": "treefmt-nix" + }, + "locked": { + "lastModified": 1762774186, + "narHash": "sha256-hRADkHjNt41+JUHw2EiSkMaL4owL83g5ZppjYUdF/Dc=", + "owner": "nix-community", + "repo": "nix-unit", + "rev": "1c9ab50554eed0b768f9e5b6f646d63c9673f0f7", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nix-unit", "type": "github" } }, @@ -124,14 +152,49 @@ "type": "github" } }, + "nixpkgs_3": { + "locked": { + "lastModified": 1767313136, + "narHash": "sha256-16KkgfdYqjaeRGBaYsNrhPRRENs0qzkQVUooNHtoy2w=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "ac62194c3917d5f474c1a844b6fd6da2db95077d", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-25.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "nt": { + "inputs": { + "nix-unit": "nix-unit", + "nixpkgs": "nixpkgs_3", + "systems": "systems_2" + }, + "locked": { + "lastModified": 1770911021, + "narHash": "sha256-8FcXBu0CjeeZeq3uW2h/c5AV+hqELuyPiHsUogOSmZM=", + "owner": "emilelcb", + "repo": "nt", + "rev": "471e1617e8cc0bc749712a7a6af2b2e9f988ffbf", + "type": "github" + }, + "original": { + "owner": "emilelcb", + "repo": "nt", + "type": "github" + } + }, "root": { "inputs": { "deploy-rs": "deploy-rs", - "mix": "mix", - "nib": "nib", "nixpkgs": "nixpkgs_2", "nixpkgs-unstable": "nixpkgs-unstable", - "systems": "systems_2" + "nt": "nt", + "systems": "systems_3" } }, "systems": { @@ -164,6 +227,43 @@ "type": "github" } }, + "systems_3": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "treefmt-nix": { + "inputs": { + "nixpkgs": [ + "nt", + "nix-unit", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1762410071, + "narHash": "sha256-aF5fvoZeoXNPxT0bejFUBXeUjXfHLSL7g+mjR/p5TEg=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "97a30861b13c3731a84e09405414398fbf3e109f", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } + }, "utils": { "inputs": { "systems": "systems" From c07ac05a0d50f05eb57221b4f8173a16404e33ae Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Fri, 13 Feb 2026 01:46:25 +1000 Subject: [PATCH 258/338] progress flake.lock --- flake.lock | 146 ++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 123 insertions(+), 23 deletions(-) diff --git a/flake.lock b/flake.lock index e9a0074..e0b0735 100644 --- a/flake.lock +++ b/flake.lock @@ -36,43 +36,71 @@ "type": "github" } }, - "mix": { + "flake-parts": { "inputs": { - "nib": [ - "nib" + "nixpkgs-lib": [ + "nt", + "nix-unit", + "nixpkgs" ] }, "locked": { - "lastModified": 1768525804, - "narHash": "sha256-jlpNb7Utqfdq2HESAB1mtddWHOsxKlTjPiLFRLd35r8=", - "owner": "emilelcb", - "repo": "mix", - "rev": "617d8915a6518a3d4e375b87c50ae34d9daee6c6", + "lastModified": 1762440070, + "narHash": "sha256-xxdepIcb39UJ94+YydGP221rjnpkDZUlykKuF54PsqI=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "26d05891e14c88eb4a5d5bee659c0db5afb609d8", "type": "github" }, "original": { - "owner": "emilelcb", - "repo": "mix", + "owner": "hercules-ci", + "repo": "flake-parts", "type": "github" } }, - "nib": { + "nix-github-actions": { "inputs": { - "systems": [ - "systems" + "nixpkgs": [ + "nt", + "nix-unit", + "nixpkgs" ] }, "locked": { - "lastModified": 1768472076, - "narHash": "sha256-bdVRCDy6oJx/CZiyxkke783FgtBW//wDuOAITUsQcNc=", - "owner": "emilelcb", - "repo": "nib", - "rev": "42ac66dfc180a13af1cc8850397db66ec5556991", + "lastModified": 1737420293, + "narHash": "sha256-F1G5ifvqTpJq7fdkT34e/Jy9VCyzd5XfJ9TO8fHhJWE=", + "owner": "nix-community", + "repo": "nix-github-actions", + "rev": "f4158fa080ef4503c8f4c820967d946c2af31ec9", "type": "github" }, "original": { - "owner": "emilelcb", - "repo": "nib", + "owner": "nix-community", + "repo": "nix-github-actions", + "type": "github" + } + }, + "nix-unit": { + "inputs": { + "flake-parts": "flake-parts", + "nix-github-actions": "nix-github-actions", + "nixpkgs": [ + "nt", + "nixpkgs" + ], + "treefmt-nix": "treefmt-nix" + }, + "locked": { + "lastModified": 1762774186, + "narHash": "sha256-hRADkHjNt41+JUHw2EiSkMaL4owL83g5ZppjYUdF/Dc=", + "owner": "nix-community", + "repo": "nix-unit", + "rev": "1c9ab50554eed0b768f9e5b6f646d63c9673f0f7", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nix-unit", "type": "github" } }, @@ -124,14 +152,49 @@ "type": "github" } }, + "nixpkgs_3": { + "locked": { + "lastModified": 1767313136, + "narHash": "sha256-16KkgfdYqjaeRGBaYsNrhPRRENs0qzkQVUooNHtoy2w=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "ac62194c3917d5f474c1a844b6fd6da2db95077d", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-25.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "nt": { + "inputs": { + "nix-unit": "nix-unit", + "nixpkgs": "nixpkgs_3", + "systems": "systems_2" + }, + "locked": { + "lastModified": 1770911021, + "narHash": "sha256-8FcXBu0CjeeZeq3uW2h/c5AV+hqELuyPiHsUogOSmZM=", + "owner": "emilelcb", + "repo": "nt", + "rev": "471e1617e8cc0bc749712a7a6af2b2e9f988ffbf", + "type": "github" + }, + "original": { + "owner": "emilelcb", + "repo": "nt", + "type": "github" + } + }, "root": { "inputs": { "deploy-rs": "deploy-rs", - "mix": "mix", - "nib": "nib", "nixpkgs": "nixpkgs_2", "nixpkgs-unstable": "nixpkgs-unstable", - "systems": "systems_2" + "nt": "nt", + "systems": "systems_3" } }, "systems": { @@ -164,6 +227,43 @@ "type": "github" } }, + "systems_3": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "treefmt-nix": { + "inputs": { + "nixpkgs": [ + "nt", + "nix-unit", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1762410071, + "narHash": "sha256-aF5fvoZeoXNPxT0bejFUBXeUjXfHLSL7g+mjR/p5TEg=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "97a30861b13c3731a84e09405414398fbf3e109f", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } + }, "utils": { "inputs": { "systems": "systems" From bc5e1ec464b21f082d9e38c476c7bf9337754804 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Fri, 13 Feb 2026 02:35:35 +1000 Subject: [PATCH 259/338] duplicate node opts to nexus --- cerulean/nexus/nexus.nix | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index 12ad933..116b8ce 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -57,6 +57,8 @@ overlays = []; extraModules = []; specialArgs = Terminal {}; + # XXX: WARNING: extraPkgConfig is a terrible solution (but im lazy for now) + extraPkgConfig = Terminal {}; groups = Terminal {}; nodes = Terminal {}; @@ -200,7 +202,8 @@ in { in [../nixos-module host] ++ groups - ++ node.extraModules; + ++ node.extraModules + ++ nexus.extraModules; # nix passes these to every single module specialArgs = let @@ -217,9 +220,11 @@ in { ++ node.overlays ++ importOverlays root; } - // node.extraPkgConfig; + // nexus.extraPkgConfig # TODO: import + // node.extraPkgConfig; # TODO: import in - node.specialArgs + nexus.specialArgs + // node.specialArgs // { inherit root; pkgs = import nixpkgs pkgConfig; From b151f45dfbdb0849d3c24940af946f11652dd8bf Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Fri, 13 Feb 2026 02:35:35 +1000 Subject: [PATCH 260/338] duplicate node opts to nexus --- cerulean/nexus/nexus.nix | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index 12ad933..116b8ce 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -57,6 +57,8 @@ overlays = []; extraModules = []; specialArgs = Terminal {}; + # XXX: WARNING: extraPkgConfig is a terrible solution (but im lazy for now) + extraPkgConfig = Terminal {}; groups = Terminal {}; nodes = Terminal {}; @@ -200,7 +202,8 @@ in { in [../nixos-module host] ++ groups - ++ node.extraModules; + ++ node.extraModules + ++ nexus.extraModules; # nix passes these to every single module specialArgs = let @@ -217,9 +220,11 @@ in { ++ node.overlays ++ importOverlays root; } - // node.extraPkgConfig; + // nexus.extraPkgConfig # TODO: import + // node.extraPkgConfig; # TODO: import in - node.specialArgs + nexus.specialArgs + // node.specialArgs // { inherit root; pkgs = import nixpkgs pkgConfig; From 8370d8f7757115431adc5f6bba8c9424b4148bb4 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Fri, 13 Feb 2026 02:53:03 +1000 Subject: [PATCH 261/338] should i rewrite nixpkgs.lib.nixosSystem? --- cerulean/nexus/nexus.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index 116b8ce..edc9361 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -207,6 +207,8 @@ in { # nix passes these to every single module specialArgs = let + # XXX: NOTE: REF: https://github.com/NixOS/nixpkgs/blob/master/flake.nix#L57 + # XXX: NOTE: lib.nixosSystem is defined here ^^^^ pkgConfig = { inherit (node) system; From 66c10fdb59fcc0778a9d2e35c2ec0698b51c245b Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Fri, 13 Feb 2026 02:53:03 +1000 Subject: [PATCH 262/338] should i rewrite nixpkgs.lib.nixosSystem? --- cerulean/nexus/nexus.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index 116b8ce..edc9361 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -207,6 +207,8 @@ in { # nix passes these to every single module specialArgs = let + # XXX: NOTE: REF: https://github.com/NixOS/nixpkgs/blob/master/flake.nix#L57 + # XXX: NOTE: lib.nixosSystem is defined here ^^^^ pkgConfig = { inherit (node) system; From 2512fc1c53e3f2ed17442bd32e357e08e1e6770b Mon Sep 17 00:00:00 2001 From: _cry64 Date: Fri, 13 Feb 2026 03:27:21 +1000 Subject: [PATCH 263/338] add TODO.md --- cerulean/TODO.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 cerulean/TODO.md diff --git a/cerulean/TODO.md b/cerulean/TODO.md new file mode 100644 index 0000000..2a08659 --- /dev/null +++ b/cerulean/TODO.md @@ -0,0 +1,2 @@ +- [ ] extend the options.nixpkgs to allow any number of package repositories!! +- [ ] auto-propagate the same specialArgs for hosts to home-manager From 54512e63996e6d73b716094952fede5b36a835c6 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Fri, 13 Feb 2026 03:27:21 +1000 Subject: [PATCH 264/338] add TODO.md --- cerulean/TODO.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 cerulean/TODO.md diff --git a/cerulean/TODO.md b/cerulean/TODO.md new file mode 100644 index 0000000..2a08659 --- /dev/null +++ b/cerulean/TODO.md @@ -0,0 +1,2 @@ +- [ ] extend the options.nixpkgs to allow any number of package repositories!! +- [ ] auto-propagate the same specialArgs for hosts to home-manager From 7aadb62077a89a1069b9f4d7470479cf53471c6f Mon Sep 17 00:00:00 2001 From: _cry64 Date: Fri, 13 Feb 2026 03:28:03 +1000 Subject: [PATCH 265/338] install deploy-rs also begin adding support for multiple pkgs repos --- cerulean/nixos-module/config.nix | 14 --------- cerulean/nixos-module/default.nix | 10 +++++-- cerulean/nixos-module/nixpkgs.nix | 48 +++++++++++++++++++++++++++++++ cerulean/nixos-module/options.nix | 20 ------------- 4 files changed, 55 insertions(+), 37 deletions(-) delete mode 100644 cerulean/nixos-module/config.nix create mode 100644 cerulean/nixos-module/nixpkgs.nix delete mode 100644 cerulean/nixos-module/options.nix diff --git a/cerulean/nixos-module/config.nix b/cerulean/nixos-module/config.nix deleted file mode 100644 index 26d459c..0000000 --- a/cerulean/nixos-module/config.nix +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright 2026 Emile Clark-Boman -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -{...}: {} diff --git a/cerulean/nixos-module/default.nix b/cerulean/nixos-module/default.nix index cc92212..e7561ee 100644 --- a/cerulean/nixos-module/default.nix +++ b/cerulean/nixos-module/default.nix @@ -11,8 +11,12 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -{...} @ inputs: { - options = import ./options.nix inputs; +{deploy-rs, ...}: { + imports = [ + # ./nixpkgs.nix + ]; - config = import ./config.nix inputs; + environment.systemPackages = [ + deploy-rs.packages.default + ]; } diff --git a/cerulean/nixos-module/nixpkgs.nix b/cerulean/nixos-module/nixpkgs.nix new file mode 100644 index 0000000..f4bb2ae --- /dev/null +++ b/cerulean/nixos-module/nixpkgs.nix @@ -0,0 +1,48 @@ +# Copyright 2026 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +{ + lib, + config, + ... +}: let + cfg = config.pkgrepo; +in { + options.pkgrepo = lib.mkOption { + type = lib.types.attrsOf lib.types.attrs; + default = {}; + description = "Declare and import custom package repositories."; + example = { + "pkgs" = { + source = "inputs.nixpkgs"; + system = "x86-64-linux"; + config = { + allowUnfree = true; + allowBroken = false; + }; + }; + }; + }; + + config.nixpkgs = + lib.mkIf (cfg ? pkgs) + (let + pkgs = cfg.pkgs; + in + lib.mkForce ( + (builtins.removeAttrs pkgs ["source"]) + // { + flake.source = pkgs.source; + } + )); +} diff --git a/cerulean/nixos-module/options.nix b/cerulean/nixos-module/options.nix deleted file mode 100644 index 8fb6a46..0000000 --- a/cerulean/nixos-module/options.nix +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright 2026 Emile Clark-Boman -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -{lib, ...}: { - cerulean.nexus.node = { - group = lib.mkOption { - type = lib.types.enum; - }; - }; -} From 71ffa820a9035c5996503912412797b66aedf2dd Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Fri, 13 Feb 2026 03:28:03 +1000 Subject: [PATCH 266/338] install deploy-rs also begin adding support for multiple pkgs repos --- cerulean/nixos-module/config.nix | 14 --------- cerulean/nixos-module/default.nix | 10 +++++-- cerulean/nixos-module/nixpkgs.nix | 48 +++++++++++++++++++++++++++++++ cerulean/nixos-module/options.nix | 20 ------------- 4 files changed, 55 insertions(+), 37 deletions(-) delete mode 100644 cerulean/nixos-module/config.nix create mode 100644 cerulean/nixos-module/nixpkgs.nix delete mode 100644 cerulean/nixos-module/options.nix diff --git a/cerulean/nixos-module/config.nix b/cerulean/nixos-module/config.nix deleted file mode 100644 index 26d459c..0000000 --- a/cerulean/nixos-module/config.nix +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright 2026 Emile Clark-Boman -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -{...}: {} diff --git a/cerulean/nixos-module/default.nix b/cerulean/nixos-module/default.nix index cc92212..e7561ee 100644 --- a/cerulean/nixos-module/default.nix +++ b/cerulean/nixos-module/default.nix @@ -11,8 +11,12 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -{...} @ inputs: { - options = import ./options.nix inputs; +{deploy-rs, ...}: { + imports = [ + # ./nixpkgs.nix + ]; - config = import ./config.nix inputs; + environment.systemPackages = [ + deploy-rs.packages.default + ]; } diff --git a/cerulean/nixos-module/nixpkgs.nix b/cerulean/nixos-module/nixpkgs.nix new file mode 100644 index 0000000..f4bb2ae --- /dev/null +++ b/cerulean/nixos-module/nixpkgs.nix @@ -0,0 +1,48 @@ +# Copyright 2026 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +{ + lib, + config, + ... +}: let + cfg = config.pkgrepo; +in { + options.pkgrepo = lib.mkOption { + type = lib.types.attrsOf lib.types.attrs; + default = {}; + description = "Declare and import custom package repositories."; + example = { + "pkgs" = { + source = "inputs.nixpkgs"; + system = "x86-64-linux"; + config = { + allowUnfree = true; + allowBroken = false; + }; + }; + }; + }; + + config.nixpkgs = + lib.mkIf (cfg ? pkgs) + (let + pkgs = cfg.pkgs; + in + lib.mkForce ( + (builtins.removeAttrs pkgs ["source"]) + // { + flake.source = pkgs.source; + } + )); +} diff --git a/cerulean/nixos-module/options.nix b/cerulean/nixos-module/options.nix deleted file mode 100644 index 8fb6a46..0000000 --- a/cerulean/nixos-module/options.nix +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright 2026 Emile Clark-Boman -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -{lib, ...}: { - cerulean.nexus.node = { - group = lib.mkOption { - type = lib.types.enum; - }; - }; -} From c2803cd03821d6233fd9223771a989489bf70d07 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Fri, 13 Feb 2026 03:55:47 +1000 Subject: [PATCH 267/338] auto-propagate home-manager options --- cerulean/nixos-module/default.nix | 3 ++- cerulean/nixos-module/home-manager.nix | 29 ++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 cerulean/nixos-module/home-manager.nix diff --git a/cerulean/nixos-module/default.nix b/cerulean/nixos-module/default.nix index e7561ee..3bf3a9c 100644 --- a/cerulean/nixos-module/default.nix +++ b/cerulean/nixos-module/default.nix @@ -13,7 +13,8 @@ # limitations under the License. {deploy-rs, ...}: { imports = [ - # ./nixpkgs.nix + ./nixpkgs.nix + ./home-manager.nix ]; environment.systemPackages = [ diff --git a/cerulean/nixos-module/home-manager.nix b/cerulean/nixos-module/home-manager.nix new file mode 100644 index 0000000..6833e1d --- /dev/null +++ b/cerulean/nixos-module/home-manager.nix @@ -0,0 +1,29 @@ +# Copyright 2026 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +{ + root, + config, + lib, + ... +} @ args: { + home-manager = { + users = + config.users.users + |> builtins.attrNames + |> builtins.filter (x: builtins.pathExists (root + "/homes/${x}")) + |> (x: lib.genAttrs x (y: import (root + "/homes/${y}"))); + + extraSpecialArgs = args; + }; +} From 4bce8ee7b2c724b746417610f772a9cfe1eab4fb Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Fri, 13 Feb 2026 03:55:47 +1000 Subject: [PATCH 268/338] auto-propagate home-manager options --- cerulean/nixos-module/default.nix | 3 ++- cerulean/nixos-module/home-manager.nix | 29 ++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 cerulean/nixos-module/home-manager.nix diff --git a/cerulean/nixos-module/default.nix b/cerulean/nixos-module/default.nix index e7561ee..3bf3a9c 100644 --- a/cerulean/nixos-module/default.nix +++ b/cerulean/nixos-module/default.nix @@ -13,7 +13,8 @@ # limitations under the License. {deploy-rs, ...}: { imports = [ - # ./nixpkgs.nix + ./nixpkgs.nix + ./home-manager.nix ]; environment.systemPackages = [ diff --git a/cerulean/nixos-module/home-manager.nix b/cerulean/nixos-module/home-manager.nix new file mode 100644 index 0000000..6833e1d --- /dev/null +++ b/cerulean/nixos-module/home-manager.nix @@ -0,0 +1,29 @@ +# Copyright 2026 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +{ + root, + config, + lib, + ... +} @ args: { + home-manager = { + users = + config.users.users + |> builtins.attrNames + |> builtins.filter (x: builtins.pathExists (root + "/homes/${x}")) + |> (x: lib.genAttrs x (y: import (root + "/homes/${y}"))); + + extraSpecialArgs = args; + }; +} From b56c07bc4cae8e887e7c7f6e62fa6c664f95d97c Mon Sep 17 00:00:00 2001 From: _cry64 Date: Fri, 13 Feb 2026 12:08:02 +1000 Subject: [PATCH 269/338] update TODO + upstream --- TODO.md | 3 +++ cerulean/TODO.md | 2 -- flake.nix | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) delete mode 100644 cerulean/TODO.md diff --git a/TODO.md b/TODO.md index c43e8c6..25f6f1b 100755 --- a/TODO.md +++ b/TODO.md @@ -3,3 +3,6 @@ also rename `Cerulean` to `cerulean` in Nix to maintain the naming convention. Using `flake-parts` ensures Cerulean is usable without restricting yourself only to the Cerulean ecosystem. + +- [ ] extend the options.nixpkgs to allow any number of package repositories!! +- [x] auto-propagate the same specialArgs for hosts to home-manager diff --git a/cerulean/TODO.md b/cerulean/TODO.md deleted file mode 100644 index 2a08659..0000000 --- a/cerulean/TODO.md +++ /dev/null @@ -1,2 +0,0 @@ -- [ ] extend the options.nixpkgs to allow any number of package repositories!! -- [ ] auto-propagate the same specialArgs for hosts to home-manager diff --git a/flake.nix b/flake.nix index 94f4b33..ff430bc 100644 --- a/flake.nix +++ b/flake.nix @@ -20,7 +20,7 @@ nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"; nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixos-unstable"; - nt.url = "github:emilelcb/nt"; + nt.url = "github:cry128/nt"; deploy-rs.url = "github:serokell/deploy-rs"; }; From 135f2fb996aea4bb1bb287383e58c438fedfe23d Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Fri, 13 Feb 2026 12:08:02 +1000 Subject: [PATCH 270/338] update TODO + upstream --- TODO.md | 3 +++ cerulean/TODO.md | 2 -- flake.nix | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) delete mode 100644 cerulean/TODO.md diff --git a/TODO.md b/TODO.md index c43e8c6..25f6f1b 100755 --- a/TODO.md +++ b/TODO.md @@ -3,3 +3,6 @@ also rename `Cerulean` to `cerulean` in Nix to maintain the naming convention. Using `flake-parts` ensures Cerulean is usable without restricting yourself only to the Cerulean ecosystem. + +- [ ] extend the options.nixpkgs to allow any number of package repositories!! +- [x] auto-propagate the same specialArgs for hosts to home-manager diff --git a/cerulean/TODO.md b/cerulean/TODO.md deleted file mode 100644 index 2a08659..0000000 --- a/cerulean/TODO.md +++ /dev/null @@ -1,2 +0,0 @@ -- [ ] extend the options.nixpkgs to allow any number of package repositories!! -- [ ] auto-propagate the same specialArgs for hosts to home-manager diff --git a/flake.nix b/flake.nix index 94f4b33..ff430bc 100644 --- a/flake.nix +++ b/flake.nix @@ -20,7 +20,7 @@ nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"; nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixos-unstable"; - nt.url = "github:emilelcb/nt"; + nt.url = "github:cry128/nt"; deploy-rs.url = "github:serokell/deploy-rs"; }; From 542f2ff3f49adc188835e9d2da5fa3998d141c0e Mon Sep 17 00:00:00 2001 From: _cry64 Date: Fri, 13 Feb 2026 12:38:53 +1000 Subject: [PATCH 271/338] add support for multiple pkg channels --- cerulean/nexus/nexus.nix | 161 ++++++++++++------------------ cerulean/nexus/nodes.nix | 2 - cerulean/nixos-module/default.nix | 2 +- cerulean/nixos-module/nixpkgs.nix | 61 ++++++++--- 4 files changed, 111 insertions(+), 115 deletions(-) diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index edc9361..4c757ba 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -12,10 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. { - self, this, - nixpkgs, - nixpkgs-unstable, nt, lib, deploy-rs, @@ -42,6 +39,11 @@ mapNodes ; + inherit + (nt) + findImport + ; + templateNexus = let inherit (nt.naive.terminal) @@ -54,11 +56,8 @@ Ensure `nexus.${path}` exists under your call to `cerulean.mkNexus`. ''); in { - overlays = []; extraModules = []; specialArgs = Terminal {}; - # XXX: WARNING: extraPkgConfig is a terrible solution (but im lazy for now) - extraPkgConfig = Terminal {}; groups = Terminal {}; nodes = Terminal {}; @@ -71,7 +70,8 @@ isAttrs g || throw '' Cerulean Nexus groups must be provided as attribute sets, got "${typeOf g}" instead! - Ensure all the `groups` definitions are attribute sets under your call to `cerulean.mkNexus`. + Ensure all the group definitions are attribute sets under your call to `cerulean.mkNexus`. + NOTE: Groups can be accessed via `self.groups.PATH.TO.YOUR.GROUP` ''; delegate = parent: gName: g: let result = @@ -126,20 +126,49 @@ in final; - importOverlays = root: let - path = findImport (root + "/overlay"); - in - if pathExists path - then import path - else []; - - # XXX: TODO: create a function in NixTypes that handles this instead - findImport = path: - if pathExists path - then path - else if pathExists (path + "default.nix") - then path + "/default.nix" - else path + ".nix"; + getGroupModules = root: nodeName: node: + assert isList node.groups + || throw '' + Cerulean Nexus node "${nodeName}" does not declare group membership as a list, got "${typeOf node.groups}" instead! + Ensure `nexus.nodes.${nodeName}.groups` is a list under your call to `cerulean.mkNexus`. + ''; + node.groups + # ensure all members are actually groups + |> map (group: let + got = + if ! isAttrs group + then toString group + else + group + |> attrNames + |> map (name: "${name} = <${typeOf (getAttr name group)}>;") + |> concatStringsSep " " + |> (x: "{ ${x} }"); + in + if group ? _name + then group + else + throw '' + Cerulean Nexus node "${nodeName}" is a member of an incorrectly structured group. + Got "${got}" of primitive type "${typeOf group}". + NOTE: Groups can be accessed via `self.groups.PATH.TO.YOUR.GROUP` + '') + # add all inherited groups via _parent + |> map (let + delegate = g: + if g._parent == null + then [g] + else [g] ++ delegate (g._parent); + in + delegate) + # flatten recursion result + |> concatLists + # find import location + |> map (group: findImport (root + "/groups/${group._name}")) + # filter by uniqueness + |> nt.prim.unique + # ignore missing groups + |> filter pathExists; in { mkNexus = root: outputsBuilder: let decl = parseDecl outputsBuilder; @@ -152,87 +181,21 @@ in { outputs = rec { nixosConfigurations = mapNodes nexus.nodes ( - nodeName: node: - lib.nixosSystem { + nodeName: node: let + nixosDecl = lib.nixosSystem { system = node.system; - modules = let - host = findImport (root + "/hosts/${nodeName}"); - groups = assert isList node.groups - || throw '' - Cerulean Nexus node "${nodeName}" does not declare group membership as a list, got "${typeOf node.groups}" instead! - Ensure `nexus.nodes.${nodeName}.groups` is a list under your call to `cerulean.mkNexus`. - ''; - node.groups - # ensure all members are actually groups - |> map (group: let - got = - if ! isAttrs group - then toString group - else - group - |> attrNames - |> map (name: "${name} = <${typeOf (getAttr name group)}>;") - |> concatStringsSep " " - |> (x: "{ ${x} }"); - in - if group ? _name - then group - else - throw '' - Cerulean Nexus node "${nodeName}" is a member of an incorrectly structured group. - Got "${got}" of primitive type "${typeOf group}". - NOTE: Groups can be accessed via `self.groups.PATH.TO.YOUR.GROUP` - '') - # add all inherited groups via _parent - |> map (let - delegate = g: - if g._parent == null - then [g] - else [g] ++ delegate (g._parent); - in - delegate) - # flatten recursion result - |> concatLists - # find import location - |> map (group: findImport (root + "/groups/${group._name}")) - # filter by uniqueness - |> nt.prim.unique - # ignore missing groups - |> filter pathExists; - in - [../nixos-module host] - ++ groups - ++ node.extraModules - ++ nexus.extraModules; - - # nix passes these to every single module - specialArgs = let - # XXX: NOTE: REF: https://github.com/NixOS/nixpkgs/blob/master/flake.nix#L57 - # XXX: NOTE: lib.nixosSystem is defined here ^^^^ - pkgConfig = - { - inherit (node) system; - # XXX: WARNING: TODO: i've stopped caring - # XXX: WARNING: TODO: just figure out a better solution to pkgConfig - config.allowUnfree = false; - config.allowBroken = false; - overlays = - self.overlays - ++ nexus.overlays - ++ node.overlays - ++ importOverlays root; - } - // nexus.extraPkgConfig # TODO: import - // node.extraPkgConfig; # TODO: import - in + specialArgs = nexus.specialArgs // node.specialArgs - // { - inherit root; - pkgs = import nixpkgs pkgConfig; - upkgs = import nixpkgs-unstable pkgConfig; - }; - } + // {inherit root;}; + modules = + [../nixos-module (findImport (root + "/hosts/${nodeName}"))] + ++ getGroupModules root nodeName node + ++ node.extraModules + ++ nexus.extraModules; + }; + in + nixosDecl ); deploy.nodes = mapNodes nexus.nodes (nodeName: node: let diff --git a/cerulean/nexus/nodes.nix b/cerulean/nexus/nodes.nix index 25a1d02..3d49434 100644 --- a/cerulean/nexus/nodes.nix +++ b/cerulean/nexus/nodes.nix @@ -31,8 +31,6 @@ in rec { extraModules = []; specialArgs = Terminal {}; overlays = []; - # XXX: WARNING: extraPkgConfig is a terrible solution (but im lazy for now) - extraPkgConfig = Terminal {}; deploy = { user = "root"; diff --git a/cerulean/nixos-module/default.nix b/cerulean/nixos-module/default.nix index 3bf3a9c..30e1967 100644 --- a/cerulean/nixos-module/default.nix +++ b/cerulean/nixos-module/default.nix @@ -11,7 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -{deploy-rs, ...}: { +{deploy-rs}: { imports = [ ./nixpkgs.nix ./home-manager.nix diff --git a/cerulean/nixos-module/nixpkgs.nix b/cerulean/nixos-module/nixpkgs.nix index f4bb2ae..ee4ef55 100644 --- a/cerulean/nixos-module/nixpkgs.nix +++ b/cerulean/nixos-module/nixpkgs.nix @@ -12,13 +12,24 @@ # See the License for the specific language governing permissions and # limitations under the License. { + nt, lib, config, ... }: let - cfg = config.pkgrepo; + inherit + (builtins) + mapAttrs + ; + + inherit + (nt) + flip + ; + + cfg = config.pkgsrc; in { - options.pkgrepo = lib.mkOption { + options.pkgsrc = lib.mkOption { type = lib.types.attrsOf lib.types.attrs; default = {}; description = "Declare and import custom package repositories."; @@ -34,15 +45,39 @@ in { }; }; - config.nixpkgs = - lib.mkIf (cfg ? pkgs) - (let - pkgs = cfg.pkgs; - in - lib.mkForce ( - (builtins.removeAttrs pkgs ["source"]) - // { - flake.source = pkgs.source; - } - )); + config = let + # TODO: use lib.types.submodule to restrict what options + # TODO: can be given to pkgsrc + repos = + cfg + |> mapAttrs ( + name: args: + assert args ? source + || abort '' + ${./.} + `pkgsrc.${name} missing required attribute "source"` + ''; + args + |> flip removeAttrs ["source"] + |> import args.source + ); + in { + # NOTE: _module.args is a special option that allows us to + # NOTE: set extend specialArgs from inside the modules. + # "pkgs" is unique since the nix module system already handles it + _module.args = + removeAttrs repos ["pkgs"]; + + # nixpkgs = + # lib.mkIf (cfg ? pkgs) + # (let + # pkgs = cfg.pkgs; + # in + # lib.mkForce ( + # (removeAttrs pkgs ["source"]) + # // { + # flake.source = pkgs.source; + # } + # )); + }; } From 7591af6e3a56fbc650f4dd7db10f8a747cbb022f Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Fri, 13 Feb 2026 12:38:53 +1000 Subject: [PATCH 272/338] add support for multiple pkg channels --- cerulean/nexus/nexus.nix | 161 ++++++++++++------------------ cerulean/nexus/nodes.nix | 2 - cerulean/nixos-module/default.nix | 2 +- cerulean/nixos-module/nixpkgs.nix | 61 ++++++++--- 4 files changed, 111 insertions(+), 115 deletions(-) diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index edc9361..4c757ba 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -12,10 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. { - self, this, - nixpkgs, - nixpkgs-unstable, nt, lib, deploy-rs, @@ -42,6 +39,11 @@ mapNodes ; + inherit + (nt) + findImport + ; + templateNexus = let inherit (nt.naive.terminal) @@ -54,11 +56,8 @@ Ensure `nexus.${path}` exists under your call to `cerulean.mkNexus`. ''); in { - overlays = []; extraModules = []; specialArgs = Terminal {}; - # XXX: WARNING: extraPkgConfig is a terrible solution (but im lazy for now) - extraPkgConfig = Terminal {}; groups = Terminal {}; nodes = Terminal {}; @@ -71,7 +70,8 @@ isAttrs g || throw '' Cerulean Nexus groups must be provided as attribute sets, got "${typeOf g}" instead! - Ensure all the `groups` definitions are attribute sets under your call to `cerulean.mkNexus`. + Ensure all the group definitions are attribute sets under your call to `cerulean.mkNexus`. + NOTE: Groups can be accessed via `self.groups.PATH.TO.YOUR.GROUP` ''; delegate = parent: gName: g: let result = @@ -126,20 +126,49 @@ in final; - importOverlays = root: let - path = findImport (root + "/overlay"); - in - if pathExists path - then import path - else []; - - # XXX: TODO: create a function in NixTypes that handles this instead - findImport = path: - if pathExists path - then path - else if pathExists (path + "default.nix") - then path + "/default.nix" - else path + ".nix"; + getGroupModules = root: nodeName: node: + assert isList node.groups + || throw '' + Cerulean Nexus node "${nodeName}" does not declare group membership as a list, got "${typeOf node.groups}" instead! + Ensure `nexus.nodes.${nodeName}.groups` is a list under your call to `cerulean.mkNexus`. + ''; + node.groups + # ensure all members are actually groups + |> map (group: let + got = + if ! isAttrs group + then toString group + else + group + |> attrNames + |> map (name: "${name} = <${typeOf (getAttr name group)}>;") + |> concatStringsSep " " + |> (x: "{ ${x} }"); + in + if group ? _name + then group + else + throw '' + Cerulean Nexus node "${nodeName}" is a member of an incorrectly structured group. + Got "${got}" of primitive type "${typeOf group}". + NOTE: Groups can be accessed via `self.groups.PATH.TO.YOUR.GROUP` + '') + # add all inherited groups via _parent + |> map (let + delegate = g: + if g._parent == null + then [g] + else [g] ++ delegate (g._parent); + in + delegate) + # flatten recursion result + |> concatLists + # find import location + |> map (group: findImport (root + "/groups/${group._name}")) + # filter by uniqueness + |> nt.prim.unique + # ignore missing groups + |> filter pathExists; in { mkNexus = root: outputsBuilder: let decl = parseDecl outputsBuilder; @@ -152,87 +181,21 @@ in { outputs = rec { nixosConfigurations = mapNodes nexus.nodes ( - nodeName: node: - lib.nixosSystem { + nodeName: node: let + nixosDecl = lib.nixosSystem { system = node.system; - modules = let - host = findImport (root + "/hosts/${nodeName}"); - groups = assert isList node.groups - || throw '' - Cerulean Nexus node "${nodeName}" does not declare group membership as a list, got "${typeOf node.groups}" instead! - Ensure `nexus.nodes.${nodeName}.groups` is a list under your call to `cerulean.mkNexus`. - ''; - node.groups - # ensure all members are actually groups - |> map (group: let - got = - if ! isAttrs group - then toString group - else - group - |> attrNames - |> map (name: "${name} = <${typeOf (getAttr name group)}>;") - |> concatStringsSep " " - |> (x: "{ ${x} }"); - in - if group ? _name - then group - else - throw '' - Cerulean Nexus node "${nodeName}" is a member of an incorrectly structured group. - Got "${got}" of primitive type "${typeOf group}". - NOTE: Groups can be accessed via `self.groups.PATH.TO.YOUR.GROUP` - '') - # add all inherited groups via _parent - |> map (let - delegate = g: - if g._parent == null - then [g] - else [g] ++ delegate (g._parent); - in - delegate) - # flatten recursion result - |> concatLists - # find import location - |> map (group: findImport (root + "/groups/${group._name}")) - # filter by uniqueness - |> nt.prim.unique - # ignore missing groups - |> filter pathExists; - in - [../nixos-module host] - ++ groups - ++ node.extraModules - ++ nexus.extraModules; - - # nix passes these to every single module - specialArgs = let - # XXX: NOTE: REF: https://github.com/NixOS/nixpkgs/blob/master/flake.nix#L57 - # XXX: NOTE: lib.nixosSystem is defined here ^^^^ - pkgConfig = - { - inherit (node) system; - # XXX: WARNING: TODO: i've stopped caring - # XXX: WARNING: TODO: just figure out a better solution to pkgConfig - config.allowUnfree = false; - config.allowBroken = false; - overlays = - self.overlays - ++ nexus.overlays - ++ node.overlays - ++ importOverlays root; - } - // nexus.extraPkgConfig # TODO: import - // node.extraPkgConfig; # TODO: import - in + specialArgs = nexus.specialArgs // node.specialArgs - // { - inherit root; - pkgs = import nixpkgs pkgConfig; - upkgs = import nixpkgs-unstable pkgConfig; - }; - } + // {inherit root;}; + modules = + [../nixos-module (findImport (root + "/hosts/${nodeName}"))] + ++ getGroupModules root nodeName node + ++ node.extraModules + ++ nexus.extraModules; + }; + in + nixosDecl ); deploy.nodes = mapNodes nexus.nodes (nodeName: node: let diff --git a/cerulean/nexus/nodes.nix b/cerulean/nexus/nodes.nix index 25a1d02..3d49434 100644 --- a/cerulean/nexus/nodes.nix +++ b/cerulean/nexus/nodes.nix @@ -31,8 +31,6 @@ in rec { extraModules = []; specialArgs = Terminal {}; overlays = []; - # XXX: WARNING: extraPkgConfig is a terrible solution (but im lazy for now) - extraPkgConfig = Terminal {}; deploy = { user = "root"; diff --git a/cerulean/nixos-module/default.nix b/cerulean/nixos-module/default.nix index 3bf3a9c..30e1967 100644 --- a/cerulean/nixos-module/default.nix +++ b/cerulean/nixos-module/default.nix @@ -11,7 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -{deploy-rs, ...}: { +{deploy-rs}: { imports = [ ./nixpkgs.nix ./home-manager.nix diff --git a/cerulean/nixos-module/nixpkgs.nix b/cerulean/nixos-module/nixpkgs.nix index f4bb2ae..ee4ef55 100644 --- a/cerulean/nixos-module/nixpkgs.nix +++ b/cerulean/nixos-module/nixpkgs.nix @@ -12,13 +12,24 @@ # See the License for the specific language governing permissions and # limitations under the License. { + nt, lib, config, ... }: let - cfg = config.pkgrepo; + inherit + (builtins) + mapAttrs + ; + + inherit + (nt) + flip + ; + + cfg = config.pkgsrc; in { - options.pkgrepo = lib.mkOption { + options.pkgsrc = lib.mkOption { type = lib.types.attrsOf lib.types.attrs; default = {}; description = "Declare and import custom package repositories."; @@ -34,15 +45,39 @@ in { }; }; - config.nixpkgs = - lib.mkIf (cfg ? pkgs) - (let - pkgs = cfg.pkgs; - in - lib.mkForce ( - (builtins.removeAttrs pkgs ["source"]) - // { - flake.source = pkgs.source; - } - )); + config = let + # TODO: use lib.types.submodule to restrict what options + # TODO: can be given to pkgsrc + repos = + cfg + |> mapAttrs ( + name: args: + assert args ? source + || abort '' + ${./.} + `pkgsrc.${name} missing required attribute "source"` + ''; + args + |> flip removeAttrs ["source"] + |> import args.source + ); + in { + # NOTE: _module.args is a special option that allows us to + # NOTE: set extend specialArgs from inside the modules. + # "pkgs" is unique since the nix module system already handles it + _module.args = + removeAttrs repos ["pkgs"]; + + # nixpkgs = + # lib.mkIf (cfg ? pkgs) + # (let + # pkgs = cfg.pkgs; + # in + # lib.mkForce ( + # (removeAttrs pkgs ["source"]) + # // { + # flake.source = pkgs.source; + # } + # )); + }; } From 20051e3cb787b14066af72d72591008f565762fa Mon Sep 17 00:00:00 2001 From: _cry64 Date: Fri, 13 Feb 2026 12:41:18 +1000 Subject: [PATCH 273/338] progress flake.lock to new upstream --- flake.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/flake.lock b/flake.lock index e0b0735..40c2662 100644 --- a/flake.lock +++ b/flake.lock @@ -175,15 +175,15 @@ "systems": "systems_2" }, "locked": { - "lastModified": 1770911021, - "narHash": "sha256-8FcXBu0CjeeZeq3uW2h/c5AV+hqELuyPiHsUogOSmZM=", - "owner": "emilelcb", + "lastModified": 1770950436, + "narHash": "sha256-+h5jrsIJBea5P+rAk4OqUpScqnHYQTvCRUhgGv/MX34=", + "owner": "cry128", "repo": "nt", - "rev": "471e1617e8cc0bc749712a7a6af2b2e9f988ffbf", + "rev": "8725f5079f8f27b3faafeff90e5fc075d55e7d0a", "type": "github" }, "original": { - "owner": "emilelcb", + "owner": "cry128", "repo": "nt", "type": "github" } From 5397bf5efcb1a047612a6e919bc2aad87ba9bb40 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Fri, 13 Feb 2026 12:41:18 +1000 Subject: [PATCH 274/338] progress flake.lock to new upstream --- flake.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/flake.lock b/flake.lock index e0b0735..40c2662 100644 --- a/flake.lock +++ b/flake.lock @@ -175,15 +175,15 @@ "systems": "systems_2" }, "locked": { - "lastModified": 1770911021, - "narHash": "sha256-8FcXBu0CjeeZeq3uW2h/c5AV+hqELuyPiHsUogOSmZM=", - "owner": "emilelcb", + "lastModified": 1770950436, + "narHash": "sha256-+h5jrsIJBea5P+rAk4OqUpScqnHYQTvCRUhgGv/MX34=", + "owner": "cry128", "repo": "nt", - "rev": "471e1617e8cc0bc749712a7a6af2b2e9f988ffbf", + "rev": "8725f5079f8f27b3faafeff90e5fc075d55e7d0a", "type": "github" }, "original": { - "owner": "emilelcb", + "owner": "cry128", "repo": "nt", "type": "github" } From accb34f234292640633d64189f6d32b6e5a52158 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Fri, 13 Feb 2026 13:26:01 +1000 Subject: [PATCH 275/338] fix missing ... --- cerulean/nixos-module/default.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cerulean/nixos-module/default.nix b/cerulean/nixos-module/default.nix index 30e1967..3bf3a9c 100644 --- a/cerulean/nixos-module/default.nix +++ b/cerulean/nixos-module/default.nix @@ -11,7 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -{deploy-rs}: { +{deploy-rs, ...}: { imports = [ ./nixpkgs.nix ./home-manager.nix From d363c47e7fcf5e0be180f89c4d045f3d4291f092 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Fri, 13 Feb 2026 13:26:01 +1000 Subject: [PATCH 276/338] fix missing ... --- cerulean/nixos-module/default.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cerulean/nixos-module/default.nix b/cerulean/nixos-module/default.nix index 30e1967..3bf3a9c 100644 --- a/cerulean/nixos-module/default.nix +++ b/cerulean/nixos-module/default.nix @@ -11,7 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -{deploy-rs}: { +{deploy-rs, ...}: { imports = [ ./nixpkgs.nix ./home-manager.nix From 5e6223d299ee33316ceef29327bb2253e67553ed Mon Sep 17 00:00:00 2001 From: _cry64 Date: Fri, 13 Feb 2026 13:54:37 +1000 Subject: [PATCH 277/338] ensure system is inherited --- cerulean/nexus/nexus.nix | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index 4c757ba..1171f3e 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -187,10 +187,13 @@ in { specialArgs = nexus.specialArgs // node.specialArgs - // {inherit root;}; + // { + inherit root; + inherit (node) system; + }; modules = [../nixos-module (findImport (root + "/hosts/${nodeName}"))] - ++ getGroupModules root nodeName node + ++ (getGroupModules root nodeName node) ++ node.extraModules ++ nexus.extraModules; }; From 933423514279ee28e4cd9f31c2370971ed257196 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Fri, 13 Feb 2026 13:54:37 +1000 Subject: [PATCH 278/338] ensure system is inherited --- cerulean/nexus/nexus.nix | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index 4c757ba..1171f3e 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -187,10 +187,13 @@ in { specialArgs = nexus.specialArgs // node.specialArgs - // {inherit root;}; + // { + inherit root; + inherit (node) system; + }; modules = [../nixos-module (findImport (root + "/hosts/${nodeName}"))] - ++ getGroupModules root nodeName node + ++ (getGroupModules root nodeName node) ++ node.extraModules ++ nexus.extraModules; }; From 7ad231ae5cff510894ab0bf8932cff5781ae58c9 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Fri, 13 Feb 2026 15:58:04 +1000 Subject: [PATCH 279/338] add flake.nix#version field --- cerulean/default.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cerulean/default.nix b/cerulean/default.nix index 68846bf..06492f8 100644 --- a/cerulean/default.nix +++ b/cerulean/default.nix @@ -21,6 +21,8 @@ mix.newMixture inputs (mixture: { ./nexus ]; + version = "0.1.0"; + overlays = [ # build deploy-rs as a package not from the flake input, # hence we can rely on a nixpkg binary cache. From 369041869a4dac25558477473d739ffd0a9bf1be Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Fri, 13 Feb 2026 15:58:04 +1000 Subject: [PATCH 280/338] add flake.nix#version field --- cerulean/default.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cerulean/default.nix b/cerulean/default.nix index 68846bf..06492f8 100644 --- a/cerulean/default.nix +++ b/cerulean/default.nix @@ -21,6 +21,8 @@ mix.newMixture inputs (mixture: { ./nexus ]; + version = "0.1.0"; + overlays = [ # build deploy-rs as a package not from the flake input, # hence we can rely on a nixpkg binary cache. From 52044bd998beddb5ee97279063221186136416af Mon Sep 17 00:00:00 2001 From: _cry64 Date: Fri, 13 Feb 2026 19:32:22 +1000 Subject: [PATCH 281/338] fix nixpkgs.channels system --- cerulean/default.nix | 9 ++- cerulean/nexus/default.nix | 4 +- cerulean/nexus/nexus.nix | 20 ++++--- cerulean/nixos-module/default.nix | 12 +++- cerulean/nixos-module/home-manager.nix | 20 +++++-- cerulean/nixos-module/nixpkgs.nix | 77 ++++++++++++++------------ flake.lock | 6 +- 7 files changed, 92 insertions(+), 56 deletions(-) diff --git a/cerulean/default.nix b/cerulean/default.nix index 06492f8..a824f8e 100644 --- a/cerulean/default.nix +++ b/cerulean/default.nix @@ -15,14 +15,19 @@ mix, deploy-rs, ... -} @ inputs: -mix.newMixture inputs (mixture: { +} @ args: +mix.newMixture args (mixture: { includes.public = [ ./nexus ]; version = "0.1.0"; + nixosModules = rec { + default = cerulean; + cerulean = ./nixos-module; + }; + overlays = [ # build deploy-rs as a package not from the flake input, # hence we can rely on a nixpkg binary cache. diff --git a/cerulean/nexus/default.nix b/cerulean/nexus/default.nix index d8e0f7c..65495bf 100644 --- a/cerulean/nexus/default.nix +++ b/cerulean/nexus/default.nix @@ -11,8 +11,8 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -{mix, ...} @ inputs: -mix.newMixture inputs (mixture: { +{mix, ...} @ args: +mix.newMixture args (mixture: { includes.public = [ ./nodes.nix ./nexus.nix diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index 1171f3e..ae8fe38 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. { + self, this, nt, lib, @@ -184,15 +185,18 @@ in { nodeName: node: let nixosDecl = lib.nixosSystem { system = node.system; - specialArgs = - nexus.specialArgs - // node.specialArgs - // { - inherit root; - inherit (node) system; - }; + specialArgs = let + specialArgs = + nexus.specialArgs + // node.specialArgs + // { + inherit root specialArgs; + inherit (node) system; + }; + in + specialArgs; modules = - [../nixos-module (findImport (root + "/hosts/${nodeName}"))] + [self.nixosModules.default (findImport (root + "/hosts/${nodeName}"))] ++ (getGroupModules root nodeName node) ++ node.extraModules ++ nexus.extraModules; diff --git a/cerulean/nixos-module/default.nix b/cerulean/nixos-module/default.nix index 3bf3a9c..d657021 100644 --- a/cerulean/nixos-module/default.nix +++ b/cerulean/nixos-module/default.nix @@ -11,9 +11,17 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -{deploy-rs, ...}: { +{ + root, + deploy-rs, + ... +} @ args: { imports = [ - ./nixpkgs.nix + # user configuration + (import (root + "/nixpkgs.nix")) + # options declarations + (import ./nixpkgs.nix (args // {contextName = "hosts";})) + ./home-manager.nix ]; diff --git a/cerulean/nixos-module/home-manager.nix b/cerulean/nixos-module/home-manager.nix index 6833e1d..4c41280 100644 --- a/cerulean/nixos-module/home-manager.nix +++ b/cerulean/nixos-module/home-manager.nix @@ -15,15 +15,27 @@ root, config, lib, + specialArgs, ... -} @ args: { +} @ args: let + inherit + (builtins) + attrNames + filter + pathExists + ; +in { home-manager = { users = config.users.users - |> builtins.attrNames - |> builtins.filter (x: builtins.pathExists (root + "/homes/${x}")) + |> attrNames + |> filter (x: pathExists (root + "/homes/${x}")) |> (x: lib.genAttrs x (y: import (root + "/homes/${y}"))); - extraSpecialArgs = args; + extraSpecialArgs = specialArgs; + sharedModules = [ + (import (root + "/nixpkgs.nix")) + (import ./nixpkgs.nix (args // {contextName = "homes";})) + ]; }; } diff --git a/cerulean/nixos-module/nixpkgs.nix b/cerulean/nixos-module/nixpkgs.nix index ee4ef55..9db5d3d 100644 --- a/cerulean/nixos-module/nixpkgs.nix +++ b/cerulean/nixos-module/nixpkgs.nix @@ -12,9 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. { - nt, lib, + system, config, + contextName, ... }: let inherit @@ -22,62 +23,68 @@ mapAttrs ; - inherit - (nt) - flip - ; - - cfg = config.pkgsrc; + cfg = config.nixpkgs.channels; in { - options.pkgsrc = lib.mkOption { - type = lib.types.attrsOf lib.types.attrs; + options.nixpkgs.channels = lib.mkOption { + type = lib.types.attrsOf (lib.types.attrs); default = {}; - description = "Declare and import custom package repositories."; + description = "Declare package repositories per module context (nixos, home-manager, etc)"; example = { - "pkgs" = { - source = "inputs.nixpkgs"; - system = "x86-64-linux"; - config = { - allowUnfree = true; - allowBroken = false; + "homes" = { + "pkgs" = { + source = "inputs.nixpkgs"; + system = "x86-64-linux"; + config = { + allowUnfree = true; + allowBroken = false; + }; + }; + "upkgs" = { + source = "inputs.nixpkgs-unstable"; + system = "x86-64-linux"; + config = { + allowUnfree = true; + allowBroken = false; + }; }; }; }; }; + # or abort '' + # `nixpkgs.channels.${contextName}` does not exist, but neither does `nixpkgs.channels.default`! + # A channel configuration must be declared for module context "${contextName}". + # '' + config = let # TODO: use lib.types.submodule to restrict what options - # TODO: can be given to pkgsrc + # TODO: can be given to `nixpkgs.channels.${moduleName}.${name}` + decl = + cfg.${contextName} or cfg.default; + repos = - cfg + decl |> mapAttrs ( name: args: assert args ? source || abort '' - ${./.} - `pkgsrc.${name} missing required attribute "source"` + ${toString ./.} + `nixpkgs.channels.${contextName}.${name} missing required attribute "source"` ''; - args - |> flip removeAttrs ["source"] + ((removeAttrs args ["source"]) + // {inherit system;}) |> import args.source + |> lib.mkOverride 200 ); in { # NOTE: _module.args is a special option that allows us to # NOTE: set extend specialArgs from inside the modules. # "pkgs" is unique since the nix module system already handles it - _module.args = - removeAttrs repos ["pkgs"]; + _module.args = removeAttrs repos ["pkgs"]; - # nixpkgs = - # lib.mkIf (cfg ? pkgs) - # (let - # pkgs = cfg.pkgs; - # in - # lib.mkForce ( - # (removeAttrs pkgs ["source"]) - # // { - # flake.source = pkgs.source; - # } - # )); + nixpkgs = + if contextName == "hosts" + then {flake.source = lib.mkIf (decl ? pkgs) (lib.mkOverride 200 decl.pkgs.source);} + else {}; }; } diff --git a/flake.lock b/flake.lock index 40c2662..0dd06b1 100644 --- a/flake.lock +++ b/flake.lock @@ -175,11 +175,11 @@ "systems": "systems_2" }, "locked": { - "lastModified": 1770950436, - "narHash": "sha256-+h5jrsIJBea5P+rAk4OqUpScqnHYQTvCRUhgGv/MX34=", + "lastModified": 1770975056, + "narHash": "sha256-ZXTz/P3zUbbM6lNXzt91u8EwfNqhXpYMu8+wvFZqQHE=", "owner": "cry128", "repo": "nt", - "rev": "8725f5079f8f27b3faafeff90e5fc075d55e7d0a", + "rev": "f42dcdd49a7921a7f433512e83d5f93696632412", "type": "github" }, "original": { From 30e8e1f6c32f157add047bcd90932b870c129d35 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Fri, 13 Feb 2026 19:32:22 +1000 Subject: [PATCH 282/338] fix nixpkgs.channels system --- cerulean/default.nix | 9 ++- cerulean/nexus/default.nix | 4 +- cerulean/nexus/nexus.nix | 20 ++++--- cerulean/nixos-module/default.nix | 12 +++- cerulean/nixos-module/home-manager.nix | 20 +++++-- cerulean/nixos-module/nixpkgs.nix | 77 ++++++++++++++------------ flake.lock | 6 +- 7 files changed, 92 insertions(+), 56 deletions(-) diff --git a/cerulean/default.nix b/cerulean/default.nix index 06492f8..a824f8e 100644 --- a/cerulean/default.nix +++ b/cerulean/default.nix @@ -15,14 +15,19 @@ mix, deploy-rs, ... -} @ inputs: -mix.newMixture inputs (mixture: { +} @ args: +mix.newMixture args (mixture: { includes.public = [ ./nexus ]; version = "0.1.0"; + nixosModules = rec { + default = cerulean; + cerulean = ./nixos-module; + }; + overlays = [ # build deploy-rs as a package not from the flake input, # hence we can rely on a nixpkg binary cache. diff --git a/cerulean/nexus/default.nix b/cerulean/nexus/default.nix index d8e0f7c..65495bf 100644 --- a/cerulean/nexus/default.nix +++ b/cerulean/nexus/default.nix @@ -11,8 +11,8 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -{mix, ...} @ inputs: -mix.newMixture inputs (mixture: { +{mix, ...} @ args: +mix.newMixture args (mixture: { includes.public = [ ./nodes.nix ./nexus.nix diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index 1171f3e..ae8fe38 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. { + self, this, nt, lib, @@ -184,15 +185,18 @@ in { nodeName: node: let nixosDecl = lib.nixosSystem { system = node.system; - specialArgs = - nexus.specialArgs - // node.specialArgs - // { - inherit root; - inherit (node) system; - }; + specialArgs = let + specialArgs = + nexus.specialArgs + // node.specialArgs + // { + inherit root specialArgs; + inherit (node) system; + }; + in + specialArgs; modules = - [../nixos-module (findImport (root + "/hosts/${nodeName}"))] + [self.nixosModules.default (findImport (root + "/hosts/${nodeName}"))] ++ (getGroupModules root nodeName node) ++ node.extraModules ++ nexus.extraModules; diff --git a/cerulean/nixos-module/default.nix b/cerulean/nixos-module/default.nix index 3bf3a9c..d657021 100644 --- a/cerulean/nixos-module/default.nix +++ b/cerulean/nixos-module/default.nix @@ -11,9 +11,17 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -{deploy-rs, ...}: { +{ + root, + deploy-rs, + ... +} @ args: { imports = [ - ./nixpkgs.nix + # user configuration + (import (root + "/nixpkgs.nix")) + # options declarations + (import ./nixpkgs.nix (args // {contextName = "hosts";})) + ./home-manager.nix ]; diff --git a/cerulean/nixos-module/home-manager.nix b/cerulean/nixos-module/home-manager.nix index 6833e1d..4c41280 100644 --- a/cerulean/nixos-module/home-manager.nix +++ b/cerulean/nixos-module/home-manager.nix @@ -15,15 +15,27 @@ root, config, lib, + specialArgs, ... -} @ args: { +} @ args: let + inherit + (builtins) + attrNames + filter + pathExists + ; +in { home-manager = { users = config.users.users - |> builtins.attrNames - |> builtins.filter (x: builtins.pathExists (root + "/homes/${x}")) + |> attrNames + |> filter (x: pathExists (root + "/homes/${x}")) |> (x: lib.genAttrs x (y: import (root + "/homes/${y}"))); - extraSpecialArgs = args; + extraSpecialArgs = specialArgs; + sharedModules = [ + (import (root + "/nixpkgs.nix")) + (import ./nixpkgs.nix (args // {contextName = "homes";})) + ]; }; } diff --git a/cerulean/nixos-module/nixpkgs.nix b/cerulean/nixos-module/nixpkgs.nix index ee4ef55..9db5d3d 100644 --- a/cerulean/nixos-module/nixpkgs.nix +++ b/cerulean/nixos-module/nixpkgs.nix @@ -12,9 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. { - nt, lib, + system, config, + contextName, ... }: let inherit @@ -22,62 +23,68 @@ mapAttrs ; - inherit - (nt) - flip - ; - - cfg = config.pkgsrc; + cfg = config.nixpkgs.channels; in { - options.pkgsrc = lib.mkOption { - type = lib.types.attrsOf lib.types.attrs; + options.nixpkgs.channels = lib.mkOption { + type = lib.types.attrsOf (lib.types.attrs); default = {}; - description = "Declare and import custom package repositories."; + description = "Declare package repositories per module context (nixos, home-manager, etc)"; example = { - "pkgs" = { - source = "inputs.nixpkgs"; - system = "x86-64-linux"; - config = { - allowUnfree = true; - allowBroken = false; + "homes" = { + "pkgs" = { + source = "inputs.nixpkgs"; + system = "x86-64-linux"; + config = { + allowUnfree = true; + allowBroken = false; + }; + }; + "upkgs" = { + source = "inputs.nixpkgs-unstable"; + system = "x86-64-linux"; + config = { + allowUnfree = true; + allowBroken = false; + }; }; }; }; }; + # or abort '' + # `nixpkgs.channels.${contextName}` does not exist, but neither does `nixpkgs.channels.default`! + # A channel configuration must be declared for module context "${contextName}". + # '' + config = let # TODO: use lib.types.submodule to restrict what options - # TODO: can be given to pkgsrc + # TODO: can be given to `nixpkgs.channels.${moduleName}.${name}` + decl = + cfg.${contextName} or cfg.default; + repos = - cfg + decl |> mapAttrs ( name: args: assert args ? source || abort '' - ${./.} - `pkgsrc.${name} missing required attribute "source"` + ${toString ./.} + `nixpkgs.channels.${contextName}.${name} missing required attribute "source"` ''; - args - |> flip removeAttrs ["source"] + ((removeAttrs args ["source"]) + // {inherit system;}) |> import args.source + |> lib.mkOverride 200 ); in { # NOTE: _module.args is a special option that allows us to # NOTE: set extend specialArgs from inside the modules. # "pkgs" is unique since the nix module system already handles it - _module.args = - removeAttrs repos ["pkgs"]; + _module.args = removeAttrs repos ["pkgs"]; - # nixpkgs = - # lib.mkIf (cfg ? pkgs) - # (let - # pkgs = cfg.pkgs; - # in - # lib.mkForce ( - # (removeAttrs pkgs ["source"]) - # // { - # flake.source = pkgs.source; - # } - # )); + nixpkgs = + if contextName == "hosts" + then {flake.source = lib.mkIf (decl ? pkgs) (lib.mkOverride 200 decl.pkgs.source);} + else {}; }; } diff --git a/flake.lock b/flake.lock index 40c2662..0dd06b1 100644 --- a/flake.lock +++ b/flake.lock @@ -175,11 +175,11 @@ "systems": "systems_2" }, "locked": { - "lastModified": 1770950436, - "narHash": "sha256-+h5jrsIJBea5P+rAk4OqUpScqnHYQTvCRUhgGv/MX34=", + "lastModified": 1770975056, + "narHash": "sha256-ZXTz/P3zUbbM6lNXzt91u8EwfNqhXpYMu8+wvFZqQHE=", "owner": "cry128", "repo": "nt", - "rev": "8725f5079f8f27b3faafeff90e5fc075d55e7d0a", + "rev": "f42dcdd49a7921a7f433512e83d5f93696632412", "type": "github" }, "original": { From f16f74b4d5aacff05040176fae5f2a796726d2de Mon Sep 17 00:00:00 2001 From: _cry64 Date: Fri, 13 Feb 2026 22:13:35 +1000 Subject: [PATCH 283/338] IM FUCKING SISYPHUS --- cerulean/nexus/nexus.nix | 1 + cerulean/nixos-module/default.nix | 5 +-- cerulean/nixos-module/home-manager.nix | 10 +++++- cerulean/nixos-module/nixpkgs.nix | 44 ++++++++++++++++++++------ 4 files changed, 47 insertions(+), 13 deletions(-) diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index ae8fe38..7466bb8 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -192,6 +192,7 @@ in { // { inherit root specialArgs; inherit (node) system; + _deploy-rs = deploy-rs; }; in specialArgs; diff --git a/cerulean/nixos-module/default.nix b/cerulean/nixos-module/default.nix index d657021..f15b236 100644 --- a/cerulean/nixos-module/default.nix +++ b/cerulean/nixos-module/default.nix @@ -13,7 +13,8 @@ # limitations under the License. { root, - deploy-rs, + system, + _deploy-rs, ... } @ args: { imports = [ @@ -26,6 +27,6 @@ ]; environment.systemPackages = [ - deploy-rs.packages.default + _deploy-rs.packages.${system}.default ]; } diff --git a/cerulean/nixos-module/home-manager.nix b/cerulean/nixos-module/home-manager.nix index 4c41280..e693ac9 100644 --- a/cerulean/nixos-module/home-manager.nix +++ b/cerulean/nixos-module/home-manager.nix @@ -32,10 +32,18 @@ in { |> filter (x: pathExists (root + "/homes/${x}")) |> (x: lib.genAttrs x (y: import (root + "/homes/${y}"))); - extraSpecialArgs = specialArgs; + # extraSpecialArgs = specialArgs; sharedModules = [ + # user configuration + # (import (root + "/nixpkgs.nix")) (import (root + "/nixpkgs.nix")) + # options declarations + # (import ./nixpkgs.nix (args // {contextName = "homes";})) (import ./nixpkgs.nix (args // {contextName = "homes";})) ]; + + # disable home-manager trying anything fancy + # we control the pkgs now!! + # useGlobalPkgs = true; }; } diff --git a/cerulean/nixos-module/nixpkgs.nix b/cerulean/nixos-module/nixpkgs.nix index 9db5d3d..c5a97f7 100644 --- a/cerulean/nixos-module/nixpkgs.nix +++ b/cerulean/nixos-module/nixpkgs.nix @@ -66,25 +66,49 @@ in { decl |> mapAttrs ( name: args: - assert args ? source - || abort '' - ${toString ./.} - `nixpkgs.channels.${contextName}.${name} missing required attribute "source"` - ''; - ((removeAttrs args ["source"]) - // {inherit system;}) - |> import args.source - |> lib.mkOverride 200 + lib.mkForce ( + # builtins.trace "SAVE ME GOT NAME: ${name}" ( + assert args ? source + || abort '' + ${toString ./.} + `nixpkgs.channels.${contextName}.${name}` missing required attribute "source" + ''; + ((removeAttrs args ["source"]) + // {inherit system;}) + |> import args.source + # DEBUG: |> lib.mkOverride 200 + ) + # ) ); in { # NOTE: _module.args is a special option that allows us to # NOTE: set extend specialArgs from inside the modules. # "pkgs" is unique since the nix module system already handles it - _module.args = removeAttrs repos ["pkgs"]; + # DEBUG: _module.args = lib.mkOverride 200 ( + # _module.args = ( + # if contextName == "hosts" + # then repos + # else + # assert ( + # repos + # |> builtins.attrNames + # |> map (x: "\"${x}\"") + # |> builtins.concatStringsSep " " + # |> (x: "FUCK YOU SO BAD: { ${x} }") + # |> abort + # ); + # removeAttrs repos ["pkgs"] + # ); + _module.args = repos; nixpkgs = if contextName == "hosts" then {flake.source = lib.mkIf (decl ? pkgs) (lib.mkOverride 200 decl.pkgs.source);} + else if contextName == "homes" + then { + config = decl.pkgs.config or {}; + overlays = decl.pkgs.overlays or {}; + } else {}; }; } From 23378831aaf2544b83dbd67f25518ad0a22899b5 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Fri, 13 Feb 2026 22:13:35 +1000 Subject: [PATCH 284/338] IM FUCKING SISYPHUS --- cerulean/nexus/nexus.nix | 1 + cerulean/nixos-module/default.nix | 5 +-- cerulean/nixos-module/home-manager.nix | 10 +++++- cerulean/nixos-module/nixpkgs.nix | 44 ++++++++++++++++++++------ 4 files changed, 47 insertions(+), 13 deletions(-) diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index ae8fe38..7466bb8 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -192,6 +192,7 @@ in { // { inherit root specialArgs; inherit (node) system; + _deploy-rs = deploy-rs; }; in specialArgs; diff --git a/cerulean/nixos-module/default.nix b/cerulean/nixos-module/default.nix index d657021..f15b236 100644 --- a/cerulean/nixos-module/default.nix +++ b/cerulean/nixos-module/default.nix @@ -13,7 +13,8 @@ # limitations under the License. { root, - deploy-rs, + system, + _deploy-rs, ... } @ args: { imports = [ @@ -26,6 +27,6 @@ ]; environment.systemPackages = [ - deploy-rs.packages.default + _deploy-rs.packages.${system}.default ]; } diff --git a/cerulean/nixos-module/home-manager.nix b/cerulean/nixos-module/home-manager.nix index 4c41280..e693ac9 100644 --- a/cerulean/nixos-module/home-manager.nix +++ b/cerulean/nixos-module/home-manager.nix @@ -32,10 +32,18 @@ in { |> filter (x: pathExists (root + "/homes/${x}")) |> (x: lib.genAttrs x (y: import (root + "/homes/${y}"))); - extraSpecialArgs = specialArgs; + # extraSpecialArgs = specialArgs; sharedModules = [ + # user configuration + # (import (root + "/nixpkgs.nix")) (import (root + "/nixpkgs.nix")) + # options declarations + # (import ./nixpkgs.nix (args // {contextName = "homes";})) (import ./nixpkgs.nix (args // {contextName = "homes";})) ]; + + # disable home-manager trying anything fancy + # we control the pkgs now!! + # useGlobalPkgs = true; }; } diff --git a/cerulean/nixos-module/nixpkgs.nix b/cerulean/nixos-module/nixpkgs.nix index 9db5d3d..c5a97f7 100644 --- a/cerulean/nixos-module/nixpkgs.nix +++ b/cerulean/nixos-module/nixpkgs.nix @@ -66,25 +66,49 @@ in { decl |> mapAttrs ( name: args: - assert args ? source - || abort '' - ${toString ./.} - `nixpkgs.channels.${contextName}.${name} missing required attribute "source"` - ''; - ((removeAttrs args ["source"]) - // {inherit system;}) - |> import args.source - |> lib.mkOverride 200 + lib.mkForce ( + # builtins.trace "SAVE ME GOT NAME: ${name}" ( + assert args ? source + || abort '' + ${toString ./.} + `nixpkgs.channels.${contextName}.${name}` missing required attribute "source" + ''; + ((removeAttrs args ["source"]) + // {inherit system;}) + |> import args.source + # DEBUG: |> lib.mkOverride 200 + ) + # ) ); in { # NOTE: _module.args is a special option that allows us to # NOTE: set extend specialArgs from inside the modules. # "pkgs" is unique since the nix module system already handles it - _module.args = removeAttrs repos ["pkgs"]; + # DEBUG: _module.args = lib.mkOverride 200 ( + # _module.args = ( + # if contextName == "hosts" + # then repos + # else + # assert ( + # repos + # |> builtins.attrNames + # |> map (x: "\"${x}\"") + # |> builtins.concatStringsSep " " + # |> (x: "FUCK YOU SO BAD: { ${x} }") + # |> abort + # ); + # removeAttrs repos ["pkgs"] + # ); + _module.args = repos; nixpkgs = if contextName == "hosts" then {flake.source = lib.mkIf (decl ? pkgs) (lib.mkOverride 200 decl.pkgs.source);} + else if contextName == "homes" + then { + config = decl.pkgs.config or {}; + overlays = decl.pkgs.overlays or {}; + } else {}; }; } From d66f35bb05a6ccc741554ecc72f026a266c83da3 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Fri, 13 Feb 2026 22:15:34 +1000 Subject: [PATCH 285/338] v0.2.0-alpha --- cerulean/default.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cerulean/default.nix b/cerulean/default.nix index a824f8e..d7797c4 100644 --- a/cerulean/default.nix +++ b/cerulean/default.nix @@ -21,7 +21,7 @@ mix.newMixture args (mixture: { ./nexus ]; - version = "0.1.0"; + version = "0.2.0"; nixosModules = rec { default = cerulean; From 0bb3c21aae76533220dddda64c8b4e664934bc41 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Fri, 13 Feb 2026 22:15:34 +1000 Subject: [PATCH 286/338] v0.2.0-alpha --- cerulean/default.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cerulean/default.nix b/cerulean/default.nix index a824f8e..d7797c4 100644 --- a/cerulean/default.nix +++ b/cerulean/default.nix @@ -21,7 +21,7 @@ mix.newMixture args (mixture: { ./nexus ]; - version = "0.1.0"; + version = "0.2.0"; nixosModules = rec { default = cerulean; From 2b619a2be2306d6f6b4bf44568f1f0e97c9830b8 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sat, 14 Feb 2026 14:06:29 +1000 Subject: [PATCH 287/338] add extraSpecialArgs --- cerulean/nixos-module/home-manager.nix | 39 ++++++++++++++------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/cerulean/nixos-module/home-manager.nix b/cerulean/nixos-module/home-manager.nix index e693ac9..97e4530 100644 --- a/cerulean/nixos-module/home-manager.nix +++ b/cerulean/nixos-module/home-manager.nix @@ -13,6 +13,7 @@ # limitations under the License. { root, + system, config, lib, specialArgs, @@ -25,25 +26,27 @@ pathExists ; in { - home-manager = { - users = - config.users.users - |> attrNames - |> filter (x: pathExists (root + "/homes/${x}")) - |> (x: lib.genAttrs x (y: import (root + "/homes/${y}"))); + config = { + home-manager = { + users = + config.users.users + |> attrNames + |> filter (x: pathExists (root + "/homes/${x}")) + |> (x: lib.genAttrs x (y: import (root + "/homes/${y}"))); - # extraSpecialArgs = specialArgs; - sharedModules = [ - # user configuration - # (import (root + "/nixpkgs.nix")) - (import (root + "/nixpkgs.nix")) - # options declarations - # (import ./nixpkgs.nix (args // {contextName = "homes";})) - (import ./nixpkgs.nix (args // {contextName = "homes";})) - ]; + extraSpecialArgs = {inherit root system;} // (specialArgs.inputs or {}); + sharedModules = [ + # user configuration + # (import (root + "/nixpkgs.nix")) + (import (root + "/nixpkgs.nix")) + # options declarations + # (import ./nixpkgs.nix (args // {contextName = "homes";})) + (import ./nixpkgs.nix (args // {contextName = "homes";})) + ]; - # disable home-manager trying anything fancy - # we control the pkgs now!! - # useGlobalPkgs = true; + # disable home-manager trying anything fancy + # we control the pkgs now!! + # useGlobalPkgs = true; + }; }; } From 91ebd09398f7a50e82d6a7a42efc6cdde77d3212 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sat, 14 Feb 2026 14:06:29 +1000 Subject: [PATCH 288/338] add extraSpecialArgs --- cerulean/nixos-module/home-manager.nix | 39 ++++++++++++++------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/cerulean/nixos-module/home-manager.nix b/cerulean/nixos-module/home-manager.nix index e693ac9..97e4530 100644 --- a/cerulean/nixos-module/home-manager.nix +++ b/cerulean/nixos-module/home-manager.nix @@ -13,6 +13,7 @@ # limitations under the License. { root, + system, config, lib, specialArgs, @@ -25,25 +26,27 @@ pathExists ; in { - home-manager = { - users = - config.users.users - |> attrNames - |> filter (x: pathExists (root + "/homes/${x}")) - |> (x: lib.genAttrs x (y: import (root + "/homes/${y}"))); + config = { + home-manager = { + users = + config.users.users + |> attrNames + |> filter (x: pathExists (root + "/homes/${x}")) + |> (x: lib.genAttrs x (y: import (root + "/homes/${y}"))); - # extraSpecialArgs = specialArgs; - sharedModules = [ - # user configuration - # (import (root + "/nixpkgs.nix")) - (import (root + "/nixpkgs.nix")) - # options declarations - # (import ./nixpkgs.nix (args // {contextName = "homes";})) - (import ./nixpkgs.nix (args // {contextName = "homes";})) - ]; + extraSpecialArgs = {inherit root system;} // (specialArgs.inputs or {}); + sharedModules = [ + # user configuration + # (import (root + "/nixpkgs.nix")) + (import (root + "/nixpkgs.nix")) + # options declarations + # (import ./nixpkgs.nix (args // {contextName = "homes";})) + (import ./nixpkgs.nix (args // {contextName = "homes";})) + ]; - # disable home-manager trying anything fancy - # we control the pkgs now!! - # useGlobalPkgs = true; + # disable home-manager trying anything fancy + # we control the pkgs now!! + # useGlobalPkgs = true; + }; }; } From b7838874d89671d7a42d070f2366b35c00d8c0aa Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sat, 14 Feb 2026 14:42:02 +1000 Subject: [PATCH 289/338] fix overlays bad type --- cerulean/nixos-module/nixpkgs.nix | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cerulean/nixos-module/nixpkgs.nix b/cerulean/nixos-module/nixpkgs.nix index c5a97f7..5bf925f 100644 --- a/cerulean/nixos-module/nixpkgs.nix +++ b/cerulean/nixos-module/nixpkgs.nix @@ -21,6 +21,7 @@ inherit (builtins) mapAttrs + typeOf ; cfg = config.nixpkgs.channels; @@ -107,7 +108,8 @@ in { else if contextName == "homes" then { config = decl.pkgs.config or {}; - overlays = decl.pkgs.overlays or {}; + # XXX: WARNING: TODO: modify options so overlays must always be given as the correct type + overlays = decl.pkgs.overlays or []; } else {}; }; From 162086b065df5ddfa26a99206c2b86a5ca391ca9 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sat, 14 Feb 2026 14:42:02 +1000 Subject: [PATCH 290/338] fix overlays bad type --- cerulean/nixos-module/nixpkgs.nix | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cerulean/nixos-module/nixpkgs.nix b/cerulean/nixos-module/nixpkgs.nix index c5a97f7..5bf925f 100644 --- a/cerulean/nixos-module/nixpkgs.nix +++ b/cerulean/nixos-module/nixpkgs.nix @@ -21,6 +21,7 @@ inherit (builtins) mapAttrs + typeOf ; cfg = config.nixpkgs.channels; @@ -107,7 +108,8 @@ in { else if contextName == "homes" then { config = decl.pkgs.config or {}; - overlays = decl.pkgs.overlays or {}; + # XXX: WARNING: TODO: modify options so overlays must always be given as the correct type + overlays = decl.pkgs.overlays or []; } else {}; }; From 93aa29dc1038cf679547cd0975c4d9ae15491dde Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sat, 14 Feb 2026 16:00:34 +1000 Subject: [PATCH 291/338] support nixpkgs.channels.*.default --- cerulean/nixos-module/home-manager.nix | 2 -- cerulean/nixos-module/nixpkgs.nix | 43 +++++++++----------------- 2 files changed, 14 insertions(+), 31 deletions(-) diff --git a/cerulean/nixos-module/home-manager.nix b/cerulean/nixos-module/home-manager.nix index 97e4530..21ee78c 100644 --- a/cerulean/nixos-module/home-manager.nix +++ b/cerulean/nixos-module/home-manager.nix @@ -37,10 +37,8 @@ in { extraSpecialArgs = {inherit root system;} // (specialArgs.inputs or {}); sharedModules = [ # user configuration - # (import (root + "/nixpkgs.nix")) (import (root + "/nixpkgs.nix")) # options declarations - # (import ./nixpkgs.nix (args // {contextName = "homes";})) (import ./nixpkgs.nix (args // {contextName = "homes";})) ]; diff --git a/cerulean/nixos-module/nixpkgs.nix b/cerulean/nixos-module/nixpkgs.nix index 5bf925f..65db5a5 100644 --- a/cerulean/nixos-module/nixpkgs.nix +++ b/cerulean/nixos-module/nixpkgs.nix @@ -21,7 +21,6 @@ inherit (builtins) mapAttrs - typeOf ; cfg = config.nixpkgs.channels; @@ -52,11 +51,6 @@ in { }; }; - # or abort '' - # `nixpkgs.channels.${contextName}` does not exist, but neither does `nixpkgs.channels.default`! - # A channel configuration must be declared for module context "${contextName}". - # '' - config = let # TODO: use lib.types.submodule to restrict what options # TODO: can be given to `nixpkgs.channels.${moduleName}.${name}` @@ -68,7 +62,6 @@ in { |> mapAttrs ( name: args: lib.mkForce ( - # builtins.trace "SAVE ME GOT NAME: ${name}" ( assert args ? source || abort '' ${toString ./.} @@ -77,39 +70,31 @@ in { ((removeAttrs args ["source"]) // {inherit system;}) |> import args.source - # DEBUG: |> lib.mkOverride 200 ) - # ) ); in { # NOTE: _module.args is a special option that allows us to # NOTE: set extend specialArgs from inside the modules. - # "pkgs" is unique since the nix module system already handles it - # DEBUG: _module.args = lib.mkOverride 200 ( - # _module.args = ( - # if contextName == "hosts" - # then repos - # else - # assert ( - # repos - # |> builtins.attrNames - # |> map (x: "\"${x}\"") - # |> builtins.concatStringsSep " " - # |> (x: "FUCK YOU SO BAD: { ${x} }") - # |> abort - # ); - # removeAttrs repos ["pkgs"] - # ); _module.args = repos; - nixpkgs = + nixpkgs = let + defaultPkgs = + decl.default or (throw '' + Your `nixpkgs.nix` file does not declare a default package source. + Ensure you set `nixpkgs.channels.*.default = ...;` + ''); + in if contextName == "hosts" - then {flake.source = lib.mkIf (decl ? pkgs) (lib.mkOverride 200 decl.pkgs.source);} + then { + flake.source = lib.mkOverride 200 defaultPkgs.source; + config = lib.mkOverride 200 defaultPkgs.config; + } else if contextName == "homes" then { - config = decl.pkgs.config or {}; + # XXX: XXX: XXX: OH OH OH OMG, its because aurora never defines pkgs + config = lib.mkOverride 200 (defaultPkgs.config or {}); # XXX: WARNING: TODO: modify options so overlays must always be given as the correct type - overlays = decl.pkgs.overlays or []; + overlays = lib.mkOverride 200 (defaultPkgs.overlays or []); } else {}; }; From 84b2fa8a76bad78540f28af04fb538db8890a050 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sat, 14 Feb 2026 16:00:34 +1000 Subject: [PATCH 292/338] support nixpkgs.channels.*.default --- cerulean/nixos-module/home-manager.nix | 2 -- cerulean/nixos-module/nixpkgs.nix | 43 +++++++++----------------- 2 files changed, 14 insertions(+), 31 deletions(-) diff --git a/cerulean/nixos-module/home-manager.nix b/cerulean/nixos-module/home-manager.nix index 97e4530..21ee78c 100644 --- a/cerulean/nixos-module/home-manager.nix +++ b/cerulean/nixos-module/home-manager.nix @@ -37,10 +37,8 @@ in { extraSpecialArgs = {inherit root system;} // (specialArgs.inputs or {}); sharedModules = [ # user configuration - # (import (root + "/nixpkgs.nix")) (import (root + "/nixpkgs.nix")) # options declarations - # (import ./nixpkgs.nix (args // {contextName = "homes";})) (import ./nixpkgs.nix (args // {contextName = "homes";})) ]; diff --git a/cerulean/nixos-module/nixpkgs.nix b/cerulean/nixos-module/nixpkgs.nix index 5bf925f..65db5a5 100644 --- a/cerulean/nixos-module/nixpkgs.nix +++ b/cerulean/nixos-module/nixpkgs.nix @@ -21,7 +21,6 @@ inherit (builtins) mapAttrs - typeOf ; cfg = config.nixpkgs.channels; @@ -52,11 +51,6 @@ in { }; }; - # or abort '' - # `nixpkgs.channels.${contextName}` does not exist, but neither does `nixpkgs.channels.default`! - # A channel configuration must be declared for module context "${contextName}". - # '' - config = let # TODO: use lib.types.submodule to restrict what options # TODO: can be given to `nixpkgs.channels.${moduleName}.${name}` @@ -68,7 +62,6 @@ in { |> mapAttrs ( name: args: lib.mkForce ( - # builtins.trace "SAVE ME GOT NAME: ${name}" ( assert args ? source || abort '' ${toString ./.} @@ -77,39 +70,31 @@ in { ((removeAttrs args ["source"]) // {inherit system;}) |> import args.source - # DEBUG: |> lib.mkOverride 200 ) - # ) ); in { # NOTE: _module.args is a special option that allows us to # NOTE: set extend specialArgs from inside the modules. - # "pkgs" is unique since the nix module system already handles it - # DEBUG: _module.args = lib.mkOverride 200 ( - # _module.args = ( - # if contextName == "hosts" - # then repos - # else - # assert ( - # repos - # |> builtins.attrNames - # |> map (x: "\"${x}\"") - # |> builtins.concatStringsSep " " - # |> (x: "FUCK YOU SO BAD: { ${x} }") - # |> abort - # ); - # removeAttrs repos ["pkgs"] - # ); _module.args = repos; - nixpkgs = + nixpkgs = let + defaultPkgs = + decl.default or (throw '' + Your `nixpkgs.nix` file does not declare a default package source. + Ensure you set `nixpkgs.channels.*.default = ...;` + ''); + in if contextName == "hosts" - then {flake.source = lib.mkIf (decl ? pkgs) (lib.mkOverride 200 decl.pkgs.source);} + then { + flake.source = lib.mkOverride 200 defaultPkgs.source; + config = lib.mkOverride 200 defaultPkgs.config; + } else if contextName == "homes" then { - config = decl.pkgs.config or {}; + # XXX: XXX: XXX: OH OH OH OMG, its because aurora never defines pkgs + config = lib.mkOverride 200 (defaultPkgs.config or {}); # XXX: WARNING: TODO: modify options so overlays must always be given as the correct type - overlays = decl.pkgs.overlays or []; + overlays = lib.mkOverride 200 (defaultPkgs.overlays or []); } else {}; }; From 07ad32475ff2bf25f14060dd368fa005361dcc74 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 15 Feb 2026 13:43:19 +1000 Subject: [PATCH 293/338] only inherit inputs --- cerulean/default.nix | 10 ++-------- cerulean/nexus/nexus.nix | 8 ++++---- flake.nix | 15 +++++++++------ 3 files changed, 15 insertions(+), 18 deletions(-) diff --git a/cerulean/default.nix b/cerulean/default.nix index d7797c4..6ec21cc 100644 --- a/cerulean/default.nix +++ b/cerulean/default.nix @@ -13,7 +13,7 @@ # limitations under the License. { mix, - deploy-rs, + inputs, ... } @ args: mix.newMixture args (mixture: { @@ -31,12 +31,6 @@ mix.newMixture args (mixture: { overlays = [ # build deploy-rs as a package not from the flake input, # hence we can rely on a nixpkg binary cache. - deploy-rs.overlays.default - # (self: super: { - # deploy-rs = { - # inherit (super) deploy-rs; - # lib = super.deploy-rs.lib; - # }; - # }) + inputs.deploy-rs.overlays.default ]; }) diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index 7466bb8..29d7387 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -16,7 +16,7 @@ this, nt, lib, - deploy-rs, + inputs, ... }: let inherit @@ -192,7 +192,7 @@ in { // { inherit root specialArgs; inherit (node) system; - _deploy-rs = deploy-rs; + _deploy-rs = inputs.deploy-rs; }; in specialArgs; @@ -220,7 +220,7 @@ in { user ; - nixosFor = system: deploy-rs.lib.${system}.activate.nixos; + nixosFor = system: inputs.deploy-rs.lib.${system}.activate.nixos; in { hostname = ssh.host; @@ -256,7 +256,7 @@ in { }; }); - checks = mapAttrs (system: deployLib: deployLib.deployChecks deploy) deploy-rs.lib; + checks = mapAttrs (system: deployLib: deployLib.deployChecks deploy) inputs.deploy-rs.lib; }; in outputs // customOutputs; diff --git a/flake.nix b/flake.nix index ff430bc..1a3f46e 100644 --- a/flake.nix +++ b/flake.nix @@ -22,7 +22,10 @@ nt.url = "github:cry128/nt"; - deploy-rs.url = "github:serokell/deploy-rs"; + deploy-rs = { + url = "github:serokell/deploy-rs"; + inputs.nixpkgs.follows = "nixpkgs"; + }; }; outputs = { @@ -31,9 +34,9 @@ ... } @ inputs: import ./cerulean - (inputs - // { - inherit (nixpkgs) lib; - inherit (nt) mix; - }); + { + inherit inputs; + inherit (nixpkgs) lib; + inherit (nt) mix; + }; } From 88cd8c4b3bdc98654f0a32fa0e4de8fdfeedf430 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 15 Feb 2026 13:43:19 +1000 Subject: [PATCH 294/338] only inherit inputs --- cerulean/default.nix | 10 ++-------- cerulean/nexus/nexus.nix | 8 ++++---- flake.nix | 15 +++++++++------ 3 files changed, 15 insertions(+), 18 deletions(-) diff --git a/cerulean/default.nix b/cerulean/default.nix index d7797c4..6ec21cc 100644 --- a/cerulean/default.nix +++ b/cerulean/default.nix @@ -13,7 +13,7 @@ # limitations under the License. { mix, - deploy-rs, + inputs, ... } @ args: mix.newMixture args (mixture: { @@ -31,12 +31,6 @@ mix.newMixture args (mixture: { overlays = [ # build deploy-rs as a package not from the flake input, # hence we can rely on a nixpkg binary cache. - deploy-rs.overlays.default - # (self: super: { - # deploy-rs = { - # inherit (super) deploy-rs; - # lib = super.deploy-rs.lib; - # }; - # }) + inputs.deploy-rs.overlays.default ]; }) diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index 7466bb8..29d7387 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -16,7 +16,7 @@ this, nt, lib, - deploy-rs, + inputs, ... }: let inherit @@ -192,7 +192,7 @@ in { // { inherit root specialArgs; inherit (node) system; - _deploy-rs = deploy-rs; + _deploy-rs = inputs.deploy-rs; }; in specialArgs; @@ -220,7 +220,7 @@ in { user ; - nixosFor = system: deploy-rs.lib.${system}.activate.nixos; + nixosFor = system: inputs.deploy-rs.lib.${system}.activate.nixos; in { hostname = ssh.host; @@ -256,7 +256,7 @@ in { }; }); - checks = mapAttrs (system: deployLib: deployLib.deployChecks deploy) deploy-rs.lib; + checks = mapAttrs (system: deployLib: deployLib.deployChecks deploy) inputs.deploy-rs.lib; }; in outputs // customOutputs; diff --git a/flake.nix b/flake.nix index ff430bc..1a3f46e 100644 --- a/flake.nix +++ b/flake.nix @@ -22,7 +22,10 @@ nt.url = "github:cry128/nt"; - deploy-rs.url = "github:serokell/deploy-rs"; + deploy-rs = { + url = "github:serokell/deploy-rs"; + inputs.nixpkgs.follows = "nixpkgs"; + }; }; outputs = { @@ -31,9 +34,9 @@ ... } @ inputs: import ./cerulean - (inputs - // { - inherit (nixpkgs) lib; - inherit (nt) mix; - }); + { + inherit inputs; + inherit (nixpkgs) lib; + inherit (nt) mix; + }; } From c244c9791984eebb49bd65f36b9a81968a966bda Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 15 Feb 2026 13:43:37 +1000 Subject: [PATCH 295/338] remove nixpkgs-unstable --- flake.nix | 3 --- 1 file changed, 3 deletions(-) diff --git a/flake.nix b/flake.nix index 1a3f46e..24c43ac 100644 --- a/flake.nix +++ b/flake.nix @@ -16,10 +16,7 @@ inputs = { systems.url = "github:nix-systems/default"; - nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"; - nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixos-unstable"; - nt.url = "github:cry128/nt"; deploy-rs = { From d9dc30cc98afe85a39bdbf6378b2a5e69b1491c2 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 15 Feb 2026 13:43:37 +1000 Subject: [PATCH 296/338] remove nixpkgs-unstable --- flake.nix | 3 --- 1 file changed, 3 deletions(-) diff --git a/flake.nix b/flake.nix index 1a3f46e..24c43ac 100644 --- a/flake.nix +++ b/flake.nix @@ -16,10 +16,7 @@ inputs = { systems.url = "github:nix-systems/default"; - nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"; - nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixos-unstable"; - nt.url = "github:cry128/nt"; deploy-rs = { From b33730c06852ffb57e957c8aa6f743b326ecfee5 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 15 Feb 2026 13:45:39 +1000 Subject: [PATCH 297/338] auto-import home-manager and microvm nixosModules --- cerulean/nexus/nexus.nix | 8 +++++++- flake.nix | 10 ++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index 29d7387..88f2863 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -197,7 +197,13 @@ in { in specialArgs; modules = - [self.nixosModules.default (findImport (root + "/hosts/${nodeName}"))] + [ + self.nixosModules.default + (findImport (root + "/hosts/${nodeName}")) + + inputs.home-manager.nixosModules.default + inputs.microvm.nixosModules.microvm + ] ++ (getGroupModules root nodeName node) ++ node.extraModules ++ nexus.extraModules; diff --git a/flake.nix b/flake.nix index 24c43ac..6d911c5 100644 --- a/flake.nix +++ b/flake.nix @@ -19,10 +19,20 @@ nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"; nt.url = "github:cry128/nt"; + home-manager = { + url = "github:nix-community/home-manager/release-25.11"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + deploy-rs = { url = "github:serokell/deploy-rs"; inputs.nixpkgs.follows = "nixpkgs"; }; + + microvm = { + url = "github:microvm-nix/microvm.nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; }; outputs = { From 26903695b884375c8fd384d523e6349d7900bc4e Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 15 Feb 2026 13:45:39 +1000 Subject: [PATCH 298/338] auto-import home-manager and microvm nixosModules --- cerulean/nexus/nexus.nix | 8 +++++++- flake.nix | 10 ++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index 29d7387..88f2863 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -197,7 +197,13 @@ in { in specialArgs; modules = - [self.nixosModules.default (findImport (root + "/hosts/${nodeName}"))] + [ + self.nixosModules.default + (findImport (root + "/hosts/${nodeName}")) + + inputs.home-manager.nixosModules.default + inputs.microvm.nixosModules.microvm + ] ++ (getGroupModules root nodeName node) ++ node.extraModules ++ nexus.extraModules; diff --git a/flake.nix b/flake.nix index 24c43ac..6d911c5 100644 --- a/flake.nix +++ b/flake.nix @@ -19,10 +19,20 @@ nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"; nt.url = "github:cry128/nt"; + home-manager = { + url = "github:nix-community/home-manager/release-25.11"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + deploy-rs = { url = "github:serokell/deploy-rs"; inputs.nixpkgs.follows = "nixpkgs"; }; + + microvm = { + url = "github:microvm-nix/microvm.nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; }; outputs = { From b5c2338456a2f326ff56fae9454b5f24e5a74b88 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 15 Feb 2026 17:34:12 +1000 Subject: [PATCH 299/338] remove overlays overlays is no longer configured this way --- cerulean/nexus/nodes.nix | 1 - 1 file changed, 1 deletion(-) diff --git a/cerulean/nexus/nodes.nix b/cerulean/nexus/nodes.nix index 3d49434..ed1c8ad 100644 --- a/cerulean/nexus/nodes.nix +++ b/cerulean/nexus/nodes.nix @@ -30,7 +30,6 @@ in rec { groups = []; extraModules = []; specialArgs = Terminal {}; - overlays = []; deploy = { user = "root"; From 368fa8978b3b2250a0bed5cbbc9c52016a66d79c Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 15 Feb 2026 17:34:12 +1000 Subject: [PATCH 300/338] remove overlays overlays is no longer configured this way --- cerulean/nexus/nodes.nix | 1 - 1 file changed, 1 deletion(-) diff --git a/cerulean/nexus/nodes.nix b/cerulean/nexus/nodes.nix index 3d49434..ed1c8ad 100644 --- a/cerulean/nexus/nodes.nix +++ b/cerulean/nexus/nodes.nix @@ -30,7 +30,6 @@ in rec { groups = []; extraModules = []; specialArgs = Terminal {}; - overlays = []; deploy = { user = "root"; From 536d48c8c3cdad1542e67b4c85e348e85fdbc1d3 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 15 Feb 2026 17:34:29 +1000 Subject: [PATCH 301/338] add new TODO.md --- TODO.md | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/TODO.md b/TODO.md index 25f6f1b..f5e657d 100755 --- a/TODO.md +++ b/TODO.md @@ -1,8 +1,14 @@ -Allow `Cerulean.mkNexus` to be an alias for `flake-parts.lib.mkFlake` -also rename `Cerulean` to `cerulean` in Nix to maintain the naming convention. +- [ ] use the Nix module system instead of projectOnto for `cerulean.mkNexus` +- [ ] find an alternative to `nix.settings.trusted-users` probably +- [ ] add support for github:microvm-nix/microvm.nix +- [ ] add support for sops-nix -Using `flake-parts` ensures Cerulean is usable without restricting -yourself only to the Cerulean ecosystem. +- [ ] create an alternative to nixos-install called cerulean-install that + allows people to easily bootstrap new machines -- [ ] extend the options.nixpkgs to allow any number of package repositories!! -- [x] auto-propagate the same specialArgs for hosts to home-manager + +- [ ] rename nixos-modules/ to nixos/ + + +- [ ] add the ceru-build user +- [ ] ensure all machines are in groups.all by default From fd32dac1bf83d3ae0bc125bc59f7066581b9effd Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 15 Feb 2026 17:34:29 +1000 Subject: [PATCH 302/338] add new TODO.md --- TODO.md | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/TODO.md b/TODO.md index 25f6f1b..f5e657d 100755 --- a/TODO.md +++ b/TODO.md @@ -1,8 +1,14 @@ -Allow `Cerulean.mkNexus` to be an alias for `flake-parts.lib.mkFlake` -also rename `Cerulean` to `cerulean` in Nix to maintain the naming convention. +- [ ] use the Nix module system instead of projectOnto for `cerulean.mkNexus` +- [ ] find an alternative to `nix.settings.trusted-users` probably +- [ ] add support for github:microvm-nix/microvm.nix +- [ ] add support for sops-nix -Using `flake-parts` ensures Cerulean is usable without restricting -yourself only to the Cerulean ecosystem. +- [ ] create an alternative to nixos-install called cerulean-install that + allows people to easily bootstrap new machines -- [ ] extend the options.nixpkgs to allow any number of package repositories!! -- [x] auto-propagate the same specialArgs for hosts to home-manager + +- [ ] rename nixos-modules/ to nixos/ + + +- [ ] add the ceru-build user +- [ ] ensure all machines are in groups.all by default From 16d9062e694e6a1de802beabc26731687ba596fd Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 15 Feb 2026 17:34:35 +1000 Subject: [PATCH 303/338] v0.2.1-alpha --- cerulean/default.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cerulean/default.nix b/cerulean/default.nix index 6ec21cc..449028b 100644 --- a/cerulean/default.nix +++ b/cerulean/default.nix @@ -21,7 +21,7 @@ mix.newMixture args (mixture: { ./nexus ]; - version = "0.2.0"; + version = "0.2.1"; nixosModules = rec { default = cerulean; From 53992e50a93cee9f7e71f69f16d257af97d43494 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 15 Feb 2026 17:34:35 +1000 Subject: [PATCH 304/338] v0.2.1-alpha --- cerulean/default.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cerulean/default.nix b/cerulean/default.nix index 6ec21cc..449028b 100644 --- a/cerulean/default.nix +++ b/cerulean/default.nix @@ -21,7 +21,7 @@ mix.newMixture args (mixture: { ./nexus ]; - version = "0.2.0"; + version = "0.2.1"; nixosModules = rec { default = cerulean; From c5c867b43fd25d0aceb12e393d01c3d621a98796 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 15 Feb 2026 17:35:18 +1000 Subject: [PATCH 305/338] always use root group --- cerulean/nexus/nexus.nix | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index 88f2863..41ef28b 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -133,7 +133,12 @@ Cerulean Nexus node "${nodeName}" does not declare group membership as a list, got "${typeOf node.groups}" instead! Ensure `nexus.nodes.${nodeName}.groups` is a list under your call to `cerulean.mkNexus`. ''; - node.groups + # ensure root group is always added + (node.groups + ++ { + _parent = null; + _name = ROOT_GROUP_NAME; + }) # ensure all members are actually groups |> map (group: let got = From a853b217c0699959a9aa1fba49e632d1293b3304 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 15 Feb 2026 17:35:18 +1000 Subject: [PATCH 306/338] always use root group --- cerulean/nexus/nexus.nix | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index 88f2863..41ef28b 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -133,7 +133,12 @@ Cerulean Nexus node "${nodeName}" does not declare group membership as a list, got "${typeOf node.groups}" instead! Ensure `nexus.nodes.${nodeName}.groups` is a list under your call to `cerulean.mkNexus`. ''; - node.groups + # ensure root group is always added + (node.groups + ++ { + _parent = null; + _name = ROOT_GROUP_NAME; + }) # ensure all members are actually groups |> map (group: let got = From e07047ab50d5328701c628c56d8ef24635b03f6b Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 15 Feb 2026 17:46:37 +1000 Subject: [PATCH 307/338] move nixos-modules/ -> nixos/ --- cerulean/default.nix | 2 +- cerulean/{nixos-module => nixos}/default.nix | 0 cerulean/{nixos-module => nixos}/home-manager.nix | 0 cerulean/{nixos-module => nixos}/nixpkgs.nix | 0 4 files changed, 1 insertion(+), 1 deletion(-) rename cerulean/{nixos-module => nixos}/default.nix (100%) rename cerulean/{nixos-module => nixos}/home-manager.nix (100%) rename cerulean/{nixos-module => nixos}/nixpkgs.nix (100%) diff --git a/cerulean/default.nix b/cerulean/default.nix index 449028b..f24bb5a 100644 --- a/cerulean/default.nix +++ b/cerulean/default.nix @@ -25,7 +25,7 @@ mix.newMixture args (mixture: { nixosModules = rec { default = cerulean; - cerulean = ./nixos-module; + cerulean = ./nixos; }; overlays = [ diff --git a/cerulean/nixos-module/default.nix b/cerulean/nixos/default.nix similarity index 100% rename from cerulean/nixos-module/default.nix rename to cerulean/nixos/default.nix diff --git a/cerulean/nixos-module/home-manager.nix b/cerulean/nixos/home-manager.nix similarity index 100% rename from cerulean/nixos-module/home-manager.nix rename to cerulean/nixos/home-manager.nix diff --git a/cerulean/nixos-module/nixpkgs.nix b/cerulean/nixos/nixpkgs.nix similarity index 100% rename from cerulean/nixos-module/nixpkgs.nix rename to cerulean/nixos/nixpkgs.nix From 0d8751780ee1ea06c2e0109ee1c4a2922d398cb1 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 15 Feb 2026 17:46:37 +1000 Subject: [PATCH 308/338] move nixos-modules/ -> nixos/ --- cerulean/default.nix | 2 +- cerulean/{nixos-module => nixos}/default.nix | 0 cerulean/{nixos-module => nixos}/home-manager.nix | 0 cerulean/{nixos-module => nixos}/nixpkgs.nix | 0 4 files changed, 1 insertion(+), 1 deletion(-) rename cerulean/{nixos-module => nixos}/default.nix (100%) rename cerulean/{nixos-module => nixos}/home-manager.nix (100%) rename cerulean/{nixos-module => nixos}/nixpkgs.nix (100%) diff --git a/cerulean/default.nix b/cerulean/default.nix index 449028b..f24bb5a 100644 --- a/cerulean/default.nix +++ b/cerulean/default.nix @@ -25,7 +25,7 @@ mix.newMixture args (mixture: { nixosModules = rec { default = cerulean; - cerulean = ./nixos-module; + cerulean = ./nixos; }; overlays = [ diff --git a/cerulean/nixos-module/default.nix b/cerulean/nixos/default.nix similarity index 100% rename from cerulean/nixos-module/default.nix rename to cerulean/nixos/default.nix diff --git a/cerulean/nixos-module/home-manager.nix b/cerulean/nixos/home-manager.nix similarity index 100% rename from cerulean/nixos-module/home-manager.nix rename to cerulean/nixos/home-manager.nix diff --git a/cerulean/nixos-module/nixpkgs.nix b/cerulean/nixos/nixpkgs.nix similarity index 100% rename from cerulean/nixos-module/nixpkgs.nix rename to cerulean/nixos/nixpkgs.nix From 9127e64fedbc488ed9140b9c9acbf1e389035f4b Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 15 Feb 2026 23:29:41 +1000 Subject: [PATCH 309/338] more TODO.md --- TODO.md | 39 ++++++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/TODO.md b/TODO.md index f5e657d..995ed69 100755 --- a/TODO.md +++ b/TODO.md @@ -1,14 +1,39 @@ - [ ] use the Nix module system instead of projectOnto for `cerulean.mkNexus` +- [ ] create an alternative to nixos-install called cerulean-install that + allows people to easily bootstrap new machines (and host it on dobutterfliescry.net) + - [ ] find an alternative to `nix.settings.trusted-users` probably +- [ ] add the ceru-build user, - [ ] add support for github:microvm-nix/microvm.nix - [ ] add support for sops-nix -- [ ] create an alternative to nixos-install called cerulean-install that - allows people to easily bootstrap new machines +- [ ] it would be cool to enable/disable groups and hosts +- [ ] find a standard for how nixpkgs.nix can have a different base per group + +- [X] rename nixos-modules/ to nixos/ +- [X] ensure all machines are in groups.all by default + +## Low Priority +- [ ] rename extraModules to modules? +- [ ] rename specialArgs to args? + +- [ ] make an extension to the nix module system (different to mix) + that allows transformations (ie a stop post config, ie outputs, which + it then returns instead of config) -- [ ] rename nixos-modules/ to nixos/ - - -- [ ] add the ceru-build user -- [ ] ensure all machines are in groups.all by default +``` +vms = { + home-assistant = { + autostart = true; + # matches in vms/* + image = "home-assistant"; + options = { + mem = 2048; + }; + }; + equinox = { + image = "home-assistant"; + }; +}; +``` From ee81f45bcfa56aac4ce8b65d516c720ffcb0b7aa Mon Sep 17 00:00:00 2001 From: _cry64 Date: Sun, 15 Feb 2026 23:29:41 +1000 Subject: [PATCH 310/338] more TODO.md --- TODO.md | 39 ++++++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/TODO.md b/TODO.md index f5e657d..995ed69 100755 --- a/TODO.md +++ b/TODO.md @@ -1,14 +1,39 @@ - [ ] use the Nix module system instead of projectOnto for `cerulean.mkNexus` +- [ ] create an alternative to nixos-install called cerulean-install that + allows people to easily bootstrap new machines (and host it on dobutterfliescry.net) + - [ ] find an alternative to `nix.settings.trusted-users` probably +- [ ] add the ceru-build user, - [ ] add support for github:microvm-nix/microvm.nix - [ ] add support for sops-nix -- [ ] create an alternative to nixos-install called cerulean-install that - allows people to easily bootstrap new machines +- [ ] it would be cool to enable/disable groups and hosts +- [ ] find a standard for how nixpkgs.nix can have a different base per group + +- [X] rename nixos-modules/ to nixos/ +- [X] ensure all machines are in groups.all by default + +## Low Priority +- [ ] rename extraModules to modules? +- [ ] rename specialArgs to args? + +- [ ] make an extension to the nix module system (different to mix) + that allows transformations (ie a stop post config, ie outputs, which + it then returns instead of config) -- [ ] rename nixos-modules/ to nixos/ - - -- [ ] add the ceru-build user -- [ ] ensure all machines are in groups.all by default +``` +vms = { + home-assistant = { + autostart = true; + # matches in vms/* + image = "home-assistant"; + options = { + mem = 2048; + }; + }; + equinox = { + image = "home-assistant"; + }; +}; +``` From 2cff3119cd2b9ab29a0936ecf5598eb639b8a477 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Mon, 16 Feb 2026 08:17:33 +1000 Subject: [PATCH 311/338] fix nt not propagated --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 6d911c5..220e732 100644 --- a/flake.nix +++ b/flake.nix @@ -42,7 +42,7 @@ } @ inputs: import ./cerulean { - inherit inputs; + inherit inputs nt; inherit (nixpkgs) lib; inherit (nt) mix; }; From 8d09a2b035621d6f8a982dc19ff9b9142c438a25 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Mon, 16 Feb 2026 08:17:33 +1000 Subject: [PATCH 312/338] fix nt not propagated --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 6d911c5..220e732 100644 --- a/flake.nix +++ b/flake.nix @@ -42,7 +42,7 @@ } @ inputs: import ./cerulean { - inherit inputs; + inherit inputs nt; inherit (nixpkgs) lib; inherit (nt) mix; }; From 803f759472aa34d7a4bdbb5d763655893833640b Mon Sep 17 00:00:00 2001 From: _cry64 Date: Mon, 16 Feb 2026 08:19:07 +1000 Subject: [PATCH 313/338] fix self not propagated --- flake.nix | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 220e732..8fcd0e0 100644 --- a/flake.nix +++ b/flake.nix @@ -36,13 +36,14 @@ }; outputs = { + self, nixpkgs, nt, ... } @ inputs: import ./cerulean { - inherit inputs nt; + inherit inputs self nt; inherit (nixpkgs) lib; inherit (nt) mix; }; From dd77742f5fe5f0bc73ce02595945b43c3e1e7b46 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Mon, 16 Feb 2026 08:19:07 +1000 Subject: [PATCH 314/338] fix self not propagated --- flake.nix | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 220e732..8fcd0e0 100644 --- a/flake.nix +++ b/flake.nix @@ -36,13 +36,14 @@ }; outputs = { + self, nixpkgs, nt, ... } @ inputs: import ./cerulean { - inherit inputs nt; + inherit inputs self nt; inherit (nixpkgs) lib; inherit (nt) mix; }; From c24cc9d46f92d2eaa6dc55f43574201aec579708 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Mon, 16 Feb 2026 08:20:24 +1000 Subject: [PATCH 315/338] fix [] ++ {} (im oopid) --- cerulean/nexus/nexus.nix | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index 41ef28b..d17cfdb 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -96,7 +96,7 @@ assert isAttrs nexus || abort '' Cerulean Nexus config must be provided as an attribute set, got "${typeOf nexus}" instead! - Ensure all the `nexus` declaration is an attribute set under your call to `cerulean.mkNexus`. + Ensure the `nexus` declaration is an attribute set under your call to `cerulean.mkNexus`. ''; let base = nt.projectOnto templateNexus nexus; in @@ -135,10 +135,12 @@ ''; # ensure root group is always added (node.groups - ++ { - _parent = null; - _name = ROOT_GROUP_NAME; - }) + ++ [ + { + _parent = null; + _name = ROOT_GROUP_NAME; + } + ]) # ensure all members are actually groups |> map (group: let got = From d241f6c1924f69912d070001c3b6c4e2226f1e2e Mon Sep 17 00:00:00 2001 From: _cry64 Date: Mon, 16 Feb 2026 08:20:24 +1000 Subject: [PATCH 316/338] fix [] ++ {} (im oopid) --- cerulean/nexus/nexus.nix | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index 41ef28b..d17cfdb 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -96,7 +96,7 @@ assert isAttrs nexus || abort '' Cerulean Nexus config must be provided as an attribute set, got "${typeOf nexus}" instead! - Ensure all the `nexus` declaration is an attribute set under your call to `cerulean.mkNexus`. + Ensure the `nexus` declaration is an attribute set under your call to `cerulean.mkNexus`. ''; let base = nt.projectOnto templateNexus nexus; in @@ -135,10 +135,12 @@ ''; # ensure root group is always added (node.groups - ++ { - _parent = null; - _name = ROOT_GROUP_NAME; - }) + ++ [ + { + _parent = null; + _name = ROOT_GROUP_NAME; + } + ]) # ensure all members are actually groups |> map (group: let got = From 8dc975fc89c543c9e98cfe3c6dfcd98a41531419 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Mon, 16 Feb 2026 08:21:50 +1000 Subject: [PATCH 317/338] fix dont use microvm.nixosModules.microvm by default --- TODO.md | 6 ++ cerulean/nexus/nexus.nix | 2 +- cerulean/nexus/nodes.nix | 1 + cerulean/nexus/snow.nix | 108 ++++++++++++++++++++++++++++++ cerulean/nixos/microvm-child.nix | 13 ++++ cerulean/nixos/microvm-parent.nix | 13 ++++ 6 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 cerulean/nexus/snow.nix create mode 100644 cerulean/nixos/microvm-child.nix create mode 100644 cerulean/nixos/microvm-parent.nix diff --git a/TODO.md b/TODO.md index 995ed69..b80f03c 100755 --- a/TODO.md +++ b/TODO.md @@ -22,6 +22,12 @@ it then returns instead of config) +- [ ] what if we automated the process of replacing windows with Nix?? + then push this to nixos-anywhere or nix-infect lmaooo + +- [ ] patch microvm so that acpi=off https://github.com/microvm-nix/microvm.nix/commit/b59a26962bb324cc0a134756a323f3e164409b72 + cause otherwise 2GB causes a failure + ``` vms = { home-assistant = { diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index d17cfdb..f03d1ce 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -209,7 +209,7 @@ in { (findImport (root + "/hosts/${nodeName}")) inputs.home-manager.nixosModules.default - inputs.microvm.nixosModules.microvm + # inputs.microvm.nixosModules.microvm ] ++ (getGroupModules root nodeName node) ++ node.extraModules diff --git a/cerulean/nexus/nodes.nix b/cerulean/nexus/nodes.nix index ed1c8ad..1687e46 100644 --- a/cerulean/nexus/nodes.nix +++ b/cerulean/nexus/nodes.nix @@ -26,6 +26,7 @@ in rec { Terminal ; in { + enabled = true; system = "x86_64-linux"; # sane default (i hope...) groups = []; extraModules = []; diff --git a/cerulean/nexus/snow.nix b/cerulean/nexus/snow.nix new file mode 100644 index 0000000..1316ee3 --- /dev/null +++ b/cerulean/nexus/snow.nix @@ -0,0 +1,108 @@ +# Copyright 2026 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +{ + inputs, + lib, + ... +}: { + # nexus + options = let + inherit + (lib) + mkOption + types + ; + in { + extraModules = mkOption { + type = types.listOf types.path; + }; + specialArgs = mkOption { + type = types.attrs; + }; + + groups = mkOption { + type = types.attrs; + }; + + nodes = mkOption { + type = types.attrsOf (types.submoduleWith ({...}: { + options = { + enabled = mkOption { + type = types.bool; + default = true; + }; + system = mkOption { + type = types.enum inputs.systems; + }; + groups = mkOption { + type = types.list; + }; + modules = mkOption { + type = types.list; + }; + args = mkOption { + type = types.attrs; + }; + + deploy = { + user = mkOption { + type = types.str; + }; + sudoCmd = mkOption { + type = types.str; + }; + interactiveSudo = mkOption { + type = types.bool; + }; + + remoteBuild = mkOption { + type = types.bool; + }; + autoRollback = mkOption { + type = types.bool; + }; + magicRollback = mkOption { + type = types.bool; + }; + + activationTimeout = mkOption { + type = types.int; + }; + confirmTimeout = mkOption { + type = types.int; + }; + + ssh = { + host = mkOption { + type = types.str; + }; + user = mkOption { + type = types.str; + }; + port = mkOption { + type = types.int; + }; + opts = mkOption { + type = types.listOf types.str; + }; + }; + }; + }; + })); + }; + }; + + config = { + }; +} diff --git a/cerulean/nixos/microvm-child.nix b/cerulean/nixos/microvm-child.nix new file mode 100644 index 0000000..2b6a12e --- /dev/null +++ b/cerulean/nixos/microvm-child.nix @@ -0,0 +1,13 @@ +# Copyright 2026 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/cerulean/nixos/microvm-parent.nix b/cerulean/nixos/microvm-parent.nix new file mode 100644 index 0000000..2b6a12e --- /dev/null +++ b/cerulean/nixos/microvm-parent.nix @@ -0,0 +1,13 @@ +# Copyright 2026 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. From 12ffb132ac596b638226eaa9ba2ed67383d743b6 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Mon, 16 Feb 2026 08:21:50 +1000 Subject: [PATCH 318/338] fix dont use microvm.nixosModules.microvm by default --- TODO.md | 6 ++ cerulean/nexus/nexus.nix | 2 +- cerulean/nexus/nodes.nix | 1 + cerulean/nexus/snow.nix | 108 ++++++++++++++++++++++++++++++ cerulean/nixos/microvm-child.nix | 13 ++++ cerulean/nixos/microvm-parent.nix | 13 ++++ 6 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 cerulean/nexus/snow.nix create mode 100644 cerulean/nixos/microvm-child.nix create mode 100644 cerulean/nixos/microvm-parent.nix diff --git a/TODO.md b/TODO.md index 995ed69..b80f03c 100755 --- a/TODO.md +++ b/TODO.md @@ -22,6 +22,12 @@ it then returns instead of config) +- [ ] what if we automated the process of replacing windows with Nix?? + then push this to nixos-anywhere or nix-infect lmaooo + +- [ ] patch microvm so that acpi=off https://github.com/microvm-nix/microvm.nix/commit/b59a26962bb324cc0a134756a323f3e164409b72 + cause otherwise 2GB causes a failure + ``` vms = { home-assistant = { diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index d17cfdb..f03d1ce 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -209,7 +209,7 @@ in { (findImport (root + "/hosts/${nodeName}")) inputs.home-manager.nixosModules.default - inputs.microvm.nixosModules.microvm + # inputs.microvm.nixosModules.microvm ] ++ (getGroupModules root nodeName node) ++ node.extraModules diff --git a/cerulean/nexus/nodes.nix b/cerulean/nexus/nodes.nix index ed1c8ad..1687e46 100644 --- a/cerulean/nexus/nodes.nix +++ b/cerulean/nexus/nodes.nix @@ -26,6 +26,7 @@ in rec { Terminal ; in { + enabled = true; system = "x86_64-linux"; # sane default (i hope...) groups = []; extraModules = []; diff --git a/cerulean/nexus/snow.nix b/cerulean/nexus/snow.nix new file mode 100644 index 0000000..1316ee3 --- /dev/null +++ b/cerulean/nexus/snow.nix @@ -0,0 +1,108 @@ +# Copyright 2026 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +{ + inputs, + lib, + ... +}: { + # nexus + options = let + inherit + (lib) + mkOption + types + ; + in { + extraModules = mkOption { + type = types.listOf types.path; + }; + specialArgs = mkOption { + type = types.attrs; + }; + + groups = mkOption { + type = types.attrs; + }; + + nodes = mkOption { + type = types.attrsOf (types.submoduleWith ({...}: { + options = { + enabled = mkOption { + type = types.bool; + default = true; + }; + system = mkOption { + type = types.enum inputs.systems; + }; + groups = mkOption { + type = types.list; + }; + modules = mkOption { + type = types.list; + }; + args = mkOption { + type = types.attrs; + }; + + deploy = { + user = mkOption { + type = types.str; + }; + sudoCmd = mkOption { + type = types.str; + }; + interactiveSudo = mkOption { + type = types.bool; + }; + + remoteBuild = mkOption { + type = types.bool; + }; + autoRollback = mkOption { + type = types.bool; + }; + magicRollback = mkOption { + type = types.bool; + }; + + activationTimeout = mkOption { + type = types.int; + }; + confirmTimeout = mkOption { + type = types.int; + }; + + ssh = { + host = mkOption { + type = types.str; + }; + user = mkOption { + type = types.str; + }; + port = mkOption { + type = types.int; + }; + opts = mkOption { + type = types.listOf types.str; + }; + }; + }; + }; + })); + }; + }; + + config = { + }; +} diff --git a/cerulean/nixos/microvm-child.nix b/cerulean/nixos/microvm-child.nix new file mode 100644 index 0000000..2b6a12e --- /dev/null +++ b/cerulean/nixos/microvm-child.nix @@ -0,0 +1,13 @@ +# Copyright 2026 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/cerulean/nixos/microvm-parent.nix b/cerulean/nixos/microvm-parent.nix new file mode 100644 index 0000000..2b6a12e --- /dev/null +++ b/cerulean/nixos/microvm-parent.nix @@ -0,0 +1,13 @@ +# Copyright 2026 Emile Clark-Boman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. From dc37c482a8c71e63a7a10704e1c170abd8f1c7c3 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Mon, 16 Feb 2026 18:56:34 +1000 Subject: [PATCH 319/338] remove nixpkgs dependency YIPPIE YIPPIE YIPPIE --- TODO.md | 3 +++ cerulean/nexus/nexus.nix | 23 +++++++++++++---------- cerulean/nexus/nodes.nix | 32 +++++++++++++++++++++++++++++--- flake.nix | 4 +--- 4 files changed, 46 insertions(+), 16 deletions(-) diff --git a/TODO.md b/TODO.md index b80f03c..e86d610 100755 --- a/TODO.md +++ b/TODO.md @@ -13,6 +13,9 @@ - [X] rename nixos-modules/ to nixos/ - [X] ensure all machines are in groups.all by default +- [X] fix nixpkgs.nix not working (default not respected) +- [X] remove dependence on nixpkgs + ## Low Priority - [ ] rename extraModules to modules? - [ ] rename specialArgs to args? diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index f03d1ce..24a0b75 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -15,7 +15,6 @@ self, this, nt, - lib, inputs, ... }: let @@ -50,13 +49,8 @@ (nt.naive.terminal) Terminal ; - - missing = msg: path: - Terminal (abort '' - Each Cerulean Nexus node is required to specify ${msg}! - Ensure `nexus.${path}` exists under your call to `cerulean.mkNexus`. - ''); in { + base = null; extraModules = []; specialArgs = Terminal {}; @@ -188,8 +182,13 @@ in { customOutputs = removeAttrs decl ["nexus"]; outputs = rec { - nixosConfigurations = mapNodes nexus.nodes ( - nodeName: node: let + nixosConfigurations = mapNodes nexus ( + { + lib, + nodeName, + node, + ... + }: let nixosDecl = lib.nixosSystem { system = node.system; specialArgs = let @@ -219,7 +218,11 @@ in { nixosDecl ); - deploy.nodes = mapNodes nexus.nodes (nodeName: node: let + deploy.nodes = mapNodes nexus ({ + nodeName, + node, + ... + }: let inherit (node.deploy) activationTimeout diff --git a/cerulean/nexus/nodes.nix b/cerulean/nexus/nodes.nix index 1687e46..fa0bc0b 100644 --- a/cerulean/nexus/nodes.nix +++ b/cerulean/nexus/nodes.nix @@ -25,6 +25,12 @@ in rec { (nt.naive.terminal) Terminal ; + + missing = msg: path: + Terminal (abort '' + Each Cerulean Nexus node is required to specify ${msg}! + Ensure `nexus.${path}` exists under your call to `cerulean.mkNexus`. + ''); in { enabled = true; system = "x86_64-linux"; # sane default (i hope...) @@ -32,6 +38,8 @@ in rec { extraModules = []; specialArgs = Terminal {}; + base = null; + deploy = { user = "root"; sudo = "sudo -u"; @@ -67,7 +75,25 @@ in rec { in nt.projectOnto templateAttrs nodeAttrs; - mapNodes = nodes: f: - nodes - |> mapAttrs (nodeName: nodeAttrs: f nodeName (parseNode nodeName nodeAttrs)); + mapNodes = nexus: f: + nexus.nodes + |> mapAttrs (nodeName: nodeAttrs: let + node = parseNode nodeName nodeAttrs; + + # use per-node base or default to nexus base + base = + if node.base != null + then node.base + else if nexus.base != null + then nexus.base + else + abort '' + Cerulean cannot construct nexus node "${nodeName}" without a base package source. + Ensure `nexus.nodes.*.base` or `nexus.base` is a flake reference to the github:NixOS/nixpkgs repository. + ''; + in + f { + inherit nodeName node; + lib = base.lib; + }); } diff --git a/flake.nix b/flake.nix index 8fcd0e0..6682de0 100644 --- a/flake.nix +++ b/flake.nix @@ -16,7 +16,7 @@ inputs = { systems.url = "github:nix-systems/default"; - nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"; + # nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"; nt.url = "github:cry128/nt"; home-manager = { @@ -37,14 +37,12 @@ outputs = { self, - nixpkgs, nt, ... } @ inputs: import ./cerulean { inherit inputs self nt; - inherit (nixpkgs) lib; inherit (nt) mix; }; } From 108fefcd9ba3e399a5c85b8fbde11787fe96ff93 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Mon, 16 Feb 2026 18:56:34 +1000 Subject: [PATCH 320/338] remove nixpkgs dependency YIPPIE YIPPIE YIPPIE --- TODO.md | 3 +++ cerulean/nexus/nexus.nix | 23 +++++++++++++---------- cerulean/nexus/nodes.nix | 32 +++++++++++++++++++++++++++++--- flake.nix | 4 +--- 4 files changed, 46 insertions(+), 16 deletions(-) diff --git a/TODO.md b/TODO.md index b80f03c..e86d610 100755 --- a/TODO.md +++ b/TODO.md @@ -13,6 +13,9 @@ - [X] rename nixos-modules/ to nixos/ - [X] ensure all machines are in groups.all by default +- [X] fix nixpkgs.nix not working (default not respected) +- [X] remove dependence on nixpkgs + ## Low Priority - [ ] rename extraModules to modules? - [ ] rename specialArgs to args? diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index f03d1ce..24a0b75 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -15,7 +15,6 @@ self, this, nt, - lib, inputs, ... }: let @@ -50,13 +49,8 @@ (nt.naive.terminal) Terminal ; - - missing = msg: path: - Terminal (abort '' - Each Cerulean Nexus node is required to specify ${msg}! - Ensure `nexus.${path}` exists under your call to `cerulean.mkNexus`. - ''); in { + base = null; extraModules = []; specialArgs = Terminal {}; @@ -188,8 +182,13 @@ in { customOutputs = removeAttrs decl ["nexus"]; outputs = rec { - nixosConfigurations = mapNodes nexus.nodes ( - nodeName: node: let + nixosConfigurations = mapNodes nexus ( + { + lib, + nodeName, + node, + ... + }: let nixosDecl = lib.nixosSystem { system = node.system; specialArgs = let @@ -219,7 +218,11 @@ in { nixosDecl ); - deploy.nodes = mapNodes nexus.nodes (nodeName: node: let + deploy.nodes = mapNodes nexus ({ + nodeName, + node, + ... + }: let inherit (node.deploy) activationTimeout diff --git a/cerulean/nexus/nodes.nix b/cerulean/nexus/nodes.nix index 1687e46..fa0bc0b 100644 --- a/cerulean/nexus/nodes.nix +++ b/cerulean/nexus/nodes.nix @@ -25,6 +25,12 @@ in rec { (nt.naive.terminal) Terminal ; + + missing = msg: path: + Terminal (abort '' + Each Cerulean Nexus node is required to specify ${msg}! + Ensure `nexus.${path}` exists under your call to `cerulean.mkNexus`. + ''); in { enabled = true; system = "x86_64-linux"; # sane default (i hope...) @@ -32,6 +38,8 @@ in rec { extraModules = []; specialArgs = Terminal {}; + base = null; + deploy = { user = "root"; sudo = "sudo -u"; @@ -67,7 +75,25 @@ in rec { in nt.projectOnto templateAttrs nodeAttrs; - mapNodes = nodes: f: - nodes - |> mapAttrs (nodeName: nodeAttrs: f nodeName (parseNode nodeName nodeAttrs)); + mapNodes = nexus: f: + nexus.nodes + |> mapAttrs (nodeName: nodeAttrs: let + node = parseNode nodeName nodeAttrs; + + # use per-node base or default to nexus base + base = + if node.base != null + then node.base + else if nexus.base != null + then nexus.base + else + abort '' + Cerulean cannot construct nexus node "${nodeName}" without a base package source. + Ensure `nexus.nodes.*.base` or `nexus.base` is a flake reference to the github:NixOS/nixpkgs repository. + ''; + in + f { + inherit nodeName node; + lib = base.lib; + }); } diff --git a/flake.nix b/flake.nix index 8fcd0e0..6682de0 100644 --- a/flake.nix +++ b/flake.nix @@ -16,7 +16,7 @@ inputs = { systems.url = "github:nix-systems/default"; - nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"; + # nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"; nt.url = "github:cry128/nt"; home-manager = { @@ -37,14 +37,12 @@ outputs = { self, - nixpkgs, nt, ... } @ inputs: import ./cerulean { inherit inputs self nt; - inherit (nixpkgs) lib; inherit (nt) mix; }; } From 272b03aa6efff55b2ed98b9f6d881c8248f3a788 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Mon, 16 Feb 2026 18:56:51 +1000 Subject: [PATCH 321/338] update TODO --- TODO.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index e86d610..a12b8c3 100755 --- a/TODO.md +++ b/TODO.md @@ -1,3 +1,5 @@ +- [ ] deploy port should default to the first port given to `services.openssh` + - [ ] use the Nix module system instead of projectOnto for `cerulean.mkNexus` - [ ] create an alternative to nixos-install called cerulean-install that allows people to easily bootstrap new machines (and host it on dobutterfliescry.net) @@ -10,12 +12,16 @@ - [ ] it would be cool to enable/disable groups and hosts - [ ] find a standard for how nixpkgs.nix can have a different base per group +- [ ] go through all flake inputs (recursively) and ENSURE we remove all duplicates by using follows!! + - [X] rename nixos-modules/ to nixos/ - [X] ensure all machines are in groups.all by default - [X] fix nixpkgs.nix not working (default not respected) - [X] remove dependence on nixpkgs +- [ ] allow multiple privesc methods, the standard is pam_ssh_agent_auth + ## Low Priority - [ ] rename extraModules to modules? - [ ] rename specialArgs to args? @@ -31,7 +37,11 @@ - [ ] patch microvm so that acpi=off https://github.com/microvm-nix/microvm.nix/commit/b59a26962bb324cc0a134756a323f3e164409b72 cause otherwise 2GB causes a failure -``` +- [ ] rewrite the ceru cli in rust +- [ ] make `ceru` do local and remote deployments + +```nix +# REF: foxora vms = { home-assistant = { autostart = true; From 7e122bbe144f3b4bfb8de55f6205236fe0f97993 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Mon, 16 Feb 2026 18:56:51 +1000 Subject: [PATCH 322/338] update TODO --- TODO.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index e86d610..a12b8c3 100755 --- a/TODO.md +++ b/TODO.md @@ -1,3 +1,5 @@ +- [ ] deploy port should default to the first port given to `services.openssh` + - [ ] use the Nix module system instead of projectOnto for `cerulean.mkNexus` - [ ] create an alternative to nixos-install called cerulean-install that allows people to easily bootstrap new machines (and host it on dobutterfliescry.net) @@ -10,12 +12,16 @@ - [ ] it would be cool to enable/disable groups and hosts - [ ] find a standard for how nixpkgs.nix can have a different base per group +- [ ] go through all flake inputs (recursively) and ENSURE we remove all duplicates by using follows!! + - [X] rename nixos-modules/ to nixos/ - [X] ensure all machines are in groups.all by default - [X] fix nixpkgs.nix not working (default not respected) - [X] remove dependence on nixpkgs +- [ ] allow multiple privesc methods, the standard is pam_ssh_agent_auth + ## Low Priority - [ ] rename extraModules to modules? - [ ] rename specialArgs to args? @@ -31,7 +37,11 @@ - [ ] patch microvm so that acpi=off https://github.com/microvm-nix/microvm.nix/commit/b59a26962bb324cc0a134756a323f3e164409b72 cause otherwise 2GB causes a failure -``` +- [ ] rewrite the ceru cli in rust +- [ ] make `ceru` do local and remote deployments + +```nix +# REF: foxora vms = { home-assistant = { autostart = true; From fd3b11c7cf88684544df6b90decf80fcbd8456cf Mon Sep 17 00:00:00 2001 From: _cry64 Date: Mon, 16 Feb 2026 19:25:23 +1000 Subject: [PATCH 323/338] force system to be specified --- cerulean/nexus/nodes.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cerulean/nexus/nodes.nix b/cerulean/nexus/nodes.nix index fa0bc0b..ccb5c20 100644 --- a/cerulean/nexus/nodes.nix +++ b/cerulean/nexus/nodes.nix @@ -33,7 +33,7 @@ in rec { ''); in { enabled = true; - system = "x86_64-linux"; # sane default (i hope...) + system = missing "its system architecture" "system"; groups = []; extraModules = []; specialArgs = Terminal {}; From e6cda64e775d89f5ccd1c36ea26691ab8f812ddf Mon Sep 17 00:00:00 2001 From: _cry64 Date: Mon, 16 Feb 2026 19:25:23 +1000 Subject: [PATCH 324/338] force system to be specified --- cerulean/nexus/nodes.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cerulean/nexus/nodes.nix b/cerulean/nexus/nodes.nix index fa0bc0b..ccb5c20 100644 --- a/cerulean/nexus/nodes.nix +++ b/cerulean/nexus/nodes.nix @@ -33,7 +33,7 @@ in rec { ''); in { enabled = true; - system = "x86_64-linux"; # sane default (i hope...) + system = missing "its system architecture" "system"; groups = []; extraModules = []; specialArgs = Terminal {}; From a2ac6b5da35cd6031e2e8c24bcda070d6d9be4fc Mon Sep 17 00:00:00 2001 From: _cry64 Date: Mon, 16 Feb 2026 19:37:21 +1000 Subject: [PATCH 325/338] ok maybe leave nixpkgs... we won't depend on it (i force that) but it's good to allow following --- flake.nix | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 6682de0..89ce6b4 100644 --- a/flake.nix +++ b/flake.nix @@ -16,7 +16,11 @@ inputs = { systems.url = "github:nix-systems/default"; - # nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"; + + # WARNING: nixpkgs is ONLY included so flakes using Cerulean can + # WARNING: force Cerulean's inputs to follow a specific revision. + nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"; + nt.url = "github:cry128/nt"; home-manager = { From 0a768b960622c398770125305f70b490f744937d Mon Sep 17 00:00:00 2001 From: _cry64 Date: Mon, 16 Feb 2026 19:37:21 +1000 Subject: [PATCH 326/338] ok maybe leave nixpkgs... we won't depend on it (i force that) but it's good to allow following --- flake.nix | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 6682de0..89ce6b4 100644 --- a/flake.nix +++ b/flake.nix @@ -16,7 +16,11 @@ inputs = { systems.url = "github:nix-systems/default"; - # nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"; + + # WARNING: nixpkgs is ONLY included so flakes using Cerulean can + # WARNING: force Cerulean's inputs to follow a specific revision. + nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"; + nt.url = "github:cry128/nt"; home-manager = { From c5ef764e27581d7049df51f42fa95833143e4acc Mon Sep 17 00:00:00 2001 From: _cry64 Date: Tue, 17 Feb 2026 11:12:21 +1000 Subject: [PATCH 327/338] update README --- README.md | 39 ++++++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index b62396e..474239b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +![enbyware](https://pride-badges.pony.workers.dev/static/v1?label=enbyware&labelColor=%23555&stripeWidth=8&stripeColors=FCF434%2CFFFFFF%2C9C59D1%2C2C2C2C) +![repo size](https://img.shields.io/github/repo-size/cry128/cerulean) + >[!WARNING] > ✨ **Under Construction** ✨ > Cerulean has lived rent free in my head for the last 12 months. @@ -5,15 +8,41 @@ > for use at my workplace. **Be not afraid!** It's only a matter > of time until Cerulean is ready for use! +# 🌌 🚀 Cerulean Nexus +The culmination of 2 years designing better Nix flakes. Cerulean removes the boilerplate of managing +NixOS infrastructure by declaring each machine as a **node** and their relationships as *"Nexus Networks"*, +virtual networks of servers that Cerulean can manage. Each Nexus is **very powerful**. Allowing for simple +distributed computing, automatic construction of VPNs, DNS for local hostnames, and that's just scratching the surface... + +- Is your node a VPS? Set `deploy.ssh.host = "example.com"` and Cerulean will configure custom build users, + ssh deployment via custom PAM modules, etc etc +- Is your node a VM? Set `vms = [ nodes.VM_NODE ]` on your host node, and Cerulean will configure + all the bridging, NAT, and other networking you so desire! + +## 🩷💜 Motivation +Nix is intended as a non-restrictive & unopinionated system, which is amazing, but it also means +every user develops their own standards to simplify their config. Cerulean however is very much +opinionated and contains all the standards I personally believe should be sane defaults for every NixOS machine. + +> Flakes are not designed for NixOS, they're designed for Nix, and that's an important distinction. + +Flakes and NixOS don't offer anything to simplify managing interconnected nodes of machines. +But this ends with *extremely messy configs* with **a lot of footguns**. You shouldn't have to spend +days reading about networking and learning to work with other peoples' modules. + +Finally, the Nix module system assumes you only use one channel of `github:NixOS/nixpkgs` but this +just isn't realistic. Most people have both `inputs.nixpkgs` and `inputs.nixpkgs-unstable` defined. +So cerulean declares the `nixpkgs.channels.*` option so you don't have to import your channels +manually! + ## 💙 Same Colour, More Control -Cerulean is what you wish Azure could be. An expansive collection of microservices, pre-configured systems, +>[!NOTE] +> This section is *mostly* for the business minded people. + +Cerulean is what you wish Azure could be. Providing an expansive collection of microservices, pre-configured systems, and entirely self-hosted! Cerulean is built using NixOS as a foundation so you know it's never going to break randomly. NixOS backing makes Cerulean **extremely scalable**! Just rent a new VPS and Cerulean will build an ISO of your configuration. No stress, no hassle! Say goodbye to Azure! And say goodbye to Kubernetes! You're taking life into your own hands 💙 -### 🌌 🚀 Nexus -Cerulean allows you to declare *"Nexus Networks"*, virtual networks of servers that Cerulean can manage. -Each Nexus is **very powerful**. Allowing for simple distributed computing, automatic construction of a wireguard -VPN, distributed DNS for local hostnames, and that's just scratching the surface... From 84b7af58636744555498f7a94b7e58186b759a49 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Tue, 17 Feb 2026 11:12:21 +1000 Subject: [PATCH 328/338] update README --- README.md | 39 ++++++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index b62396e..474239b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +![enbyware](https://pride-badges.pony.workers.dev/static/v1?label=enbyware&labelColor=%23555&stripeWidth=8&stripeColors=FCF434%2CFFFFFF%2C9C59D1%2C2C2C2C) +![repo size](https://img.shields.io/github/repo-size/cry128/cerulean) + >[!WARNING] > ✨ **Under Construction** ✨ > Cerulean has lived rent free in my head for the last 12 months. @@ -5,15 +8,41 @@ > for use at my workplace. **Be not afraid!** It's only a matter > of time until Cerulean is ready for use! +# 🌌 🚀 Cerulean Nexus +The culmination of 2 years designing better Nix flakes. Cerulean removes the boilerplate of managing +NixOS infrastructure by declaring each machine as a **node** and their relationships as *"Nexus Networks"*, +virtual networks of servers that Cerulean can manage. Each Nexus is **very powerful**. Allowing for simple +distributed computing, automatic construction of VPNs, DNS for local hostnames, and that's just scratching the surface... + +- Is your node a VPS? Set `deploy.ssh.host = "example.com"` and Cerulean will configure custom build users, + ssh deployment via custom PAM modules, etc etc +- Is your node a VM? Set `vms = [ nodes.VM_NODE ]` on your host node, and Cerulean will configure + all the bridging, NAT, and other networking you so desire! + +## 🩷💜 Motivation +Nix is intended as a non-restrictive & unopinionated system, which is amazing, but it also means +every user develops their own standards to simplify their config. Cerulean however is very much +opinionated and contains all the standards I personally believe should be sane defaults for every NixOS machine. + +> Flakes are not designed for NixOS, they're designed for Nix, and that's an important distinction. + +Flakes and NixOS don't offer anything to simplify managing interconnected nodes of machines. +But this ends with *extremely messy configs* with **a lot of footguns**. You shouldn't have to spend +days reading about networking and learning to work with other peoples' modules. + +Finally, the Nix module system assumes you only use one channel of `github:NixOS/nixpkgs` but this +just isn't realistic. Most people have both `inputs.nixpkgs` and `inputs.nixpkgs-unstable` defined. +So cerulean declares the `nixpkgs.channels.*` option so you don't have to import your channels +manually! + ## 💙 Same Colour, More Control -Cerulean is what you wish Azure could be. An expansive collection of microservices, pre-configured systems, +>[!NOTE] +> This section is *mostly* for the business minded people. + +Cerulean is what you wish Azure could be. Providing an expansive collection of microservices, pre-configured systems, and entirely self-hosted! Cerulean is built using NixOS as a foundation so you know it's never going to break randomly. NixOS backing makes Cerulean **extremely scalable**! Just rent a new VPS and Cerulean will build an ISO of your configuration. No stress, no hassle! Say goodbye to Azure! And say goodbye to Kubernetes! You're taking life into your own hands 💙 -### 🌌 🚀 Nexus -Cerulean allows you to declare *"Nexus Networks"*, virtual networks of servers that Cerulean can manage. -Each Nexus is **very powerful**. Allowing for simple distributed computing, automatic construction of a wireguard -VPN, distributed DNS for local hostnames, and that's just scratching the surface... From ce996f99562a86e81f5dc17185d4bd5715493e7e Mon Sep 17 00:00:00 2001 From: _cry64 Date: Tue, 17 Feb 2026 11:18:11 +1000 Subject: [PATCH 329/338] update construction README --- README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 474239b..b1362ae 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,13 @@ >[!WARNING] > ✨ **Under Construction** ✨ -> Cerulean has lived rent free in my head for the last 12 months. -> I'm developing this project for personal use and especially -> for use at my workplace. **Be not afraid!** It's only a matter -> of time until Cerulean is ready for use! +> +> Cerulean is in an **experimental alpha release stage**.
+> **Be not afraid!** It's only a matter of time until Cerulean is ready for use! +> +> If you're curious about or want to use Cerulean then
+> *please please please* contact me on Bluesky [@dobutterfliescry.net](https://bsky.app/profile/dobutterfliescry.net).
+> (i would actually *melt* and fall in love with you...) # 🌌 🚀 Cerulean Nexus The culmination of 2 years designing better Nix flakes. Cerulean removes the boilerplate of managing From 57a4ec8d82ee553f3107daec51d5b231bb52ec51 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Tue, 17 Feb 2026 11:18:11 +1000 Subject: [PATCH 330/338] update construction README --- README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 474239b..b1362ae 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,13 @@ >[!WARNING] > ✨ **Under Construction** ✨ -> Cerulean has lived rent free in my head for the last 12 months. -> I'm developing this project for personal use and especially -> for use at my workplace. **Be not afraid!** It's only a matter -> of time until Cerulean is ready for use! +> +> Cerulean is in an **experimental alpha release stage**.
+> **Be not afraid!** It's only a matter of time until Cerulean is ready for use! +> +> If you're curious about or want to use Cerulean then
+> *please please please* contact me on Bluesky [@dobutterfliescry.net](https://bsky.app/profile/dobutterfliescry.net).
+> (i would actually *melt* and fall in love with you...) # 🌌 🚀 Cerulean Nexus The culmination of 2 years designing better Nix flakes. Cerulean removes the boilerplate of managing From af1da3dbc383872836864d8595e596ddf707d0f6 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Tue, 17 Feb 2026 11:42:59 +1000 Subject: [PATCH 331/338] rename specialArgs -> args --- cerulean/nexus/nexus.nix | 23 ++++++++++------------- cerulean/nexus/nodes.nix | 2 +- cerulean/nexus/snow.nix | 2 +- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index 24a0b75..2424cd2 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -52,7 +52,7 @@ in { base = null; extraModules = []; - specialArgs = Terminal {}; + args = Terminal {}; groups = Terminal {}; nodes = Terminal {}; @@ -189,19 +189,16 @@ in { node, ... }: let - nixosDecl = lib.nixosSystem { + nixosDecl = lib.nixosSystem rec { system = node.system; - specialArgs = let - specialArgs = - nexus.specialArgs - // node.specialArgs - // { - inherit root specialArgs; - inherit (node) system; - _deploy-rs = inputs.deploy-rs; - }; - in - specialArgs; + specialArgs = + nexus.args + // node.args + // { + inherit root specialArgs; + inherit (node) system; + _deploy-rs = inputs.deploy-rs; + }; modules = [ self.nixosModules.default diff --git a/cerulean/nexus/nodes.nix b/cerulean/nexus/nodes.nix index ccb5c20..46b9add 100644 --- a/cerulean/nexus/nodes.nix +++ b/cerulean/nexus/nodes.nix @@ -36,7 +36,7 @@ in rec { system = missing "its system architecture" "system"; groups = []; extraModules = []; - specialArgs = Terminal {}; + args = Terminal {}; base = null; diff --git a/cerulean/nexus/snow.nix b/cerulean/nexus/snow.nix index 1316ee3..6704710 100644 --- a/cerulean/nexus/snow.nix +++ b/cerulean/nexus/snow.nix @@ -27,7 +27,7 @@ extraModules = mkOption { type = types.listOf types.path; }; - specialArgs = mkOption { + args = mkOption { type = types.attrs; }; From 880c4ebfbee72760775cce5d015e1fa85ff9ecf4 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Tue, 17 Feb 2026 11:42:59 +1000 Subject: [PATCH 332/338] rename specialArgs -> args --- cerulean/nexus/nexus.nix | 23 ++++++++++------------- cerulean/nexus/nodes.nix | 2 +- cerulean/nexus/snow.nix | 2 +- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index 24a0b75..2424cd2 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -52,7 +52,7 @@ in { base = null; extraModules = []; - specialArgs = Terminal {}; + args = Terminal {}; groups = Terminal {}; nodes = Terminal {}; @@ -189,19 +189,16 @@ in { node, ... }: let - nixosDecl = lib.nixosSystem { + nixosDecl = lib.nixosSystem rec { system = node.system; - specialArgs = let - specialArgs = - nexus.specialArgs - // node.specialArgs - // { - inherit root specialArgs; - inherit (node) system; - _deploy-rs = inputs.deploy-rs; - }; - in - specialArgs; + specialArgs = + nexus.args + // node.args + // { + inherit root specialArgs; + inherit (node) system; + _deploy-rs = inputs.deploy-rs; + }; modules = [ self.nixosModules.default diff --git a/cerulean/nexus/nodes.nix b/cerulean/nexus/nodes.nix index ccb5c20..46b9add 100644 --- a/cerulean/nexus/nodes.nix +++ b/cerulean/nexus/nodes.nix @@ -36,7 +36,7 @@ in rec { system = missing "its system architecture" "system"; groups = []; extraModules = []; - specialArgs = Terminal {}; + args = Terminal {}; base = null; diff --git a/cerulean/nexus/snow.nix b/cerulean/nexus/snow.nix index 1316ee3..6704710 100644 --- a/cerulean/nexus/snow.nix +++ b/cerulean/nexus/snow.nix @@ -27,7 +27,7 @@ extraModules = mkOption { type = types.listOf types.path; }; - specialArgs = mkOption { + args = mkOption { type = types.attrs; }; From 0d85fe39a678701e1419f0ee750725255029e374 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Tue, 17 Feb 2026 11:46:59 +1000 Subject: [PATCH 333/338] rename extraModules -> modules --- cerulean/nexus/nexus.nix | 6 +++--- cerulean/nexus/nodes.nix | 2 +- cerulean/nexus/snow.nix | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index 2424cd2..22424ba 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -51,7 +51,7 @@ ; in { base = null; - extraModules = []; + modules = []; args = Terminal {}; groups = Terminal {}; @@ -208,8 +208,8 @@ in { # inputs.microvm.nixosModules.microvm ] ++ (getGroupModules root nodeName node) - ++ node.extraModules - ++ nexus.extraModules; + ++ node.modules + ++ nexus.modules; }; in nixosDecl diff --git a/cerulean/nexus/nodes.nix b/cerulean/nexus/nodes.nix index 46b9add..a1b6117 100644 --- a/cerulean/nexus/nodes.nix +++ b/cerulean/nexus/nodes.nix @@ -35,7 +35,7 @@ in rec { enabled = true; system = missing "its system architecture" "system"; groups = []; - extraModules = []; + modules = []; args = Terminal {}; base = null; diff --git a/cerulean/nexus/snow.nix b/cerulean/nexus/snow.nix index 6704710..28496ca 100644 --- a/cerulean/nexus/snow.nix +++ b/cerulean/nexus/snow.nix @@ -24,7 +24,7 @@ types ; in { - extraModules = mkOption { + modules = mkOption { type = types.listOf types.path; }; args = mkOption { From f8ed7e278762477443635b5222ddb82ba71b3b03 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Tue, 17 Feb 2026 11:46:59 +1000 Subject: [PATCH 334/338] rename extraModules -> modules --- cerulean/nexus/nexus.nix | 6 +++--- cerulean/nexus/nodes.nix | 2 +- cerulean/nexus/snow.nix | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cerulean/nexus/nexus.nix b/cerulean/nexus/nexus.nix index 2424cd2..22424ba 100644 --- a/cerulean/nexus/nexus.nix +++ b/cerulean/nexus/nexus.nix @@ -51,7 +51,7 @@ ; in { base = null; - extraModules = []; + modules = []; args = Terminal {}; groups = Terminal {}; @@ -208,8 +208,8 @@ in { # inputs.microvm.nixosModules.microvm ] ++ (getGroupModules root nodeName node) - ++ node.extraModules - ++ nexus.extraModules; + ++ node.modules + ++ nexus.modules; }; in nixosDecl diff --git a/cerulean/nexus/nodes.nix b/cerulean/nexus/nodes.nix index 46b9add..a1b6117 100644 --- a/cerulean/nexus/nodes.nix +++ b/cerulean/nexus/nodes.nix @@ -35,7 +35,7 @@ in rec { enabled = true; system = missing "its system architecture" "system"; groups = []; - extraModules = []; + modules = []; args = Terminal {}; base = null; diff --git a/cerulean/nexus/snow.nix b/cerulean/nexus/snow.nix index 6704710..28496ca 100644 --- a/cerulean/nexus/snow.nix +++ b/cerulean/nexus/snow.nix @@ -24,7 +24,7 @@ types ; in { - extraModules = mkOption { + modules = mkOption { type = types.listOf types.path; }; args = mkOption { From 04d6bd04c3988d5719a7906879e11f180570f0d1 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Tue, 17 Feb 2026 11:47:49 +1000 Subject: [PATCH 335/338] v0.2.2-alpha --- cerulean/default.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cerulean/default.nix b/cerulean/default.nix index f24bb5a..202fdf9 100644 --- a/cerulean/default.nix +++ b/cerulean/default.nix @@ -21,7 +21,7 @@ mix.newMixture args (mixture: { ./nexus ]; - version = "0.2.1"; + version = "0.2.2"; nixosModules = rec { default = cerulean; From e5d6c1830b4b31d2c0d7709c2dbe4c64b03b44b7 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Tue, 17 Feb 2026 11:47:49 +1000 Subject: [PATCH 336/338] v0.2.2-alpha --- cerulean/default.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cerulean/default.nix b/cerulean/default.nix index f24bb5a..202fdf9 100644 --- a/cerulean/default.nix +++ b/cerulean/default.nix @@ -21,7 +21,7 @@ mix.newMixture args (mixture: { ./nexus ]; - version = "0.2.1"; + version = "0.2.2"; nixosModules = rec { default = cerulean; From 59d1028e19d9ac5f43122d08119e90d77e56a7da Mon Sep 17 00:00:00 2001 From: _cry64 Date: Tue, 17 Feb 2026 12:41:53 +1000 Subject: [PATCH 337/338] add CHANGELOG.md --- CHANGELOG.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..1c7a9a3 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,25 @@ +# Changelog + +## v0.2.0-alpha +Initial "stable" release. Cerulean is currently usable and supports: +1. local & remote deployment configuration +2. nixos/homemanager module-level support for any number of nixpkg branches +3. use of the [nix-systems standard](https://github.com/nix-systems/nix-systems), the introduction of the `snow/flake` standard, and the introduction of the `nixpkgs.nix` standard module. +4. hierarchical groups for NixOS hosts via `snow.nix` + +This is still a alpha-build of Cerulean. Everything will break in the future as I change the internals a bunch. I'll aim to write documentation in future cause currently there's no guide. + +## v0.2.1-alpha +Minor patches +- cerulean no longer has a `inputs.nixpkgs-unstable` (the `nixpkgs.nix` is the new alternative) +- `home-manager.nixosModules.default` and `microvm.nixosModules.microvm` are added as default modules +- fixed `groups.all` not being added to nodes with `groups = []` + +## v0.2.2-alpha +Minor patches +- fixed `nexus.groups.all` not added to empty `nexus.nodes.*.groups` declarations +- fixed bad propagation of inputs +- forced system architecture to be specified per node +- cerulean no longer depends on `nixpkgs`, `base` package set should be set instead +- rename `extraModules` -> `modules` +- rename `specialArgs` -> `args` From f4ad564a83e4a1cded9062eb793138026159b14c Mon Sep 17 00:00:00 2001 From: _cry64 Date: Tue, 17 Feb 2026 12:41:53 +1000 Subject: [PATCH 338/338] add CHANGELOG.md --- CHANGELOG.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..1c7a9a3 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,25 @@ +# Changelog + +## v0.2.0-alpha +Initial "stable" release. Cerulean is currently usable and supports: +1. local & remote deployment configuration +2. nixos/homemanager module-level support for any number of nixpkg branches +3. use of the [nix-systems standard](https://github.com/nix-systems/nix-systems), the introduction of the `snow/flake` standard, and the introduction of the `nixpkgs.nix` standard module. +4. hierarchical groups for NixOS hosts via `snow.nix` + +This is still a alpha-build of Cerulean. Everything will break in the future as I change the internals a bunch. I'll aim to write documentation in future cause currently there's no guide. + +## v0.2.1-alpha +Minor patches +- cerulean no longer has a `inputs.nixpkgs-unstable` (the `nixpkgs.nix` is the new alternative) +- `home-manager.nixosModules.default` and `microvm.nixosModules.microvm` are added as default modules +- fixed `groups.all` not being added to nodes with `groups = []` + +## v0.2.2-alpha +Minor patches +- fixed `nexus.groups.all` not added to empty `nexus.nodes.*.groups` declarations +- fixed bad propagation of inputs +- forced system architecture to be specified per node +- cerulean no longer depends on `nixpkgs`, `base` package set should be set instead +- rename `extraModules` -> `modules` +- rename `specialArgs` -> `args`