summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorAlan Pearce2024-06-07 10:04:16 +0200
committerAlan Pearce2024-06-07 10:31:38 +0200
commitb32bf5bbd6ab2d493df866b0e6f7ecc83b731eaa (patch)
treeeaa83569b25af5d7f0c83c9124a27db724eae80f
parentabbb369ade5c2450e85c0803b47dc6808653951c (diff)
downloadnixfiles-b32bf5bbd6ab2d493df866b0e6f7ecc83b731eaa.tar.lz
nixfiles-b32bf5bbd6ab2d493df866b0e6f7ecc83b731eaa.tar.zst
nixfiles-b32bf5bbd6ab2d493df866b0e6f7ecc83b731eaa.zip
linde: extract git server setup to own file
-rw-r--r--lib/caddy.nix47
-rw-r--r--system/linde.nix243
-rw-r--r--system/settings/services/git-server.nix213
3 files changed, 262 insertions, 241 deletions
diff --git a/lib/caddy.nix b/lib/caddy.nix
new file mode 100644
index 00000000..42777eeb
--- /dev/null
+++ b/lib/caddy.nix
@@ -0,0 +1,47 @@
+{ lib
+, ...
+}:
+rec {
+  subValue = v:
+    if builtins.isList v
+    then
+      builtins.concatStringsSep " "
+        (builtins.map
+          (v:
+            (if lib.strings.hasPrefix "http" v
+            then v
+            else "'${v}'"))
+          v)
+    else toString v;
+
+  headerValue = sep: val:
+    if builtins.isAttrs val
+    then
+      builtins.concatStringsSep "; "
+        (lib.attrsets.mapAttrsToList
+          (k: v:
+            if builtins.isBool v then k else
+            "${k}${sep}${subValue v}"
+          )
+          val)
+    else toString val;
+  genHeader = header:
+    let
+      sep = if header == "content-security-policy" then " " else "=";
+    in
+    value: "${header} \"${headerValue sep value}\"";
+
+  headers = matcher: headers: ''
+    header ${matcher} {
+      ${builtins.concatStringsSep "\n"
+        (lib.attrsets.mapAttrsToList genHeader headers)}
+    }
+  '';
+  security-headers = { matcher ? "", overrides ? { } }: headers matcher ({
+    strict-transport-security = {
+      max-age = 2 * 365 * 24 * 60 * 60;
+    };
+    x-content-type-options = "nosniff";
+    x-frame-options = "DENY";
+  } // overrides);
+}
diff --git a/system/linde.nix b/system/linde.nix
index a257cfeb..d3f60cb0 100644
--- a/system/linde.nix
+++ b/system/linde.nix
@@ -24,6 +24,7 @@ in
       ./linde-hardware.nix
 
       ./settings/pin.nix
+      ./settings/services/git-server.nix
     ];
   age.secrets = {
     paperless =
@@ -75,51 +76,6 @@ in
     nix-output-monitor
   ];
 
