This repository has no description
1# modified from https://tangled.sh/@tangled.sh/core/blob/master/flake.nix
2tangledFlake:
3{
4 config,
5 pkgs,
6 lib,
7 ...
8}:
9let
10 inherit (lib)
11 mkOption
12 types
13 mkIf
14 optional
15 ;
16 cfg = config.services.tangled-knotserver;
17 tangledPkgs = tangledFlake.packages.${pkgs.system};
18
19 wrapped-packages =
20 pkgs.runCommandCC "tangled-packages-wrapped" { nativeBuildInputs = [ pkgs.makeBinaryWrapper ]; }
21 ''
22 mkdir -p $out/bin
23
24 makeBinaryWrapper ${lib.getExe' tangledPkgs.repoguard "repoguard"} $out/bin/repoguard \
25 --add-flags -internal-api=http://${cfg.server.internalListenAddr}
26 # other flags are set by keyfetch
27
28 makeBinaryWrapper ${lib.getExe' tangledPkgs.keyfetch "keyfetch"} $out/bin/keyfetch \
29 --add-flags "-repoguard-path=$out/bin/repoguard" \
30 --add-flags "-internal-api=http://${cfg.server.internalListenAddr}" \
31 --add-flags "-git-dir=${cfg.repo.scanPath}" \
32 --add-flags "-log-path=/var/log/knotserver/repoguard.log"
33 '';
34
35in
36{
37 imports = [
38 (lib.mkRenamedOptionModule ["services" "tangled-knotserver" "gitUser"] ["services" "tangled-knotserver" "user"])
39 ];
40
41 options = {
42 services.tangled-knotserver = {
43 enable = mkOption {
44 type = types.bool;
45 default = false;
46 description = "Enable a tangled knotserver";
47 };
48
49 appviewEndpoint = mkOption {
50 type = types.str;
51 default = "https://tangled.sh";
52 description = "Appview endpoint";
53 };
54
55 user = mkOption {
56 type = types.str;
57 default = "git";
58 description = "User that runs the server, hosts git repos and performs git operations";
59 };
60
61 git = {
62 name = mkOption {
63 type = types.str;
64 default = "Tangled Knot daemon";
65 description = "Git username for git operations that requires one.";
66 };
67 email = mkOption {
68 type = types.str;
69 default = "knot@example.invalid";
70 description = "Git email address for git operations that requires one.";
71 };
72 };
73
74 repo = {
75 scanPath = mkOption {
76 type = types.path;
77 default = "/var/lib/tangled-knot";
78 description = "Path where repositories are stored";
79 };
80
81 mainBranch = mkOption {
82 type = types.str;
83 default = "main";
84 description = "Default branch name for repositories";
85 };
86 };
87
88 server = {
89 listenAddr = mkOption {
90 type = types.str;
91 default = "0.0.0.0:5555";
92 description = "Address to listen on";
93 };
94
95 internalListenAddr = mkOption {
96 type = types.str;
97 default = "127.0.0.1:5444";
98 description = "Internal address for inter-service communication";
99 };
100
101 dbPath = mkOption {
102 type = types.path;
103 default = "knotserver.db";
104 description = "Path to the database file";
105 };
106
107 hostname = mkOption {
108 type = types.str;
109 example = "knot.tangled.sh";
110 description = "Hostname for the server (required)";
111 };
112
113 dev = mkOption {
114 type = types.bool;
115 default = false;
116 description = "Enable development mode (disables signature verification)";
117 internal = true;
118 };
119 };
120
121 extraConfig = mkOption {
122 type = types.attrsOf types.str;
123 default = { };
124 example = lib.literalExpression ''
125 {
126 # this is only an example, do NOT do this! your secret will end up readable by *everyone*!
127 KNOT_SERVER_SECRET = "verysecuresecret";
128 }
129 '';
130 description = ''
131 Additional environment variables. Use `environmentFile` for secrets.
132
133 `KNOT_SERVER_SECRET` must be set for the knotserver to work, and can be obtained from
134 [this page](https://tangled.sh/knots). Please set this with environmentFile instead of setting it here
135 directly.
136 '';
137 };
138
139 extraSshdConfig = mkOption {
140 type = types.lines;
141 default = "";
142 example = ''
143 Banner none
144 PasswordAuthentication no
145 KbdInteractiveAuthentication no
146 '';
147 description = "Additional sshd_config options to set for the git user.";
148 };
149
150 environmentFile = mkOption {
151 type = types.nullOr types.path;
152 default = null;
153 example = "/etc/tangled/knotserver.env";
154 description = ''
155 Environment file to set additional configuration and secrets for the knotserver.
156
157 `KNOT_SERVER_SECRET` must be set for the knotserver to work, and can be obtained from
158 [this page](https://tangled.sh/knots).
159 '';
160 };
161 };
162 };
163
164 config = mkIf cfg.enable {
165 warnings = optional cfg.server.dev ''
166 tangled-knotserver: development mode is enabled. This is not recommended in production as signature checks are disabled.
167 '';
168
169 environment.systemPackages = with pkgs; [ git ];
170
171 users.users.${cfg.user} = {
172 home = cfg.repo.scanPath;
173 group = cfg.user;
174 isSystemUser = true;
175 useDefaultShell = true;
176 };
177
178 users.groups.${cfg.user} = { };
179
180 systemd.services.knotserver = {
181 description = "knotserver service";
182 path = [pkgs.git];
183 after = [
184 "network-online.target"
185 "sshd.service"
186 ];
187 wants = [
188 "network-online.target"
189 "sshd.service"
190 ];
191 wantedBy = [ "multi-user.target" ];
192
193 preStart = ''
194 git config --global user.name "${cfg.git.name}"
195 git config --global user.email "${cfg.git.email}"
196 '';
197
198 serviceConfig = {
199 User = cfg.user;
200 WorkingDirectory = cfg.repo.scanPath;
201 ExecStart = lib.getExe' tangledPkgs.knotserver "knotserver";
202 Restart = "always";
203
204 StateDirectory = mkIf (lib.hasPrefix "/var/lib/tangled-knot" cfg.repo.scanPath) "tangled-knot";
205 EnvironmentFile = mkIf (cfg.environmentFile != null) cfg.environmentFile;
206 # TODO: hardening
207 };
208
209 environment = {
210 APPVIEW_ENDPOINT = cfg.appviewEndpoint;
211 KNOT_REPO_SCAN_PATH = cfg.repo.scanPath;
212 KNOT_SERVER_INTERNAL_LISTEN_ADDR = cfg.server.internalListenAddr;
213 KNOT_SERVER_LISTEN_ADDR = cfg.server.listenAddr;
214 KNOT_SERVER_HOSTNAME = cfg.server.hostname;
215 } // cfg.extraConfig;
216 };
217
218 systemd.tmpfiles.settings."knotserver-settings"."/var/log/knotserver"."d" = {
219 mode = "0750";
220 user = config.users.users.${cfg.user}.name;
221 group = config.users.groups.${cfg.user}.name;
222 };
223
224 services.openssh = {
225 enable = true;
226 extraConfig = ''
227 Match User ${cfg.user}
228 AuthorizedKeysCommand ${config.security.wrapperDir}/keyfetch
229 AuthorizedKeysCommandUser nobody
230 ${cfg.extraSshdConfig}
231 '';
232 };
233
234 # get around openssh restrictions
235 security.wrappers.keyfetch = {
236 owner = "root";
237 group = config.users.groups.${cfg.user}.name;
238 permissions = "u+rx,go+x";
239 source = lib.getExe' wrapped-packages "keyfetch";
240 };
241 };
242}