diff --git a/README.md b/README.md index 2379e42..ddae5f2 100644 --- a/README.md +++ b/README.md @@ -43,17 +43,38 @@ Add the module to your `flake.nix`: Add this to your `configuration.nix` file ```nix -environment.etc."eintopf-radar-sync-secrets.yml".text = '' -EINTOPF_AUTHORIZATION_TOKEN=foobar23 +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 = { - EINTOPF_URL = "https://karlsunruh.eintopf.info"; - RADAR_GROUP_ID = "436012"; + CHECK_INTERVAL_DAYS = 7; + QUOTA_WARNING_THRESHOLD_PERCENT = 80; }; - secrets = [ /etc/mail-quota-warning-secrets.yml ]; + secretFile = "/etc/mail-quota-warning-secrets.yml"; }; ``` @@ -64,8 +85,7 @@ Replace setting variables according to your setup. ``` cd mail-quota-warning nix develop -export EINTOPF_URL = "https://karlsunruh.eintopf.info" -export EINTOPF_AUTHORIZATION_TOKEN = "secret key" -export RADAR_GROUP_ID = "436012" +export CHECK_INTERVAL_DAYS=7 +export QUOTA_WARNING_THRESHOLD_PERCENT=80 nix run ``` diff --git a/config.yml.example b/config.yml.example index 9b3f4ff..dfc6526 100644 --- a/config.yml.example +++ b/config.yml.example @@ -1,6 +1,5 @@ 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 5b2eea3..26fb045 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): +def send_warning(config, triggered_accounts, all_quotas, threshold): mail_cfg = config["mail"] # Create subject based on number of accounts @@ -199,7 +199,7 @@ def send_warning(config, triggered_accounts, all_quotas): 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("The following mailboxes have exceeded the quota threshold:") + body_lines.append(f"The following mailboxes have exceeded the quota threshold ({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 = config.get("check_interval_days", 7) - threshold = config.get("quota_warning_threshold_percent", 80) + 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) # 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) + send_warning(config, triggered_accounts, all_quotas, threshold) save_state(state) diff --git a/module.nix b/module.nix index 1e6efe1..3de0c6c 100644 --- a/module.nix +++ b/module.nix @@ -1,139 +1,157 @@ -{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. - ''; - }; + 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 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. - ''; - }; - }; + 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. + ''; + }; }; - default = {}; - description = '' - Extra options which should be used by the Radar sync script. - ''; - example = lib.literalExpression '' - { - EINTOPF_URL = "eintopf.info"; - RADAR_GROUP_ID = "436012"; - } - ''; }; - - 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 = [ ]; - }; - - 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"; - } // 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; - }; + 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; + } + ''; }; - systemd.timers.mail-quota-warning = { - timerConfig = { - OnCalendar = [ - "" - cfg.interval - ]; - }; - wantedBy = [ "timers.target" ]; + 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. + ''; + }; + + 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)`. + ''; }; }; + }; - meta = { - maintainers = with lib.maintainers; [ onny ]; + 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 + ]; + }; + wantedBy = [ "timers.target" ]; + }; + }; + + meta = { + maintainers = with lib.maintainers; [ onny ]; + }; + +} diff --git a/vm-eintopf.nix b/vm-eintopf.nix deleted file mode 100644 index f4933d6..0000000 --- a/vm-eintopf.nix +++ /dev/null @@ -1,29 +0,0 @@ -{ 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 new file mode 100644 index 0000000..93bc22e --- /dev/null +++ b/vm-mail-quota-warning.py @@ -0,0 +1,38 @@ +{ 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"; + }; + +}