rm setup-permisssions, s3 deletion added
This commit is contained in:
parent
2179d5110f
commit
044bb3a960
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
Loading…
Reference in New Issue