all repos

mugit @ f35f950

🐮 git server that your cow will love

mugit/flake.nix (view raw)

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