git.delta.rocks / fleet / refs/commits / ddc0a9996bc8

difftreelog

docs prepare for delta.rocks

zqpwyrzwYaroslav Bolyukin2026-04-23parent: #3ddf3ff.patch.diff
Some of the docs were generated by Claude Opus, but they still should
be correct. Wireguard example and explanation is based on mesh example
written by me:

16 files changed

modifiedREADME.adocdiffbeforeafterboth
--- 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" "$@"
       '';
     })
addeddocs/examples/_section.adocdiffbeforeafterboth
--- /dev/null
+++ b/docs/examples/_section.adoc
@@ -0,0 +1,2 @@
+= Examples
+:order: 2
addeddocs/examples/wireguard.adocdiffbeforeafterboth
--- /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.
addeddocs/features/_section.adocdiffbeforeafterboth
--- /dev/null
+++ b/docs/features/_section.adoc
@@ -0,0 +1,2 @@
+= Features
+:order: 1
addeddocs/features/modules.adocdiffbeforeafterboth

no changes

addeddocs/features/rollback.adocdiffbeforeafterboth
--- /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
+----
addeddocs/features/secrets.adocdiffbeforeafterboth
--- /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
+          '';
+        };
+    };
+  }
+}
+----
addeddocs/getting-started/_section.adocdiffbeforeafterboth
--- /dev/null
+++ b/docs/getting-started/_section.adoc
@@ -0,0 +1,2 @@
+= Getting Started
+:order: 0
addeddocs/getting-started/hosts.adocdiffbeforeafterboth
--- /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 ];
+  };
+};
+----
addeddocs/getting-started/index.adocdiffbeforeafterboth
--- /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;
+  };
+}
+----
deleteddocs/index.adocdiffbeforeafterboth
--- 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;
-  };
-}
-----
deleteddocs/migration.adocdiffbeforeafterboth
--- 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`.
addeddocs/reference/_section.adocdiffbeforeafterboth
--- /dev/null
+++ b/docs/reference/_section.adoc
@@ -0,0 +1,2 @@
+= Reference
+:order: 2
addeddocs/reference/migration.adocdiffbeforeafterboth
--- /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`.
deleteddocs/secrets.adocdiffbeforeafterboth
--- 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)
-
modifiedlib/default.nixdiffbeforeafterboth
--- a/lib/default.nix
+++ b/lib/default.nix
@@ -160,7 +160,6 @@
           mkImpureSecretGenerator,
         }:
         mkImpureSecretGenerator {
-          # TODO: Escape prompt/part (preferrably just use env) to prevent shell injection
           script = ''
             mkdir $out
             ${kdePackages.kdialog}/bin/kdialog --inputbox "${prompt}" | gh private -o $out/${part}