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 options = {
38 services.tangled-knotserver = {
39 enable = mkOption {
40 type = types.bool;
41 default = false;
42 description = "Enable a tangled knotserver";
43 };
44
45 appviewEndpoint = mkOption {
46 type = types.str;
47 default = "https://tangled.sh";
48 description = "Appview endpoint";
49 };
50
51 gitUser = mkOption {
52 type = types.str;
53 default = "git";
54 description = "User that hosts git repos and performs git operations";
55 };
56
57 repo = {
58 scanPath = mkOption {
59 type = types.path;
60 default = "/var/lib/tangled-knot";
61 description = "Path where repositories are scanned from";
62 };
63
64 mainBranch = mkOption {
65 type = types.str;
66 default = "main";
67 description = "Default branch name for repositories";
68 };
69 };
70
71 server = {
72 listenAddr = mkOption {
73 type = types.str;
74 default = "0.0.0.0:5555";
75 description = "Address to listen on";
76 };
77
78 internalListenAddr = mkOption {
79 type = types.str;
80 default = "127.0.0.1:5444";
81 description = "Internal address for inter-service communication";
82 };
83
84 dbPath = mkOption {
85 type = types.path;
86 default = "knotserver.db";
87 description = "Path to the database file";
88 };
89
90 hostname = mkOption {
91 type = types.str;
92 example = "knot.tangled.sh";
93 description = "Hostname for the server (required)";
94 };
95
96 dev = mkOption {
97 type = types.bool;
98 default = false;
99 description = "Enable development mode (disables signature verification)";
100 internal = true;
101 };
102 };
103
104 extraConfig = mkOption {
105 type = types.attrsOf types.str;
106 default = { };
107 example = lib.literalExpression ''
108 {
109 # this is only an example, do NOT do this! your secret will end up readable by *everyone*!
110 KNOT_SERVER_SECRET = "verysecuresecret";
111 }
112 '';
113 description = "Additional environment variables. Use `environmentFile` for secrets.";
114 };
115
116 extraSshdConfig = mkOption {
117 type = types.lines;
118 default = "";
119 example = ''
120 Banner none
121 PasswordAuthentication no
122 KbdInteractiveAuthentication no
123 '';
124 description = "Additional sshd_config options to set for the git user.";
125 };
126
127 environmentFile = mkOption {
128 type = types.nullOr types.path;
129 default = null;
130 example = "/etc/tangled/knotserver.env";
131 description = ''
132 Environment file to set additional configuration and secrets for the knotserver.
133
134 `KNOT_SERVER_SECRET` must be set for the knotserver to work, and can be obtained from
135 [this page](https://tangled.sh/knots).
136 '';
137 };
138 };
139 };
140
141 config = mkIf cfg.enable {
142 warnings = optional cfg.server.dev ''
143 tangled-knotserver: development mode is enabled. This is not recommended in production as signature checks are disabled.
144 '';
145
146 environment.systemPackages = with pkgs; [ git ];
147
148 users.users.${cfg.gitUser} = {
149 home = cfg.repo.scanPath;
150 group = cfg.gitUser;
151 isSystemUser = true;
152 useDefaultShell = true;
153 };
154
155 users.groups.${cfg.gitUser} = { };
156
157 systemd.services.knotserver = {
158 description = "knotserver service";
159 after = [
160 "network-online.target"
161 "sshd.service"
162 ];
163 wants = [
164 "network-online.target"
165 "sshd.service"
166 ];
167 wantedBy = [ "multi-user.target" ];
168 serviceConfig = {
169 User = cfg.gitUser;
170 WorkingDirectory = cfg.repo.scanPath;
171 ExecStart = lib.getExe' tangledPkgs.knotserver "knotserver";
172 Restart = "always";
173
174 StateDirectory = mkIf (lib.hasPrefix "/var/lib/tangled-knot" cfg.repo.scanPath) "tangled-knot";
175 EnvironmentFile = mkIf (cfg.environmentFile != null) cfg.environmentFile;
176 # TODO: hardening
177 };
178
179 environment = {
180 APPVIEW_ENDPOINT = cfg.appviewEndpoint;
181 KNOT_REPO_SCAN_PATH = cfg.repo.scanPath;
182 KNOT_SERVER_INTERNAL_LISTEN_ADDR = cfg.server.internalListenAddr;
183 KNOT_SERVER_LISTEN_ADDR = cfg.server.listenAddr;
184 KNOT_SERVER_HOSTNAME = cfg.server.hostname;
185 } // cfg.extraConfig;
186 };
187
188 systemd.tmpfiles.settings."knotserver-settings"."/var/log/knotserver"."d" = {
189 mode = "0750";
190 user = config.users.users.${cfg.gitUser}.name;
191 group = config.users.groups.${cfg.gitUser}.name;
192 };
193
194 services.openssh = {
195 enable = true;
196 extraConfig = ''
197 Match User ${cfg.gitUser}
198 AuthorizedKeysCommand ${config.security.wrapperDir}/keyfetch
199 AuthorizedKeysCommandUser nobody
200 ${cfg.extraSshdConfig}
201 '';
202 };
203
204 # get around openssh restrictions
205 security.wrappers.keyfetch = {
206 owner = "root";
207 group = config.users.groups.${cfg.gitUser}.name;
208 permissions = "u+rx,go+x";
209 source = lib.getExe' wrapped-packages "keyfetch";
210 };
211 };
212}