diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index d40edb2408f..f6fcd5a325b 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -490,6 +490,7 @@
./services/misc/logkeys.nix
./services/misc/leaps.nix
./services/misc/lidarr.nix
+ ./services/misc/lifecycled.nix
./services/misc/mame.nix
./services/misc/matrix-appservice-discord.nix
./services/misc/matrix-synapse.nix
diff --git a/nixos/modules/services/misc/lifecycled.nix b/nixos/modules/services/misc/lifecycled.nix
new file mode 100644
index 00000000000..1c8942998d6
--- /dev/null
+++ b/nixos/modules/services/misc/lifecycled.nix
@@ -0,0 +1,164 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+let
+ cfg = config.services.lifecycled;
+
+ # TODO: Add the ability to extend this with an rfc 42-like interface.
+ # In the meantime, one can modify the environment (as
+ # long as it's not overriding anything from here) with
+ # systemd.services.lifecycled.serviceConfig.Environment
+ configFile = pkgs.writeText "lifecycled" ''
+ LIFECYCLED_HANDLER=${cfg.handler}
+ ${lib.optionalString (cfg.cloudwatchGroup != null) "LIFECYCLED_CLOUDWATCH_GROUP=${cfg.cloudwatchGroup}"}
+ ${lib.optionalString (cfg.cloudwatchStream != null) "LIFECYCLED_CLOUDWATCH_STREAM=${cfg.cloudwatchStream}"}
+ ${lib.optionalString cfg.debug "LIFECYCLED_DEBUG=${lib.boolToString cfg.debug}"}
+ ${lib.optionalString (cfg.instanceId != null) "LIFECYCLED_INSTANCE_ID=${cfg.instanceId}"}
+ ${lib.optionalString cfg.json "LIFECYCLED_JSON=${lib.boolToString cfg.json}"}
+ ${lib.optionalString cfg.noSpot "LIFECYCLED_NO_SPOT=${lib.boolToString cfg.noSpot}"}
+ ${lib.optionalString (cfg.snsTopic != null) "LIFECYCLED_SNS_TOPIC=${cfg.snsTopic}"}
+ ${lib.optionalString (cfg.awsRegion != null) "AWS_REGION=${cfg.awsRegion}"}
+ '';
+in
+{
+ meta.maintainers = with maintainers; [ cole-h grahamc ];
+
+ options = {
+ services.lifecycled = {
+ enable = mkEnableOption "lifecycled";
+
+ queueCleaner = {
+ enable = mkEnableOption "lifecycled-queue-cleaner";
+
+ frequency = mkOption {
+ type = types.str;
+ default = "hourly";
+ description = ''
+ How often to trigger the queue cleaner.
+
+ NOTE: This string should be a valid value for a systemd
+ timer's OnCalendar configuration. See
+ systemd.timer5
+ for more information.
+ '';
+ };
+
+ parallel = mkOption {
+ type = types.ints.unsigned;
+ default = 20;
+ description = ''
+ The number of parallel deletes to run.
+ '';
+ };
+ };
+
+ instanceId = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The instance ID to listen for events for.
+ '';
+ };
+
+ snsTopic = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The SNS topic that receives events.
+ '';
+ };
+
+ noSpot = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Disable the spot termination listener.
+ '';
+ };
+
+ handler = mkOption {
+ type = types.path;
+ description = ''
+ The script to invoke to handle events.
+ '';
+ };
+
+ json = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable JSON logging.
+ '';
+ };
+
+ cloudwatchGroup = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Write logs to a specific Cloudwatch Logs group.
+ '';
+ };
+
+ cloudwatchStream = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Write logs to a specific Cloudwatch Logs stream. Defaults to the instance ID.
+ '';
+ };
+
+ debug = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable debugging information.
+ '';
+ };
+
+ # XXX: Can be removed if / when
+ # https://github.com/buildkite/lifecycled/pull/91 is merged.
+ awsRegion = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The region used for accessing AWS services.
+ '';
+ };
+ };
+ };
+
+ ### Implementation ###
+
+ config = mkMerge [
+ (mkIf cfg.enable {
+ environment.etc."lifecycled".source = configFile;
+
+ systemd.packages = [ pkgs.lifecycled ];
+ systemd.services.lifecycled = {
+ wantedBy = [ "network-online.target" ];
+ restartTriggers = [ configFile ];
+ };
+ })
+
+ (mkIf cfg.queueCleaner.enable {
+ systemd.services.lifecycled-queue-cleaner = {
+ description = "Lifecycle Daemon Queue Cleaner";
+ environment = optionalAttrs (cfg.awsRegion != null) { AWS_REGION = cfg.awsRegion; };
+ serviceConfig = {
+ Type = "oneshot";
+ ExecStart = "${pkgs.lifecycled}/bin/lifecycled-queue-cleaner -parallel ${toString cfg.queueCleaner.parallel}";
+ };
+ };
+
+ systemd.timers.lifecycled-queue-cleaner = {
+ description = "Lifecycle Daemon Queue Cleaner Timer";
+ wantedBy = [ "timers.target" ];
+ after = [ "network-online.target" ];
+ timerConfig = {
+ Unit = "lifecycled-queue-cleaner.service";
+ OnCalendar = "${cfg.queueCleaner.frequency}";
+ };
+ };
+ })
+ ];
+}