{ config, pkgs, ... }: with pkgs.lib; let cfg = config.services.hydra; baseDir = "/var/lib/hydra"; hydraConf = pkgs.writeScript "hydra.conf" '' using_frontend_proxy 1 base_uri ${cfg.hydraURL} notification_sender ${cfg.notificationSender} max_servers 25 ''; env = { NIX_REMOTE = "daemon"; HYDRA_DBI = cfg.dbi; HYDRA_CONFIG = "${baseDir}/data/hydra.conf"; HYDRA_DATA = "${baseDir}/data"; HYDRA_PORT = "${toString cfg.port}"; OPENSSL_X509_CERT_FILE = "/etc/ssl/certs/ca-bundle.crt"; GIT_SSL_CAINFO = "/etc/ssl/certs/ca-bundle.crt"; }; serverEnv = env // { HYDRA_LOGO = if cfg.logo != null then cfg.logo else ""; HYDRA_TRACKER = cfg.tracker; } // (optionalAttrs cfg.debugMode { DBIC_TRACE = 1; }); in { ###### interface options = { services.hydra = rec { enable = mkOption { default = false; description = '' Whether to run Hydra services. ''; }; dbi = mkOption { default = "dbi:Pg:dbname=hydra;host=localhost;user=hydra;"; example = "dbi:SQLite:/home/hydra/db/hydra.sqlite"; description = '' The DBI string for Hydra database connection. ''; }; hydra = mkOption { #default = pkgs.hydra; description = '' Location of hydra ''; }; hydraURL = mkOption { description = '' The base URL for the Hydra webserver instance. Used for links in emails. ''; }; port = mkOption { default = 3000; description = '' TCP port the web server should listen to. ''; }; minimumDiskFree = mkOption { default = 5; description = '' Threshold of minimum disk space (G) to determine if queue runner should run or not. ''; }; minimumDiskFreeEvaluator = mkOption { default = 2; description = '' Threshold of minimum disk space (G) to determine if evaluator should run or not. ''; }; notificationSender = mkOption { description = '' Sender email address used for email notifications. ''; }; tracker = mkOption { default = ""; description = '' Piece of HTML that is included on all pages. ''; }; logo = mkOption { default = null; description = '' File name of an alternate logo to be displayed on the web pages. ''; }; useWAL = mkOption { default = true; description = '' Whether to use SQLite's Write-Ahead Logging, which may improve performance. ''; }; debugServer = mkOption { default = false; type = types.bool; description = "Whether to run the server in debug mode"; }; }; }; ###### implementation config = mkIf cfg.enable { environment.systemPackages = [ cfg.hydra ]; users.extraUsers.hydra = { description = "Hydra"; home = baseDir; createHome = true; useDefaultShell = true; }; 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 # Hydra needs caching of build failures. build-cache-failure = true build-poll-interval = 10 # Online log compression makes it impossible to get the tail of # builds that are in progress. build-compress-log = false use-sqlite-wal = ${if cfg.useWAL then "true" else "false"} ''; systemd.services."hydra-init" = { wantedBy = [ "multi-user.target" ]; requires = [ "postgresql.service" ]; after = [ "postgresql.service" ]; environment = env; script = '' mkdir -p ${baseDir}/data chown hydra ${baseDir}/data ln -sf ${hydraConf} ${baseDir}/data/hydra.conf pass=$(HOME=/root ${pkgs.openssl}/bin/openssl rand -base64 32) if [ ! -f ${baseDir}/.pgpass ]; then ${config.services.postgresql.package}/bin/psql postgres << EOF CREATE USER hydra PASSWORD '$pass'; EOF ${config.services.postgresql.package}/bin/createdb -O hydra hydra cat > ${baseDir}/.pgpass-tmp << EOF localhost:*:hydra:hydra:$pass EOF chown hydra ${baseDir}/.pgpass-tmp chmod 600 ${baseDir}/.pgpass-tmp mv ${baseDir}/.pgpass-tmp ${baseDir}/.pgpass fi ${pkgs.shadow}/bin/su hydra -c ${cfg.hydra}/bin/hydra-init ${config.services.postgresql.package}/bin/psql hydra << EOF BEGIN; INSERT INTO Users(userName, emailAddress, password) VALUES ('admin', '${cfg.notificationSender}', '$(echo -n $pass | sha1sum | cut -c1-40)'); INSERT INTO UserRoles(userName, role) values('admin', 'admin'); COMMIT; EOF ''; serviceConfig.Type = "oneshot"; serviceConfig.RemainAfterExit = true; }; systemd.services."hydra-server" = { wantedBy = [ "multi-user.target" ]; wants = [ "hydra-init.service" ]; after = [ "hydra-init.service" ]; environment = serverEnv; serviceConfig = { ExecStart = "@${cfg.hydra}/bin/hydra-server hydra-server -f -h \* --max_spare_servers 5 --max_servers 25 --max_requests 100${optionalString cfg.debugServer " -d"}"; User = "hydra"; Restart = "always"; }; }; systemd.services."hydra-queue-runner" = { wantedBy = [ "multi-user.target" ]; wants = [ "hydra-init.service" ]; after = [ "hydra-init.service" "network.target" ]; path = [ pkgs.nettools ]; environment = env; serviceConfig = { ExecStartPre = "${cfg.hydra}/bin/hydra-queue-runner --unlock"; ExecStart = "@${cfg.hydra}/bin/hydra-queue-runner hydra-queue-runner"; User = "hydra"; Restart = "always"; }; }; systemd.services."hydra-evaluator" = { wantedBy = [ "multi-user.target" ]; wants = [ "hydra-init.service" ]; after = [ "hydra-init.service" "network.target" ]; path = [ pkgs.nettools ]; environment = env; serviceConfig = { ExecStart = "@${cfg.hydra}/bin/hydra-evaluator hydra-evaluator"; User = "hydra"; Restart = "always"; }; }; systemd.services."hydra-update-gc-roots" = { wants = [ "hydra-init.service" ]; after = [ "hydra-init.service" ]; environment = env; serviceConfig = { ExecStart = "@${cfg.hydra}/bin/hydra-update-gc-roots hydra-update-gc-roots"; User = "hydra"; }; }; services.cron.systemCronJobs = let # If there is less than ... GiB of free disk space, stop the queue # to prevent builds from failing or aborting. checkSpace = pkgs.writeScript "hydra-check-space" '' #! ${pkgs.stdenv.shell} if [ $(($(stat -f -c '%a' /nix/store) * $(stat -f -c '%S' /nix/store))) -lt $((${toString cfg.minimumDiskFree} * 1024**3)) ]; then 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 systemctl stop hydra-evaluator fi ''; compressLogs = pkgs.writeScript "compress-logs" '' #! ${pkgs.stdenv.shell} -e touch -d 'last month' r find /nix/var/log/nix/drvs -type f -a ! -newer r -name '*.drv' | xargs bzip2 -v ''; in [ "*/5 * * * * root ${checkSpace} &> ${baseDir}/data/checkspace.log" "15 5 * * * root ${compressLogs} &> ${baseDir}/data/compress.log" "15 2 * * * root ${pkgs.systemd}/bin/systemctl start hydra-update-gc-roots.service" ]; }; }