all repos

mugit @ 43dfa274f440e76ab536b78d9263c0820c0be970

🐮 git server that your cow will love

mugit/flake.nix (view raw)

Oleksandr Smirnov Oleksandr Smirnov
olexsmir@gmail.com
refactor: markdown renderer , 3 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.buildGoModule {
15
            pname = "mugit";
16
            version = version;
17
            src = ./.;
18
            vendorHash = "sha256-TmCwI6axUTGZEVTXyBxQbO4mbK8Dn9vaTU+/Y+K1oxM=";
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
                  port = mkOption {
145
                    type = types.port;
146
                    default = 2222;
147
                    description = "Website port";
148
                  };
149
                  host_key = mkOption {
150
                    type = types.str;
151
                    default = "";
152
                    description = "Path to ssh private key (required if ssh enabled)";
153
                  };
154
                  keys = mkOption {
155
                    type = types.listOf types.str;
156
                    default = [];
157
                    description = "List of public ssh keys which are allows to do git pushes, and access private repositories";
158
                  };
159
                };
160
                options.mirror = {
161
                  enable = mkOption {
162
                    type = types.bool;
163
                    default = false;
164
                    description = "Wharever to run mirroring worker";
165
                  };
166
                  interval = mkOption {
167
                    type = types.str;
168
                    default = "8h";
169
                    description = "Interval in which mirroring will happen";
170
                  };
171
                  github_token = mkOption {
172
                    type = types.str;
173
                    default = "";
174
                    description = "Github token for pulling from github repos";
175
                  };
176
                };
177
                options.cache = {
178
                  home_page = mkOption {
179
                    type = types.str;
180
                    default = "5m";
181
                    description = "For how long index page is cached";
182
                  };
183
                  readme = mkOption {
184
                    type = types.str;
185
                    default = "1m";
186
                    description = "For how long repos readme is cached";
187
                  };
188
                };
189
              };
190
            };
191
          };
192
193
194
          config = mkIf cfg.enable {
195
            networking.firewall = mkIf cfg.openFirewall {
196
              allowedTCPPorts =
197
                let
198
                  serverPort = cfg.config.server.port or 8080;
199
                  sshPort = cfg.config.ssh.port or 2222;
200
                  sshEnabled = cfg.config.ssh.enable or false;
201
                in
202
                [ serverPort ] ++ lib.optional sshEnabled sshPort;
203
            };
204
205
            users.users.${cfg.user} = {
206
              isSystemUser = true;
207
              group = cfg.group;
208
              home = cfg.config.repo.dir;
209
              createHome = true;
210
              description = "mugit service user";
211
            };
212
213
            users.groups.${cfg.group} = { };
214
215
            environment.systemPackages = lib.mkIf cfg.exposeCli [
216
              (pkgs.runCommandLocal "mugit-completions" {} ''
217
                mkdir -p $out/share/bash-completion/completions
218
                mkdir -p $out/share/zsh/site-functions
219
                mkdir -p $out/share/fish/vendor_completions.d
220
                ${cfg.package}/bin/mugit completion bash > $out/share/bash-completion/completions/mugit
221
                ${cfg.package}/bin/mugit completion zsh  > $out/share/zsh/site-functions/_mugit
222
                ${cfg.package}/bin/mugit completion fish > $out/share/fish/vendor_completions.d/mugit.fish
223
              '')
224
            ];
225
226
            security.wrappers = lib.mkIf cfg.exposeCli {
227
              mugit = {
228
                source =
229
                  let
230
                    resolvedConfig = if cfg.configFile != null then cfg.configFile else configFile;
231
                    mugitWrapped = pkgs.writeScriptBin "mugit" ''
232
                      #!${pkgs.bash}/bin/bash
233
                      exec ${cfg.package}/bin/mugit --config ${resolvedConfig} "$@"
234
                    '';
235
                  in
236
                    "${mugitWrapped}/bin/mugit";
237
                owner = cfg.user;
238
                group = cfg.group;
239
                setuid = true;
240
                setgid = true;
241
                permissions = "u+rx,g+rx,o+rx";
242
              };
243
            };
244
245
            systemd.services.mugit = {
246
              description = "mugit service";
247
              wantedBy = [ "multi-user.target" ];
248
              after = [ "network.target" ];
249
              path = [ pkgs.git ];
250
251
              serviceConfig =
252
                let
253
                  serverPort = cfg.config.server.port or 8080;
254
                  sshPort = cfg.config.ssh.port or 2222;
255
                  sshEnabled = cfg.config.ssh.enable or false;
256
                  needsPrivPort = serverPort < 1024 || (sshEnabled && sshPort < 1024);
257
                in
258
                {
259
                  Type = "simple";
260
                  User = cfg.user;
261
                  Group = cfg.group;
262
                  WorkingDirectory = cfg.config.repo.dir;
263
                  StateDirectory = "mugit";
264
                  ExecStart = "${cfg.package}/bin/mugit serve --config ${configFile}";
265
                  Restart = "on-failure";
266
                  RestartSec = "5s";
267
                  NoNewPrivileges = true;
268
                  PrivateTmp = true;
269
                  ProtectSystem = "strict";
270
                  ProtectHome = true;
271
                  ReadWritePaths = [ cfg.config.repo.dir ];
272
                  ProtectKernelTunables = true;
273
                  ProtectKernelModules = true;
274
                  ProtectControlGroups = true;
275
                }
276
                // lib.optionalAttrs needsPrivPort {
277
                  AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
278
                };
279
            };
280
          };
281
        };
282
    };
283
}