]> Git — Sourcephile - sourcephile-nix.git/blob - nixos/modules/services/misc/sourcehut/builds.nix
sourcehut: use systemd timers instead of cron
[sourcephile-nix.git] / nixos / modules / services / misc / sourcehut / builds.nix
1 { config, lib, pkgs, ... }:
2
3 with lib;
4 let
5 cfg = config.services.sourcehut;
6 scfg = cfg.builds;
7 rcfg = config.services.redis;
8 iniKey = "builds.sr.ht";
9 statePath = "/var/lib/sourcehut/buildsrht";
10
11 drv = pkgs.sourcehut.buildsrht;
12 in
13 {
14 options.services.sourcehut.builds = {
15 enable = mkEnableOption "builds service";
16
17 user = mkOption {
18 type = types.str;
19 default = "buildsrht";
20 description = ''
21 User for builds.sr.ht.
22 '';
23 };
24
25 port = mkOption {
26 type = types.port;
27 default = 5002;
28 description = ''
29 Port on which the "builds" module should listen.
30 '';
31 };
32
33 database = mkOption {
34 type = types.str;
35 default = "builds.sr.ht";
36 description = ''
37 PostgreSQL database name for builds.sr.ht.
38 '';
39 };
40
41 enableWorker = mkOption {
42 type = types.bool;
43 default = false;
44 description = ''
45 Run workers for builds.sr.ht.
46 '';
47 };
48
49 images = mkOption {
50 type = types.attrsOf (types.attrsOf (types.attrsOf types.package));
51 default = { };
52 example = lib.literalExample ''(let
53 # Pinning unstable to allow usage with flakes and limit rebuilds.
54 pkgs_unstable = builtins.fetchGit {
55 url = "https://github.com/NixOS/nixpkgs";
56 rev = "ff96a0fa5635770390b184ae74debea75c3fd534";
57 ref = "nixos-unstable";
58 };
59 image_from_nixpkgs = pkgs_unstable: (import ("${pkgs.sourcehut.buildsrht}/lib/images/nixos/image.nix") {
60 pkgs = (import pkgs_unstable {});
61 });
62 in
63 {
64 nixos.unstable.x86_64 = image_from_nixpkgs pkgs_unstable;
65 }
66 )'';
67 description = ''
68 Images for builds.sr.ht. Each package should be distro.release.arch and point to a /nix/store/package/root.img.qcow2.
69 '';
70 };
71
72 };
73
74 config = with scfg; let
75 image_dirs = lib.lists.flatten (
76 lib.attrsets.mapAttrsToList
77 (distro: revs:
78 lib.attrsets.mapAttrsToList
79 (rev: archs:
80 lib.attrsets.mapAttrsToList
81 (arch: image:
82 pkgs.runCommandNoCC "buildsrht-images" { } ''
83 mkdir -p $out/${distro}/${rev}/${arch}
84 ln -s ${image}/*.qcow2 $out/${distro}/${rev}/${arch}/root.img.qcow2
85 '')
86 archs)
87 revs)
88 scfg.images);
89 image_dir_pre = pkgs.symlinkJoin {
90 name = "builds.sr.ht-worker-images-pre";
91 paths = image_dirs ++ [
92 "${pkgs.sourcehut.buildsrht}/lib/images"
93 ];
94 };
95 image_dir = pkgs.runCommandNoCC "builds.sr.ht-worker-images" { } ''
96 mkdir -p $out/images
97 cp -Lr ${image_dir_pre}/* $out/images
98 '';
99 in
100 lib.mkIf (cfg.enable && scfg.enable) {
101 users = {
102 users = {
103 "${user}" = {
104 isSystemUser = true;
105 group = user;
106 extraGroups = lib.optionals cfg.builds.enableWorker [ "docker" ];
107 description = "builds.sr.ht user";
108 };
109 };
110
111 groups = {
112 "${user}" = { };
113 };
114 };
115
116 virtualisation.docker.enable = true;
117
118 services.postgresql = {
119 authentication = ''
120 local ${database} ${user} trust
121 '';
122 ensureDatabases = [ database ];
123 ensureUsers = [
124 {
125 name = user;
126 ensurePermissions = { "DATABASE \"${database}\"" = "ALL PRIVILEGES"; };
127 }
128 ];
129 };
130
131 systemd = {
132 services = {
133 buildsrht = import ./service.nix { inherit config pkgs lib; } scfg drv iniKey
134 {
135 after = [ "postgresql.service" "network.target" ];
136 requires = [ "postgresql.service" ];
137 wantedBy = [ "multi-user.target" ];
138
139 description = "builds.sr.ht website service";
140
141 serviceConfig.ExecStart = "${cfg.python}/bin/gunicorn ${drv.pname}.app:app -b ${cfg.address}:${toString port}";
142
143 # Hack to bypass this hack: https://git.sr.ht/~sircmpwn/core.sr.ht/tree/master/item/srht-update-profiles#L6
144 } // { preStart = " "; };
145
146 buildsrht-worker = {
147 enable = scfg.enableWorker;
148 after = [ "postgresql.service" "network.target" ];
149 requires = [ "postgresql.service" ];
150 wantedBy = [ "multi-user.target" ];
151 partOf = [ "buildsrht.service" ];
152 description = "builds.sr.ht worker service";
153 path = [ pkgs.openssh pkgs.docker ];
154 preStart = let qemuPackage = pkgs.qemu_kvm;
155 in ''
156 if [[ "$(docker images -q qemu:latest 2> /dev/null)" == "" || "$(cat ${statePath}/docker-image-qemu 2> /dev/null || true)" != "${qemuPackage.version}" ]]; then
157 # Create and import qemu:latest image for docker
158 ${
159 pkgs.dockerTools.streamLayeredImage {
160 name = "qemu";
161 tag = "latest";
162 contents = [ qemuPackage ];
163 }
164 } | docker load
165 # Mark down current package version
166 printf "%s" "${qemuPackage.version}" > ${statePath}/docker-image-qemu
167 fi
168 '';
169 serviceConfig = {
170 Type = "simple";
171 User = user;
172 Group = "nginx";
173 Restart = "always";
174 LogsDirectory = [ "sourcehut/builds" ];
175 };
176 serviceConfig.ExecStart = "${pkgs.sourcehut.buildsrht}/bin/builds.sr.ht-worker";
177 };
178 };
179 };
180
181 services.sourcehut.settings = {
182 # Register the builds.sr.ht dispatcher
183 "git.sr.ht::dispatch".${builtins.unsafeDiscardStringContext "${pkgs.sourcehut.buildsrht}/bin/buildsrht-keys"} = mkDefault "${user}:${user}";
184 # Location for build logs, images, and control command
185 } // lib.attrsets.optionalAttrs scfg.enableWorker {
186 # Default worker stores logs that are accessible via this address:port
187 "builds.sr.ht::worker".name = mkDefault "127.0.0.1:5020";
188 "builds.sr.ht::worker".buildlogs = mkDefault "/var/log/sourcehut/builds";
189 "builds.sr.ht::worker".images = mkDefault "${image_dir}/images";
190 "builds.sr.ht::worker".controlcmd = mkDefault "${image_dir}/images/control";
191 "builds.sr.ht::worker".timeout = mkDefault "3m";
192 };
193
194 services.nginx.virtualHosts."logs.${cfg.originBase}" =
195 if scfg.enableWorker then {
196 listen = with builtins; let address = split ":" cfg.settings."builds.sr.ht::worker".name;
197 in [{ addr = elemAt address 0; port = lib.toInt (elemAt address 2); }];
198 locations."/logs".alias = cfg.settings."builds.sr.ht::worker".buildlogs + "/";
199 } else { };
200
201 services.nginx.virtualHosts."builds.${cfg.originBase}" = {
202 forceSSL = true;
203 locations."/".proxyPass = "http://${cfg.address}:${toString port}";
204 locations."/query".proxyPass = cfgIni."meta.sr.ht".api-origin;
205 locations."/static".root = "${pkgs.sourcehut.buildsrht}/${pkgs.sourcehut.python.sitePackages}/buildsrht";
206 };
207 };
208 }