aboutsummaryrefslogtreecommitdiff
path: root/service.nix
diff options
context:
space:
mode:
authorMartin Fischer <martin@push-f.com>2026-10-25 09:08:01 +0100
committerMartin Fischer <martin@push-f.com>2026-10-25 19:10:47 +0100
commit41212a7a15198e53d205252a02069a7dd6ddadeb (patch)
tree38a24eebf956b2453675ac1cefec925faf09de02 /service.nix
initial commit
Diffstat (limited to 'service.nix')
-rw-r--r--service.nix122
1 files changed, 122 insertions, 0 deletions
diff --git a/service.nix b/service.nix
new file mode 100644
index 0000000..37d567e
--- /dev/null
+++ b/service.nix
@@ -0,0 +1,122 @@
+{
+ config,
+ lib,
+ pkgs,
+ ...
+}:
+
+let
+ inherit (lib) mkEnableOption mkIf mkMerge mkOption types;
+
+ cfg = config.services.bsky-news;
+
+ mkService = botName: bot: operation: {
+ description = "Bluesky ${botName} ${operation} job";
+ after = [ "network-online.target" ];
+ wants = [ "network-online.target" ];
+
+ environment.BSKY_CREDENTIALS_FILE = "%d/bsky";
+
+ serviceConfig = {
+ Type = "oneshot";
+ ExecStart = lib.escapeShellArgs [
+ (lib.getExe' cfg.package "bsky-news")
+ botName
+ operation
+ ];
+
+ LoadCredential = "bsky:${bot.credentialsFile}";
+ DynamicUser = true;
+ StateDirectory = "bsky-news-${botName}";
+ WorkingDirectory = "%S/bsky-news-${botName}";
+ UMask = "0077";
+
+ NoNewPrivileges = true;
+ PrivateTmp = true;
+ ProtectHome = true;
+ ProtectSystem = "strict";
+ ProtectClock = true;
+ ProtectKernelLogs = true;
+ ProtectKernelModules = true;
+ ProtectKernelTunables = true;
+ RestrictSUIDSGID = true;
+ };
+ };
+
+ mkTimer = unitName: schedule: randomizedDelaySec: {
+ description = "Run ${unitName} periodically";
+ wantedBy = [ "timers.target" ];
+
+ timerConfig = {
+ OnCalendar = schedule;
+ Persistent = true;
+ RandomizedDelaySec = randomizedDelaySec;
+ Unit = "${unitName}.service";
+ };
+ };
+
+ servicesForBot = botName: bot:
+ let
+ breakingUnit = "bsky-news-${botName}-breaking";
+ in
+ {
+ ${breakingUnit} = mkService botName bot "breaking";
+ };
+
+ timersForBot = botName: bot:
+ let
+ breakingUnit = "bsky-news-${botName}-breaking";
+ in
+ {
+ ${breakingUnit} = mkTimer breakingUnit bot.breakingNews.schedule bot.breakingNews.randomizedDelaySec;
+ };
+in
+{
+ options.services.bsky-news = {
+ enable = mkEnableOption "periodic Bluesky news bots";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.callPackage ./default.nix { };
+ defaultText = lib.literalExpression "pkgs.callPackage ./default.nix { }";
+ description = "The bsky-news package to run.";
+ };
+
+ bots = mkOption {
+ default = { };
+ description = "Bluesky bot accounts and their schedules.";
+ type = types.attrsOf (types.submodule ({ ... }: {
+ options = {
+ credentialsFile = mkOption {
+ type = types.str;
+ description = ''
+ Path to a credentials file, normally an agenix secret. It is
+ exposed to the process through systemd's credential directory;
+ BSKY_CREDENTIALS_FILE contains the resulting path.
+ '';
+ };
+
+ breakingNews = {
+ schedule = mkOption {
+ type = types.str;
+ default = "*:0/10";
+ description = "systemd OnCalendar expression for breaking news.";
+ };
+
+ randomizedDelaySec = mkOption {
+ type = types.str;
+ default = "30s";
+ description = "Maximum random delay applied to breaking-news runs.";
+ };
+ };
+
+ };
+ }));
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services = mkMerge (lib.mapAttrsToList servicesForBot cfg.bots);
+ systemd.timers = mkMerge (lib.mapAttrsToList timersForBot cfg.bots);
+ };
+}