diff --git a/README.md b/README.md index ddae5f2..2379e42 100644 --- a/README.md +++ b/README.md @@ -43,38 +43,17 @@ Add the module to your `flake.nix`: Add this to your `configuration.nix` file ```nix -environment.etc."mail-quota-warning-secrets.yml".text = '' -accounts: - - name: Sales - imap_server: mail.example.com - imap_port: 993 - username: sales@example.com - password: secret - - - name: Support - imap_server: mail.example.com - imap_port: 993 - username: support@example.com - password: secret - -mail: - smtp_server: mail.example.com - smtp_port: 587 - smtp_username: monitoring@example.com - smtp_password: secret - from_address: monitoring@example.com - recipients: - - admin1@example.com - - admin2@example.com +environment.etc."eintopf-radar-sync-secrets.yml".text = '' +EINTOPF_AUTHORIZATION_TOKEN=foobar23 ''; services.mail-quota-warning = { enable = true; settings = { - CHECK_INTERVAL_DAYS = 7; - QUOTA_WARNING_THRESHOLD_PERCENT = 80; + EINTOPF_URL = "https://karlsunruh.eintopf.info"; + RADAR_GROUP_ID = "436012"; }; - secretFile = "/etc/mail-quota-warning-secrets.yml"; + secrets = [ /etc/mail-quota-warning-secrets.yml ]; }; ``` @@ -85,7 +64,8 @@ Replace setting variables according to your setup. ``` cd mail-quota-warning nix develop -export CHECK_INTERVAL_DAYS=7 -export QUOTA_WARNING_THRESHOLD_PERCENT=80 +export EINTOPF_URL = "https://karlsunruh.eintopf.info" +export EINTOPF_AUTHORIZATION_TOKEN = "secret key" +export RADAR_GROUP_ID = "436012" nix run ``` diff --git a/config.yml.example b/config.yml.example index dfc6526..9b3f4ff 100644 --- a/config.yml.example +++ b/config.yml.example @@ -1,5 +1,6 @@ check_interval_days: 7 # Minimum days between warnings for same account quota_warning_threshold_percent: 80 +working_dir: /var/lib/mail-quota-warning accounts: - name: Sales diff --git a/mail-quota-warning.py b/mail-quota-warning.py index 26fb045..5b2eea3 100644 --- a/mail-quota-warning.py +++ b/mail-quota-warning.py @@ -181,7 +181,7 @@ def should_send_warning(state, account_name, interval_days): last_sent = datetime.fromisoformat(last_sent_str) return datetime.now() - last_sent >= timedelta(days=interval_days) -def send_warning(config, triggered_accounts, all_quotas, threshold): +def send_warning(config, triggered_accounts, all_quotas): mail_cfg = config["mail"] # Create subject based on number of accounts @@ -199,7 +199,7 @@ def send_warning(config, triggered_accounts, all_quotas, threshold): body_lines.append(f"The mailbox for account '{account_name}' has reached {quota_info['percent_used']:.1f}% of its quota.") body_lines.append(f"Usage: {format_bytes(quota_info['used_kb'])} of {format_bytes(quota_info['limit_kb'])}") else: - body_lines.append(f"The following mailboxes have exceeded the quota threshold ({threshold}%):") + body_lines.append("The following mailboxes have exceeded the quota threshold:") body_lines.append("") for account_name, quota_info in triggered_accounts.items(): body_lines.append(f"• {account_name}: {quota_info['percent_used']:.1f}% ({format_bytes(quota_info['used_kb'])} of {format_bytes(quota_info['limit_kb'])})") @@ -263,8 +263,8 @@ def main(): args = parse_args() config = load_config(args.config) state = load_state() - interval_days = get_config_value(config, "CHECK_INTERVAL_DAYS", "check_interval_days", 7, int) - threshold = get_config_value(config, "QUOTA_WARNING_THRESHOLD_PERCENT", "quota_warning_threshold_percent", 80, int) + interval_days = config.get("check_interval_days", 7) + threshold = config.get("quota_warning_threshold_percent", 80) # For thread-safe state updates state_lock = threading.Lock() @@ -287,7 +287,7 @@ def main(): # Send consolidated warning email if any accounts triggered if triggered_accounts: - send_warning(config, triggered_accounts, all_quotas, threshold) + send_warning(config, triggered_accounts, all_quotas) save_state(state) diff --git a/module.nix b/module.nix index 3de0c6c..1e6efe1 100644 --- a/module.nix +++ b/module.nix @@ -1,157 +1,139 @@ -{ - config, - lib, - pkgs, - ... -}: +{config, lib, pkgs, ...}: let cfg = config.services.mail-quota-warning; -in -{ +in + { - options = { - services.mail-quota-warning = { + options = { + services.mail-quota-warning = { - enable = lib.mkOption { - type = lib.types.bool; - default = false; - description = '' - Enable mail-quota-warning daemon. - ''; - }; - - settings = lib.mkOption { - type = lib.types.submodule { - freeformType = with lib.types; attrsOf anything; - options = { - CHECK_INTERVAL_DAYS = lib.mkOption { - default = 7; - type = lib.types.int; - description = '' - Interval of days in which a warning message will be - delivered. - ''; - }; - QUOTA_WARNING_THRESHOLD_PERCENT = lib.mkOption { - default = 80; - type = lib.types.int; - description = '' - Threshold of used mailbox space in percent after which - a warning message will be delivered. - ''; - }; - }; + enable = lib.mkOption { + type = lib.types.bool; + default = false; + description = '' + Enable mail-quota-warning daemon. + ''; }; - default = { }; - description = '' - Extra options which should be used by the mailbox quota warning script. - ''; - example = lib.literalExpression '' - { - CHECK_INTERVAL_DAYS = 7; - QUOTA_WARNING_THRESHOLD_PERCENT = 80; - } - ''; - }; - secretFile = lib.mkOption { - type = lib.types.nullOr (lib.types.pathWith { - inStore = false; - absolute = true; - }); - default = null; - example = "/run/keys/mail-quota-warning-secrets"; - description = '' - A YAML file containing secrets, see example config file - in the repository. - ''; - }; + settings = lib.mkOption { + type = lib.types.submodule { + freeformType = with lib.types; attrsOf types.str; + options = { + CHECK_INTERVAL_DAYS = lib.mkOption { + default = ""; + type = lib.types.str; + description = '' + Base URL of the target Eintopf host. + ''; + }; + QUOTA_WARNING_THRESHOLD_PERCENT = lib.mkOption { + default = ""; + type = lib.types.str; + description = '' + Radar group ID which events to sync. + ''; + }; + }; + }; + default = {}; + description = '' + Extra options which should be used by the Radar sync script. + ''; + example = lib.literalExpression '' + { + EINTOPF_URL = "eintopf.info"; + RADAR_GROUP_ID = "436012"; + } + ''; + }; - interval = lib.mkOption { - type = lib.types.str; - default = "*:00,30:00"; - description = '' - How often we run the sync. Default is half an hour. + secretFile = lib.mkOption { + type = with lib.types; listOf path; + description = '' + A list of files containing the various secrets. Should be in the + format expected by systemd's `EnvironmentFile` directory. + ''; + default = [ ]; + }; - The format is described in - {manpage}`systemd.time(7)`. - ''; - }; + interval = lib.mkOption { + type = lib.types.str; + default = "*:00,30:00"; + description = '' + How often we run the sync. Default is half an hour. + + The format is described in + {manpage}`systemd.time(7)`. + ''; + }; - }; - }; - - config = lib.mkIf cfg.enable { - - systemd.services."mail-quota-warning" = { - description = "mail-quota-warning script"; - after = [ "network.target" ]; - wants = [ "network-online.target" ]; - environment = { - PYTHONUNBUFFERED = "1"; - } - // lib.mapAttrs (_: v: toString v) cfg.settings; - serviceConfig = { - Type = "simple"; - LoadCredential = lib.optionalString (cfg.secretFile != null) "secrets.yaml:${cfg.secretFile}"; - ExecStart = "${lib.getExe pkgs.mail-quota-warning}${lib.optionalString (cfg.secretFile != null) " --config \${CREDENTIALS_DIRECTORY}/secrets.yaml"; - WorkingDirectory = "%S/mail-quota-warning"; - StateDirectory = "mail-quota-warning"; - - # hardening - AmbientCapabilities = ""; - CapabilityBoundingSet = ""; - DevicePolicy = "closed"; - DynamicUser = true; - LockPersonality = true; - MemoryDenyWriteExecute = true; - NoNewPrivileges = true; - PrivateDevices = true; - PrivateTmp = true; - PrivateUsers = true; - ProcSubset = "pid"; - ProtectClock = true; - ProtectControlGroups = true; - ProtectHome = true; - ProtectHostname = true; - ProtectKernelLogs = true; - ProtectKernelModules = true; - ProtectKernelTunables = true; - ProtectProc = "invisible"; - ProtectSystem = "strict"; - RemoveIPC = true; - RestrictAddressFamilies = [ - "AF_INET" - "AF_INET6" - ]; - RestrictNamespaces = true; - RestrictRealtime = true; - RestrictSUIDSGID = true; - SystemCallArchitectures = "native"; - SystemCallFilter = [ - "@system-service" - "~@privileged" - ]; - UMask = "0077"; }; }; - systemd.timers.mail-quota-warning = { - timerConfig = { - OnCalendar = [ - "" - cfg.interval - ]; + config = lib.mkIf cfg.enable { + + systemd.services."mail-quota-warning" = { + description = "mail-quota-warning script"; + after = [ "network.target" ]; + wants = [ "network-online.target" ]; + environment = { + PYTHONUNBUFFERED = "1"; + } // cfg.settings; + serviceConfig = { + Type = "simple"; + ExecStart = lib.getExe pkgs.mail-quota-warning; + + # hardening + AmbientCapabilities = ""; + CapabilityBoundingSet = "" ; + DevicePolicy = "closed"; + DynamicUser = true; + LockPersonality = true; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; + PrivateDevices = true; + PrivateTmp = true; + PrivateUsers = true; + ProcSubset = "pid"; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + ProtectSystem = "strict"; + RemoveIPC = true; + RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + SystemCallFilter = [ "@system-service" "~@privileged" ]; + UMask = "0077"; + } // lib.optionalAttrs (cfg.secretFile != [ ]) { + EnvironmentFile = cfg.secretFile; + }; }; - wantedBy = [ "timers.target" ]; + + systemd.timers.mail-quota-warning = { + timerConfig = { + OnCalendar = [ + "" + cfg.interval + ]; + }; + wantedBy = [ "timers.target" ]; + }; + }; - }; + meta = { + maintainers = with lib.maintainers; [ onny ]; + }; - meta = { - maintainers = with lib.maintainers; [ onny ]; - }; + } -} diff --git a/vm-eintopf.nix b/vm-eintopf.nix new file mode 100644 index 0000000..f4933d6 --- /dev/null +++ b/vm-eintopf.nix @@ -0,0 +1,29 @@ +{ pkgs, ... }: +let + + template-karlsunruh = pkgs.stdenv.mkDerivation { + name = "karlsunruh"; + src = pkgs.fetchgit { + url = "https://git.project-insanity.org/onny/eintopf-karlsunruh.git"; + rev = "0c2a36574260da70da80b379d7475af7b29849c9"; + hash = "sha256-GPKlqpztl4INqVyz/4y/vVrkDPHA3rIxtUZB9LNZ96c="; + }; + dontBuild = true; + installPhase = '' + cp -r . $out/ + ''; + }; + +in +{ + + services.eintopf = { + enable = true; + settings = { + EINTOPF_THEMES = "eintopf,${template-karlsunruh}"; + EINTOPF_ADMIN_PASSWORD = "foobar23"; + EINTOPF_ADMIN_EMAIL = "onny@project-insanity.org"; + }; + }; + +} diff --git a/vm-mail-quota-warning.py b/vm-mail-quota-warning.py deleted file mode 100644 index 93bc22e..0000000 --- a/vm-mail-quota-warning.py +++ /dev/null @@ -1,38 +0,0 @@ -{ pkgs, ... }: -{ - - environment.etc."mail-quota-warning-secrets.yml".text = '' - accounts: - - name: Sales - imap_server: mail.example.com - imap_port: 993 - username: sales@example.com - password: secret - - - name: Support - imap_server: mail.example.com - imap_port: 993 - username: support@example.com - password: secret - - mail: - smtp_server: mail.example.com - smtp_port: 587 - smtp_username: monitoring@example.com - smtp_password: secret - from_address: monitoring@example.com - recipients: - - admin1@example.com - - admin2@example.com - ''; - - services.mail-quota-warning = { - enable = true; - settings = { - CHECK_INTERVAL_DAYS = 7; - QUOTA_WARNING_THRESHOLD_PERCENT = 80; - }; - secretFile = "/etc/mail-quota-warning-secrets.yml"; - }; - -}