@@ -0,0 +1,148 @@
1+{ config, lib, pkgs, ... }:
2+
3+with lib;
4+
5+let
6+ cfg = config.services.agate;
7+in
8+{
9+ options = {
10+ services.agate = {
11+ enable = mkEnableOption "Agate Server";
12+
13+ package = mkOption {
14+ type = types.package;
15+ default = pkgs.agate;
16+ defaultText = literalExpression "pkgs.agate";
17+ description = "The package to use";
18+ };
19+
20+ addresses = mkOption {
21+ type = types.listOf types.str;
22+ default = [ "0.0.0.0:1965" ];
23+ description = ''
24+ Addresses to listen on, IP:PORT, if you haven't disabled forwarding
25+ only set IPv4.
26+ '';
27+ };
28+
29+ contentDir = mkOption {
30+ default = "/var/lib/agate/content";
31+ type = types.path;
32+ description = "Root of the content directory.";
33+ };
34+
35+ certificatesDir = mkOption {
36+ default = "/var/lib/agate/certificates";
37+ type = types.path;
38+ description = "Root of the certificate directory.";
39+ };
40+
41+ hostnames = mkOption {
42+ default = [ ];
43+ type = types.listOf types.str;
44+ description = ''
45+ Domain name of this Gemini server, enables checking hostname and port
46+ in requests. (multiple occurences means basic vhosts)
47+ '';
48+ };
49+
50+ language = mkOption {
51+ default = null;
52+ type = types.nullOr types.str;
53+ description = "RFC 4646 Language code for text/gemini documents.";
54+ };
55+
56+ onlyTls_1_3 = mkOption {
57+ default = false;
58+ type = types.bool;
59+ description = "Only use TLSv1.3 (default also allows TLSv1.2).";
60+ };
61+
62+ extraArgs = mkOption {
63+ type = types.listOf types.str;
64+ default = [ "" ];
65+ example = [ "--log-ip" ];
66+ description = "Extra arguments to use running agate.";
67+ };
68+ };
69+ };
70+
71+ config = mkIf cfg.enable {
72+ # available for generating certs by hand
73+ # it can be a bit arduous with openssl
74+ environment.systemPackages = [ cfg.package ];
75+
76+ systemd.services.agate = {
77+ description = "Agate";
78+ wantedBy = [ "multi-user.target" ];
79+ after = [ "network.target" "network-online.target" ];
80+
81+ script =
82+ let
83+ prefixKeyList = key: list: concatMap (v: [ key v ]) list;
84+ addresses = prefixKeyList "--addr" cfg.addresses;
85+ hostnames = prefixKeyList "--hostname" cfg.hostnames;
86+ in
87+ ''
88+ exec ${cfg.package}/bin/agate ${
89+ escapeShellArgs (
90+ [
91+ "--content" "${cfg.contentDir}"
92+ "--certs" "${cfg.certificatesDir}"
93+ ] ++
94+ addresses ++
95+ (optionals (cfg.hostnames != []) hostnames) ++
96+ (optionals (cfg.language != null) [ "--lang" cfg.language ]) ++
97+ (optionals cfg.onlyTls_1_3 [ "--only-tls13" ]) ++
98+ (optionals (cfg.extraArgs != []) cfg.extraArgs)
99+ )
100+ }
101+ '';
102+
103+ serviceConfig = {
104+ Restart = "always";
105+ RestartSec = "5s";
106+ DynamicUser = true;
107+ StateDirectory = "agate";
108+
109+ # Security options:
110+ AmbientCapabilities = "";
111+ CapabilityBoundingSet = "";
112+
113+ # ProtectClock= adds DeviceAllow=char-rtc r
114+ DeviceAllow = "";
115+
116+ LockPersonality = true;
117+
118+ PrivateTmp = true;
119+ PrivateDevices = true;
120+ PrivateUsers = true;
121+
122+ ProtectClock = true;
123+ ProtectControlGroups = true;
124+ ProtectHostname = true;
125+ ProtectKernelLogs = true;
126+ ProtectKernelModules = true;
127+ ProtectKernelTunables = true;
128+
129+ RestrictNamespaces = true;
130+ RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
131+ RestrictRealtime = true;
132+
133+ SystemCallArchitectures = "native";
134+ SystemCallErrorNumber = "EPERM";
135+ SystemCallFilter = [
136+ "@system-service"
137+ "~@cpu-emulation"
138+ "~@debug"
139+ "~@keyring"
140+ "~@memlock"
141+ "~@obsolete"
142+ "~@privileged"
143+ "~@setuid"
144+ ];
145+ };
146+ };
147+ };
148+}