all repos

mugit @ b883855

🐮 git server that your cow will love

mugit/flake.nix (view raw)

Oleksandr Smirnov Oleksandr Smirnov
olexsmir@gmail.com
nix: mugit completion (hopefully), 3 months ago
1
{
2
  description = "a git server that your cow will love";
3
  inputs = {
4
    nixpkgs.url = "github:NixOS/nixpkgs";
5
    flake-utils.url = "github:numtide/flake-utils";
6
  };
7
  outputs =
8
    {
9
      self,
10
      nixpkgs,
11
      flake-utils,
12
    }:
13
    flake-utils.lib.eachDefaultSystem (
14
      system:
15
      let
16
        pkgs = nixpkgs.legacyPackages.${system};
17
        version = self.rev or "dev";
18
      in
19
      {
20
        packages = {
21
          default = self.packages.${system}.mugit;
22
          mugit = pkgs.buildGoModule {
23
            pname = "mugit";
24
            version = version;
25
            src = ./.;
26
            vendorHash = "sha256-xF8IRS0Ne1zp4u6uolKFpKEZObSM6VhV95JUj2krXPY=";
27
            ldflags = [
28
              "-s"
29
              "-w"
30
              "-X main.version=${version}"
31
            ];
32
            meta = with pkgs.lib; {
33
              homepage = "https://github.com/olexsmir/mugit";
34
              license = licenses.mit;
35
            };
36
          };
37
        };
38
      }
39
    )
40
    // {
41
      nixosModules.default =
42
        {
43
          config,
44
          lib,
45
          pkgs,
46
          ...
47
        }:
48
        with lib;
49
        let
50
          cfg = config.services.mugit;
51
          format = pkgs.formats.yaml { };
52
          configFile = format.generate "config.yaml" cfg.config;
53
        in
54
        {
55
          options.services.mugit = {
56
            enable = mkEnableOption "mugit service";
57
58
            package = mkOption {
59
              type = types.package;
60
              default = self.packages.${pkgs.system}.mugit;
61
              defaultText = literalExpression "self.packages.\${pkgs.system}.mugit";
62
              description = "The mugit package to use.";
63
            };
64
65
            openFirewall = mkOption {
66
              type = types.bool;
67
              default = false;
68
              description = "Whether to open the firewall for mugit. Can only be used with `config`, not `configFile`.";
69
            };
70
71
            exposeCli = mkOption {
72
              type = types.bool;
73
              default = false;
74
              description = "Whether to expose a mugit CLI wrapper to all system users, runs as the mugit user/group.";
75
            };
76
77
            configFile = mkOption {
78
              type = types.nullOr types.path;
79
              default = null;
80
              description = "Path to an existing mugit configuration file. Mutually exclusive with `config`.";
81
            };
82
83
            user = mkOption {
84
              type = types.str;
85
              default = "mugit";
86
              description = "User account under which mugit runs.";
87
            };
88
89
            group = mkOption {
90
              type = types.str;
91
              default = "mugit";
92
              description = "Group under which mugit runs.";
93
            };
94
95
            config = mkOption {
96
              default = {};
97
              description = ''
98
                The primary mugit configuration.
99
                See [docs](https://github.com/olexsmir/mugit) for possible values.
100
              '';
101
              example = literalExpression ''
102
                {
103
                  meta.host = "git.example.org";
104
                  repo.dir = "/var/lib/mugit";
105
                  ssh = {
106
                    enable = true;
107
                    host_key = "/var/lib/mugit/key";
108
                  };
109
                }
110
              '';
111
              type = types.submodule {
112
                options.meta = {
113
                  title = mkOption {
114
                    type = types.str;
115
                    default = "mugit";
116
                    description = "Website title";
117
                  };
118
                  description = mkOption {
119
                    type = types.str;
120
                    default = "";
121
                    description = "Website description";
122
                  };
123
                  host = mkOption {
124
                    type = types.str;
125
                    default = "";
126
                    description = "Website CNAME (required)";
127
                  };
128
                };
129
                options.server = {
130
                  host = mkOption {
131
                    type = types.str;
132
                    default = "";
133
                    description = "Host address";
134
                  };
135
                  port = mkOption {
136
                    type = types.port;
137
                    default = 8080;
138
                    description = "Website port";
139
                  };
140
                };
141
                options.repo = {
142
                  dir = mkOption {
143
                    type = types.str;
144
                    default = "";
145
                    description = "Directory which mugit will scan for repositories (required)";
146
                  };
147
                  masters = mkOption {
148
                    type = types.listOf types.str;
149
                    default = ["master" "main"];
150
                    description = "Master branch to look for";
151
                  };
152
                  readmes = mkOption {
153
                    type = types.listOf types.str;
154
                    default = ["README.md" "readme.md" "README.html" "readme.html" "README.txt" "readme.txt" "readme"];
155
                    description = "Readme files to look for";
156
                  };
157
                };
158
                options.ssh = {
159
                  enable = mkOption {
160
                    type = types.bool;
161
                    default = false;
162
                    description = "Wharever to run ssh server";
163
                  };
164
                  port = mkOption {
165
                    type = types.port;
166
                    default = 2222;
167
                    description = "Website port";
168
                  };
169
                  host_key = mkOption {
170
                    type = types.str;
171
                    default = "";
172
                    description = "Path to ssh private key (required if ssh enabled)";
173
                  };
174
                  keys = mkOption {
175
                    type = types.listOf types.str;
176
                    default = [];
177
                    description = "List of public ssh keys which are allows to do git pushes, and access private repositories";
178
                  };
179
                };
180
                options.mirror = {
181
                  enable = mkOption {
182
                    type = types.bool;
183
                    default = false;
184
                    description = "Wharever to run mirroring worker";
185
                  };
186
                  interval = mkOption {
187
                    type = types.str;
188
                    default = "8h";
189
                    description = "Interval in which mirroring will happen";
190
                  };
191
                  github_token = mkOption {
192
                    type = types.str;
193
                    default = "";
194
                    description = "Github token for pulling from github repos";
195
                  };
196
                };
197
                options.cache = {
198
                  home_page = mkOption {
199
                    type = types.str;
200
                    default = "5m";
201
                    description = "For how long index page is cached";
202
                  };
203
                  readme = mkOption {
204
                    type = types.str;
205
                    default = "1m";
206
                    description = "For how long repos readme is cached";
207
                  };
208
                };
209
              };
210
            };
211
          };
212
213
214
          config = mkIf cfg.enable {
215
            networking.firewall = mkIf cfg.openFirewall {
216
              allowedTCPPorts =
217
                let
218
                  serverPort = cfg.config.server.port or 8080;
219
                  sshPort = cfg.config.ssh.port or 2222;
220
                  sshEnabled = cfg.config.ssh.enable or false;
221
                in
222
                [ serverPort ] ++ lib.optional sshEnabled sshPort;
223
            };
224
225
            users.users.${cfg.user} = {
226
              isSystemUser = true;
227
              group = cfg.group;
228
              home = cfg.config.repo.dir;
229
              createHome = true;
230
              description = "mugit service user";
231
            };
232
233
            users.groups.${cfg.group} = { };
234
235
            environment.systemPackages = lib.mkIf cfg.exposeCli [
236
              (pkgs.runCommandLocal "mugit-completions" {} ''
237
                mkdir -p $out/share/bash-completion/completions
238
                mkdir -p $out/share/zsh/site-functions
239
                mkdir -p $out/share/fish/vendor_completions.d
240
                ${cfg.package}/bin/mugit completion bash > $out/share/bash-completion/completions/mugit
241
                ${cfg.package}/bin/mugit completion zsh  > $out/share/zsh/site-functions/_mugit
242
                ${cfg.package}/bin/mugit completion fish > $out/share/fish/vendor_completions.d/mugit.fish
243
              '')
244
            ];
245
246
            security.wrappers = lib.mkIf cfg.exposeCli {
247
              mugit = {
248
                source =
249
                  let
250
                    resolvedConfig = if cfg.configFile != null then cfg.configFile else configFile;
251
                    mugitWrapped = pkgs.writeScriptBin "mugit" ''
252
                      #!${pkgs.bash}/bin/bash
253
                      exec ${cfg.package}/bin/mugit --config ${resolvedConfig} "$@"
254
                    '';
255
                  in
256
                    "${mugitWrapped}/bin/mugit";
257
                owner = cfg.user;
258
                group = cfg.group;
259
                setuid = true;
260
                setgid = true;
261
                permissions = "u+rx,g+rx,o+rx";
262
              };
263
            };
264
265
            systemd.services.mugit = {
266
              description = "mugit service";
267
              wantedBy = [ "multi-user.target" ];
268
              after = [ "network.target" ];
269
              path = [ pkgs.git ];
270
271
              serviceConfig =
272
                let
273
                  serverPort = cfg.config.server.port or 8080;
274
                  sshPort = cfg.config.ssh.port or 2222;
275
                  sshEnabled = cfg.config.ssh.enable or false;
276
                  needsPrivPort = serverPort < 1024 || (sshEnabled && sshPort < 1024);
277
                in
278
                {
279
                  Type = "simple";
280
                  User = cfg.user;
281
                  Group = cfg.group;
282
                  WorkingDirectory = cfg.config.repo.dir;
283
                  StateDirectory = "mugit";
284
                  ExecStart = "${cfg.package}/bin/mugit serve --config ${configFile}";
285
                  Restart = "on-failure";
286
                  RestartSec = "5s";
287
                  NoNewPrivileges = true;
288
                  PrivateTmp = true;
289
                  ProtectSystem = "strict";
290
                  ProtectHome = true;
291
                  ReadWritePaths = [ cfg.config.repo.dir ];
292
                  ProtectKernelTunables = true;
293
                  ProtectKernelModules = true;
294
                  ProtectControlGroups = true;
295
                }
296
                // lib.optionalAttrs needsPrivPort {
297
                  AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
298
                };
299
            };
300
          };
301
        };
302
    };
303
}