-  programs.ssh = with pkgs; {
-    knownHostsFiles = [
-      (writeText "github.keys" ''
-        # github.com:22 SSH-2.0-babeld-05989c77
-        # github.com:22 SSH-2.0-babeld-05989c77
-        # github.com:22 SSH-2.0-babeld-05989c77
-        # github.com:22 SSH-2.0-babeld-05989c77
-        # github.com:22 SSH-2.0-babeld-05989c77
-        github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=
-        github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=
-        github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl
-      '')
-      (writeText "gitlab.keys" ''
-        # gitlab.com:22 SSH-2.0-GitLab-SSHD
-        # gitlab.com:22 SSH-2.0-GitLab-SSHD
-        # gitlab.com:22 SSH-2.0-GitLab-SSHD
-        # gitlab.com:22 SSH-2.0-GitLab-SSHD
-        # gitlab.com:22 SSH-2.0-GitLab-SSHD
-        gitlab.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCsj2bNKTBSpIYDEGk9KxsGh3mySTRgMtXL583qmBpzeQ+jqCMRgBqB98u3z++J1sKlXHWfM9dyhSevkMwSbhoR8XIq/U0tCNyokEi/ueaBMCvbcTHhO7FcwzY92WK4Yt0aGROY5qX2UKSeOvuP4D6TPqKF1onrSzH9bx9XUf2lEdWT/ia1NEKjunUqu1xOB/StKDHMoX4/OKyIzuS0q/T1zOATthvasJFoPrAjkohTyaDUz2LN5JoH839hViyEG82yB+MjcFV5MU3N1l1QL3cVUCh93xSaua1N85qivl+siMkPGbO5xR/En4iEY6K2XPASUEMaieWVNTRCtJ4S8H+9
-        gitlab.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFSMqzJeV9rUzU4kWitGjeR4PWSa29SPqJ1fVkhtj3Hw9xjLVXVYrU9QlYWrOLXBpQ6KWjbjTDTdDkoohFzgbEY=
-        gitlab.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAfuCHKVTjquxvt6CM6tdG4SLp1Btn/nOeHHE5UOzRdf
-      '')
-      (writeText "codeberg.keys" ''
-        # codeberg.org:22 SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u2
-        # codeberg.org:22 SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u2
-        # codeberg.org:22 SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u2
-        # codeberg.org:22 SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u2
-        # codeberg.org:22 SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u2
-        codeberg.org ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC8hZi7K1/2E2uBX8gwPRJAHvRAob+3Sn+y2hxiEhN0buv1igjYFTgFO2qQD8vLfU/HT/P/rqvEeTvaDfY1y/vcvQ8+YuUYyTwE2UaVU5aJv89y6PEZBYycaJCPdGIfZlLMmjilh/Sk8IWSEK6dQr+g686lu5cSWrFW60ixWpHpEVB26eRWin3lKYWSQGMwwKv4LwmW3ouqqs4Z4vsqRFqXJ/eCi3yhpT+nOjljXvZKiYTpYajqUC48IHAxTWugrKe1vXWOPxVXXMQEPsaIRc2hpK+v1LmfB7GnEGvF1UAKnEZbUuiD9PBEeD5a1MZQIzcoPWCrTxipEpuXQ5Tni4mN
-        codeberg.org ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL2pDxWr18SoiDJCGZ5LmxPygTlPu+cCKSkpqkvCyQzl5xmIMeKNdfdBpfbCGDPoZQghePzFZkKJNR/v9Win3Sc=
-        codeberg.org ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIVIC02vnjFyL+I4RHfvIGNtOgJMe769VTF1VR4EB3ZB
-      '')
-      (writeText "sr.ht.keys" ''
-        # git.sr.ht:22 SSH-2.0-OpenSSH_9.6
-        # git.sr.ht:22 SSH-2.0-OpenSSH_9.6
-        # git.sr.ht:22 SSH-2.0-OpenSSH_9.6
-        # git.sr.ht:22 SSH-2.0-OpenSSH_9.6
-        # git.sr.ht:22 SSH-2.0-OpenSSH_9.6
-        git.sr.ht ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDZ+l/lvYmaeOAPeijHL8d4794Am0MOvmXPyvHTtrqvgmvCJB8pen/qkQX2S1fgl9VkMGSNxbp7NF7HmKgs5ajTGV9mB5A5zq+161lcp5+f1qmn3Dp1MWKp/AzejWXKW+dwPBd3kkudDBA1fa3uK6g1gK5nLw3qcuv/V4emX9zv3P2ZNlq9XRvBxGY2KzaCyCXVkL48RVTTJJnYbVdRuq8/jQkDRA8lHvGvKI+jqnljmZi2aIrK9OGT2gkCtfyTw2GvNDV6aZ0bEza7nDLU/I+xmByAOO79R1Uk4EYCvSc1WXDZqhiuO2sZRmVxa0pQSBDn1DB3rpvqPYW+UvKB3SOz
-        git.sr.ht ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCj6y+cJlqK3BHZRLZuM+KP2zGPrh4H66DacfliU1E2DHAd1GGwF4g1jwu3L8gOZUTIvUptqWTkmglpYhFp4Iy4=
-        git.sr.ht ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMZvRd4EtM7R+IHVMWmDkVU3VLQTSwQDSAvW0t2Tkj60
-      '')
-    ];
-  };
-
   # Initial empty root password for easy login:
   users.users.root.initialHashedPassword = "";
   services.openssh = {
@@ -631,48 +587,7 @@ in
     '';
     virtualHosts =
       let
-        subValue = v:
-          if builtins.isList v
-          then
-            builtins.concatStringsSep " "
-              (builtins.map
-                (v:
-                  (if lib.strings.hasPrefix "http" v
-                  then v
-                  else "'${v}'"))
-                v)
-          else toString v;
-
-        headerValue = sep: val:
-          if builtins.isAttrs val
-          then
-            builtins.concatStringsSep "; "
-              (lib.attrsets.mapAttrsToList
-                (k: v:
-                  if builtins.isBool v then k else
-                  "${k}${sep}${subValue v}"
-                )
-                val)
-          else toString val;
-        genHeader = header:
-          let
-            sep = if header == "content-security-policy" then " " else "=";
-          in
-          value: "${header} \"${headerValue sep value}\"";
-
-        headers = matcher: headers: ''
-          header ${matcher} {
-            ${builtins.concatStringsSep "\n"
-              (lib.attrsets.mapAttrsToList genHeader headers)}
-          }
-        '';
-        security-headers = { matcher ? "", overrides ? { } }: headers matcher ({
-          strict-transport-security = {
-            max-age = 2 * 365 * 24 * 60 * 60;
-          };
-          x-content-type-options = "nosniff";
-          x-frame-options = "DENY";
-        } // overrides);
+        inherit (import ../lib/caddy.nix { inherit lib; }) security-headers;
       in
       {
         "http://" = {
@@ -740,85 +655,6 @@ in
             file_server browse
           '';
         };
-        "git.alanpearce.eu" =
-          let
-            fcgi = config.services.fcgiwrap;
-            fcgisocket = "${fcgi.socketType}/${fcgi.socketAddress}";
-          in
-          {
-            useACMEHost = "alanpearce.eu";
-            extraConfig = ''
-              root * ${pkgs.cgit-pink}/cgit/
-              encode zstd gzip
-              ${security-headers {
-                overrides.content-security-policy = {
-                  default-src = [ "none" ];
-                  base-uri = [ "none" ];
-                  style-src = [ "self" "unsafe-inline" ];
-                  script-src = [ "self" "unsafe-inline" ];
-                  form-action = [ "self" ];
-                  connect-src = [ "self" ];
-                  img-src = [ "https" ];
-                  object-src = [ "none" ];
-                };
-              }}
-              handle_path /custom/* {
-                file_server {
-                  root /srv/http/cgit/
-                }
-              }
-              rewrite /robots.txt /assets/robots.txt
-              handle_path /assets/* {
-                file_server  {
-                  hide cgit.cgi
-                }
-              }
-              @git_http_backend path_regexp "^.*/(HEAD|info/refs|objects/info/[^/]+|git-upload-pack)$"
-              handle @git_http_backend {
-                reverse_proxy ${fcgisocket} {
-                  transport fastcgi {
-                    env SCRIPT_FILENAME ${pkgs.git}/libexec/git-core/git-http-backend
-                    env GIT_PROJECT_ROOT ${config.services.gitolite.dataDir}/repositories
-                  }
-                }
-              }
-              handle {
-                reverse_proxy ${fcgisocket} {
-                  transport fastcgi {
-                    env       SCRIPT_FILENAME  {http.vars.root}/cgit.cgi
-                    env       CGIT_CONFIG      ${pkgs.writeText "cgitrc" ''
-                      head-include=/srv/http/cgit/responsive-cgit-css-master/head.html
-                      css=/custom/responsive-cgit-css-master/cgit.css
-                      virtual-root=/
-                      logo=
-                      readme=:README.md
-                      source-filter=${pkgs.cgit-pink}/lib/cgit/filters/syntax-highlighting.py
-                      about-filter=${pkgs.cgit-pink}/lib/cgit/filters/about-formatting.sh
-                      enable-git-config=1
-                      enable-index-owner=0
-                      enable-index-links=1
-                      enable-follow-links=0
-                      enable-log-linecount=1
-                      max-stats=year
-                      snapshots=tar.lz tar.zst zip
-                      cache-size=10240
-                      enable-http-clone=1
-                      enable-commit-graph=1
-                      mimetype-file=${pkgs.nginx}/conf/mime.types
-                      section-from-path=1
-                      noplainemail=1
-                      repository-sort=age
-                      root-title=my personal projects
-                      clone-url=git://git.alanpearce.eu/$CGIT_REPO_URL https://git.alanpearce.eu/$CGIT_REPO_URL
-                      remove-suffix=1
-                      strict-export=git-daemon-export-ok
-                      scan-path=${config.services.gitolite.dataDir}/repositories/
-                    ''}
-                    }
-                  }
-              }
-            '';
-          };
         "ntfy.alanpearce.eu" = {
           useACMEHost = "alanpearce.eu";
           extraConfig = ''
@@ -847,33 +683,6 @@ in
             }
           '';
         };
-        "legit.alanpearce.eu" =
-          let
-            server = config.services.legit.settings.server;
-          in
-          {
-            useACMEHost = "alanpearce.eu";
-            extraConfig = ''
-              encode zstd gzip
-              handle_path /static/* {
-                root * /srv/http/legit/src/static
-                file_server
-              }
-              ${security-headers {
-                overrides.content-security-policy = {
-                  default-src = [ "none" ];
-                  base-uri = [ "none" ];
-                  style-src = [ "self" ];
-                  script-src = [ "none" ];
-                  form-action = [ "self" ];
-                  connect-src = [ "self" ];
-                  img-src = [ "https" ];
-                  object-src = [ "none" ];
-                };
-              }}
-              reverse_proxy ${server.host}:${toString server.port}
-            '';
-          };
         "binarycache.alanpearce.eu" =
           let
             ns = config.services.nix-serve;
@@ -889,54 +698,6 @@ in
     UMask = "007";
   };
 
-  services.fcgiwrap = {
-    enable = true;
-    user = "gitolite";
-    group = "gitolite";
-    preforkProcesses = 2;
-    socketType = "tcp6";
-    socketAddress = "[::1]:9000";
-  };
-  services.gitolite = {
-    enable = true;
-    adminPubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAII8VIII+598QOBxi/52O1Kb19RdUdX0aZmS1/dNoyqc5 alan@hetzner.strongbox";
-    extraGitoliteRc = ''
-      $RC{UMASK} = 0027;
-      $RC{LOG_EXTRA} = 0;
-      $RC{HOSTNAME} = "${config.networking.hostName}";
-      $RC{LOCAL_CODE} = "$rc{GL_ADMIN_BASE}/local";
-      push( @{$RC{ENABLE}}, 'D' );
-      push( @{$RC{ENABLE}}, 'Shell alan' );
-      push( @{$RC{ENABLE}}, 'cgit' );
-      push( @{$RC{ENABLE}}, 'repo-specific-hooks' );
-    '';
-  };
-  services.legit = {
-    enable = true;
-    group = "gitolite";
-    settings = {
-      server.name = "legit.alanpearce.eu";
-      dirs = {
-        templates = "/srv/http/legit/src/templates";
-      };
-      repo = {
-        scanPath = "/srv/http/legit/repos";
-        readme = [
-          "readme"
-          "readme.md"
-          "README.md"
-        ];
-      };
-    };
-  };
-  users.groups.git.gid = config.ids.gids.git;
-  services.gitDaemon = {
-    enable = true;
-    user = "gitolite";
-    group = "gitolite";
-    basePath = "${config.services.gitolite.dataDir}/repositories/";
-  };
-
   networking.nat = {
     enable = true;
     internalInterfaces = [ "ve-+" ];
diff --git a/system/settings/services/git-server.nix b/system/settings/services/git-server.nix
new file mode 100644
index 00000000..612d3ae1
--- /dev/null
+++ b/system/settings/services/git-server.nix
@@ -0,0 +1,213 @@
+{ config
+, lib
+, pkgs
+, ...
+}:
+let
+  inherit (import ../../../lib/caddy.nix { inherit lib; }) security-headers;
+  repos = "${config.services.gitolite.dataDir}/repositories";
+in
+{
+
+  services.fcgiwrap = {
+    enable = true;
+    user = "gitolite";
+    group = "gitolite";
+    preforkProcesses = 2;
+    socketType = "tcp6";
+    socketAddress = "[::1]:9000";
+  };
+  services.gitolite = {
+    enable = true;
+    adminPubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAII8VIII+598QOBxi/52O1Kb19RdUdX0aZmS1/dNoyqc5 alan@hetzner.strongbox";
+    extraGitoliteRc = ''
+      $RC{UMASK} = 0027;
+      $RC{LOG_EXTRA} = 0;
+      $RC{HOSTNAME} = "${config.networking.hostName}";
+      $RC{LOCAL_CODE} = "$rc{GL_ADMIN_BASE}/local";
+      push( @{$RC{ENABLE}}, 'D' );
+      push( @{$RC{ENABLE}}, 'Shell alan' );
+      push( @{$RC{ENABLE}}, 'cgit' );
+      push( @{$RC{ENABLE}}, 'repo-specific-hooks' );
+    '';
+  };
+  services.legit = {
+    enable = true;
+    group = "gitolite";
+    settings = {
+      server.name = "legit.alanpearce.eu";
+      dirs = {
+        templates = "/srv/http/legit/src/templates";
+      };
+      repo = {
+        scanPath = "/srv/http/legit/repos";
+        readme = [
+          "readme"
+          "readme.md"
+          "README.md"
+        ];
+      };
+    };
+  };
+  services.gitDaemon = {
+    enable = true;
+    user = "gitolite";
+    group = "gitolite";
+    basePath = repos;
+  };
+
+  services.caddy.virtualHosts = {
+    "git.alanpearce.eu" =
+      let
+        fcgi = config.services.fcgiwrap;
+        fcgisocket = "${fcgi.socketType}/${fcgi.socketAddress}";
+      in
+      {
+        useACMEHost = "alanpearce.eu";
+        extraConfig = ''
+          root * ${pkgs.cgit-pink}/cgit/
+          encode zstd gzip
+          ${security-headers {
+            overrides.content-security-policy = {
+              default-src = [ "none" ];
+              base-uri = [ "none" ];
+              style-src = [ "self" "unsafe-inline" ];
+              script-src = [ "self" "unsafe-inline" ];
+              form-action = [ "self" ];
+              connect-src = [ "self" ];
+              img-src = [ "https" ];
+              object-src = [ "none" ];
+            };
+          }}
+          handle_path /custom/* {
+            file_server {
+              root /srv/http/cgit/
+            }
+          }
+          rewrite /robots.txt /assets/robots.txt
+          handle_path /assets/* {
+            file_server  {
+              hide cgit.cgi
+            }
+          }
+          @git_http_backend path_regexp "^.*/(HEAD|info/refs|objects/info/[^/]+|git-upload-pack)$"
+          handle @git_http_backend {
+            reverse_proxy ${fcgisocket} {
+              transport fastcgi {
+                env SCRIPT_FILENAME ${pkgs.git}/libexec/git-core/git-http-backend
+                env GIT_PROJECT_ROOT ${repos}
+              }
+            }
+          }
+          handle {
+            reverse_proxy ${fcgisocket} {
+              transport fastcgi {
+                env       SCRIPT_FILENAME  {http.vars.root}/cgit.cgi
+                env       CGIT_CONFIG      ${pkgs.writeText "cgitrc" ''
+                  head-include=/srv/http/cgit/responsive-cgit-css-master/head.html
+                  css=/custom/responsive-cgit-css-master/cgit.css
+                  virtual-root=/
+                  logo=
+                  readme=:README.md
+                  source-filter=${pkgs.cgit-pink}/lib/cgit/filters/syntax-highlighting.py
+                  about-filter=${pkgs.cgit-pink}/lib/cgit/filters/about-formatting.sh
+                  enable-git-config=1
+                  enable-index-owner=0
+                  enable-index-links=1
+                  enable-follow-links=0
+                  enable-log-linecount=1
+                  max-stats=year
+                  snapshots=tar.lz tar.zst zip
+                  cache-size=10240
+                  enable-http-clone=1
+                  enable-commit-graph=1
+                  mimetype-file=${pkgs.nginx}/conf/mime.types
+                  section-from-path=1
+                  noplainemail=1
+                  repository-sort=age
+                  root-title=my personal projects
+                  clone-url=git://git.alanpearce.eu/$CGIT_REPO_URL https://git.alanpearce.eu/$CGIT_REPO_URL
+                  remove-suffix=1
+                  strict-export=git-daemon-export-ok
+                  scan-path=${repos}
+                ''}
+                }
+              }
+          }
+        '';
+      };
+
+    "legit.alanpearce.eu" =
+      let
+        server = config.services.legit.settings.server;
+      in
+      {
+        useACMEHost = "alanpearce.eu";
+        extraConfig = ''
+          encode zstd gzip
+          handle_path /static/* {
+            root * /srv/http/legit/src/static
+            file_server
+          }
+          ${security-headers {
+            overrides.content-security-policy = {
+              default-src = [ "none" ];
+              base-uri = [ "none" ];
+              style-src = [ "self" ];
+              script-src = [ "none" ];
+              form-action = [ "self" ];
+              connect-src = [ "self" ];
+              img-src = [ "https" ];
+              object-src = [ "none" ];
+            };
+          }}
+          reverse_proxy ${server.host}:${toString server.port}
+        '';
+      };
+  };
+
+  programs.ssh = with pkgs; {
+    knownHostsFiles = [
+      (writeText "github.keys" ''
+        # github.com:22 SSH-2.0-babeld-05989c77
+        # github.com:22 SSH-2.0-babeld-05989c77
+        # github.com:22 SSH-2.0-babeld-05989c77
+        # github.com:22 SSH-2.0-babeld-05989c77
+        # github.com:22 SSH-2.0-babeld-05989c77
+        github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=
+        github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=
+        github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl
+      '')
+      (writeText "gitlab.keys" ''
+        # gitlab.com:22 SSH-2.0-GitLab-SSHD
+        # gitlab.com:22 SSH-2.0-GitLab-SSHD
+        # gitlab.com:22 SSH-2.0-GitLab-SSHD
+        # gitlab.com:22 SSH-2.0-GitLab-SSHD
+        # gitlab.com:22 SSH-2.0-GitLab-SSHD
+        gitlab.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCsj2bNKTBSpIYDEGk9KxsGh3mySTRgMtXL583qmBpzeQ+jqCMRgBqB98u3z++J1sKlXHWfM9dyhSevkMwSbhoR8XIq/U0tCNyokEi/ueaBMCvbcTHhO7FcwzY92WK4Yt0aGROY5qX2UKSeOvuP4D6TPqKF1onrSzH9bx9XUf2lEdWT/ia1NEKjunUqu1xOB/StKDHMoX4/OKyIzuS0q/T1zOATthvasJFoPrAjkohTyaDUz2LN5JoH839hViyEG82yB+MjcFV5MU3N1l1QL3cVUCh93xSaua1N85qivl+siMkPGbO5xR/En4iEY6K2XPASUEMaieWVNTRCtJ4S8H+9
+        gitlab.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFSMqzJeV9rUzU4kWitGjeR4PWSa29SPqJ1fVkhtj3Hw9xjLVXVYrU9QlYWrOLXBpQ6KWjbjTDTdDkoohFzgbEY=
+        gitlab.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAfuCHKVTjquxvt6CM6tdG4SLp1Btn/nOeHHE5UOzRdf
+      '')
+      (writeText "codeberg.keys" ''
+        # codeberg.org:22 SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u2
+        # codeberg.org:22 SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u2
+        # codeberg.org:22 SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u2
+        # codeberg.org:22 SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u2
+        # codeberg.org:22 SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u2
+        codeberg.org ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC8hZi7K1/2E2uBX8gwPRJAHvRAob+3Sn+y2hxiEhN0buv1igjYFTgFO2qQD8vLfU/HT/P/rqvEeTvaDfY1y/vcvQ8+YuUYyTwE2UaVU5aJv89y6PEZBYycaJCPdGIfZlLMmjilh/Sk8IWSEK6dQr+g686lu5cSWrFW60ixWpHpEVB26eRWin3lKYWSQGMwwKv4LwmW3ouqqs4Z4vsqRFqXJ/eCi3yhpT+nOjljXvZKiYTpYajqUC48IHAxTWugrKe1vXWOPxVXXMQEPsaIRc2hpK+v1LmfB7GnEGvF1UAKnEZbUuiD9PBEeD5a1MZQIzcoPWCrTxipEpuXQ5Tni4mN
+        codeberg.org ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL2pDxWr18SoiDJCGZ5LmxPygTlPu+cCKSkpqkvCyQzl5xmIMeKNdfdBpfbCGDPoZQghePzFZkKJNR/v9Win3Sc=
+        codeberg.org ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIVIC02vnjFyL+I4RHfvIGNtOgJMe769VTF1VR4EB3ZB
+      '')
+      (writeText "sr.ht.keys" ''
+        # git.sr.ht:22 SSH-2.0-OpenSSH_9.6
+        # git.sr.ht:22 SSH-2.0-OpenSSH_9.6
+        # git.sr.ht:22 SSH-2.0-OpenSSH_9.6
+        # git.sr.ht:22 SSH-2.0-OpenSSH_9.6
+        # git.sr.ht:22 SSH-2.0-OpenSSH_9.6
+        git.sr.ht ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDZ+l/lvYmaeOAPeijHL8d4794Am0MOvmXPyvHTtrqvgmvCJB8pen/qkQX2S1fgl9VkMGSNxbp7NF7HmKgs5ajTGV9mB5A5zq+161lcp5+f1qmn3Dp1MWKp/AzejWXKW+dwPBd3kkudDBA1fa3uK6g1gK5nLw3qcuv/V4emX9zv3P2ZNlq9XRvBxGY2KzaCyCXVkL48RVTTJJnYbVdRuq8/jQkDRA8lHvGvKI+jqnljmZi2aIrK9OGT2gkCtfyTw2GvNDV6aZ0bEza7nDLU/I+xmByAOO79R1Uk4EYCvSc1WXDZqhiuO2sZRmVxa0pQSBDn1DB3rpvqPYW+UvKB3SOz
+        git.sr.ht ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCj6y+cJlqK3BHZRLZuM+KP2zGPrh4H66DacfliU1E2DHAd1GGwF4g1jwu3L8gOZUTIvUptqWTkmglpYhFp4Iy4=
+        git.sr.ht ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMZvRd4EtM7R+IHVMWmDkVU3VLQTSwQDSAvW0t2Tkj60
+      '')
+    ];
+  };
+}