1# NixOS module for Buildbot continuous integration server. 2 3{ config, lib, options, pkgs, ... }: 4 5with lib; 6 7let 8 cfg = config.services.buildbot-master; 9 opt = options.services.buildbot-master; 10 11 package = pkgs.python3.pkgs.toPythonModule cfg.package; 12 python = package.pythonModule; 13 14 escapeStr = escape [ "'" ]; 15 16 defaultMasterCfg = pkgs.writeText "master.cfg" '' 17 from buildbot.plugins import * 18 factory = util.BuildFactory() 19 c = BuildmasterConfig = dict( 20 workers = [${concatStringsSep "," cfg.workers}], 21 protocols = { 'pb': {'port': ${toString cfg.pbPort} } }, 22 title = '${escapeStr cfg.title}', 23 titleURL = '${escapeStr cfg.titleUrl}', 24 buildbotURL = '${escapeStr cfg.buildbotUrl}', 25 db = dict(db_url='${escapeStr cfg.dbUrl}'), 26 www = dict(port=${toString cfg.port}), 27 change_source = [ ${concatStringsSep "," cfg.changeSource} ], 28 schedulers = [ ${concatStringsSep "," cfg.schedulers} ], 29 builders = [ ${concatStringsSep "," cfg.builders} ], 30 services = [ ${concatStringsSep "," cfg.reporters} ], 31 ) 32 for step in [ ${concatStringsSep "," cfg.factorySteps} ]: 33 factory.addStep(step) 34 35 ${cfg.extraConfig} 36 ''; 37 38 tacFile = pkgs.writeText "buildbot-master.tac" '' 39 import os 40 41 from twisted.application import service 42 from buildbot.master import BuildMaster 43 44 basedir = '${cfg.buildbotDir}' 45 46 configfile = '${cfg.masterCfg}' 47 48 # Default umask for server 49 umask = None 50 51 # note: this line is matched against to check that this is a buildmaster 52 # directory; do not edit it. 53 application = service.Application('buildmaster') 54 55 m = BuildMaster(basedir, configfile, umask) 56 m.setServiceParent(application) 57 ''; 58 59in { 60 options = { 61 services.buildbot-master = { 62 63 factorySteps = mkOption { 64 type = types.listOf types.str; 65 description = lib.mdDoc "Factory Steps"; 66 default = []; 67 example = [ 68 "steps.Git(repourl='https://github.com/buildbot/pyflakes.git', mode='incremental')" 69 "steps.ShellCommand(command=['trial', 'pyflakes'])" 70 ]; 71 }; 72 73 changeSource = mkOption { 74 type = types.listOf types.str; 75 description = lib.mdDoc "List of Change Sources."; 76 default = []; 77 example = [ 78 "changes.GitPoller('https://github.com/buildbot/pyflakes.git', workdir='gitpoller-workdir', branch='master', pollinterval=300)" 79 ]; 80 }; 81 82 enable = mkOption { 83 type = types.bool; 84 default = false; 85 description = lib.mdDoc "Whether to enable the Buildbot continuous integration server."; 86 }; 87 88 extraConfig = mkOption { 89 type = types.str; 90 description = lib.mdDoc "Extra configuration to append to master.cfg"; 91 default = "c['buildbotNetUsageData'] = None"; 92 }; 93 94 masterCfg = mkOption { 95 type = types.path; 96 description = lib.mdDoc "Optionally pass master.cfg path. Other options in this configuration will be ignored."; 97 default = defaultMasterCfg; 98 defaultText = literalMD ''generated configuration file''; 99 example = "/etc/nixos/buildbot/master.cfg"; 100 }; 101 102 schedulers = mkOption { 103 type = types.listOf types.str; 104 description = lib.mdDoc "List of Schedulers."; 105 default = [ 106 "schedulers.SingleBranchScheduler(name='all', change_filter=util.ChangeFilter(branch='master'), treeStableTimer=None, builderNames=['runtests'])" 107 "schedulers.ForceScheduler(name='force',builderNames=['runtests'])" 108 ]; 109 }; 110 111 builders = mkOption { 112 type = types.listOf types.str; 113 description = lib.mdDoc "List of Builders."; 114 default = [ 115 "util.BuilderConfig(name='runtests',workernames=['example-worker'],factory=factory)" 116 ]; 117 }; 118 119 workers = mkOption { 120 type = types.listOf types.str; 121 description = lib.mdDoc "List of Workers."; 122 default = [ "worker.Worker('example-worker', 'pass')" ]; 123 }; 124 125 reporters = mkOption { 126 default = []; 127 type = types.listOf types.str; 128 description = lib.mdDoc "List of reporter objects used to present build status to various users."; 129 }; 130 131 user = mkOption { 132 default = "buildbot"; 133 type = types.str; 134 description = lib.mdDoc "User the buildbot server should execute under."; 135 }; 136 137 group = mkOption { 138 default = "buildbot"; 139 type = types.str; 140 description = lib.mdDoc "Primary group of buildbot user."; 141 }; 142 143 extraGroups = mkOption { 144 type = types.listOf types.str; 145 default = []; 146 description = lib.mdDoc "List of extra groups that the buildbot user should be a part of."; 147 }; 148 149 home = mkOption { 150 default = "/home/buildbot"; 151 type = types.path; 152 description = lib.mdDoc "Buildbot home directory."; 153 }; 154 155 buildbotDir = mkOption { 156 default = "${cfg.home}/master"; 157 defaultText = literalExpression ''"''${config.${opt.home}}/master"''; 158 type = types.path; 159 description = lib.mdDoc "Specifies the Buildbot directory."; 160 }; 161 162 pbPort = mkOption { 163 default = 9989; 164 type = types.either types.str types.int; 165 example = "'tcp:9990:interface=127.0.0.1'"; 166 description = lib.mdDoc '' 167 The buildmaster will listen on a TCP port of your choosing 168 for connections from workers. 169 It can also use this port for connections from remote Change Sources, 170 status clients, and debug tools. 171 This port should be visible to the outside world, and youll need to tell 172 your worker admins about your choice. 173 If put in (single) quotes, this can also be used as a connection string, 174 as defined in the [ConnectionStrings guide](https://twistedmatrix.com/documents/current/core/howto/endpoints.html). 175 ''; 176 }; 177 178 listenAddress = mkOption { 179 default = "0.0.0.0"; 180 type = types.str; 181 description = lib.mdDoc "Specifies the bind address on which the buildbot HTTP interface listens."; 182 }; 183 184 buildbotUrl = mkOption { 185 default = "http://localhost:8010/"; 186 type = types.str; 187 description = lib.mdDoc "Specifies the Buildbot URL."; 188 }; 189 190 title = mkOption { 191 default = "Buildbot"; 192 type = types.str; 193 description = lib.mdDoc "Specifies the Buildbot Title."; 194 }; 195 196 titleUrl = mkOption { 197 default = "Buildbot"; 198 type = types.str; 199 description = lib.mdDoc "Specifies the Buildbot TitleURL."; 200 }; 201 202 dbUrl = mkOption { 203 default = "sqlite:///state.sqlite"; 204 type = types.str; 205 description = lib.mdDoc "Specifies the database connection string."; 206 }; 207 208 port = mkOption { 209 default = 8010; 210 type = types.port; 211 description = lib.mdDoc "Specifies port number on which the buildbot HTTP interface listens."; 212 }; 213 214 package = mkOption { 215 type = types.package; 216 default = pkgs.buildbot-full; 217 defaultText = literalExpression "pkgs.buildbot-full"; 218 description = lib.mdDoc "Package to use for buildbot."; 219 example = literalExpression "pkgs.buildbot"; 220 }; 221 222 packages = mkOption { 223 default = [ pkgs.git ]; 224 defaultText = literalExpression "[ pkgs.git ]"; 225 type = types.listOf types.package; 226 description = lib.mdDoc "Packages to add to PATH for the buildbot process."; 227 }; 228 229 pythonPackages = mkOption { 230 type = types.functionTo (types.listOf types.package); 231 default = pythonPackages: with pythonPackages; [ ]; 232 defaultText = literalExpression "pythonPackages: with pythonPackages; [ ]"; 233 description = lib.mdDoc "Packages to add the to the PYTHONPATH of the buildbot process."; 234 example = literalExpression "pythonPackages: with pythonPackages; [ requests ]"; 235 }; 236 }; 237 }; 238 239 config = mkIf cfg.enable { 240 users.groups = optionalAttrs (cfg.group == "buildbot") { 241 buildbot = { }; 242 }; 243 244 users.users = optionalAttrs (cfg.user == "buildbot") { 245 buildbot = { 246 description = "Buildbot User."; 247 isNormalUser = true; 248 createHome = true; 249 inherit (cfg) home group extraGroups; 250 useDefaultShell = true; 251 }; 252 }; 253 254 systemd.services.buildbot-master = { 255 description = "Buildbot Continuous Integration Server."; 256 after = [ "network-online.target" ]; 257 wantedBy = [ "multi-user.target" ]; 258 path = cfg.packages ++ cfg.pythonPackages python.pkgs; 259 environment.PYTHONPATH = "${python.withPackages (self: cfg.pythonPackages self ++ [ package ])}/${python.sitePackages}"; 260 261 preStart = '' 262 mkdir -vp "${cfg.buildbotDir}" 263 # Link the tac file so buildbot command line tools recognize the directory 264 ln -sf "${tacFile}" "${cfg.buildbotDir}/buildbot.tac" 265 ${cfg.package}/bin/buildbot create-master --db "${cfg.dbUrl}" "${cfg.buildbotDir}" 266 rm -f buildbot.tac.new master.cfg.sample 267 ''; 268 269 serviceConfig = { 270 Type = "simple"; 271 User = cfg.user; 272 Group = cfg.group; 273 WorkingDirectory = cfg.home; 274 # NOTE: call twistd directly with stdout logging for systemd 275 ExecStart = "${python.pkgs.twisted}/bin/twistd -o --nodaemon --pidfile= --logfile - --python ${tacFile}"; 276 }; 277 }; 278 }; 279 280 imports = [ 281 (mkRenamedOptionModule [ "services" "buildbot-master" "bpPort" ] [ "services" "buildbot-master" "pbPort" ]) 282 (mkRemovedOptionModule [ "services" "buildbot-master" "status" ] '' 283 Since Buildbot 0.9.0, status targets are deprecated and ignored. 284 Review your configuration and migrate to reporters (available at services.buildbot-master.reporters). 285 '') 286 ]; 287 288 meta.maintainers = with lib.maintainers; [ mic92 lopsided98 ]; 289}