feat: Cleanup-Subcommand zum Entfernen doppelter Kalender hinzugefügt, Doku-Hinweis auf v0.2.0 präzisiert
This commit is contained in:
@@ -40,6 +40,13 @@ type Daemon struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
if len(os.Args) > 1 && os.Args[1] == "cleanup" {
|
||||||
|
if err := runCleanup(); err != nil {
|
||||||
|
slog.Error("cleanup failed", "err", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
if err := run(); err != nil {
|
if err := run(); err != nil {
|
||||||
slog.Error("fatal error", "err", err)
|
slog.Error("fatal error", "err", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -175,6 +182,77 @@ func (d *Daemon) processUser(ctx context.Context, m mailcow.Mailbox) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func runCleanup() error {
|
||||||
|
if len(os.Args) < 3 {
|
||||||
|
fmt.Fprintf(os.Stderr, "Verwendung: %s cleanup <alter-kalendername>\n", os.Args[0])
|
||||||
|
fmt.Fprintf(os.Stderr, "\nEntfernt einen automatisch erstellten Geburtstagskalender aus allen Mailboxen.\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "Nur Kalender, deren Einträge ausschließlich vom Daemon erstellt wurden, werden gelöscht.\n")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
oldCalendarName := os.Args[2]
|
||||||
|
|
||||||
|
slog.Info("starting calendar cleanup", "calendarName", oldCalendarName)
|
||||||
|
|
||||||
|
mailcowBase := os.Getenv("MAILCOW_BASE")
|
||||||
|
if mailcowBase == "" {
|
||||||
|
return fmt.Errorf("MAILCOW_BASE environment variable is not set")
|
||||||
|
}
|
||||||
|
mailcowAPIKey := os.Getenv("MAILCOW_APIKEY")
|
||||||
|
if mailcowAPIKey == "" {
|
||||||
|
return fmt.Errorf("MAILCOW_APIKEY environment variable is not set")
|
||||||
|
}
|
||||||
|
calendarName := os.Getenv("CALENDAR_NAME")
|
||||||
|
if calendarName == "" {
|
||||||
|
calendarName = "Birthdays"
|
||||||
|
}
|
||||||
|
|
||||||
|
d := &Daemon{
|
||||||
|
userTokens: make(map[string]string),
|
||||||
|
userTokensLock: &sync.RWMutex{},
|
||||||
|
baseURL: mailcowBase,
|
||||||
|
stateFilepath: os.Getenv("STATEFILE"),
|
||||||
|
httpClient: &http.Client{Transport: buildTransport()},
|
||||||
|
calendarName: calendarName,
|
||||||
|
}
|
||||||
|
if len(d.stateFilepath) == 0 {
|
||||||
|
d.stateFilepath = "state.json"
|
||||||
|
}
|
||||||
|
d.mailcowClient = mailcow.New(d.httpClient, mailcowBase, mailcowAPIKey)
|
||||||
|
|
||||||
|
if err := d.loadState(); err != nil {
|
||||||
|
return fmt.Errorf("error loading state: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mb, err := d.mailcowClient.GetMailboxes(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error fetching mailboxes: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
processed, skipped := 0, 0
|
||||||
|
for _, m := range mb {
|
||||||
|
if !m.IsActive() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
d.userTokensLock.RLock()
|
||||||
|
pass, ok := d.userTokens[m.Username]
|
||||||
|
d.userTokensLock.RUnlock()
|
||||||
|
if !ok {
|
||||||
|
slog.Warn("no stored password for user, skipping", "user", m.Username)
|
||||||
|
skipped++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ctx := context.Background()
|
||||||
|
davclient := webdav.HTTPClientWithBasicAuth(d.httpClient, m.Username, pass)
|
||||||
|
if err := d.cleanupOldCalendar(ctx, davclient, m.Username, oldCalendarName); err != nil {
|
||||||
|
slog.Error("error cleaning up calendar", "user", m.Username, "err", err)
|
||||||
|
}
|
||||||
|
processed++
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Info("cleanup finished", "processed", processed, "skipped", skipped)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// buildTransport erstellt einen http.Transport.
|
// buildTransport erstellt einen http.Transport.
|
||||||
// Wenn MAILCOW_RESOLVE_HOST gesetzt ist (z. B. "nginx-mailcow"), wird der
|
// Wenn MAILCOW_RESOLVE_HOST gesetzt ist (z. B. "nginx-mailcow"), wird der
|
||||||
// tatsächliche TCP-Connect auf diesen Host umgeleitet, während TLS-SNI und
|
// tatsächliche TCP-Connect auf diesen Host umgeleitet, während TLS-SNI und
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ Der Mailcow Birthday Daemon synchronisiert automatisch Geburtstagskalender für
|
|||||||
- Die berechneten Ereignisse werden in einen Kalender synchronisiert, dessen Name über `CALENDAR_NAME` konfigurierbar ist (Standard: „Birthdays"). Der Anzeigename kann vom Benutzer in SOGo zusätzlich umbenannt werden.
|
- Die berechneten Ereignisse werden in einen Kalender synchronisiert, dessen Name über `CALENDAR_NAME` konfigurierbar ist (Standard: „Birthdays"). Der Anzeigename kann vom Benutzer in SOGo zusätzlich umbenannt werden.
|
||||||
- Bei Änderung von `CALENDAR_NAME` wird der alte Kalender beim nächsten Start automatisch entfernt und ein neuer mit dem neuen Namen erstellt. Der alte Kalender wird dabei nur gelöscht, wenn er ausschließlich vom Daemon erstellte Einträge enthält – manuell angelegte Kalender mit gleichem Namen bleiben unangetastet.
|
- Bei Änderung von `CALENDAR_NAME` wird der alte Kalender beim nächsten Start automatisch entfernt und ein neuer mit dem neuen Namen erstellt. Der alte Kalender wird dabei nur gelöscht, wenn er ausschließlich vom Daemon erstellte Einträge enthält – manuell angelegte Kalender mit gleichem Namen bleiben unangetastet.
|
||||||
|
|
||||||
> **Wichtig:** Damit die Umbenennung korrekt erkannt wird, muss der Daemon **mindestens einmal** mit dem neuen Code und dem **alten** Kalendernamen gelaufen sein, damit der Name im State-File gespeichert wird. Erst danach `CALENDAR_NAME` ändern und erneut starten. Wird der Name geändert, bevor der State aktualisiert wurde, kann der alte Kalender nicht automatisch entfernt werden und muss manuell gelöscht werden.
|
> **Wichtig (betrifft nur Updates von vor v0.2.0):** Damit die Umbenennung korrekt erkannt wird, muss der Daemon **mindestens einmal** mit dem neuen Code (ab v0.2.0) und dem **alten** Kalendernamen gelaufen sein, damit der Name im State-File gespeichert wird. Erst danach `CALENDAR_NAME` ändern und erneut starten. Wird der Name geändert, bevor der State aktualisiert wurde, kann der alte Kalender nicht automatisch entfernt werden. In diesem Fall kann der integrierte Cleanup-Befehl verwendet werden (siehe [Troubleshooting](troubleshooting.md#doppelte-kalender-nach-umbenennung-von-calendar_name)).
|
||||||
|
|
||||||
## Benachrichtigungen
|
## Benachrichtigungen
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,23 @@
|
|||||||
|
|
||||||
Das gespeicherte App-Passwort für den betroffenen Benutzer ist ungültig (z. B. manuell in Mailcow gelöscht). Der Daemon verwirft das alte Passwort automatisch und erstellt beim nächsten Zyklus ein neues.
|
Das gespeicherte App-Passwort für den betroffenen Benutzer ist ungültig (z. B. manuell in Mailcow gelöscht). Der Daemon verwirft das alte Passwort automatisch und erstellt beim nächsten Zyklus ein neues.
|
||||||
|
|
||||||
|
## Doppelte Kalender nach Umbenennung von `CALENDAR_NAME`
|
||||||
|
|
||||||
|
Wurde `CALENDAR_NAME` geändert, bevor der alte Name im State-File gespeichert war (z. B. bei einem Update von vor v0.2.0), existieren pro Mailbox zwei Geburtstagskalender. Der integrierte Cleanup-Befehl entfernt den alten Kalender aus allen Mailboxen – aber nur, wenn alle enthaltenen Einträge vom Daemon erstellt wurden. Manuell angelegte Kalender mit gleichem Namen bleiben unangetastet.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/mailcow-dockerized
|
||||||
|
docker compose exec birthdaydaemon /mailcow-birthday-daemon cleanup <alter-kalendername>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Beispiel:** Der alte Kalender hieß `Birthdays` und wurde auf `Geburtstage` umgestellt:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose exec birthdaydaemon /mailcow-birthday-daemon cleanup Birthdays
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Hinweis:** Der Daemon muss vorher mindestens einmal gelaufen sein, damit App-Passwörter im State-File vorhanden sind. Benutzer ohne gespeichertes Passwort werden übersprungen.
|
||||||
|
|
||||||
## Kalender erscheint nicht in SOGo
|
## Kalender erscheint nicht in SOGo
|
||||||
|
|
||||||
SOGo zeigt neue Kalender manchmal erst nach einem Neuladen der Seite (Strg+Shift+R) oder nach dem nächsten Login an. Der Kalender wird unter dem Namen erstellt, der in `CALENDAR_NAME` konfiguriert ist (Standard: `Birthdays`).
|
SOGo zeigt neue Kalender manchmal erst nach einem Neuladen der Seite (Strg+Shift+R) oder nach dem nächsten Login an. Der Kalender wird unter dem Namen erstellt, der in `CALENDAR_NAME` konfiguriert ist (Standard: `Birthdays`).
|
||||||
|
|||||||
Reference in New Issue
Block a user