16 files changed
--- a/README.adoc
+++ b/README.adoc
@@ -125,12 +125,12 @@
};
services.gitlab = let secrets = config.secrets; in {
enable = true;
- initialRootPasswordFile = secrets.gitlab-initial-root.secretPath;
+ initialRootPasswordFile = secrets.gitlab-initial-root.secret.path;
secrets = {
- secretFile = secrets.gitlab-secret.secretPath;
- otpFile = secrets.gitlab-otp.secretPath;
- dbFile = secrets.gitlab-db.secretPath;
- jwsFile = secrets.gitlab-jws.secretPath;
+ secretFile = secrets.gitlab-secret.secret.path;
+ otpFile = secrets.gitlab-otp.secret.path;
+ dbFile = secrets.gitlab-db.secret.path;
+ jwsFile = secrets.gitlab-jws.secret.path;
};
};
}
@@ -200,7 +200,7 @@
set -eu
export OPENSSL_CONF=${openssl-conf}
# Yay, using secrets to generate secrets!
- HSM_PIN=$(cat ${config.secrets.hsm-pin.secretPath})
+ HSM_PIN=$(cat ${config.secrets.hsm-pin.secret.path})
exec ${pkgs.openssl}/bin/openssl "$@" -keyform=engine -CAkeyform=engine -engine=pkcs11 -passin=pass:"$HSM_PIN"
'';
})
@@ -208,7 +208,7 @@
name = "hsmt";
text = ''
set -eu
- HSM_PIN=$(cat ${config.secrets.hsm-pin.secretPath})
+ HSM_PIN=$(cat ${config.secrets.hsm-pin.secret.path})
exec ${pkgs.opensc}/bin/pkcs11-tool -l --pin="$HSM_PIN" "$@"
'';
})
--- /dev/null
+++ b/docs/examples/_section.adoc
@@ -0,0 +1,2 @@
+= Examples
+:order: 2
--- /dev/null
+++ b/docs/examples/wireguard.adoc
@@ -0,0 +1,282 @@
+= WireGuard Mesh Network
+:order: 0
+
+Full-mesh WireGuard VPN with automatic key generation, cross-host peer wiring, and hash-derived IPv6 ULA addresses.
+
+== Directory Structure
+
+----
+.
+├── flake.nix
+└── wireguard/
+ └── default.nix
+----
+
+== flake.nix
+
+[source,nix]
+----
+{...}: {
+ outputs =
+ inputs:
+ inputs.flake-parts.lib.mkFlake { inherit inputs; } {
+ imports = [
+ inputs.fleet.flakeModule
+ ];
+ fleetConfigurations.default = {
+ imports = [
+ (import ./wireguard { iface = "testwg"; })
+ ];
+ cluster.wireguard.testwg = {
+ hosts = [
+ "bernard"
+ "edgeworth"
+ ];
+ port = 51825;
+ };
+ hosts.bernard = {
+ system = "x86_64-linux";
+ network.externalIps = [
+ "78.11.11.11"
+ ];
+ };
+ hosts.edgeworth = {
+ system = "aarch64-linux";
+ };
+ };
+ };
+}
+----
+
+Module is imported with a parameterized interface name (`testwg`), then configured declaratively via `cluster.wireguard.testwg`.
+Hosts without `network.externalIps` (like `edgeworth`) are treated as NAT/roaming — no endpoint is set for them, peers connect outbound only.
+
+== wireguard/default.nix
+
+[source,nix]
+----
+{ iface }:
+{ config, lib, fleetLib, ... }:
+let
+ inherit (lib.attrsets) genAttrs optionalAttrs;
+ inherit (lib.strings) substring;
+ inherit (lib.lists)
+ filter
+ sort
+ unique
+ length
+ elemAt
+ ;
+ inherit (lib.options) mkOption;
+ inherit (lib.types) listOf str int;
+ inherit (lib) hashString lessThan;
+
+ cfg = config.cluster.wireguard.${iface};
+ hostNames = sort lessThan cfg.hosts;
+ hostHash = name: hashString "sha256" (name + iface);
+ hostSubnet =
+ name:
+ let
+ h = hostHash name;
+ in
+ "fd00:${substring 0 4 h}:${substring 4 4 h}:${substring 8 4 h}";
+ hostIPv6 = name: "${hostSubnet name}::1";
+in
+{
+ options.cluster.wireguard.${iface} = {
+ hosts = mkOption {
+ description = "Hostnames participating in this wireguard mesh";
+ type = listOf str;
+ };
+ port = mkOption {
+ description = "WireGuard listen port";
+ type = int;
+ default = 51824;
+ };
+ };
+
+ config = {
+ assertions = [
+ {
+ assertion = length (unique (map hostSubnet hostNames)) == length hostNames;
+ message = "${iface}: hostname-derived IPv6 subnet collision detected";
+ }
+ ];
+ secrets."${iface}-psk" = {
+ expectedOwners = hostNames;
+ generator = fleetLib.mkBase64Bytes { count = 32; };
+ };
+
+ hosts = genAttrs hostNames (name: {
+ nixos =
+ {
+ config,
+ nixosHosts,
+ hosts,
+ ...
+ }:
+ let
+ peerNames = filter (n: n != name) hostNames;
+ in
+ {
+ secrets."${iface}-key" = {
+ generator = fleetLib.mkX25519 { encoding = "base64"; };
+ };
+ secrets."${iface}-psk" = {
+ generator = "shared";
+ };
+
+ networking.wireguard.interfaces.${iface} = {
+ ips = [ "${hostIPv6 name}/64" ];
+ listenPort = cfg.port;
+ privateKeyFile = config.secrets."${iface}-key".secret.path;
+
+ peers = map (
+ peerName:
+ {
+ publicKey = nixosHosts.${peerName}.secrets."${iface}-key".public.data;
+ presharedKeyFile = config.secrets."${iface}-psk".secret.path;
+ allowedIPs = [ "${hostSubnet peerName}::/64" ];
+ }
+ // optionalAttrs (hosts.${peerName}.network.externalIps or [ ] != [ ]) {
+ endpoint = "${elemAt hosts.${peerName}.network.externalIps 0}:${toString cfg.port}";
+ }
+ ) peerNames;
+ };
+
+ networking.firewall.allowedUDPPorts = [ cfg.port ];
+ };
+ });
+ };
+ _file = ./default.nix;
+}
+----
+
+== How It Works
+
+=== Multi-Instance Module
+
+The outer function `{ iface }:` makes the module parameterizable — import it multiple times with different interface names to create independent WireGuard meshes with separate keys and subnets:
+
+[source,nix]
+----
+imports = [
+ (import ./wireguard { iface = "internal"; })
+ (import ./wireguard { iface = "management"; })
+];
+----
+
+=== IPv6 ULA Addressing
+
+Each host gets a unique `fd00::/64` subnet derived from `sha256(hostname + iface)`.
+No manual IP assignment needed — deterministic, collision-checked via assertion.
+
+For example, `bernard` on interface `testwg` gets `fd00:1c86:78d4:dcef::1/64`.
+
+=== Secrets
+
+`${iface}-key`::
+Per-host X25519 keypair via `fleetLib.mkX25519 { encoding = "base64"; }`.
+Private key encrypted in fleet state, public key plaintext — readable by other hosts at build time through `nixosHosts.${peerName}.secrets."${iface}-key".public.data`.
+
+`${iface}-psk`::
+Shared preshared key via `fleetLib.mkBase64Bytes { count = 32; }`.
+Defined at fleet level with `expectedOwners`, each host claims with `generator = "shared"`.
+
+=== NAT Handling
+
+Endpoint is set only for peers that have `network.externalIps`:
+
+[source,nix]
+----
+// optionalAttrs (hosts.${peerName}.network.externalIps or [ ] != [ ]) {
+ endpoint = "...";
+}
+----
+
+Hosts behind NAT initiate connections outward. Add `persistentKeepalive = 25;` if needed to maintain NAT mappings.
+
+== Deployment
+
+Single command generates all secrets and deploys:
+
+[source,shell]
+----
+fleet --only bernard --only edgeworth deploy switch
+----
+
+No separate key generation step — fleet generates and provisions secrets automatically.
+
+=== Fleet State After Deploy
+
+The fleet state file records encryption keys, per-host keypairs (separate distributions), and shared PSK:
+
+[source,nix]
+----
+{
+ hosts = {
+ bernard.encryptionKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ25i6...";
+ edgeworth.encryptionKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIELc1m...";
+ };
+ secrets = {
+ testwg-key = [
+ {
+ owners = [ "bernard" ];
+ createdAt = "2026-04-23T16:39:29.532714437Z";
+ public.raw = "<PLAINTEXT>T7F6A+6jU87kz4c8GftttyU+xB0KhHIaplDV65psb3M=";
+ secret.raw = "<ENCRYPTED><BASE64-ENCODED>...";
+ }
+ {
+ owners = [ "edgeworth" ];
+ createdAt = "2026-04-23T16:39:43.337788671Z";
+ public.raw = "<PLAINTEXT>QgERRQnE+blQaVNMNiQZ+uk+HEOFKVTmvg4O+MqQ0hA=";
+ secret.raw = "<ENCRYPTED><BASE64-ENCODED>...";
+ }
+ ];
+ testwg-psk = {
+ owners = [ "bernard" "edgeworth" ];
+ createdAt = "2026-04-23T16:39:30.126077659Z";
+ secret.raw = "<ENCRYPTED><BASE64-ENCODED>...";
+ };
+ };
+}
+----
+
+Note how `testwg-key` is a list — each host has its own distribution with a unique keypair.
+`testwg-psk` is a single distribution shared by both hosts.
+
+=== Verification
+
+`wg show` on `edgeworth`:
+
+----
+interface: testwg
+ public key: QgERRQnE+blQaVNMNiQZ+uk+HEOFKVTmvg4O+MqQ0hA=
+ private key: (hidden)
+ listening port: 51825
+
+peer: T7F6A+6jU87kz4c8GftttyU+xB0KhHIaplDV65psb3M=
+ preshared key: (hidden)
+ endpoint: 78.11.11.11:51825
+ allowed ips: fd00:1c86:78d4:dcef::/64
+ latest handshake: 1 second ago
+ transfer: 616 B received, 760 B sent
+----
+
+`wg show` on `bernard`:
+
+----
+interface: testwg
+ public key: T7F6A+6jU87kz4c8GftttyU+xB0KhHIaplDV65psb3M=
+ private key: (hidden)
+ listening port: 51825
+
+peer: QgERRQnE+blQaVNMNiQZ+uk+HEOFKVTmvg4O+MqQ0hA=
+ preshared key: (hidden)
+ endpoint: 109.11.11.11:51825
+ allowed ips: fd00:cac2:10cb:6ee9::/64
+ latest handshake: 7 minutes, 18 seconds ago
+ transfer: 468 B received, 380 B sent
+----
+
+Public keys match between state file and `wg show`. `edgeworth` has no external IP in fleet config, yet `bernard` sees its endpoint — `edgeworth` initiated the connection outward through NAT.
--- /dev/null
+++ b/docs/features/_section.adoc
@@ -0,0 +1,2 @@
+= Features
+:order: 1
--- /dev/null
+++ b/docs/features/modules.adoc
@@ -0,0 +1,51 @@
+= Multi-host Modules
+:order: 0
+
+Fleet modules configure multiple hosts simultaneously. Unlike standard NixOS modules which configure a single machine, fleet modules have access to all hosts in the cluster.
+
+== Basic Module
+
+[source,nix]
+----
+# wireguard/default.nix
+{ config, ... }: {
+ hosts = builtins.mapAttrs (name: host: {
+ nixos.networking.wireguard.interfaces.wg0 = {
+ ips = [ host.config.wireguardIp ];
+ peers = builtins.filter (p: p.name != name)
+ (builtins.attrValues config.hosts);
+ };
+ }) config.hosts;
+}
+----
+
+== Using Modules
+
+Modules are imported in the `fleetConfigurations` block:
+
+[source,nix]
+----
+fleetConfigurations.default = {
+ imports = [
+ ./wireguard
+ ./monitoring
+ ];
+
+ hosts.node-1 = { ... };
+ hosts.node-2 = { ... };
+};
+----
+
+== Multi-instance Modules
+
+Modules can be parameterized and imported multiple times:
+
+[source,nix]
+----
+fleetConfigurations.default = {
+ imports = [
+ (import ./kubernetes { hosts = ["cp-1" "cp-2"]; })
+ (import ./kubernetes { hosts = ["cp-3" "cp-4"]; })
+ ];
+};
+----
--- /dev/null
+++ b/docs/features/rollback.adoc
@@ -0,0 +1,29 @@
+
+= Automatic Rollback
+:order: 2
+
+Fleet automatically rolls back deployments that fail, preventing bricked machines.
+
+== How It Works
+
+When deploying a new system configuration:
+
+1. Fleet activates the new configuration
+2. A health check verifies the system booted successfully
+3. If the check fails, the previous configuration is restored
+
+Rollback works as long as the system passes the initrd stage. Be careful with changes to root filesystem mounts — those are harder to recover from.
+
+== Deployment Process
+
+[source,shell]
+----
+# Deploy to all hosts
+fleet deploy
+
+# Deploy to specific host
+fleet deploy web-1
+
+# Deploy without rollback (not recommended)
+fleet deploy --no-rollback
+----
--- /dev/null
+++ b/docs/features/secrets.adoc
@@ -0,0 +1,30 @@
+= Fleet Secrets Management System
+:order: 1
+
+== Overview
+
+Secret management system is a built-in way to deploy secrets to remote hosts, similar to agenix and other systems.
+
+Secrets are encrypted using system's host ssh key (/etc/ssh/ssh_host_ed25519_key), which is not required to build the
+remote system/add secret to fleet configuration, fleet users are encrypting secrets using received public key instead,
+they don't need the root access to receive the public encryption key.
+
+== Example
+
+[source,nix]
+----
+{
+ secrets = {
+ "my-secret" = {
+ expectedOwners = [ "host1" "host2" ];
+ regenerateOnOwnerAdded = true;
+ generator = {mkImpureSecretGenerator}:
+ mkImpureSecretGenerator {
+ script = ''
+ echo "secret content" | gh private -o $out/secret
+ '';
+ };
+ };
+ }
+}
+----
--- /dev/null
+++ b/docs/getting-started/_section.adoc
@@ -0,0 +1,2 @@
+= Getting Started
+:order: 0
--- /dev/null
+++ b/docs/getting-started/hosts.adoc
@@ -0,0 +1,58 @@
+
+= Configuring Hosts
+:order: 1
+
+Each host in a fleet configuration represents a machine in your cluster.
+
+== Defining Hosts
+
+[source,nix]
+----
+fleetConfigurations.default = {
+ hosts.web-1 = {
+ system = "x86_64-linux";
+ nixos = {
+ imports = [
+ ./web-1/hardware-configuration.nix
+ ./web-1/configuration.nix
+ ];
+ };
+ };
+
+ hosts.web-2 = {
+ system = "x86_64-linux";
+ nixos = {
+ imports = [
+ ./web-2/hardware-configuration.nix
+ ./web-2/configuration.nix
+ ];
+ };
+ };
+};
+----
+
+== Host Systems
+
+Every host must specify a `system` attribute. Supported systems:
+
+- `x86_64-linux`
+- `aarch64-linux`
+- `armv7l-linux`
+- `armv6l-linux`
+
+== Inline NixOS Configuration
+
+NixOS configuration can be specified inline:
+
+[source,nix]
+----
+hosts.monitoring = {
+ system = "x86_64-linux";
+ nixos = {
+ imports = [ ./monitoring/hardware-configuration.nix ];
+ services.prometheus.enable = true;
+ services.grafana.enable = true;
+ networking.firewall.allowedTCPPorts = [ 3000 9090 ];
+ };
+};
+----
--- /dev/null
+++ b/docs/getting-started/index.adoc
@@ -0,0 +1,77 @@
+
+= Fleet
+:order: 0
+
+An NixOS cluster deployment tool.
+
+== Advantages over existing configuration systems (NixOps/Morph)
+
+- Modules can configure multiple hosts at once (i.e. for wireguard/kubernetes installation)
+- Secrets can be securely stored in Git (no one except target hosts can decrypt them), automatically regenerated, reencrypted, etc.
+- Automatic rollback on deployment failure, which will work as long as system is passing initrd stage
+
+== Flake example
+
+[source,nix]
+----
+{
+ description = "My cluster configuration";
+ inputs = {
+ nixpkgs.url = "github:nixos/nixpkgs";
+ fleet = {
+ url = "github:CertainLach/fleet";
+ inputs.nixpkgs.follows = "nixpkgs";
+ };
+ flake-parts.url = "github:hercules-ci/flake-parts";
+ };
+ outputs = inputs:
+ inputs.flake-parts.lib.mkFlake {inherit inputs;} {
+ imports = [inputs.fleet.flakeModules.default];
+
+ fleetConfigurations.default = {
+ nixos = {
+ # Shared NixOS configuration for all hosts
+ };
+
+ imports = [
+ ./wireguard
+ ];
+
+ hosts.controlplane-1 = {
+ system = "x86_64-linux";
+ nixos = {
+ imports = [
+ ./controlplane-1/hardware-configuration.nix
+ ./controlplane-1/configuration.nix
+ ];
+ };
+ };
+ };
+ };
+}
+----
+
+== Secret generator example
+
+[source,nix]
+----
+{config, ...}: {
+ secrets = {
+ gitlab-initial-root = {
+ generator = {mkPassword}: mkPassword {};
+ owner = "gitlab";
+ group = "gitlab";
+ };
+ gitlab-secret = {
+ generator = {mkPassword}: mkPassword {};
+ owner = "gitlab";
+ group = "gitlab";
+ };
+ };
+ services.gitlab = {
+ enable = true;
+ initialRootPasswordFile = config.secrets.gitlab-initial-root.secret.path;
+ secrets.secretFile = config.secrets.gitlab-secret.secret.path;
+ };
+}
+----
--- a/docs/index.adoc
+++ /dev/null
@@ -1,78 +0,0 @@
-
-= Fleet
-:slug: index
-:order: 0
-
-An NixOS cluster deployment tool.
-
-== Advantages over existing configuration systems (NixOps/Morph)
-
-- Modules can configure multiple hosts at once (i.e. for wireguard/kubernetes installation)
-- Secrets can be securely stored in Git (no one except target hosts can decrypt them), automatically regenerated, reencrypted, etc.
-- Automatic rollback on deployment failure, which will work as long as system is passing initrd stage
-
-== Flake example
-
-[source,nix]
-----
-{
- description = "My cluster configuration";
- inputs = {
- nixpkgs.url = "github:nixos/nixpkgs";
- fleet = {
- url = "github:CertainLach/fleet";
- inputs.nixpkgs.follows = "nixpkgs";
- };
- flake-parts.url = "github:hercules-ci/flake-parts";
- };
- outputs = inputs:
- inputs.flake-parts.lib.mkFlake {inherit inputs;} {
- imports = [inputs.fleet.flakeModules.default];
-
- fleetConfigurations.default = {
- nixos = {
- # Shared NixOS configuration for all hosts
- };
-
- imports = [
- ./wireguard
- ];
-
- hosts.controlplane-1 = {
- system = "x86_64-linux";
- nixos = {
- imports = [
- ./controlplane-1/hardware-configuration.nix
- ./controlplane-1/configuration.nix
- ];
- };
- };
- };
- };
-}
-----
-
-== Secret generator example
-
-[source,nix]
-----
-{config, ...}: {
- secrets = {
- gitlab-initial-root = {
- generator = {mkPassword}: mkPassword {};
- owner = "gitlab";
- group = "gitlab";
- };
- gitlab-secret = {
- generator = {mkPassword}: mkPassword {};
- owner = "gitlab";
- group = "gitlab";
- };
- };
- services.gitlab = {
- enable = true;
- initialRootPasswordFile = config.secrets.gitlab-initial-root.secretPath;
- secrets.secretFile = config.secrets.gitlab-secret.secretPath;
- };
-}
-----
--- a/docs/migration.adoc
+++ /dev/null
@@ -1,42 +0,0 @@
-
-= Migration Guide
-:slug: migration
-:order: 2
-
-== fleet.nix <unset> => 0.1.0
-
-Add version field::
-Set it to 0.1.0; This field specifies which version of fleet do you use for cluster management, breaking changes will also break this value to make sure you read this guide.
-
-Move every secret part::
-Before it was only public and private, now it can be any number of parts.
-
-In your fleet.nix file, look at every record like this:
-[source,nix]
-----
-gitlab-initial-root = {
- createdAt = "2024-03-01T15:54:32.983358495Z";
- public = "example";
- secret = "vp%d6wO#0#D2.../dgCA+v4Gf:YG";
-};
-----
-
-And modify it as following:
-[source,nix]
-----
-gitlab-initial-root = {
- createdAt = "2024-03-01T15:54:32.983358495Z";
- public.raw = "<PLAINTEXT>example";
- secret.raw = ''
- <ENCRYPTED><Z85-ENCODED>
- vp%d6wO#0#D2.../dgCA+v4Gf:YG
- '';
-};
-----
-
-Default encoding was also changed from `Z85` to `base64`. This conversion will be done by fleet automatically.
-
-Update references to secrets in fleet/nixos configurations::
-Instead of `config.secrets.secret-name.secretPath` use `config.secrets.secret-name.secret.path`,
-instead of `config.secrets.secret-name.stableSecretPath` use `config.secrets.secret-name.secret.stablePath`,
-instead of `config.secrets.secret-name.public` use `config.secrets.secret-name.public.data`.
--- /dev/null
+++ b/docs/reference/_section.adoc
@@ -0,0 +1,2 @@
+= Reference
+:order: 2
--- /dev/null
+++ b/docs/reference/migration.adoc
@@ -0,0 +1,41 @@
+
+= Migration Guide
+:order: 2
+
+== fleet.nix <unset> => 0.1.0
+
+Add version field::
+Set it to 0.1.0; This field specifies which version of fleet do you use for cluster management, breaking changes will also break this value to make sure you read this guide.
+
+Move every secret part::
+Before it was only public and private, now it can be any number of parts.
+
+In your fleet.nix file, look at every record like this:
+[source,nix]
+----
+gitlab-initial-root = {
+ createdAt = "2024-03-01T15:54:32.983358495Z";
+ public = "example";
+ secret = "vp%d6wO#0#D2.../dgCA+v4Gf:YG";
+};
+----
+
+And modify it as following:
+[source,nix]
+----
+gitlab-initial-root = {
+ createdAt = "2024-03-01T15:54:32.983358495Z";
+ public.raw = "<PLAINTEXT>example";
+ secret.raw = ''
+ <ENCRYPTED><Z85-ENCODED>
+ vp%d6wO#0#D2.../dgCA+v4Gf:YG
+ '';
+};
+----
+
+Default encoding was also changed from `Z85` to `base64`. This conversion will be done by fleet automatically.
+
+Update references to secrets in fleet/nixos configurations::
+Instead of `config.secrets.secret-name.secretPath` use `config.secrets.secret-name.secret.path`,
+instead of `config.secrets.secret-name.stableSecretPath` use `config.secrets.secret-name.secret.stablePath`,
+instead of `config.secrets.secret-name.public` use `config.secrets.secret-name.public.data`.
--- a/docs/secrets.adoc
+++ /dev/null
@@ -1,37 +0,0 @@
-= Fleet Secrets Management System
-:slug: secrets
-:order: 1
-
-== Overview
-
-Secret management system is a built-in way to deploy secrets to remote systems, similar to agenix and other similar systems.
-
-Secrets are encrypted using system's host ssh key (/etc/ssh/ssh_host_ed25519_key), which is not required to build the
-remote system/add secret to fleet configuration, fleet users are encrypting secrets using received public key instead,
-they don't need the root access to receive the public encryption key.
-
-== Example
-
-[source,nix]
-----
-{
- fleet.secrets = {
- "my-secret" = {
- expectedOwners = [ "host1" "host2" ];
- regenerateOnOwnerAdded = true;
- generator = {mkImpureSecretGenerator}:
- mkImpureSecretGenerator {
- script = ''
- echo "secret content" | gh private -o $out/secret
- '';
- };
- };
- }
-}
-----
-
-== Limitations and Future Improvements
-
-- Pure secret generators are currently disabled
-- Support for other secret management systems (e.g systemd-creds has planned asymmetric encryption support)
-
12{ lib }:3let4 inherit (lib.trivial) isFunction functionArgs;5 inherit (lib.options) mkOption mergeOneOption;6 inherit (lib.modules) mkOverride;7 inherit (lib.types)8 listOf9 submodule10 attrsOf11 mkOptionType12 ;13 inherit (lib.strings) optionalString hasPrefix removePrefix;14in15rec {16 types = {17 overlay = mkOptionType {18 name = "nixpkgs-overlay";19 description = "nixpkgs overlay";20 check = {21 __functor = _self: isFunction;22 isV2MergeCoherent = true;23 };24 merge = mergeOneOption;25 };26 listOfOverlay = listOf types.overlay;2728 mkHostsType = module: attrsOf (submodule module);29 mkDataType = module: submodule module;30 };3132 options = {33 mkHostsOption =34 module:35 mkOption {36 type = types.mkHostsType module;37 };38 mkDataOption =39 module:40 mkOption {41 type = types.mkDataType module;42 };43 };4445 inherit (options) mkHostsOption;4647 modules = {48 495051 mkFleetDefault = mkOverride 999;52 535455 mkFleetGeneratorDefault = mkOverride 1001;56 };5758 inherit (modules) mkFleetDefault mkFleetGeneratorDefault;5960 secrets = {6162 6364656667686970717273 mkPassword =74 {75 size ? 32,76 }:77 (78 {79 coreutils,80 mkSecretGenerator,81 }:82 mkSecretGenerator {83 script = ''84 mkdir $out85 gh generate password -o $out/secret --size ${toString size}86 '';87 parts.secret.encrypted = true;88 }89 );9091 9293949596979899100101102103104 mkEd25519 =105 {106 noEmbedPublic ? false,107 encoding ? null,108 }:109 (110 { mkSecretGenerator }:111 mkSecretGenerator {112 script = ''113 mkdir $out114 gh generate ed25519 -p $out/public -s $out/secret \115 ${optionalString noEmbedPublic "--no-embed-public"} \116 ${optionalString (encoding != null) "--encoding=${encoding}"}117 '';118 parts.secret.encrypted = true;119 parts.public.encrypted = false;120 }121 );122123 124125126127128129130131132133134 mkX25519 =135 {136 encoding ? null,137 }:138 (139 { mkSecretGenerator }:140 mkSecretGenerator {141 script = ''142 mkdir $out143 gh generate x25519 -p $out/public -s $out/secret \144 ${optionalString (encoding != null) "--encoding=${encoding}"}145 '';146147 parts.secret.encrypted = true;148 parts.public.encrypted = false;149 }150 );151152 mkAskPass =153 {154 prompt ? "Secret value",155 part ? "secret",156 }:157 (158 {159 kdePackages,160 mkImpureSecretGenerator,161 }:162 mkImpureSecretGenerator {163 164 script = ''165 mkdir $out166 ${kdePackages.kdialog}/bin/kdialog --inputbox "${prompt}" | gh private -o $out/${part}167 '';168169 parts.${part}.encrypted = true;170 }171 );172173 mkAskFile =174 {175 header ? "",176 part ? "secret",177 }:178 (179 {180 kdePackages,181 coreutils,182 mkImpureSecretGenerator,183 }:184 mkImpureSecretGenerator {185 script = ''186 mkdir $out187 tmpfile=$(${coreutils}/bin/mktemp)188 trap "${coreutils}/bin/rm -f $tmpfile" EXIT189 cat > "$tmpfile" <<'HEADER'190 ${header}191 HEADER192 ${kdePackages.kate}/bin/kate --startanon --new --block "$tmpfile"193 gh private -o $out/${part} < "$tmpfile"194 '';195196 parts.${part}.encrypted = true;197 }198 );199200 mkAskEnv =201 {202 header ? "",203 variables ? [ ],204 part ? "secret",205 }:206 mkAskFile {207 inherit part;208 header = builtins.concatStringsSep "\n" (209 (map (l: "# ${l}") (lib.splitString "\n" header))210 ++ (map (v: "${v}=") variables)211 );212 };213214 215216217218219220221222223224 mkRsa =225 {226 size ? 4096,227 }:228 (229 {230 openssl,231 mkSecretGenerator,232 }:233 mkSecretGenerator {234 script = ''235 mkdir $out236237 ${openssl}/bin/openssl genrsa -out rsa_private.key ${toString size}238 ${openssl}/bin/openssl rsa -in rsa_private.key -pubout -out rsa_public.key239240 cat rsa_private.key | gh private -o $out/secret241 cat rsa_public.key | gh public -o $out/public242 '';243244 parts.secret.encrypted = true;245 parts.public.encrypted = false;246 }247 );248249 250251252253254255256257258259260261262263 mkBytes =264 {265 count ? 32,266 encoding,267 noNuls ? false,268 }:269 (270 { mkSecretGenerator }:271 mkSecretGenerator {272 script = ''273 mkdir $out274 gh generate bytes --count=${toString count} --encoding=${encoding} -o $out/secret \275 ${optionalString noNuls "--no-nuls"}276 '';277 parts.secret.encrypted = true;278 }279 );280 281282283 mkHexBytes =284 {285 count ? 32,286 }:287 mkBytes {288 inherit count;289 encoding = "hex";290 };291 292293294 mkBase64Bytes =295 {296 count ? 32,297 }:298 mkBytes {299 inherit count;300 encoding = "base64";301 };302303 304 305 306 };307308 inherit (secrets)309 mkPassword310 mkEd25519311 mkX25519312 mkRsa313 mkBytes314 mkHexBytes315 mkBase64Bytes316 mkAskPass317 mkAskFile318 mkAskEnv319 ;320321 strings =322 let323 plaintextPrefix = "<PLAINTEXT>";324 plaintextNewlinePrefix = "<PLAINTEXT-NL>";325 in326 {327 328329330 decodeRawSecret =331 raw:332 if hasPrefix plaintextPrefix raw then333 removePrefix plaintextPrefix raw334 else if hasPrefix plaintextNewlinePrefix raw then335 removePrefix plaintextNewlinePrefix raw336 else337 throw "decodeRawSecret only works with plaintext-encoded secret public parts, got ${raw}";338 };339340 inherit (strings) decodeRawSecret;341}