diff --git a/README.md b/README.md index a3eab3b..2379e42 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ -# eintopf-radar-sync -Small script to sync events of an radar.quad.net group to a specific Eintopf -instance. +# mail-quota-warning + +Small script to check a configured list of IMAP accounts for mailbox quota and send +a warning mail in case a specific threashold is exceeded. ## Installation @@ -11,7 +12,7 @@ Add the module to your `flake.nix`: ```nix { inputs = { - eintopf-radar-sync.url = "git+https://git.project-insanity.org/onny/eintopf-radar-sync.git"; + mail-quota-warning.url = "git+https://git.project-insanity.org/onny/mail-quota-warning.git"; [...] }; @@ -21,12 +22,12 @@ Add the module to your `flake.nix`: system = "x86_64-linux"; specialArgs.inputs = inputs; modules = [ - inputs.eintopf-radar-sync.nixosModule + inputs.mail-quota-warning.nixosModule ({ pkgs, ... }:{ nixpkgs.overlays = [ - inputs.eintopf-radar-sync.overlay + inputs.mail-quota-warning.overlay ]; }) @@ -42,30 +43,26 @@ Add the module to your `flake.nix`: Add this to your `configuration.nix` file ```nix -environment.etc."eintopf-radar-sync-secrets".text = '' +environment.etc."eintopf-radar-sync-secrets.yml".text = '' EINTOPF_AUTHORIZATION_TOKEN=foobar23 ''; -services.eintopf-radar-sync = { +services.mail-quota-warning = { enable = true; settings = { EINTOPF_URL = "https://karlsunruh.eintopf.info"; RADAR_GROUP_ID = "436012"; }; - secrets = [ /etc/eintopf-radar-sync-secrets ]; + secrets = [ /etc/mail-quota-warning-secrets.yml ]; }; ``` Replace setting variables according to your setup. -Get the authorization token through login request in the Eintopf -Swagger api interface, for example -https://karlsunruh.project-insanity.org/api/v1/swagger#/auth/login - ### From source ``` -cd eintopf-radar-sync +cd mail-quota-warning nix develop export EINTOPF_URL = "https://karlsunruh.eintopf.info" export EINTOPF_AUTHORIZATION_TOKEN = "secret key" 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 8843d74..5b2eea3 100644 --- a/mail-quota-warning.py +++ b/mail-quota-warning.py @@ -5,30 +5,55 @@ import yaml import json import os import threading +import argparse from email.mime.text import MIMEText from datetime import datetime, timedelta CONFIG_FILE = "config.yml" STATE_FILE = "quota_state.json" -# TODO: -# - make working dir configurable for state file -# - optional read config.yml from command line argument -# - note in warning mail that it exceeded XX% quota threashold -# - fix list summary mark all accounts which are critical +# TODO +# - load config from file +# - override with env vars -def load_config(): - with open(CONFIG_FILE, "r") as f: +def get_config_value(config, env_var, config_key, default_value, value_type=int): + """Get configuration value from environment variable or config file, with fallback to default""" + env_value = os.environ.get(env_var) + if env_value is not None: + try: + return value_type(env_value) + except ValueError: + log(f"Invalid value for {env_var}: {env_value}, using config/default") + + return config.get(config_key, default_value) + +def parse_args(): + parser = argparse.ArgumentParser(description="Email quota monitoring script") + parser.add_argument( + "--config", + default="config.yml", + help="Path to config.yml file (default: config.yml in current directory)" + ) + return parser.parse_args() + +def load_config(config_file): + if not os.path.exists(config_file): + log(f"Config file not found: {config_file}") + raise FileNotFoundError(f"Config file not found: {config_file}") + + with open(config_file, "r") as f: return yaml.safe_load(f) def load_state(): - if os.path.exists(STATE_FILE): - with open(STATE_FILE, "r") as f: + state_file = "quota_state.json" + if os.path.exists(state_file): + with open(state_file, "r") as f: return json.load(f) return {} def save_state(state): - with open(STATE_FILE, "w") as f: + state_file = "quota_state.json" + with open(state_file, "w") as f: json.dump(state, f, indent=2) def log(msg): @@ -235,7 +260,8 @@ def check_account_quota(account, config, state, threshold, interval_days): return None, quota_info def main(): - config = load_config() + 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) diff --git a/module.nix b/module.nix index 375b462..1ae4fb4 100644 --- a/module.nix +++ b/module.nix @@ -21,14 +21,14 @@ in type = lib.types.submodule { freeformType = with lib.types; attrsOf types.str; options = { - EINTOPF_URL = lib.mkOption { + CHECK_INTERVAL_DAYS = lib.mkOption { default = ""; type = lib.types.str; description = '' Base URL of the target Eintopf host. ''; }; - RADAR_GROUP_ID = lib.mkOption { + QUOTA_WARNING_THRESHOLD_PERCENT = lib.mkOption { default = ""; type = lib.types.str; description = '' @@ -49,7 +49,7 @@ in ''; }; - secrets = lib.mkOption { + secretFile = lib.mkOption { type = with lib.types; listOf path; description = '' A list of files containing the various secrets. Should be in the