From 76ae6b21ef59a39fd2874a2077171c142ce1f245 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20B=C3=B6hme?= Date: Fri, 6 Jun 2025 16:04:15 +0200 Subject: [PATCH 1/4] feat: add comprehensive Sieve email filtering rules feat: add rules.sieve feat: add German translations to email filtering rules fix: sieve script regex errors --- machines/moritz-server/mail-server.nix | 1 + machines/moritz-server/rules.sieve | 243 +++++++++++++++++++++++++ 2 files changed, 244 insertions(+) create mode 100644 machines/moritz-server/rules.sieve diff --git a/machines/moritz-server/mail-server.nix b/machines/moritz-server/mail-server.nix index adc44a2..44e327b 100644 --- a/machines/moritz-server/mail-server.nix +++ b/machines/moritz-server/mail-server.nix @@ -27,6 +27,7 @@ "main@moritz.place" = { hashedPasswordFile = config.clan.core.vars.generators.mail-server.files.main-password-hash.path; aliases = ["@moritz.place"]; + sieveScript = builtins.readFile ./rules.sieve; }; }; diff --git a/machines/moritz-server/rules.sieve b/machines/moritz-server/rules.sieve new file mode 100644 index 0000000..5adb05c --- /dev/null +++ b/machines/moritz-server/rules.sieve @@ -0,0 +1,243 @@ +require ["body", "comparator-i;ascii-numeric", "comparator-i;ascii-casemap", "copy", "date", "duplicate", "envelope", "fileinto", "imap4flags", "index", "mailbox", "regex", "reject", "relational", "subaddress", "vacation", "variables"]; + +# ----------------------------------------------------------------------- +# CONFIGURATION +# ----------------------------------------------------------------------- + +# Folder Structure (HEY-inspired) +# INBOX - Primary inbox for important mail (HEY's "Imbox") +# INBOX.Feed - Newsletters, updates, marketing (HEY's "The Feed") +# INBOX.Papertrail - Receipts, confirmations, records (HEY's "Paper Trail") +# INBOX.Notifications - Time-sensitive notifications and alerts +# Archive - Storage for processed emails + +# Thunderbird Labels +# $label1 = 1 Important +# $label2 = 2 Work +# $label3 = 3 Personal +# $label4 = 4 To Do +# $label5 = 5 Later + +# Custom Tags +# "delivery" - Package tracking and delivery notifications +# "finances" - Financial transactions and statements +# "tickets" - Event tickets and registrations +# "travel" - Travel confirmations and itineraries +# "ads" - Promotional content +# "newsletter" - Subscription content +# "security" - Security alerts and notifications + +# ----------------------------------------------------------------------- +# HELPER FUNCTIONS +# ----------------------------------------------------------------------- + +# Function to process security and authentication emails +if allof( + # Security and authentication patterns (English and German) + anyof( + header :comparator "i;ascii-casemap" :regex "Subject" "\\b(PIN|Verify|Verification|Confirm|One-Time|Single(-|\\s)Use)\\b.*?(passcode|number|code.*$)", + header :comparator "i;ascii-casemap" :regex "Subject" "\\b(PIN|Bestätigen|Bestätigung|Verifizieren|Verifizierung|Einmal|Einmalig)\\b.*?(Passwort|Kennwort|Nummer|Sicherheitscode|Code.*$)", + header :comparator "i;ascii-casemap" :regex "Subject" "^.*new.*(sign(in|-in|ed)|(log(in|-in|ged)))", + header :comparator "i;ascii-casemap" :regex "Subject" "^.*neue.*(Anmeldung|Einloggen)", + header :comparator "i;ascii-casemap" :regex "Subject" "^.*verify.*(device|email|phone)", + header :comparator "i;ascii-casemap" :regex "Subject" "^.*(verifiziere|bestätige).*(Gerät|E-Mail|Telefon)", + header :comparator "i;ascii-casemap" :regex "Subject" "^.*email.*modified", + header :comparator "i;ascii-casemap" :regex "Subject" "^.*(E-Mail|Mail).*ändert", + header :comparator "i;ascii-casemap" :regex "Subject" "^.*(computer|phone|device).*(added)", + header :comparator "i;ascii-casemap" :regex "Subject" "^.*(Computer|Telefon|Gerät).*(hinzugefügt)", + header :comparator "i;ascii-casemap" :regex "Subject" "^.*2FA.*(turned on)", + header :comparator "i;ascii-casemap" :regex "Subject" "^.*2FA.*(aktiviert|eingeschaltet)", + header :comparator "i;ascii-casemap" :regex "Subject" "^.*confirm.*(you)", + header :comparator "i;ascii-casemap" :regex "Subject" "^.*bestätige.*(Sie|dir|dich)", + header :comparator "i;ascii-casemap" :regex "Subject" "^.*you.*((log|sign)\\s?-?\\s?in).*$", + header :comparator "i;ascii-casemap" :regex "Subject" "^.*(Sie|du).*(haben sich|hast dich).*((angemeldet|eingeloggt)).*$", + header :comparator "i;ascii-casemap" :regex "Subject" "^.*sign.*in", + header :comparator "i;ascii-casemap" :regex "Subject" "^.*melde.*an", + header :comparator "i;ascii-casemap" :regex "Subject" "^.*logge.*ein" + ) +) { + fileinto "INBOX.Notifications"; + setflag "security"; + stop; +} + +# ----------------------------------------------------------------------- +# THE IMBOX (IMPORTANT MAIL) +# ----------------------------------------------------------------------- + +# Critical alerts that should be seen immediately +if anyof( + header :comparator "i;ascii-casemap" :regex "Subject" "^(.*dependabot|.*security|.*advisor).*$", + header :comparator "i;ascii-casemap" :regex "Subject" "^(.*dependabot|.*sicherheit|.*berater).*$", + header :comparator "i;ascii-casemap" :regex "Subject" "\\b(uptime|downtime|outage|alert|PagerDuty).*\\b", + header :comparator "i;ascii-casemap" :regex "Subject" "\\b(verfügbarkeit|ausfall|störung|warnung|alarm).*\\b", + header :regex "Subject" "^\\[URGENT\\] Certificate (discovered|expiration) for", + header :regex "Subject" "^Certificate (discovered|expiration) for", + header :regex "Subject" "^\\[DRINGEND\\] Zertifikat (entdeckt|Ablauf|läuft ab) für", + header :regex "Subject" "^Zertifikat (entdeckt|Ablauf|läuft ab) für" +) { + fileinto "INBOX"; + setflag "$label1"; + setflag "security"; + stop; +} + +# Important appointments and events +if anyof( + header :comparator "i;ascii-casemap" :regex "Subject" "^.*upcoming (appointment|visit).*", + header :comparator "i;ascii-casemap" :regex "Subject" "^.*bevorstehend(er|e|es|en) (Termin|Besuch).*", + header :comparator "i;ascii-casemap" :regex "Subject" "^.*anstehend(er|e|es|en) (Termin|Besuch).*", + header :comparator "i;ascii-casemap" :regex "Subject" "^.*(meeting|visit|appointment|event).*\\b(reminder|notification)", + header :comparator "i;ascii-casemap" :regex "Subject" "^.*(Besprechung|Besuch|Termin|Veranstaltung).*\\b(Erinnerung|Benachrichtigung)" +) { + fileinto "INBOX"; + setflag "$label4"; # To Do + stop; +} + +# Customer support communications +if anyof( + header :regex "From" "(^|,)[[:space:]]*\"?.*customer.*.?\\(are\\|uccess\\|upport\\)\"?[[:space:]]*<", + header :regex "From" "(^|,)[[:space:]]*\"?.*(kunden|kundendienst|kundenservice|support|hilfe).*\"?[[:space:]]*<", + header :comparator "i;ascii-casemap" :regex "Subject" "(\\b(feedback|opinion).*review)|(\\breview.*(experience|order|purchase))|(\\b(leave|write)\\b.*review)|(\\bshare|love (your|some) (thoughts|feedback|experience))", + header :comparator "i;ascii-casemap" :regex "Subject" "(\\b(Feedback|Meinung).*Bewertung)|(\\bBewertung.*(Erfahrung|Bestellung|Kauf))|(\\b(hinterlassen|schreiben)\\b.*Bewertung)|(\\bteilen|mögen (Ihre|Deine|einige) (Gedanken|Meinung|Erfahrung))" +) { + fileinto "INBOX"; + stop; +} + +# Account-related important information +if anyof( + header :comparator "i;ascii-casemap" :regex "Subject" "\\b((Privacy|User).*((Policy|Agreement).*$)|(Protect|Register|Update).*((Your Account).*$))", + header :comparator "i;ascii-casemap" :regex "Subject" "\\b((Datenschutz|Benutzer|Nutzer).*((Richtlinie|Vereinbarung).*$)|(Schützen|Registrieren|Aktualisieren).*((Ihr Konto|Dein Konto).*$))", + header :comparator "i;ascii-casemap" :regex "Subject" "\\b((Important|Critical).*((Account|Plan).*(Information|Updates))|^.*(Failed|Unsuccessful).*(Deployment)(\\b.*?|$))", + header :comparator "i;ascii-casemap" :regex "Subject" "\\b((Wichtig|Kritisch).*((Konto|Plan).*(Informationen|Aktualisierungen))|^.*(Fehlgeschlagen|Erfolglos).*(Bereitstellung)(\\b.*?|$))", + header :comparator "i;ascii-casemap" :regex "Subject" "^.*((Google Account)).*((Inactive|Closed|Settings))(\\b.*?|$)", + header :comparator "i;ascii-casemap" :regex "Subject" "^.*((Google-Konto)).*((Inaktiv|Geschlossen|Einstellungen))(\\b.*?|$)" +) { + fileinto "INBOX"; + stop; +} + +# ----------------------------------------------------------------------- +# THE PAPER TRAIL (RECEIPTS, CONFIRMATIONS, RECORDS) +# ----------------------------------------------------------------------- + +# Package deliveries and tracking +if anyof( + address :regex "From" "usps|fedex|narvar|shipment-tracking|getconvey|dhl", + address :regex "From" "hermes|dpd|gls|deutsche.?post", + header :regex "From" "(^|,)[[:space:]]*\"?.?\\(ed.*x delivery manager\\|.*ed.*x\\.com\\|tracking.*updates.*\\)\"?[[:space:]]*<", + header :regex "From" "(^|,)[[:space:]]*\"?.?\\(Paketdienst\\|Versand\\|Lieferung\\|Zustellung\\)\"?[[:space:]]*<", + header :comparator "i;ascii-casemap" :regex "Subject" "^.*package (has been?|was) delivered.*$", + header :comparator "i;ascii-casemap" :regex "Subject" "^.*Paket (wurde|ist) (geliefert|zugestellt).*$", + allof ( + anyof( + header :comparator "i;ascii-casemap" :regex "Subject" "(ship(ped)?)|(.*a shipment (from|to).*(was|has) shipped.*)|((package|order) (is|has))|(track(ing)? .* your)", + header :comparator "i;ascii-casemap" :regex "Subject" "(versand|versendet)|(.*eine Sendung (von|an).*(wurde|ist) verschickt.*)|((Paket|Bestellung) (ist|wurde))|(Sendungsverfolgung .* (Ihre|Deine))" + ), + anyof( + body :regex "(1Z)[0-9A-Z]{16}", + body :regex "(T)+[0-9A-Z]{10}", + body :regex "[0-9]{9}", + body :regex "[0-9]{26}", + body :regex "(94|93|92|94|95)[0-9]{20}", + body :regex "(94|93|92|94|95)[0-9]{22}", + body :regex "(70|14|23|03)[0-9]{14}", + body :regex "(M0|82)[0-9]{8}", + body :regex "([A-Z]{2})[0-9]{9}([A-Z]{2})", + body :regex "[0-9]{20}", + body :regex "[0-9]{15}", + body :regex "[0-9]{12}", + body :regex "[0-9]{22}" + ) + ) +) { + fileinto "INBOX.Papertrail"; + setflag "delivery"; + stop; +} + +# Travel-related confirmations +if anyof( + header :comparator "i;ascii-casemap" :regex "Subject" "\\b(flight|confirmation|you're going to).*\\b(reservation|on)\\b", + header :comparator "i;ascii-casemap" :regex "Subject" "\\b(Flug|Bestätigung|Sie reisen nach|Du reist nach).*\\b(Reservierung|am)\\b", + header :comparator "i;ascii-casemap" :regex "Subject" "\\b(hotel|reservation|booking|dining|restaurant|travel)(s)?( |-)?(confirmation|reservations?|bookings?|details)\\b", + header :comparator "i;ascii-casemap" :regex "Subject" "\\b(Hotel|Reservierung|Buchung|Essen|Restaurant|Reise)(s)?( |-)?(Bestätigung|Reservierungen?|Buchungen?|Details)\\b", + header :comparator "i;ascii-casemap" :regex "Subject" "\\b(uber|lyft|rideshare)(s)?( |-)?(receipt|confirmation|ride summary|your ride with)\\b", + header :comparator "i;ascii-casemap" :regex "Subject" "\\b(Uber|Lyft|Fahrdienst)(s)?( |-)?(Quittung|Bestätigung|Fahrtübersicht|Ihre Fahrt mit|Deine Fahrt mit)\\b" +) { + fileinto "INBOX.Papertrail"; + setflag "travel"; + setflag "$label1"; # Important + stop; +} + +# Event tickets +if header :comparator "i;ascii-casemap" :regex "Subject" "\\b(concert|event|show|performance|ticket|admission|venue|registration)\\b|\\b(Konzert|Veranstaltung|Show|Auftritt|Ticket|Eintrittskarte|Eintritt|Veranstaltungsort|Anmeldung)\\b" { + fileinto "INBOX.Papertrail"; + setflag "tickets"; + stop; +} + +# Financial records +if anyof( + body :comparator "i;ascii-casemap" :text :regex "you[\\s-]*pre[\\s-]?order", + body :comparator "i;ascii-casemap" :text :regex "your[\\s-]*pre[\\s-]?order", + body :comparator "i;ascii-casemap" :text :regex "(Ihre|Deine)[\\s-]*Vorbestellung", + body :comparator "i;ascii-casemap" :text :regex "(Ihre|Deine)[\\s-]*Vorab[\\s-]?bestellung", + header :regex ["To","Cc"] "(^|,)[[:space:]]*\"?.*\\[Aa\\]\\[Pp\\]\\[Pp\\]\\[Ll\\]\\[Ee\\] \\[Cc\\]\\[Aa\\]\\[Rr\\]\\[Dd\\].*\\[Ss\\]\\[Uu\\]\\[Pp\\]\\[Pp\\]\\[Oo\\]\\[Rr\\]\\[Tt\\].*\"?[[:space:]]*<", + header :comparator "i;ascii-casemap" :regex "Subject" "\\b(receipt|bill|invoice|transaction|statement|payment|order|subscription|authorized|booking|renew(al|ing)?|expir(e|ed|ing)?|deposit|withdrawal|purchased?|(itunes|apple) store|credit (score|report)|manage (account|loan))\\b.*", + header :comparator "i;ascii-casemap" :regex "Subject" "\\b(Quittung|Rechnung|Transaktion|Kontoauszug|Zahlung|Bestellung|Abonnement|autorisiert|Buchung|Verlänger(ung|n)?|Ablauf(en)?|Einzahlung|Abhebung|gekauft|(iTunes|Apple) Store|Kreditwürdigkeit|Konto (verwalten|führen)|Kredit)\\b.*", + header :comparator "i;ascii-casemap" :regex "Subject" "\\b(gift (card|certificate)|zelle|new plan|autopay)\\b.*", + header :comparator "i;ascii-casemap" :regex "Subject" "\\b(Geschenk(karte|gutschein)|neuer Plan|automatische Zahlung)\\b.*", + header :comparator "i;ascii-casemap" :regex "Subject" ".* paid .* \\$(\\d,?)+\\.\\d{2}", + header :comparator "i;ascii-casemap" :regex "Subject" ".* bezahlt .* €(\\d,?)+,\\d{2}" +) { + fileinto "INBOX.Papertrail"; + setflag "finances"; + stop; +} + +# ----------------------------------------------------------------------- +# THE FEED (NEWSLETTERS, UPDATES) +# ----------------------------------------------------------------------- + +# Regular reports and updates +if header :comparator "i;ascii-casemap" :regex "Subject" "^.*((Weekly|Monthly).*(Report|Update))(\\b.*?|$)|^.*((Wöchentlich|Monatlich).*(Bericht|Aktualisierung))(\\b.*?|$)" { + fileinto "INBOX.Feed"; + stop; +} + +# Newsletters (identified by List-Unsubscribe header) +if exists "List-Unsubscribe" { + fileinto "INBOX.Feed"; + setflag "newsletter"; + stop; +} + +# ----------------------------------------------------------------------- +# PROMOTIONS AND MARKETING +# ----------------------------------------------------------------------- + +# Promotional content +if anyof( + address :regex "From" "(^.*store-news.*$|^.*axxess.*$)(\\b.*?|$)", + address :regex "From" "(^.*shop-neuigkeiten.*$|^.*angebote.*$)(\\b.*?|$)", + header :comparator "i;ascii-casemap" :regex "Subject" "^.*(final offer|limited time|last chance|black friday|cyber monday|holiday|christmas|free shipping).*", + header :comparator "i;ascii-casemap" :regex "Subject" "^.*(letztes Angebot|zeitlich begrenzt|letzte Chance|Black Friday|Cyber Monday|Feiertag|Weihnachten|kostenloser Versand).*", + body :text :regex "\\b\\d{1,2}% off\\b", + body :text :regex "\\b\\d{1,2}% Rabatt\\b", + body :text :regex "\\b\\d{1,2}% reduziert\\b", + header :comparator "i;ascii-casemap" :regex "Subject" "\\b(discount(ed)?|save|\\d+% off|free)\\b", + header :comparator "i;ascii-casemap" :regex "Subject" "\\b(Rabatt|sparen|\\d+% reduziert|gratis|kostenlos)\\b" +) { + fileinto "Archive"; + setflag "ads"; + setflag "\\Seen"; + stop; +} + +# Default rule - if no other rules match, keep in INBOX +# This ensures no mail is lost +keep; From 6de7582cd78d2eda627df83dca24be6b101115d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20B=C3=B6hme?= Date: Tue, 24 Jun 2025 08:38:27 +0200 Subject: [PATCH 2/4] feat: add navidrome --- machines/moritz-server/configuration.nix | 28 +++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/machines/moritz-server/configuration.nix b/machines/moritz-server/configuration.nix index ef26b4d..73daf5d 100644 --- a/machines/moritz-server/configuration.nix +++ b/machines/moritz-server/configuration.nix @@ -1,4 +1,8 @@ -{pkgs, ...}: { +{ + pkgs, + config, + ... +}: { imports = [ ../../modules/zfs_unencrypted.nix ../../modules/shared.nix @@ -17,6 +21,28 @@ clan.core.networking.targetHost = "root@moritz-server"; nix.package = pkgs.lixPackageSets.latest.lix; + services.navidrome = { + enable = true; + settings = { + MusicFolder = "/mnt/music/tagged"; + }; + }; + + services.nginx = { + virtualHosts = { + "music.moritz.place" = { + forceSSL = true; + useACMEHost = "any.moritz.place"; + locations."/" = { + proxyPass = "http://${config.services.navidrome.settings.Address}:${builtins.toString config.services.navidrome.settings.Port}"; + # extraConfig = '' + # proxy_redirect off + # ''; + }; + }; + }; + }; + networking = { interfaces.enp2s0 = { ipv4.addresses = [ From fd7487015f96405ed20135375ee8263981338239 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20B=C3=B6hme?= Date: Tue, 24 Jun 2025 08:56:45 +0200 Subject: [PATCH 3/4] feat(navidrome): add backup --- machines/moritz-server/configuration.nix | 28 +---------- machines/moritz-server/navidrome.nix | 60 ++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 27 deletions(-) create mode 100644 machines/moritz-server/navidrome.nix diff --git a/machines/moritz-server/configuration.nix b/machines/moritz-server/configuration.nix index 73daf5d..ef26b4d 100644 --- a/machines/moritz-server/configuration.nix +++ b/machines/moritz-server/configuration.nix @@ -1,8 +1,4 @@ -{ - pkgs, - config, - ... -}: { +{pkgs, ...}: { imports = [ ../../modules/zfs_unencrypted.nix ../../modules/shared.nix @@ -21,28 +17,6 @@ clan.core.networking.targetHost = "root@moritz-server"; nix.package = pkgs.lixPackageSets.latest.lix; - services.navidrome = { - enable = true; - settings = { - MusicFolder = "/mnt/music/tagged"; - }; - }; - - services.nginx = { - virtualHosts = { - "music.moritz.place" = { - forceSSL = true; - useACMEHost = "any.moritz.place"; - locations."/" = { - proxyPass = "http://${config.services.navidrome.settings.Address}:${builtins.toString config.services.navidrome.settings.Port}"; - # extraConfig = '' - # proxy_redirect off - # ''; - }; - }; - }; - }; - networking = { interfaces.enp2s0 = { ipv4.addresses = [ diff --git a/machines/moritz-server/navidrome.nix b/machines/moritz-server/navidrome.nix new file mode 100644 index 0000000..aa2d9bd --- /dev/null +++ b/machines/moritz-server/navidrome.nix @@ -0,0 +1,60 @@ +{ + config, + pkgs, + ... +}: { + services.navidrome = { + enable = true; + settings = { + MusicFolder = "/mnt/music/tagged"; + }; + }; + + services.nginx = { + virtualHosts = { + "music.moritz.place" = { + forceSSL = true; + useACMEHost = "any.moritz.place"; + locations."/" = { + proxyPass = "http://${config.services.navidrome.settings.Address}:${builtins.toString config.services.navidrome.settings.Port}"; + }; + }; + }; + }; + + services.borgbackup.jobs = { + mailDirectory = { + paths = config.services.navidrome.settings.MusicFolder; + repo = "u461386-sub1@u461386.your-storagebox.de:musicFolder"; + doInit = true; + encryption = { + mode = "repokey"; + passCommand = "cat ${config.clan.core.vars.generators.borg-navidrome.files.password.path}"; + }; + environment = {BORG_RSH = "ssh -i ${config.clan.core.vars.generators.borg-navidrome.files."ssh.id_ed25519".path} -p 23";}; + compression = "auto,zstd"; + startAt = "hourly"; + persistentTimer = true; + prune.keep = { + within = "1d"; # Keep all archives from the last day + daily = 7; + weekly = 3; + monthly = 3; + }; + }; + }; + + clan.core.vars.generators.borg-navidrome = { + prompts.password.persist = true; + + files."ssh.id_ed25519" = {}; + files."ssh.id_ed25519.pub".secret = false; + runtimeInputs = [ + pkgs.coreutils + pkgs.openssh + ]; + script = '' + ssh-keygen -t ed25519 -N "" -f "$out"/ssh.id_ed25519 + ''; + }; +} From d032d6e93b09318cee29bdde800663ad75978952 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20B=C3=B6hme?= Date: Tue, 24 Jun 2025 08:57:01 +0200 Subject: [PATCH 4/4] feat: add envrc --- templates/rust/.envrc | 1 + 1 file changed, 1 insertion(+) create mode 100644 templates/rust/.envrc diff --git a/templates/rust/.envrc b/templates/rust/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/templates/rust/.envrc @@ -0,0 +1 @@ +use flake