all repos

mugit @ f70e150377a19db58e4348197a9bf08f23c07a4f

🐮 git server that your cow will love

mugit/flake.nix (view raw)

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