treewide: rework GPG signing
[buildbot.git] / phase2 / master.cfg
1 # -*- python -*-
2 # ex: set syntax=python:
3
4 import os
5 import re
6 import base64
7 import subprocess
8 import ConfigParser
9
10 from buildbot import locks
11
12 ini = ConfigParser.ConfigParser()
13 ini.read("./config.ini")
14
15 buildbot_url = ini.get("general", "buildbot_url")
16
17 # This is a sample buildmaster config file. It must be installed as
18 # 'master.cfg' in your buildmaster's base directory.
19
20 # This is the dictionary that the buildmaster pays attention to. We also use
21 # a shorter alias to save typing.
22 c = BuildmasterConfig = {}
23
24 ####### BUILDSLAVES
25
26 # The 'slaves' list defines the set of recognized buildslaves. Each element is
27 # a BuildSlave object, specifying a unique slave name and password. The same
28 # slave name and password must be configured on the slave.
29 from buildbot.buildslave import BuildSlave
30
31 slave_port = 9990
32 persistent = False
33 other_builds = 0
34 tree_expire = 0
35 git_ssh = False
36 git_ssh_key = None
37
38 if ini.has_option("general", "port"):
39 slave_port = ini.getint("general", "port")
40
41 if ini.has_option("general", "persistent"):
42 persistent = ini.getboolean("general", "persistent")
43
44 if ini.has_option("general", "other_builds"):
45 other_builds = ini.getint("general", "other_builds")
46
47 if ini.has_option("general", "expire"):
48 tree_expire = ini.getint("general", "expire")
49
50 if ini.has_option("general", "git_ssh"):
51 git_ssh = ini.getboolean("general", "git_ssh")
52
53 if ini.has_option("general", "git_ssh_key"):
54 git_ssh_key = ini.get("general", "git_ssh_key")
55 else:
56 git_ssh = False
57
58 c['slaves'] = []
59 max_builds = dict()
60
61 for section in ini.sections():
62 if section.startswith("slave "):
63 if ini.has_option(section, "name") and ini.has_option(section, "password"):
64 name = ini.get(section, "name")
65 password = ini.get(section, "password")
66 max_builds[name] = 1
67 if ini.has_option(section, "builds"):
68 max_builds[name] = ini.getint(section, "builds")
69 c['slaves'].append(BuildSlave(name, password, max_builds = max_builds[name]))
70
71 # 'slavePortnum' defines the TCP port to listen on for connections from slaves.
72 # This must match the value configured into the buildslaves (with their
73 # --master option)
74 c['slavePortnum'] = slave_port
75
76 # coalesce builds
77 c['mergeRequests'] = True
78
79 # Reduce amount of backlog data
80 c['buildHorizon'] = 30
81 c['logHorizon'] = 20
82
83 ####### CHANGESOURCES
84
85 home_dir = os.path.abspath(ini.get("general", "homedir"))
86 scripts_dir = os.path.abspath("../scripts")
87
88 rsync_bin_url = ini.get("rsync", "binary_url")
89 rsync_bin_key = ini.get("rsync", "binary_password")
90
91 rsync_src_url = None
92 rsync_src_key = None
93
94 if ini.has_option("rsync", "source_url"):
95 rsync_src_url = ini.get("rsync", "source_url")
96 rsync_src_key = ini.get("rsync", "source_password")
97
98 rsync_sdk_url = None
99 rsync_sdk_key = None
100 rsync_sdk_pat = "openwrt-sdk-*.tar.xz"
101
102 if ini.has_option("rsync", "sdk_url"):
103 rsync_sdk_url = ini.get("rsync", "sdk_url")
104
105 if ini.has_option("rsync", "sdk_password"):
106 rsync_sdk_key = ini.get("rsync", "sdk_password")
107
108 if ini.has_option("rsync", "sdk_pattern"):
109 rsync_sdk_pat = ini.get("rsync", "sdk_pattern")
110
111 repo_url = ini.get("repo", "url")
112 repo_branch = "master"
113
114 if ini.has_option("repo", "branch"):
115 repo_branch = ini.get("repo", "branch")
116
117 gpg_key = None
118 gpg_passphrase = None
119 gpg_comment = repo_branch.replace("-", " ").title() + " key"
120
121 if ini.has_option("gpg", "key"):
122 gpg_key = ini.get("gpg", "key")
123
124 if ini.has_option("gpg", "passphrase"):
125 gpg_passphrase = ini.get("gpg", "passphrase")
126
127 if ini.has_option("gpg", "comment"):
128 gpg_comment = ini.get("gpg", "comment")
129
130 usign_key = None
131 usign_comment = "untrusted comment: " + repo_branch.replace("-", " ").title() + " key"
132
133 if ini.has_option("usign", "key"):
134 usign_key = ini.get("usign", "key")
135
136 if ini.has_option("usign", "comment"):
137 usign_comment = ini.get("usign", "comment")
138
139
140 # find arches
141 arches = [ ]
142 archnames = [ ]
143
144 if not os.path.isdir(home_dir+'/source.git'):
145 subprocess.call(["git", "clone", "--depth=1", "--branch="+repo_branch, repo_url, home_dir+'/source.git'])
146 else:
147 subprocess.call(["git", "pull"], cwd = home_dir+'/source.git')
148
149 findarches = subprocess.Popen([scripts_dir + '/dumpinfo.pl', 'architectures'],
150 stdout = subprocess.PIPE, cwd = home_dir+'/source.git')
151
152 while True:
153 line = findarches.stdout.readline()
154 if not line:
155 break
156 at = line.strip().split()
157 arches.append(at)
158 archnames.append(at[0])
159
160
161 # find feeds
162 feeds = []
163 feedbranches = dict()
164
165 from buildbot.changes.gitpoller import GitPoller
166 c['change_source'] = []
167
168 def parse_feed_entry(line):
169 parts = line.strip().split()
170 if parts[0] == "src-git":
171 feeds.append(parts)
172 url = parts[2].strip().split(';')
173 branch = url[1] if len(url) > 1 else 'master'
174 feedbranches[url[0]] = branch
175 c['change_source'].append(GitPoller(url[0], branch=branch, workdir='%s/%s.git' %(os.getcwd(), parts[1]), pollinterval=300))
176
177 make = subprocess.Popen(['make', '--no-print-directory', '-C', home_dir+'/source.git/target/sdk/', 'val.BASE_FEED'],
178 env = dict(os.environ, TOPDIR=home_dir+'/source.git'), stdout = subprocess.PIPE)
179
180 line = make.stdout.readline()
181 if line:
182 parse_feed_entry(line)
183
184 with open(home_dir+'/source.git/feeds.conf.default', 'r') as f:
185 for line in f:
186 parse_feed_entry(line)
187
188
189 ####### SCHEDULERS
190
191 # Configure the Schedulers, which decide how to react to incoming changes. In this
192 # case, just kick off a 'basebuild' build
193
194 def branch_change_filter(change):
195 return change.branch == feedbranches[change.repository]
196
197 from buildbot.schedulers.basic import SingleBranchScheduler
198 from buildbot.schedulers.forcesched import ForceScheduler
199 from buildbot.changes import filter
200 c['schedulers'] = []
201 c['schedulers'].append(SingleBranchScheduler(
202 name="all",
203 change_filter=filter.ChangeFilter(filter_fn=branch_change_filter),
204 treeStableTimer=60,
205 builderNames=archnames))
206
207 c['schedulers'].append(ForceScheduler(
208 name="force",
209 builderNames=archnames))
210
211 ####### BUILDERS
212
213 # The 'builders' list defines the Builders, which tell Buildbot how to perform a build:
214 # what steps, and which slaves can execute them. Note that any particular build will
215 # only take place on one slave.
216
217 from buildbot.process.factory import BuildFactory
218 from buildbot.steps.source import Git
219 from buildbot.steps.shell import ShellCommand
220 from buildbot.steps.shell import SetProperty
221 from buildbot.steps.transfer import FileUpload
222 from buildbot.steps.transfer import FileDownload
223 from buildbot.steps.transfer import StringDownload
224 from buildbot.steps.master import MasterShellCommand
225 from buildbot.process.properties import WithProperties
226
227
228 def GetDirectorySuffix(props):
229 verpat = re.compile('^([0-9]{2})\.([0-9]{2})(?:\.([0-9]+)(?:-rc([0-9]+))?|-(SNAPSHOT))$')
230 if props.hasProperty("release_version"):
231 m = verpat.match(props["release_version"])
232 if m is not None:
233 return "-%02d.%02d" %(int(m.group(1)), int(m.group(2)))
234 return ""
235
236 def GetNumJobs(props):
237 if props.hasProperty("slavename") and props.hasProperty("nproc"):
238 return ((int(props["nproc"]) / (max_builds[props["slavename"]] + other_builds)) + 1)
239 else:
240 return 1
241
242 def GetCwd(props):
243 if props.hasProperty("builddir"):
244 return props["builddir"]
245 elif props.hasProperty("workdir"):
246 return props["workdir"]
247 else:
248 return "/"
249
250 def UsignSec2Pub(seckey, comment="untrusted comment: secret key"):
251 try:
252 seckey = base64.b64decode(seckey)
253 except:
254 return None
255
256 return "{}\n{}".format(re.sub(r"\bsecret key$", "public key", comment),
257 base64.b64encode(seckey[0:2] + seckey[32:40] + seckey[72:]))
258
259
260 c['builders'] = []
261
262 dlLock = locks.SlaveLock("slave_dl")
263
264 slaveNames = [ ]
265
266 for slave in c['slaves']:
267 slaveNames.append(slave.slavename)
268
269 for arch in arches:
270 ts = arch[1].split('/')
271
272 factory = BuildFactory()
273
274 # find number of cores
275 factory.addStep(SetProperty(
276 name = "nproc",
277 property = "nproc",
278 description = "Finding number of CPUs",
279 command = ["nproc"]))
280
281 # prepare workspace
282 factory.addStep(FileDownload(mastersrc="cleanup.sh", slavedest="cleanup.sh", mode=0755))
283
284 if not persistent:
285 factory.addStep(ShellCommand(
286 name = "cleanold",
287 description = "Cleaning previous builds",
288 command = ["./cleanup.sh", buildbot_url, WithProperties("%(slavename)s"), WithProperties("%(buildername)s"), "full"],
289 haltOnFailure = True,
290 timeout = 2400))
291
292 factory.addStep(ShellCommand(
293 name = "cleanup",
294 description = "Cleaning work area",
295 command = ["./cleanup.sh", buildbot_url, WithProperties("%(slavename)s"), WithProperties("%(buildername)s"), "single"],
296 haltOnFailure = True,
297 timeout = 2400))
298
299 # expire tree if needed
300 elif tree_expire > 0:
301 factory.addStep(FileDownload(
302 mastersrc = scripts_dir + '/expire.sh',
303 slavedest = "../expire.sh",
304 mode = 0755))
305
306 factory.addStep(ShellCommand(
307 name = "expire",
308 description = "Checking for build tree expiry",
309 command = ["./expire.sh", str(tree_expire)],
310 workdir = ".",
311 haltOnFailure = True,
312 timeout = 2400))
313
314 factory.addStep(ShellCommand(
315 name = "mksdkdir",
316 description = "Preparing SDK directory",
317 command = ["mkdir", "-p", "sdk"],
318 haltOnFailure = True))
319
320 factory.addStep(ShellCommand(
321 name = "downloadsdk",
322 description = "Downloading SDK archive",
323 command = ["rsync", "-4", "-va", "%s/%s/%s/%s" %(rsync_sdk_url, ts[0], ts[1], rsync_sdk_pat), "sdk.archive"],
324 env={'RSYNC_PASSWORD': rsync_sdk_key},
325 haltOnFailure = True,
326 logEnviron = False))
327
328 factory.addStep(ShellCommand(
329 name = "unpacksdk",
330 description = "Unpacking SDK archive",
331 command = "rm -rf sdk_update && mkdir sdk_update && tar --strip-components=1 -C sdk_update/ -vxf sdk.archive",
332 haltOnFailure = True))
333
334 factory.addStep(ShellCommand(
335 name = "updatesdk",
336 description = "Updating SDK",
337 command = "rsync --checksum -av sdk_update/ sdk/ && rm -rf sdk_update",
338 haltOnFailure = True))
339
340 factory.addStep(StringDownload(
341 name = "writeversionmk",
342 s = 'TOPDIR:=${CURDIR}\n\ninclude $(TOPDIR)/include/version.mk\n\nversion:\n\t@echo $(VERSION_NUMBER)\n',
343 slavedest = "sdk/getversion.mk",
344 mode = 0755))
345
346 factory.addStep(SetProperty(
347 name = "getversion",
348 property = "release_version",
349 description = "Finding SDK release version",
350 workdir = "build/sdk",
351 command = ["make", "-f", "getversion.mk"]))
352
353 # install build key
354 if usign_key is not None:
355 factory.addStep(StringDownload(
356 name = "dlkeybuildpub",
357 s = UsignSec2Pub(usign_key, usign_comment),
358 slavedest = "sdk/key-build.pub",
359 mode = 0600))
360
361 factory.addStep(StringDownload(
362 name = "dlkeybuild",
363 s = "# fake private key",
364 slavedest = "sdk/key-build",
365 mode = 0600))
366
367 factory.addStep(StringDownload(
368 name = "dlkeybuilducert",
369 s = "# fake certificate",
370 slavedest = "sdk/key-build.ucert",
371 mode = 0600))
372
373 factory.addStep(ShellCommand(
374 name = "mkdldir",
375 description = "Preparing download directory",
376 command = ["sh", "-c", "mkdir -p $HOME/dl && rm -rf ./sdk/dl && ln -sf $HOME/dl ./sdk/dl"],
377 haltOnFailure = True))
378
379 factory.addStep(ShellCommand(
380 name = "mkconf",
381 description = "Preparing SDK configuration",
382 workdir = "build/sdk",
383 command = ["sh", "-c", "rm -f .config && make defconfig"]))
384
385 factory.addStep(FileDownload(
386 mastersrc = scripts_dir + '/ccache.sh',
387 slavedest = 'sdk/ccache.sh',
388 mode = 0755))
389
390 factory.addStep(ShellCommand(
391 name = "prepccache",
392 description = "Preparing ccache",
393 workdir = "build/sdk",
394 command = ["./ccache.sh"],
395 haltOnFailure = True))
396
397 if git_ssh:
398 factory.addStep(StringDownload(
399 name = "dlgitclonekey",
400 s = git_ssh_key,
401 slavedest = "../git-clone.key",
402 mode = 0600))
403
404 factory.addStep(ShellCommand(
405 name = "patchfeedsconf",
406 description = "Patching feeds.conf",
407 workdir = "build/sdk",
408 command = "sed -e 's#https://#ssh://git@#g' feeds.conf.default > feeds.conf",
409 haltOnFailure = True))
410
411 factory.addStep(ShellCommand(
412 name = "updatefeeds",
413 description = "Updating feeds",
414 workdir = "build/sdk",
415 command = ["./scripts/feeds", "update", "-f"],
416 env = {'GIT_SSH_COMMAND': WithProperties("ssh -o IdentitiesOnly=yes -o IdentityFile=%(cwd)s/git-clone.key -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no", cwd=GetCwd)} if git_ssh else {},
417 haltOnFailure = True))
418
419 if git_ssh:
420 factory.addStep(ShellCommand(
421 name = "rmfeedsconf",
422 description = "Removing feeds.conf",
423 workdir = "build/sdk",
424 command=["rm", "feeds.conf"],
425 haltOnFailure = True))
426
427 factory.addStep(ShellCommand(
428 name = "installfeeds",
429 description = "Installing feeds",
430 workdir = "build/sdk",
431 command = ["./scripts/feeds", "install", "-a"],
432 haltOnFailure = True))
433
434 factory.addStep(ShellCommand(
435 name = "logclear",
436 description = "Clearing failure logs",
437 workdir = "build/sdk",
438 command = ["rm", "-rf", "logs/package/error.txt", "faillogs/"],
439 haltOnFailure = False
440 ))
441
442 factory.addStep(ShellCommand(
443 name = "compile",
444 description = "Building packages",
445 workdir = "build/sdk",
446 timeout = 3600,
447 command = ["make", WithProperties("-j%(jobs)d", jobs=GetNumJobs), "IGNORE_ERRORS=n m y", "BUILD_LOG=1", "CONFIG_AUTOREMOVE=y"],
448 env = {'CCACHE_BASEDIR': WithProperties("%(cwd)s", cwd=GetCwd)},
449 haltOnFailure = True))
450
451 factory.addStep(ShellCommand(
452 name = "mkfeedsconf",
453 description = "Generating pinned feeds.conf",
454 workdir = "build/sdk",
455 command = "./scripts/feeds list -s -f > bin/packages/%s/feeds.conf" %(arch[0])))
456
457 if gpg_key is not None or usign_key is not None:
458 factory.addStep(MasterShellCommand(
459 name = "signprepare",
460 description = "Preparing temporary signing directory",
461 command = ["mkdir", "-p", "%s/signing" %(home_dir)],
462 haltOnFailure = True
463 ))
464
465 factory.addStep(ShellCommand(
466 name = "signpack",
467 description = "Packing files to sign",
468 workdir = "build/sdk",
469 command = "find bin/packages/%s/ -mindepth 2 -maxdepth 2 -type f -name Packages -print0 | xargs -0 tar -czf sign.tar.gz" %(arch[0]),
470 haltOnFailure = True
471 ))
472
473 factory.addStep(FileUpload(
474 slavesrc = "sdk/sign.tar.gz",
475 masterdest = "%s/signing/%s.tar.gz" %(home_dir, arch[0]),
476 haltOnFailure = True
477 ))
478
479 factory.addStep(MasterShellCommand(
480 name = "signfiles",
481 description = "Signing files",
482 command = ["%s/signall.sh" %(scripts_dir), "%s/signing/%s.tar.gz" %(home_dir, arch[0])],
483 env = {
484 'GPGKEY': gpg_key,
485 'GPGPASS': gpg_passphrase,
486 'GPGCOMMENT': gpg_comment,
487 'USIGNKEY': usign_key,
488 'USIGNCOMMENT': usign_comment
489 },
490 haltOnFailure = True
491 ))
492
493 factory.addStep(FileDownload(
494 mastersrc = "%s/signing/%s.tar.gz" %(home_dir, arch[0]),
495 slavedest = "sdk/sign.tar.gz",
496 haltOnFailure = True
497 ))
498
499 factory.addStep(ShellCommand(
500 name = "signunpack",
501 description = "Unpacking signed files",
502 workdir = "build/sdk",
503 command = ["tar", "-xzf", "sign.tar.gz"],
504 haltOnFailure = True
505 ))
506
507 factory.addStep(ShellCommand(
508 name = "uploadprepare",
509 description = "Preparing package directory",
510 workdir = "build/sdk",
511 command = ["rsync", "-4", "-av", "--include", "/%s/" %(arch[0]), "--exclude", "/*", "--exclude", "/%s/*" %(arch[0]), "bin/packages/", WithProperties("%s/packages%%(suffix)s/" %(rsync_bin_url), suffix=GetDirectorySuffix)],
512 env={'RSYNC_PASSWORD': rsync_bin_key},
513 haltOnFailure = True,
514 logEnviron = False
515 ))
516
517 factory.addStep(ShellCommand(
518 name = "packageupload",
519 description = "Uploading package files",
520 workdir = "build/sdk",
521 command = ["rsync", "-4", "--progress", "--delete", "--checksum", "--delay-updates", "--partial-dir=.~tmp~%s" %(arch[0]), "-avz", "bin/packages/%s/" %(arch[0]), WithProperties("%s/packages%%(suffix)s/%s/" %(rsync_bin_url, arch[0]), suffix=GetDirectorySuffix)],
522 env={'RSYNC_PASSWORD': rsync_bin_key},
523 haltOnFailure = True,
524 logEnviron = False
525 ))
526
527 factory.addStep(ShellCommand(
528 name = "logprepare",
529 description = "Preparing log directory",
530 workdir = "build/sdk",
531 command = ["rsync", "-4", "-av", "--include", "/%s/" %(arch[0]), "--exclude", "/*", "--exclude", "/%s/*" %(arch[0]), "bin/packages/", WithProperties("%s/faillogs%%(suffix)s/" %(rsync_bin_url), suffix=GetDirectorySuffix)],
532 env={'RSYNC_PASSWORD': rsync_bin_key},
533 haltOnFailure = True,
534 logEnviron = False
535 ))
536
537 factory.addStep(ShellCommand(
538 name = "logfind",
539 description = "Finding failure logs",
540 workdir = "build/sdk/logs/package/feeds",
541 command = ["sh", "-c", "sed -ne 's!^ *ERROR: package/feeds/\\([^ ]*\\) .*$!\\1!p' ../error.txt | sort -u | xargs -r find > ../../../logs.txt"],
542 haltOnFailure = False
543 ))
544
545 factory.addStep(ShellCommand(
546 name = "logcollect",
547 description = "Collecting failure logs",
548 workdir = "build/sdk",
549 command = ["rsync", "-av", "--files-from=logs.txt", "logs/package/feeds/", "faillogs/"],
550 haltOnFailure = False
551 ))
552
553 factory.addStep(ShellCommand(
554 name = "logupload",
555 description = "Uploading failure logs",
556 workdir = "build/sdk",
557 command = ["rsync", "-4", "--progress", "--delete", "--delay-updates", "--partial-dir=.~tmp~%s" %(arch[0]), "-avz", "faillogs/", WithProperties("%s/faillogs%%(suffix)s/%s/" %(rsync_bin_url, arch[0]), suffix=GetDirectorySuffix)],
558 env={'RSYNC_PASSWORD': rsync_bin_key},
559 haltOnFailure = False,
560 logEnviron = False
561 ))
562
563 if rsync_src_url is not None:
564 factory.addStep(ShellCommand(
565 name = "sourcelist",
566 description = "Finding source archives to upload",
567 workdir = "build/sdk",
568 command = "find dl/ -maxdepth 1 -type f -not -size 0 -not -name '.*' -newer ../sdk.archive -printf '%f\\n' > sourcelist",
569 haltOnFailure = True
570 ))
571
572 factory.addStep(ShellCommand(
573 name = "sourceupload",
574 description = "Uploading source archives",
575 workdir = "build/sdk",
576 command = ["rsync", "--files-from=sourcelist", "-4", "--progress", "--checksum", "--delay-updates",
577 WithProperties("--partial-dir=.~tmp~%s~%%(slavename)s" %(arch[0])), "-avz", "dl/", "%s/" %(rsync_src_url)],
578 env={'RSYNC_PASSWORD': rsync_src_key},
579 haltOnFailure = False,
580 logEnviron = False
581 ))
582
583 factory.addStep(ShellCommand(
584 name = "df",
585 description = "Reporting disk usage",
586 command=["df", "-h", "."],
587 env={'LC_ALL': 'C'},
588 haltOnFailure = False,
589 alwaysRun = True
590 ))
591
592 from buildbot.config import BuilderConfig
593
594 c['builders'].append(BuilderConfig(name=arch[0], slavenames=slaveNames, factory=factory))
595
596
597 ####### STATUS arches
598
599 # 'status' is a list of Status arches. The results of each build will be
600 # pushed to these arches. buildbot/status/*.py has a variety to choose from,
601 # including web pages, email senders, and IRC bots.
602
603 c['status'] = []
604
605 from buildbot.status import html
606 from buildbot.status.web import authz, auth
607
608 if ini.has_option("status", "bind"):
609 if ini.has_option("status", "user") and ini.has_option("status", "password"):
610 authz_cfg=authz.Authz(
611 # change any of these to True to enable; see the manual for more
612 # options
613 auth=auth.BasicAuth([(ini.get("status", "user"), ini.get("status", "password"))]),
614 gracefulShutdown = 'auth',
615 forceBuild = 'auth', # use this to test your slave once it is set up
616 forceAllBuilds = 'auth',
617 pingBuilder = False,
618 stopBuild = 'auth',
619 stopAllBuilds = 'auth',
620 cancelPendingBuild = 'auth',
621 )
622 c['status'].append(html.WebStatus(http_port=ini.get("status", "bind"), authz=authz_cfg))
623 else:
624 c['status'].append(html.WebStatus(http_port=ini.get("status", "bind")))
625
626 ####### PROJECT IDENTITY
627
628 # the 'title' string will appear at the top of this buildbot
629 # installation's html.WebStatus home page (linked to the
630 # 'titleURL') and is embedded in the title of the waterfall HTML page.
631
632 c['title'] = ini.get("general", "title")
633 c['titleURL'] = ini.get("general", "title_url")
634
635 # the 'buildbotURL' string should point to the location where the buildbot's
636 # internal web server (usually the html.WebStatus page) is visible. This
637 # typically uses the port number set in the Waterfall 'status' entry, but
638 # with an externally-visible host name which the buildbot cannot figure out
639 # without some help.
640
641 c['buildbotURL'] = buildbot_url
642
643 ####### DB URL
644
645 c['db'] = {
646 # This specifies what database buildbot uses to store its state. You can leave
647 # this at its default for all but the largest installations.
648 'db_url' : "sqlite:///state.sqlite",
649 }