rm setup-permisssions, s3 deletion added
This commit is contained in:
parent
2179d5110f
commit
044bb3a960
|
|
@ -3,18 +3,12 @@
|
||||||
S3 E-Mail Downloader Script
|
S3 E-Mail Downloader Script
|
||||||
|
|
||||||
Dieses Script lädt E-Mails aus einem S3-Bucket herunter und speichert sie im
|
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:
|
Nutzung:
|
||||||
python3 s3_email_downloader.py
|
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
|
||||||
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)
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import boto3
|
import boto3
|
||||||
|
|
@ -24,11 +18,14 @@ import logging
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from email.parser import BytesParser
|
from email.parser import BytesParser
|
||||||
from email import policy
|
from email import policy
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
load_dotenv() # .env-Datei laden
|
|
||||||
|
# .env-Datei laden
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
# Logging konfigurieren
|
# Logging konfigurieren
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
|
|
@ -46,10 +43,18 @@ AWS_REGION = os.environ.get('AWS_REGION', 'us-east-2')
|
||||||
S3_BUCKET = os.environ.get('S3_BUCKET', '')
|
S3_BUCKET = os.environ.get('S3_BUCKET', '')
|
||||||
EMAIL_PREFIX = os.environ.get('EMAIL_PREFIX', 'emails/')
|
EMAIL_PREFIX = os.environ.get('EMAIL_PREFIX', 'emails/')
|
||||||
MAIL_DIR = os.environ.get('MAIL_DIR', './data/mail') # Pfad zum Docker-Volume
|
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-Datei für die Synchronisation
|
||||||
STATUS_FILE = Path('sync_status.json')
|
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():
|
def load_sync_status():
|
||||||
"""Lädt den letzten Synchronisationsstatus"""
|
"""Lädt den letzten Synchronisationsstatus"""
|
||||||
if STATUS_FILE.exists():
|
if STATUS_FILE.exists():
|
||||||
|
|
@ -90,6 +95,19 @@ def extract_email_address(address):
|
||||||
# Fallback
|
# Fallback
|
||||||
return address.strip()
|
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):
|
def get_maildir_path(to_address, mail_dir):
|
||||||
"""
|
"""
|
||||||
Ermittelt den Pfad im Maildir-Format basierend auf der Empfängeradresse
|
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:
|
if '@' in email:
|
||||||
user, domain = email.split('@', 1)
|
user, domain = email.split('@', 1)
|
||||||
else:
|
else:
|
||||||
user, domain = email, 'bizmatch.net'
|
user, domain = email, VALID_DOMAIN
|
||||||
|
|
||||||
# Pfad erstellen
|
# Pfad erstellen
|
||||||
mail_dir_path = Path(mail_dir)
|
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)}")
|
logger.error(f"Fehler beim Speichern der E-Mail {s3_key}: {str(e)}")
|
||||||
return False
|
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():
|
def main():
|
||||||
"""Hauptfunktion"""
|
"""Hauptfunktion"""
|
||||||
# Prüfen, ob die erforderlichen Umgebungsvariablen gesetzt sind
|
# 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"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"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:
|
try:
|
||||||
# S3-Client initialisieren
|
# S3-Client initialisieren
|
||||||
|
|
@ -162,83 +214,33 @@ def main():
|
||||||
# Letzten Synchronisationsstatus laden
|
# Letzten Synchronisationsstatus laden
|
||||||
last_sync = load_sync_status()
|
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')
|
paginator = s3.get_paginator('list_objects_v2')
|
||||||
pages = paginator.paginate(Bucket=S3_BUCKET, Prefix=EMAIL_PREFIX)
|
pages = paginator.paginate(Bucket=S3_BUCKET, Prefix=EMAIL_PREFIX)
|
||||||
|
|
||||||
new_emails = 0
|
new_emails = 0
|
||||||
|
total_emails = 0
|
||||||
|
emails_to_delete = []
|
||||||
|
|
||||||
|
# Alle Seiten durchlaufen
|
||||||
for page in pages:
|
for page in pages:
|
||||||
if 'Contents' not in page:
|
if 'Contents' not in page:
|
||||||
logger.error(f"Keine 'Contents' in der Seite gefunden")
|
continue
|
||||||
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']:
|
objects = page['Contents']
|
||||||
|
total_emails += len(objects)
|
||||||
|
|
||||||
|
for obj in objects:
|
||||||
key = obj['Key']
|
key = obj['Key']
|
||||||
|
|
||||||
if not key.endswith('/'):
|
# Verzeichnisse überspringen
|
||||||
|
if key.endswith('/'):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
logger.info(f"Verarbeite Objekt mit Key: {key}")
|
|
||||||
|
|
||||||
# Prüfen, ob die E-Mail bereits synchronisiert wurde
|
# Prüfen, ob die E-Mail bereits synchronisiert wurde
|
||||||
if key in last_sync:
|
if key in last_sync:
|
||||||
logger.info(f"E-Mail {key} bereits synchronisiert - ÜBERSPRUNGEN")
|
logger.debug(f"E-Mail {key} bereits synchronisiert - übersprungen")
|
||||||
continue
|
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:
|
try:
|
||||||
# E-Mail aus S3 laden
|
# E-Mail aus S3 laden
|
||||||
|
|
@ -246,33 +248,55 @@ def main():
|
||||||
email_content = response['Body'].read()
|
email_content = response['Body'].read()
|
||||||
|
|
||||||
# Header parsen
|
# Header parsen
|
||||||
headers = BytesParser(policy=policy.default).parsebytes(email_content, headersonly=True)
|
try:
|
||||||
|
headers = BytesParser(policy=policy.default).parsebytes(email_content, headersonly=True)
|
||||||
|
to_address = headers.get('To', '')
|
||||||
|
message_id = headers.get('Message-ID', '')
|
||||||
|
|
||||||
# Empfänger-Adresse extrahieren
|
# Prüfen, ob die Empfängeradresse gültig ist
|
||||||
to_address = headers.get('To', '')
|
if is_valid_recipient(to_address):
|
||||||
message_id = headers.get('Message-ID', '')
|
logger.info(f"Gültige E-Mail für: {to_address}")
|
||||||
|
|
||||||
# E-Mail speichern
|
# E-Mail speichern
|
||||||
if store_email(email_content, to_address, message_id, key, MAIL_DIR):
|
if store_email(email_content, to_address, message_id, key, MAIL_DIR):
|
||||||
# Status aktualisieren
|
# Status aktualisieren
|
||||||
last_sync[key] = {
|
last_sync[key] = {
|
||||||
'timestamp': time.time(),
|
'timestamp': time.time(),
|
||||||
'to': to_address,
|
'to': to_address,
|
||||||
'message_id': message_id
|
'message_id': message_id
|
||||||
}
|
}
|
||||||
new_emails += 1
|
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:
|
except Exception as e:
|
||||||
logger.error(f"Fehler bei der Verarbeitung der E-Mail {key}: {str(e)}")
|
logger.error(f"Fehler bei der Verarbeitung der E-Mail {key}: {str(e)}")
|
||||||
|
|
||||||
# Status speichern
|
# Abschließend den Status speichern
|
||||||
logger.info(f"Aktueller Status enthält {len(last_sync)} E-Mails")
|
|
||||||
save_sync_status(last_sync)
|
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:
|
except Exception as e:
|
||||||
logger.error(f"Fehler: {str(e)}")
|
logger.error(f"Fehler: {str(e)}")
|
||||||
|
import traceback
|
||||||
|
logger.error(traceback.format_exc())
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
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