hydra/hydra-module.nix

406 lines
12 KiB
Nix
Raw Normal View History

{ config, pkgs, lib ? pkgs.lib, ... }:
2011-05-05 06:27:38 +00:00
with lib;
2011-05-05 06:27:38 +00:00
let
2011-05-05 06:27:38 +00:00
cfg = config.services.hydra;
baseDir = "/var/lib/hydra";
hydraConf = pkgs.writeScript "hydra.conf" cfg.extraConfig;
2013-01-22 13:41:02 +00:00
hydraEnv =
{ HYDRA_DBI = cfg.dbi;
HYDRA_CONFIG = "${baseDir}/hydra.conf";
HYDRA_DATA = "${baseDir}";
};
env =
{ NIX_REMOTE = "daemon";
2016-03-02 14:03:54 +00:00
SSL_CERT_FILE = "/etc/ssl/certs/ca-certificates.crt"; # Remove in 16.03
PGPASSFILE = "${baseDir}/pgpass";
2016-03-25 14:41:38 +00:00
NIX_REMOTE_SYSTEMS = concatStringsSep ":" cfg.buildMachinesFiles;
2016-04-13 14:46:24 +00:00
} // optionalAttrs (cfg.smtpHost != null) {
EMAIL_SENDER_TRANSPORT = "SMTP";
EMAIL_SENDER_TRANSPORT_host = cfg.smtpHost;
} // hydraEnv // cfg.extraEnv;
serverEnv = env //
2013-11-06 16:27:36 +00:00
{ HYDRA_TRACKER = cfg.tracker;
COLUMNS = "80";
2015-07-02 00:55:15 +00:00
PGPASSFILE = "${baseDir}/pgpass-www"; # grrr
2016-04-29 01:37:25 +00:00
} // (optionalAttrs cfg.debugServer { DBIC_TRACE = "1"; });
localDB = "dbi:Pg:dbname=hydra;user=hydra;";
haveLocalDB = cfg.dbi == localDB;
2011-05-05 06:27:38 +00:00
in
{
###### interface
options = {
2013-11-06 16:27:36 +00:00
2011-05-05 06:27:38 +00:00
services.hydra = rec {
2013-01-22 13:41:02 +00:00
2011-05-05 06:27:38 +00:00
enable = mkOption {
type = types.bool;
2011-05-05 06:27:38 +00:00
default = false;
description = ''
Whether to run Hydra services.
'';
};
dbi = mkOption {
2015-06-29 09:28:34 +00:00
type = types.str;
default = localDB;
example = "dbi:Pg:dbname=hydra;host=postgres.example.org;user=foo;";
2011-05-05 06:27:38 +00:00
description = ''
2011-05-05 10:07:59 +00:00
The DBI string for Hydra database connection.
2011-05-05 06:27:38 +00:00
'';
};
2013-01-22 13:41:02 +00:00
package = mkOption {
type = types.path;
#default = pkgs.hydra;
description = "The Hydra package.";
2011-05-05 06:27:38 +00:00
};
2013-01-22 13:41:02 +00:00
2011-05-05 06:27:38 +00:00
hydraURL = mkOption {
type = types.str;
2011-05-05 06:27:38 +00:00
description = ''
2013-01-22 13:41:02 +00:00
The base URL for the Hydra webserver instance. Used for links in emails.
2011-05-05 06:27:38 +00:00
'';
};
2011-05-05 10:07:56 +00:00
listenHost = mkOption {
type = types.str;
default = "*";
example = "localhost";
description = ''
The hostname or address to listen on or <literal>*</literal> to listen
on all interfaces.
'';
};
2011-05-05 10:07:56 +00:00
port = mkOption {
type = types.int;
2011-05-05 10:07:56 +00:00
default = 3000;
description = ''
TCP port the web server should listen to.
'';
};
2011-05-05 06:27:38 +00:00
minimumDiskFree = mkOption {
type = types.int;
default = 0;
2011-05-05 06:27:38 +00:00
description = ''
2015-10-07 11:12:24 +00:00
Threshold of minimum disk space (GiB) to determine if the queue runner should run or not.
2011-05-05 06:27:38 +00:00
'';
};
minimumDiskFreeEvaluator = mkOption {
type = types.int;
default = 0;
2011-05-05 06:27:38 +00:00
description = ''
2015-10-07 11:12:24 +00:00
Threshold of minimum disk space (GiB) to determine if the evaluator should run or not.
2011-05-05 06:27:38 +00:00
'';
};
notificationSender = mkOption {
type = types.str;
2011-05-05 06:27:38 +00:00
description = ''
2013-01-22 13:41:02 +00:00
Sender email address used for email notifications.
2011-05-05 06:27:38 +00:00
'';
2013-01-22 13:41:02 +00:00
};
2011-05-05 06:27:38 +00:00
2016-04-13 14:46:24 +00:00
smtpHost = mkOption {
type = types.nullOr types.str;
default = null;
example = ["localhost"];
description = ''
Hostname of the SMTP server to use to send email.
'';
};
2011-05-05 06:27:38 +00:00
tracker = mkOption {
type = types.str;
2011-05-05 06:27:38 +00:00
default = "";
description = ''
Piece of HTML that is included on all pages.
'';
2011-05-05 10:07:59 +00:00
};
logo = mkOption {
2013-11-06 16:27:36 +00:00
type = types.nullOr types.path;
2011-05-05 10:07:59 +00:00
default = null;
description = ''
Path to a file containing the logo of your Hydra instance.
2011-05-05 10:07:59 +00:00
'';
};
debugServer = mkOption {
type = types.bool;
default = false;
description = "Whether to run the server in debug mode.";
};
extraConfig = mkOption {
type = types.lines;
description = "Extra lines for the Hydra configuration.";
};
extraEnv = mkOption {
type = types.attrsOf types.str;
default = {};
description = "Extra environment variables for Hydra.";
};
gcRootsDir = mkOption {
type = types.path;
default = "/nix/var/nix/gcroots/hydra";
description = "Directory that holds Hydra garbage collector roots.";
};
2016-03-25 14:41:38 +00:00
buildMachinesFiles = mkOption {
type = types.list types.path;
default = [];
example = [ "/etc/nix/machines" "/var/lib/hydra/provisioner/machines" ];
description = "List of files containing build machines.";
};
2011-05-05 06:27:38 +00:00
};
};
2013-01-22 13:41:02 +00:00
2011-05-05 06:27:38 +00:00
###### implementation
config = mkIf cfg.enable {
2013-11-06 16:27:36 +00:00
users.extraGroups.hydra = { };
users.extraUsers.hydra =
{ description = "Hydra";
group = "hydra";
createHome = true;
home = baseDir;
useDefaultShell = true;
};
users.extraUsers.hydra-queue-runner =
{ description = "Hydra queue runner";
group = "hydra";
useDefaultShell = true;
2015-07-02 00:55:15 +00:00
home = "${baseDir}/queue-runner"; # really only to keep SSH happy
};
users.extraUsers.hydra-www =
{ description = "Hydra web server";
group = "hydra";
useDefaultShell = true;
};
nix.trustedUsers = [ "hydra-queue-runner" ];
services.hydra.package = mkDefault ((import ./release.nix {}).build.x86_64-linux);
services.hydra.extraConfig =
''
using_frontend_proxy 1
base_uri ${cfg.hydraURL}
notification_sender ${cfg.notificationSender}
max_servers 25
2013-11-06 16:27:36 +00:00
${optionalString (cfg.logo != null) ''
hydra_logo ${cfg.logo}
''}
gc_roots_dir ${cfg.gcRootsDir}
'';
environment.systemPackages = [ cfg.package ];
2011-05-05 06:27:38 +00:00
environment.variables = hydraEnv;
2011-05-05 06:27:38 +00:00
nix.extraOptions = ''
gc-keep-outputs = true
gc-keep-derivations = true
# The default (`true') slows Nix down a lot since the build farm
# has so many GC roots.
gc-check-reachability = false
'';
2015-06-26 13:24:12 +00:00
systemd.services.hydra-init =
{ wantedBy = [ "multi-user.target" ];
2015-07-02 00:54:16 +00:00
requires = optional haveLocalDB "postgresql.service";
after = optional haveLocalDB "postgresql.service";
environment = env;
preStart = ''
mkdir -p ${baseDir}
chown hydra.hydra ${baseDir}
chmod 0750 ${baseDir}
ln -sf ${hydraConf} ${baseDir}/hydra.conf
2015-07-02 00:54:59 +00:00
mkdir -m 0700 -p ${baseDir}/www
chown hydra-www.hydra ${baseDir}/www
2015-07-02 00:54:59 +00:00
mkdir -m 0700 -p ${baseDir}/queue-runner
mkdir -m 0750 -p ${baseDir}/build-logs
chown hydra-queue-runner.hydra ${baseDir}/queue-runner ${baseDir}/build-logs
${optionalString haveLocalDB ''
if ! [ -e ${baseDir}/.db-created ]; then
${config.services.postgresql.package}/bin/createuser hydra
${config.services.postgresql.package}/bin/createdb -O hydra hydra
touch ${baseDir}/.db-created
fi
''}
if [ ! -e ${cfg.gcRootsDir} ]; then
# Move legacy roots directory.
if [ -e /nix/var/nix/gcroots/per-user/hydra/hydra-roots ]; then
mv /nix/var/nix/gcroots/per-user/hydra/hydra-roots ${cfg.gcRootsDir}
fi
mkdir -p ${cfg.gcRootsDir}
fi
# Move legacy hydra-www roots.
if [ -e /nix/var/nix/gcroots/per-user/hydra-www/hydra-roots ]; then
find /nix/var/nix/gcroots/per-user/hydra-www/hydra-roots/ -type f \
| xargs -r mv -f -t ${cfg.gcRootsDir}/
rmdir /nix/var/nix/gcroots/per-user/hydra-www/hydra-roots
fi
chown hydra.hydra ${cfg.gcRootsDir}
chmod 2775 ${cfg.gcRootsDir}
2011-05-05 06:27:38 +00:00
'';
serviceConfig.ExecStart = "${cfg.package}/bin/hydra-init";
serviceConfig.PermissionsStartOnly = true;
serviceConfig.User = "hydra";
serviceConfig.Type = "oneshot";
serviceConfig.RemainAfterExit = true;
};
2015-06-26 13:24:12 +00:00
systemd.services.hydra-server =
{ wantedBy = [ "multi-user.target" ];
requires = [ "hydra-init.service" ];
after = [ "hydra-init.service" ];
environment = serverEnv;
serviceConfig =
{ ExecStart =
"@${cfg.package}/bin/hydra-server hydra-server -f -h '${cfg.listenHost}' "
+ "-p ${toString cfg.port} --max_spare_servers 5 --max_servers 25 "
+ "--max_requests 100 ${optionalString cfg.debugServer "-d"}";
User = "hydra-www";
PermissionsStartOnly = true;
Restart = "always";
};
2011-05-05 06:27:38 +00:00
};
2015-06-26 13:24:12 +00:00
systemd.services.hydra-queue-runner =
{ wantedBy = [ "multi-user.target" ];
requires = [ "hydra-init.service" ];
after = [ "hydra-init.service" "network.target" ];
path = [ cfg.package pkgs.nettools pkgs.openssh pkgs.bzip2 config.nix.package ];
2015-07-02 00:55:15 +00:00
environment = env // {
PGPASSFILE = "${baseDir}/pgpass-queue-runner"; # grrr
IN_SYSTEMD = "1"; # to get log severity levels
2015-07-02 00:55:15 +00:00
};
serviceConfig =
{ ExecStart = "@${cfg.package}/bin/hydra-queue-runner hydra-queue-runner -v --option build-use-substitutes false";
2015-06-26 09:29:30 +00:00
ExecStopPost = "${cfg.package}/bin/hydra-queue-runner --unlock";
User = "hydra-queue-runner";
Restart = "always";
2016-02-28 13:09:04 +00:00
# Ensure we can get core dumps.
LimitCORE = "infinity";
WorkingDirectory = "${baseDir}/queue-runner";
};
2011-05-05 06:27:38 +00:00
};
2015-06-26 13:24:12 +00:00
systemd.services.hydra-evaluator =
{ wantedBy = [ "multi-user.target" ];
requires = [ "hydra-init.service" ];
after = [ "hydra-init.service" "network.target" ];
path = [ pkgs.nettools ];
environment = env;
serviceConfig =
{ ExecStart = "@${cfg.package}/bin/hydra-evaluator hydra-evaluator";
User = "hydra";
Restart = "always";
2016-03-25 14:41:38 +00:00
WorkingDirectory = baseDir;
};
2011-05-05 06:27:38 +00:00
};
2015-06-26 13:24:12 +00:00
systemd.services.hydra-update-gc-roots =
{ requires = [ "hydra-init.service" ];
after = [ "hydra-init.service" ];
environment = env;
serviceConfig =
{ ExecStart = "@${cfg.package}/bin/hydra-update-gc-roots hydra-update-gc-roots";
User = "hydra";
};
startAt = "2,14:15";
2011-05-05 06:27:38 +00:00
};
2015-06-26 13:24:12 +00:00
systemd.services.hydra-send-stats =
{ wantedBy = [ "multi-user.target" ];
after = [ "hydra-init.service" ];
environment = env;
serviceConfig =
{ ExecStart = "@${cfg.package}/bin/hydra-send-stats hydra-send-stats";
User = "hydra";
};
};
# If there is less than a certain amount of free disk space, stop
# the queue/evaluator to prevent builds from failing or aborting.
systemd.services.hydra-check-space =
{ script =
''
if [ $(($(stat -f -c '%a' /nix/store) * $(stat -f -c '%S' /nix/store))) -lt $((${toString cfg.minimumDiskFree} * 1024**3)) ]; then
echo "stopping Hydra queue runner due to lack of free space..."
systemctl stop hydra-queue-runner
fi
if [ $(($(stat -f -c '%a' /nix/store) * $(stat -f -c '%S' /nix/store))) -lt $((${toString cfg.minimumDiskFreeEvaluator} * 1024**3)) ]; then
echo "stopping Hydra evaluator due to lack of free space..."
systemctl stop hydra-evaluator
fi
'';
startAt = "*:0/5";
};
2015-10-07 11:12:24 +00:00
# Periodically compress build logs. The queue runner compresses
# logs automatically after a step finishes, but this doesn't work
# if the queue runner is stopped prematurely.
systemd.services.hydra-compress-logs =
{ path = [ pkgs.bzip2 ];
script =
''
2015-10-27 15:10:02 +00:00
find /var/lib/hydra/build-logs -type f -name "*.drv" -mtime +3 -size +0c | xargs -r bzip2 -v -f
2015-10-07 11:12:24 +00:00
'';
startAt = "Sun 01:45";
};
services.postgresql.enable = mkIf haveLocalDB true;
services.postgresql.identMap = optionalString haveLocalDB
''
hydra-users hydra hydra
hydra-users hydra-queue-runner hydra
hydra-users hydra-www hydra
hydra-users root hydra
'';
services.postgresql.authentication = optionalString haveLocalDB
''
local hydra all ident map=hydra-users
'';
};
2011-05-05 06:27:38 +00:00
}