cleanup
This commit is contained in:
parent
67c2440f4a
commit
9bb327eada
|
|
@ -1,9 +1,7 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
|
||||||
sync_dynamodb_to_sieve.py - Sync DynamoDB rules to Dovecot Sieve
|
|
||||||
"""
|
|
||||||
import boto3
|
import boto3
|
||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
|
|
@ -41,7 +39,7 @@ def generate_sieve(email, rules):
|
||||||
if forwards:
|
if forwards:
|
||||||
lines.append('# rule:[forward]')
|
lines.append('# rule:[forward]')
|
||||||
for fwd in forwards:
|
for fwd in forwards:
|
||||||
lines.append(f'redirect :copy "{fwd}";')
|
lines.append(f'redirect :copy "{fwd}";')
|
||||||
lines.append('')
|
lines.append('')
|
||||||
|
|
||||||
# OOO
|
# OOO
|
||||||
|
|
@ -58,105 +56,165 @@ def generate_sieve(email, rules):
|
||||||
'',
|
'',
|
||||||
msg,
|
msg,
|
||||||
'.',
|
'.',
|
||||||
';' # <--- HIER WAR DER FEHLER
|
';'
|
||||||
])
|
])
|
||||||
else:
|
else:
|
||||||
# Sicherheitshalber JSON dump für escaping von Anführungszeichen nutzen
|
|
||||||
safe_msg = json.dumps(msg, ensure_ascii=False)
|
safe_msg = json.dumps(msg, ensure_ascii=False)
|
||||||
lines.append(f'vacation :days 1 :from "{email}" {safe_msg};')
|
lines.append(f'vacation :days 1 :from "{email}" {safe_msg};')
|
||||||
|
|
||||||
return '\n'.join(lines) + '\n'
|
return '\n'.join(lines) + '\n'
|
||||||
|
|
||||||
def sync():
|
def deactivate_sieve(email, mailbox_home):
|
||||||
"""Sync all rules from DynamoDB to Sieve"""
|
"""
|
||||||
response = table.scan()
|
SICHERHEITS-VARIANTE:
|
||||||
|
Überschreibt das Sieve-Skript mit einem leeren 'keep;',
|
||||||
|
anstatt Dateien zu löschen.
|
||||||
|
"""
|
||||||
|
|
||||||
for item in response.get('Items', []):
|
# Pfad zur aktiven Datei
|
||||||
email = item['email_address']
|
sieve_path = mailbox_home / '.dovecot.sieve'
|
||||||
domain = email.split('@')[1]
|
|
||||||
user = email.split('@')[0]
|
# Inhalt: Nur "keep;" -> Mail behalten, nichts tun.
|
||||||
|
safe_content = (
|
||||||
# Path: /var/mail/domain.de/user/.dovecot.sieve
|
'# Script deactivated by DynamoDB Sync (User not in DB)\n'
|
||||||
mailbox_dir = Path(VMAIL_BASE) / domain / user / 'home'
|
'keep;\n'
|
||||||
|
)
|
||||||
# Skip if mailbox doesn't exist
|
|
||||||
if not mailbox_dir.exists():
|
|
||||||
mailbox_dir.mkdir(exist_ok=True)
|
|
||||||
os.system(f'chown docker:docker {mailbox_dir}')
|
|
||||||
|
|
||||||
sieve_path = mailbox_dir / '.dovecot.sieve'
|
|
||||||
|
|
||||||
# Generate & write
|
|
||||||
script = generate_sieve(email, item)
|
|
||||||
sieve_path.write_text(script)
|
|
||||||
|
|
||||||
# Compile
|
# Prüfen, ob wir überhaupt etwas tun müssen (um unnötige Schreibvorgänge zu meiden)
|
||||||
|
# Wenn der Inhalt schon "keep;" ist, brechen wir ab.
|
||||||
|
if sieve_path.exists() and not sieve_path.is_symlink():
|
||||||
|
try:
|
||||||
|
current_content = sieve_path.read_text()
|
||||||
|
if "Script deactivated" in current_content:
|
||||||
|
return # Ist schon deaktiviert
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Datei sicher schreiben (überschreibt auch Symlinks, wenn os.open genutzt wird,
|
||||||
|
# aber pathlib write_text folgt symlinks oder überschreibt file).
|
||||||
|
# Um sicher zu gehen, dass wir keinen Symlink auf eine Systemdatei überschreiben:
|
||||||
|
if sieve_path.is_symlink():
|
||||||
|
try:
|
||||||
|
os.unlink(sieve_path) # Link entfernen
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
sieve_path.write_text(safe_content)
|
||||||
|
|
||||||
|
# Kompilieren (wichtig, damit Dovecot die Änderung sofort sieht)
|
||||||
os.system(f'sievec {sieve_path}')
|
os.system(f'sievec {sieve_path}')
|
||||||
|
|
||||||
|
# Ownership sicherstellen
|
||||||
|
os.system(f'chown docker:docker {sieve_path}')
|
||||||
|
|
||||||
|
print(f'⚪ {email} (Regeln deaktiviert/geleert)')
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Fehler beim Deaktivieren von {email}: {e}")
|
||||||
|
|
||||||
# Copy to sieve dir
|
|
||||||
sieve_dir = mailbox_dir / 'sieve'
|
|
||||||
sieve_dir.mkdir(exist_ok=True)
|
|
||||||
managed_script = sieve_dir / 'default.sieve'
|
|
||||||
managed_script.write_text(script)
|
|
||||||
os.system(f'sievec {managed_script}')
|
|
||||||
|
|
||||||
# Ownership
|
def sync():
|
||||||
os.system(f'chown -R docker:docker {sieve_dir}')
|
"""Sync logic"""
|
||||||
|
|
||||||
|
# 1. DB Status abrufen
|
||||||
|
try:
|
||||||
|
response = table.scan()
|
||||||
|
db_users = {item['email_address']: item for item in response.get('Items', [])}
|
||||||
|
except Exception as e:
|
||||||
|
print(f"FATAL: Konnte DynamoDB nicht lesen ({e}). Breche ab, um keine Regeln zu löschen.")
|
||||||
|
return
|
||||||
|
|
||||||
# Aktivieren mit doveadm sieve put
|
# 2. Filesystem scannen
|
||||||
os.system(f'doveadm sieve put -u {email} -a default {managed_script}')
|
base_path = Path(VMAIL_BASE)
|
||||||
|
|
||||||
|
if not base_path.exists():
|
||||||
|
print("Warnung: /var/mail existiert nicht.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Iteriere durch Domains
|
||||||
|
for domain_dir in base_path.iterdir():
|
||||||
|
if not domain_dir.is_dir(): continue
|
||||||
|
|
||||||
|
# Iteriere durch User
|
||||||
|
for user_dir in domain_dir.iterdir():
|
||||||
|
if not user_dir.is_dir(): continue
|
||||||
|
|
||||||
|
user = user_dir.name
|
||||||
|
domain = domain_dir.name
|
||||||
|
email = f"{user}@{domain}"
|
||||||
|
|
||||||
|
# WICHTIG: Wir arbeiten NUR im 'home' Unterordner
|
||||||
|
# Die Mails liegen in user_dir/cur etc. -> Die fassen wir nicht an.
|
||||||
|
mailbox_home = user_dir / 'home'
|
||||||
|
|
||||||
|
# --- FALL A: User ist in der DB (Update) ---
|
||||||
|
if email in db_users:
|
||||||
|
item = db_users[email]
|
||||||
|
|
||||||
|
if not mailbox_home.exists():
|
||||||
|
mailbox_home.mkdir(exist_ok=True)
|
||||||
|
os.system(f'chown docker:docker {mailbox_home}')
|
||||||
|
|
||||||
|
sieve_path = mailbox_home / '.dovecot.sieve'
|
||||||
|
|
||||||
|
script = generate_sieve(email, item)
|
||||||
|
sieve_path.write_text(script)
|
||||||
|
|
||||||
|
os.system(f'sievec {sieve_path}')
|
||||||
|
|
||||||
|
# Ownership
|
||||||
|
os.system(f'chown docker:docker {sieve_path}')
|
||||||
|
|
||||||
|
# (Optional) Auch in den sieve/ Ordner spiegeln für Roundcube Kompatibilität
|
||||||
|
sieve_dir = mailbox_home / 'sieve'
|
||||||
|
if sieve_dir.exists():
|
||||||
|
managed_script = sieve_dir / 'default.sieve'
|
||||||
|
managed_script.write_text(script)
|
||||||
|
os.system(f'sievec {managed_script}')
|
||||||
|
os.system(f'chown -R docker:docker {sieve_dir}')
|
||||||
|
|
||||||
|
print(f'✓ {email}')
|
||||||
|
|
||||||
|
# --- FALL B: User ist NICHT in DB (Deaktivieren) ---
|
||||||
|
else:
|
||||||
|
# Nur wenn der Home-Ordner existiert (wir legen keine Leichen für nicht-existente User an)
|
||||||
|
if mailbox_home.exists():
|
||||||
|
deactivate_sieve(email, mailbox_home)
|
||||||
|
|
||||||
print(f'✓ {email}')
|
|
||||||
|
|
||||||
def wait_for_dovecot():
|
def wait_for_dovecot():
|
||||||
"""Wartet, bis der Dovecot Auth Socket verfügbar ist."""
|
|
||||||
# Der Pfad zum Socket, über den doveadm kommuniziert
|
|
||||||
socket_path = '/var/run/dovecot/auth-userdb'
|
socket_path = '/var/run/dovecot/auth-userdb'
|
||||||
|
|
||||||
print("⏳ Warte auf Dovecot Start...")
|
print("⏳ Warte auf Dovecot Start...")
|
||||||
while not os.path.exists(socket_path):
|
while not os.path.exists(socket_path):
|
||||||
print(f" ... Socket {socket_path} noch nicht da. Schlafe 5s.")
|
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
|
|
||||||
print("✅ Dovecot ist bereit!")
|
print("✅ Dovecot ist bereit!")
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
||||||
# 1. Erst warten, bis Dovecot da ist, sonst hagelt es Fehler beim Start
|
|
||||||
wait_for_dovecot()
|
wait_for_dovecot()
|
||||||
|
|
||||||
# Pfad zur Cron-Definition (nur der String, z.B. "*/5 * * * *")
|
|
||||||
CRON_FILE = '/etc/sieve-schedule'
|
CRON_FILE = '/etc/sieve-schedule'
|
||||||
|
|
||||||
# Fallback, falls Datei fehlt
|
|
||||||
cron_string = "*/5 * * * *"
|
cron_string = "*/5 * * * *"
|
||||||
|
|
||||||
if os.path.exists(CRON_FILE):
|
if os.path.exists(CRON_FILE):
|
||||||
with open(CRON_FILE, 'r') as f:
|
with open(CRON_FILE, 'r') as f:
|
||||||
# Kommentare entfernen und String holen
|
|
||||||
content = f.read().strip()
|
content = f.read().strip()
|
||||||
if content and not content.startswith('#'):
|
if content and not content.startswith('#'):
|
||||||
cron_string = content
|
cron_string = content
|
||||||
|
|
||||||
print(f"DynamoDB Sieve Sync gestartet. Zeitplan: {cron_string}")
|
print(f"DynamoDB Sieve Sync (Safe Mode) gestartet. Zeitplan: {cron_string}")
|
||||||
print(f"AWS Region: {os.environ.get('AWS_DEFAULT_REGION', 'nicht gesetzt')}") # Debug Check
|
|
||||||
|
|
||||||
# Initialer Lauf beim Start? (Optional, hier auskommentiert)
|
|
||||||
sync()
|
sync()
|
||||||
|
|
||||||
# Iterator erstellen
|
|
||||||
base_time = datetime.now()
|
base_time = datetime.now()
|
||||||
iter = croniter(cron_string, base_time)
|
iter = croniter(cron_string, base_time)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
# Den nächsten Zeitpunkt berechnen
|
|
||||||
next_run = iter.get_next(datetime)
|
next_run = iter.get_next(datetime)
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
|
|
||||||
sleep_seconds = (next_run - now).total_seconds()
|
sleep_seconds = (next_run - now).total_seconds()
|
||||||
|
|
||||||
if sleep_seconds > 0:
|
if sleep_seconds > 0:
|
||||||
# Warten bis zum nächsten Slot
|
|
||||||
time.sleep(sleep_seconds)
|
time.sleep(sleep_seconds)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -164,5 +222,4 @@ if __name__ == '__main__':
|
||||||
sync()
|
sync()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Fehler beim Sync: {e}")
|
print(f"Fehler beim Sync: {e}")
|
||||||
# Wichtig: Bei Fehler nicht abstürzen, sondern weitermachen
|
|
||||||
pass
|
pass
|
||||||
Loading…
Reference in New Issue