rm setup-permisssions, s3 deletion added

This commit is contained in:
Andreas Knuth 2025-03-16 11:10:51 +01:00
parent 2179d5110f
commit 044bb3a960
2 changed files with 119 additions and 116 deletions

View File

@ -3,18 +3,12 @@
S3 E-Mail Downloader Script
Dieses Script lädt E-Mails aus einem S3-Bucket herunter und speichert sie im
Maildir-Format für Dovecot. Es berücksichtigt die Docker-Container-Umgebung.
Maildir-Format für Dovecot. Es filtert nach bestimmten Domains und Benutzernamen
und kann nicht passende E-Mails löschen.
Verwendung:
python3 s3_email_downloader.py
Umgebungsvariablen:
AWS_ACCESS_KEY_ID: AWS Zugriffsschlüssel
AWS_SECRET_ACCESS_KEY: AWS geheimer Zugriffsschlüssel
AWS_REGION: AWS-Region (Standard: us-east-2)
S3_BUCKET: S3-Bucket-Name
EMAIL_PREFIX: Präfix für E-Mails im S3-Bucket (Standard: emails/)
MAIL_DIR: Lokales Verzeichnis für E-Mails (Standard: ./data/mail)
Nutzung:
python3 s3_email_downloader.py # Mit Bestätigungsabfrage für Löschungen
python3 s3_email_downloader.py y # Ohne Bestätigungsabfrage für Löschungen
"""
import boto3
@ -24,11 +18,14 @@ import logging
import json
import re
import hashlib
import sys
from pathlib import Path
from email.parser import BytesParser
from email import policy
from dotenv import load_dotenv
load_dotenv() # .env-Datei laden
# .env-Datei laden
load_dotenv()
# Logging konfigurieren
logging.basicConfig(
@ -46,10 +43,18 @@ AWS_REGION = os.environ.get('AWS_REGION', 'us-east-2')
S3_BUCKET = os.environ.get('S3_BUCKET', '')
EMAIL_PREFIX = os.environ.get('EMAIL_PREFIX', 'emails/')
MAIL_DIR = os.environ.get('MAIL_DIR', './data/mail') # Pfad zum Docker-Volume
VALID_DOMAIN = os.environ.get('VALID_DOMAIN', 'bizmatch.net')
VALID_USERNAMES = os.environ.get('VALID_USERNAMES', 'accounting,info,sales,support,test1,test2,test3')
# Umwandlung der komma-getrennten Liste in ein Set für schnellere Suche
VALID_USERNAMES_SET = set(VALID_USERNAMES.split(','))
# Status-Datei für die Synchronisation
STATUS_FILE = Path('sync_status.json')
# Prüfen, ob automatische Löschung aktiviert ist
AUTO_DELETE = len(sys.argv) > 1 and sys.argv[1].lower() == 'y'
def load_sync_status():
"""Lädt den letzten Synchronisationsstatus"""
if STATUS_FILE.exists():
@ -90,6 +95,19 @@ def extract_email_address(address):
# Fallback
return address.strip()
def is_valid_recipient(to_address):
"""Prüft, ob die Empfängeradresse gültig ist (passende Domain und Username)"""
email = extract_email_address(to_address)
# E-Mail-Adresse aufteilen
if '@' not in email:
return False
username, domain = email.split('@', 1)
# Domain und Username prüfen
return domain.lower() == VALID_DOMAIN.lower() and username.lower() in VALID_USERNAMES_SET
def get_maildir_path(to_address, mail_dir):
"""
Ermittelt den Pfad im Maildir-Format basierend auf der Empfängeradresse
@ -101,7 +119,7 @@ def get_maildir_path(to_address, mail_dir):
if '@' in email:
user, domain = email.split('@', 1)
else:
user, domain = email, 'bizmatch.net'
user, domain = email, VALID_DOMAIN
# Pfad erstellen
mail_dir_path = Path(mail_dir)
@ -145,6 +163,37 @@ def store_email(email_content, to_address, message_id, s3_key, mail_dir):
logger.error(f"Fehler beim Speichern der E-Mail {s3_key}: {str(e)}")
return False
def delete_s3_emails(s3, emails_to_delete):
"""Löscht E-Mails aus dem S3-Bucket"""
if not emails_to_delete:
return 0
if not AUTO_DELETE:
# Bestätigung einholen
print(f"\nFolgende {len(emails_to_delete)} E-Mails werden gelöscht:")
for i, key in enumerate(emails_to_delete[:10]):
print(f" - {key}")
if len(emails_to_delete) > 10:
print(f" ... und {len(emails_to_delete) - 10} weitere")
confirmation = input("\nMöchten Sie diese E-Mails wirklich löschen? (j/n): ")
if confirmation.lower() not in ['j', 'ja', 'y', 'yes']:
logger.info("Löschung abgebrochen")
return 0
# Löschung durchführen
deleted_count = 0
for key in emails_to_delete:
try:
s3.delete_object(Bucket=S3_BUCKET, Key=key)
logger.info(f"E-Mail gelöscht: {key}")
deleted_count += 1
except Exception as e:
logger.error(f"Fehler beim Löschen der E-Mail {key}: {str(e)}")
return deleted_count
def main():
"""Hauptfunktion"""
# Prüfen, ob die erforderlichen Umgebungsvariablen gesetzt sind
@ -154,6 +203,9 @@ def main():
logger.info(f"S3 E-Mail Downloader gestartet. Bucket: {S3_BUCKET}, Präfix: {EMAIL_PREFIX}")
logger.info(f"E-Mails werden nach {MAIL_DIR} heruntergeladen")
logger.info(f"Gültige Domain: {VALID_DOMAIN}")
logger.info(f"Gültige Usernames: {VALID_USERNAMES}")
logger.info(f"Automatisches Löschen: {'Ja' if AUTO_DELETE else 'Nein (mit Bestätigung)'}")
try:
# S3-Client initialisieren
@ -162,83 +214,33 @@ def main():
# Letzten Synchronisationsstatus laden
last_sync = load_sync_status()
# Alle E-Mails im Bucket auflisten
# Um alle E-Mails zu verarbeiten, müssen wir den Paginator verwenden
paginator = s3.get_paginator('list_objects_v2')
pages = paginator.paginate(Bucket=S3_BUCKET, Prefix=EMAIL_PREFIX)
new_emails = 0
total_emails = 0
emails_to_delete = []
# Alle Seiten durchlaufen
for page in pages:
if 'Contents' not in page:
logger.error(f"Keine 'Contents' in der Seite gefunden")
else:
logger.info(f"Gefunden: {len(page['Contents'])} Objekte in der Seite")
# Ersten 5 Objekte anzeigen
for i, obj in enumerate(page['Contents'][:5]):
logger.info(f"Objekt {i}: {obj['Key']}")
for obj in page['Contents']:
key = obj['Key']
if not key.endswith('/'):
continue
logger.info(f"Verarbeite Objekt mit Key: {key}")
objects = page['Contents']
total_emails += len(objects)
for obj in objects:
key = obj['Key']
# Verzeichnisse überspringen
if key.endswith('/'):
continue
# Prüfen, ob die E-Mail bereits synchronisiert wurde
if key in last_sync:
logger.info(f"E-Mail {key} bereits synchronisiert - ÜBERSPRUNGEN")
logger.debug(f"E-Mail {key} bereits synchronisiert - übersprungen")
continue
else:
logger.info(f"E-Mail {key} wird heruntergeladen...")
try:
# E-Mail aus S3 laden
logger.info(f"Versuche, E-Mail aus S3 zu laden: {key}")
response = s3.get_object(Bucket=S3_BUCKET, Key=key)
email_content = response['Body'].read()
logger.info(f"E-Mail erfolgreich geladen, Größe: {len(email_content)} Bytes")
# Debug: Erste 100 Bytes anzeigen
logger.info(f"E-Mail-Anfang: {email_content[:100]}")
# Header parsen
headers = BytesParser(policy=policy.default).parsebytes(email_content, headersonly=True)
# Empfänger-Adresse extrahieren
to_address = headers.get('To', '')
message_id = headers.get('Message-ID', '')
logger.info(f"Empfänger: '{to_address}', Message-ID: '{message_id}'")
# Überprüfe MAIL_DIR
logger.info(f"MAIL_DIR ist: {MAIL_DIR}")
mail_dir_path = Path(MAIL_DIR)
logger.info(f"MAIL_DIR existiert: {mail_dir_path.exists()}, ist Verzeichnis: {mail_dir_path.is_dir() if mail_dir_path.exists() else 'N/A'}")
# E-Mail speichern
result = store_email(email_content, to_address, message_id, key, MAIL_DIR)
logger.info(f"Ergebnis des Speicherns: {result}")
if result:
# Status aktualisieren
last_sync[key] = {
'timestamp': time.time(),
'to': to_address,
'message_id': message_id
}
new_emails += 1
logger.info(f"E-Mail {key} erfolgreich synchronisiert")
else:
logger.error(f"E-Mail {key} konnte nicht gespeichert werden")
except Exception as e:
logger.error(f"Fehler bei der Verarbeitung der E-Mail {key}: {str(e)}")
import traceback
logger.error(traceback.format_exc())
try:
# E-Mail aus S3 laden
@ -246,12 +248,15 @@ def main():
email_content = response['Body'].read()
# Header parsen
try:
headers = BytesParser(policy=policy.default).parsebytes(email_content, headersonly=True)
# Empfänger-Adresse extrahieren
to_address = headers.get('To', '')
message_id = headers.get('Message-ID', '')
# Prüfen, ob die Empfängeradresse gültig ist
if is_valid_recipient(to_address):
logger.info(f"Gültige E-Mail für: {to_address}")
# E-Mail speichern
if store_email(email_content, to_address, message_id, key, MAIL_DIR):
# Status aktualisieren
@ -261,18 +266,37 @@ def main():
'message_id': message_id
}
new_emails += 1
logger.info(f"E-Mail {key} erfolgreich synchronisiert ({new_emails})")
# Status nach jeweils 10 E-Mails speichern
if new_emails % 10 == 0:
save_sync_status(last_sync)
logger.info(f"Zwischenspeicherung: {new_emails} neue E-Mails bisher")
else:
logger.info(f"Ungültige Empfängeradresse: {to_address} für {key}")
emails_to_delete.append(key)
except Exception as e:
logger.error(f"Fehler beim Parsen der E-Mail-Header {key}: {str(e)}")
emails_to_delete.append(key)
except Exception as e:
logger.error(f"Fehler bei der Verarbeitung der E-Mail {key}: {str(e)}")
# Status speichern
logger.info(f"Aktueller Status enthält {len(last_sync)} E-Mails")
# Abschließend den Status speichern
save_sync_status(last_sync)
logger.info(f"{new_emails} neue E-Mails heruntergeladen")
# Ungültige E-Mails löschen
if emails_to_delete:
deleted_count = delete_s3_emails(s3, emails_to_delete)
logger.info(f"Insgesamt {deleted_count} von {len(emails_to_delete)} ungültigen E-Mails gelöscht")
logger.info(f"Verarbeitung abgeschlossen. Insgesamt {total_emails} E-Mails gefunden, {new_emails} neue heruntergeladen.")
except Exception as e:
logger.error(f"Fehler: {str(e)}")
import traceback
logger.error(traceback.format_exc())
if __name__ == "__main__":
main()

View File

@ -1,21 +0,0 @@
#!/bin/bash
# Dieses Skript richtet die Verzeichnisstruktur und Berechtigungen für Dovecot ein
# Erstellen der Verzeichnisstruktur
mkdir -p /var/mail/domains/bizmatch.net/info
mkdir -p /var/mail/user1
mkdir -p /var/mail/user2
# Festlegen der richtigen Besitzer und Berechtigungen
chown -R 1001:1000 /var/mail/domains/bizmatch.net/info
chown -R 1000:1000 /var/mail/user1
chown -R 1001:1000 /var/mail/user2
# Setzen der Berechtigungen (wichtig für den dovecot Zugriff)
chmod -R 0770 /var/mail
# Ausgabe zur Bestätigung
echo "Verzeichnisse und Berechtigungen wurden korrekt eingerichtet."
echo "Verzeichnisstruktur:"
ls -la /var/mail
ls -la /var/mail/domains/bizmatch.net