diff --git a/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml
index 29aa70fd616..806a03b51e1 100644
--- a/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml
+++ b/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml
@@ -144,6 +144,14 @@
services.tetrd.
+
+
+ agate,
+ a very simple server for the Gemini hypertext protocol.
+ Available as
+ services.agate.
+
+
ArchiSteamFarm,
diff --git a/nixos/doc/manual/release-notes/rl-2205.section.md b/nixos/doc/manual/release-notes/rl-2205.section.md
index c4ace1366f2..f6d5a3cd4b0 100644
--- a/nixos/doc/manual/release-notes/rl-2205.section.md
+++ b/nixos/doc/manual/release-notes/rl-2205.section.md
@@ -45,6 +45,8 @@ In addition to numerous new and upgraded packages, this release has the followin
- [tetrd](https://tetrd.app), share your internet connection from your device to your PC and vice versa through a USB cable. Available at [services.tetrd](#opt-services.tetrd.enable).
+- [agate](https://github.com/mbrubeck/agate), a very simple server for the Gemini hypertext protocol. Available as [services.agate](options.html#opt-services.agate.enable).
+
- [ArchiSteamFarm](https://github.com/JustArchiNET/ArchiSteamFarm), a C# application with primary purpose of idling Steam cards from multiple accounts simultaneously. Available as [services.archisteamfarm](options.html#opt-services.archisteamfarm.enable).
- [teleport](https://goteleport.com), allows engineers and security professionals to unify access for SSH servers, Kubernetes clusters, web applications, and databases across all environments. Available at [services.teleport](#opt-services.teleport.enable).
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 28724d1e85d..08536de531a 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -1055,6 +1055,7 @@
./services/web-apps/wordpress.nix
./services/web-apps/youtrack.nix
./services/web-apps/zabbix.nix
+ ./services/web-servers/agate.nix
./services/web-servers/apache-httpd/default.nix
./services/web-servers/caddy/default.nix
./services/web-servers/darkhttpd.nix
diff --git a/nixos/modules/services/web-servers/agate.nix b/nixos/modules/services/web-servers/agate.nix
new file mode 100644
index 00000000000..3afdb561c0b
--- /dev/null
+++ b/nixos/modules/services/web-servers/agate.nix
@@ -0,0 +1,148 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.agate;
+in
+{
+ options = {
+ services.agate = {
+ enable = mkEnableOption "Agate Server";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.agate;
+ defaultText = literalExpression "pkgs.agate";
+ description = "The package to use";
+ };
+
+ addresses = mkOption {
+ type = types.listOf types.str;
+ default = [ "0.0.0.0:1965" ];
+ description = ''
+ Addresses to listen on, IP:PORT, if you haven't disabled forwarding
+ only set IPv4.
+ '';
+ };
+
+ contentDir = mkOption {
+ default = "/var/lib/agate/content";
+ type = types.path;
+ description = "Root of the content directory.";
+ };
+
+ certificatesDir = mkOption {
+ default = "/var/lib/agate/certificates";
+ type = types.path;
+ description = "Root of the certificate directory.";
+ };
+
+ hostnames = mkOption {
+ default = [ ];
+ type = types.listOf types.str;
+ description = ''
+ Domain name of this Gemini server, enables checking hostname and port
+ in requests. (multiple occurences means basic vhosts)
+ '';
+ };
+
+ language = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = "RFC 4646 Language code for text/gemini documents.";
+ };
+
+ onlyTls_1_3 = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Only use TLSv1.3 (default also allows TLSv1.2).";
+ };
+
+ extraArgs = mkOption {
+ type = types.listOf types.str;
+ default = [ "" ];
+ example = [ "--log-ip" ];
+ description = "Extra arguments to use running agate.";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ # available for generating certs by hand
+ # it can be a bit arduous with openssl
+ environment.systemPackages = [ cfg.package ];
+
+ systemd.services.agate = {
+ description = "Agate";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" "network-online.target" ];
+
+ script =
+ let
+ prefixKeyList = key: list: concatMap (v: [ key v ]) list;
+ addresses = prefixKeyList "--addr" cfg.addresses;
+ hostnames = prefixKeyList "--hostname" cfg.hostnames;
+ in
+ ''
+ exec ${cfg.package}/bin/agate ${
+ escapeShellArgs (
+ [
+ "--content" "${cfg.contentDir}"
+ "--certs" "${cfg.certificatesDir}"
+ ] ++
+ addresses ++
+ (optionals (cfg.hostnames != []) hostnames) ++
+ (optionals (cfg.language != null) [ "--lang" cfg.language ]) ++
+ (optionals cfg.onlyTls_1_3 [ "--only-tls13" ]) ++
+ (optionals (cfg.extraArgs != []) cfg.extraArgs)
+ )
+ }
+ '';
+
+ serviceConfig = {
+ Restart = "always";
+ RestartSec = "5s";
+ DynamicUser = true;
+ StateDirectory = "agate";
+
+ # Security options:
+ AmbientCapabilities = "";
+ CapabilityBoundingSet = "";
+
+ # ProtectClock= adds DeviceAllow=char-rtc r
+ DeviceAllow = "";
+
+ LockPersonality = true;
+
+ PrivateTmp = true;
+ PrivateDevices = true;
+ PrivateUsers = true;
+
+ ProtectClock = true;
+ ProtectControlGroups = true;
+ ProtectHostname = true;
+ ProtectKernelLogs = true;
+ ProtectKernelModules = true;
+ ProtectKernelTunables = true;
+
+ RestrictNamespaces = true;
+ RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
+ RestrictRealtime = true;
+
+ SystemCallArchitectures = "native";
+ SystemCallErrorNumber = "EPERM";
+ SystemCallFilter = [
+ "@system-service"
+ "~@cpu-emulation"
+ "~@debug"
+ "~@keyring"
+ "~@memlock"
+ "~@obsolete"
+ "~@privileged"
+ "~@setuid"
+ ];
+ };
+ };
+ };
+}