all repos

mugit @ ad82a5b45b1dd9fb368d3fb07ef803df002a2681

🐮 git server that your cow will love

mugit/flake.nix (view raw)

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