Compare commits
10 commits
62676eaaa3
...
539d81cf46
| Author | SHA1 | Date | |
|---|---|---|---|
| 539d81cf46 | |||
| 3b4176fa0a | |||
| 74502b0c62 | |||
| 8bd5cace11 | |||
| 805e5be3b1 | |||
| e658def798 | |||
| 9a76430181 | |||
| 80bb80632d | |||
| ee7b81d07a | |||
| ccbfeb097b |
6 changed files with 210 additions and 164 deletions
36
README.md
36
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
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
260
module.nix
260
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 ];
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
38
vm-mail-quota-warning.py
Normal file
38
vm-mail-quota-warning.py
Normal file
|
|
@ -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";
|
||||
};
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue