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