buildbot-nix/nix/master.nix
Andreas Fuchs 760aec7e07 Make the webhook endpoint URL configurable
In some CI setups, the URL to access the CI server differs from the
URL that the (external) webhook endpoint listens on. This change
introduces an option webhookBaseUrl that allows setting that URL
independently from the buildbot installation URL.
2024-05-08 20:00:55 +00:00

387 lines
14 KiB
Nix

{ config
, pkgs
, lib
, ...
}:
let
cfg = config.services.buildbot-nix.master;
inherit (lib) mkRenamedOptionModule;
in
{
imports = [
(mkRenamedOptionModule
[
"services"
"buildbot-nix"
"master"
"github"
"admins"
]
[
"services"
"buildbot-nix"
"master"
"admins"
]
)
];
options = {
services.buildbot-nix.master = {
enable = lib.mkEnableOption "buildbot-master";
dbUrl = lib.mkOption {
type = lib.types.str;
default = "postgresql://@/buildbot";
description = "Postgresql database url";
};
authBackend = lib.mkOption {
type = lib.types.enum [
"github"
"gitea"
"none"
];
default = "github";
description = ''
Which OAuth2 backend to use.
'';
};
cachix = {
name = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "Cachix name";
};
signingKeyFile = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
description = "Cachix signing key";
};
authTokenFile = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "Cachix auth token";
};
};
gitea = {
enable = lib.mkEnableOption "Enable Gitea integration" // {
default = cfg.authBackend == "gitea";
};
tokenFile = lib.mkOption {
type = lib.types.path;
description = "Gitea token file";
};
webhookSecretFile = lib.mkOption {
type = lib.types.path;
description = "Gitea webhook secret file";
};
oauthSecretFile = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
description = "Gitea oauth secret file";
};
instanceUrl = lib.mkOption {
type = lib.types.str;
description = "Gitea instance URL";
};
oauthId = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "Gitea oauth id. Used for the login button";
};
topic = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = "build-with-buildbot";
description = ''
Projects that have this topic will be built by buildbot.
If null, all projects that the buildbot Gitea user has access to, are built.
'';
};
};
github = {
enable = lib.mkEnableOption "Enable GitHub integration" // {
default = cfg.authBackend == "github";
};
tokenFile = lib.mkOption {
type = lib.types.path;
description = "Github token file";
};
webhookSecretFile = lib.mkOption {
type = lib.types.path;
description = "Github webhook secret file";
};
oauthSecretFile = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
description = "Github oauth secret file";
};
# TODO: make this an option
# https://github.com/organizations/numtide/settings/applications
# Application name: BuildBot
# Homepage URL: https://buildbot.numtide.com
# Authorization callback URL: https://buildbot.numtide.com/auth/login
# oauth_token: 2516248ec6289e4d9818122cce0cbde39e4b788d
oauthId = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "Github oauth id. Used for the login button";
};
# Most likely you want to use the same user as for the buildbot
user = lib.mkOption {
type = lib.types.str;
description = "Github user that is used for the buildbot";
};
topic = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = "build-with-buildbot";
description = ''
Projects that have this topic will be built by buildbot.
If null, all projects that the buildbot github user has access to, are built.
'';
};
};
admins = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = "Users that are allowed to login to buildbot, trigger builds and change settings";
};
workersFile = lib.mkOption {
type = lib.types.path;
description = "File containing a list of nix workers";
};
buildSystems = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ pkgs.hostPlatform.system ];
description = "Systems that we will be build";
};
evalMaxMemorySize = lib.mkOption {
type = lib.types.str;
default = "2048";
description = ''
Maximum memory size for nix-eval-jobs (in MiB) per
worker. After the limit is reached, the worker is
restarted.
'';
};
evalWorkerCount = lib.mkOption {
type = lib.types.nullOr lib.types.int;
default = null;
description = ''
Number of nix-eval-jobs worker processes. If null, the number of cores is used.
If you experience memory issues (buildbot-workers going out-of-memory), you can reduce this number.
'';
};
domain = lib.mkOption {
type = lib.types.str;
description = "Buildbot domain";
example = "buildbot.numtide.com";
};
webhookBaseUrl = lib.mkOption {
type = lib.types.str;
description = "URL base for the webhook endpoint that will be registered for github or gitea repos.";
example = "https://buildbot-webhooks.numtide.com/";
default = "${config.services.buildbot-master.buildbotUrl}";
};
outputsPath = lib.mkOption {
type = lib.types.nullOr lib.types.path;
description = "Path where we store the latest build store paths names for nix attributes as text files. This path will be exposed via nginx at \${domain}/nix-outputs";
default = null;
example = "/var/www/buildbot/nix-outputs";
};
};
};
config = lib.mkIf cfg.enable {
# By default buildbot uses a normal user, which is not a good default, because
# we grant normal users potentially access to other resources. Also
# we don't to be able to ssh into buildbot.
users.users.buildbot = {
isNormalUser = lib.mkForce false;
isSystemUser = true;
};
assertions = [
{
assertion =
cfg.cachix.name != null -> cfg.cachix.signingKeyFile != null || cfg.cachix.authTokenFile != null;
message = "if cachix.name is provided, then cachix.signingKeyFile and cachix.authTokenFile must be set";
}
{
assertion =
cfg.authBackend != "github" || (cfg.github.oauthId != null && cfg.github.oauthSecretFile != null);
message = ''If config.services.buildbot-nix.master.authBackend is set to "github", then config.services.buildbot-nix.master.github.oauthId and config.services.buildbot-nix.master.github.oauthSecretFile have to be set.'';
}
{
assertion =
cfg.authBackend != "gitea" || (cfg.gitea.oauthId != null && cfg.gitea.oauthSecretFile != null);
message = ''config.services.buildbot-nix.master.authBackend is set to "gitea", then config.services.buildbot-nix.master.gitea.oauthId and config.services.buildbot-nix.master.gitea.oauthSecretFile have to be set.'';
}
];
services.buildbot-master = {
enable = true;
# disable example workers from nixpkgs
builders = [ ];
schedulers = [ ];
workers = [ ];
home = "/var/lib/buildbot";
extraImports = ''
from datetime import timedelta
from buildbot_nix import GithubConfig, NixConfigurator, CachixConfig, GiteaConfig
'';
configurators = [
''
util.JanitorConfigurator(logHorizon=timedelta(weeks=4), hour=12, dayOfWeek=6)
''
''
NixConfigurator(
auth_backend=${builtins.toJSON cfg.authBackend},
github=${
if (!cfg.github.enable) then
"None"
else
"GithubConfig(
oauth_id=${builtins.toJSON cfg.github.oauthId},
buildbot_user=${builtins.toJSON cfg.github.user},
topic=${builtins.toJSON cfg.github.topic},
)"
},
gitea=${
if !cfg.gitea.enable then
"None"
else
"GiteaConfig(
instance_url=${builtins.toJSON cfg.gitea.instanceUrl},
oauth_id=${builtins.toJSON cfg.gitea.oauthId},
topic=${builtins.toJSON cfg.gitea.topic},
)"
},
cachix=${
if cfg.cachix.name == null then
"None"
else
"CachixConfig(
name=${builtins.toJSON cfg.cachix.name},
signing_key_secret_name=${
if cfg.cachix.signingKeyFile != null then builtins.toJSON "cachix-signing-key" else "None"
},
auth_token_secret_name=${
if cfg.cachix.authTokenFile != null then builtins.toJSON "cachix-auth-token" else "None"
},
)"
},
admins=${builtins.toJSON cfg.admins},
url=${builtins.toJSON config.services.buildbot-nix.master.webhookBaseUrl},
nix_eval_max_memory_size=${builtins.toJSON cfg.evalMaxMemorySize},
nix_eval_worker_count=${
if cfg.evalWorkerCount == null then "None" else builtins.toString cfg.evalWorkerCount
},
nix_supported_systems=${builtins.toJSON cfg.buildSystems},
outputs_path=${if cfg.outputsPath == null then "None" else builtins.toJSON cfg.outputsPath},
)
''
];
buildbotUrl =
let
host = config.services.nginx.virtualHosts.${cfg.domain};
hasSSL = host.forceSSL || host.addSSL;
in
"${if hasSSL then "https" else "http"}://${cfg.domain}/";
dbUrl = config.services.buildbot-nix.master.dbUrl;
# Can be dropped after we have 24.05 everywhere
package = lib.mkIf (lib.versionOlder pkgs.buildbot.version "3.10.0") (
pkgs.buildbot.overrideAttrs (old: {
patches = old.patches ++ [ ./0001-allow-secrets-to-be-group-readable.patch ];
})
);
pythonPackages = ps: [
ps.requests
ps.treq
ps.psycopg2
(ps.toPythonModule pkgs.buildbot-worker)
pkgs.buildbot-plugins.www-react
(pkgs.python3.pkgs.callPackage ../default.nix { })
(pkgs.python3.pkgs.callPackage ./buildbot-gitea.nix { buildbot = pkgs.buildbot; })
];
};
systemd.services.buildbot-master = {
after = [ "postgresql.service" ];
serviceConfig = {
# in master.py we read secrets from $CREDENTIALS_DIRECTORY
LoadCredential =
[ "buildbot-nix-workers:${cfg.workersFile}" ]
++ lib.optional (cfg.authBackend == "gitea") "gitea-oauth-secret:${cfg.gitea.oauthSecretFile}"
++ lib.optional (cfg.authBackend == "github") "github-oauth-secret:${cfg.github.oauthSecretFile}"
++ lib.optional
(
cfg.cachix.signingKeyFile != null
) "cachix-signing-key:${builtins.toString cfg.cachix.signingKeyFile}"
++ lib.optional
(
cfg.cachix.authTokenFile != null
) "cachix-auth-token:${builtins.toString cfg.cachix.authTokenFile}"
++ lib.optionals (cfg.github.enable) [
"github-token:${cfg.github.tokenFile}"
"github-webhook-secret:${cfg.github.webhookSecretFile}"
]
++ lib.optionals cfg.gitea.enable [
"gitea-token:${cfg.gitea.tokenFile}"
"gitea-webhook-secret:${cfg.gitea.webhookSecretFile}"
];
# Needed because it tries to reach out to github on boot.
# FIXME: if github is not available, we shouldn't fail buildbot, instead it should just try later again in the background
Restart = "on-failure";
RestartSec = "30s";
};
};
services.postgresql = {
enable = true;
ensureDatabases = [ "buildbot" ];
ensureUsers = [
{
name = "buildbot";
ensureDBOwnership = true;
}
];
};
services.nginx.enable = true;
services.nginx.virtualHosts.${cfg.domain} = {
locations = {
"/".proxyPass = "http://127.0.0.1:${builtins.toString config.services.buildbot-master.port}/";
"/sse" = {
proxyPass = "http://127.0.0.1:${builtins.toString config.services.buildbot-master.port}/sse";
# proxy buffering will prevent sse to work
extraConfig = "proxy_buffering off;";
};
"/ws" = {
proxyPass = "http://127.0.0.1:${builtins.toString config.services.buildbot-master.port}/ws";
proxyWebsockets = true;
# raise the proxy timeout for the websocket
extraConfig = "proxy_read_timeout 6000s;";
};
} // lib.optionalAttrs (cfg.outputsPath != null) { "/nix-outputs".root = cfg.outputsPath; };
};
systemd.tmpfiles.rules =
[
# delete legacy gcroot location, can be dropped after 2024-06-01
"R /var/lib/buildbot-worker/gcroot - - - - -"
]
++ lib.optional (cfg.outputsPath != null)
# Allow buildbot-master to write to this directory
"d ${cfg.outputsPath} 0755 buildbot buildbot - -";
};
}