1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8let
9 cfg = config.services.chhoto-url;
10
11 environment = lib.mapAttrs (
12 _: value:
13 if value == true then
14 "True"
15 else if value == false then
16 "False"
17 else
18 toString value
19 ) cfg.settings;
20in
21
22{
23 meta.maintainers = with lib.maintainers; [ defelo ];
24
25 options.services.chhoto-url = {
26 enable = lib.mkEnableOption "Chhoto URL";
27
28 package = lib.mkPackageOption pkgs "chhoto-url" { };
29
30 settings = lib.mkOption {
31 description = ''
32 Configuration of Chhoto URL.
33 See <https://github.com/SinTan1729/chhoto-url/blob/main/compose.yaml> for a list of options.
34 '';
35 example = {
36 port = 4567;
37 };
38
39 type = lib.types.submodule {
40 freeformType =
41 with lib.types;
42 attrsOf (oneOf [
43 str
44 int
45 bool
46 ]);
47
48 options = {
49 db_url = lib.mkOption {
50 type = lib.types.path;
51 description = "The path of the sqlite database.";
52 default = "/var/lib/chhoto-url/urls.sqlite";
53 };
54
55 port = lib.mkOption {
56 type = lib.types.port;
57 description = "The port to listen on.";
58 example = 4567;
59 };
60
61 cache_control_header = lib.mkOption {
62 type = lib.types.nullOr lib.types.str;
63 description = "The Cache-Control header to send.";
64 default = null;
65 example = "no-cache, private";
66 };
67
68 disable_frontend = lib.mkOption {
69 type = lib.types.bool;
70 description = "Whether to disable the frontend.";
71 default = false;
72 };
73
74 public_mode = lib.mkOption {
75 type = lib.types.bool;
76 description = "Whether to enable public mode.";
77 default = false;
78 apply = x: if x then "Enable" else "Disable";
79 };
80
81 public_mode_expiry_delay = lib.mkOption {
82 type = lib.types.nullOr lib.types.ints.unsigned;
83 description = "The maximum expiry delay in seconds to force in public mode.";
84 default = null;
85 example = 3600;
86 };
87
88 redirect_method = lib.mkOption {
89 type = lib.types.enum [
90 "TEMPORARY"
91 "PERMANENT"
92 ];
93 description = "The redirect method to use.";
94 default = "PERMANENT";
95 };
96
97 hash_algorithm = lib.mkOption {
98 type = lib.types.nullOr (lib.types.enum [ "Argon2" ]);
99 description = ''
100 The hash algorithm to use for passwords and API keys.
101 Set to `null` if you want to provide these secrets as plaintext.
102 '';
103 default = null;
104 };
105
106 site_url = lib.mkOption {
107 type = lib.types.nullOr lib.types.str;
108 description = "The URL under which Chhoto URL is externally reachable.";
109 default = null;
110 };
111
112 slug_style = lib.mkOption {
113 type = lib.types.enum [
114 "Pair"
115 "UID"
116 ];
117 description = "The slug style to use for auto-generated URLs.";
118 default = "Pair";
119 };
120
121 slug_length = lib.mkOption {
122 type = lib.types.addCheck lib.types.int (x: x >= 4);
123 description = "The length of auto-generated slugs.";
124 default = 8;
125 };
126
127 try_longer_slugs = lib.mkOption {
128 type = lib.types.bool;
129 description = "Whether to try a longer UID upon collision.";
130 default = false;
131 };
132
133 allow_capital_letters = lib.mkOption {
134 type = lib.types.bool;
135 description = "Whether to allow capital letters in slugs.";
136 default = false;
137 };
138
139 custom_landing_directory = lib.mkOption {
140 type = lib.types.nullOr lib.types.path;
141 description = "The path of a directory which contains a custom landing page.";
142 default = null;
143 };
144 };
145 };
146 };
147
148 environmentFiles = lib.mkOption {
149 type = lib.types.listOf lib.types.path;
150 default = [ ];
151 example = [ "/run/secrets/chhoto-url.env" ];
152 description = ''
153 Files to load environment variables from in addition to [](#opt-services.chhoto-url.settings).
154 This is useful to avoid putting secrets into the nix store.
155 See <https://github.com/SinTan1729/chhoto-url/blob/main/compose.yaml> for a list of options.
156 '';
157 };
158 };
159
160 config = lib.mkIf cfg.enable {
161 systemd.services.chhoto-url = {
162 wantedBy = [ "multi-user.target" ];
163
164 inherit environment;
165
166 serviceConfig = {
167 User = "chhoto-url";
168 Group = "chhoto-url";
169 DynamicUser = true;
170 StateDirectory = "chhoto-url";
171 EnvironmentFile = cfg.environmentFiles;
172
173 ExecStart = lib.getExe cfg.package;
174
175 # hardening
176 AmbientCapabilities = "";
177 CapabilityBoundingSet = [ "" ];
178 DevicePolicy = "closed";
179 LockPersonality = true;
180 MemoryDenyWriteExecute = true;
181 NoNewPrivileges = true;
182 PrivateDevices = true;
183 PrivateTmp = true;
184 PrivateUsers = true;
185 ProcSubset = "pid";
186 ProtectClock = true;
187 ProtectControlGroups = true;
188 ProtectHome = true;
189 ProtectHostname = true;
190 ProtectKernelLogs = true;
191 ProtectKernelModules = true;
192 ProtectKernelTunables = true;
193 ProtectProc = "invisible";
194 ProtectSystem = "strict";
195 RemoveIPC = true;
196 RestrictAddressFamilies = [ "AF_INET AF_INET6" ];
197 RestrictNamespaces = true;
198 RestrictRealtime = true;
199 RestrictSUIDSGID = true;
200 SocketBindAllow = "tcp:${toString cfg.settings.port}";
201 SocketBindDeny = "any";
202 SystemCallArchitectures = "native";
203 SystemCallFilter = [
204 "@system-service"
205 "~@privileged"
206 "~@resources"
207 ];
208 UMask = "0077";
209 };
210 };
211 };
212}