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