From fc673b09fbfcfae308acf996e9ad5470f9faa046 Mon Sep 17 00:00:00 2001 From: "Patrick Asmus (scriptos)" Date: Sun, 29 Mar 2026 20:57:52 +0200 Subject: [PATCH 01/11] =?UTF-8?q?Graceful=20Shutdown=20mit=20Signal-Handli?= =?UTF-8?q?ng=20hinzugef=C3=BCgt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/mcbdd/main.go | 41 +++++++++++++++++++++++++++++++++++++---- docs/funktionsweise.md | 10 ++++++++++ 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/cmd/mcbdd/main.go b/cmd/mcbdd/main.go index ea3bc59..c8f82dc 100644 --- a/cmd/mcbdd/main.go +++ b/cmd/mcbdd/main.go @@ -7,8 +7,10 @@ import ( "net" "net/http" "os" + "os/signal" "strings" "sync" + "syscall" "time" "git.techniverse.net/scriptos/mailcow-birthday-daemon/pkg/mailcow" @@ -67,11 +69,20 @@ func main() { func run() error { slog.Info("starting mcbdd", "version", version, "commit", commit, "date", date) + // Signal-Handling für Graceful Shutdown (SIGTERM, SIGINT). + ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) + defer cancel() + // Kurze Wartezeit beim Start, damit abhängige Dienste (z. B. nginx) // vollständig hochgefahren sind, bevor Verbindungen aufgebaut werden. const startupDelay = 15 * time.Second slog.Info("waiting for dependent services to become ready", "delay", startupDelay) - time.Sleep(startupDelay) + select { + case <-time.After(startupDelay): + case <-ctx.Done(): + slog.Info("shutdown signal received during startup, exiting") + return nil + } mailcowBase := os.Getenv("MAILCOW_BASE") if mailcowBase == "" { @@ -128,18 +139,40 @@ func run() error { if err := d.loadState(); err != nil { return err } - d.daemonLoop() + d.daemonLoop(ctx) return nil } -func (d *Daemon) daemonLoop() { +func (d *Daemon) daemonLoop(ctx context.Context) { for { + // Vor jedem Sync prüfen, ob ein Shutdown angefordert wurde. + select { + case <-ctx.Done(): + slog.Info("shutdown signal received, exiting") + return + default: + } + err := d.daemonRun() d.health.update(err) if err != nil { slog.Error("error while syncing birthdays", "err", err) } - time.Sleep(d.syncInterval) + + // Auf nächsten Zyklus oder Shutdown-Signal warten. + timer := time.NewTimer(d.syncInterval) + select { + case <-timer.C: + case <-ctx.Done(): + timer.Stop() + slog.Info("shutdown signal received, saving state and exiting") + if d.stateUnsaved { + if err := d.saveState(); err != nil { + slog.Error("error saving state during shutdown", "err", err) + } + } + return + } } } diff --git a/docs/funktionsweise.md b/docs/funktionsweise.md index 44e2bcd..a6d06c3 100644 --- a/docs/funktionsweise.md +++ b/docs/funktionsweise.md @@ -33,6 +33,16 @@ Der Mailcow Birthday Daemon synchronisiert automatisch Geburtstagskalender für Der Synchronisationszyklus läuft standardmäßig alle **15 Minuten** automatisch. Das Intervall kann über die Umgebungsvariable `SYNC_INTERVAL` angepasst werden (z. B. `SYNC_INTERVAL=30m`). Details zu den möglichen Werten finden sich in der [Umgebungsvariablen-Tabelle](schnellstart.md#umgebungsvariablen). +## Graceful Shutdown + +Der Daemon reagiert auf `SIGTERM` und `SIGINT` (z. B. durch `docker stop`) und beendet sich sauber: + +1. Der aktuelle Synchronisationszyklus wird noch vollständig abgeschlossen. +2. Ungespeicherte App-Passwörter werden in die Zustandsdatei geschrieben. +3. Erst danach beendet sich der Prozess. + +Dadurch wird sichergestellt, dass das State-File konsistent bleibt und keine Daten verloren gehen. Auch die Startverzögerung (15 Sekunden) wird bei einem Shutdown-Signal sofort abgebrochen. + ## Healthcheck - Das Dockerfile enthält eine `HEALTHCHECK`-Anweisung, die den eingebauten Subcommand `healthcheck` nutzt – es werden keine externen Tools wie `curl` oder `wget` benötigt und kein Port wird geöffnet. -- 2.54.0 From e74d5f711ab9550a19f69e39df48c36d2c1922d9 Mon Sep 17 00:00:00 2001 From: "Patrick Asmus (scriptos)" Date: Sun, 29 Mar 2026 21:20:36 +0200 Subject: [PATCH 02/11] =?UTF-8?q?feat:=20Jahrestage=20(ANNIVERSARY)=20und?= =?UTF-8?q?=20Emoji-Pr=C3=A4fixe=20f=C3=BCr=20Kalendereintr=C3=A4ge?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/mcbdd/caldav.go | 6 ++++- cmd/mcbdd/carddav.go | 50 ++++++++++++++++++++++++++++++++---------- cmd/mcbdd/util.go | 4 ++-- cmd/mcbdd/util_test.go | 14 ++++++------ docs/funktionsweise.md | 2 ++ 5 files changed, 55 insertions(+), 21 deletions(-) diff --git a/cmd/mcbdd/caldav.go b/cmd/mcbdd/caldav.go index 8504e14..2271bd1 100644 --- a/cmd/mcbdd/caldav.go +++ b/cmd/mcbdd/caldav.go @@ -179,8 +179,12 @@ func generateBirthdayEvents(birthdays []BirthdayContact) []birthdayEvent { for _, v := range birthdays { for year := cyear; year <= 10+cyear; year++ { yearshift := year - v.Date.Year() + prefix := "\U0001F382 " // 🎂 + if v.Type == ContactTypeAnniversary { + prefix = "\U0001F48D " // 💍 + } ev := birthdayEvent{ - Summary: fmt.Sprintf("%s %s", v.GivenName, v.FamilyName), + Summary: fmt.Sprintf("%s%s %s", prefix, v.GivenName, v.FamilyName), DateTimeStart: v.Date.AddDate(yearshift, 0, 0).Format("20060102"), DateTimeEnd: v.Date.AddDate(yearshift, 0, 1).Format("20060102"), } diff --git a/cmd/mcbdd/carddav.go b/cmd/mcbdd/carddav.go index 8089b24..548e1be 100644 --- a/cmd/mcbdd/carddav.go +++ b/cmd/mcbdd/carddav.go @@ -10,11 +10,20 @@ import ( "github.com/emersion/go-webdav/carddav" ) +// ContactType unterscheidet zwischen Geburtstagen und Jahrestagen. +type ContactType int + +const ( + ContactTypeBirthday ContactType = iota + ContactTypeAnniversary +) + type BirthdayContact struct { FamilyName string GivenName string Date time.Time YearKnown bool + Type ContactType } func (d *Daemon) getBirthdays(ctx context.Context, httpClient webdav.HTTPClient, user string) ([]BirthdayContact, error) { @@ -41,20 +50,39 @@ func (d *Daemon) getBirthdays(ctx context.Context, httpClient webdav.HTTPClient, } for _, v := range oo { nn := v.Card.Names() - bdayprop := v.Card.Value(vcard.FieldBirthday) - if len(nn) == 0 || len(bdayprop) == 0 { + if len(nn) == 0 { continue } - yyyy, mm, dd, err := sanitizeBirthday(bdayprop) - if err != nil { - return nil, err + bdayprop := v.Card.Value(vcard.FieldBirthday) + if len(bdayprop) > 0 { + yyyy, mm, dd, err := sanitizeDate(bdayprop) + if err != nil { + return nil, err + } + contacts = append(contacts, BirthdayContact{ + GivenName: nn[0].GivenName, + FamilyName: nn[0].FamilyName, + Date: time.Date(int(yyyy), time.Month(int(mm)), int(dd), 0, 0, 0, 0, time.UTC), + YearKnown: yyyy != 0, + Type: ContactTypeBirthday, + }) + } + { + annivprop := v.Card.Value("ANNIVERSARY") + if len(annivprop) > 0 { + yyyy, mm, dd, err := sanitizeDate(annivprop) + if err != nil { + return nil, err + } + contacts = append(contacts, BirthdayContact{ + GivenName: nn[0].GivenName, + FamilyName: nn[0].FamilyName, + Date: time.Date(int(yyyy), time.Month(int(mm)), int(dd), 0, 0, 0, 0, time.UTC), + YearKnown: yyyy != 0, + Type: ContactTypeAnniversary, + }) + } } - contacts = append(contacts, BirthdayContact{ - GivenName: v.Card.Names()[0].GivenName, - FamilyName: v.Card.Names()[0].FamilyName, - Date: time.Date(int(yyyy), time.Month(int(mm)), int(dd), 0, 0, 0, 0, time.UTC), - YearKnown: yyyy != 0, - }) } } return contacts, nil diff --git a/cmd/mcbdd/util.go b/cmd/mcbdd/util.go index 86d7aea..20b0734 100644 --- a/cmd/mcbdd/util.go +++ b/cmd/mcbdd/util.go @@ -32,7 +32,7 @@ func randomPassword(length int) (string, error) { return string(pass), nil } -func sanitizeBirthday(input string) (uint16, uint16, uint16, error) { +func sanitizeDate(input string) (uint16, uint16, uint16, error) { input = strings.ReplaceAll(input, "-", "") switch len(input) { case 4: @@ -63,7 +63,7 @@ func sanitizeBirthday(input string) (uint16, uint16, uint16, error) { } return uint16(yyyy), uint16(mm), uint16(dd), nil } - return 0, 0, 0, fmt.Errorf("birthday prop format unknown: %s", input) + return 0, 0, 0, fmt.Errorf("date prop format unknown: %s", input) } // parseNotificationTrigger konvertiert eine Uhrzeit im Format "HH:MM" in eine diff --git a/cmd/mcbdd/util_test.go b/cmd/mcbdd/util_test.go index 55e94fc..c2112cd 100644 --- a/cmd/mcbdd/util_test.go +++ b/cmd/mcbdd/util_test.go @@ -2,7 +2,7 @@ package main import "testing" -func Test_sanitizeBirthday(t *testing.T) { +func Test_sanitizeDate(t *testing.T) { tests := []struct { name string // description of this test case // Named input parameters for target function. @@ -47,24 +47,24 @@ func Test_sanitizeBirthday(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, got2, got3, gotErr := sanitizeBirthday(tt.input) + got, got2, got3, gotErr := sanitizeDate(tt.input) if gotErr != nil { if !tt.wantErr { - t.Errorf("sanitizeBirthday() failed: %v", gotErr) + t.Errorf("sanitizeDate() failed: %v", gotErr) } return } if tt.wantErr { - t.Fatal("sanitizeBirthday() succeeded unexpectedly") + t.Fatal("sanitizeDate() succeeded unexpectedly") } if got != tt.want { - t.Errorf("sanitizeBirthday() = %v, want %v", got, tt.want) + t.Errorf("sanitizeDate() = %v, want %v", got, tt.want) } if got2 != tt.want2 { - t.Errorf("sanitizeBirthday() = %v, want %v", got2, tt.want2) + t.Errorf("sanitizeDate() = %v, want %v", got2, tt.want2) } if got3 != tt.want3 { - t.Errorf("sanitizeBirthday() = %v, want %v", got3, tt.want3) + t.Errorf("sanitizeDate() = %v, want %v", got3, tt.want3) } }) } diff --git a/docs/funktionsweise.md b/docs/funktionsweise.md index a6d06c3..cdcc106 100644 --- a/docs/funktionsweise.md +++ b/docs/funktionsweise.md @@ -12,6 +12,8 @@ Der Mailcow Birthday Daemon synchronisiert automatisch Geburtstagskalender für ## Kontakte und Geburtstage - Alle Kontakte aus sämtlichen Adressbüchern werden abgerufen und die Geburtstagsinformationen je Benutzer extrahiert. +- Zusätzlich zu Geburtstagen werden auch Jahrestage (`ANNIVERSARY`-Feld nach vCard 4.0 / RFC 6350) ausgelesen. Dieses Feld wird von allen gängigen Clients unterstützt (Android, iOS, Thunderbird). +- Geburtstags-Events erhalten das Präfix 🎂, Jahrestags-Events das Präfix 💍 – so sind beide Typen im Kalender sofort unterscheidbar. - Die daraus resultierenden Kalendereinträge werden im Voraus berechnet. - Aktuell fest eingestellt: 1 Jahr in der Vergangenheit, 10 Jahre in der Zukunft. - Selbstverständlich pro Mailbox isoliert – ein Benutzer sieht nur die Geburtstage seiner eigenen Kontakte. -- 2.54.0 From 58bb3b038ed4555643ae0a360f186d52314907ac Mon Sep 17 00:00:00 2001 From: "Patrick Asmus (scriptos)" Date: Sun, 29 Mar 2026 21:29:52 +0200 Subject: [PATCH 03/11] =?UTF-8?q?feat:=20Konfigurierbarer=20Log-Level=20?= =?UTF-8?q?=C3=BCber=20LOG=5FLEVEL-Umgebungsvariable=20(debug/info/warn/er?= =?UTF-8?q?ror)=20hinzugef=C3=BCgt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/mcbdd/caldav.go | 4 ++++ cmd/mcbdd/carddav.go | 4 ++++ cmd/mcbdd/mailcow.go | 3 +++ cmd/mcbdd/main.go | 26 ++++++++++++++++++++++++++ cmd/mcbdd/persist.go | 1 + docs/schnellstart.md | 4 ++-- docs/troubleshooting.md | 5 +++-- 7 files changed, 43 insertions(+), 4 deletions(-) diff --git a/cmd/mcbdd/caldav.go b/cmd/mcbdd/caldav.go index 2271bd1..dc8aa85 100644 --- a/cmd/mcbdd/caldav.go +++ b/cmd/mcbdd/caldav.go @@ -126,7 +126,9 @@ func (d *Daemon) syncBirthdaysToCal(ctx context.Context, httpClient webdav.HTTPC if err != nil { return err } + slog.DebugContext(ctx, "queried existing calendar events", "user", user, "count", len(events)) bevs := generateBirthdayEvents(birthdays) + slog.DebugContext(ctx, "generated birthday events", "user", user, "count", len(bevs)) bevsInSync := make([]int, 0) driftedEvents := make([]string, 0) for _, ev := range events { @@ -145,6 +147,7 @@ func (d *Daemon) syncBirthdaysToCal(ctx context.Context, httpClient webdav.HTTPC } counterDelete, counterAdded := 0, 0 for _, v := range driftedEvents { + slog.DebugContext(ctx, "removing drifted calendar event", "user", user, "path", v) if err := cl.RemoveAll(ctx, v); err != nil { return err } @@ -154,6 +157,7 @@ func (d *Daemon) syncBirthdaysToCal(ctx context.Context, httpClient webdav.HTTPC if slices.Contains(bevsInSync, i) { continue } + slog.DebugContext(ctx, "adding calendar event", "user", user, "summary", v.Summary, "start", v.DateTimeStart) p, ic := v.generateICAL(calendarPath, d.notificationEnabled, d.notificationTrigger) _, err := cl.PutCalendarObject(ctx, p, ic) if err != nil { diff --git a/cmd/mcbdd/carddav.go b/cmd/mcbdd/carddav.go index 548e1be..82ac281 100644 --- a/cmd/mcbdd/carddav.go +++ b/cmd/mcbdd/carddav.go @@ -2,6 +2,7 @@ package main import ( "context" + "log/slog" "net/url" "time" @@ -66,6 +67,7 @@ func (d *Daemon) getBirthdays(ctx context.Context, httpClient webdav.HTTPClient, YearKnown: yyyy != 0, Type: ContactTypeBirthday, }) + slog.DebugContext(ctx, "found birthday contact", "user", user, "name", nn[0].GivenName+" "+nn[0].FamilyName, "date", bdayprop) } { annivprop := v.Card.Value("ANNIVERSARY") @@ -81,9 +83,11 @@ func (d *Daemon) getBirthdays(ctx context.Context, httpClient webdav.HTTPClient, YearKnown: yyyy != 0, Type: ContactTypeAnniversary, }) + slog.DebugContext(ctx, "found anniversary contact", "user", user, "name", nn[0].GivenName+" "+nn[0].FamilyName, "date", annivprop) } } } } + slog.DebugContext(ctx, "collected contacts with dates", "user", user, "count", len(contacts)) return contacts, nil } diff --git a/cmd/mcbdd/mailcow.go b/cmd/mcbdd/mailcow.go index 65c18df..68b1233 100644 --- a/cmd/mcbdd/mailcow.go +++ b/cmd/mcbdd/mailcow.go @@ -23,6 +23,9 @@ func (d *Daemon) getUserPass(ctx context.Context, username string) (string, erro oldIDs = append(oldIDs, p.ID) } } + if len(oldIDs) > 0 { + slog.DebugContext(ctx, "removing old app passwords", "user", username, "count", len(oldIDs)) + } if err := d.mailcowClient.DeleteAppPasswords(ctx, oldIDs); err != nil { return "", fmt.Errorf("error deleting app passwords: %w", err) } diff --git a/cmd/mcbdd/main.go b/cmd/mcbdd/main.go index c8f82dc..9c86f80 100644 --- a/cmd/mcbdd/main.go +++ b/cmd/mcbdd/main.go @@ -43,7 +43,29 @@ type Daemon struct { health *healthState } +// initLogLevel liest die Umgebungsvariable LOG_LEVEL und konfiguriert den +// globalen slog-Logger entsprechend. Gültige Werte: debug, info, warn, error. +// Standard: info. +func initLogLevel() { + var level slog.Level + switch strings.ToLower(os.Getenv("LOG_LEVEL")) { + case "debug": + level = slog.LevelDebug + case "warn": + level = slog.LevelWarn + case "error": + level = slog.LevelError + default: + level = slog.LevelInfo + } + slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{ + Level: level, + }))) +} + func main() { + initLogLevel() + if len(os.Args) > 1 { switch os.Args[1] { case "cleanup": @@ -68,6 +90,7 @@ func main() { func run() error { slog.Info("starting mcbdd", "version", version, "commit", commit, "date", date) + slog.Debug("log level configured", "LOG_LEVEL", os.Getenv("LOG_LEVEL")) // Signal-Handling für Graceful Shutdown (SIGTERM, SIGINT). ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) @@ -181,6 +204,7 @@ func (d *Daemon) daemonRun() error { if err != nil { return fmt.Errorf("error fetching mailboxes: %w", err) } + slog.Debug("fetched mailboxes", "count", len(mb)) eg := sync.WaitGroup{} for _, m := range mb { eg.Go(func() { @@ -204,8 +228,10 @@ func (d *Daemon) daemonRun() error { func (d *Daemon) processUser(ctx context.Context, m mailcow.Mailbox) error { if !m.IsActive() { + slog.DebugContext(ctx, "skipping inactive mailbox", "user", m.Username) return nil } + slog.DebugContext(ctx, "processing user", "user", m.Username) pass, err := d.getUserPass(ctx, m.Username) if err != nil { return fmt.Errorf("error getting userpass: %w", err) diff --git a/cmd/mcbdd/persist.go b/cmd/mcbdd/persist.go index 483e010..710cb57 100644 --- a/cmd/mcbdd/persist.go +++ b/cmd/mcbdd/persist.go @@ -23,6 +23,7 @@ func (d *Daemon) loadState() error { if err := d.loadFromDisk(&d.userTokens); err != nil { return fmt.Errorf("cant load state v%d: %w", stateVer.Version, err) } + slog.Debug("loaded state", "version", stateVer.Version, "users", len(d.userTokens)) d.stateUnsaved = true case 1: state := struct { diff --git a/docs/schnellstart.md b/docs/schnellstart.md index 630ef2e..90eea52 100644 --- a/docs/schnellstart.md +++ b/docs/schnellstart.md @@ -28,6 +28,7 @@ services: # - NOTIFICATION_ENABLED=true # - NOTIFICATION_TIME=08:00 # - SYNC_INTERVAL=15m + # - LOG_LEVEL=info volumes: - birthdaydaemon:/data @@ -63,8 +64,7 @@ docker compose pull birthdaydaemon && docker compose up -d --no-deps birthdaydae | `NOTIFICATION_ENABLED` | Nein | `false` | Aktiviert Kalender-Benachrichtigungen (VALARM) für Geburtstags-Events (`true`/`false`) | | `NOTIFICATION_TIME` | Nein | `08:00` | Uhrzeit der Benachrichtigung im Format `HH:MM` (nur wirksam wenn `NOTIFICATION_ENABLED=true`) | | `STATEFILE` | Nein | `state.json` (im Container: `/data/state.json`) | Pfad zur Zustandsdatei, in der App-Passwörter und der aktuelle Kalendername gespeichert werden | -| `SYNC_INTERVAL` | Nein | `15m` | Intervall zwischen den Synchronisationsläufen im Go-Duration-Format (z. B. `10m`, `30m`, `1h`). Mindestwert: `1m`. | - +| `SYNC_INTERVAL` | Nein | `15m` | Intervall zwischen den Synchronisationsläufen im Go-Duration-Format (z. B. `10m`, `30m`, `1h`). Mindestwert: `1m`. || `LOG_LEVEL` | Nein | `info` | Log-Verbosity: `debug`, `info`, `warn` oder `error`. Im `debug`-Modus werden einzelne Kalendereinträge (hinzugefügt/entfernt) und Kontaktdetails geloggt. | ## API-Key erstellen Den API-Key findet man im Admin-Panel unter Konfiguration → Zugang → Administratordetails bearbeiten → API → Lese-/Schreibzugriff. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 0b3c154..9ff2fdf 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -3,8 +3,9 @@ ## Container startet, aber keine Kalender werden erstellt 1. Logs prüfen: `docker compose logs -f birthdaydaemon` -2. Sicherstellen, dass der API-Key **Lese- und Schreibzugriff** hat (nicht nur Lesezugriff). -3. Prüfen, ob die Mailbox aktiv ist – inaktive Mailboxen werden übersprungen. +2. Für detaillierte Ausgaben `LOG_LEVEL=debug` als Umgebungsvariable setzen – damit werden einzelne Kontakte und Kalendereinträge sichtbar. +3. Sicherstellen, dass der API-Key **Lese- und Schreibzugriff** hat (nicht nur Lesezugriff). +4. Prüfen, ob die Mailbox aktiv ist – inaktive Mailboxen werden übersprungen. ## Verbindungsfehler / Timeout -- 2.54.0 From 1e51010f10290ddc0541343ac255e96742b72d4f Mon Sep 17 00:00:00 2001 From: "Patrick Asmus (scriptos)" Date: Sun, 29 Mar 2026 21:44:25 +0200 Subject: [PATCH 04/11] Log-Format auf [INFO]/[WARN]/[ERROR]/[DEBUG]-Tags umgestellt, Logging erweitert --- cmd/mcbdd/caldav.go | 10 +++- cmd/mcbdd/carddav.go | 2 + cmd/mcbdd/health.go | 11 +++- cmd/mcbdd/logger.go | 115 ++++++++++++++++++++++++++++++++++++++++ cmd/mcbdd/mailcow.go | 3 ++ cmd/mcbdd/main.go | 35 ++++++++++-- cmd/mcbdd/persist.go | 15 +++++- docs/troubleshooting.md | 12 ++--- 8 files changed, 188 insertions(+), 15 deletions(-) create mode 100644 cmd/mcbdd/logger.go diff --git a/cmd/mcbdd/caldav.go b/cmd/mcbdd/caldav.go index dc8aa85..265b623 100644 --- a/cmd/mcbdd/caldav.go +++ b/cmd/mcbdd/caldav.go @@ -23,6 +23,7 @@ const ( // ihn nur, wenn alle enthaltenen Events die Daemon-PRODID tragen. Enthält der // Kalender fremde Events, wird er nicht gelöscht und eine Warnung geloggt. func (d *Daemon) cleanupOldCalendar(ctx context.Context, httpClient webdav.HTTPClient, user, oldName string) error { + slog.DebugContext(ctx, "checking for old calendar to clean up", "user", user, "oldName", oldName) endpoint, err := url.JoinPath(d.baseURL, "SOGo/dav", user, "Calendar/") if err != nil { return err @@ -35,6 +36,7 @@ func (d *Daemon) cleanupOldCalendar(ctx context.Context, httpClient webdav.HTTPC if err != nil { return err } + slog.DebugContext(ctx, "found calendars", "user", user, "count", len(cc)) found := false for _, c := range cc { if strings.HasSuffix(c.Path, fmt.Sprintf("/%s", oldName)) { @@ -43,8 +45,10 @@ func (d *Daemon) cleanupOldCalendar(ctx context.Context, httpClient webdav.HTTPC } } if !found { + slog.DebugContext(ctx, "old calendar not found, nothing to clean up", "user", user, "oldName", oldName) return nil } + slog.DebugContext(ctx, "old calendar found, checking events", "user", user, "oldName", oldName) calendarPath := fmt.Sprintf("/SOGo/dav/%s/Calendar/%s", user, oldName) events, err := cl.QueryCalendar(ctx, calendarPath, &caldav.CalendarQuery{ CompRequest: caldav.CalendarCompRequest{ @@ -76,6 +80,7 @@ func (d *Daemon) cleanupOldCalendar(ctx context.Context, httpClient webdav.HTTPC } func (d *Daemon) ensureBirthdayCal(ctx context.Context, httpClient webdav.HTTPClient, user string) error { + slog.DebugContext(ctx, "ensuring birthday calendar exists", "user", user, "calendar", d.calendarName) endpoint, err := url.JoinPath(d.baseURL, "SOGo/dav", user, "Calendar/") if err != nil { return err @@ -90,13 +95,14 @@ func (d *Daemon) ensureBirthdayCal(ctx context.Context, httpClient webdav.HTTPCl } for _, c := range cc { if strings.HasSuffix(c.Path, fmt.Sprintf("/%s", d.calendarName)) { + slog.DebugContext(ctx, "birthday calendar already exists", "user", user) return nil } } if err := cl.Mkdir(ctx, d.calendarName); err != nil { return err } - slog.InfoContext(ctx, "created birthday calendar", "user", user) + slog.InfoContext(ctx, "created birthday calendar", "user", user, "calendar", d.calendarName) return nil } @@ -167,6 +173,8 @@ func (d *Daemon) syncBirthdaysToCal(ctx context.Context, httpClient webdav.HTTPC } if (counterAdded + counterDelete) > 0 { slog.InfoContext(ctx, "synchronized birthday events", "user", user, "added", counterAdded, "removed", counterDelete) + } else { + slog.DebugContext(ctx, "birthday events already in sync", "user", user, "total", len(bevs)) } return nil } diff --git a/cmd/mcbdd/carddav.go b/cmd/mcbdd/carddav.go index 82ac281..9a411d8 100644 --- a/cmd/mcbdd/carddav.go +++ b/cmd/mcbdd/carddav.go @@ -40,8 +40,10 @@ func (d *Daemon) getBirthdays(ctx context.Context, httpClient webdav.HTTPClient, if err != nil { return nil, err } + slog.DebugContext(ctx, "found address books", "user", user, "count", len(bb)) contacts := make([]BirthdayContact, 0) for _, b := range bb { + slog.DebugContext(ctx, "querying address book", "user", user, "path", b.Path) oo, err := cl.QueryAddressBook(ctx, b.Path, &carddav.AddressBookQuery{}) if err != nil { if err.Error() == "501 Not Implemented" { diff --git a/cmd/mcbdd/health.go b/cmd/mcbdd/health.go index e421101..96fc165 100644 --- a/cmd/mcbdd/health.go +++ b/cmd/mcbdd/health.go @@ -3,6 +3,7 @@ package main import ( "encoding/json" "fmt" + "log/slog" "os" "path/filepath" "sync" @@ -57,7 +58,11 @@ func (h *healthState) update(err error) { s.LastError = err.Error() } data, _ := json.Marshal(s) - os.WriteFile(h.filePath, data, 0644) + if writeErr := os.WriteFile(h.filePath, data, 0644); writeErr != nil { + slog.Error("failed to write health file", "path", h.filePath, "err", writeErr) + } else { + slog.Debug("health status updated", "path", h.filePath, "lastError", s.LastError != "") + } } // runHealthcheck liest die Health-Datei und prüft, ob der Daemon healthy ist. @@ -68,6 +73,7 @@ func runHealthcheck() error { stateFilepath = "state.json" } healthPath := filepath.Join(filepath.Dir(stateFilepath), healthFile) + slog.Debug("running healthcheck", "healthPath", healthPath) data, err := os.ReadFile(healthPath) if err != nil { @@ -78,10 +84,13 @@ func runHealthcheck() error { return fmt.Errorf("invalid health file: %w", err) } if s.LastError != "" { + slog.Warn("healthcheck: last sync had an error", "lastError", s.LastError) return fmt.Errorf("last sync failed: %s", s.LastError) } if time.Since(s.LastSync) > maxSyncAge() { + slog.Warn("healthcheck: last sync too old", "age", time.Since(s.LastSync).Round(time.Second)) return fmt.Errorf("last sync too old: %s ago", time.Since(s.LastSync).Round(time.Second)) } + slog.Debug("healthcheck passed", "lastSync", s.LastSync.Format("2006/01/02 15:04:05")) return nil } diff --git a/cmd/mcbdd/logger.go b/cmd/mcbdd/logger.go new file mode 100644 index 0000000..e37eff0 --- /dev/null +++ b/cmd/mcbdd/logger.go @@ -0,0 +1,115 @@ +package main + +import ( + "context" + "fmt" + "io" + "log/slog" + "strings" + "sync" + "time" +) + +// bracketHandler ist ein slog.Handler, der Log-Nachrichten im Format +// "2006/01/02 15:04:05 [LEVEL] message key=value" ausgibt. +// Die Level-Tags [INFO], [WARN], [ERROR] und [DEBUG] ermöglichen eine +// schnelle visuelle Zuordnung in Container-Logs. +type bracketHandler struct { + level slog.Level + out io.Writer + mu *sync.Mutex + attrs []slog.Attr +} + +func newBracketHandler(out io.Writer, level slog.Level) *bracketHandler { + return &bracketHandler{ + level: level, + out: out, + mu: &sync.Mutex{}, + } +} + +func (h *bracketHandler) Enabled(_ context.Context, level slog.Level) bool { + return level >= h.level +} + +func (h *bracketHandler) Handle(_ context.Context, r slog.Record) error { + var levelTag string + switch { + case r.Level >= slog.LevelError: + levelTag = "[ERROR]" + case r.Level >= slog.LevelWarn: + levelTag = "[WARN]" + case r.Level >= slog.LevelInfo: + levelTag = "[INFO]" + default: + levelTag = "[DEBUG]" + } + + var b strings.Builder + b.WriteString(r.Time.Format("2006/01/02 15:04:05")) + b.WriteByte(' ') + b.WriteString(levelTag) + b.WriteByte(' ') + b.WriteString(r.Message) + + writeAttr := func(a slog.Attr) bool { + a.Value = a.Value.Resolve() + if a.Equal(slog.Attr{}) { + return true + } + b.WriteByte(' ') + b.WriteString(a.Key) + b.WriteByte('=') + b.WriteString(formatAttrValue(a.Value)) + return true + } + + for _, a := range h.attrs { + writeAttr(a) + } + r.Attrs(writeAttr) + + b.WriteByte('\n') + + h.mu.Lock() + defer h.mu.Unlock() + _, err := h.out.Write([]byte(b.String())) + return err +} + +func (h *bracketHandler) WithAttrs(attrs []slog.Attr) slog.Handler { + newAttrs := make([]slog.Attr, len(h.attrs)+len(attrs)) + copy(newAttrs, h.attrs) + copy(newAttrs[len(h.attrs):], attrs) + return &bracketHandler{ + level: h.level, + out: h.out, + mu: h.mu, + attrs: newAttrs, + } +} + +func (h *bracketHandler) WithGroup(_ string) slog.Handler { + // Gruppen werden für dieses Projekt nicht benötigt. + return h +} + +// formatAttrValue formatiert einen slog.Value als String. +// String-Werte mit Leerzeichen oder Sonderzeichen werden in Anführungszeichen gesetzt. +func formatAttrValue(v slog.Value) string { + switch v.Kind() { + case slog.KindString: + s := v.String() + if s == "" || strings.ContainsAny(s, " \t\n\"\\") { + return fmt.Sprintf("%q", s) + } + return s + case slog.KindTime: + return v.Time().Format(time.RFC3339) + case slog.KindDuration: + return v.Duration().String() + default: + return fmt.Sprintf("%v", v.Any()) + } +} diff --git a/cmd/mcbdd/mailcow.go b/cmd/mcbdd/mailcow.go index 68b1233..7b86703 100644 --- a/cmd/mcbdd/mailcow.go +++ b/cmd/mcbdd/mailcow.go @@ -11,12 +11,15 @@ func (d *Daemon) getUserPass(ctx context.Context, username string) (string, erro pass, ok := d.userTokens[username] d.userTokensLock.RUnlock() if ok { + slog.DebugContext(ctx, "using cached app password", "user", username) return pass, nil } + slog.DebugContext(ctx, "no cached password found, creating new app password", "user", username) pp, err := d.mailcowClient.GetAppPasswords(ctx, username) if err != nil { return "", err } + slog.DebugContext(ctx, "retrieved existing app passwords", "user", username, "total", len(pp)) oldIDs := make([]int, 0) for _, p := range pp { if p.Name == ConstUsertokenName { diff --git a/cmd/mcbdd/main.go b/cmd/mcbdd/main.go index 9c86f80..2eff512 100644 --- a/cmd/mcbdd/main.go +++ b/cmd/mcbdd/main.go @@ -58,9 +58,7 @@ func initLogLevel() { default: level = slog.LevelInfo } - slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{ - Level: level, - }))) + slog.SetDefault(slog.New(newBracketHandler(os.Stderr, level))) } func main() { @@ -102,8 +100,9 @@ func run() error { slog.Info("waiting for dependent services to become ready", "delay", startupDelay) select { case <-time.After(startupDelay): + slog.Debug("startup delay completed, proceeding with initialization") case <-ctx.Done(): - slog.Info("shutdown signal received during startup, exiting") + slog.Warn("shutdown signal received during startup, exiting") return nil } @@ -125,6 +124,13 @@ func run() error { return err } slog.Info("sync interval configured", "interval", syncInterval) + slog.Debug("configuration summary", + "MAILCOW_BASE", mailcowBase, + "CALENDAR_NAME", calendarName, + "NOTIFICATION_ENABLED", notificationEnabled, + "MAILCOW_RESOLVE_HOST", os.Getenv("MAILCOW_RESOLVE_HOST"), + "STATEFILE", os.Getenv("STATEFILE"), + ) notificationTrigger := "PT8H" if notificationEnabled { @@ -138,6 +144,8 @@ func run() error { } notificationTrigger = trigger slog.Info("birthday notifications enabled", "time", notificationTime, "trigger", notificationTrigger) + } else { + slog.Debug("birthday notifications disabled") } d := &Daemon{ userTokens: make(map[string]string), @@ -153,6 +161,7 @@ func run() error { if len(d.stateFilepath) == 0 { d.stateFilepath = "state.json" } + slog.Debug("state file path", "path", d.stateFilepath) d.health = newHealthState(d.stateFilepath) d.mailcowClient = mailcow.New( d.httpClient, @@ -162,6 +171,7 @@ func run() error { if err := d.loadState(); err != nil { return err } + slog.Info("initialization complete, entering daemon loop") d.daemonLoop(ctx) return nil } @@ -176,12 +186,17 @@ func (d *Daemon) daemonLoop(ctx context.Context) { default: } + slog.Debug("starting sync cycle") + start := time.Now() err := d.daemonRun() d.health.update(err) if err != nil { slog.Error("error while syncing birthdays", "err", err) + } else { + slog.Info("sync cycle completed", "duration", time.Since(start).Round(time.Millisecond)) } + slog.Debug("waiting for next sync cycle", "interval", d.syncInterval) // Auf nächsten Zyklus oder Shutdown-Signal warten. timer := time.NewTimer(d.syncInterval) select { @@ -200,11 +215,12 @@ func (d *Daemon) daemonLoop(ctx context.Context) { } func (d *Daemon) daemonRun() error { + slog.Debug("fetching mailboxes from mailcow API") mb, err := d.mailcowClient.GetMailboxes(context.Background()) if err != nil { return fmt.Errorf("error fetching mailboxes: %w", err) } - slog.Debug("fetched mailboxes", "count", len(mb)) + slog.Info("fetched mailboxes", "count", len(mb)) eg := sync.WaitGroup{} for _, m := range mb { eg.Go(func() { @@ -238,6 +254,7 @@ func (d *Daemon) processUser(ctx context.Context, m mailcow.Mailbox) error { } davclient := webdav.HTTPClientWithBasicAuth(d.httpClient, m.Username, pass) if d.oldCalendarName != "" { + slog.DebugContext(ctx, "cleaning up old calendar", "user", m.Username, "oldName", d.oldCalendarName) if err := d.cleanupOldCalendar(ctx, davclient, m.Username, d.oldCalendarName); err != nil { slog.WarnContext(ctx, "error cleaning up old calendar", "err", err, "user", m.Username) } @@ -253,12 +270,14 @@ func (d *Daemon) processUser(ctx context.Context, m mailcow.Mailbox) error { } return fmt.Errorf("error getting birthdays from carddav: %w", err) } + slog.DebugContext(ctx, "found birthday contacts", "user", m.Username, "count", len(bb)) if err := d.ensureBirthdayCal(ctx, davclient, m.Username); err != nil { return fmt.Errorf("error creating birthday calendar in caldav: %w", err) } if err := d.syncBirthdaysToCal(ctx, davclient, m.Username, bb); err != nil { return fmt.Errorf("error syncing birthday events to caldav: %w", err) } + slog.DebugContext(ctx, "user processing complete", "user", m.Username) return nil } @@ -272,6 +291,7 @@ func runCleanup() error { oldCalendarName := os.Args[2] slog.Info("starting calendar cleanup", "calendarName", oldCalendarName) + slog.Debug("loading environment configuration for cleanup") mailcowBase := os.Getenv("MAILCOW_BASE") if mailcowBase == "" { @@ -309,8 +329,10 @@ func runCleanup() error { } processed, skipped := 0, 0 + slog.Debug("starting cleanup for all mailboxes", "totalMailboxes", len(mb)) for _, m := range mb { if !m.IsActive() { + slog.Debug("skipping inactive mailbox during cleanup", "user", m.Username) continue } d.userTokensLock.RLock() @@ -322,9 +344,12 @@ func runCleanup() error { continue } ctx := context.Background() + slog.Debug("cleaning up calendar for user", "user", m.Username, "calendar", oldCalendarName) 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) + } else { + slog.Debug("calendar cleanup successful for user", "user", m.Username) } processed++ } diff --git a/cmd/mcbdd/persist.go b/cmd/mcbdd/persist.go index 710cb57..19bc1a1 100644 --- a/cmd/mcbdd/persist.go +++ b/cmd/mcbdd/persist.go @@ -10,22 +10,25 @@ import ( ) func (d *Daemon) loadState() error { + slog.Debug("loading state file", "path", d.stateFilepath) stateVer := struct { Version int `json:"version"` }{} if err := d.loadFromDisk(&stateVer); err != nil { return fmt.Errorf("cant detect state version: %w", err) } + slog.Debug("detected state version", "version", stateVer.Version) var storedCalendarName string switch stateVer.Version { case 0: - slog.Warn("loading old state version", "stateVer", stateVer.Version) + slog.Warn("loading old state version, migration required", "stateVer", stateVer.Version) if err := d.loadFromDisk(&d.userTokens); err != nil { return fmt.Errorf("cant load state v%d: %w", stateVer.Version, err) } slog.Debug("loaded state", "version", stateVer.Version, "users", len(d.userTokens)) d.stateUnsaved = true case 1: + slog.Warn("loading state v1, migration to v2 required", "stateVer", stateVer.Version) state := struct { Version int `json:"version"` UserTokens map[string]string `json:"userTokens"` @@ -42,6 +45,7 @@ func (d *Daemon) loadState() error { } d.stateUnsaved = true case 2: + slog.Debug("loading current state format", "stateVer", stateVer.Version) state := struct { Version int `json:"version"` UserTokens map[string]string `json:"userTokens"` @@ -59,6 +63,7 @@ func (d *Daemon) loadState() error { } storedCalendarName = state.CalendarName } + slog.Info("state loaded", "users", len(d.userTokens)) if storedCalendarName != "" && storedCalendarName != d.calendarName { slog.Info("calendar name changed, old calendars will be cleaned up", "old", storedCalendarName, "new", d.calendarName) @@ -69,6 +74,7 @@ func (d *Daemon) loadState() error { } func (d *Daemon) saveState() error { + slog.Debug("saving state to disk", "path", d.stateFilepath, "users", len(d.userTokens)) encTokens := make(map[string]string, len(d.userTokens)) for k, v := range d.userTokens { encTokens[k] = base64.StdEncoding.EncodeToString([]byte(v)) @@ -82,13 +88,18 @@ func (d *Daemon) saveState() error { UserTokens: encTokens, CalendarName: d.calendarName, } - return d.saveToDisk(state) + if err := d.saveToDisk(state); err != nil { + return err + } + slog.Debug("state saved successfully") + return nil } func (d *Daemon) loadFromDisk(state any) error { f, err := os.OpenFile(d.stateFilepath, os.O_RDONLY, 0o660) if err != nil { if errors.Is(err, os.ErrNotExist) { + slog.Debug("state file does not exist, starting fresh", "path", d.stateFilepath) return nil } return err diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 9ff2fdf..7eea469 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -34,12 +34,12 @@ docker compose exec birthdaydaemon /mailcow-birthday-daemon cleanup Birthdays **Beispielausgabe:** ``` -2026/03/28 23:46:22 INFO starting calendar cleanup calendarName=Birthdays -2026/03/28 23:46:22 INFO using internal resolve host for connections resolveHost=nginx-mailcow -2026/03/28 23:46:22 INFO removed old birthday calendar user=user1@example.com calendar=Birthdays -2026/03/28 23:46:23 INFO removed old birthday calendar user=user2@example.com calendar=Birthdays -2026/03/28 23:46:23 INFO removed old birthday calendar user=user3@example.com calendar=Birthdays -2026/03/28 23:46:24 INFO cleanup finished processed=5 skipped=0 +2026/03/28 23:46:22 [INFO] starting calendar cleanup calendarName=Birthdays +2026/03/28 23:46:22 [INFO] using internal resolve host for connections resolveHost=nginx-mailcow +2026/03/28 23:46:22 [INFO] removed old birthday calendar user=user1@example.com calendar=Birthdays +2026/03/28 23:46:23 [INFO] removed old birthday calendar user=user2@example.com calendar=Birthdays +2026/03/28 23:46:23 [INFO] removed old birthday calendar user=user3@example.com calendar=Birthdays +2026/03/28 23:46:24 [INFO] cleanup finished processed=5 skipped=0 ``` > **Hinweis:** Der Daemon muss vorher mindestens einmal gelaufen sein, damit App-Passwörter im State-File vorhanden sind. Benutzer ohne gespeichertes Passwort werden übersprungen. -- 2.54.0 From 05faa43541fac8b28c3cb14c8b282da6966ac8c4 Mon Sep 17 00:00:00 2001 From: "Patrick Asmus (scriptos)" Date: Sun, 29 Mar 2026 21:53:02 +0200 Subject: [PATCH 05/11] =?UTF-8?q?feat:=20MAILBOX=5FEXCLUDE=20hinzugef?= =?UTF-8?q?=C3=BCgt=20=E2=80=93=20Mailboxen=20per=20Env=20von=20der=20Sync?= =?UTF-8?q?hronisation=20ausschlie=C3=9Fen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/mcbdd/main.go | 45 ++++++++++++++++++++++++++++++++++++++++++ docs/funktionsweise.md | 6 ++++++ docs/schnellstart.md | 5 ++++- 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/cmd/mcbdd/main.go b/cmd/mcbdd/main.go index 2eff512..6e2fa49 100644 --- a/cmd/mcbdd/main.go +++ b/cmd/mcbdd/main.go @@ -40,6 +40,7 @@ type Daemon struct { notificationEnabled bool notificationTrigger string syncInterval time.Duration + excludeMailboxes map[string]bool health *healthState } @@ -130,8 +131,17 @@ func run() error { "NOTIFICATION_ENABLED", notificationEnabled, "MAILCOW_RESOLVE_HOST", os.Getenv("MAILCOW_RESOLVE_HOST"), "STATEFILE", os.Getenv("STATEFILE"), + "MAILBOX_EXCLUDE", os.Getenv("MAILBOX_EXCLUDE"), ) + excludeMailboxes := parseMailboxExclude() + if len(excludeMailboxes) > 0 { + slog.Info("mailbox exclusion configured", "count", len(excludeMailboxes)) + for addr := range excludeMailboxes { + slog.Debug("excluding mailbox", "address", addr) + } + } + notificationTrigger := "PT8H" if notificationEnabled { notificationTime := os.Getenv("NOTIFICATION_TIME") @@ -157,6 +167,7 @@ func run() error { notificationEnabled: notificationEnabled, notificationTrigger: notificationTrigger, syncInterval: syncInterval, + excludeMailboxes: excludeMailboxes, } if len(d.stateFilepath) == 0 { d.stateFilepath = "state.json" @@ -242,11 +253,24 @@ func (d *Daemon) daemonRun() error { return nil } +// isMailboxExcluded prüft, ob eine Mailbox über MAILBOX_EXCLUDE +// von der Synchronisation ausgeschlossen ist. +func (d *Daemon) isMailboxExcluded(username string) bool { + if len(d.excludeMailboxes) == 0 { + return false + } + return d.excludeMailboxes[strings.ToLower(username)] +} + func (d *Daemon) processUser(ctx context.Context, m mailcow.Mailbox) error { if !m.IsActive() { slog.DebugContext(ctx, "skipping inactive mailbox", "user", m.Username) return nil } + if d.isMailboxExcluded(m.Username) { + slog.DebugContext(ctx, "skipping excluded mailbox", "user", m.Username) + return nil + } slog.DebugContext(ctx, "processing user", "user", m.Username) pass, err := d.getUserPass(ctx, m.Username) if err != nil { @@ -358,6 +382,27 @@ func runCleanup() error { return nil } +// parseMailboxExclude liest MAILBOX_EXCLUDE aus der Umgebung und gibt eine +// Map mit den ausgeschlossenen Mailadressen (lowercase) zurück. +// Mehrere Adressen werden durch Komma getrennt. +func parseMailboxExclude() map[string]bool { + raw := os.Getenv("MAILBOX_EXCLUDE") + if raw == "" { + return nil + } + exclude := make(map[string]bool) + for _, addr := range strings.Split(raw, ",") { + addr = strings.TrimSpace(addr) + if addr != "" { + exclude[strings.ToLower(addr)] = true + } + } + if len(exclude) == 0 { + return nil + } + return exclude +} + // parseSyncInterval liest SYNC_INTERVAL aus der Umgebung und gibt die // geparste Dauer zurück. Standard: 15m. func parseSyncInterval() (time.Duration, error) { diff --git a/docs/funktionsweise.md b/docs/funktionsweise.md index cdcc106..b9e1f85 100644 --- a/docs/funktionsweise.md +++ b/docs/funktionsweise.md @@ -18,6 +18,12 @@ Der Mailcow Birthday Daemon synchronisiert automatisch Geburtstagskalender für - Aktuell fest eingestellt: 1 Jahr in der Vergangenheit, 10 Jahre in der Zukunft. - Selbstverständlich pro Mailbox isoliert – ein Benutzer sieht nur die Geburtstage seiner eigenen Kontakte. +## Mailbox-Filter + +Standardmäßig erhalten alle aktiven Mailboxen einen Geburtstagskalender. Über die Umgebungsvariable `MAILBOX_EXCLUDE` können einzelne Mailboxen von der Synchronisation ausgeschlossen werden (z. B. Service-Accounts oder Shared Mailboxen). Ausgeschlossene Mailboxen werden beim Sync-Zyklus übersprungen. Bereits vorhandene Kalender in diesen Mailboxen werden dabei nicht automatisch entfernt – dafür kann der `cleanup`-Befehl verwendet werden (siehe [Troubleshooting](troubleshooting.md#doppelte-kalender-nach-umbenennung-von-calendar_name)). + +Die Variable kann jederzeit – auch bei bestehenden Installationen – per Update hinzugefügt oder geändert werden. + ## Kalendersynchronisation - 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. diff --git a/docs/schnellstart.md b/docs/schnellstart.md index 90eea52..9473bbb 100644 --- a/docs/schnellstart.md +++ b/docs/schnellstart.md @@ -28,6 +28,7 @@ services: # - NOTIFICATION_ENABLED=true # - NOTIFICATION_TIME=08:00 # - SYNC_INTERVAL=15m + # - MAILBOX_EXCLUDE=user1@example.com,user2@example.com # - LOG_LEVEL=info volumes: - birthdaydaemon:/data @@ -64,7 +65,9 @@ docker compose pull birthdaydaemon && docker compose up -d --no-deps birthdaydae | `NOTIFICATION_ENABLED` | Nein | `false` | Aktiviert Kalender-Benachrichtigungen (VALARM) für Geburtstags-Events (`true`/`false`) | | `NOTIFICATION_TIME` | Nein | `08:00` | Uhrzeit der Benachrichtigung im Format `HH:MM` (nur wirksam wenn `NOTIFICATION_ENABLED=true`) | | `STATEFILE` | Nein | `state.json` (im Container: `/data/state.json`) | Pfad zur Zustandsdatei, in der App-Passwörter und der aktuelle Kalendername gespeichert werden | -| `SYNC_INTERVAL` | Nein | `15m` | Intervall zwischen den Synchronisationsläufen im Go-Duration-Format (z. B. `10m`, `30m`, `1h`). Mindestwert: `1m`. || `LOG_LEVEL` | Nein | `info` | Log-Verbosity: `debug`, `info`, `warn` oder `error`. Im `debug`-Modus werden einzelne Kalendereinträge (hinzugefügt/entfernt) und Kontaktdetails geloggt. | +| `SYNC_INTERVAL` | Nein | `15m` | Intervall zwischen den Synchronisationsläufen im Go-Duration-Format (z. B. `10m`, `30m`, `1h`). Mindestwert: `1m`. | +| `MAILBOX_EXCLUDE` | Nein | – | Kommagetrennte Liste von Mailadressen, die von der Synchronisation ausgeschlossen werden (z. B. `admin@example.com,noreply@example.com`). Standardmäßig erhalten alle aktiven Mailboxen einen Geburtstagskalender. | +| `LOG_LEVEL` | Nein | `info` | Log-Verbosity: `debug`, `info`, `warn` oder `error`. Im `debug`-Modus werden einzelne Kalendereinträge (hinzugefügt/entfernt) und Kontaktdetails geloggt. | ## API-Key erstellen Den API-Key findet man im Admin-Panel unter Konfiguration → Zugang → Administratordetails bearbeiten → API → Lese-/Schreibzugriff. -- 2.54.0 From c1514330e3f0657347f518bc3fe23a5b31ec0bcb Mon Sep 17 00:00:00 2001 From: "Patrick Asmus (scriptos)" Date: Sun, 29 Mar 2026 22:38:43 +0200 Subject: [PATCH 06/11] =?UTF-8?q?docs:=20Hinweis=20zu=20fehlender=20Jahres?= =?UTF-8?q?tag-Unterst=C3=BCtzung=20in=20SOGo-Weboberfl=C3=A4che=20+=20Kal?= =?UTF-8?q?enderansicht=20aktualisiert?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/img/kalenderansicht.png | Bin 90924 -> 67550 bytes docs/funktionsweise.md | 2 ++ 2 files changed, 2 insertions(+) diff --git a/assets/img/kalenderansicht.png b/assets/img/kalenderansicht.png index 574fe5bad1330ce7f6dd20249b10ecfb397abcee..825ff320bc6907f662f45530d9add8cdaa028cb4 100644 GIT binary patch literal 67550 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 literal 90924 zcmZTv1yodP*9Jrdl@gWi5)n~4ghA;BX@>6Zt^rg+>5>LTy1PT^?(Xg!YM6of5BIxz zulIKyU9*rm=Y8K@&-3hkg5+hzu4fCDc6!1qByH zLiml6E9!3YUC)@YE2QtP;M+SNXv2%SI<@biVWW%uc=NbILo8rAw_-dOWMx$`tx*xm z%R4BkEKGW#s{SIJ%8&A?2RcT0_y^0*JJ$!RohuGv*4B}US8m!1Dc-3Eu$0rrh}i+2 za{)7l5s*%FkPn8d#NyAp9Q;v6GJSMJFIl+Y{8x%slYm`+PN{ALf)UyyR-x@`2HG#MeULp;t1XHsLwUvOXaUSHWH z3M9iWgc0Btr=8K9>ipgZhLYBptoZ9f#8DA|b^o!XU?0YX=GlZLu{j+Y^`I^~?%~s3 z2#fmhh)Ih2@#)0-S|_%*$(W~~!sPlp#djz17Ff;02p$i$esT;6qSCj{X)s?F7G`bW z=Y@ECzxnZW-9#^Gp+hFe8VO+&?aj~G{YGlk1?a+-|K^thGpU{K-< zHd)|A@+&N7B6=L6pN3BgbRaX(0k#GL&}C<77QqX|Tn`HfxfIZ8KMo(sQH(bX*|>%mTaZMb9)h&$a%}n4zyz%3|sD?Z#2?~9hP`&)#fkMurYyB%UrCPg~r4_e*CHxL>U*+ttKvy zTjj;)f_-u>R=qre)6disyQFGN<9_;$D_`fWUyQ?evu|FNwU$y4C z%J^AIDvFJ@{d6Z_fk{AsgXum4Cu^kr#!?HNk&#P((p1B8*_H5XaFuLwB`J6D%HkW+ zs4nSAZdU`Cy#siHZkPJhQ9OrB8bKfdJE|Xp(Xu8L)-zOyVcEsEl9q)mBbyHWrnE0XI zq}6oGTZpSZW_&?~M0!(usIZS%VO_tlCwLe%OxB0I@VnhR3-6Z@g_ZrVgQ=UiEqv-F zGUyQf_|dD_nK^HLT#AM$m;HOq7nx{!G-}F!E`P!Io!)Hal6bhj?Qa*iKHjHD20$q7AKhz^-H!)L>A6lK&ujvSbl(`g5-4A?lU#Og-uMg`UM%q0Z zWIyB@KmOBzH5wR`+qyv2^EDrK<|pPm)*&`ANigI|yb0(2l!1unW-+xCbRrELjodB( z&)m6N=CWu6{mSowy*nMFbtlkms#Ws+Gg4Do1{L|xf5oQ{hLS{@o)IKwM9~jOM*Mh! zt=~!_c7nOKg4yJ+gUI-_xy_?_EX8I+H^)9dGe~N;nXWrm(_3OD&~l{Zn?dLM5J1uq z-Ki8h2wJFjGd!55U)DI=8NN+s;lD{nF~H2z`@z5}sg~3DqCZzzy0rb)n_X9hp>T5k z`dz)@#UF+*W~w7f@0_iLl9jV2FLwTsEt^TuLWIve*p%0Rd@_31cX@bt(Z|Q`>N3N~ z#9`Rla_I7Oj@OB`#U=Lw8{}=SGJdGtaF+Rfksv1aU9EGHPFw+B|Z9n?%Sp=IJFedW)nh+C5M>^wLOloW5)r>pqx;oWOfdnUg!y#aEi z&C2(8j}+P1ZarJQ$}mZREsm)sN_UrBUG5c55>R$@`Je7eI51TobmGw-`Fuc6D!7X+ zP0rf-6vDeT*w_#=KACjuaTsXR+g+};3 z(e8~(M}jWq-~h3ocXPY|av>WON|3K9F+1ko-cGH*cQl%xj~WytoT6Qg>-O4dReS^! zk6vO5qoBAtPTlwOa9NwEu&eB0hy)N?@GiI#PJcIbJo9UtfHQ>zt&iW2Yh39KMHPfdp z1+%3e-~CQ0@&xGpoAYFO`6ju=-R6&NEYbbXcHMw9CWl09SSBEVYJL>NUMVr&;z|7O zj6ql|RpIF311=%L1 zr~S$af>4^nkxv~j-E=rXXdK`Ow~KO^khp@LUc<@?Q1-9-D;dilwtZsW9i?P4EjsWk(7s)j3;DD+!TCkdojj7*$osRl5&=->|6*!Cy2d zk}*|-5AmNj<33O-2#S!#CwAYjiLH>8)3MJ(@W|(^J#94K_w^6RH4o7aN!>L+-H~BM zn!#QcL>4}J;wvh=82kCfvJP)gbp?HZO`BbHMQ%bsOH(DG#n)Eb1m7c)3`Ijjdr|*$ z3z+R3Z)8>&xz^l4%n=RV<0ofbTnoE=_8-U%f1gt$olkI)bpVIyyE&>(ki}xNAo7_2 zwM;_P?MwK`Amb(!?5~@)?JR{)tWu?1acq;o+9vT5dOfn$3sG8V9NR7njRa7od8qTO z_oYudYworCN=W@i{@NyB$g)r}sPUP0rt(n; zb>ZQuvdQjS@h0w5pBBI^9;egpi_IPuiACn_;@JVu`kfx~Ij@S$7Mq0a*y>m> zXunfw|1pq{iLYsq_ZYByYQ(`_gC|^FNLv$bW8zw)8mS{$F#v^8$fV5Yfd zc_iHw3`P)2+!m>~<|cv=SUi6MACY6x+~iTL_`NaafaZ-vF692&msd&tS{xk@2kMam z8~7<%Z_vzWJ!GMJ%m-=D*9N-W4lZ0hHk=wxbt!rZ1`-APom|;?Y})S4Q`D~GB6RmO zmV4`CbtFv1rwdY{m+Y9|OGbHLIx(VAFS4Oo=9GF_km=y;OhtjS=Ib0c!;>P0 z*f_@-`(^Kf$;0|nbu=~H$6ABtm>!bH@qI}vDc`m9mgBB4KLlSN8I1vG=v*18c%FE+ zZ{&-J48^`*XLaf5jdOI+xbsQyM3^9Q)+R>3trE@5DK1~=ce!m zz}v{Z0PqTzc-PG1b}#p(pEar8+s^dpIn=)FQNfBJ<7m)9g?#@h+qd)d;Jl03&Dblr zn~9ow9vum%?U~RX5x=|q_#}{!*tXgdxnfeDw%A!HMJ!f!XT5O(QrqG!H2BDHZF_56 zsQkt3oeSh4fBAk%2>>}7&!$YojXa}_S?~s)&Im$~jmdm7E0%P^{vR83N=D4WxT-6hhRoG}UX_zHFlJqNVnE6yG*VEt5^(L#-qm8GDU)*qc@dtqRGrscA zFE;XQFI1a^1$d|jKmTXUuJ>d>LNB~;697}yAj#XV}HkOE&L5L9#$xicbHNh;mX$lcHE z-@^PRx7rG0Bo2s@F-o61^ofu%?s`)7?m@;s zU}oz;b=sP2Q@E&%l{~eEyPuL4~7!*M$Er8=DTe;+W-trQT(JhY%ZZ19Q`n3t%tW zc!Gsd9hk`C6n=#^Qx9jk!`@EX5msq#+p zXk*vP{U_}9Yc}&BXy`=aWU2u4fpJ(IdX#0DYLCsM(k2Zy>oh|wCLrp9P)v@M4g@@_ zr2?=4^gQiwX)_(qvJDOMPHO`FBzFs&ZDV-bSQ7FWGEK?MS65v=dL0kD?~k+KzUVV0 z@G?H+YZ9+MdQ(Fr8|(7|?E>`@oS#&n8KV3IeDBI{bDSn)h(h=4794;FI|6PR2;qBG zCG+SvXEvomcbzzat@qW-2~y|-P|Wgm&xX(HPlfcCNq3SSA7sv}SA43n8Yk5R3|#)T zAE8)U8aJ6%sKv-1C-Jh2eE7Ah#F@wXibh@_#%L0XN&Tj_B#xFm*eEn$9qZl4XpR+Y zNlD4w$p|42C8mhT(gTXgsX7M}r58=H!Bp!(;ks2-szv)`6k*w%Z$hg{mZA`9IWSkG zMXHzax_Uw6dQfmd*Ky1KM>y3)XsXYgfD^I*d%W+t#(BA4REULv;v3%#hzH>(r1FWMb`gm1 zG)hF_t5-@(gnZ#C`>7cr0Qpcu#C}oQB8ld&G&byrgywLm~=+e+Ja8*LsTcxT1fGFrS z`Doo@L~egmZ2SdTeK005*~!-zB{@P@Le;M+fk15#4dpiet}8<1to#x%;j978w7 z&i0dIllzL|iHrQM(4rwqYLhaGnWNSC|0qb=FMMsz_sYOdiEz%;s?X%TRR$a@^R80k zg#8G&bNKK#5iky^;Cwn-A>X+sbb0yjguP~=P*j>K1W!%&AZLID!nXSp>Dj;YBr8=} zHEdW20;g{*qH8*kLJ&prC8h; z$mZZo7{SyUs}8ud${rGK_C?v496#c~u2SI;zym;U?kf9pRP1dNxui}~^RlsNKwxsl z>b*<4nagR+OEgwxW5ufg{uNoM3}_5;DYW`XA+Hp#NE!Sj-U`6>12L7kM;x5Kk1f1OyDEp!OvM~hCAmjl@$2*2$sgpojXWm1%&ofI6H5mKQg%AD^WKCpM%GBkrK9h(DyLe35jJlGMQY|(iCRJ_=zlYyD z$$MJmR9wmaA7;__-p6e?@!SZi=G0K&$K{dj<)7Z#kt4^(l1Eg+x-dFk9lcxhidTn2 z2?U&BNJKNBXz;johR-oFa(FmGl3#QM@L@Rd$~paZE8wI*qL?GI23`wIDbZ^dxJjO= z-d9bg&vW=o71ISaxPe6y8=HIu$ZR1-vIzmd0$T7sy$^LkY=y3pjLp`13?`j4bB)lMtK`Y>WDJ(FJ+@N51rOi`x=WH+ne|D8-P5BT?G&iWKmR+D+JuHO-CK zx1N5WG{@tRi7DWEI;Ij{J=S$2--IbDXK|LB4CK+vzE)A=Hko2gePAdMbr4l3&0se) z)UNP5NB)&5l^EBr+2@ib% za`yVA%YGylN!-b23a>|-2Vwpl6k}Bf`z--EA}vL8CFy{WkG`PRhyZk#$#NH#AE$gS z^v=8DxFjIk#oA;(iT146Qtw>?0&TS((8LC>-1~0;Kqyq^_}nTYLdNneSME7SVwT1q z%eaFUc@?sxeCUnc4VXdnCgt=_CP`J@#})`r>1VEFWDMFvQS2!w$u535nt#M6BDx%B zb8nW-;)ypu;jLEzIOByJGgtzouXt$QlZEU*4m&9_suDDa1ZaWzR9M1IcjVVqjte|X z=H%mexk#EBF5yIxj)rq3&ll1BCr=0x-5e^z@JaCAm`(Y#wk8K)+wtiQQc7AQ;av=2 zDxNeq1f2R^mjC(z83hu69_zOARR#qMHBs1oS>(3n28#YTS+L`S#`K%M)bA$-3dW$* z!t;bjJMr21@l5N{n1-RBOolCJEPzL}cCu(1Xb?;kOl2Okji|%`$sJ(tC1TLSb z7}RFAY$If?hiH1Ey_WlsVm^;R+I)nEm9xDqEc z?&JBki==s2?yi|6SGez}?A(H?!(dd8-BW_zNtp>C!rl`K0F>WF7F0XXiic3r6Duua z-02@gLJsWi(!J9bFL9$^kn=qWJ}A-xzh5_}{`W&!F@c9R zO=vMa^z~=(DZOg@NMJ8%19}ZoQ5xl-ZN1 z3FLBf=|KPXnKvI1>>mqIqRr!m@1Fa=QydsVh@@#_+D6au36R49 z`Hpbqdjiq(sp6%bxw?oc0O->tqOC`5(*jK#yLoF?#)wN=#Y9y#@pZW@S*qPwq|3{{m-!~FL$WdWB=_F+E&7s#S-zehQ~29m1*msELEspXW_ z07z~Oum$05IN%6&7F_sQv;o{Oo98;(Vo*`&2(-CSE8u)vS?Y7a#pm}Y>meoE4D)Vx z=@$Oi(qo|jrC?!KzJ9(EOR31KqI3h&Y2WuJro_tHzo;X=3XOf zw5MB`HZsf$EBZA9QhE_+&kdfDobQ(0DD>z#?iV3klc$tZJE1X8|F{)4up2Vl@_Ld2 zHQ-iDn7}ckS`D=O8XXj<3l%S!WD@-ojX=rtx=8^@j+4H7qo4m3r~ivtK{i^-sf7nR zqtnb11%J5NU;YO4MZ2!op&FO&blYbpXZ%8^GTqZ>e;nD3GYk^mgl@ZP2IGK)!*3Py zcN5!G7(KyfS1vHL(WSk&skd=``Ut17~x%4em6E=?u^uWljl6xgJJt+3DE#UC~a4W$mG5DoO<(M~6HU45~Du zZVrxJ2rbL;pCl}}LMcDRSCG)@J<-S=axnhN_$=PUjb=s00eiJ1RBvg0T*&Pu8}x7L zUR0p5MO6^197M*e#(%Q^$O2}+?c-B>Lp^=h1)CAxCUk$)bS&{^cR9Zm!-~kO&s<*b zi8~r+E26w_ArKwsdjTV>E)Ed}Gk9y{($mUJ- zoqR3`>st_Tn$AvX5O0K#N3%fE>m3OjgGeFqBT!}K?^`jEnPgc0j z2Ibl&nag0)MSUG#UBtJmSemQFf6lK^c{NCtbkX@VOPZGp`s!*jl|UTb;3v@a(6@iu zr?1}G0;-MF7&kfy#}G{oHVOv!lK;q}@SAL4GoBV>mq}2~8M|?F!4aBf8x`-iJLu@b zVqzUBjm!VGgclz7)g9Vc7n*v8rDV(lIn4znnCRJvjf6U5hLP9uaxbhVi1IrN(W`Hz z2fc}G&QTImbkDZyf4moZ?{|n}Z2Yd4_>()(NWps?cA5Spfb2ui)!jwDDcJh}`On{x z5R%5i{Hj8-p0IPa<74M6vR8e3iX!DBHSstVC!B?HI)*%rMk|*mdIIzTk!{z@^-y?g zTM5ov?58itIEXu>pvJ;1b>`+~2}OT-5h~g`!FE>^gOrq1OH0cY0+G=CokQdo$M9X3 z^7*|FwG6i+KZ9PA(k3n2;;oXj@90hqt|MFys)f}zYLI|3hOR^Q^=beGF0G#xPh2qa z%Q?X^*xyn>TN8mOrBkBI$WpiKiJycBrhdA6nq_Db&+b22%wh(BM3qoG`jmcz`@y`` zV+I)gzLN6;7D%h0xhyu%?Y6d2=L15aAUo?Y(+)t@?)QAz{?o;M5XA#kXMb9PnTa~S z%{lawyUCoYFzYciu=wcBR)`zVH!dxXHBv4IE{f!fo?VNVo?JxtZo);-3vaQCQAI^% zJTqj#J8FDlmg9Gi7f6iF9uMc^53kW3G*WEl^Lq(jm4ZLYin0|g)CIhfUlcbbeUMtJ zan^V3wM#bG8qMvz1IU3jSB5<<^uAP2@~?)`3s|XmiKhmvB%R0MB#rHM7Jzg4NoJWt zMDMJ9&3`T_%$ET+$p0b$o6G@zR!j7=H$a1v_`#?0BvNY&18cE-nE;vGz4Iut!Hmyt z@xA;4cq@O+-T2<2YPP-%!e<~hG{yQ?qANsqHTen0pKvWOM3#ST;dW_jKi!g=$YAJx zVl)5Y{nsj+RNfF8b@Nx$^CVZ#=jx|~yk|biJqb77dtNQm=s3DLm`3SLJr8c`xAMbG zc<(hFED1`|x~@P|qLZTZP2(vv(OJ0>F;>1iNGCn3BV&42y0E~`6ZhjQla3pksjPm_+CIEJjz6a4v<+>?4u$=+$nO}EQ|i-yxR zVr=M?kuLsMIZaY2IFKFy_9{_GlZV9HF{P^5a@I$pT9T9u{LHz&E#z z@1lyDIbb@0+~gp67W9k0Ph&mu|IW?1^v!(uy&OdeJzR!=HyAwgpMy6lcJK*Y0Fh{X<>4Ng z|DD@-S+S(73}+O&MKLV@WX!))CyK8|m-loX2hI1W;SrVU*9HSt-cIj3m;@9Co~DG& zP8`gYw;#F?*q@q=)co!`koCQ7K06qk)CGEwI4W%+W28=YqF64ve@4RI*DGMp|t5lnCQwSD3sIBd3@4tRzI1V`e{f!DLxV4 zr`<~dhE452F8 z=gK`EFE9|(xH7(Ma|{*wkdq)sH27g=IG`>s%$jc5JN16Vu| z`;BY5taObQZx=3Hr)%3E|97Iy_lZ=RtRb@T_cUPRre@R)QyGD$;UBkoVLBI zmwjPL@lm!nXFcx2Rn+=vZwA-f(0PZ@z(0=Dak5&#D;ax3f;%oAHa0)Ujp}{9fkWFDjIo7>|M`@ei#Es-yn@S(oSHPa%(w2e>0S za)TA^um@5Rhwp)C5zaqALQeWbY3AJUcE5cA+dNo0Un@*j5h4}U)H$lUm7I{i)R!rg zT3sC!s)Hn-r29dem~~FF5G@*C#@PI%Wm#sywr4fxTPuV9wI)Rb@ubJj#88k~Ae&9! z_Pd!>pSR<~prheWI5P6f_&I%n#`&4=O4Xr22G;)y=NBZP7#< zFo$Z*c(f;qn=a>pOC&9{Oc<&x zhp8Am?HKywn@V<{m*UWKtJ`{+>AZ$9@g4*e;Iav^=CqkgC6$P$wKBY|7a;dcr=2{w{#Qj1TO zFqk$U*>zV9KXXy~6X^rr=}QHGC=?QsIC~GsM+eET%D^3j@Lm8RnLrF_1n@l2VCCNm z_&1Y_p8qPZzi_2#C4lY|_oQaN6O3~{?KHavJnu^|;g5)GAU5Tf?_R+8)~YM*C9}6H z1-2dR?a3jCBeRjr(}L<736H|S;=!MH^oQ`EE$Y_s_*zjsH0R2i#@5qwzK@Mj z4AytNTlewKo$0=uJW>0Z27}-^Gz4N-r9i^@!TmCD#WZKk>ihV3KE-#MCZGE^#3Ax; zsezC~D=^(5F7p0fsNs1&=Mn`dk8CEg@7_fc-3kEvDa8lgOm8LNsa2SdCA-Y?cq1-8 zF;_J1=3HH$HvyuF&lpKXEQ{DbFjxcIw>I*MIUr3%g8*F{Tv3`%*QPx6^l{9&l1g6n-`VUU$;Ys1AR{zd$AS6wLNjYE|vl!&XWWiPshQ)7+1s=-e2p`^?L>; zMs?uTnk)5ws_ddd-{ie+Ec#}6AcddgBLy+>r(+0wuZpR1)OlIcZ7pV1p2O|f(9B!@ zpP?-cdVpoc!fLsEm1$OP>O)TtXvTSSaJVnA#wmdvZ+SFQh`ve|)5!tcqRrC<42-dr zu6_+5x8L)-5$e}(x;lYAchP{;d2|@~;q*caa0T0~*M=KM|<-mTW<#2+mOPyxm$neWH@1esY`9t#i+jcNEn(}7nb z>)-q{%@{qG3hvR-K4oS5_S;-07MU@Z2mN(;q1GNSqS9@BtB!WkO08<&d1w4pyEW#} zkn;}tNS8G}9H)3@a=u}Yvs4VHPKKO&(AvQLv~nKjqgf;g?vG zj2@XE&dzEcFR!dH&S}&+?*%5Z|20<4g&v%n%I{GL?pv^I95`^4*JW7w+MNQp0>=T9Z7395IDAcMuJDt$6nIvLG%^WIrFny4I$@k zD=XDlvAJv4DlrLShkVn%%f^K#=~4W~v-Pa*ps~&kx~^5{%+ZNX82vRKWki>Cwo+}y zrJG|y(_z16^(Ed@|Kbg_DyV5V=Rfvv29&(yj8=uJEZy8#-)uW)$V1QvO9+M=Fb zE{z7tCsxf~^&=E`w}-qwKxY4Bu=d1;n!QEy*W4f=!rWx1P0cu${(#PE*2C;C#DM-v zSfC2M1Yl>Ni^cbsyd-J~->!B|Kn65jlP6mv{477dH(YM#eJ4sqK06AD$5`v2fLKz8 z6nGvqoSPrYgETlQKbi_)iRfPaxb@lD(&>^qB>)_0_AaX|X17SOep?g$f$wqTIq%o6 zUq2JDTn~kLmz$5}9?R#c)bjw!Ru$x;h2p6ZbG*;UQ2ES^`}&GAw(O4^^KyScuR^#8 zGg>@F!?1+UY(ppjTdUfZ%F661zHhs9^HviXOO{5NineoMIlV9nDr8mdI_Oh~E=!gS z(5u`u^2QnllFC#|C0IkdaPEwhN53k1_j%pkKxgsH*IwzmwI`1zt$KbK)+N7o8n*Vm zzINtINVTaFQjd3*5^x{2h7YstR=Ock>!$J!%R(ut!1n73bs2_6Ggc;&pXRVlOaLJ< zLaA;~hEKn0E+@x%#`_|CZkk(LV#~~VCN;vEi|-zS7$KM~F^9M4H91@3pm3eYWz)Eo znb@0^UR1q&&l(e(h}94%S}yZ$CUY`^{GWl5!e)rr^KQ$eB_(6h#>gp0B%C!BC*RVE zeqScE>%=OkTvT_2L7${6WMr|OJ?_DXuD&!*)|=ifg?FbscV#akHdDPX8PVWCfInt{ zj&H7q$9}lJf>Q{1PJWV0VBboxC|o+5br>MyFpoYG3nOpvf;;#GTycfr*VXGRcD@KAgpFB=;~x|d1GDC`Hy!fD zSd{M66>N4$7&*T*nn}#;!`0*`c%xDy%JjWD^k}EjK1feOyZL%Ia%@BnD-MFIYqiHy z1VtgFJd+EEFOsYX!YDmSY5%QP5=pVfCP{X8?u#MH4tI*0uGh_C&t`e z-ZoDe#n3%Peb}Ca`h=ga8w>+MvL3rQ0J(O6vfJl=M|kORFgnu$!q;u!VjbxE16<6$|Tn_mNxa&tW_F29a~dd5U1vX9;F zT5zo!H?_HtP(5{^K0N(u*n2Ys+2x%|qZ~jj&xTfz;(cirjTw@G@TJ5@Wx3^n~}O^o?{iA#ZP;QI8nn~nYJbNMoj zP)G#O8#AV1fW%v`&pjr@h03^Z8-Puvn`AX+%ldU>-QA_&^%4W;9>%A|iC07)F4Q|hr}`Rs5$U0F?BzJ0bU+{>;xPI#I2L= zu+ulQ$Dt_d>vNx)6@aTM{XJ%nN|#R;gDF6b5ajjl$J5OrgUgextIJalz=esku#W`VosCpj zB|qNZRXsCBagXBQK_i9Zknx@X_?H*34;ZvQLs7phV@*|vUpQqGl2Bn!riF)>eIE@t zTBld_mw=FMOXnl`Ca*VzAMO})Q_0iJ(ZtEFXVfdq9~0jKW{`Bpm}3+yfcj;~IY(LJ zvN!#4gtC25+UINh1I3>?CKj{{dtx559tRB=PSBytjTDbijHkF}hg26w9Te$fj6t{z z;sV(I@0WDBm6ja$rccLIbuy?#Tg$1v`#2_W-#|x5fJwg$ur*^)+8qftwy)d3u3Qg) z+?5Hh&Nsu;tqW)%F@V5&bb42HykTp_#l=9GFJmePvT>!;h1n}*a>q% zwB3bHSu{8nlQ_yTTmDwp#I8=UFBzv#4V zJqxE20_-qCiLWc~Z;U4WzRQ-$!WONotLrAGJWn2ARy1E|FR~zkg z2=}I7;~A(VkGq|)p2zRKuQVnpi;^0|sa%;)FT!sbu55@@Clx`pxZOcChp zf4vJ}>WGdzLyF|wEssMPF`eWwb^MG%d;k=hWC5>Qo(Z6+BjLMc1JsWS(0Ql_xiRbC zjpT*K8bH$g*TC5CH5%6@-(NWCg)SJnCsEZ0g{z1ehsYvh54I;Qj=-T3XH`iP8cbHP z=;cpuS`>24-q=1iIxnl7%T&9HAZQU05f9wMCJ4F|6cnQSCa-}RR|o*e)-}&p!a&CN z(}-A9r0{)tscnHDf{!0B1pBQR4X)-{3Zkt$xD^_C$x4(q37ALTK&=NK^#J3k)IW^QE)9U2E+ zn)81olfF-Y0#fGAQ$dJ#NQQb!`=4cz-n8xmR23|6eQRmc8vnDUgqOgU=n>0i+tl(+ z=RG$li> z9uFgg(-?6FN&oX`Vn*Q6qL)8SHK+&KF-^z+v#pTNz_wQ0`PoVEOkbdvWB*5M5q$%+ zG!^O}Bz(YN*Fn?x&kMJLLN`Yyen|{Q*La9p0EFtl?U9Chb0#Q`Sd3`Qi9Ra-k<{cN z=+m!Vf3V-&@(VgP`Rll(Bu~LFHW=9ic&3u*+Xq}_ETWQguo9M91&ZH`#uBBhx>PlC z7&C(y89pLFcoI1<FxE>Zq+^snLet3l z^XPrT0GEIhX4e8R@bEV3Db=55T_gd{ws9$28`#*kzmNOl-}n9N!-a2i3crVU^-+J+ z?s;fbDezmwaX$Wk*v~gsR<|pX4tdc5dYX6pEii#w9S!*O;37KvpEolNLYIdVGG(>e z*0Xncv%FyhMt{>v@CH5qJk$(a!3~4_O4TCm=nKM7F8ZkhEV`RJ)0E#Qd-GxN0j8mmQOUDW0b>{Gq(Bp3 zSG{64?>qo~==DDXvF{U#A4OU0hcNZ36tXVw0Z%1hl4bBars?sYcU;8`Cg-14Ud5-{ z@0+HDH+=n0deb_U6@>nIN|})6F;AK ze;R@ZKO1KXa=7@z&(CjKG5-w%yWYt(P60VF2sIi}?*;|Nj{#N1rv9k?M$OzDYDwS( zws0eS!Bh%J@vUdpII6&0OF`z;B8v!dZa&Mxb*X5 z82=d|3F^9v5gW}RAOw8dfS(^gu1+V!L`7@0$o}dAc~$K|8LKI0f2su zjHHu#4h#9@y&S|a?+NW|52K}~23i_}gM;0f-Hj+xdHE_w?b3^hHjQ1qy_OS&bGHdJ zbyGJ0S*2eaCxc%cJAQSfoBRt4+N(7K#a~*$3-m|7X9sRR0E2jG^rZ`po44;jLBD-l z%V|vcOn2pkK_;P9q9~vy1JKB_m6Z+Xi_OQLWx5QB5PbabVbPV-zLU12{AWavpfb7= z3mWs6<{yaF3dTIpBy=9a+2nKOw3oVjwmTt*>*=zAc-)m5IYsz#?U4}}-`j45?QCk9 zZKei%4gHaF&Y59j`k-u#^lx*ekzTse+)v0;126)$xMn=G%auiu4bqi%HM~ys4oJ5D4ldW;`2ypY zi;+Gy69Eszhm@TmRj$^jMZTz4A{H5X9j+oOR8}kHY!CFLbouCc@#kIZVIs1 z`1p8w*)3-bUfQY29+oC)?0CYJgfzyjC`#kw0&5v!JM|Gd+m|4>sYa2`b*{>50*R)* zO_%tOVz06uKaPEjJDc2qpCLmJD}U?CmoYZL3>6+mI*d4Q8~=2ptC1CAn;A7=9DN~k zxmjd^6c%7}=>~Mu|CK;?HZ+beSM_;9v^pMp72_T2>EFJ+s9yA2xIU{YGZ~m~^r{2r z1PkNf;4o}NO1*#~paWa2Xx%d-sfbkuu6b9QnY(d8Q526JInG#B*{Rs5n)bvp_jGnr zUXP?+pDg}(FYXd0*M;|Dq} zF#Ic1;S5||t6Wz@%r8kT`=htqDM z)bxge3$m7juia|!Gz=zd(pFgN`1Qh)L4Y^kHfn;-?8@+J{Z(dn^yjlH(2A1Ew~dXx zI1jyZDZ=HBTh?!H?RL+oy+Pp-xqCX!MF$IdR8FK4`U-EA)7T z8utDCwNRGE@f3~g*$%_g=fnJG&#kgZlU4>sJ|VXcSkyFHnpe9UWz5 zLzRt4g}s3p1Z7n%qcvJOy7*_3ANbu9Wy2Rp>+yM94=G_ANe8HnDz zH9JdrdK98yF)ZC^WcQFgaoK)TpXeXh{B zBb02VBMhL+aNtbMIUm|5%P6zIMz$Y^X}^8@)98zqsM+bd8^-l3idXvR$aPhi;W%4A z_sWb-lM#2G36D&upsd1K9p47cMKb4gZe4h3lo4sxz#MB9xqx`ss0pRN!QgiPNVkbg zq&F_|^rfDKJkQ^SO5O(D{3)F0`SyGRoNK|;01^-&2?5v`o+<>fLJ?x^qT!DR8zrXc zcrlS2Sp!tR@8wPWYfprt6zyG&E|(}j7X}gv4UlvQb@{h_dd!sKv0t;+^JV6XE=SJ3 zQh~ocfEXWM>~9)ZVh-+EGf+Gx4LL?$G@nBrwOGk^gcDZYd9v$1mlG=h;=4HdilE?J zPkNXzRB-Sm5=a%PsbNX`6GaQ-1z-!#8ytkYhtDAEI5V3_#c4U&+;}VTyR}EL-!67| zJE%ZKSayWkXITW~(WIrewmmp36(yiiX`>Jm&mkLEW%VNKR0gNLD#Nr`W@Kap82t-)am>|c z^I;`US+qchM=Z4yEhj&3&4>j!5)SGW8wD-R1-hEP9Rm^oO~0L|1&_!Si?wGvH0$A8 zgiw462blC)nRa=heWAs0@*=Ko4RmrqC8qqhTMm|EjB1`i>5sdaidN&2&m;2V&)qu0 zC{o$Y9x}XDlb6SR!?2@p)XL$EF8*@eJ<`VJ;kzRx=lP%0?=Q+T9$;g)mjsJf?2E{53@AhRQ214@e3w2+@l+$dtPGU>H_D2?qRl@RAl_nt~d-j zl8W7o<$Q2e=)3U`7?eGD8!$Hn+ePi(=b&{iWkHzEB?C!W&ZWz9?r+;l?#mX8hSfeSs+mVSI80 zAjwR#Wu;7VDL0Re*I8-Se-pp7t%v_d*H=bG+5CSipn}qfN_PuLNtY-HNGK&BA>Aw8 zEFjXMbf*H+4N59XcXyX`F174FvkLd`|K>TzbDwkb0jY3}FX|S8yj8i#E7fD7RNlOK5iZ&`2WdEEv}ZJ%Ni8GtiQqDg{K_rmF0MAw zK^iRK;Zf)Mu<_KwpMQ}0;Pvljr1^$|7hPA-?QlwB;d@dVlQ{tfY12F37l?ivF7}kQ z)_ypqC)ps3HH#o?^i+QO*4$_B&qv0-)W_$;-)sJz}m({YQ)RHRBESgMeFAY2&8%#728-njNQ3P`qwddlHQo6xPd^=to;kT846YXLAF@%5 z3EKX20n~>5#FCne^i8qK4i3DxPUPb_DtkW`GV6-Txql?*ZqJDkok@mk`%l?YL((?T zVXWGV5od2yRMk&Ze>?BnhuDkBX{|jzfENuu&MtJ;ex_|DGoo%0M+N^SqUk+3tLpJ3 zO$=|Dsds$e;G^EhU*dsUm3iOitQxheZQ#vjR-klQ8BmJ2e2m@vVwY(nwtNU+-L0AW zBzDCGn=q=Qms!&6x^-il^#jcfp64&J1882L8Jn0e))rYc;*# z+w2|M&USJ{nPbY$063^=SrAp|C@X~a{_*!{I)~9N(BmWz)VAak3&y9xby^rl8M{uu zOY*xTp8Hy|5oHk95+CZ@Y%Pzaw8+!^oa(;0`VoJ)^y&G2t zVFiYh_CGRbI<;%uqdA9;Sn|AhV^S_j#F`O@bhBsi^$6IqtOPlf?LF*eR>w^I`dCRi zlL~L+4)cTUE^_QBHN^A{j~5y&|Lfo`l-ss!SWU%Y|MngJ?b+7w7NE{aT7dJJ{=)zP zFxdFviQ*t5J{462w`6b5=g)QU{mx`-Mg|5=`Jcf0`nS;KSyPPN{d9!|*!85fil0t= z*wKPT1 zA}=sqq5=;3r0_mT^`*9iHx*XTSlDec*n-tG3`RcC|5yYg<)hVRQSEk_e|iR%))$fw z9{!KSgzsjLfO4OVF>y<##w&-SJHp_&-B0kqS*`yt)COoCOO2aad{ng6ZDor)Q+)Yq zQ)bRzx!GHE2C7k>n(QE+%!P-PxiQ zGfboLwIm+BAz0c&grU|{KdgaLFVCOe>35o#0ftx$4D9{wf!m^(22a;~tkD4P*yHuX zn?om;VzSoR<#}5H*COJeTn}!s&Ks2uC`S}tAA|y*GdYv9w^AjZKX24ZkF+k# zM`DQ1jv^vLm#R#26zS83i>VO1Hq(1t76~^A3{3*V&Kr6y^|(Pl&sY zn`A@?$A-JHPUsICU(0HiQ$E4nUY3p?wsyyL_6D8%InUW4@9zxBn#{k<3EbG) z;cP_v@kUiESWy}-j`(q&I&7y}yAQ$GloehWBKDnns=On>H9vnp*=zUR$S{C z_>2FTqU90$2+=f^dZZjq=BY)r%@m265*vTNnIF2@dyY_^NF5i=%()n0@-li?VJR+L zUR8TDg_aq1-=^;LtenX%vbP-xmX27pQE7~nU%ht{@+h*jm#x7aGOWIWaSKCTozH%0 zO#%u%Gm7pIGnktgK@5AE7$~jbt8?u0*c}wi?a^M)T)X2BuxVhJo;>p(2?xxyV0B4#th@VG*kXHuBmto^ZwmT-qEIO6PSI* z$ei*t@lrYSmlm2TeK1o(H+&Hq#TC3S}_GK9J{dn;HMXSLm`%KwUiIRPjJTE%fDe=(|5tXy>r z()&Kw8mWdLi-`$4Ce18owHuwi2U|K`4V7@&^DH+Qtr=yb>BUxk-OrymIcaH1*4?3! zTR49Py*AdX@32B?^+!q#)nhi2LGlRQlgYB6Cfpk)#vT7;`DW{=IdaC9d-~U$tc`XX zM9>WRNhtOoD^*qVVA>7WI(ao%v-#(x@tKg@4q7~EL`>ZS@rl0$ zk)SMlK~ISd0YcYpTmD-=(S@T~>dAJhm+Wyk*CZ}Qrc!8s0y=tvf1rnS7YlQG@XIcq z)14GsmOaq85j}j!{>^^knRtSLpa3;B`|)`rn#8yJjP>g-Pcuz$9GoB@93Wa z79IsJ^f{nN+UZe69uz_3V?m%?hRJ))XMl*IC`&rP!2!oDyvDQ1OGkt*J+}ZZysH@X z)$V6=tz+Zr0tx4o%K_92b_ZJx3XFy@#Tw7`)=`ybuH7eCs?#E;Q!>ThNn0N%4kn^WDn6M+GB5AKet0(yNAO&L~mMb#>FDriQ+T%OV=}4O2-no`_OGRGl%;>2_ z7^-m7m)z0!B=^2d9+pr+c}?%?@pb>D)h(nXte=5gQX^2haAWaTRw9>OA#`MS3})ax4^Wh=#A3xrU{x3jBmZZa4DirWJWQ@XFts+ zP=liEjs{{VNWHJIe!rPTTPFoiLEf9o5c+rlU?w32&c9{6ye`aY5_Vv%ou<8ifsQ$b zcGA0=*3kIYGrbX7tXSG@*RB}Rs-F?J!;8irTnKw0WZAn|cYZY5du`M@vCwB;c*0RHCh);(FYVV!j?Ao#%y7Y(&8+fmwW7UN(Q8$wQD< z^%}gf?DN3_`PXR1)|+Jw8q&4ri4yCj(SoxLZRx{XIH=a!`S&D)PcnQq2<;ASTgCpRN|tbc~@+sfDOFJ#V~D(V(e*d0|h*FW|a@2kNLFmG)@)BRs-QvL+%35pP8RX6SIdIPHXS%QENB@5&J5 zOQdCvLnAnVrRIm)-O;p<9&XH<+uw-9MB7VJ@ zByubU2dxUL0D~Nc)^fa|#4fn^>C=Po(h!+Yk;o__O2YlDQq#z*?|Ho!gH!^RmbbJo z^73-9csbFu6nN-MtY$O2e(u{t&L;$37VneKr0TI#{XkU!K;b%1_U{OERaYND7JK_( zf%$34)zJB(8#5xX*~&uES1xBfaIkVRW}LsoijPGYBI9@8ExCb^RQenvVUgj8Kx4)q zZXD@Bx6aP>9$QrGrKnp^`d?uI9n~X6Z=)@llEe0$^9HV{-WtMOjd94KSk$BN`U+ox ze?#dL`&7cxnh^tcg$Wv0+V048|1f@=^ibB12vJ?t=(9zo4L? zV&Ugk%;fYAxdRdNtHG5)(^45^EaCmr%pJ#cO7qR~w9>U&8O%&5kR#YAoq)~E5lljx{CyMq5|K^}cmzn$0T^__-F)9|aWtz5H z&zsRF^#Cc6#%c0wKZk1Dm9vfq9~q-qS;!ezS%~lH>U@1p(@U}3zBcM;=_>n(T%pc z)8zdfn7u(n0^TW6!NANw>v_J>=gSQ1`)H0F=LEYC3@Fta-%7O=L#G$NtuCk+U`|$Z z=GN3WN=hc7-$DcV%fl<(KR$y5-Y(;++y6>w1v8qK)gX1#_&H#-gu90u!;pg0*#b8# zq32-zjbs-%oxQ$pr~|4(=b0`E0r?+SOv16Cx!p9N+x`W8tNCN#bHsx-dkvV@`TMh0 z*t){-x{mO9t8+`~rWxY6tTFd%zfLbhhm4 zk}Vvykr=n{+yOeLVE}A}5;h|KDJDG>Brf=L(Ev9WXf%3?vn^18LRnp`X#w<#l3pl^ z`rWEp{RlPd!px~zodna%?H&@F7cl$1DPxv}tw*gg(k*@ganUhHK_UEDin9ZFxY*P- zqVt@^CGVOW=xI~Kcs=W8d1x;uwW@TwdRf|{ThD47C#3e>+Y@cbGl6mV=@uL9)@m>0 zcr9!1VAVO-iV${^1aXo$&H53x!v957vH@hr>knot0Os-Y%Y{W$3!8Z7dSO;S!3orT zX5MAWT+4QA0ksrZUUyCc?ap@3%%35wL@3OAL!j9?*xl9g7qUc=?qae=F!njv_rvyO zKJvU#{k?c*TLPlG6(}F|iOfW3KSpE5Gc|o_u;>;i4%?xUzu(wk!2s)Sox`83LPeTYTwsudc;Y_qXJ5apRJ$1lEXZ6Gf8l2{5S(F#=7`tHOoG#@9*@@(m*MR!;L>{coD~j3OCHy&SEx^TDzP4GdD zPhS4MYL)+(h2>p$qSKczsfl!>N-08`Z#)bZTQKu8_;IcG?w~nSm zdyEEn*02apq!|N4L!mU-?|}^`otW>z_3+v1Zyrmjh6i`t-l#4v9(UY37)KZ@s$n}Q zR94&5Oy-M`w zR}wq!&o8PX%q?3bT8&MQnzrA=&bvyMn@jL!n%33q{P}YvxkW?WGQ@raO3TMrQ&KXK zWn^*&8N=rohrDO0CdGL2l4;gR^bV`=bZizN|7t!ypXlLy{9eJE`AYo5p^hR-$({AG}sAok%GQ_eLr5)Y4k#nK>C+~fy=Rl*-rQIKhr2)4AT~j_K zG865se3F|c}2*Ljh3 z*7h%dD%RU8dI6R=#kaCi2asbMucztUMjBQ-+yq*uXDMFvOhT1!A!2A${-Od;(`|`z zgEqyzrt;O>91Cj3icK)=>@Up2hYP-G0eU6rkRBNe-?!YNo%8p8(tzp_(MKbu?4O7g zw*GD)Uvbd8wGT^if}S8VQu2;h&%Dh3d>G}=6W4xF6f_xOV~fLjl{OZZ=BY+y2|S=j|SJAYZEm{zYX+ zFU%D8K2Jxix>|d&)fvS?_~c1%(Qmze`}JR$y73w7bC@wq`;>N0)F*>|MHV5}F9K98m^r^g?z=0E zrGm)5V3bmha<5Pc=uNt_dS$3QhXhV+!WgLKr`1`;FtK(!_T5Optp#y-lp?R+%Su0c z#=ywv@9(ePSg1AR*A%>@X8$z<9oC#+)a?p}QIlYkRp z1pruzj7I zpe_Y@F(N$9&^Q3UH9u+5v1>1J*+uX%4wikLw4kmhMX^)({T;Kdk>S0vs80~4Ci!=s zi!PCkI%5x9rOeYT*gd^OEQ(#5h>3^>zuB*9DQ1T!&bnU z{;JvO1p@bf-?8AQRZlY^`>7iOQ7Yk13OPVeMu^^+npKD=*T)ZuOYX^aoFJY?; zrykV*j)34`@2CJ2q>ZM%2I7r*;@=|-S~IiCEvozx7lUSh=Vg$DA+@*Bvtj)gEB<6Q zO+R)$jiWp=Tm@K+Q-QnAzu07${;a*DgO!yvZ&~&H@K5s{3+yd9NPmBSbW65El1U!W zc7!%8$1-M*NVR`E25#LAnv4Xs>(;*l34Z_n9e_*0EpOP0a-%?5@JYtYC8c5GAC;2dQ=|g(+Qb zlDrpBEG+WWp4l18=a(tD*(Wt8cQ0){2`3PI%=Cm!j+BtgL{r1C-Q>JisaOTp%}l*}mVU~DVl`M$Ce%W|eBx#Gu}tnR+`0Eq zz-|e1@zrm??)`avhQW^l2Q&%lj8RM3mSm5uFo7Y>oi=@rp#~U#QBJ{+A3vhJfCV9# zoC#Rrq5uYv>?9+u$hTxi%XB>jnVDaJr#R8-Idx&$mDXC18=&w=-o$)D(CW*i0; zUStE+2TB$4yqTMa2W3Bg+w4ye@F0rDtPB4Ek4fm4Z3&QzDb9^D;g;k0JzP3Tk#ZsD zHg5~2rK3~8EynasOE1L}JDCGlstD5B12bC_ujFVXYxX*+b8xyL5OU$s7UbrxT^!Fc z`7D>{{IrYj1?*Z7a*0X&t=nNxIG6X2;vpj5dj{jbFUL7Kcxv3DVJ_x4s5q z(#`SmKYc@5RBVqbW8(5ym_DjRU(9bGzd`!x8n{#gDoZQ1{{>Q`u=g%`Tl<;E&Nf~Z z`|4<5?$2*h9&9m80#!25Aju@L0AJQX2NqG9$F*D#E{Aq4q zump=xADMRieTH~9?>hk}r;evKG`T(ENRJgI(%ao~yCuBeX%kKtYhdPX=^cElp*%ej zJN)u`NgwHx43E)=>_NU(97gBZ~!@+E* zv>Thy|I&3G*bYmGtAzvK*(6|nF*PLp+)vYzpO%uMAXb((&_$A3*YNOTV8Cd&Ea{Dn z&k*xrKkU2ws&5Jsr%Ajj;zhnxQtSdnAO8G}1!5zO5LO$NSFcWPT$g~KmBk9^5?BxuwAiwc++(@#S2#?Q^jdAJ`oMcj%0Bh^q+xwcv z`0VhW-MV$f;s?KyN#)j%MU`5b>dlenJyNhgia8fk?1*hWp4ubbbiuK$+q7?zu^Yot z)8}tJ3UktGuwh558`|63n|igd$V(~J^oZnFsNmAmnFkC~_#uIT=?;AMiD3o~g?Zdh zkf8?thHv(}$=_L+tTd71Yz|F1bmE&lylHHflT12g2Sb2L+|MMc-966sH8dv1P>_6+ zQ|EiMVl1bguJYVUFAnLaUi_B$#~0e&^|n9`c>#Xc*k5)HaLodm<&H z!;Y-f)$zzh791aEn;)!1Q%-O1>_iv@g@uWpZ+_M+SO4`(^-SZMkki9&@cmv!r5UBa zaqIq%bmrThHp!#W(9nb}$s%GDPJHihoapwc!u5>J8-ilEv#XN-!_rAw#8HwkEO@b!zo^j`6I)gFQPg2>&nEFJJ*e{+kSZJ{%AJUSOyEcHD z9#8DBPihm5m)GYW7XtLvQ3o(#cH-_{K`6z=H?f>j)>_ecy|4&;^%2>ay{XJ(R{Iw} zNlNz?zdP}Z8LOI^)izQ435mrfrfJwMV8|S1T=$tWxG)qdz4Os)^SsyE)kYEV2`(bE zNlNhHZw3L1>>|a_1sXxs&^I}U2M1FAj@whNUGXX!Z}x!JPgB@#=@W;}Z7C@wurlOc zJbb`Kd$bVJn9R3PX}dV_H^AP^5#TyLE0Wh3nxCA|v;vXsbb?@Pb<0mK+PoIJGB%k> zP>E}R_%?OVv$g`b*mN%Yy zVVaN{X{mRPhWn6altO-9_mY-wNtb*dKNCnm*C2XveEKk;J8@ybfSZi!diIovH8^4x zCC&oi=5Qu%BxeOe*4GmQF?s8c>YC~zMMlM@#a4Uywtju3YJkVv*1^B}eagsyRY-_K zE%llx`5Wp_eUb}^auk32e@J4Dxhy8i=xC12=Lt9i6pMYSS?-^ofZoof(S0SMwMTF@ zB!OqOH}#^|ve@%ar2LG~@l=dyieoak3xzI$k@wfyQ${pkc#njD7$y*n^Q1l9>D{`6 zkIgexYw4%HAnwBxzSr87)^1M?Ia)dRk^pg_gO%X1Q5u<5kx55ThIt22+DniajF2aj zllCO>_f~N0#aADK_-9^IER$!Txn^x21Ffh0)K@CJ>QFB*xm}-^XwV!9ZskB*Zlolj zV+m+#YAPm-S;(ea_td2x6*x+2eOZd9!Fw6 zF#+1~;=29YlAA9lXIHbTY+sir{sza9M%nWTbJ^v@#?dc8&Y-v_RBzqXm>K5;4-i)| zDCFX1oSpvV{e@B=mZ4@k)aASZ%s)cUl(;>)82@44UZec1~=$qdkP`AW@ zD}3_3bG@P559&XyHG!Vg}agmzYdk798Xh+6T~ z3c_`+F$Rf(B5;E7c7xSBh##*O#AHzkdrpJoh`O4Q@8BSD6N5Df-A5o9sCwlv+<^gp zT=eZ?kU+xIcaMaAy?f+@Ur-|&z`8KBFqFV|8a|p`(2^Yc_Aeu*xc9w50n@p>^WBom z>A}Ie!y14Lx_<67xX&*hX*Rr zjsaDi84JEmKhucYUQoQY-P9S3yjF>A-~7Y-m&^2-m=j?}b}xQy6ID1wf8{d<1{cr< zDlt~V$RV*Nz5MkFku$m6Vz{DPf^NK>vU{6?FMy<-N61Yg0UK7T?)qYsAKj)9;I~z z%|1FS2e}Pb-V0m?g}>-l-xJINy+$tt{4@|c5|Yi%!GVhnNA&AGHNuYv8x-ghQ41Cz z84%zVu(h@2G3lb7d0No^f~XrgpL*P(C`2V05hiX4U?QB#&=V(}Hvzh?X5TFP0zP&B znXQ41@WBICVc$jwS(w>RS9X6$h&K}G5Y`k9pk2VX1Su1bx5nB#uyHW4u`nM#EIcG> zSI_>dWCCKQM;Pm}u%sV-2M@%)L}^O?6D|O-JWj`nB*fwseZ!#+XcOGQL;`QDvwo{Zti7iokbF5_g z%Yp$j1fb1x#?wzt@oIWl#;B|57F%k|z1-}d{YW=uQVho|I&6n;;86-_y-OZEuglB~3&7KgRolJ5f;Pr0uED`qdiiX> zp&*O?I2eJT!+t8lze6%T|5|o$)!liJeA10RCd?ajdoNyGg(>_?$d9x`QDrCri5KS3 zG4nxH_cIaUFjC4fFRh4>6ki;7cg0bbt`0}s7l_|sz3F2*`FX^eRgnGw_Zj>A(_0P? zx*FaG%!J28D|ceS!BVUxPD7k~B##|Ous|89DrLf#W&Hh8$17`(E|9EA$HCjr55PBy zYXjfYWMs0 z@nCftqv|>_GyHidxy5%iM1gN*UFlIHkPrkox(*&>AL0N;2MNs0AJa5lqAB0tbob&0 zTE4j|?`B#k?Z?Q*m+^lzc*ScpG2IhDz}dLdHvk^!NFQwHHcWFgX3@|<=oh7OdC}OP ze?-ra?T!(4{($Ozlhb|27x=qYfb+J3_Ya-=`dRCpf_`@ySjxiM3xKr>34Q2~{F)VmC>I(1y0ZGnQ)6?arW zz*2N|h0p)=Dwphw1Q8=Xsq&v7&zSsR%q@EgMATyw}Q@u)2 zS3u=b47%0xUj4pz41I^RDH9{Go!*Qt2rxr^QTip$1P7{+BFYmY+|zM&GdBr7!^;gZ zH(4U7BmaLnrogern=tNw%m3V{DEX8Q!MSEjkXdhXCSIf?;(jZ67o2Bxa47lbqSSJU zu6~6IN*fZak#hC?@9}fp`jP^s&T2~=AKB`UIzf<-dTcx#UYgg&FM zOh^JX%)%xC_6BccH(D&Dfis=ROGEtGl8lyPm^X z1zl=bc$~d?hq{OI2k;b?Y4(+^nO!ox>@i?a!^PwX>^{Qn{;%rd~|(_Xt% zY<&6oRME7mFa6a(+k%pNl<7hWudby<5fztj1~g%q;Gx(Tj7MlMIq-Blw7g430tjMz zP!r=IKfm%adPKqzlUd^uu6~+Mg3@98bLPcfWujElrT;Hxq&omS^8w9phQ$>Y;L2V} zcsa(qeAkbd{vP_l;_2JPC@U+QQp_x?Q7OYSLVlIw$ocf2=a{z72+M5Yva6>?-4inX z-BCXFp)F}Drfp98fPkpEH(_FT+sfCPX zR9s3qxo%6MwBiB+HGpRW&;fX?jfqN2;K~F#P&+UoVvY?|E!c=@@Px zpCi;~6WJ<`kPK4t6Hts%w;qr1ZpA;yb*u0MWY~)rFD9$3LAMHwJky}-1Sp3M)+^$> zcb}5B^Zv+74kNsrN`O3gcwONit%EAfMX1+xn+5GGgP8wfn3@udZko$T#6XOeHd7@> z5rII&;7F-iSv@pI-K+K5xE(O1cvyGu-D>~*6a1Uri_P9jx>N zuCp&7tE9rB&5Le=MJZXpCq5!H^w0!*K**2c4l|s!#zp6OyPL|w7<-M8q4Ic?#f|O!<@sS}hN#RjfD-~;ZR#sZaNna# z8FqH0?Kwv>X0wrZYE=vMuNV~}gNEh?cHoL8+O}6tYY=hl>wWLP_!kiTQtx(@XCpJy z;dS<`i0-^JoI==6n>{3u?4_2R%9+4&2F;mxiB-gG5H_A*9-$8_7BC+0iixH2%6VL= zLl>#jHLrwTm_z(N7M%#-xI8;ORdY`CLS$sjrwH!I9BYg}-hFD9ufm{~%_51xN{3HK zXa|gYv(B@g(I+uqSpHq3)%OmAx9n>8!R_q3axk(&MNQzc2T`br3heYE^H0ZvREPsE z9F|L?q7Y02qKvu!j1s7s;ELb(_wwhzdp5Y3RHH17g?gwIU?d=+!&V#p4tDsXoQp#h zV|%2EAf+8Qd2wO*=;PLn_*ToP#pSWVk*M%dr`FEBAHKJo1a$R;bOH6GqTL!^y<=Y| zlCB)1{g;f?=0M4y10}K;)7$?`DH_7M4lx#gPdCan#O8(5#Yw%iBr7DatS~uJSm*Y+ zkza=;=LVrZ4->-|I48C8LbU{%*54Q9HAYLUy?SGBPApYz7~Pbmh?sRf;RPS11kQhx z09@%(_}?WYO}9`{yWM8S#>OpQZ8X)j);yzB3eoD6)Uz=K2p;4yoeC5Di=9waq~2<0 zs>UTL6XYl&NLsn-Zxm9mHChiM5XT)*n$fokd`8fX;QW#j()DSvy#?Qbeca8Aj*GGp zf*6fFX^cE-{7j}(4aw)js710X>O%)&&pt7wGRs4Tlm@IR?mA3#P&5W&*506}A7jsj z=+$~Q_6#CNg&y_UNP3=)Z8^m5AjNCI&joS%gs7WrtXo4%wIx=2oa`+&94vc(Ip;Zk z$Jw5pwYg`M!#jPYf&)(&A%YNSbhR9x1kU@sNvTR>sI;`SW9y?+nL${U`C`g0WIDS* zA=G@iYIEJiw!*UdQ@94CHNPHwO_foXRSw-Jrlsit zA{|F;RZlB9E-H0tH2cYMwau1mFoQLrb|uzfa+Vgsu0*=B(*Ci;KQFmHowEAiw1+m; zusu<#=i{>p%ly$6m8$jikJmWwtt?-wE4WZE+~?N(d!cln0oKq8Kc@v1It7FO;ulwj zbM0!5`|BX54|aUK+I9w@l!G#7HtR|L&WS3V{expmof(z zUhClXi$A7S*JKJWroSLQt~a!423I7G+-YywG-twAzS za7aj0WaNv!Bs*l!aE@HL?|Zb1iRd&~?PJNUG%F(AmHQe?;wPW=K3n?IUJfud+L6+= z7%}~3f10mf-$zA$?@mxz#KK^t$2i)QJqTS*>(wfucK?9}7vgc;BtzQ+Nw!}zGo_Aa zBM>`Bh;#S@{mr@f8LYAxDWkqBYun>#nG*fQ7I?3?X^uCm%_Yh%MqgqxEkw5ukc%@^ z0gdbl!f*XLkjH+Gj)pc-4QYfTy*R1pODso=J~#iCq+pD$q);Ij`Oa+S@OF-wiS0JI z?&fUuP_caeV>fST3}*si9^)fbrTt>kNbbMKg4<~Qqq=0sCUj#>O_m6e$cvRE1ci`l ze^c%H`|8=$(+SI&Wv?{i$F`Z%KRIifY)TtX!=|6y#1XPyR$OhREZ3HJlRQn!@x9H4 z!V6(v>v*;W*}7-nO5qgp9i~<5Sa+me`x~+C>~x2O`bQxr!yBC2tWuoT#sDR)Si5_JU19s8+Y39P{_Bq8R7Xh8*(!757-U=bT~IeDTmugj+j;LEXTb84zPBjFkpeUI9>~O- z2a}|M4&U?Qi0RH9yo+sT@_zfJTGJNnv+e!0tmJLG7GVJvtTfCP_E#Tk1aD{4``SLW z8lo1ACE%wbPhYATqaUogs4!pZV-gdDA%aGGhQ3c=2+cT=p*8GILd@GAcUKY6y`pjt zWhb+_n5g<CgrQ7r}BD|oV!1OdS{Vxpq;fKLRy7MO0V4Y>}gu!I*firsfySE*55qEc{$vm5J)YXOxG!u~LdfPHoG%JGm zJs!)%*v1deCug+;i*ThX05=PTX*RY!SIm&jEr8~Eyj^eBMw29l9CYmOZ{m#InX7W? zO{1S<50GKPl znEMZ_#GZ=cuZ^5@H;X&TP@czJ7`E0eu_iiP99X1OmOwL_Ob zpqnE&@bdCPwXV6H&rtV@m;dX3rwCo3i~hohV+-B4^V@VXHm=4!{I_7W3MzjgjrFi= zrbpNJ_7wGcc6JBT5(D72bvsUsOl9R;wmI7FG1{}%NTxft$sdX%KX(g2tF$$x{iknN z+f+^l!98kYj7`(3>{g$u$4<+ssBEe49*sI#I<7}gcX0D;soTwYPb>F3u?ae~+5A2~ zp6nKrf-g+hZmoD{rq4aKDVh#;+OA0QybC9Cb#<~_UNz;h##+>p7KI zd4)zqxck+Dh*Zv8Y`1rP7tEJA?<*0l2*$*a6)Q8;5WKahe;Zyd81dh^+=9`3WX}pw z&6h-rA*`})|F<12#OqtjZ6~wQBFg~Wc^Vq)_r8Rg7UzY}-AsslXb`(SXy>?Sleam& z_PK0B1z%@PE-JAjn1Ao-)D9k$fyi4-yYl@hIKu4N@)0QTYm*6tm!dTa*toRwQN&*Lsrf2G(w&kqn z*_slfte?QY^)*7Y3P_c@ZEa_d)_lBWCMdomy*#i@TTk|)`>1`i$U~Dwc}(zsM4I$6 z`j)o%_#?s1Jo*At3w<;R55kAV_ik3+6LM*h=d8rVUGWol-doTCorR^}-oiei zlwaiiz_!@y=qG|SKZ}&dmf8iWyq*(RUyAiXF50cK#=VEqJOn<{^65|y)1I#G!-k3S z03B~iF0N+`Ul^a9uEbe_>FlemoA6$=@`SGHCZ<91B$1?-_?fnk|LheOfOt~-9ztU7 zbO#UX4+WWJ#NB)M?Q4(vXyh$7>$^1m1*}Tc15;=*{}5^J2g2t)T~7rg)@I(* zInb>ff2Q~|Qkq2+?zd)!I}CN|`S#jI@&}lb<3uxWYE2A~2vu;Ic~9QSp_tgcwH@*A zrb_PFQ4r}+oBPrt2d^7+CI^Wnvt`VyeS<}P73Y_s$t*0QDPI#?amqS(JMMRGCHxhd z0BDq4yRyUPTgbiftu-jZu-Z`1<85`psBU@EMUdx70`j7Gy6pbLlB9YXMCow8lJ>W# zqq7aC&Q$SP8%;=qc{QAvZPNr2s27`RQ;nL2({?k^SsSo=A=OjJV%{GMDwjQn<6HWv~i#v{=WE;7)ly(%uwq*l@A19$<9Ge<#DhTgKl{4vXoCsX| zD+2EJ_HOLtFyeWwD#{@#bp<6IVe+;?blVZ@_0K!*`H&K=-M^2twzHFSdd#$I=At+! z-_cPp<3uI6KeP}b@Um5Xa|{H`ec%~d#)8&Hkkf2|W#J#-_i=9I!A5zZcFWQF7ESBd zc_7~Z<2uoop`I)nQk$*wOB&r?%yf==!}glGFRF$zDo_K6Bk2%M*1g1c1+(r3OT34# z#B3g~SH6tI9Ez8K?JZ_o1Mg(=JVewXu-t)`;%BG0OT_z(UgX~1tK>8_k2Yke-&2oX z*u;9w%Gj*bgt++mXB8Lss+$GLQ$Nu<`4yilf3#&zXwlXyOc^T{r%lr~6g4mil}dO( zVLh8iO$$yCz)EbeQ_l}eb%$Fczx+SO-U2AuUVn>IiLLq4Mm}N`XH!0}TK!>rJ+plmV z>8tEL=;ST&hk<75KRgHz-Yw}r02&b6wLsuBW8G*PBfE9`Y_??o_10jChb`CVk408X zvlr$b!;f+xT1Oz}FYlqf7Q`NCpKVccg3&l&9HpGVQmoYWWXZ_uwV^W|CsOh1voEC1 zR67yD9hTr;HFW2E_QPwbdUuvczWo~9x4DGk<_^zqR3%dTDt1<28KV}=8Esj3`JBJ} z36765{Pq4eowr}yfu-8KQME`S*VhwXe0=7`b%JUQx(JSuma==hJ7ajYwhPwZx6cp8 za=iy5MSVJ>uDyQ`p5XiLA)+UiKiHB!Q}{Jbv0GU^x)Mz;Qk??rzGPg)5@_f$d^~zc z`f%#hhCBKEMFksaN6e2wc4Pg(dC*YlT+-}s`0T2-q31bu&PSDfy%+`c$! z%FrL_KF(`z7*givEDoe4@_vITdW_h9$PZ}r*414Kpiuk(G^$cOoav;om)xi~ENu)+ zwes^3BR#xCf;{R=6gEG`)4RGh{`w|M6QBLYVONh;Qmv-Zy-h`Lh@ln#b^Gt%$+zezj#BdNcYmg#xud(YP<2O3jH$z zo=SaWnfb=j&*9LTR-DVDy=7LDzkLjleZIPzqFvscgYtZu_NU06d6db;)P65dPKp$d zPQ0P73s#dM(7Okf3?<&GvC)isnlAE(QIZHEv3@h%5QDzDtM^Rx_ZcL$PKVZso?6!k zyCg41esbBI>nzgqx>0B{FY{3ZC~oHbZR-AbA*s&7$ny7mC@}j!hs7l8 zEp`jLZ1Oo6?N^N?e{;9WpqtR{!`$Wls4ZCz^6@1u13CUL8P$(z856yvZ|(3e_YzRw z%>w|%?8Us`i`V~?e`1_@C1DapZ~B(C>gn{9M$Br%O%fx(Nfy>jvkERo%SNPq`F>kT z-c;MN<;7LoE2@@U^C!#sHgLRd;4c`{8=jrHZH`F-q+Pp{n~OLz+)uC zoyTcGVQ-;&t+jO=X!oYj+DnBdAdJ)WBZO+8PrnqcaBnu9y$lAO*pqF!%2oPZ#;7*? z9b&kx;LnoDYZ#12{iTkM(IWKziu*H>ClQl%=%Z%$a8R#rPK1AC9?)*DhSEe~`JcJ$uV%58Lmtm{v^e0wr})jq6k0Cre-3+N7G%+)s=2e2rsi z#c3z%kB$US;eWn<9vF#+ShW3*3o>Ey*NSSIeKBwB8;4o1z^JLE0X_S9W51*-6KwWa zYM#d6Ab|0}rfc~;2Ht0;i#-jD%Fh^+?oPFN#rh@eT4q^;-{AOMR%-bCFiX5IDf>@X(zDt+Q98pd?Ki!6J12sK{K;>iey=eby=c4HchzXssAi)cQ!sF`0IL2it8$GP{Mt@GC} z-qtm%`H>~N5npc|J-LpavEtD&shNK^Jj#6@&O-}kHtgD6zFJRrzN=8bSfXQE6WE)w zaXD$8M)2~%I`w@YAwN(80aU6Ry+DTQ<^q$*-cQzZ7KqV}{3XvM3jV(?iw<)BJ4Uge z3a_$xThiQayhg=$oe%1QdF*DU ze0@dV(zj+MxtAJ&_E!TMI>Vjb7uN>FG3gju|-{v+_pI!wi?d|G#d6g4k+J0y%T2d+K74YPmGH z1Z`d$2%3r9nJ<#pC_pQd|jJF!pdLQc=(C$C$-2UUb za3LWfA)w`J)jUc}=G7L1hUTA>-|va%*wDO&+7muJ5@q?}9sx79OkP&Dt#bYyFq4JB zpjyl@3rnlKyj?R|0M*08q*d%%erW9TARFBsx1m+|60uf9RG{V6Uf$jvck3K5EmXY@ zj|yhRyVhvB*51rlA$K0RG*sU=>Y^s@&3@bG&ZI-b*22 ze8a*hebO0@>bIA(u3z-qkDd$z&Em+Ns%IPS-&Vc`MpoR%3~7V^-!y4|xJ)@W0(bOa z;`nWis>{$D3xW>4md)n_S*FGaIxx~k!rnfm{0^I_OmT*&+sE~eSN^G~?zqJFSv+ek zgKk=E8M!Q}B(Gfg&z@)WAp<~U5J@zoUvV>2`gi#INgi6=ryYQuGT(2qU;xBU)FXo?0z5)2=ok_UdWm*F+l>EBOQoax_uy6^dZ zT^6`$HQd?B;C=X5xC7isQ%)77PJKK0Av?R$xlM_k!lD&yDuG_tB>#IN)whOfm;q zKsWxcuHQU#X7#`Mvo-?U`%$&AS<`rV`iRXITukgyPAB~OHUDm(xwKB_GHNB5OXD9h z`vy+Hy5O`e;b1@Q!_3dneSQOTSDVo}d^`Y{USOt!Oc%G?9QUo`2&D7OQx9>kUw@pm zF2JzxYk-Wc8@4VB*9x4+*-BNpK?iHE>Do#1)jtw3Isqh}vpztY2Lh&b_V^;_rA}jL zyT*){K%+cntH3aFg!F>{vwWeU%Mu_d_OKQ2)Ps(4%MZLo6@(~q zO>#5-#R9(O;sfxu3c2ElbBuZwD6e6k==N8Rm#?VdtEWQFJC{} zw$gv(c&1+AIsR6~p6-qX@LGycn}-tcKa8#sn1pUOK7DRfVLV)As|oZXyZic5)YR4& zyQforK638%-afVqd^FC^1>ufv8IGvzJKWGMw_5J<_9%k;lx9|r_x-ef^|SH`skJJ^ zn11xg&Zhft|L%w`Np=`?gkxGJ5{%T4P4|4yB{o?z`LTvVULu_0pH{8y|8m&$nAH!( ztmWqxCYeWXg;%z=e9OfuxbFV+<1~ggu<=$dihYIkX2c{QSwziD6m!$n2Ba-5Y1E0l zRTPuJfxRkulQA15IGO&9@L`iLWa1jL*)_-2?{}s>vo`M^Y4!z(Js<5Y@%643jxsyj z22_mQdPZz3G4<`tp(_&I4izt3~YpAmS zV~b>C#y98;R~T){i@>c<4nm^p&X2S7QDN_K3UAoOazMzTl6q5bQ59FN+z+naAe&@h z5PdIeu^(JKDcj*u?EemovdG-I_6s(cuk9+k6#7MWb^ZctQk8}%r|BhOm}S#;58m$u zTV-@M$Z@IQgNz$M+8en_oXt-m=Y!XOd(~8Y00m3|lboxq)v)_$KU$|*Fl;w&F|^+= z&V9Sst#hc`-OZ!ng|HD{jaknm2bfOIypWV6U5P~3v)K8e zksX_5PnP>eK(Jl9B}roJ>)S(B>z1R8?lB)`%g%so7H@YmV|vm4Y&Z($)_V`XPt@G_ z14>2#%jw7Rd{ld`^1+iA%vlxVI|Mex>-R0PJ`wheF z;D<5pg$VxjCtDQF8>40J1Q~Za?GG1cLX}djYhL#7)X;RSQ-Czjy#irI4L@arm(ipl zizc(>f9LDEQAhJ}ZD#iM+?g)R4J-=V1$VP7fJRRW2Y-qsWS2g=?*|Eril_$abfaGg z0o>&HPBroy?KiuNVn{!(iILq#`B~Q;Mmy>UO$AVS=pM8 zzji5eGM4kRgy{Wtj$~n)<%2gow?hx|wq?w7GnY>CSxy(Kelj zs*Zm0P6f|Dd3ulgPBlD`m$%B!&P1wh-J=;U@jYvy7lW9YKTXHXg^v+Xc;+*IO#r&| z)lWv}zj$qlvG&`2r(Nsv&$z-!btc3*I;eifp@CG?(WFb?A*cG%4MteL14U$e(GE@N zH~ogh0VEMj`PC)2jpvLaB70RM+m_We zU!Iq@@I@}!^Qj0f;jN7^{#&>5lu`t)p^PLo^s-p|m(IJ&b1FVZG}=rg0hVJ}bjLw> z3pLZ?el2lE@XKkS>Cku^p%PJXKsDsv#arhdFpvqZsa zCmSVxxR|gMe`hz0^OgqYxa(&ZJ?B(?&19|9gc(aH@m|=>U4lWz-)ZnF=ztuLaOAj| zndf~pZ1j@eX7S@qFIeY0`%;RX^H3ayM@ zKf0XcY=Czcw95dk+xI8l+O%5r-ztXy`fEFpU@>P)!km-aMFR zK0wFnx-pe+S6?~bRZc)qw)I1O#m1yF6qO~_4Ws#PXGmjpmO4RyhT3Zfvp%VL8cIQi zt+oy&C8WiePpS}Hp4y++Q12}KLS1V|gCAQ4GOZ4KJe-e;P`245E4g+C$ z4=t`rXKuVCwam}IL?dX1l!=(>n)zZL6_=Bp>6oYv_~F=m1d6FOrqLUju5~Ae+vsa5 zDNWQdt}+v!^1C38roqsdqwol>@wzHUWHoB2K!e_k8Pxiq13^qL z@+ivw2wXlTB8q(dT4{CAs@=GyRb)DDPM z?8i2VM{47O6z-`%Uu4t|h4mFrK9+i6B9Y9{`A$s8By9%sXphqay0oQs5ISo?l`4nDY`0DQb@?*6=B}eV)ee_$E$`G_q#7M6YueF*Vnj>Z=P|{>(G}iQoJ?Z7{Z# zC0lvbp3`MOu!VP(f$0)ZhyHI>CoqiIhiLXj%TtSOQ+@+iWaD` zB`YOuxs09ksVDy3c~Ng#rcvV+;LDT6kQUWML^7v8_$RMja3P>WMupRls@CpsX*&O{ z?`zs6m~jo!7b#`9CbsKgFva|{cYlfmR#ng+_mnY0X=Qmilu)WAeRcXAfj%vw6FrtY z+6);OU<(RVz68DQ`s>Yj5i%j@a^r9_A|(Jv9+azEEKciT zE^F@TA^jsx@vqajZ{`)Y;ftXkpMz$IvBA~vN6$vSSGi2}&^OEz5uK-1<0vmItT(T) z&P68}Pq7LK?Y$P+dUlOtsj679;bQw>nEh2(=6PZ9+cb6sPyrVE`u?xQKAIbtf{Pn;NSmPJ%w<|FE z$kIOJCQ;j63qPt;9|MfeTPfi2gon1!1G4yi;tFqT&o=pCyAFuikO={AP6dUE;nBqb zS>bPeMx`y7t@Odeq__z)TJFccaZf?$s_G&CgKUw{7agCpGcHZywxgxy=u_W;tS*$e zFJ`RyTmc8SX{i3FJ6$a5XiMdy7Z-Hln7J~N1;k5bmR5GOo8{Y4USCfJ@oLp^_p}|C zcxln3{!Y|pZ?`S%IxY3JANht!?vZZeM^ zrDmDTm`p2v7Om+!{@_~gYx2>cr~A~S_i=VHercRJjPY4(;7?h4VNIRUjp=jN+blT2 zAI!e&zQLe_&i2{ItHOmZd_CgvV-Syblb!XG(!TZPgaxg|%F^@2*SoTl5}g1fh6%W* zHxGhTKdm#Z-#DoBD#3&_s`LipBh?ldh%<1rcrIeR2pB?h*tJhf> zqwc0+j9G(GFs5|v$F5ptHPzSw>|IUg^z8U<7E9bBU0)6{B4D7GAKr`Q^m(Bk`Wn=a zFdh+|8lhs+z&PDFwDz#7QwCXSwX4BUZMq>^RP(RNDCh zTV`ma!vqy*A_4#LY5-I?F1nD$FS6MeJISEUE&W8mXXFn$6wUvv?eif!=x1{(8iygra$K&BD|ETCPl zjzu9}i2F~w`~3uTgbh^GY^tx1STm}c*zFUZx-{ge78&TIy6X)AB>Yb9`OnVSR;$N{ zp{G+Q=iUwsnlme!gr4vAyR{RsGsTUeqtwkt;M(sw@#tsU|=h2A|o3ZSEFQqt4S zsCs49hGL%s-OqbWy~6L zR?U0LPUUK<0lu5^w2kb>1e+ERNkJoGh zhLPYcwNk0P3QbWL|BD6G==jGHUcZq5#&aS%#|b@9 z+kgM$`)#Af#a{&iCj_|wn49>c8nm!fD*3LLm1oH^J^9XTCjOvhSYnB@Rp@<{1&k6#Kfqr;!_zX9Gu+#Z#J?>OMyQr6|x#P(=O|rM!D?wWTN^SFzFC}~$Kdc-u@bzsxRbOfm|?7vv6s4>dL%sk>hrP~zr%eD z(>>3{?l045<=Cs1G=hwFt^>o_AKu4=G7#WeE^al<3%K4g>;IVX{L1+dLGJ8={PfBo zPVr&xWN}@S5BE6EBdn?`!^B2S6ON~@QV+1NG;hn&Kg}=X^4kVpV zjAoi`h4*lt<0B;+0xpNMt1D^$pJ`5`750z_~y=cn6?)@;0`0I=9_Q8t6$A{s}s(n(W1Icnu zZJBZ{UU=E`BGuMrXTnkCi|kBu={j{>fR)Ov@vB42U0ywZZJetV4okY@^!}PTv? zKI%sfn@prAJ$cBv&q%|`lSNrr8Z8{-`S`KDY^%STEmVqx@Lde05ec6)DNm}EhB_~S z)U9hKcx;5Ordc6Zadr|f=^Mc?yERiWn)}G#6KOaGS z134&H$ z4X(3ZXeaNrQr-Y1Z;$C{QKlB8zAk0xd59)?-X3`rFG+>jQenc;f zY;%1H@tOL1S>%S^Hc8L6@GGea%R5{SVylnVl0>rdOI71P4N_ToLthl>3o`J#I6Lu4 zU!1^;7q2698!52fii^7lhw2;>UvW43>4iTdA)Z@l(fLi670-r-u$IzfZheA|n%=Le zu(HBsZ@^MMybDqY1ZVi+YO@gcOK1S;kSeDR{MC&oNOTJr*{Qi?bN=# z`sd*RAM*Rvat(Zpejww>P3%3q89DgDXjzqDb|U3eeO}%>sjEsG8J8sFVU#y0*4Oln zvd9#;(%d&bpJ_>1{j`a=vg{$eyeXHyGj}EBT(3kff%~65{JoRlZ;1>%uy(sTqLdh85M^-x1=V)OTcJ)fIEidPXhY)6yOAsmZT-{lY1}HdIHKd_s;1)BZ^D*F4?CFtz@C6-46+fDZHK?jp#gdt+%y`lK;jz za`&#_?67}LLc;NJ3#MXBc5g4;;5iaJgf611E6!#PX|v5IN~7jcpRS%}oNd}oMX+I0 zQlch^yyx&ygk+chdnQ8o*zsuPT^W~@AbG4#(QdcY<)c(+*kQ0KX`jRN3yw+VJKUUW ziwM70XOKzm_d_F5tH;~tr7bN^Y6FSlqRiQ~A8*L`N@aDo;|M|==dnz?DKUIMVIg9t z)@CxFkHabLBo%|8-&p-KkZ*A5fAtZ=m;ad6g=P7Ynv6LZZ&6lM!6C>fi&SZ$%fgdkrFX|JFL+X@-LRofB{+86O{P z|Fh{y)M;^A#b;(e#H?r=t=7bE@A>c1^XCIg{VY1lgsniPbKh;e0B(2y>|g9x)7mm; z!s{zUGDk^^9$$8J9zH&Jl=!M_r)h{a9fZkJNNOS;4z_g}k)BYT+Gr&E=I?;}*IsC& zCBQIOhZ=B{YC5P?z+={iCUW5u3Sg(TZe!kU8BN61nR<*L&UY=ihE-AK1L2=wei6^h@YhGn<}acSvcRr}Tsc#C{(V7g)P#+ZA2qyPtNo3> z8fO0KVCUjel)R|AquQuY#*wQ_#&tTTx(b;{j>3=Es(&zko>qvXr6i7OesoWm)!q2J z?x^Y$HSD_RfPhjm(Ba~Pc049d0d5%t0q}B^gXHZo*m5&4U~}8zy$}Z9gox!c@Xl1E zdE@1sc@DXBA2vFmg@^n;4SD^*mAsMhHA41hy3*(;lh-TETa?}ZHf;>hBqh6|U+E*( z?;%0KuLW3zYO`esg8usyQAj8;j6xbS*BK52`Ye^BOrqQEwI6j3jmb%IfOk~HuZ&Gd zsavn>TS$xYAM$%qu)3!DrpG_@^mxt)OEhoRX}o#S-yRIw^oT7vYk(%@WJ@P9r13>K7wle3KA@!1<&Jh-S>xFw>` zN9YlRdFB}U;`rD=J#_Y%W{IdsE>o{$Yu4t7xv%40qbJw&uWiT0c2qQWz`%pn#FzS5 z*7c7K&CL`1?#ussMq{w9_{ojku$5amv;_`0iC7l=EHws8Pzj>NuC zn=$vpArGdeCb@5le{S|f-NSGU{g9XM$yGc%4sB=cI-^>(uPe#5@{rmG=lJ4XSolIz z0icd?|CF_fVr{+1sv4sRgBfbV$S(i0DFU+o_|YI_K+leoeHNR_o0NUn&JA>R=SuM` zHG%x*!A@@x?{i0)%$l0@uLH4H_lQX}Pz{ggsIlJ{3~O~kYY;pVu4Dh>a3otESvITB z)>KzlyEY`Jq@>(8;=NDHO{Lxzi?aS_A$Fvtv$kf&zT|Z_fdhSFl;=BL+E>l}rSUS` zO6+)VDUd&_@-s4ePm`%mogC4WF+KWY-Q4E|oZ-%+tie~FmC{3+rhb0BnP59_9hl<9 zt+1P{pVs3B{P?3%F!r*8GQ96RcYj~KG`IBXg)^(M`;J;V6G<&E(1 z%iBhZ>a&TMJUg%G`t2=I%Nw}^nNs<;7AJ}sw|hK36Y9#WeM_ONb}!=(suWRXjt*y^s{fH8tq^BHTta$!Y3xHp;cH!p#tq7 z`S+BAEeOEoIa6zqy0xS>B)B=Ikr4CVxTbigqEAmb)w-a>VQ@uHIYWk0ned+98Fw2= z1pAxRTXw(=oH6ps7eyi)PCg1VT@LMI{N^!!7u0GYb}B`73pY;o2kkCP4ZKx>u+MNx zQsTe=0?UZTX0z>0aS$-7C24Z!w(Y(GjaN*xwzl_k(q4Tr`P3AH*^^-l3s(+{OoBgI zkB?|~$lhvI^|M?|)^*_v-fgOtDR8g}ygGTuX_xrK3vblb1UAQngEJd!l6i)Chm+W} z_w(zb#)npU;1qwK=r0O-J7&ww!MIYVXIla3Ec&IIxkiIGGY|%2;`M$?p0{^(+ zF>n+Tb;4_)Gx2&Z~zc6~!b!TvLeEgmk05sNR3%n#3yD_jlF zbX)a5yYEkuXzs>nH8z<}i?DgOl!n=*BO5Ytv!DT4<-rsr+%Inc>1D9EdEmY#yXP_U3!jlG2E&R$IGu^qzck(a!aF-^Lc}$4WyH>G6)ahl-wnLLtXt)+nwpzU9!(SA z2;A-H%3ZG-nFtJgG3A6?cT2SZeol-1dbuxj!UHBM+3GQ&w$q9`&eMF|kjAbkF5b%Z z?(L!XL+{BUj^J>2o**{6T; znZ=1Y6ik)O3ZbjDz_(={RyVp$INE=bqu?ZQRSNHt4KPE-%zXp<1W% zKm^KM7c*ka!-<*=we;%v+8 zyK+c8SM;LyMpgOv4I0an@5woM(EF6z3&-z!+RKAA zHdmi}SS)3sYxs2 zKYrk_Uq7Sje2Q>+R`AUJEQ3z6rbcpXdSb%oX+Ayd%61kEee3SsyFfEG;SKyP&pNp1 z$gNlTD+;L2^7!q9H@h_~eNQX!xi67B*h)-1o}7H65UbS_$nnlLY*ZtE3=Yncf(xh* zsZb<@!L3o9aYGBYi(m5oD(j*rt6}DS6zxBF?UzA<`=Kj7+rAb2*~+mER^(AVVX1bm z^qxHzN6ZM#^P|bl=;-?^#>Tt1Y@wO=_P8aO|HqRvn>0gD)emb@MLdSCW-ZBfMuKSt zVPOTAJ*fNR)41u^L*42KRZ}mLDPLbSxB^QaWt;usgCF>c&6tCd3k~l2`$k|cbbA_v zbnkSn%dYI{@OjcOYYZ6HT% zc!MY0v`2%8--j4hxp6+Jd92MuD0>R5_&rir>hVXdMtZ-fHzbvbk;CsSUiDf}`H;z> zZ^Pg79CyKGY5y6gQ2~Y*goNEV1#+7}$09trp-1leDmFH@AyMlmo1u?R^Pd@jrLByXnQuz~;{!MR z&q{BChA3Au(J<8??I6iuc$Yn|Q3V?F#5jC}-hm0=6PNr0?oW@5P@0w|aO=B+{H!&z zPzK8(Cs}2;=EmkG&rb~7-2JAKO%Xf2-blLNV5jxMmS3nA`lrSL-BHg+`1ynw#}(OA z>~IRVPwJiZThD>^^tyI)+nC#Y2thhsU-8#0#Hy6QO9C#4+DmOy8ZvSY`Nv zCg<0ALp)Jb=;m8LUmK{iad13ZHN3O7wuY{Me#=BjsTa%;ZnPd?oOpJOoW`)eFZ1*A z@-j7Ls5?8_>N3Ql0(X!F^&T=B`9QG@0g@NivIFuNtEF$PQwkt{E*$STzSpGh3?*A3 zW0IAm>eX+&k@XTpZ2~EL{{XlCwtO)8k;D5yQ#S|1si4~vUb%%~y(yODm}Nxw4;cJ5 z=T^G6MO+iAoXZQ({bZ@PJd7SOA*Uj$#+-k=lFO6d{w5)Wy9B5H6n3u4jAt!i@2Z*q zKEs=65+Iu)CSM>bjzMeyvJsPw=pYYcG1lrS6zDI_sQ%vg!&|wirMLW5?u#iarFV@l4 z#--7#b)3!D$W=Yb!pKsNSKFls&2}*YiFQ&_QpbPr-F{;T0E_<1awH&)%>Hc+3H$~I zFOE;VZtL{#-PNX~{JAdmg77J=ctdXlaatW!v%&m)%9B2K7ngR%H-O2f=dg<%>nGr8 zaGZZF2-Il~e@Gmivo$$Ku>+0wU*g@1z}fB{95k+cNC7lK^!ahrxhmsGp{|C$e)vi= zdcy&TmlHQ1_xJ!uz$6pVGKu^1+)+|ZO|90+UykdJ&Cgq0cx;yUAU-k8L#lg{K{Ec7 ztbZfjKi0N~x1Y1#)xAl^>N)AGkdi>T{{33WeGbGCgOpE0!`;CTZjmcv8f8yjBjjGz z*PrF&7?dRr5MDnKGNe33?ez_iw*v)4y3{ng*$2Lu*-#kc%whua{E(ioBb=dddsyEA zpT@M5FD#A}OX|4mH4Pg|2kKY3{qh~sq1hL8ypwpV&g?qbJF9drnVoR8Y8)khm0g&1 zo5A!8JYMqu^$OR1MN7bX{0dhDY-0NwkLrxQH{OiuP{v)MC;L=aN95#o;OyZM9#ta$ z=nnP9JALo1#*d#|8XFp35K;$9KYv*n&5*k1Fj4DtusS3VLp*o^3dlev`2Q!eV&S_= zXKq=-wOkGk4^#PNt9(B7;Se9Ab`w~4(ThB2zmpz+W6T}1$-2+yn62@%O&6zoVwGKt zGR|&ACo3vfIkljdFF*J69F-o63Oo;?G}5Ve=T<&`ec1MWhE#6uEee-dyhQXEnuz^> zeW)DtRP#__Eit*cc!OMg)!oCxBkmD7DXAKwi*EH`&Ct9a(Ppfu*y@0Irt|4qYhvK5 z#7H+oLu5jrX-XZC$%F$rR?D9Fu_w>JCy{3?!27rAQ^pX+E}8tZr(&ctI!DlB{s}Rf z$bU^n=te*Q$64#wp{hm$4>vbA7Z)jN3Oc%tj2P$jqh_A7MV=H2Zf9!#&qLGGLtR~# z6Sbdd9JqY<2fkUQH1q%(2lXeIve3{#L%Q4Z@aCLSv587Nl+W>Ur_h~B<>P5 zg~98KOk(sVHcFr-wovadJX`6CUk<0t1$r@h3A^{j)v>`3X*a}Eopm5Yr%Q9oT6?Pn z6sTOTJA9p&L#~eh!MOvWFFq?*%-9%!Pi(o2KU4E5=PsUQbB_A*eXyt?>CANm2Gbzg?%rGNVJ;4&cMZgdP5M9ps$-f2+vUhj5+MOSc1O6R?T**lz5{BndiFu_fT} zbA~DIbN)~NT6aJJiOCp(T!I!jreBJgISmaJZ|T_>P0sZf`%2Nxp%eewi~lFUETkxe zIsM+?571Gz|M6v|Uf>h$Fx+N=7e6*RgC?3rL5UU@n`$`^Uhj*kMt)Fy#XW2zxG`O0 z>TzTLC}NAe+4d%V1qqfZofqEZ?SwdU>wl4@H&{J>7(#HWgzf||U}KjXXmaQ_j(^g3 zEamdj9c5Mc6dFqNbd=Xap0$B9r3n-*DC?ZHfQOr1(Rj z0CNt+(f@r+!mrJkCE#07kSj*MdQC_e%*4PY+A%_$yI&ebNq*{`&nn&3Vq((T^A`(v zVV@$ps3^}zs4KK8tLSVXnu_5dA0A6^p%922>n*2x|KHP+U~MTk{_r5>;*-~3wSel0 z{%!mdzt4Xltfek})*ma{!X*O_FI{G*G7Ah+u&MmDq!k-_a@I~FscAeiddoIPOt&!J z+;K!gL!VQoUSBJs{NI}DCUywe&r6rCUM!@PA2R!Fr26MbN`~uiz^GQ&1an^{%)&&r!6VY;y@^%FJP~TG6)!Xe>kz)1$4mRzicO-58SgqPCeDCcCoi74* z{o3+1-u|>{pe&so01P$ASZj5$ym=QbMmBw3@L?_0hWaEo(vmmrUu*xa=x^D~-+VS9f=_rI+gFQ%L%r-dz)?U&P}d$RJ<@I0 z66MFt>Dqfkx7`o@j7jWplvh4lMT~`W{avWVrOD?7g)J%eG33sDV&!I-vu4Xn6P{Ux z+kopPFLiuZ_(rq9f*s4Zq`vr5ZS9jTqFL-^(3C)Q1bIklxA0b_dgY)Ks9TXZIOO~s z$;q8BdcX_0Y^-Blv0U- zz@vQ&#O1cl$k&jyn~uB<#WCa(5@*6OluHVEMZ`4}jDK1Ikfj%PbHfq^t*W4GvcYu^$}dDw{rTVCS`!CQ1uc|K>5pV&nbz_}pzzy;AeQE&2x&)FOE3c}l3G;=teP!^LJ*n#3ZC&bCD--h0NqpA zf1@1P%(8jUnR&fCN?dY{lr&(NV)3Q$iB~?zuRE81j;|)p91?Gb7hAhsZjTNOXE*N( zFaB})x%aYGEaxPx(irbK*1$b0UDB`rx`URl_@(O}yD%*K4Cv)xtgybNIWsk{k1|ok zecw7PAAuPbZL=-_L#hkAn+$&24Wlu`?cGIiX2XuA+6`mM=u|Wv_x3boHhwr} zX3K_zh3k)G3kYCTMXV=!KaK?e@zy!`(Tc!q@-%A zvU&kSUo=ywr#F)#s5Y(JVO~(q8DoydfwR7msUZiW|`p@{tk{NU?ASyITQPCf4G!1+@%J2&7IZSgLh z)2w&{6PbD|`fTdgL(gD=jpE3Z9cty+(mi6hkn692hlF7YFlo zwHdE$0B|lpRU6EYik@~igG#uZ^`7QEQ^|dozK|*J5@i27%aTT|gOyw=&7XS4JO!T` z4B{_3EbJK=%9Fdh|7on?(k3R><-T{X+)-On>p+8U4?E08aF%HUtC0G zzV3g`@PDIn51F+nf*uz$H%hH}OS#!;2DUqxq2;rUO|&8HJ9YArg{Q%v-)DMZ;o^99 z{j62;PmDi3?ax4Sp`vB=I5-uIymIohq2xRek{2e558j3IdiJ+v`3%z;%4$x|wgc>J zZp^QR^XJDAsW`g64Nd@RPncqpc|u{C+Zlo?pDy_q`-YGGPu7B;_y7bU>Nix95axb1 z2xzK`l$BmAGU9%+Tx{@a^XtyMT25ByEI)^)XFuc2w^U&gLOc!S2-;T+p1gX9g)abt zB`3DgD|ECOzYA@7Wuh2B_N*rv*;BvPm^}={DG0i z>0@mRy{vs*jCb8Cx3%}*y=w8ppV9s#-BCWxNc9Qtq97oI1jJ|?IjtQh2PKUhMnn$h zTM3YXl%kKFnePn%KdTD|ad z_JZ9YVvDb9EybQkhPqW}hhJl07~BhB;D7tZKv_xFC@szOk~B6dUUb#+(&5qXPeVFCilxDb~?X zV@TDsA7?>TlaUred)bGEq2FDWVUxZ^)jTtp(l0=P$*pnw1vwK$15 zM+5n1YGLQ>7+SE1u3Kg#uRJYdK_`bl|FxO)|L~j^VLYAkb;Q|5&Bg4s<(bchBaQa)Ml5LYXCaurR{yV-b&1&H};C^AtQZ-1>9~Azc%DW zGVGh&wbBO=!*fEU$@_~AoWILFRmub;#giu=LPJAKO18i>#?!Q**SA%5b)RM>@f9gU zn?TNYY+tv+>fqo&B}M?gWklkopjGaR3iY{2$PTlK2DEsW-&W((9qRv{!!KBXcsx2+ zB|}+G4xq#V3~c@V{iUF+2EfEpPJ$7~ty`)u0Dwk7j1rR6MhPi|;u`W*+93s4==~;i zJ;8RFW@?ISU`Z85F&VHJ6$WV)x@^xpu;dV`Wq+)~D0Ur^zyMDnCqpU# zO7neaX>lXH%x3lmtIknfKvJSeYh-wM4hxaJZ;45Fkz$_XBXIGK{6Bns1ys~uv^C&R z(j_I0bR!|Hq@)SbARs7>bPnAhN`r)mA`ME{ND4@)NK1EjGvA#7O(ZRkUk#jxigX_6?`Ad-^ zy-yZ>APe8o*+~T5ONXD#mkk&k?|jz)D%W?rGvB;nGo3x&@nqdeN4 zJg8a+VR^<0bBZNj)ZZns5|02Ne~hT9sR1-UNy1G)ILgHN)2*18n4EquPfwe=-Jh+k z3Y%moKYoRVhGGTz43(ONNO&Ib=`b`02L-*sXc`5bl_uppx`YNMh!fGW^7CW92CcW4Xw6vIlVKA9by=hVF+YP;&#B}2_BYl>(d|?zy z&EZuGNKnz}3hKYpS%=8=q{qY%(b4fbQ<0H5fdz=io21ZMt9VA7Oudx`Jy&`LNsuTD6Of7;%ILcpC_ch_HPUJ%*@Vaj*N8e6|9hJfhL;2 zlozk1&V2$Z?s0PkwR^*sT}js-vuLE9fPQS8Fb_iyL* zhh4qG^{Du4OT}~Q(yCdTJ4oz-slH5rjBgikP`erahVvsVz00L^=3@Y$Qa-J<8WM06 zPxy@0)T-z#@t9jc;NBUVlf1nA__G3jlU5_OIAwHMB8fSRgN;c%JiHDrip;tb98)PN zsT^7#P)#_!_Zi3~bXBpL--a~62TIaFf$oCvQb#;0#z!gh+NiwGO;e2lYv(W~^%`bGDm5;5ht;cP2 zjl_fqA#dKiX*N0Rf=^*EZc07AGKVjxtoI6_Fdwk(w*Q2f%tk7R2QxP_YiVum&_$T4 z`>w5#)WOeAKvvJh*m&}_%P;oSN+CY8j@SkZhQ{Am0Adt2vI5T9HN5>^J?H;1vIn5q z$mnSJ!E|)V18?v8!|(Rx4Pcl}14Gk2w?dV(8zn>1-t+2wVw03S1sCmG-VM=E0fib# z=H)hHVRaLO4f9o3%LIHu2dRlL(2bq!7uR2Vi{M20qB6kb|2bJOuy(3fd}d-4??`th zA$E$ot2cNm6RcH`t2qVMK)GJL*mZMicX^;GTB$|)$yW;3-p4!vp;-Q?pNj_h_#mtS z$LO4z`7pLm&-yv|$+@d7h)|r&r`Il(HnQpDhh`JvUZSPARUGF8tW@}?$(s~ULV2u+ zzW#K^nlMmRQo_RDZ~+F(yewnd$cBeR=u`@r*_pSa$k<&jShxv^t(+Ox}kE zNf38NJ8&?2xVxuGc^%d5|H{kFT@oPmj$p)y30W5iLgub0WYF}I|C*$Hb>M{c$87su zk_Al=p3Qc|<;?d#+ciAfO8D{P2hO)~i-3!Bok*H5)b1g;@jN5I^?s7$ZE)3M3MZ1? z2P0Ta@7v*ri&NN|4URvn7bxB-y2@O~-10A%aivfnY$2MGf-{+lnmU?Be6O7~z1Hp! z9T3d5y#yNSjeMf(rn%m{c_ z&~)9dyrSfN6RG06Z0j|(&<15PNqJ^GarOcQIcv4>B6wZ$#GfRc9U3A+m{6O$@)|00Q_ac!ERg2LP_GO6;k7cYXV|QRk`+p5 z&bR3qxby~$2z{&!&H1i=t>Y6hju>nQYq$BfHv#{vEE7Q-oUpQ0pHI9$g>KFO_Jzg z@JTIR&39p%K@p#?gI{2K)9#~aY=wPGDap;{)e)Zw{rG23Wifv#jeopgxIIO+$NsZ= zgPFkMo&0sr=F1^g?9ZiINeLE3xfTq_&F@(z<^<8(n-kOZdIyZCx=b6rwdk+jn8fDW zB@*<2jrYPqbG_)EWZBG<>=CnwN2vVK<_o$&O7YTOqX#Q04QwLU;RZ}=H&sn9LZH(3-f8=@B3 zFIHI6pJUeP7)9`ADRWqz;W$*uUFSoTs+^RAaaH34E-I+OQl zL3Xyp}<&>#_W`=oG39m##pZ2_|85RwaOKotd<$qJrzjKb{2NX|F*? z@28On!9~FM4~sCxyfihxcI^^VSHp&W+BvZRo%s8&_-$ zY@GE$m+>?Fn<(hgKgKlg%qT)IKta@pzU8vrT}e3>R%TC+Jynx#JgoqMN!S;c{OMic?b zGNt}Bzhayhy?}AY<#s=cS}@H6#g|xwW))UiMjF(Dr^l_vLcsB!;%y zvobC~Rb%G0e5peN$ije`1#Rr=(Vzu>k+Tex{SWzl5EnQ{!rPjBMDr{!3N#JLWK8XP z`%Y@RjTKeG_Gae|a&p;nJ}4a5Rrh}H;%1>oiLt*a+vjcg0Lrl7s$EhM91NtbPcCsk zD9F(VCa=kE%XEm^HY1f5U?&@LpgD=8UNtaVrk`rQTcy8cmJ>Fp4f*-2|AFyQY6~DL zXD)r$xXJtY^eBE<$77s++roYz6hPa}7 zXozN2&HO{H*Ib;EhT4K~V&IHR$E= z)DVIb5yz9)(xbOO=f8VN{D~BMPz-er_(UKj4Zir45G8oTt_JZC9umIm!t&^ttP1k_ z&O1l&yCYza>Qkf_i!$(={h%1!V9S#`0F~rK(F|C!Q_fHUu?!4l?H`3g3DSkqZ>I?; z&(ZJ7!u3y2E$lm1Y01d;Cj8&{1-}3I>B9%Mb=kFQ9Af~OAFICRcw8I{CRRc*s@bh_ zBZF+59cpYy9L-EfO&WEE?Kc)gBb(Y0s@Wf4W49NS$@GHUr44){(gsiDjcN5ZmqtS5& z3SMQqtqN(AtvB$he6-KX_Y0;));CivhO&k+GoWf8N*^b2by3@X=`j4La794mZVM(I zNZimCyYl~Repmb%P!iGnu$t?h5thB} zEGrnHgZlo)0#s1SPE%~_-2fL|L!;;I+qdHDwa-tH=r#fvemeG}x_%gx1u``?-Q5VE z6n|-6TXwumHQlxq&hX=;ZEO2oLjK;TcPToi#H=DQ^YYR-LBlHcfgIm$Js@|zfrmyuLlB7I+- z`)qpPY0lw#jkoth{V~36+Ozkn?~M&1-Mso?S>A%rdEB?!N8>oVaGy~8k2P@v4b%>$ zyUiup)*rrmz6Fr)OC81CXFQVWUVD^NHCLD7NRnz#L9>M9Mk8I8joAdlH)(W$vzYrZ z&(Gh#MwRS07|{Bx8&oP!yGmzFv%7T2-)B*;-9K_OR|!iWuU`dah;PQyHk26z>|g^Yd5=jwYc$wGwF*!t|z z)$>Q%Q-o6f$ujN4As#6t1nY;&l*81kLv(cXH@c4=Ei5ilQc-bKgOc`VNElvk`CW?5 zmavpW@W+pY{z?ZeTojsDE<4x~Xj}zt1FFY?l@UkRSHJmseR0iON1s_i<3o6FK~8@t z4-ycxV?!Io{cUe#c(DO`6de_1V`bI+S&GMTvN5o^xw)f5d62Lgv@qn$07&~pELkud zWNOl|f=D>HxxK*AGO5UU%+DO)iu<|y>sO#_G(d>WPR9l{ys{=rPl3u?xAX0@KV}P| zjRMS{LDcxm^GVW8i1L#UFI34_cuu8>AVM))y& zI#p`TqN}ag8|wn)u`Fm4!F6ye;gd>CUj!N>gZ7_cPE^boF!KQoCpB9?OX)o520e=E z>+5qq^%WEr(n@)X;yzeWjHcDE_dEpLSxa8tA_N?*&hS8cd%nkKAv2hYipsQsVywoc zNFnPGf%~oXX3COt>jaPi^c}JJW5?~1v97tx$e=t%VC0e>dm-25R6({2fLtE%(|smN zB~hYiPdld>sA>SoDqyGD=^yy)eN3PHiL#~*V-L7w{B{%Iy+8# z2=x9&Hr!Y=UPIE^Y88*YPJap1A@kHYyAynTkVb2tX{?P-PL$O@z=|85lbh#{rxs0j zxTK+FU2E%9T(mA7O%tfNaL^LI;o`!1v$+__A`+*Ek^V7j?#u{mI8RJN>EnL(S?JAVzyA(Udt2Mu%z-K{D8jJ2jO}A#u>cBT38bTj0_nJyR(E!GfXO5I z*em~#MN@_vK=>Y)?K*D77;0-*J#M+&98M7v6mruzI?sscZ2->a&|RViyUAMzMA51f zq>WyvKlV0c!M>|^Eva)R2DPnz_Sk)9mj00D9t9(#j4ya1P=m@7Vwdn<182R%v<3h& zw>10O*nc})%=9N1x7gW1ea;hh$?d_ekI~#8&o9+U2(;nGVI;x2xU>f!h z@9uRb$kvXo#;vxlZ|#JU`&&n&_yk$6OP0tf=>5r7W^kf;u^%@9OU;$}*!J18TlzFa zL<~`?cOOjipcMKEP{+XqX?Qn7yki$OI=9vi@XHT=KsS80+F4(*YP9;j3FkNkvn^W| zWLBJE*HOT>#m^ByQKgr_6j(5@Y|OUYbcMKzL8Am;pzYpcEbBXW2zA0BN4HUg;LA16 zZ{|eY9N`ZWy!KP-!1A9;@*G1CFWS_Iph?pzY}d?FTRJ>7|Cs*kXc34fyidQuT{JO0 zd(Fke472D_U!E(*D`)}HL{D)+9n+QW;Lbl;EI^38x0lsj^x?w|m$j?-lO;cL?q=Do zM04nSrx`weJf*xCDEM4%FW<7TYHnxo`|Rv2Bd!Z7Snct=hEZlg_%s};KwO_g2lTz4 zM`>Z|@Xoe;{53^HLmM-wovo%^Rc17~?^zjRJ37vl{DzZceiMvT3tsV`CZx2;JPdx0 zT-W$v&+e}0-P9531}|MzyO1Qq%P_n^8C$xqw@%mRv!7YmG#*v%1Rpn?wy#uCuj@+M zu86vFz4=Q~-5> z^a+w}`6Ho}PVq>TAUKG?7S1Bw-Lt&3q@E_hzJ^Bs@yi#bPbP1*<_X%hoF%Xb8A3>P zYF$gx)5k$UM39Y_m*me>X&)&x`l7<9YamV1fad}sqz_Q#s}K+EA)nX_xQ#N`n04=i z5u0CdDLGnG5T0!A>v8ShX^@X!dKHfwO_#+U>HQx3u(F zJnh{L2H`7lKF|u0!iSpo{Vn^ca4mwJJTKkS*AG;3YyVE4*3P#U_pvq8q`O|_K!P7Ygb%;hD|t7qXgC=2Sz z*V)CzJ=TBK6$=Uq2BK2mqUhevTyKC&@$(bWYInwSH;qA>r-gmrzn=$$eZDZB)F}_h z!YvGcRFJ}Z0}?mhC!1}L@BoLOI+bEoozZ)LZ467?V~=Y~i!r7>Es7QGF;+rTj(}X# z9=4pk{AAEwYwLo8U%@x9nTR(N1FPnm_gSAD$3zIWENKwX$Xy$M{pV*fr@z0W zXdsJv>cIdgfC3pd80uL;#V?Lj<#6iDj~Ue;lXE??1ExLyMS6OJ!z@4uB9pJiU_s%4 zu?{yHcbk9x2s5v4yIUO8o2N=*ZBj*ovd5Chc2KC!daxO1Pc_$-6}DjqtDOkE(G= z0t-HY9l>qIg@4>a=8~9r8vK08Vi!RbBPyYls@!R`Hs6F{ z*)uZyCF5{cJ@mb>DAq6r++}(N*IVLgJW)m30feDBr`Zg%PJ;C#|5IrjEHJJ5<;{^3bV5}Lo`PN$Z#`C9>A6+b?M!8)c^ljO($^IGQ(9O@1(4jGZs>7M5-0fEz+X!!8S_ka{h{+n~`0T|8WY2%V}zwmq~ zCMJLyT)s}(v-2ot{RzexG8&(N%tfl}feb>f%c1gl0_FDJm%aAKLN=G#Xk+)2hjscS zk)&sAYHL+HyW3sa1ZWv5c%-O>zavU#B{0S`^-ookkmxha9(&<;#w3#a88VeZaSNGB zX)LtnQw@tE64n7&`O4hWcNE!N8c@y)Pak>;Sn#&e>turGZvb%rGmeL*GDePf`1Zp7 z5oIV)gAV-8x}>mzmz*1CWU)|zT)upn$DlT`ha6GyPgFeD5cczHTDZTLr9sR<=|792 zTp2uYLA~JDP8RIu)>eEvDNl$Y7>#a4qdRA>175+`3YV7`hX7mOUlp40n_y3k0Jd6x z5x$eXSX&9$5F}UCaII>_q9f=KGo&y*VafGwW^rF`SwH{h)o~oC+R$axx!;Si96?O8 zt|j;iBy>Qc2?`M-I?sfVx&XRpVtV>Q)MXsm54={N%*6ENWax1_u3+=AUhF%H2Ow<6 zB$MV|965zMe&HQ*9+e33nW)o zB%B-$W1oVS_FjZW@#i~@*+?FND`2H0{8j@YPso1tlAeSZ&PyPnNbd-wRyMP&**T z{2PbEt>0Jx68}d|hZ3wDku0WXD0fzcuG1sO1^S%?Q5XJ~fcJYkDA4rcE~fEF5o+cG zKd=}69jXELeK1N9;2$DwkQXDYv_WG~^dAlvMmS2*@9k3u@|X=GpD4ipC#I&7Q!JUw zhT|OQ(IU~&ORuhX(Kwt`zQ8eL2GS;#=odHV1Lx$$b5ipT!hC&d~in2#r6VD7!0YqpZKz0OC_G1#~11l>8oxmG47dXdKsz6Ogt_j-TR2W3z+8SZ2a z*dDIIKz_-~`&c+r>t2Ica{9n*i+nK_$?6^xrM^N(H%LT~9yXnv*f)ZbB3T`ODcED)!7vn=f3$SKe<&wTbEbw#crJf z)3OW@A{X0}n%6*TKP*hJ!oK z=}qaqpHY5j+UfIi9b%wvfBBAfQhvh|^UmS;1XfE!`;vU&X)SxNy8P!l1^E@~>G9Wi zagGTTqrQk4+-1nC&b+B9?c@42o#(kfH+&GLFkJf8Fg}KPVggcm0i&B=1ExlM+-+?n z4DzkC)L^_!P?&iJXe&vSI41RmQGVP@6|34@zWz$?&NkEFNT*6b=oS7-GbjVa*oUfY zvygDhfb@oxygbpS0U=>X?t>7K>xM@!d_eA0|NiusiA*Mc*~gds-YK?K<59lU(hsFL zPlm&^vFy^%7md1tQOK)R&}ahHl0;PN-pl(seoJ0J@a=FIWL4l*Gc2xl_fZNMf1Iei zd>OC)`kkv$hR1aCZQTZG$Z_uFdv01k2BvWvO0v;s>neiHRdiDluhs8G`x!d+xz~97 z5Ece%ipVXO8igWqzUL>dLNGE{M9wxAWSKf3t|C(@W8lt;z3f62MY#zJ1x_h|( z5E2S<9KEx#ZP7q}XXFPI1s-*bHkATjxOFtgBHgn`fDS7Uc@~lWjaB*v!Jc-3rsjQ1 zOM>d0@9jquE85!Qv9Vu{mWMu;&X`YsKC8}Aia*Jb7^F)TXU)saycu8k;dcD(Msz2q z(wSHzx}yBpvWB;En%4JU<%gG*m6mdFnjv_^d=#ebhfVP$L9ey%GN_D?a&b0}=xREd zWO2O`*@16uo6QdV>?r*b{o+P=fSX4r72Q=X&gM>)_!~F^fUp3IML9lSMfh@v%D*r- zLQpBVMJs<+foEv-L;6e(2p?*Ro2jjqH5BNAV1?oqN+TiHI~Y*doW#Da=l$y!$f^K& zG|6=lMm{C|hy7-%d7at38z$Gk@zVFIkWJ1Lh!2iX7u;#nI+ZNtypAr^-@7l5UO9e+ z{Q85xHCOl1R?hv-LFs?wa?G!y~Ga||& zholYGppe2_C_S3?iY*6~9;sZMfw)~=B*F3>`{;7G7o}^&eAi%7=c~gBJ9YguuLgoyzM{B@pBbUZ z-%z(%Aid;sci6q8<^kW1-`N@5(i0O=>H6YozKHj0Vej^s;?79Jjl`m7(^@?w)RBgg znDZ13Vc_2$Axfd{O%A$~pOg(85r$Q*ysvO}b3OVF`tz!;;I?XXAeq}ZycvPkpX0b| z9z+_z2W-FDCOIr-o*o4S^+NZy4`~aDsfk|}Xu42>+zV0v*~4H@35oZLZHyGj;y@vQ zF{4u(L32hhy=BND3+D$%u?X5J$1%0eZW7+{9#y$b9(;Rz8E*PUV@ddv$I{tO=$b~) zz}WHaCQhDj!7pl1`^&Q@v%!1SWM|9&IC-La>u+lvPd^&x*0)&3?Bjw^V_AxwFN2NVH#dLL5wU-hZ!9HM>MiN!Kv2!eaP#NK@9#U078ChP<6BzVbp)OB zxtLijfgWV(ulh50ZPc{1(kpGP9m%A`jFwOJQu7TXs9BeNo_u|=Tl3+*nHoNJj9D4o z*oVx_QVwb=Q#Bl%S3s&&Ow^n|wRCZfjZz}kj= zw`8TE*K_6!UY27q7!S6zIx`FK%slLM(|%>|J$^d!-Jn0%Y<%Zqw(_|Me zN5(ot6c9tf2?C2pzy>`}yRDq87mdlSMHku*rPNnbbXD}8dxWeQW10Cpf4)7NJq#Dn zF4*imV0WtCk1!mkYWNmm7#wLh5^2~LX}C_+aQ5^>S;)K2>_otA!ypp;;h_upIKMZg z;e5I@MMD%tLpk_Jq#+BrfOb{$ftl)>?sQ;jFFGH@km3uz4g#K*16b_2Cf$g0tbE!&+`q1^Jn^ z=&Egh^#eVj%1)?Xxz(moL-}4=HE!lBNHt5wz_LC0m%a2E-ck+PxQ(u)@5OP_4VX3J z{UVOXUCBUD^U?9z6SFCR$Vk@D+A8k@0OpOKKhV3Z1yZ(QuRy5-atl8SMd3Y{fV#ma(Bx$6EFtcpZkfoS?G7%-H?49yujd_hJE#8FzX?wZU_O6mX=oL^6KnE zgDn3(|H~X4#NxENy*DtzU8`%RB@A)no|Or?*A*8Gt+BDa*p&xDs)#cMANW8MG)Fl;Dk^R+bf0&`Vf;^*EXg))@ly5C#NI@sb?9;{Cm;!XK+`(V@G2 zUQAv+K-{eKJ}!F!v463wo5FaCEICODS^%HfWjx|y0<^C$&zGMLVgNmA4wBN-v{IrE zKRcLasN{@htD()2D3wmK9=iN$0e#|)3=I$7M}&wM@c8xlD}U;*RO{7;0l<6vI#tAz zyngH^Mzq?xewrSvfX(ng5vy9;-D#2WqalJ|BKjTu?kYt3v2u#1&kTeJd>KGZ(pEB= zXv$;JC8yg+#s$zVfi9VZ_cU>&dV)7k&2zL1eMkTJv*<)hA}=;J{WolJtZ<~ zJwK!`sb=t+`Ft8-+b(rz>1?>xTx;|_XQ8|=5ug1M$MgW(^Z@hpz@urc)J@J`2SP0e zWKT}0o}OI3?oC-lYFqn_RC+q025cBHW0vP_6b%xQhB2+Y)2mT zX5SJ4L_kL%*D0N;iKF|}K4^VCTRAE`7<2QL$>aUm)IdD5D`XkeloVLh;S)9hH8BQ( zV;dw<47ojEFvNb8?(ljEmYnqb`qI*rSo1YGx{->nZ9-oiS6z3l#mr-%k%T!_!{jR; zFY^LkPKXtGM0&C0G^VkOO@ma7Qdpay#SYm_)m1iawkLt{Qbrs##r-Ax=Q;s<^pK@S_kmANtD=)9m0=}Vqqc( z)ehnEAXB9!33>n$?eDg8k-`(hT{mZZW-@G_dG5Z?6aG~}b%q+((WRyt8y%hh?Z{m_ z2Aq72faM8Tx9wpYn=wKh^fHxAV!n}j81MktO zYiT7gtjX4T)lLb0*K7_9@5UnV`_kI;0{;~%#E(Qr7Lvfyof@ZymMIhn*e2PYbV z=Kirx$qsIBQ?-|i7W=2M!yd05ASGRlq;SsGZrV2+U}bEj^SkftMbIHszlo|jpM2Hf ze;(h?11!qj!)0o!SS5-OwWFaa6FkV9sbm|@48;}L^1n!!ufO{W<^8DQETaff+s7rS+3rfh_H|@Ex{jf9P zP-4R*-7tAUecbSDa6Q2V<7BHa#3_#&E}p$7MV^pHJ6yw)f4Jz`6b7n)2nYyXm5z@e zn4NG{yM#n@#1WW$LD7+NPx+FemNqF$0thqmhQK(KM;1C;70L0jdV# z2t$_Vkr}XxWDsCj~>m7G0sn-HzWEvI6qDr;0 z@5#%_=@}wrYbFa<(~VXf9w(}N-SQjf;meofduPP>j_F%}nHM_{B~82yt52J;oqI>2 z0Dzwt2%6@&zEu=55xP!%)hZ zCyF7J{K&oGn@2>EQ%X1BgTx6cmTYli;NqW~bNF5&@6Jl}Ve*P*Tp>-HqAIBWfGzCU+1xHY zGjBckx_l8$qmcZ&d;WYKt_%-$ zb5y1<=aCNIUp(E6H9Mxbgf!>IPTI_{Xmc%Rh z!cnKw{B}aCx=ttH^SK`azDp8<>gUgFAYulJI1|$Bh+h~rK{oP68L0w50kzw6L<#iY zuVM;R9-s;joIw?UtWVchj8a~#4P>Nhi_Hg8I-ndk3c#ho9x7D~e?Lm^dqy&G8F7ON zgl39(hj_c!3uqbECoV%9&=^RUjkuTzjX-+z8-wL3Nz*d8-k>xpzy!eMR#L&`#C!4KWHp*2XTeHrBWZ1sQDQvEX>DA-tN z2#-w(#sNl17hRC|H$Z-Vi7}!KWFg8Q#vVw-3*!~w>F z&4&b#DNqnO5fqLDM?JKXl9DE%J^uMT`C~WgQ(|upE-ZW-ij)?H&LRf#s!Abpz5IFT z_-im^NkGOpc&2@72J-XeW%n4ZW$39eTP?#yA=&~t>a@?ugDa+w{*SV;;PY635*s8B z1~WF)2QQ?g1R!E(WrEk8lhL)N5w)$tN{Af)wfNI`#&)Okt$gtv76x%ez7bzESXdx%IP{00p`C;O9^Ix#fL1YUkC$S$HJW6KU`bnlR+(M8E z4n0QLdCe0!dHm@K`^A3rMPQSU3cmyG@hQCrC4{7l;xJMluyK=JOssRVL@=A&;bz)ol(~SV=vQ44awtUW|eg zJC{C+OhH#BpRIdWuIbogdHt^M%|-=klB;_6o-g{NoYyYfIiW>>@vx%;%kuSH!JNYR zJXyublxQBydjC4%4^_Hj&L@#_lzrj+Vh7#_5XFZ=g~2kspxb@?jS4^tBJklu084`l zEeN5iBd+HNu19l9Gk12rT&z-H@MDMLRxb~Gz35!pq@%nC=hF4X_Bbfk3AvV5E8jGb zDV?3yDCUSUL=+d|AGJE@o{ypPtqH`@u}pns(#X&)5e^2hg&ReD2=zPI+jgR8qab?? zH9?ebJz8gVxroPuA$E;EBQxx6yl`V_Gt3Rn^3ciU#ivuo35ex?k;na;?!LH_ItrQh zeUeH!H6f8z^(shPWlXc zV&Uvmc06;KT=!67dtop{$=LS4>C;KYna`s*&q-QgK`e@GPp(`cwH$$4(8Y87)MRaI zY0^Mk9vX+WApkDFCkyt{X|2}Ri>^Jspn;Lb)hnBGA|8l}FJl*-jEa4*f(Ge2j{oZ! zFc<-qnC-TRNZeY-w*;5Q_6cZUJFnlX^P~&leMYWITr^YzfG)u15cKlHiV*B`%P&gk z3&hkCK@SMwfbGLJTRk*HHQ1ZE;PG?4779PS$u%gJOvF?G-7PIuMU=*gOvG|A;=|q; z29NwF=?yAS&(8_u_Z8sS1Cw3Mk2oq;@QlUI=Z)D28({$-1tIf&xGbRgiNR}7iztme zj6t<5Q3Qgrb#9%)WlSGmCVq@eXbRti!W9ty1_|m3M@0xQ^bjV%13TYY7vC$XP@{lF z!o@(GFgIThAP$tjU*;^ZI`G>^F<@76HqIdes(-&s(U37p8iLks{+09G75wvkM-`d{ z-r$k~NFPHpXB#ix=J(lGcMawW?xg~XC2XI*(To1im6bB!kV=rifd(Ru3PL5G^M(KW zv&?*ikV;@ZW#Hh~>vYoHzmv)r4_}OrY7hv9NCR<_U{kZ6$9I384EGAE0oW;^niKpw z=*oWtWWoDc1)~UpVfvfd{r8;#%!$n>0_^`9!v3e>DgNh|z#|DqL8x~x01NNK%Ix^( zp65RdilfefESOyq7_v_01Ni?KlPE=~I3wzN1dn3T2ZHtA(N6V6*;hjAMi3@G)^CrB z{O3FuWkp>}hNMP-iDLC=s?zsh+1UB10f^peD>C{I%RqwC_^+rq!ki)X4dxN`TQ`20 zuQ?QBTS>nx0Ziai-X9OsXdiQC3k^@BlLuOVTa!*XJ&13Zbg;13cY8o9rgiTPt~E#r z1Lklrd*w&F>(UyVO)?Z~LPj~;&nW&`A}}H>I|Am3UNFewJ0qQ4L%6=L(9zS@*4$>3 z&rEkfOR=-(?P`)m(8ja%VEk^XR|ngH(zj}uwkaeYhgIi}cAGBz#sZd_PRAQVGb?+G z2fjNu`Xf6(-GjHa3r<91;T|oEEH*|n01SzeHY!v1y*keBw*M=z?q6U7`JbVT7nyiZ zS`h9!mr_P4eUo3F{Rd8n)zFq*U zv@v4Q*jN5pX8_5*R9hz~#$inqCb$Pcg4Rfs+50;k?Gk8w0Z`EA?z2*7haaXfVMJOW zu|7Vi;|aAfsCuIJ;8Stm6bx>96BXA~NQ@DY7nAh|;*5H!A~vbTh+yDiah{9rz|Igx z4!-|F97tCJ0xi=m*%%g%)$dD;6s5weW0BvE=5c%i`Uv)Y@(OeBDcp~y38xYxl?DBZ z!aiy#$UeVcTvU{JFKZz9;1GwJ`y^XPhoY3wzYwcQ4Rdf3)wgm0s!I@Udq}-@2PXS> zKVID12669^im1>L0$;#vyxTM!{hbw$&Oq}%ld|QtQ!uG1pawv32Q-1Z^nMG~V~}mQ z#eB=2KHA1Mkb@>6h2Q;?0RQy~jIwc3llprS$4~)F4qhi zPDD}ME+d+6TzuK_pp9~44^ji;x3?F?Z|K1U9j5Nz-8qO+@_f8v_bJ$c$7_gIcxHMw z@X9S~Ixzd5i*cAMo$2k0GPb*uXI_s8MM%G<<`m`*du=s;4e0wKmsHBk&Tcj($5jey z8boJnh|k@TbIUbR3#oig3{quW$E!1ukCUBMCPqG*-;q~D$27s@11qZZa?M$j2NpE8 zXsT%~`q$|bTPLs6CJ~;O#!UeYj~SSjF#daOy{J7#?e-BFK4PnSsj( z&bv!V%}oRVv$ctOhkIXmMcp4rj&gWYECAV2vMj=ja|qx{fD@yPR;~tYiFJeY*X}dy znWxu<+)ojSxVZiwGYJVU6$KcJc?V-HiPIXL9b%qmCr#|(c3C$=gZ1xvWUmzE=E+hP zMcAB(b@l#W9%tFuzGu9n*B+-qM=$9L9422WLYdGra0Z_|T!lYMQiV%wTiD1~z;zB7 zIk$(2>;X)kpCsh6J|x&OtP(u~;_|<|J)l^S$kln?W&lP{IC1SZh%&(XXs-yoed97J zD1TZyX?gX^yM9CQ7TkN)DYC0;i{v=2ZfXt1FDnTDp@f&7QE7k1YJ*s9H8Hb`lvwSRgxOsc`hrqk2xq~Utlr_eLjYI@5+xT z7k!?9GJvWd#3~dRWLIY6!cM>wY^)<>gc*UI}_5zz^YX1{_zICmLHfa80Lbw_}?Qs|3Z;PW#-j z(q6SJ@xeJYrkXDUQsF`7HkH}~Bhda5A<3ZblOl_NWDr|~LKUr67<&)}><{4$7C!X0 zYj&j>DCTo4$JEr6P6`E@srJUky7l54duu*VmzUL~;)|Lch3~uojoS|G;1w(cSQ3Vs z!-Jy{)igL9#NPqQ!biZP+xO$pMuJ72J+~`g>{yKIQPTGf952u+?W8v>>7$+a1UL;E zqt{H}&Vqp6uLSdhbjOlvMA9(u<{&#zA%s^`b?T&~-k$dOGzKBXSB+#UHCTNVP%2O^ zXG_CFQ7%X{=bZ-C2g zNus&lapE-QE2e_TVhKOK$D&TE-#6uqlFC9XYhkZBeyCF!c3VPIp_ov?@N|&_V#Ea4 zDvbzDOc)~s6!cL%4+B}?o3D6}QhpGxdB(U=KB0R;%;*%#X76Zeo zso0HkVTl3y#Dq?MztwrOE=}m;{FG4xQve>)Mgu*BiEbsB4me45!>GNB7zWzTaQ!J9p!X&C$NThF~NDX9E@E>+J0e545bt;8V}Z=w4^K zMg)End=2zBGsw+5^f9y{C*DJ}b;HAS2NIW?e`%DLMeqY6H7|4}P(Y_qgvSFjz~-|h zrl3F!Z4jr{HB>i( zAZ^>U_DKzMuZvDl)9nfhuH(40;{=#QagEn{QfA4&=?)W#kV;PC1x#?cQ)t_V3*W7m z8VzVd0I`*fPD+G0?Bu@ypsIESANsviM&dCLCHR3(M99U0c!V9$@@DdLsU6 zg2h;Z&*-|gR?%^!7&}|VfE!VPnxm&e4 z%AE|4Gdqbo+2s93fU#1P2j}T@fScM~yBPFhAGA^5KME*FZ1LY=L&QygBuoB?jGWIq z6mkSO+@c8Opr|c%2fBYhhxi#0Hobd|h`8o$>A(MGQ~#OlWx|PvfTz{zee5}&D1?nm z?7Qw%W7e*rxLhoKT749D_VAQ|=_!1(Xst~5n83uVT-QV1<5N22^0wqK=WyO>t)lfv zUfr9WWNU?QHW~>#k%EH5&!~0Bv84=~_a)9WKlJ+;#tPFO)GWVv+f-)QDfXyFMYGyD zCI8b>L+9|_T@mf{hqc@)-cj@`(btQ<3_sYy)ZS{MG4{!EyZw{(+cu7|4~wKCq4TqT z#Zrt#Iwnx+e!j(4(otMMZ%Y7f^pCWdzK#HDHoJhns|~b4QX^0+elvm*qXuB_#AHbX zLQAkt{M^w7jifP{J#1s!#fD<^JoS6N7ocjyM)&@KfyJls0cMH#)r4w*F5cGDAfs@hKP2VqP|&Kj-)AGpIgT>Zu^95^W$-=1yqck77h9>ov2%K zws9#X5q(Kqvxv*J>lHElY8AL_yi0+HOHs+V7X6DLAJc zPs#N8QeQsoeZizMmdKfxTcxV0rjpI&W%)W^I6+g}>!50u^!M`f{mcxrB=`e$tIN=z z6VOa#Z>4E_w~?qMU>79vTlRc-MctsjObyjcD91K#0_53)5_mo84A<}LAUqdKl8Idm z;Cr3jMEiezeFrqv|NlQXWhAb>w~$>~h3pwZ_Q)<}lTpgO*UE_On<%qVM)npdSs5iV zvof;x9{<<97pdRp`#+yKBHD5l1a65Yzgfv{MpaCaFl!1bN2dJ(3 z;)2R+yNr8+1Js< zjM5@FiW}9<0r(q4g5`{EE@j?piSMr6bQk5`A>)^7gxyunSU`bBa~V+7Fd|$Fc^|T# zl7ge|B#>2pe)B_TDLFnNEeHUG@30B|p+9?svn9)1(W$0CEhVe=(?l)8yC_8!xd-M7^|GTj`Lkt7jZ>@GXgfR zKSthTt$3IVAUrLQs7m=CdG%5ee~)yilXSi$#m0=F-S%~ zK3otL(V!h-XKP5mu^H-iJeOM805KN`z3)H#(F)pv9)|}_4ypo*E_QR=zo^<=%dOa0 znVywMPrmpSzZ8*M{U5|_J802b%5naGC2Pa4!Nq4#$7dDJXJ=AC3=86a$|mkguld#G znVj);>XCEJ%^UF^EQ^SIQb!`B@Extvtn(#`4IxQP9CjO7f`+4xPoiQ=c~$kk6lxlh z&4di;g!&C1v6o z`{Xd%qxLK%@}}YLcH}R69+C|Z&^@h|QoVak)N@%N_~HYYfG*%Ke+3j8|LyG&|38#* z&a9CBxA%bYO{fpnVmD`j{L|h@Y4o%iEc9+6?8ILJ&tty#kzb?CbSaRI4*;Dw#ysT{ z!p|~lm@U%64jXaq9)K7~Q4zhv6L97Aphte}g625Rt<`IRF;9n}dV426KRq1{HzjSS zAq2p+cprYc9=rbuiOPEGi_T($?FPS2HZ)#=f@AP-uS9~vWV%|Sfv*1Raafk|X))lA z7b{{FZGoaq#`2)l3Il;p`oueJg;O|+xFDqBvU(IqUl5-^__6bP+?}8&yk<;h zhKQ8C{0D56*7pL4B0DFIIWuqQp3U^gP}B=vzh|i6bIW=Rml0|6^afYv?_N^$mbQEL zJ^xqz_J=sI6e`|iYjrX2vo9{)ggU#s#M*p*j5rfd&Tt~wj+YUp?%?}aiqfm{g7wE)Cb0*%x?H7Ej^Y9Q^fc3*)A^3 z%2JZ@BKBq30ZoITIS@+@WQ`IqHdGwqbAwwwTSEibMe&kUlfx*5-xq9V0=jG?V+OMR zy(+A3OgI7NPWl>7qSIrW>gB45{=6^}fS(|pneMaE-fp`IT8!yI!@sGS0Bm3Cb>Xkh zq@4R=HeYW^(dT@fuIrz7Z5=-KA(Us1)D+d0=%Ix5dUx^LiJgdj{|2tGxu!5UCX(4m zLDxS1Lmn5<=f1|b?3ovG5*EFaiQ^oVB*>fEJkuISek~}hu05!|ecnx=2Kwy$eIder zB0OR|F*7{^I)bG|1TKo_!kh|aMg}jwSK`SBh)V-&^N027ROX- z7hl4U!XQ-h48}1`{((dP5Yp_U1bb9g{edGn@v|pVS#P-Rm%l&1Qd>snXj8Yf9v2i`_QHJhy#jO{BW)E;k040}`J*kxfD2hG)hwBp< z+I=}J)`=%HbTnSSm6=YJ&ofJQmS}&##mx_lw7vhMc^Wz5yl5r1`L`R`gt_1cYgGifdxyrD9e+M$@F)6E+$ypjH z3EJS-fF4&{EkrbkV$?O z)n2c`DScQuH%MjeHgreQ@}p5-aA6&0SKGci^Y6Ln)~l~ zH@DYvD|TFkpR)@F;k?=wh#BME?{__bHGw^KLZ-&2%lZIkiXeh?D23BfI{()C$v|W$ z`Zttt=4W<8@BYHVeUw9v{t&6ujEmM`0H?~3&p04iGqc%;{dpLa(GE>5`Uismc=io{ z49fIxKOCJ6V8rh>X#Kn3c6bYLqi~rT5swe~gCak1ZysVtj~;;?=*gdQHv=gUG#1oD zxkgy@hw`1GY4eP{Wa+NtU*;{L#S|2cQR_r~>||cn!W85U7x3rGc!1X8#Lx>R(~uBr z8vYwGoy#Uy!(T|v-#7HwXft!t_HcJc*9*~m*FFrsxw$g_!>26|1Px4`3tuL4<5z@+0g0!j+ReaMn{C%e=>%`f!`4@P&ttCBw1!c(rY~LCM6$sPx zIHog0scaZ{`C?JnKD#GIeABk~%CgwT#`1r;n%V_WRF61qMed;~&v9ii0jF>2nVr1-W=0nK-vA!0fYsuSZz=-|dS}1ydy~n%T6nDH9 z3h%)QDd@1*R31E4V5tgbgpys;Fv0>I2>}cSX^qqJ-qg|qjON9`0D^b`4RYyzbX3bT zx1ACCn~hqIw+Q;RhIGSh;=rD)QzsBn=MhoQrxOt>THS)M#48AXC*ymC3kxk=Bjn07G$G?Ni2z&RlnM{OkCfHt(_R1eQtqm4;KK1zHNcheZma8IT3c`s3;>G#zM&22o>#1=XW@{YWeWZQ zWq(i@;j?BZ*CLl(4OwYkJ{Pusnspulq9Vu%n{x$Q=4t@I)%l2`JYfVNU780wuIqUg z_{03GtFsjH^3ag5B@a*JS-r`e*rWzLah;b@HW!*ZH-x|R8ONzOx0%wMoarGU$sdaB zT-NTQQ3wuS@&UMemGgN^6adL!e17(_i$HSoGp1V`82;=pvfaO)c0asy%R(+*;pC*^ zsZ+I|`=Mr1{f2P$KQji$N9@Jzax~}t89`aBx=Z2HT8Yg*{ikj1Z-0)M5y}JM&&Qoz z3QoQD?_D8t_rf^11(myPtx(E${12fh(cl9|4~|0)q2$?)r9e4vn2$*;{FNRR1GPWf z7I5EQsP8V#(+q~5yX=QO0J_d5|2Hedu@DHgr0}tuk-HpJ=I9ogIH2<^twnaV7|0Qg7g8^!HmUY;H7gXTL-0Itn&~ ztSv{6jg5EtK_n$@;^li!^N;`jf>YT)VCCEQWaT6d`Mx?IU#*^z7)9Twh1C_I#oVI_ zkcOC&=HBTUv^jN%vxJk81`!Z`n_880lE4HW!HuY9aAK$_hIZQ`P1i1SECFu;)s1`b zVa6lW=n=>{QXfi;lYXFVn^$bq#pjLfok#<13ee6Q! zvgQ%ZKGF8whx?9`We62to|+YNmz@!v{qlxpku9L=_Wl)@s3a(5j-4QVQ**!(^ERQq zt?OO;n-pn(T((B9$tSw-==zU1ok4Gz*h?5_Km<`O;^)t0 zMNZ3RuK7^A!mU>v-R*oJyo*SB1jm$2L@q3Ri@fqr(S+9b&=CO^cv7uqylJoX94Zt9 zyby{Tn~ko*KI+88$XIe(ZaZ1$+^jqqnf%YVxXsTl*+1pI$7(Om`dR0BNKN|WQ(!tY ziyxoZ_l~*+ny90I3RawkDyffHl98+1(pZ`|{e~3*1PEiXeyuYB9oA$Te{tQ{cz~gk z?>QX0zkDa)(TH@mcYvu%j0|vI-G!*_y>>ADc`xwa?K7F+bGw;E8GV%-cKhtzQLzyyP)XsmRWq zcDa}iQrr6rGKO1bJ0@LqN&KFBd;Oy+0S}Q13Xtv5H^Kq+Y2wxz*6C7mAD3W!a_Y0Kt z8#S%R_w0?ZrRx2WFI(FhdlOEsW-+V3>dxddHuz{yO1bXYU0GLxZ5f1@roo&D(x>9VLn0 z`}?~K36cu84JboLNlT&pxE3~F4=Erp^YR7NfA&BR$-0Ri?;(F2`Nl9x*>mB~D)Zi) zPn&6H3YQ2rswz)u(pJm;KG?dqN~{F))LA~kk2pQ-TFT{rHHZ>J)Oj#n<{Th_dDC}_ z^)WwcnL3j~Qo9vH3FtW&xa#O=>59j8ciTAkOVZzaF*C<}e={hNXJI@y=)z(`L{;}P z5-aDQSB76gWe(+jbDu|v^fF`Paxgd97lH{w?D2`RkH0jBYv)ihnl^2nX-AF zatd6AB^?nO^7mso>w?PTyO0bjY(n%p#bskA0M{vV`4zrYA#3)2_!BHCN5bOy^A~R= z7G1lRpOs%Qp~YuD!|r!A^zSRRqa;5imw%6Py}rba_#;o!D#I)wKup)t+}u3tWePOh zYvP@f+BRTQy|Q+D7jNhoFN}P>MU*7zk8_!{Q9+gUe4*ZTPhi3ZpTO?)-h*-7hgOnNGOEgivKLYVjve0se4+xHB9R8C+8bY@+<_W? zyyV0{P4l!bK(lDC+86e#E0L-HOJvQh%_L@;bpKBgj6$X77EwLKhYM*N%sg?g_ove+ zD*<;P)x2|?nk!f9Cd|0p(`ooeam1|X)WEjt8uMB9z!iDb5Ej7ydd${Xg9*2x^|M-j zi@Kn>9ge2*s2GE+yMyh88Sb4Xj|*x%Td$Y2VAer84xFMl54~gXWf2sy)|skvYM`wY zSgYsojMT5P8JjLS@2?e9n7pG|`4oc_p?3dwbp-V{xhQC${OONtv4?erqJ3e1prHzM zJ;_mMBydtk^94~8$^fFMF|E61{juDy*wj!Q=SOdf(SD)3@RR^G;)JZ&G3`*IZ(904 zM1Q;-hn){;qT0n?7=!I{2%RG-dv>C>`c-GW!Or2N6?N0xSo=^E(_Y; z^(v*-?iI+t@Q#6`#L=P&=sN>N=<8XXAuJfTyW_vzZs2vpyY-MgC`$$x#t+(};qyM@ltogMFY>4&VUx2h)#>IqoBkn#~GbWX%80Y6pirFVImXS}te!e-^y;3a75 z!fusLqsyW*=#KxlI6zlIEc;;Bd4u?PN(|K?2X1bQyp8gd0iGw>E6_QFNVr5~mS3%) z)!o}Wc4cJXjQW{u1_5@$RgaU?^@Gt8Zokvh=W-H!_{A>>HdwD%=h1o+kaIS9t2<;* zqM*C!8)(Uyt4ugh>|@J98klDRv2OKddDK!dPsUI7R}>EOTj}VHKEoG?cyBV;B`P$1 z&F&hrN_vR@c5#q;%jFckHrh)B=YFHf)jpYGb)))`*vSK-O?%p=VSo>U5cGujhf#ul zB%mDqmoo>}5k9oy_XTtwCzlsnOCl{m^J-e^746L`8v%=+&Tze5IC~^FX<%-ttws0q zkfPqtaW{2V8~V*&h7jUCCL#z7`yYNas40*!ul=w=qsh(BLAVlF$iHb`wQCLzQM;Ng z0i90Fx$?FD+hZM;^fPBZ>1Gw&?2W&E-Rcp~52K6TIi)9)y-;e^2OsGqRGI7I<%EO= zf7H1EIG||4KCf)HlbewU8B@It;E9$|j5QSj7PXH%C**&$U3CZSgWa~BX!+v#lQpj} z2N>#4jE4BIt;jmM{&162x`mL1n$KUhT+QrpPFQAT3i%sW3%oa}0p{JvaB>rJ;^pbY zHB@R}yx9Wv3|x?A8kp9u)lwS65P&6O{q;h=SSxU=Fh({<%9YwC;fWt zD;&=rM8i7v9*(dx`I!0*Yu@0u1eyK+LO4*KJBDz;Oa+L$K(r)HU)em1+6*~EruKI{PTj#67l9eI*->5mvf!=p5{d& zkl`y9$=iD=Cs1UKXgS>ud5J&XE-6OFDX8FX83x0ohP7Id8WlW z7v)>Kx3Rlb`E_pFW>;cdk1#O+{QQo+s@iSYMq!@ThEMD+(N2i-*aKza91V2jj9M43`+LOv1r@{+6wNH9ud%89%n@< z6v8i(EzpnL-tH?2Wp`j=yLg13aJC0FjA47X=*wIwFybJD#9JfC+coV|h$?;(Nps}l zi=i4G!gu$KygW^Sgjo#@Ua3cA;C-T^-Il=2KzH`(JKJwA$e{)d^P%Pi*ToI3d}xO3 z0f(R%H@22S+L=|_o7>)mi-dA-Ws8&e{yYU}W?&Jt7BypUCx*0&xNGj68&g0LqRa2{ z_KMAWWtx_>QHAK9q3Q`KZku*yJ>2`?x`Nm**2EVQ>^id-Ey1V}$zh#ciC~cnEK}O_ z+Az9veWYh^_km-z%7veESHNavwTNapR`W-tA<$aXymIhNFm&Al*x%d025E2z>MO0m zFRnx`%KQNKlz_#dA+Es2@#x@V7c@W}@s#)KtNA?vWj_cPH8093qc(1DOUo?nKD(E_ zo-(ltt1des%QX%sR-40IKp_-x2_4)2fXdHIM#kzuNza=F{)|SJL#ZlibXAxcpX!4I1#zw8O}0_NK&)5rCiMS%ES*~js|o2_oO6Zgu! zSyb@w<%;7u3s8VS%m*O@oM%}3AmkQkZ9{>1)FE6iSm>sFkRgUYeq=ZCe zzpiNDpirq{qrMrvPiV<&QBt}4%A~Tj5%h8x&O?mPC5@SbkLWgBX6$J)Xx&94JO$zK z4i@rmtIE%9!MVY=wp$3QeG2c;Ys*?fqvYR04gPr(X3(xC(2?vAiu05V?4wv<{(vtK zo=W>t_>Mgwyk*)H2wrBoJ*K9{kA#Yyx_#g|f=flHuvh$%QWqCpm{&ZTD0@Lg(k(47RA1bA!TfFr@&5kAs*9G%mqUXEn)Z-ScOi9Y|n9O3|I^r7!^z1f(8mcnR)raek-raP@je)M5{OP~Z% zKGBGFb3c*SK}~r|&>&gq+@tQI{{fJ_b$~&m@qIL5YzcaglBy= z7*hB#QOIr_Yz|N`Gbe$hCW6&lqZl7%Nq1KCj#EQh$9{1QgxxKfO~K z5Em^xbZ8Y5LG9o2PoUO*7TcR|8_)o}R*NSEJO~~X9gCouXM$bOZTw5$OJqF9%}bp8 z0RNPuTGQD{aXu#}CnHbmcG}Oig$3#1>?yB%nUxcp={wdNQ+P?r{)Q$WE7|5k-a8ey zK}<46eYp>!I&f=#?th|dPaIyoHBJtWpUxl8lni*#HSrbZ4*#BeJHD;eomPnq@HJC& za&VwHWdwu4b|#n!6%YtSi2BEPeCK(v^d53BhukUTD$ue|`=vO0W_XCZ~o{w}!qoh|dwjxIT!JAZr4gFPV_bVrCfSSRXfR$+(cXbK8Jf zThL|XQ*tu%4lWIUAP~V?Nb7@9s0d{1g`Nxz4F%-n%}q__o|KMcgbbGp3z8UdITi*y z2Y1uz^^ty0$7<9{g-Iu+u(gcR6!OAgC20$vCH1}!XM7GYHye##S+>}Bv0Isy(`DH$ zV|_J9f3L5tmZ?lJ*59UX%hqBvwN!{ynpXLgKFL4Nb_+tuwXN0n$lt%PurOIf%6SMY zga?cmf+gu?f1tI=jB{<+AK-?gKo)j^P>4!Eskq?V`D+z}`qsLu} zc!HdlZ#ySN3|o1fXZOlqWBu;(g?&;y4T7?@xykMG5A&ytq?};9cor@u5UScSXDt(2 zSy}_bLcy7eRfEaQq z(D8R@y(92fN@lL9smVJ$A=vNEvl0{2&FnN_;KOK?4;5y8dbI!b8SiQ0@U3A&0s>zk zgiK5q^7wJ0mv zT-^wiU&y)v3bssCYJi2AsuVK&>u<(NADO4s)lrvPCnN{@0fGmwEh{n&gk%2Mg+Yt! z+Gp#F25Z+28N7KEFNPAdhs|ub$3ZE>=?KJjsN{~vh#c*M`GeJ?)#gjf%VP=@;^N|T z#X$&*lbt_)gickJmC>9kFST`c76i%&Dqiii!F1RQr-!QrT*R^DM&4l8Hg?V7J-8J- zzt%U96EBEw5Dn)l`zp&-=K^_x%GHHD(D}XLd5+tIC7SjIVM+iLPU*{HP5Ss{E;D$$ zQXGE&W|{6csA%wjA$-as`U{=$Gj$1$0U)rtsez4-a)U0R*uihyi?T$qgR5^BI30kw zF)LgX4=nF`ShNM7d;P6#i>Dd+`J{{_C(_!Uxo!$O_LpAa_1%93f>|3&D=W4=JMG`J zgs1xPG|PpQYwGF(puj6AXJ;mGDwNG=Ty*l6ljqYGizTd~j+Cih$yRPopbZH$54rVsT$D%8EtLcgmkN zc+hFqFA2}yxGhGAh(EDB;PYtsV^{?IC0_DQU@onZ&bVyGH?7PxOTY~@#UM*VoH9d2 zT7qTHbIKk^8ZQv!S@ZDXNbOSV&VCB;$L{-Z_u7&geQ#*}QWz1j{*^CG7o*}MJQ3k*3ZKjpv#AGhqB!X`+Td`YQ_V6Z_@RLOWMp#(2YSCToZ%gMouIvrxHZEQX zEN2_SaJB#Ox|qo(oqCdJu@4dmq|%Xj&e08!Wboug2@-{Rg#Hu#6bE@8Y-Q_@1G+mkNt$Q zR=}4(fCXB*U(%CSUVADT6h*h zmZIXz9HqP7*r^9-8sLuAL;~u7Sy%CaO?BUG<29=?ph1*eTo3C6>`ekDr3-6)PD!M1+*xNU(` zUh($j_iCI^$#wMuHaGFT|5tr5Txn=w_@lVTj9?Fy#6p5t#f(w%OnrIAPC($vWj5gdCpiF-@wUaC?M ze_V@+wj?SIoTNFg56MSheA$|vEwlI$`_J7$4Sr=*Aw|f&sTUhxq*`}ASYXl{rkM0b zA}B5|?k81Mcsg(sjEdceDwey!RcM-C9;0buPr;JfUPz#NI>fIcI>9tCafi-istbBI ziaM&11@FD4ttPppGz;{xOiHr$V`ka%jc7k))VOZ6v+Gio1bwUI`p;j9*YMZxX-DxY zQGVCwZDH`<-i$7*Tv=^)T&>ttlxARsN@HQ;K&5LWu_-nQoJ^;9DhIlr|BTtaA8whK z!73Hqn67=UXv$80bo0TU6c0=i4LdwVs8*(+XUY0U-mgQnu{ zy^lLs;aCtnW;om%($Udj6f^ap?n5}u59R4e!=AyJ+f8E^Vqq8QKa-#yR#A0mYD6WV z#CjLD*A);ds^6Mf=KxcxTLx;xXKjpvDS zW`0QMY+he(MqsAPJOUy?b#NOoHfXSXxoNX>^PVGT{U+wB20Z3I>FvG0g%hd$4(>CQ zD_!vG(^S4xgUIBrrZE;4c8`j@j7}|tsG8Q07J?_nmYgshkrQzH@Ws6$Ef>f#oA;9J z*?5P3hDK1Zz~NSqD@kqh>%`lsPo<9TpZw+1gq`N7D7EX&7G50mKqIc;PY&Fz+t~0* z(NyM$4wh$SeBxwSAo()~Gz*X3#f&mES9M71mN9p%>g$dk`(o`kTo<)`QZt%iT-(#q z-3sW1nH&Z$OPCs12@Vbho6lB=HndL9q{jgFaEy`Cyp-2PQWNFlgO|w0&U5H%)#KPy zyCfg{wm9~H=C05FuV2>8P`p!qDVkjLM33lu4G0c?-+&)VwBINh^9TomJ;vUo7L!D; zVav5moFM3nUXaps*lyQ!Gq*EkopKV|8GclmD;x}``D?g})OT(=E%4B%tc=_%lV{es zCeahm+#Ud*dqZHF>>=-KbGWjLAU8BpHB2?vejsu~G1sPobp71kzI>jM{$O$PlH108 z&|DLZwxCCxf{Ygp3}MNE+tnx&3k&NMYLZW2En0Icw-Y?*1^ **Hinweis zu Jahrestagen:** Die SOGo-Weboberfläche bietet kein Feld zum Anzeigen oder Bearbeiten von Jahrestagen. Das `ANNIVERSARY`-Feld muss über einen externen Client (z. B. Thunderbird, Android- oder iOS-Kontakte-App) gepflegt und per CardDAV synchronisiert werden. Das SOGo-CardDAV-Backend speichert und liefert das Feld korrekt – es fehlt lediglich die Unterstützung in der Web-UI. - Aktuell fest eingestellt: 1 Jahr in der Vergangenheit, 10 Jahre in der Zukunft. - Selbstverständlich pro Mailbox isoliert – ein Benutzer sieht nur die Geburtstage seiner eigenen Kontakte. -- 2.54.0 From ea598a979bc4eadb2e4ceacf6ab04c2bf7e5b092 Mon Sep 17 00:00:00 2001 From: "Patrick Asmus (scriptos)" Date: Sun, 29 Mar 2026 22:47:56 +0200 Subject: [PATCH 07/11] =?UTF-8?q?Umgebungsvariable=20EVENT=5FYEARS=20f?= =?UTF-8?q?=C3=BCr=20konfigurierbaren=20Event-Horizont=20hinzugef=C3=BCgt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/mcbdd/caldav.go | 6 +++--- cmd/mcbdd/main.go | 27 +++++++++++++++++++++++++++ docs/funktionsweise.md | 2 +- docs/schnellstart.md | 2 ++ 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/cmd/mcbdd/caldav.go b/cmd/mcbdd/caldav.go index 265b623..7d66090 100644 --- a/cmd/mcbdd/caldav.go +++ b/cmd/mcbdd/caldav.go @@ -133,7 +133,7 @@ func (d *Daemon) syncBirthdaysToCal(ctx context.Context, httpClient webdav.HTTPC return err } slog.DebugContext(ctx, "queried existing calendar events", "user", user, "count", len(events)) - bevs := generateBirthdayEvents(birthdays) + bevs := generateBirthdayEvents(birthdays, d.eventYears) slog.DebugContext(ctx, "generated birthday events", "user", user, "count", len(bevs)) bevsInSync := make([]int, 0) driftedEvents := make([]string, 0) @@ -185,11 +185,11 @@ type birthdayEvent struct { DateTimeEnd string } -func generateBirthdayEvents(birthdays []BirthdayContact) []birthdayEvent { +func generateBirthdayEvents(birthdays []BirthdayContact, eventYears int) []birthdayEvent { cyear := time.Now().Year() bb := make([]birthdayEvent, 0) for _, v := range birthdays { - for year := cyear; year <= 10+cyear; year++ { + for year := cyear; year <= eventYears+cyear; year++ { yearshift := year - v.Date.Year() prefix := "\U0001F382 " // 🎂 if v.Type == ContactTypeAnniversary { diff --git a/cmd/mcbdd/main.go b/cmd/mcbdd/main.go index 6e2fa49..5957f1a 100644 --- a/cmd/mcbdd/main.go +++ b/cmd/mcbdd/main.go @@ -8,6 +8,7 @@ import ( "net/http" "os" "os/signal" + "strconv" "strings" "sync" "syscall" @@ -39,6 +40,7 @@ type Daemon struct { oldCalendarName string notificationEnabled bool notificationTrigger string + eventYears int syncInterval time.Duration excludeMailboxes map[string]bool health *healthState @@ -120,6 +122,11 @@ func run() error { calendarName = "Birthdays" } notificationEnabled := strings.EqualFold(os.Getenv("NOTIFICATION_ENABLED"), "true") + eventYears, err := parseEventYears() + if err != nil { + return err + } + slog.Info("event horizon configured", "years", eventYears) syncInterval, err := parseSyncInterval() if err != nil { return err @@ -129,6 +136,7 @@ func run() error { "MAILCOW_BASE", mailcowBase, "CALENDAR_NAME", calendarName, "NOTIFICATION_ENABLED", notificationEnabled, + "EVENT_YEARS", eventYears, "MAILCOW_RESOLVE_HOST", os.Getenv("MAILCOW_RESOLVE_HOST"), "STATEFILE", os.Getenv("STATEFILE"), "MAILBOX_EXCLUDE", os.Getenv("MAILBOX_EXCLUDE"), @@ -166,6 +174,7 @@ func run() error { calendarName: calendarName, notificationEnabled: notificationEnabled, notificationTrigger: notificationTrigger, + eventYears: eventYears, syncInterval: syncInterval, excludeMailboxes: excludeMailboxes, } @@ -420,6 +429,24 @@ func parseSyncInterval() (time.Duration, error) { return d, nil } +// parseEventYears liest EVENT_YEARS aus der Umgebung und gibt die Anzahl +// der Jahre zurück, für die Geburtstags-Events im Voraus erzeugt werden. +// Standard: 10. Gültige Werte: 1–30. +func parseEventYears() (int, error) { + raw := os.Getenv("EVENT_YEARS") + if raw == "" { + return 10, nil + } + n, err := strconv.Atoi(raw) + if err != nil { + return 0, fmt.Errorf("invalid EVENT_YEARS %q: %w", raw, err) + } + if n < 1 || n > 30 { + return 0, fmt.Errorf("EVENT_YEARS must be between 1 and 30, got %d", n) + } + return n, nil +} + // buildTransport erstellt einen http.Transport. // 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 diff --git a/docs/funktionsweise.md b/docs/funktionsweise.md index 97ea059..51e9a5d 100644 --- a/docs/funktionsweise.md +++ b/docs/funktionsweise.md @@ -17,7 +17,7 @@ Der Mailcow Birthday Daemon synchronisiert automatisch Geburtstagskalender für - Die daraus resultierenden Kalendereinträge werden im Voraus berechnet. > **Hinweis zu Jahrestagen:** Die SOGo-Weboberfläche bietet kein Feld zum Anzeigen oder Bearbeiten von Jahrestagen. Das `ANNIVERSARY`-Feld muss über einen externen Client (z. B. Thunderbird, Android- oder iOS-Kontakte-App) gepflegt und per CardDAV synchronisiert werden. Das SOGo-CardDAV-Backend speichert und liefert das Feld korrekt – es fehlt lediglich die Unterstützung in der Web-UI. - - Aktuell fest eingestellt: 1 Jahr in der Vergangenheit, 10 Jahre in der Zukunft. + - Aktuell standardmäßig: 1 Jahr in der Vergangenheit, 10 Jahre in der Zukunft (konfigurierbar über `EVENT_YEARS`). - Selbstverständlich pro Mailbox isoliert – ein Benutzer sieht nur die Geburtstage seiner eigenen Kontakte. ## Mailbox-Filter diff --git a/docs/schnellstart.md b/docs/schnellstart.md index 9473bbb..f25c56a 100644 --- a/docs/schnellstart.md +++ b/docs/schnellstart.md @@ -28,6 +28,7 @@ services: # - NOTIFICATION_ENABLED=true # - NOTIFICATION_TIME=08:00 # - SYNC_INTERVAL=15m + # - EVENT_YEARS=10 # - MAILBOX_EXCLUDE=user1@example.com,user2@example.com # - LOG_LEVEL=info volumes: @@ -66,6 +67,7 @@ docker compose pull birthdaydaemon && docker compose up -d --no-deps birthdaydae | `NOTIFICATION_TIME` | Nein | `08:00` | Uhrzeit der Benachrichtigung im Format `HH:MM` (nur wirksam wenn `NOTIFICATION_ENABLED=true`) | | `STATEFILE` | Nein | `state.json` (im Container: `/data/state.json`) | Pfad zur Zustandsdatei, in der App-Passwörter und der aktuelle Kalendername gespeichert werden | | `SYNC_INTERVAL` | Nein | `15m` | Intervall zwischen den Synchronisationsläufen im Go-Duration-Format (z. B. `10m`, `30m`, `1h`). Mindestwert: `1m`. | +| `EVENT_YEARS` | Nein | `10` | Anzahl der Jahre, für die Geburtstags-Events im Voraus erzeugt werden. Gültige Werte: `1`–`30`. Kleinere Werte können die Performance in Kalender-Clients verbessern. | | `MAILBOX_EXCLUDE` | Nein | – | Kommagetrennte Liste von Mailadressen, die von der Synchronisation ausgeschlossen werden (z. B. `admin@example.com,noreply@example.com`). Standardmäßig erhalten alle aktiven Mailboxen einen Geburtstagskalender. | | `LOG_LEVEL` | Nein | `info` | Log-Verbosity: `debug`, `info`, `warn` oder `error`. Im `debug`-Modus werden einzelne Kalendereinträge (hinzugefügt/entfernt) und Kontaktdetails geloggt. | ## API-Key erstellen -- 2.54.0 From 93371e3afafd21b4df35e25c39e4ce1979a97b67 Mon Sep 17 00:00:00 2001 From: "Patrick Asmus (scriptos)" Date: Sun, 29 Mar 2026 23:06:09 +0200 Subject: [PATCH 08/11] feat: Kalenderfarbe (calendar-color) automatisch setzen --- cmd/mcbdd/caldav.go | 155 +++++++++++++++++++++++++++++++++++++++++ cmd/mcbdd/main.go | 11 +++ cmd/mcbdd/persist.go | 29 +++++--- docs/funktionsweise.md | 7 ++ docs/schnellstart.md | 2 + 5 files changed, 195 insertions(+), 9 deletions(-) diff --git a/cmd/mcbdd/caldav.go b/cmd/mcbdd/caldav.go index 7d66090..ecc1ede 100644 --- a/cmd/mcbdd/caldav.go +++ b/cmd/mcbdd/caldav.go @@ -2,8 +2,10 @@ package main import ( "context" + "encoding/xml" "fmt" "log/slog" + "net/http" "net/url" "slices" "strings" @@ -270,3 +272,156 @@ func (bev birthdayEvent) generateICAL(calendar string, notificationEnabled bool, cal.Children = append(cal.Children, event) return fmt.Sprintf("%s/%s.ics", calendar, id), cal } + +// XML-Strukturen für das Parsen der PROPFIND-Antwort (calendar-color). +type multistatusResponse struct { + XMLName xml.Name `xml:"DAV: multistatus"` + Responses []davResponse `xml:"DAV: response"` +} + +type davResponse struct { + Href string `xml:"DAV: href"` + PropStats []davPropStat `xml:"DAV: propstat"` +} + +type davPropStat struct { + Prop davProp `xml:"DAV: prop"` + Status string `xml:"DAV: status"` +} + +type davProp struct { + CalendarColor string `xml:"http://apple.com/ns/ical/ calendar-color"` +} + +// normalizeColor bringt eine Kalenderfarbe in ein einheitliches Format (#rrggbb, +// Kleinbuchstaben, ohne Alpha-Kanal), damit zuverlässig verglichen werden kann. +func normalizeColor(c string) string { + c = strings.TrimSpace(strings.ToLower(c)) + if len(c) == 9 && strings.HasPrefix(c, "#") { + c = c[:7] + } + return c +} + +// getCalendarColor liest die calendar-color-Property (Apple-Namespace) des +// Geburtstagskalenders per PROPFIND aus und gibt sie normalisiert zurück. +func (d *Daemon) getCalendarColor(ctx context.Context, httpClient webdav.HTTPClient, user string) (string, error) { + calendarURL, err := url.JoinPath(d.baseURL, "SOGo/dav", user, "Calendar", d.calendarName) + if err != nil { + return "", err + } + body := ` + + + + +` + req, err := http.NewRequestWithContext(ctx, "PROPFIND", calendarURL, strings.NewReader(body)) + if err != nil { + return "", err + } + req.Header.Set("Content-Type", "application/xml; charset=utf-8") + req.Header.Set("Depth", "0") + + resp, err := httpClient.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusMultiStatus { + return "", fmt.Errorf("PROPFIND calendar-color returned status %d", resp.StatusCode) + } + + var ms multistatusResponse + if err := xml.NewDecoder(resp.Body).Decode(&ms); err != nil { + return "", fmt.Errorf("error parsing PROPFIND response: %w", err) + } + + for _, r := range ms.Responses { + for _, ps := range r.PropStats { + if ps.Prop.CalendarColor != "" { + return normalizeColor(ps.Prop.CalendarColor), nil + } + } + } + return "", nil +} + +// setCalendarColor setzt die calendar-color-Property (Apple-Namespace) des +// Geburtstagskalenders per PROPPATCH. Die Farbe wird mit Alpha-Kanal (#RRGGBBFF) +// übertragen, da SOGo dieses Format erwartet. +func (d *Daemon) setCalendarColor(ctx context.Context, httpClient webdav.HTTPClient, user, color string) error { + calendarURL, err := url.JoinPath(d.baseURL, "SOGo/dav", user, "Calendar", d.calendarName) + if err != nil { + return err + } + colorValue := strings.ToUpper(color) + if len(colorValue) == 7 { + colorValue += "FF" + } + body := fmt.Sprintf(` + + + + %s + + +`, colorValue) + + req, err := http.NewRequestWithContext(ctx, "PROPPATCH", calendarURL, strings.NewReader(body)) + if err != nil { + return err + } + req.Header.Set("Content-Type", "application/xml; charset=utf-8") + + resp, err := httpClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusMultiStatus && resp.StatusCode != http.StatusOK { + return fmt.Errorf("PROPPATCH calendar-color returned status %d", resp.StatusCode) + } + return nil +} + +// ensureCalendarColor stellt sicher, dass der Geburtstagskalender die +// konfigurierte Farbe hat, sofern der Benutzer sie nicht manuell geändert hat. +// Die Erkennung manueller Änderungen basiert auf dem Vergleich zwischen der +// aktuellen Kalenderfarbe und der zuletzt vom Daemon gesetzten Farbe (aus dem +// State-File). Stimmen diese nicht überein, wurde die Farbe vom Benutzer +// angepasst und wird nicht überschrieben. +func (d *Daemon) ensureCalendarColor(ctx context.Context, httpClient webdav.HTTPClient, user string) error { + if d.calendarColor == "" { + return nil + } + + current, err := d.getCalendarColor(ctx, httpClient, user) + if err != nil { + return fmt.Errorf("error reading calendar color: %w", err) + } + + desired := normalizeColor(d.calendarColor) + stored := normalizeColor(d.storedCalendarColor) + + if current == desired { + slog.DebugContext(ctx, "calendar color already correct", "user", user, "color", desired) + return nil + } + + // Wenn der Benutzer die Farbe manuell geändert hat (current != stored && current != ""), + // wird die Farbe nicht überschrieben. + if current != "" && stored != "" && current != stored { + slog.DebugContext(ctx, "calendar color was customized by user, skipping", + "user", user, "current", current, "daemon", stored) + return nil + } + + if err := d.setCalendarColor(ctx, httpClient, user, desired); err != nil { + return fmt.Errorf("error setting calendar color: %w", err) + } + slog.InfoContext(ctx, "set calendar color", "user", user, "color", desired) + return nil +} diff --git a/cmd/mcbdd/main.go b/cmd/mcbdd/main.go index 5957f1a..0b6d4b5 100644 --- a/cmd/mcbdd/main.go +++ b/cmd/mcbdd/main.go @@ -38,6 +38,8 @@ type Daemon struct { stateUnsaved bool calendarName string oldCalendarName string + calendarColor string + storedCalendarColor string notificationEnabled bool notificationTrigger string eventYears int @@ -121,6 +123,10 @@ func run() error { if calendarName == "" { calendarName = "Birthdays" } + calendarColor := os.Getenv("CALENDAR_COLOR") + if calendarColor == "" { + calendarColor = "#D01818" + } notificationEnabled := strings.EqualFold(os.Getenv("NOTIFICATION_ENABLED"), "true") eventYears, err := parseEventYears() if err != nil { @@ -135,6 +141,7 @@ func run() error { slog.Debug("configuration summary", "MAILCOW_BASE", mailcowBase, "CALENDAR_NAME", calendarName, + "CALENDAR_COLOR", calendarColor, "NOTIFICATION_ENABLED", notificationEnabled, "EVENT_YEARS", eventYears, "MAILCOW_RESOLVE_HOST", os.Getenv("MAILCOW_RESOLVE_HOST"), @@ -172,6 +179,7 @@ func run() error { stateFilepath: os.Getenv("STATEFILE"), httpClient: &http.Client{Transport: buildTransport()}, calendarName: calendarName, + calendarColor: calendarColor, notificationEnabled: notificationEnabled, notificationTrigger: notificationTrigger, eventYears: eventYears, @@ -307,6 +315,9 @@ func (d *Daemon) processUser(ctx context.Context, m mailcow.Mailbox) error { if err := d.ensureBirthdayCal(ctx, davclient, m.Username); err != nil { return fmt.Errorf("error creating birthday calendar in caldav: %w", err) } + if err := d.ensureCalendarColor(ctx, davclient, m.Username); err != nil { + slog.WarnContext(ctx, "error setting calendar color", "user", m.Username, "err", err) + } if err := d.syncBirthdaysToCal(ctx, davclient, m.Username, bb); err != nil { return fmt.Errorf("error syncing birthday events to caldav: %w", err) } diff --git a/cmd/mcbdd/persist.go b/cmd/mcbdd/persist.go index 19bc1a1..3861c27 100644 --- a/cmd/mcbdd/persist.go +++ b/cmd/mcbdd/persist.go @@ -47,9 +47,10 @@ func (d *Daemon) loadState() error { case 2: slog.Debug("loading current state format", "stateVer", stateVer.Version) state := struct { - Version int `json:"version"` - UserTokens map[string]string `json:"userTokens"` - CalendarName string `json:"calendarName"` + Version int `json:"version"` + UserTokens map[string]string `json:"userTokens"` + CalendarName string `json:"calendarName"` + CalendarColor string `json:"calendarColor,omitempty"` }{} if err := d.loadFromDisk(&state); err != nil { return fmt.Errorf("cant load state v%d: %w", stateVer.Version, err) @@ -62,6 +63,7 @@ func (d *Daemon) loadState() error { d.userTokens[k] = string(dec) } storedCalendarName = state.CalendarName + d.storedCalendarColor = state.CalendarColor } slog.Info("state loaded", "users", len(d.userTokens)) if storedCalendarName != "" && storedCalendarName != d.calendarName { @@ -70,6 +72,13 @@ func (d *Daemon) loadState() error { d.oldCalendarName = storedCalendarName d.stateUnsaved = true } + if normalizeColor(d.storedCalendarColor) != normalizeColor(d.calendarColor) { + if d.storedCalendarColor != "" { + slog.Info("calendar color configuration changed", + "old", d.storedCalendarColor, "new", d.calendarColor) + } + d.stateUnsaved = true + } return nil } @@ -80,13 +89,15 @@ func (d *Daemon) saveState() error { encTokens[k] = base64.StdEncoding.EncodeToString([]byte(v)) } state := struct { - Version int `json:"version"` - UserTokens map[string]string `json:"userTokens"` - CalendarName string `json:"calendarName"` + Version int `json:"version"` + UserTokens map[string]string `json:"userTokens"` + CalendarName string `json:"calendarName"` + CalendarColor string `json:"calendarColor,omitempty"` }{ - Version: 2, - UserTokens: encTokens, - CalendarName: d.calendarName, + Version: 2, + UserTokens: encTokens, + CalendarName: d.calendarName, + CalendarColor: d.calendarColor, } if err := d.saveToDisk(state); err != nil { return err diff --git a/docs/funktionsweise.md b/docs/funktionsweise.md index 51e9a5d..7e06bb9 100644 --- a/docs/funktionsweise.md +++ b/docs/funktionsweise.md @@ -33,6 +33,13 @@ Die Variable kann jederzeit – auch bei bestehenden Installationen – per Upda > **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)). +## Kalenderfarbe + +- Der Daemon setzt automatisch die CalDAV-Property `calendar-color` (Apple-Namespace) auf dem Geburtstagskalender. Dadurch hebt sich der Kalender im Client farblich sofort ab. +- Die Farbe ist über `CALENDAR_COLOR` konfigurierbar (Standard: `#D01818`). Das Format ist `#RRGGBB`. +- Hat ein Benutzer die Farbe seines Kalenders manuell geändert (z. B. in SOGo oder einem anderen Client), wird diese Anpassung respektiert und nicht überschrieben. +- Bei einer Änderung von `CALENDAR_COLOR` werden alle Kalender aktualisiert, deren Farbe nicht manuell angepasst wurde. + ## Benachrichtigungen - Wenn `NOTIFICATION_ENABLED=true` gesetzt ist, erhält jedes Geburtstags-Event einen **VALARM** (iCal-Alarm). Kalender-Clients (SOGo, iOS, Android, Thunderbird) zeigen dann zur konfigurierten Uhrzeit eine Benachrichtigung an. Das Event bleibt weiterhin ein Ganztags-Event. diff --git a/docs/schnellstart.md b/docs/schnellstart.md index f25c56a..46839a8 100644 --- a/docs/schnellstart.md +++ b/docs/schnellstart.md @@ -24,6 +24,7 @@ services: - MAILCOW_BASE=https://mail.example.com - MAILCOW_APIKEY=DEIN-APIKEY-HIER - CALENDAR_NAME=Birthdays + # - CALENDAR_COLOR=#D01818 # - MAILCOW_RESOLVE_HOST=nginx-mailcow # - NOTIFICATION_ENABLED=true # - NOTIFICATION_TIME=08:00 @@ -63,6 +64,7 @@ docker compose pull birthdaydaemon && docker compose up -d --no-deps birthdaydae | `MAILCOW_APIKEY` | **Ja** | – | API-Key mit Lese-/Schreibzugriff aus dem Mailcow-Admin-Panel | | `MAILCOW_RESOLVE_HOST` | Nein | – | Interner Hostname für TCP-Verbindungen (z. B. `nginx-mailcow`). Löst Hairpin-NAT-Probleme in Docker-Netzen. TLS nutzt weiterhin den Hostnamen aus `MAILCOW_BASE`. | | `CALENDAR_NAME` | Nein | `Birthdays` | Name des Geburtstagskalenders, der in jeder Mailbox erstellt wird | +| `CALENDAR_COLOR` | Nein | `#D01818` | Farbe des Geburtstagskalenders im Hex-Format (z. B. `#FF6600`). Wird nur gesetzt, wenn der Benutzer die Farbe nicht manuell geändert hat. | | `NOTIFICATION_ENABLED` | Nein | `false` | Aktiviert Kalender-Benachrichtigungen (VALARM) für Geburtstags-Events (`true`/`false`) | | `NOTIFICATION_TIME` | Nein | `08:00` | Uhrzeit der Benachrichtigung im Format `HH:MM` (nur wirksam wenn `NOTIFICATION_ENABLED=true`) | | `STATEFILE` | Nein | `state.json` (im Container: `/data/state.json`) | Pfad zur Zustandsdatei, in der App-Passwörter und der aktuelle Kalendername gespeichert werden | -- 2.54.0 From 0311c830bd901084eae6d4e8ec1164e8aaf9dc4c Mon Sep 17 00:00:00 2001 From: "Patrick Asmus (scriptos)" Date: Sun, 29 Mar 2026 23:16:17 +0200 Subject: [PATCH 09/11] =?UTF-8?q?Startup-Connectivity-Check:=20Fixen=2015s?= =?UTF-8?q?-Delay=20durch=20aktive=20Erreichbarkeitspr=C3=BCfung=20von=20A?= =?UTF-8?q?PI=20und=20SOGo=20ersetzt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/mcbdd/main.go | 20 +++---- cmd/mcbdd/startup.go | 121 +++++++++++++++++++++++++++++++++++++++++ docs/funktionsweise.md | 6 +- docs/schnellstart.md | 2 +- 4 files changed, 135 insertions(+), 14 deletions(-) create mode 100644 cmd/mcbdd/startup.go diff --git a/cmd/mcbdd/main.go b/cmd/mcbdd/main.go index 0b6d4b5..c9584c9 100644 --- a/cmd/mcbdd/main.go +++ b/cmd/mcbdd/main.go @@ -99,18 +99,6 @@ func run() error { ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer cancel() - // Kurze Wartezeit beim Start, damit abhängige Dienste (z. B. nginx) - // vollständig hochgefahren sind, bevor Verbindungen aufgebaut werden. - const startupDelay = 15 * time.Second - slog.Info("waiting for dependent services to become ready", "delay", startupDelay) - select { - case <-time.After(startupDelay): - slog.Debug("startup delay completed, proceeding with initialization") - case <-ctx.Done(): - slog.Warn("shutdown signal received during startup, exiting") - return nil - } - mailcowBase := os.Getenv("MAILCOW_BASE") if mailcowBase == "" { return fmt.Errorf("MAILCOW_BASE environment variable is not set") @@ -149,6 +137,14 @@ func run() error { "MAILBOX_EXCLUDE", os.Getenv("MAILBOX_EXCLUDE"), ) + // Aktive Erreichbarkeitsprüfung: Mailcow-API und SOGo werden wiederholt + // geprüft, bevor der erste Sync startet. Damit entfällt der frühere + // fixe 15-Sekunden-Delay. Der Check nutzt exponentielles Backoff + // (2 s → 4 s → … → max 30 s) und bricht bei Shutdown-Signal sofort ab. + if err := waitForServices(ctx, &http.Client{Transport: buildTransport()}, mailcowBase, mailcowAPIKey); err != nil { + return err + } + excludeMailboxes := parseMailboxExclude() if len(excludeMailboxes) > 0 { slog.Info("mailbox exclusion configured", "count", len(excludeMailboxes)) diff --git a/cmd/mcbdd/startup.go b/cmd/mcbdd/startup.go new file mode 100644 index 0000000..4d3a2d8 --- /dev/null +++ b/cmd/mcbdd/startup.go @@ -0,0 +1,121 @@ +package main + +import ( + "context" + "log/slog" + "net/http" + "net/url" + "time" + + "git.techniverse.net/scriptos/mailcow-birthday-daemon/pkg/mailcow" +) + +const ( + // startupBackoffInit ist das anfängliche Warteintervall zwischen + // Erreichbarkeitsprüfungen (2 Sekunden). + startupBackoffInit = 2 * time.Second + + // startupBackoffMax begrenzt das maximale Warteintervall auf 30 Sekunden. + startupBackoffMax = 30 * time.Second + + // startupRequestTimeout begrenzt einzelne HTTP-Anfragen während der + // Startphase auf 10 Sekunden. + startupRequestTimeout = 10 * time.Second +) + +// waitForServices prüft in einer Schleife, ob die Mailcow-API und SOGo +// erreichbar sind. Erst wenn beide Dienste antworten, kehrt die Funktion +// zurück. Bei einem Shutdown-Signal (ctx.Done) wird sofort nil +// zurückgegeben. +func waitForServices(ctx context.Context, httpClient *http.Client, baseURL, apiKey string) error { + slog.Info("checking connectivity to mailcow API and SOGo") + + backoff := startupBackoffInit + + for { + apiOK := checkMailcowAPI(ctx, httpClient, baseURL, apiKey) + sogoOK := checkSOGo(ctx, httpClient, baseURL) + + if apiOK && sogoOK { + slog.Info("all services reachable, proceeding with initialization") + return nil + } + + // Klare Meldung, welcher Dienst noch nicht bereit ist. + if !apiOK && !sogoOK { + slog.Warn("mailcow API and SOGo not reachable yet, retrying", "backoff", backoff) + } else if !apiOK { + slog.Warn("mailcow API not reachable yet, retrying", "backoff", backoff) + } else { + slog.Warn("SOGo not reachable yet, retrying", "backoff", backoff) + } + + // Auf nächsten Versuch oder Shutdown-Signal warten. + timer := time.NewTimer(backoff) + select { + case <-timer.C: + case <-ctx.Done(): + timer.Stop() + slog.Warn("shutdown signal received during startup connectivity check, exiting") + return nil + } + + // Exponentielles Backoff verdoppeln, aber nicht über das Maximum. + backoff *= 2 + if backoff > startupBackoffMax { + backoff = startupBackoffMax + } + } +} + +// checkMailcowAPI führt einen einfachen API-Aufruf (GetMailboxes) durch, +// um zu prüfen, ob die Mailcow-API erreichbar ist und der API-Key gültig +// ist. Bei Erfolg wird true zurückgegeben. +func checkMailcowAPI(ctx context.Context, httpClient *http.Client, baseURL, apiKey string) bool { + reqCtx, cancel := context.WithTimeout(ctx, startupRequestTimeout) + defer cancel() + + mc := mailcow.New(httpClient, baseURL, apiKey) + _, err := mc.GetMailboxes(reqCtx) + if err != nil { + slog.Debug("mailcow API check failed", "err", err) + return false + } + slog.Debug("mailcow API is reachable") + return true +} + +// checkSOGo prüft die Erreichbarkeit von SOGo über einen HTTP-GET auf den +// SOGo-Basispfad. Jeder HTTP-Statuscode (auch Redirects oder 401) gilt als +// "erreichbar", da SOGo in diesem Fall antwortet. Nur Netzwerkfehler +// (connection refused, timeout) gelten als "nicht erreichbar". +func checkSOGo(ctx context.Context, httpClient *http.Client, baseURL string) bool { + reqCtx, cancel := context.WithTimeout(ctx, startupRequestTimeout) + defer cancel() + + sogoURL, err := url.JoinPath(baseURL, "SOGo/") + if err != nil { + slog.Debug("SOGo URL construction failed", "err", err) + return false + } + + req, err := http.NewRequestWithContext(reqCtx, http.MethodGet, sogoURL, nil) + if err != nil { + slog.Debug("SOGo request creation failed", "err", err) + return false + } + + resp, err := httpClient.Do(req) + if err != nil { + slog.Debug("SOGo check failed", "err", err) + return false + } + defer resp.Body.Close() + + // SOGo ist erreichbar – der konkrete Statuscode ist irrelevant, + // solange eine HTTP-Antwort zurückkommt. + slog.Debug("SOGo is reachable", "status", resp.StatusCode) + return true +} + + diff --git a/docs/funktionsweise.md b/docs/funktionsweise.md index 7e06bb9..a8dffaa 100644 --- a/docs/funktionsweise.md +++ b/docs/funktionsweise.md @@ -4,6 +4,10 @@ Der Mailcow Birthday Daemon synchronisiert automatisch Geburtstagskalender für jede aktive Mailbox. Der gesamte Prozess läuft ohne Benutzereingriff ab. +## Startup-Connectivity-Check + +Beim Start prüft der Daemon aktiv, ob die Mailcow-API und SOGo erreichbar sind, bevor die erste Synchronisation beginnt. Die Prüfung nutzt exponentielles Backoff (2 s → 4 s → … → max 30 s) und wiederholt sich, bis beide Dienste antworten. Im Log wird klar angegeben, welcher Dienst noch nicht bereit ist. Ein Shutdown-Signal bricht den Check sofort ab. + ## App-Passwörter - Über die Mailcow-API wird für jeden aktiven Benutzer ein App-Passwort mit Zugriff auf CardDAV und CalDAV erzeugt. @@ -58,7 +62,7 @@ Der Daemon reagiert auf `SIGTERM` und `SIGINT` (z. B. durch `docker stop`) und b 2. Ungespeicherte App-Passwörter werden in die Zustandsdatei geschrieben. 3. Erst danach beendet sich der Prozess. -Dadurch wird sichergestellt, dass das State-File konsistent bleibt und keine Daten verloren gehen. Auch die Startverzögerung (15 Sekunden) wird bei einem Shutdown-Signal sofort abgebrochen. +Dadurch wird sichergestellt, dass das State-File konsistent bleibt und keine Daten verloren gehen. Auch der Startup-Connectivity-Check wird bei einem Shutdown-Signal sofort abgebrochen. ## Healthcheck diff --git a/docs/schnellstart.md b/docs/schnellstart.md index 46839a8..146c685 100644 --- a/docs/schnellstart.md +++ b/docs/schnellstart.md @@ -54,7 +54,7 @@ cd /opt/mailcow-dockerized docker compose pull birthdaydaemon && docker compose up -d --no-deps birthdaydaemon ``` -> **Hinweis:** Nach dem Start wartet der Daemon zunächst **15 Sekunden**, bevor die erste Synchronisation beginnt. Diese Verzögerung stellt sicher, dass abhängige Dienste (z. B. Nginx, SOGo) vollständig bereit sind. +> **Hinweis:** Nach dem Start prüft der Daemon aktiv die Erreichbarkeit der Mailcow-API und SOGo, bevor die erste Synchronisation beginnt. Die Prüfung wiederholt sich mit steigendem Intervall (2 s → 4 s → … → max 30 s), bis beide Dienste antworten. Im Log wird klar angezeigt, welcher Dienst noch nicht bereit ist. ## Umgebungsvariablen -- 2.54.0 From 3ede89322f344df3a9775897c632321e0137ebe2 Mon Sep 17 00:00:00 2001 From: "Patrick Asmus (scriptos)" Date: Sun, 29 Mar 2026 23:22:10 +0200 Subject: [PATCH 10/11] =?UTF-8?q?fix:=20=C3=9Cberfl=C3=BCssige=20Leerzeile?= =?UTF-8?q?n=20am=20Ende=20von=20startup.go=20entfernt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/mcbdd/startup.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/cmd/mcbdd/startup.go b/cmd/mcbdd/startup.go index 4d3a2d8..8ea958f 100644 --- a/cmd/mcbdd/startup.go +++ b/cmd/mcbdd/startup.go @@ -117,5 +117,3 @@ func checkSOGo(ctx context.Context, httpClient *http.Client, baseURL string) boo slog.Debug("SOGo is reachable", "status", resp.StatusCode) return true } - - -- 2.54.0 From cb1b420a3fb094384bedf7f4ba5ca5f853c520f1 Mon Sep 17 00:00:00 2001 From: "Patrick Asmus (scriptos)" Date: Sun, 29 Mar 2026 23:48:54 +0200 Subject: [PATCH 11/11] fix(ci): Release-Workflow auf manuellen Trigger umgestellt, SCM-Release deaktiviert --- .gitea/workflows/release.yaml | 31 ++++++++++++++++++++++++++----- .goreleaser.yaml | 15 +-------------- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/.gitea/workflows/release.yaml b/.gitea/workflows/release.yaml index 121ca28..d1ad9a3 100644 --- a/.gitea/workflows/release.yaml +++ b/.gitea/workflows/release.yaml @@ -1,19 +1,42 @@ name: Make Release on: - push: - tags: - - 'v*' + workflow_dispatch: + inputs: + version: + description: 'Release-Version (z.B. 0.3.3 – ohne v-Prefix)' + required: true + type: string jobs: release: runs-on: ubuntu-latest steps: + - name: Validate version format + run: | + VERSION="${{ github.event.inputs.version }}" + if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then + echo "::error::Ungültiges Versionsformat: '$VERSION'. Erwartet: X.Y.Z (z.B. 0.3.3)" + exit 1 + fi + echo "VERSION=v${VERSION}" >> $GITHUB_ENV + - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 0 + - name: Create and push tag + run: | + git config user.name "Gitea Actions" + git config user.email "actions@git.techniverse.net" + if git rev-parse "$VERSION" >/dev/null 2>&1; then + echo "::error::Tag $VERSION existiert bereits!" + exit 1 + fi + git tag -a "$VERSION" -m "Release $VERSION" + git push origin "$VERSION" + - name: Set up Go uses: actions/setup-go@v5 with: @@ -32,6 +55,4 @@ jobs: args: release --clean env: GITEA_TOKEN: ${{ secrets.REGISTRY_TOKEN }} - REGISTRY_URL: ${{ secrets.REGISTRY_URL }} - REGISTRY_USER: ${{ secrets.REGISTRY_USER }} GORELEASER_FORCE_TOKEN: gitea diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 8897d6f..fa16831 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -26,20 +26,7 @@ checksum: snapshot: name_template: "{{ incpatch .Version }}-next" release: - gitea: - owner: "{{ .Env.REGISTRY_USER }}" - name: mailcow-birthday-daemon - prerelease: auto -gitea_urls: - api: "{{ .Env.REGISTRY_URL }}/api/v1/" - download: "{{ .Env.REGISTRY_URL }}" - skip_tls_verify: false -changelog: - sort: asc - filters: - exclude: - - "^docs:" - - "^test:" + disable: true upx: - enabled: true goos: [linux] -- 2.54.0