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