From b592f0100a73ae6113482074a9a77d8b55775ee9 Mon Sep 17 00:00:00 2001 From: Adrian G L Date: Sun, 31 May 2026 02:04:30 +0200 Subject: [PATCH 01/10] feat: add radicle to bekkalokk --- hosts/bekkalokk/configuration.nix | 1 + hosts/bekkalokk/services/radicle.nix | 40 ++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 hosts/bekkalokk/services/radicle.nix diff --git a/hosts/bekkalokk/configuration.nix b/hosts/bekkalokk/configuration.nix index f208a48..42e0aa9 100644 --- a/hosts/bekkalokk/configuration.nix +++ b/hosts/bekkalokk/configuration.nix @@ -7,6 +7,7 @@ ./services/alps.nix ./services/bluemap.nix + ./services/radicle.nix ./services/idp-simplesamlphp ./services/kerberos.nix ./services/mediawiki diff --git a/hosts/bekkalokk/services/radicle.nix b/hosts/bekkalokk/services/radicle.nix new file mode 100644 index 0000000..c647115 --- /dev/null +++ b/hosts/bekkalokk/services/radicle.nix @@ -0,0 +1,40 @@ +{ config, lib, ... }: +let + domain = "dav.pvv.ntnu.no"; + radicalePort = 5232; +in { + services.radicale = { + enable = true; + + settings = { + server = { + hosts = [ "127.0.0.1:${toString radicalePort}" ]; + }; + + auth = { + type = "imap"; + imap_host = "imap.pvv.ntnu.no"; + imap_security = "tls"; + }; + + storage = { + filesystem_folder = "/var/lib/radicale/collections"; + }; + }; + }; + services.nginx.virtualHosts."${domain}" = { + forceSSL = true; + enableACME = true; + kTLS = true; + + extraConfig = '' + client_max_body_size 128M; + ''; + + locations."/" = { + proxyPass = "http://127.0.0.1:${toString radicalePort}"; + proxyWebsockets = true; + }; + }; + networking.firewall.allowedTCPPorts = [ radicalePort ]; +} -- 2.54.0 From 352f25f6fe9f1a599c6c86f8f0168f8e57552dd2 Mon Sep 17 00:00:00 2001 From: Vegard Bieker Matthey Date: Sat, 7 Mar 2026 20:35:06 +0100 Subject: [PATCH 02/10] drumknotty: init --- flake.lock | 46 +++++- flake.nix | 122 ++++++++-------- hosts/skrot/configuration.nix | 24 +++- modules/drumknotty.nix | 261 ++++++++++++++++++++++++++++++++++ 4 files changed, 387 insertions(+), 66 deletions(-) create mode 100644 modules/drumknotty.nix diff --git a/flake.lock b/flake.lock index 82c4a9f..6bdbd1b 100644 --- a/flake.lock +++ b/flake.lock @@ -161,6 +161,27 @@ "url": "https://git.pvv.ntnu.no/Grzegorz/grzegorz-clients.git" } }, + "libdib": { + "inputs": { + "nixpkgs": [ + "worblehat", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1769338528, + "narHash": "sha256-t18ZoSt9kaI1yde26ok5s7aFLkap1Q9+/2icVh2zuaE=", + "ref": "refs/heads/main", + "rev": "7218348163fd8d84df4a6f682c634793e67a3fed", + "revCount": 13, + "type": "git", + "url": "https://git.pvv.ntnu.no/Projects/libdib.git" + }, + "original": { + "type": "git", + "url": "https://git.pvv.ntnu.no/Projects/libdib.git" + } + }, "matrix-next": { "inputs": { "nixpkgs": [ @@ -391,7 +412,8 @@ "pvv-nettsiden": "pvv-nettsiden", "qotd": "qotd", "roowho2": "roowho2", - "sops-nix": "sops-nix" + "sops-nix": "sops-nix", + "worblehat": "worblehat" } }, "roowho2": { @@ -522,6 +544,28 @@ "repo": "sops-nix", "type": "github" } + }, + "worblehat": { + "inputs": { + "libdib": "libdib", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1770887951, + "narHash": "sha256-6LGqM9yhONtfCXHtPNn3S0GFsmB2dCchyozHDevwmiQ=", + "ref": "main", + "rev": "911063041f24d594a772a2a699d71d3d94953ce8", + "revCount": 101, + "type": "git", + "url": "https://git.pvv.ntnu.no/Projects/worblehat.git" + }, + "original": { + "ref": "main", + "type": "git", + "url": "https://git.pvv.ntnu.no/Projects/worblehat.git" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 84285e3..2f29488 100644 --- a/flake.nix +++ b/flake.nix @@ -23,6 +23,9 @@ dibbler.url = "git+https://git.pvv.ntnu.no/Projects/dibbler.git?ref=main"; dibbler.inputs.nixpkgs.follows = "nixpkgs"; + worblehat.url = "git+https://git.pvv.ntnu.no/Projects/worblehat.git?ref=main"; + worblehat.inputs.nixpkgs.follows = "nixpkgs"; + matrix-next.url = "github:dali99/nixos-matrix-modules/v0.8.0"; matrix-next.inputs.nixpkgs.follows = "nixpkgs"; @@ -170,67 +173,63 @@ ); stableNixosConfig = name: extraArgs: - nixosConfig nixpkgs name ./hosts/${name}/configuration.nix extraArgs; - in - { - bicep = stableNixosConfig "bicep" { - modules = [ - inputs.matrix-next.nixosModules.default - inputs.pvv-calendar-bot.nixosModules.default - inputs.minecraft-heatmap.nixosModules.default - self.nixosModules.gickup - self.nixosModules.matrix-ooye - ]; - overlays = [ - inputs.pvv-calendar-bot.overlays.default - inputs.minecraft-heatmap.overlays.default - (final: prev: { - inherit (self.packages.${prev.stdenv.hostPlatform.system}) out-of-your-element; - }) - ]; - }; - bekkalokk = stableNixosConfig "bekkalokk" { - overlays = [ - (final: prev: { - mediawiki-extensions = final.callPackage ./packages/mediawiki-extensions {}; - simplesamlphp = final.callPackage ./packages/simplesamlphp {}; - }) - inputs.pvv-nettsiden.overlays.default - inputs.qotd.overlays.default - ]; - modules = [ - inputs.pvv-nettsiden.nixosModules.default - inputs.qotd.nixosModules.default - ]; - }; - ildkule = stableNixosConfig "ildkule" { - modules = [ - inputs.disko.nixosModules.disko - ]; - }; - skrot = stableNixosConfig "skrot" { - modules = [ - inputs.disko.nixosModules.disko - inputs.dibbler.nixosModules.default - ]; - overlays = [inputs.dibbler.overlays.default]; - }; - shark = stableNixosConfig "shark" {}; - wenche = stableNixosConfig "wenche" {}; - temmie = stableNixosConfig "temmie" { - overlays = [ - inputs.bro.overlays.default - ]; - modules = [ - inputs.bro.nixosModules.default - ]; - }; - gluttony = stableNixosConfig "gluttony" { - overlays = [ - (final: prev: { bluemap = final.callPackage ./packages/bluemap.nix {}; }) - ]; - modules = [ self.nixosModules.bluemap ]; - }; + nixosConfig nixpkgs name ./hosts/${name}/configuration.nix extraArgs; + in { + bakke = stableNixosConfig "bakke" { + modules = [ + inputs.disko.nixosModules.disko + ]; + }; + bicep = stableNixosConfig "bicep" { + modules = [ + inputs.matrix-next.nixosModules.default + inputs.pvv-calendar-bot.nixosModules.default + inputs.minecraft-heatmap.nixosModules.default + self.nixosModules.gickup + self.nixosModules.matrix-ooye + ]; + overlays = [ + inputs.pvv-calendar-bot.overlays.default + inputs.minecraft-heatmap.overlays.default + (final: prev: { + inherit (self.packages.${prev.stdenv.hostPlatform.system}) out-of-your-element; + }) + ]; + }; + bekkalokk = stableNixosConfig "bekkalokk" { + overlays = [ + (final: prev: { + mediawiki-extensions = final.callPackage ./packages/mediawiki-extensions { }; + simplesamlphp = final.callPackage ./packages/simplesamlphp { }; + bluemap = final.callPackage ./packages/bluemap.nix { }; + }) + inputs.pvv-nettsiden.overlays.default + inputs.qotd.overlays.default + ]; + modules = [ + inputs.pvv-nettsiden.nixosModules.default + self.nixosModules.bluemap + inputs.qotd.nixosModules.default + ]; + }; + ildkule = stableNixosConfig "ildkule" { }; + #ildkule-unstable = unstableNixosConfig "ildkule" { }; + skrot = stableNixosConfig "skrot" { + modules = [ + self.nixosModules.drumknotty + inputs.disko.nixosModules.disko + inputs.dibbler.nixosModules.default + inputs.worblehat.nixosModules.default + ]; + overlays = [ + inputs.dibbler.overlays.default + inputs.worblehat.overlays.default + ]; + }; + shark = stableNixosConfig "shark" { }; + wenche = stableNixosConfig "wenche" { }; + temmie = stableNixosConfig "temmie" { }; + gluttony = stableNixosConfig "gluttony" { }; kommode = stableNixosConfig "kommode" { overlays = [ @@ -284,6 +283,7 @@ rsync-pull-targets = ./modules/rsync-pull-targets.nix; snakeoil-certs = ./modules/snakeoil-certs.nix; snappymail = ./modules/snappymail.nix; + drumknotty = ./modules/drumknotty.nix; }; devShells = forAllSystems (system: { diff --git a/hosts/skrot/configuration.nix b/hosts/skrot/configuration.nix index e2241b7..d1f3c2c 100644 --- a/hosts/skrot/configuration.nix +++ b/hosts/skrot/configuration.nix @@ -28,18 +28,22 @@ sops.secrets = { "dibbler/postgresql/password" = { - owner = "dibbler"; - group = "dibbler"; + owner = "drumknotty"; + group = "drumknotty"; + }; + "worblehat/postgresql/password" = { + owner = "drumknotty"; + group = "drumknotty"; }; }; - services.dibbler = { + services.drumknotty = { enable = true; kioskMode = true; limitScreenWidth = 80; limitScreenHeight = 42; - settings = { + dibblerSettings = { general.quit_allowed = false; database = { type = "postgresql"; @@ -51,6 +55,18 @@ }; }; }; + worblehatSettings = { + general.quit_allowed = false; + database = { + type = "postgresql"; + postgresql = { + username = "pvv_vv"; + dbname = "pvv_vv"; + host = "postgres.pvv.ntnu.no"; + password = config.sops.secrets."worblehat/postgresql/password".path; + }; + }; + }; }; systemd.services."serial-getty@ttyUSB0" = lib.mkIf (!config.virtualisation.isVmVariant) { diff --git a/modules/drumknotty.nix b/modules/drumknotty.nix new file mode 100644 index 0000000..a579ee9 --- /dev/null +++ b/modules/drumknotty.nix @@ -0,0 +1,261 @@ +{ + config, + pkgs, + lib, + ... +}: +let + cfg = config.services.drumknotty; + + format = pkgs.formats.toml { }; +in +{ + options.services.drumknotty = { + enable = lib.mkEnableOption "DrumknoTTY"; + + dibblerPackage = lib.mkPackageOption pkgs "dibbler" { }; + worblehatPackage = lib.mkPackageOption pkgs "worblehat" { }; + screenPackage = lib.mkPackageOption pkgs "screen" { }; + + screenSessionName = lib.mkOption { + type = lib.types.str; + default = "drumknotty"; + example = "myscreensessionname"; + description = '' + Sets the screen session name. + ''; + }; + + createLocalDatabase = lib.mkEnableOption "" // { + description = '' + Whether to set up a local postgres database automatically. + + ::: {.note} + You must set up postgres manually before enabling this option. + ::: + ''; + }; + + kioskMode = lib.mkEnableOption "" // { + description = '' + Whether to let dibbler take over the entire machine. + + This will restrict the machine to a single TTY and make the program unquittable. + You can still get access to PTYs via SSH and similar, if enabled. + ''; + }; + + limitScreenHeight = lib.mkOption { + type = with lib.types; nullOr ints.unsigned; + default = null; + example = 42; + description = '' + If set, limits the height of the screen dibbler uses to the given number of lines. + ''; + }; + + limitScreenWidth = lib.mkOption { + type = with lib.types; nullOr ints.unsigned; + default = null; + example = 80; + description = '' + If set, limits the width of the screen dibbler uses to the given number of columns. + ''; + }; + + dibblerSettings = lib.mkOption { + description = "Configuration for dibbler"; + default = { }; + type = lib.types.submodule { + freeformType = format.type; + }; + }; + + worblehatSettings = lib.mkOption { + description = "Configuration for worblehat"; + default = { }; + type = lib.types.submodule { + freeformType = format.type; + }; + }; + + }; + + config = lib.mkIf cfg.enable ( + lib.mkMerge [ + { + environment.systemPackages = [ + cfg.dibblerPackage + cfg.worblehatPackage + ]; + + environment.etc."dibbler/dibbler.toml".source = format.generate "dibbler.toml" cfg.dibblerSettings; + environment.etc."worblehat/config.toml".source = + format.generate "worblehat-config.toml" cfg.worblehatSettings; + + users = { + users.drumknotty = { + group = "drumknotty"; + isNormalUser = true; + }; + groups.drumknotty = { }; + }; + + services.dibbler.settings.database = lib.mkIf cfg.createLocalDatabase { + type = "postgresql"; + postgresql.host = "/run/postgresql"; + }; + + services.postgresql = lib.mkIf cfg.createLocalDatabase { + ensureDatabases = [ + "dibbler" + "worblehat" + ]; + ensureUsers = [ + { + name = "drumknotty"; + ensureDBOwnership = true; + ensureClauses.login = true; + } + ]; + }; + + systemd.services.dibbler-setup-database = lib.mkIf cfg.createLocalDatabase { + description = "Dibbler database setup"; + wantedBy = [ "default.target" ]; + after = [ "postgresql.service" ]; + unitConfig = { + ConditionPathExists = "!/var/lib/dibbler/.db-setup-done"; + }; + serviceConfig = { + Type = "oneshot"; + ExecStart = "${lib.getExe cfg.dibblerPackage} --config /etc/dibbler/dibbler.toml create-db"; + ExecStartPost = "${lib.getExe' pkgs.coreutils "touch"} /var/lib/dibbler/.db-setup-done"; + StateDirectory = "dibbler"; + + User = "drumknotty"; + Group = "drumknotty"; + }; + }; + + systemd.services.worblehat-setup-database = lib.mkIf cfg.createLocalDatabase { + description = "Worblehat database setup"; + wantedBy = [ "default.target" ]; + after = [ "postgresql.service" ]; + unitConfig = { + ConditionPathExists = "!/var/lib/worblehat/.db-setup-done"; + }; + serviceConfig = { + Type = "oneshot"; + ExecStart = "${lib.getExe cfg.worblehatPackage} --config /etc/worblehat/config.toml create-db"; + ExecStartPost = "${lib.getExe' pkgs.coreutils "touch"} /var/lib/worblehat/.db-setup-done"; + StateDirectory = "worblehat"; + + User = "drumknotty"; + Group = "drumknotty"; + }; + }; + + } + (lib.mkIf cfg.kioskMode { + boot.kernelParams = [ + "console=tty1" + ]; + + users.users.drumknotty = { + extraGroups = [ "lp" ]; + shell = + (pkgs.writeShellScriptBin "login-shell" "${lib.getExe' cfg.screenPackage "screen"} -x ${cfg.screenSessionName}") + // { + shellPath = "/bin/login-shell"; + }; + }; + + services.drumknotty.dibblerSettings.general = { + quit_allowed = false; + stop_allowed = false; + }; + + services.drumknotty.worblehatSettings.general = { + quit_allowed = false; + stop_allowed = false; + }; + + systemd.services.drumknotty-screen-session = { + description = "Drumknotty Screen Session"; + wantedBy = [ + "default.target" + ]; + after = + if cfg.createLocalDatabase then + [ + "postgresql.service" + "dibbler-setup-database.service" + "worblehat-setup-database.service" + ] + else + [ + "network.target" + ]; + serviceConfig = + let + dibblerArgs = lib.cli.toCommandLineShellGNU { } { + config = "/etc/dibbler/dibbler.toml"; + }; + + worblehatArgs = lib.cli.toCommandLineShellGNU { } { + config = "/etc/worblehat/config.toml"; + }; + + in + { + Type = "forking"; + RemainAfterExit = false; + Restart = "always"; + RestartSec = "5s"; + SuccessExitStatus = 1; + + User = "drumknotty"; + Group = "drumknotty"; + + ExecStartPre = "-${lib.getExe' cfg.screenPackage "screen"} -X -S ${cfg.screenSessionName} kill"; + ExecStart = + let + screenArgs = lib.escapeShellArgs [ + # -dm creates the screen in detached mode without accessing it + "-dm" + + # Session name + "-S" + "${cfg.screenSessionName}" + + # Window name + "-t" + "dibbler" + + # Set optimal output mode instead of VT100 emulation + "-O" + + # Enable login mode, updates utmp entries + "-l" + ]; + + in + "${lib.getExe' cfg.screenPackage "screen"} ${screenArgs} ${lib.getExe cfg.dibblerPackage} ${dibblerArgs} loop"; + ExecStartPost = [ + "${lib.getExe' cfg.screenPackage "screen"} -S ${cfg.screenSessionName} -X screen -t worblehat ${lib.getExe cfg.worblehatPackage} ${worblehatArgs} cli" + ] + ++ lib.optionals (cfg.limitScreenWidth != null) [ + "${lib.getExe' cfg.screenPackage "screen"} -X -S ${cfg.screenSessionName} width ${toString cfg.limitScreenWidth}" + ] + ++ lib.optionals (cfg.limitScreenHeight != null) [ + "${lib.getExe' cfg.screenPackage "screen"} -X -S ${cfg.screenSessionName} height ${toString cfg.limitScreenHeight}" + ]; + }; + }; + + services.getty.autologinUser = "drumknotty"; + }) + ] + ); +} -- 2.54.0 From ebb721efc89ee532bfc7596c91a45da7ba0465f3 Mon Sep 17 00:00:00 2001 From: Vegard Bieker Matthey Date: Tue, 17 Mar 2026 16:30:38 +0100 Subject: [PATCH 03/10] add database password for worblehat --- secrets/skrot/skrot.yaml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/secrets/skrot/skrot.yaml b/secrets/skrot/skrot.yaml index 3485aa3..7cd7024 100644 --- a/secrets/skrot/skrot.yaml +++ b/secrets/skrot/skrot.yaml @@ -1,6 +1,9 @@ dibbler: postgresql: password: ENC[AES256_GCM,data:3X9A3jOpFVRuBg0gRiCEsZVKfLI=,iv:XC7LBNUhALk9IEhItV8fO5p/m7VKL0REBY1W2IZt7G4=,tag:l18R7EhbOlucZHFQiEvpHw==,type:str] +worblehat: + postgresql: + password: ENC[AES256_GCM,data:WpJR6MumY+7WUYdVVgAqv1af+NmqecTMO9aP5lidSpE=,iv:7aoN8mjXckd81LxasMSG3R2vqj0SvzSl7wrEQ1LwToo=,tag:zeeNcEpkYnqyd8be0ZS+kQ==,type:str] sops: age: - recipient: age1hzkvnktkr8t5gvtq0ccw69e44z5z6wf00n3xhk3hj24emf07je5s6q2evr @@ -66,8 +69,8 @@ sops: MmxPMWNPYzJiOFRqY2VYczhvRm5IR3cKpUVV+zsMolsHI2YK9YqC6ecNT6QXv0TV d1SpXRAexZBeWCCHBjSdvQBl8AT4EwrAIP2M2o++6i5DaGoGiEIWZQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2026-02-10T20:02:28Z" - mac: ENC[AES256_GCM,data:i8CjVxoD7zdkLNJlI9DCo/tDV5DUI7JdpozLtYZzI7Cu51GayaE2Y3Wg4de6P0L7C3FER04WfRe/h+G9PLZICX/CfSipQysyrEq3Pjt9IKsjytDhP9VYJ36QFGF0PuHUQAMSLts/tAoAvLue6MP+V82l5js9ghvyBrzyBGxoyJw=,iv:QFNxvCYxrSkwy7iT+2BEacNPftDXju1cibprVPDjic0=,tag:496E+oCy/VwTylyaWhQD+A==,type:str] + lastmodified: "2026-03-18T14:56:22Z" + mac: ENC[AES256_GCM,data:nBKtFmFKx/Mt9TIFnKuuznsPAXCQpc3+WIspNu5TN9TpIqw75nzYXpxIb2hxRfRu0nbjHXpBy4bkzeMi41BGkvkvV57CZyq11J5i/iIKwuvllaB1IWrdDT2u+6RH3jIspp3KoyxFWdRqcGfNma9dSmtI+1Dd5z7XaxVaoVK2QMI=,iv:6joviyJ2cXmGh/9HH7VEcoK3+4GK5I6i2N/1d65PAN0=,tag:0BFVPWL3BByJH8HbrBTKOw==,type:str] pgp: - created_at: "2026-02-10T20:01:32Z" enc: |- @@ -90,4 +93,4 @@ sops: -----END PGP MESSAGE----- fp: F7D37890228A907440E1FD4846B9228E814A2AAC unencrypted_suffix: _unencrypted - version: 3.11.0 + version: 3.12.1 -- 2.54.0 From e1c820d37e83e0de411f638b6205e6a0ea93870b Mon Sep 17 00:00:00 2001 From: Vegard Bieker Matthey Date: Tue, 17 Mar 2026 16:51:59 +0100 Subject: [PATCH 04/10] add worblehat daemons --- flake.nix | 5 ++-- modules/drumknotty.nix | 56 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/flake.nix b/flake.nix index 2f29488..994af7c 100644 --- a/flake.nix +++ b/flake.nix @@ -23,7 +23,7 @@ dibbler.url = "git+https://git.pvv.ntnu.no/Projects/dibbler.git?ref=main"; dibbler.inputs.nixpkgs.follows = "nixpkgs"; - worblehat.url = "git+https://git.pvv.ntnu.no/Projects/worblehat.git?ref=main"; + worblehat.url = "git+https://git.pvv.ntnu.no/Projects/worblehat.git?ref=stable_deps"; worblehat.inputs.nixpkgs.follows = "nixpkgs"; matrix-next.url = "github:dali99/nixos-matrix-modules/v0.8.0"; @@ -221,7 +221,8 @@ inputs.dibbler.nixosModules.default inputs.worblehat.nixosModules.default ]; - overlays = [ + overlays = + [ inputs.dibbler.overlays.default inputs.worblehat.overlays.default ]; diff --git a/modules/drumknotty.nix b/modules/drumknotty.nix index a579ee9..8cef532 100644 --- a/modules/drumknotty.nix +++ b/modules/drumknotty.nix @@ -79,6 +79,28 @@ in }; }; + deadline-daemon = { + enable = lib.mkEnableOption "" // { + description = '' + Whether to enable the worblehat deadline-daemon service, + which periodically checks for upcoming deadlines and notifies users. + + Note that this service is independent of the main worblehat service, + and must be enabled separately. + ''; + }; + + onCalendar = lib.mkOption { + type = lib.types.str; + description = '' + How often to trigger rendering the map, + in the format of a systemd timer onCalendar configuration. + + See {manpage}`systemd.timer(5)`. + ''; + default = "*-*-* 10:15:00"; + }; + }; }; config = lib.mkIf cfg.enable ( @@ -256,6 +278,40 @@ in services.getty.autologinUser = "drumknotty"; }) + (lib.mkIf cfg.deadline-daemon.enable { + systemd.timers.worblehat-deadline-daemon = { + description = "Worblehat Deadline Daemon"; + wantedBy = [ "timers.target" ]; + timerConfig = { + OnCalendar = cfg.deadline-daemon.onCalendar; + Persistent = true; + }; + }; + + systemd.services.worblehat-deadline-daemon = { + description = "Worblehat Deadline Daemon"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + serviceConfig = { + Type = "oneshot"; + CPUSchedulingPolicy = "idle"; + IOSchedulingClass = "idle"; + + ExecStart = + let + worblehatArgs = lib.cli.toCommandLineShellGNU { } { + config = "/etc/worblehat/config.toml"; + }; + in + "${lib.getExe cfg.package} ${worblehatArgs} deadline-daemon"; + + User = "worblehat"; + Group = "worblehat"; + }; + }; + }) + ] ); + } -- 2.54.0 From 0d41e53589810b3843a3abe9b552dd11cda7a3c4 Mon Sep 17 00:00:00 2001 From: Vegard Bieker Matthey Date: Wed, 18 Mar 2026 13:39:47 +0100 Subject: [PATCH 05/10] set default settings for worblehat and dibbler --- flake.lock | 12 +++---- hosts/skrot/configuration.nix | 4 +-- modules/drumknotty.nix | 64 ++++++++++++++++++++++++++++++++++- 3 files changed, 71 insertions(+), 9 deletions(-) diff --git a/flake.lock b/flake.lock index 6bdbd1b..77ea26f 100644 --- a/flake.lock +++ b/flake.lock @@ -553,16 +553,16 @@ ] }, "locked": { - "lastModified": 1770887951, - "narHash": "sha256-6LGqM9yhONtfCXHtPNn3S0GFsmB2dCchyozHDevwmiQ=", - "ref": "main", - "rev": "911063041f24d594a772a2a699d71d3d94953ce8", - "revCount": 101, + "lastModified": 1773832647, + "narHash": "sha256-VZVQDwqDmMGJcJ8WnD6Ug+DTV0b6S2DKHkdj3QvFO+w=", + "ref": "stable_deps", + "rev": "9bdaaf6c5180309b7810bb72f9e717a7c3b2a504", + "revCount": 102, "type": "git", "url": "https://git.pvv.ntnu.no/Projects/worblehat.git" }, "original": { - "ref": "main", + "ref": "stable_deps", "type": "git", "url": "https://git.pvv.ntnu.no/Projects/worblehat.git" } diff --git a/hosts/skrot/configuration.nix b/hosts/skrot/configuration.nix index d1f3c2c..473038b 100644 --- a/hosts/skrot/configuration.nix +++ b/hosts/skrot/configuration.nix @@ -60,8 +60,8 @@ database = { type = "postgresql"; postgresql = { - username = "pvv_vv"; - dbname = "pvv_vv"; + username = "worblehat"; + dbname = "worblehat"; host = "postgres.pvv.ntnu.no"; password = config.sops.secrets."worblehat/postgresql/password".path; }; diff --git a/modules/drumknotty.nix b/modules/drumknotty.nix index 8cef532..8e8e142 100644 --- a/modules/drumknotty.nix +++ b/modules/drumknotty.nix @@ -123,7 +123,69 @@ in groups.drumknotty = { }; }; - services.dibbler.settings.database = lib.mkIf cfg.createLocalDatabase { + services.drumknotty.dibblerSettings = { + limits = { + low_credit_warning_limit = lib.mkDefault (-100); + user_recent_transaction_limit = lib.mkDefault 100; + }; + + printer = { + label_type = lib.mkDefault "62"; + label_rotate = lib.mkDefault false; + }; + }; + + services.drumknotty.worblehatSettings = { + logging = { + debug = lib.mkDefault true; + debug_sql = lib.mkDefault false; + }; + + database = { + type = lib.mkDefault "sqlite"; + sqlite.path = lib.mkDefault "./worblehat.sqlite"; + postgresql = { + host = lib.mkDefault "localhost"; + port = lib.mkDefault 5432; + username = lib.mkDefault "worblehat"; + password = lib.mkDefault "/var/lib/worblehat/db-password"; + database = lib.mkDefault "worblehat"; + }; + }; + + flask = { + TESTING = lib.mkDefault true; + DEBUG = lib.mkDefault true; + FLASK_ENV = lib.mkDefault "development"; + SECRET_KEY = lib.mkDefault "change-me"; + }; + + smtp = { + enabled = lib.mkDefault false; + host = lib.mkDefault "smtp.pvv.ntnu.no"; + port = lib.mkDefault 587; + username = lib.mkDefault "worblehat"; + password = lib.mkDefault "/var/lib/worblehat/smtp-password"; + from = lib.mkDefault "worblehat@pvv.ntnu.no"; + subject_prefix = lib.mkDefault "[Worblehat]"; + }; + + deadline_daemon = { + enabled = lib.mkDefault true; + dryrun = lib.mkDefault false; + warn_days_before_borrowing_deadline = lib.mkDefault [ + 5 + 1 + ]; + days_before_queue_position_expires = lib.mkDefault 14; + warn_days_before_expiring_queue_position_deadline = lib.mkDefault [ + 3 + 1 + ]; + }; + }; + + services.drumknotty.dibblerSettings.database = lib.mkIf cfg.createLocalDatabase { type = "postgresql"; postgresql.host = "/run/postgresql"; }; -- 2.54.0 From 7681089ceb2849c0dc38b9c2bb72e95d60c272d5 Mon Sep 17 00:00:00 2001 From: Vegard Bieker Matthey Date: Thu, 19 Mar 2026 16:09:24 +0100 Subject: [PATCH 06/10] use main branch for worblehat after merge --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 994af7c..0e45e9b 100644 --- a/flake.nix +++ b/flake.nix @@ -23,7 +23,7 @@ dibbler.url = "git+https://git.pvv.ntnu.no/Projects/dibbler.git?ref=main"; dibbler.inputs.nixpkgs.follows = "nixpkgs"; - worblehat.url = "git+https://git.pvv.ntnu.no/Projects/worblehat.git?ref=stable_deps"; + worblehat.url = "git+https://git.pvv.ntnu.no/Projects/worblehat.git?ref=main"; worblehat.inputs.nixpkgs.follows = "nixpkgs"; matrix-next.url = "github:dali99/nixos-matrix-modules/v0.8.0"; -- 2.54.0 From 793b6c163ac4cdd32e8276ead02a0b81c787f73c Mon Sep 17 00:00:00 2001 From: Vegard Bieker Matthey Date: Thu, 19 Mar 2026 17:20:46 +0100 Subject: [PATCH 07/10] flake.lock: bump dibbler and worblehat --- flake.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/flake.lock b/flake.lock index 77ea26f..974d16c 100644 --- a/flake.lock +++ b/flake.lock @@ -553,16 +553,16 @@ ] }, "locked": { - "lastModified": 1773832647, - "narHash": "sha256-VZVQDwqDmMGJcJ8WnD6Ug+DTV0b6S2DKHkdj3QvFO+w=", - "ref": "stable_deps", - "rev": "9bdaaf6c5180309b7810bb72f9e717a7c3b2a504", - "revCount": 102, + "lastModified": 1773932847, + "narHash": "sha256-IklIAdlonrmO8/lkDxNIVz9+ORL4pcVotMTxeyvxzoc=", + "ref": "main", + "rev": "0871a319f51d3cb0d1abb5b11edb768b39906d3f", + "revCount": 104, "type": "git", "url": "https://git.pvv.ntnu.no/Projects/worblehat.git" }, "original": { - "ref": "stable_deps", + "ref": "main", "type": "git", "url": "https://git.pvv.ntnu.no/Projects/worblehat.git" } -- 2.54.0 From d42711e1ed1f09597d5602ae148d711a480ba204 Mon Sep 17 00:00:00 2001 From: Vegard Bieker Matthey Date: Sat, 21 Mar 2026 14:34:29 +0100 Subject: [PATCH 08/10] attach with dibbler window selected --- modules/drumknotty.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/drumknotty.nix b/modules/drumknotty.nix index 8e8e142..ba0e08a 100644 --- a/modules/drumknotty.nix +++ b/modules/drumknotty.nix @@ -249,7 +249,7 @@ in users.users.drumknotty = { extraGroups = [ "lp" ]; shell = - (pkgs.writeShellScriptBin "login-shell" "${lib.getExe' cfg.screenPackage "screen"} -x ${cfg.screenSessionName}") + (pkgs.writeShellScriptBin "login-shell" "${lib.getExe' cfg.screenPackage "screen"} -x ${cfg.screenSessionName} -p dibbler") // { shellPath = "/bin/login-shell"; }; -- 2.54.0 From 372511f31ed3b2189cb37a6496153dadc8494177 Mon Sep 17 00:00:00 2001 From: h7x4 Date: Thu, 26 Mar 2026 16:46:02 +0900 Subject: [PATCH 09/10] modules/drumknotty: split into several parts This also fixes a few issues, such as enabling `createLocalDatabase` for multiple programs, and wraps all the screen logic within a screenrc file. Some assertions were also added to avoid some easy-to-make mistakes. --- flake.nix | 2 +- hosts/skrot/configuration.nix | 50 ++-- modules/drumknotty.nix | 379 ------------------------------- modules/drumknotty/default.nix | 198 ++++++++++++++++ modules/drumknotty/dibbler.nix | 113 +++++++++ modules/drumknotty/worblehat.nix | 209 +++++++++++++++++ 6 files changed, 551 insertions(+), 400 deletions(-) delete mode 100644 modules/drumknotty.nix create mode 100644 modules/drumknotty/default.nix create mode 100644 modules/drumknotty/dibbler.nix create mode 100644 modules/drumknotty/worblehat.nix diff --git a/flake.nix b/flake.nix index 0e45e9b..618a964 100644 --- a/flake.nix +++ b/flake.nix @@ -284,7 +284,7 @@ rsync-pull-targets = ./modules/rsync-pull-targets.nix; snakeoil-certs = ./modules/snakeoil-certs.nix; snappymail = ./modules/snappymail.nix; - drumknotty = ./modules/drumknotty.nix; + drumknotty = ./modules/drumknotty; }; devShells = forAllSystems (system: { diff --git a/hosts/skrot/configuration.nix b/hosts/skrot/configuration.nix index 473038b..fcc0000 100644 --- a/hosts/skrot/configuration.nix +++ b/hosts/skrot/configuration.nix @@ -40,30 +40,40 @@ services.drumknotty = { enable = true; kioskMode = true; - limitScreenWidth = 80; - limitScreenHeight = 42; - dibblerSettings = { - general.quit_allowed = false; - database = { - type = "postgresql"; - postgresql = { - username = "pvv_vv"; - dbname = "pvv_vv"; - host = "postgres.pvv.ntnu.no"; - password_file = config.sops.secrets."dibbler/postgresql/password".path; + screen = { + limitWidth = 80; + limitHeight = 42; + }; + + dibbler = { + enable = true; + settings = { + general.quit_allowed = false; + database = { + type = "postgresql"; + postgresql = { + username = "pvv_vv"; + dbname = "pvv_vv"; + host = "postgres.pvv.ntnu.no"; + password_file = config.sops.secrets."dibbler/postgresql/password".path; + }; }; }; }; - worblehatSettings = { - general.quit_allowed = false; - database = { - type = "postgresql"; - postgresql = { - username = "worblehat"; - dbname = "worblehat"; - host = "postgres.pvv.ntnu.no"; - password = config.sops.secrets."worblehat/postgresql/password".path; + + worblehat = { + enable = true; + settings = { + general.quit_allowed = false; + database = { + type = "postgresql"; + postgresql = { + username = "worblehat"; + dbname = "worblehat"; + host = "postgres.pvv.ntnu.no"; + password = config.sops.secrets."worblehat/postgresql/password".path; + }; }; }; }; diff --git a/modules/drumknotty.nix b/modules/drumknotty.nix deleted file mode 100644 index ba0e08a..0000000 --- a/modules/drumknotty.nix +++ /dev/null @@ -1,379 +0,0 @@ -{ - config, - pkgs, - lib, - ... -}: -let - cfg = config.services.drumknotty; - - format = pkgs.formats.toml { }; -in -{ - options.services.drumknotty = { - enable = lib.mkEnableOption "DrumknoTTY"; - - dibblerPackage = lib.mkPackageOption pkgs "dibbler" { }; - worblehatPackage = lib.mkPackageOption pkgs "worblehat" { }; - screenPackage = lib.mkPackageOption pkgs "screen" { }; - - screenSessionName = lib.mkOption { - type = lib.types.str; - default = "drumknotty"; - example = "myscreensessionname"; - description = '' - Sets the screen session name. - ''; - }; - - createLocalDatabase = lib.mkEnableOption "" // { - description = '' - Whether to set up a local postgres database automatically. - - ::: {.note} - You must set up postgres manually before enabling this option. - ::: - ''; - }; - - kioskMode = lib.mkEnableOption "" // { - description = '' - Whether to let dibbler take over the entire machine. - - This will restrict the machine to a single TTY and make the program unquittable. - You can still get access to PTYs via SSH and similar, if enabled. - ''; - }; - - limitScreenHeight = lib.mkOption { - type = with lib.types; nullOr ints.unsigned; - default = null; - example = 42; - description = '' - If set, limits the height of the screen dibbler uses to the given number of lines. - ''; - }; - - limitScreenWidth = lib.mkOption { - type = with lib.types; nullOr ints.unsigned; - default = null; - example = 80; - description = '' - If set, limits the width of the screen dibbler uses to the given number of columns. - ''; - }; - - dibblerSettings = lib.mkOption { - description = "Configuration for dibbler"; - default = { }; - type = lib.types.submodule { - freeformType = format.type; - }; - }; - - worblehatSettings = lib.mkOption { - description = "Configuration for worblehat"; - default = { }; - type = lib.types.submodule { - freeformType = format.type; - }; - }; - - deadline-daemon = { - enable = lib.mkEnableOption "" // { - description = '' - Whether to enable the worblehat deadline-daemon service, - which periodically checks for upcoming deadlines and notifies users. - - Note that this service is independent of the main worblehat service, - and must be enabled separately. - ''; - }; - - onCalendar = lib.mkOption { - type = lib.types.str; - description = '' - How often to trigger rendering the map, - in the format of a systemd timer onCalendar configuration. - - See {manpage}`systemd.timer(5)`. - ''; - default = "*-*-* 10:15:00"; - }; - }; - }; - - config = lib.mkIf cfg.enable ( - lib.mkMerge [ - { - environment.systemPackages = [ - cfg.dibblerPackage - cfg.worblehatPackage - ]; - - environment.etc."dibbler/dibbler.toml".source = format.generate "dibbler.toml" cfg.dibblerSettings; - environment.etc."worblehat/config.toml".source = - format.generate "worblehat-config.toml" cfg.worblehatSettings; - - users = { - users.drumknotty = { - group = "drumknotty"; - isNormalUser = true; - }; - groups.drumknotty = { }; - }; - - services.drumknotty.dibblerSettings = { - limits = { - low_credit_warning_limit = lib.mkDefault (-100); - user_recent_transaction_limit = lib.mkDefault 100; - }; - - printer = { - label_type = lib.mkDefault "62"; - label_rotate = lib.mkDefault false; - }; - }; - - services.drumknotty.worblehatSettings = { - logging = { - debug = lib.mkDefault true; - debug_sql = lib.mkDefault false; - }; - - database = { - type = lib.mkDefault "sqlite"; - sqlite.path = lib.mkDefault "./worblehat.sqlite"; - postgresql = { - host = lib.mkDefault "localhost"; - port = lib.mkDefault 5432; - username = lib.mkDefault "worblehat"; - password = lib.mkDefault "/var/lib/worblehat/db-password"; - database = lib.mkDefault "worblehat"; - }; - }; - - flask = { - TESTING = lib.mkDefault true; - DEBUG = lib.mkDefault true; - FLASK_ENV = lib.mkDefault "development"; - SECRET_KEY = lib.mkDefault "change-me"; - }; - - smtp = { - enabled = lib.mkDefault false; - host = lib.mkDefault "smtp.pvv.ntnu.no"; - port = lib.mkDefault 587; - username = lib.mkDefault "worblehat"; - password = lib.mkDefault "/var/lib/worblehat/smtp-password"; - from = lib.mkDefault "worblehat@pvv.ntnu.no"; - subject_prefix = lib.mkDefault "[Worblehat]"; - }; - - deadline_daemon = { - enabled = lib.mkDefault true; - dryrun = lib.mkDefault false; - warn_days_before_borrowing_deadline = lib.mkDefault [ - 5 - 1 - ]; - days_before_queue_position_expires = lib.mkDefault 14; - warn_days_before_expiring_queue_position_deadline = lib.mkDefault [ - 3 - 1 - ]; - }; - }; - - services.drumknotty.dibblerSettings.database = lib.mkIf cfg.createLocalDatabase { - type = "postgresql"; - postgresql.host = "/run/postgresql"; - }; - - services.postgresql = lib.mkIf cfg.createLocalDatabase { - ensureDatabases = [ - "dibbler" - "worblehat" - ]; - ensureUsers = [ - { - name = "drumknotty"; - ensureDBOwnership = true; - ensureClauses.login = true; - } - ]; - }; - - systemd.services.dibbler-setup-database = lib.mkIf cfg.createLocalDatabase { - description = "Dibbler database setup"; - wantedBy = [ "default.target" ]; - after = [ "postgresql.service" ]; - unitConfig = { - ConditionPathExists = "!/var/lib/dibbler/.db-setup-done"; - }; - serviceConfig = { - Type = "oneshot"; - ExecStart = "${lib.getExe cfg.dibblerPackage} --config /etc/dibbler/dibbler.toml create-db"; - ExecStartPost = "${lib.getExe' pkgs.coreutils "touch"} /var/lib/dibbler/.db-setup-done"; - StateDirectory = "dibbler"; - - User = "drumknotty"; - Group = "drumknotty"; - }; - }; - - systemd.services.worblehat-setup-database = lib.mkIf cfg.createLocalDatabase { - description = "Worblehat database setup"; - wantedBy = [ "default.target" ]; - after = [ "postgresql.service" ]; - unitConfig = { - ConditionPathExists = "!/var/lib/worblehat/.db-setup-done"; - }; - serviceConfig = { - Type = "oneshot"; - ExecStart = "${lib.getExe cfg.worblehatPackage} --config /etc/worblehat/config.toml create-db"; - ExecStartPost = "${lib.getExe' pkgs.coreutils "touch"} /var/lib/worblehat/.db-setup-done"; - StateDirectory = "worblehat"; - - User = "drumknotty"; - Group = "drumknotty"; - }; - }; - - } - (lib.mkIf cfg.kioskMode { - boot.kernelParams = [ - "console=tty1" - ]; - - users.users.drumknotty = { - extraGroups = [ "lp" ]; - shell = - (pkgs.writeShellScriptBin "login-shell" "${lib.getExe' cfg.screenPackage "screen"} -x ${cfg.screenSessionName} -p dibbler") - // { - shellPath = "/bin/login-shell"; - }; - }; - - services.drumknotty.dibblerSettings.general = { - quit_allowed = false; - stop_allowed = false; - }; - - services.drumknotty.worblehatSettings.general = { - quit_allowed = false; - stop_allowed = false; - }; - - systemd.services.drumknotty-screen-session = { - description = "Drumknotty Screen Session"; - wantedBy = [ - "default.target" - ]; - after = - if cfg.createLocalDatabase then - [ - "postgresql.service" - "dibbler-setup-database.service" - "worblehat-setup-database.service" - ] - else - [ - "network.target" - ]; - serviceConfig = - let - dibblerArgs = lib.cli.toCommandLineShellGNU { } { - config = "/etc/dibbler/dibbler.toml"; - }; - - worblehatArgs = lib.cli.toCommandLineShellGNU { } { - config = "/etc/worblehat/config.toml"; - }; - - in - { - Type = "forking"; - RemainAfterExit = false; - Restart = "always"; - RestartSec = "5s"; - SuccessExitStatus = 1; - - User = "drumknotty"; - Group = "drumknotty"; - - ExecStartPre = "-${lib.getExe' cfg.screenPackage "screen"} -X -S ${cfg.screenSessionName} kill"; - ExecStart = - let - screenArgs = lib.escapeShellArgs [ - # -dm creates the screen in detached mode without accessing it - "-dm" - - # Session name - "-S" - "${cfg.screenSessionName}" - - # Window name - "-t" - "dibbler" - - # Set optimal output mode instead of VT100 emulation - "-O" - - # Enable login mode, updates utmp entries - "-l" - ]; - - in - "${lib.getExe' cfg.screenPackage "screen"} ${screenArgs} ${lib.getExe cfg.dibblerPackage} ${dibblerArgs} loop"; - ExecStartPost = [ - "${lib.getExe' cfg.screenPackage "screen"} -S ${cfg.screenSessionName} -X screen -t worblehat ${lib.getExe cfg.worblehatPackage} ${worblehatArgs} cli" - ] - ++ lib.optionals (cfg.limitScreenWidth != null) [ - "${lib.getExe' cfg.screenPackage "screen"} -X -S ${cfg.screenSessionName} width ${toString cfg.limitScreenWidth}" - ] - ++ lib.optionals (cfg.limitScreenHeight != null) [ - "${lib.getExe' cfg.screenPackage "screen"} -X -S ${cfg.screenSessionName} height ${toString cfg.limitScreenHeight}" - ]; - }; - }; - - services.getty.autologinUser = "drumknotty"; - }) - (lib.mkIf cfg.deadline-daemon.enable { - systemd.timers.worblehat-deadline-daemon = { - description = "Worblehat Deadline Daemon"; - wantedBy = [ "timers.target" ]; - timerConfig = { - OnCalendar = cfg.deadline-daemon.onCalendar; - Persistent = true; - }; - }; - - systemd.services.worblehat-deadline-daemon = { - description = "Worblehat Deadline Daemon"; - wantedBy = [ "multi-user.target" ]; - after = [ "network.target" ]; - serviceConfig = { - Type = "oneshot"; - CPUSchedulingPolicy = "idle"; - IOSchedulingClass = "idle"; - - ExecStart = - let - worblehatArgs = lib.cli.toCommandLineShellGNU { } { - config = "/etc/worblehat/config.toml"; - }; - in - "${lib.getExe cfg.package} ${worblehatArgs} deadline-daemon"; - - User = "worblehat"; - Group = "worblehat"; - }; - }; - }) - - ] - ); - -} diff --git a/modules/drumknotty/default.nix b/modules/drumknotty/default.nix new file mode 100644 index 0000000..b2da9f7 --- /dev/null +++ b/modules/drumknotty/default.nix @@ -0,0 +1,198 @@ +{ + config, + pkgs, + lib, + ... +}: +let + cfg = config.services.drumknotty; +in +{ + imports = [ + ./dibbler.nix + ./worblehat.nix + ]; + + options.services.drumknotty = { + enable = lib.mkEnableOption "DrumknoTTY"; + + kioskMode = lib.mkEnableOption "" // { + description = '' + Whether to let dibbler take over the entire machine. + + This will restrict the machine to a single TTY and make the program unquittable. + You can still get access to PTYs via SSH and similar, if enabled. + ''; + }; + + screen = { + package = lib.mkPackageOption pkgs "screen" { }; + + sessionName = lib.mkOption { + type = lib.types.str; + default = "drumknotty"; + example = "myscreensessionname"; + description = '' + Sets the screen session name. + ''; + }; + + limitHeight = lib.mkOption { + type = with lib.types; nullOr ints.unsigned; + default = null; + example = 42; + description = '' + If set, limits the height of the screen dibbler uses to the given number of lines. + ''; + }; + + limitWidth = lib.mkOption { + type = with lib.types; nullOr ints.unsigned; + default = null; + example = 80; + description = '' + If set, limits the width of the screen dibbler uses to the given number of columns. + ''; + }; + }; + }; + + config = lib.mkIf cfg.enable { + assertions = [ + { + assertion = cfg.enable -> lib.any (b: b) [ + cfg.dibbler.enable + cfg.worblehat.enable + ]; + message = "DrumknoTTY must have at least one service enabled"; + } + ]; + + users = { + users.drumknotty = { + group = "drumknotty"; + extraGroups = [ "lp" ]; + isNormalUser = true; + + # TODO: make this display the error log or error message in case that + # the screen session service is bootlooping or otherwise off. + shell = + lib.mkIf cfg.kioskMode + (pkgs.writeShellScriptBin "login-shell" + "${lib.getExe' cfg.screen.package "screen"} -x ${cfg.screen.sessionName} -p dibbler" + // { + shellPath = "/bin/login-shell"; + }); + }; + groups.drumknotty = { }; + }; + + boot.kernelParams = lib.mkIf cfg.kioskMode [ + "console=tty1" + ]; + + services.getty.autologinUser = lib.mkIf cfg.kioskMode "drumknotty"; + + systemd.services.drumknotty-screen-session = lib.mkIf cfg.kioskMode { + description = "Drumknotty Screen Session"; + wantedBy = [ + "default.target" + ]; + after = + # TODO: this could be refined + if (cfg.dibbler.createLocalDatabase || cfg.worblehat.createLocalDatabase) then + [ + "postgresql.service" + "dibbler-setup-database.service" + "worblehat-setup-database.service" + ] + else + [ + "network.target" + ]; + + serviceConfig = { + Type = "forking"; + RemainAfterExit = false; + Restart = "always"; + RestartSec = "5s"; + SuccessExitStatus = 1; + + User = "drumknotty"; + Group = "drumknotty"; + + ExecStartPre = + let + screenArgs = lib.escapeShellArgs [ + # Send the specified command to a running screen session + "-X" + + # Session name + "-S" + "${cfg.screen.sessionName}" + + "kill" + ]; + in + "-${lib.getExe' cfg.screen.package "screen"} ${screenArgs}"; + + ExecStart = + let + screenrc = let + convertToFile = lines: lib.pipe lines [ + lib.concatLists + (lib.concatStringsSep "\n") + (pkgs.writeText "drumknotty-screenrc") + ]; + in convertToFile [ + (lib.optionals (cfg.screen.limitWidth != null) [ + "screen width ${toString cfg.screen.limitWidth}" + ]) + (lib.optionals (cfg.screen.limitHeight != null) [ + "screen height ${toString cfg.screen.limitHeight}" + ]) + + (let + dibblerArgs = lib.cli.toCommandLineShellGNU { } { + config = "/etc/dibbler/dibbler.toml"; + }; + in lib.optionals cfg.dibbler.enable [ + "screen -t worblehat ${lib.getExe cfg.dibbler.package} ${dibblerArgs} loop" + + ]) + + (let + worblehatArgs = lib.cli.toCommandLineShellGNU { } { + config = "/etc/worblehat/config.toml"; + }; + in lib.optionals cfg.worblehat.enable [ + "screen -t worblehat ${lib.getExe cfg.worblehat.package} ${worblehatArgs} cli" + ]) + + [ "select 0" ] + ]; + + screenArgs = lib.escapeShellArgs [ + # -dm creates the screen in detached mode without accessing it + "-dm" + + # Session name + "-S" + "${cfg.screen.sessionName}" + + # Set optimal output mode instead of VT100 emulation + "-O" + + # Enable login mode, updates utmp entries + "-l" + + # Config file path + "-c" + "${screenrc}" + ]; + in + "${lib.getExe' cfg.screen.package "screen"} ${screenArgs}"; + }; + }; + }; +} diff --git a/modules/drumknotty/dibbler.nix b/modules/drumknotty/dibbler.nix new file mode 100644 index 0000000..02d42a2 --- /dev/null +++ b/modules/drumknotty/dibbler.nix @@ -0,0 +1,113 @@ +{ + config, + pkgs, + lib, + ... +}: +let + mainCfg = config.services.drumknotty; + cfg = config.services.drumknotty.dibbler; + + format = pkgs.formats.toml { }; +in +{ + options.services.drumknotty.dibbler = { + enable = lib.mkEnableOption ""; + + package = lib.mkPackageOption pkgs "dibbler" { }; + + settings = lib.mkOption { + description = "Configuration for dibbler"; + default = { }; + type = lib.types.submodule { + freeformType = format.type; + }; + }; + + createLocalDatabase = lib.mkEnableOption "" // { + description = '' + Whether to set up a local postgres database automatically. + + ::: {.note} + You must set up postgres manually before enabling this option. + ::: + ''; + }; + }; + + config = lib.mkIf (mainCfg.enable && cfg.enable) { + assertions = [ + { + assertion = cfg.createLocalDatabase -> config.services.postgresql.enable; + message = "PostgreSQL must be enabled for dibbler to create a local database"; + } + ]; + + environment.systemPackages = [ cfg.package ]; + environment.etc."dibbler/dibbler.toml".source = format.generate "dibbler.toml" cfg.settings; + + services.drumknotty.dibbler.settings = { + limits = { + low_credit_warning_limit = lib.mkDefault (-100); + user_recent_transaction_limit = lib.mkDefault 100; + }; + + printer = { + label_type = lib.mkDefault "62"; + label_rotate = lib.mkDefault false; + }; + + database = { + type = lib.mkIf cfg.createLocalDatabase "postgresql"; + postgresql = { + username = lib.mkDefault "dibbler"; + dbname = lib.mkDefault "dibbler"; + + host = lib.mkIf cfg.createLocalDatabase "/run/postgresql"; + }; + }; + }; + + services.drumknotty.dibbler.settings.general = lib.mkIf mainCfg.kioskMode { + quit_allowed = false; + stop_allowed = false; + }; + + services.postgresql = lib.mkIf cfg.createLocalDatabase { + authentication = '' + local ${cfg.settings.database.postgresql.dbname} ${cfg.settings.database.postgresql.username} peer map=${cfg.settings.database.postgresql.username} + ''; + identMap = '' + ${cfg.settings.database.postgresql.username} drumknotty ${cfg.settings.database.postgresql.username} + ''; + ensureDatabases = [ cfg.settings.database.postgresql.dbname ]; + ensureUsers = [{ + name = cfg.settings.database.postgresql.username; + ensureDBOwnership = true; + ensureClauses.login = true; + }]; + }; + + systemd.services.dibbler-setup-database = lib.mkIf cfg.createLocalDatabase { + description = "Dibbler database setup"; + + wantedBy = [ "default.target" ]; + requiredBy = [ "drumknotty-screen-session.service" ]; + before = [ "drumknotty-screen-session.service" ]; + after = [ "postgresql.service" ]; + + unitConfig = { + ConditionPathExists = "!/var/lib/dibbler/.db-setup-done"; + }; + serviceConfig = { + Type = "oneshot"; + ExecStart = "${lib.getExe cfg.package} --config /etc/dibbler/dibbler.toml create-db"; + ExecStartPost = "${lib.getExe' pkgs.coreutils "touch"} /var/lib/dibbler/.db-setup-done"; + StateDirectory = "dibbler"; + + User = "drumknotty"; + Group = "drumknotty"; + }; + }; + }; +} diff --git a/modules/drumknotty/worblehat.nix b/modules/drumknotty/worblehat.nix new file mode 100644 index 0000000..b8e6a22 --- /dev/null +++ b/modules/drumknotty/worblehat.nix @@ -0,0 +1,209 @@ +{ + config, + pkgs, + lib, + ... +}: +let + mainCfg = config.services.drumknotty; + cfg = config.services.drumknotty.worblehat; + + format = pkgs.formats.toml { }; +in +{ + options.services.drumknotty.worblehat = { + enable = lib.mkEnableOption ""; + + package = lib.mkPackageOption pkgs "worblehat" { }; + + settings = lib.mkOption { + description = "Configuration for worblehat"; + default = { }; + type = lib.types.submodule { + freeformType = format.type; + }; + }; + + createLocalDatabase = lib.mkEnableOption "" // { + description = '' + Whether to set up a local postgres database automatically. + + ::: {.note} + You must set up postgres manually before enabling this option. + ::: + ''; + }; + + deadline-daemon = { + enable = lib.mkEnableOption "" // { + description = '' + Whether to enable the worblehat deadline-daemon service, + which periodically checks for upcoming deadlines and notifies users. + + Note that this service is independent of the main worblehat service, + and must be enabled separately. + ''; + }; + + onCalendar = lib.mkOption { + type = lib.types.str; + description = '' + How often to trigger rendering the map, + in the format of a systemd timer onCalendar configuration. + + See {manpage}`systemd.timer(5)`. + ''; + default = "*-*-* 10:15:00"; + }; + }; + }; + + config = lib.mkMerge [ + { + assertions = [ + { + assertion = cfg.createLocalDatabase -> config.services.postgresql.enable; + message = "PostgreSQL must be enabled for worblehat to create a local database"; + } + ]; + + # TODO: Retrieve defaults from the example config file in the project code. + services.drumknotty.worblehat.settings = { + logging = { + debug = lib.mkDefault true; + debug_sql = lib.mkDefault false; + }; + + database = { + type = lib.mkDefault "sqlite"; + sqlite.path = lib.mkDefault "./worblehat.sqlite"; + postgresql = { + host = lib.mkDefault "localhost"; + port = lib.mkDefault 5432; + username = lib.mkDefault "worblehat"; + password = lib.mkDefault "/var/lib/worblehat/db-password"; + database = lib.mkDefault "worblehat"; + }; + }; + + flask = { + TESTING = lib.mkDefault true; + DEBUG = lib.mkDefault true; + FLASK_ENV = lib.mkDefault "development"; + SECRET_KEY = lib.mkDefault "change-me"; + }; + + smtp = { + enabled = lib.mkDefault false; + host = lib.mkDefault "smtp.pvv.ntnu.no"; + port = lib.mkDefault 587; + username = lib.mkDefault "worblehat"; + password = lib.mkDefault "/var/lib/worblehat/smtp-password"; + from = lib.mkDefault "worblehat@pvv.ntnu.no"; + subject_prefix = lib.mkDefault "[Worblehat]"; + }; + + deadline_daemon = { + enabled = lib.mkDefault true; + dryrun = lib.mkDefault false; + warn_days_before_borrowing_deadline = lib.mkDefault [ + 5 + 1 + ]; + days_before_queue_position_expires = lib.mkDefault 14; + warn_days_before_expiring_queue_position_deadline = lib.mkDefault [ + 3 + 1 + ]; + }; + }; + } + + (lib.mkIf ((mainCfg.enable && cfg.enable) || cfg.deadline-daemon.enable) { + environment.systemPackages = [ cfg.package ]; + environment.etc."worblehat/config.toml".source = format.generate "worblehat-config.toml" cfg.settings; + }) + + (lib.mkIf (mainCfg.enable && cfg.enable) { + services.drumknotty.worblehat.settings.general = lib.mkIf mainCfg.kioskMode { + quit_allowed = false; + stop_allowed = false; + }; + + services.drumknotty.worblehat.settings.database = lib.mkIf cfg.createLocalDatabase { + type = "postgresql"; + postgresql.host = "/run/postgresql"; + }; + + services.postgresql = lib.mkIf cfg.createLocalDatabase { + authentication = '' + local ${cfg.settings.database.postgresql.database} ${cfg.settings.database.postgresql.username} peer map=${cfg.settings.database.postgresql.username} + ''; + identMap = '' + ${cfg.settings.database.postgresql.username} drumknotty ${cfg.settings.database.postgresql.username} + ''; + ensureDatabases = [ cfg.settings.database.postgresql.database ]; + ensureUsers = [{ + name = cfg.settings.database.postgresql.username; + ensureDBOwnership = true; + ensureClauses.login = true; + }]; + }; + + systemd.services.worblehat-setup-database = lib.mkIf cfg.createLocalDatabase { + description = "Worblehat database setup"; + + wantedBy = [ "default.target" ]; + requiredBy = [ "drumknotty-screen-session.service" ]; + before = [ "drumknotty-screen-session.service" ]; + after = [ "postgresql.service" ]; + + unitConfig = { + ConditionPathExists = "!/var/lib/worblehat/.db-setup-done"; + }; + serviceConfig = { + Type = "oneshot"; + ExecStart = "${lib.getExe cfg.package} --config /etc/worblehat/config.toml create-db"; + ExecStartPost = "${lib.getExe' pkgs.coreutils "touch"} /var/lib/worblehat/.db-setup-done"; + StateDirectory = "worblehat"; + + User = "drumknotty"; + Group = "drumknotty"; + }; + }; + }) + + (lib.mkIf cfg.deadline-daemon.enable { + systemd.timers.worblehat-deadline-daemon = lib.mkIf cfg.deadline-daemon.enable { + description = "Worblehat Deadline Daemon"; + wantedBy = [ "timers.target" ]; + timerConfig = { + OnCalendar = cfg.deadline-daemon.onCalendar; + Persistent = true; + }; + }; + + systemd.services.worblehat-deadline-daemon = lib.mkIf cfg.deadline-daemon.enable { + description = "Worblehat Deadline Daemon"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + serviceConfig = { + Type = "oneshot"; + CPUSchedulingPolicy = "idle"; + IOSchedulingClass = "idle"; + + ExecStart = + let + worblehatArgs = lib.cli.toCommandLineShellGNU { } { + config = "/etc/worblehat/config.toml"; + }; + in + "${lib.getExe cfg.package} ${worblehatArgs} deadline-daemon"; + + User = "drumknotty"; + Group = "drumknotty"; + }; + }; + }) + ]; +} -- 2.54.0 From d783ea15f75bd82f6585170405dfcc37c6a96328 Mon Sep 17 00:00:00 2001 From: Vegard Bieker Matthey Date: Thu, 26 Mar 2026 11:46:41 +0100 Subject: [PATCH 10/10] remove unused modules dibbler and worblehat --- flake.nix | 2 -- 1 file changed, 2 deletions(-) diff --git a/flake.nix b/flake.nix index 618a964..5b677e8 100644 --- a/flake.nix +++ b/flake.nix @@ -218,8 +218,6 @@ modules = [ self.nixosModules.drumknotty inputs.disko.nixosModules.disko - inputs.dibbler.nixosModules.default - inputs.worblehat.nixosModules.default ]; overlays = [ -- 2.54.0