From 03e3c11e9002cdcb7fa7b1459077ab36c7716050 Mon Sep 17 00:00:00 2001 From: Olaf Date: Sun, 5 Apr 2026 10:17:56 +0000 Subject: [PATCH] feat: crash recovery, service_failed monitoring, broker health fields, command expiry sweep - Add GET /api/clients/crashed endpoint (process_status=crashed or stale heartbeat) - Add restart_app command action with same lifecycle + lockout as reboot_host - Scheduler: crash auto-recovery loop (CRASH_RECOVERY_ENABLED flag, lockout, MQTT publish) - Scheduler: unconditional command expiry sweep per poll cycle (sweep_expired_commands) - Listener: subscribe to infoscreen/+/service_failed; persist service_failed_at + unit - Listener: extract broker_connection block from health payload; persist reconnect_count + last_disconnect_at - DB migration b1c2d3e4f5a6: service_failed_at, service_failed_unit, mqtt_reconnect_count, mqtt_last_disconnect_at on clients - Add GET /api/clients/service_failed and POST /api/clients//clear_service_failed - Monitoring overview API: include mqtt_reconnect_count + mqtt_last_disconnect_at per client - Frontend: orange service-failed alert panel (hidden when empty, auto-refresh, quittieren action) - Frontend: MQTT reconnect count + last disconnect in client detail panel - MQTT auth hardening: listener/scheduler/server use env credentials; broker enforces allow_anonymous false - Client command lifecycle foundation: ClientCommand model, reboot_host/shutdown_host, full ACK lifecycle - Docs: TECH-CHANGELOG, DEV-CHANGELOG, MQTT_EVENT_PAYLOAD_GUIDE, copilot-instructions updated - Add implementation-plans/, RESTART_VALIDATION_CHECKLIST.md, TODO.md --- .env.example | 20 +- .github/copilot-instructions.md | 12 +- DEV-CHANGELOG.md | 25 ++ MQTT_EVENT_PAYLOAD_GUIDE.md | 85 +++++ README.md | 9 + RESTART_VALIDATION_CHECKLIST.md | 149 ++++++++ TECH-CHANGELOG.md | 36 ++ TODO.md | 55 +++ dashboard/index.html | 4 +- dashboard/public/favicon.png | Bin 0 -> 230542 bytes dashboard/public/program-info.json | 4 - dashboard/src/apiClientMonitoring.ts | 2 + dashboard/src/apiClients.ts | 112 +++++- dashboard/src/monitoring.css | 45 +++ dashboard/src/monitoring.tsx | 244 +++++++++++- dashboard/src/programminfo.tsx | 40 +- dashboard/src/vite-env.d.ts | 4 + dashboard/vite.config.ts | 5 + docker-compose.prod.yml | 44 ++- docker-compose.yml | 31 +- .../reboot-command-payload-schemas.json | 149 ++++++++ .../reboot-command-payload-schemas.md | 59 +++ ...boot-implementation-handoff-client-team.md | 146 ++++++++ .../reboot-implementation-handoff-share.md | 214 +++++++++++ .../reboot-kickoff-summary.md | 54 +++ implementation-plans/server-team-actions.md | 127 +++++++ listener/listener.py | 165 ++++++++- models/models.py | 35 ++ scheduler/db_utils.py | 169 ++++++++- scheduler/scheduler.py | 90 ++++- .../aa12bb34cc56_add_client_commands_table.py | 63 ++++ ...d_service_failed_and_mqtt_broker_fields.py | 43 +++ server/init_defaults.py | 2 +- server/routes/client_logs.py | 2 + server/routes/clients.py | 347 ++++++++++++++++-- 35 files changed, 2511 insertions(+), 80 deletions(-) create mode 100644 RESTART_VALIDATION_CHECKLIST.md create mode 100644 TODO.md create mode 100644 dashboard/public/favicon.png create mode 100644 implementation-plans/reboot-command-payload-schemas.json create mode 100644 implementation-plans/reboot-command-payload-schemas.md create mode 100644 implementation-plans/reboot-implementation-handoff-client-team.md create mode 100644 implementation-plans/reboot-implementation-handoff-share.md create mode 100644 implementation-plans/reboot-kickoff-summary.md create mode 100644 implementation-plans/server-team-actions.md create mode 100644 server/alembic/versions/aa12bb34cc56_add_client_commands_table.py create mode 100644 server/alembic/versions/b1c2d3e4f5a6_add_service_failed_and_mqtt_broker_fields.py diff --git a/.env.example b/.env.example index 5aee511..a605145 100644 --- a/.env.example +++ b/.env.example @@ -20,8 +20,18 @@ DB_HOST=db # MQTT MQTT_BROKER_HOST=mqtt MQTT_BROKER_PORT=1883 -# MQTT_USER=your_mqtt_user -# MQTT_PASSWORD=your_mqtt_password +# Required for authenticated broker access +MQTT_USER=your_mqtt_user +MQTT_PASSWORD=replace_with_a_32plus_char_random_password +# Optional: dedicated canary client account +MQTT_CANARY_USER=your_canary_mqtt_user +MQTT_CANARY_PASSWORD=replace_with_a_different_32plus_char_random_password +# Optional TLS settings +MQTT_TLS_ENABLED=false +MQTT_TLS_CA_CERT= +MQTT_TLS_CERTFILE= +MQTT_TLS_KEYFILE= +MQTT_TLS_INSECURE=false MQTT_KEEPALIVE=60 # Dashboard @@ -39,6 +49,12 @@ HEARTBEAT_GRACE_PERIOD_PROD=170 # Optional: force periodic republish even without changes # REFRESH_SECONDS=0 +# Crash recovery (scheduler auto-recovery) +# CRASH_RECOVERY_ENABLED=false +# CRASH_RECOVERY_GRACE_SECONDS=180 +# CRASH_RECOVERY_LOCKOUT_MINUTES=15 +# CRASH_RECOVERY_COMMAND_EXPIRY_SECONDS=240 + # Default superadmin bootstrap (server/init_defaults.py) # REQUIRED: Must be set for superadmin creation DEFAULT_SUPERADMIN_USERNAME=superadmin diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 2e85676..4c31ba0 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -13,15 +13,16 @@ It is not a changelog and not a full architecture handbook. - Keep changes minimal, match existing patterns, and update docs in the same commit when behavior changes. ## Fast file map -- `scheduler/scheduler.py` - scheduler loop, MQTT event publishing, TV power intent publishing -- `scheduler/db_utils.py` - event formatting and power-intent helper logic -- `listener/listener.py` - discovery/heartbeat/log/screenshot MQTT consumption +- `scheduler/scheduler.py` - scheduler loop, MQTT event publishing, TV power intent publishing, crash auto-recovery, command expiry sweep +- `scheduler/db_utils.py` - event formatting, power-intent helpers, crash recovery helpers, command expiry sweep +- `listener/listener.py` - discovery/heartbeat/log/screenshot/service_failed MQTT consumption - `server/init_academic_periods.py` - idempotent academic-period seeding + auto-activation for current date - `server/initialize_database.py` - migration + bootstrap orchestration for local/manual setup - `server/routes/events.py` - event CRUD, recurrence handling, UTC normalization - `server/routes/eventmedia.py` - file manager, media upload/stream endpoints - `server/routes/groups.py` - group lifecycle, alive status, order persistence - `server/routes/system_settings.py` - system settings CRUD and supplement-table endpoint +- `server/routes/clients.py` - client metadata, restart/shutdown/restart_app command issuing, command status, crashed/service_failed alert endpoints - `dashboard/src/settings.tsx` - settings UX and system-defaults integration - `dashboard/src/components/CustomEventModal.tsx` - event creation/editing UX - `dashboard/src/monitoring.tsx` - superadmin monitoring page @@ -54,6 +55,9 @@ It is not a changelog and not a full architecture handbook. - Logs topic family: `infoscreen/{uuid}/logs/{error|warn|info}` - Health topic: `infoscreen/{uuid}/health` - Dashboard screenshot topic: `infoscreen/{uuid}/dashboard` +- Client command topic (QoS1, non-retained): `infoscreen/{uuid}/commands` (compat alias: `infoscreen/{uuid}/command`) +- Client command ack topic (QoS1, non-retained): `infoscreen/{uuid}/commands/ack` (compat alias: `infoscreen/{uuid}/command/ack`) +- Service-failed topic (retained, client→server): `infoscreen/{uuid}/service_failed` - TV power intent Phase 1 topic (retained, QoS1): `infoscreen/groups/{group_id}/power/intent` TV power intent Phase 1 rules: @@ -82,7 +86,9 @@ TV power intent Phase 1 rules: - Scheduler: `POLL_INTERVAL_SECONDS`, `REFRESH_SECONDS` - Power intent: `POWER_INTENT_PUBLISH_ENABLED`, `POWER_INTENT_HEARTBEAT_ENABLED`, `POWER_INTENT_EXPIRY_MULTIPLIER`, `POWER_INTENT_MIN_EXPIRY_SECONDS` - Monitoring: `PRIORITY_SCREENSHOT_TTL_SECONDS` +- Crash recovery: `CRASH_RECOVERY_ENABLED`, `CRASH_RECOVERY_GRACE_SECONDS`, `CRASH_RECOVERY_LOCKOUT_MINUTES`, `CRASH_RECOVERY_COMMAND_EXPIRY_SECONDS` - Core: `DB_CONN`, `DB_USER`, `DB_PASSWORD`, `DB_HOST`, `DB_NAME`, `ENV` +- MQTT auth/connectivity: `MQTT_BROKER_HOST`, `MQTT_BROKER_PORT`, `MQTT_USER`, `MQTT_PASSWORD` (listener/scheduler/server should use authenticated broker access) ## Edit guardrails - Do not edit generated assets in `dashboard/dist/`. diff --git a/DEV-CHANGELOG.md b/DEV-CHANGELOG.md index 9580c37..245806e 100644 --- a/DEV-CHANGELOG.md +++ b/DEV-CHANGELOG.md @@ -5,6 +5,31 @@ This changelog tracks all changes made in the development workspace, including i --- ## Unreleased (development workspace) +- Crash detection API: Added `GET /api/clients/crashed` returning clients with `process_status=crashed` or stale heartbeat; includes `crash_reason` field (`process_crashed` | `heartbeat_stale`). +- Crash auto-recovery (scheduler): Feature-flagged loop (`CRASH_RECOVERY_ENABLED`) scans crash candidates, issues `reboot_host` command, publishes to primary + compat MQTT topics; lockout window and expiry configurable via env. +- Command expiry sweep (scheduler): Unconditional per-cycle sweep in `sweep_expired_commands()` marks non-terminal `ClientCommand` rows past `expires_at` as `expired`. +- `restart_app` action registered in `server/routes/clients.py` API action map; sends same command lifecycle as `reboot_host`; safety lockout covers both actions. +- `service_failed` listener: subscribes to `infoscreen/+/service_failed` on every connect; persists `service_failed_at` + `service_failed_unit` to `Client`; empty payload (retain clear) silently ignored. +- Broker connection health: Listener health handler now extracts `broker_connection.reconnect_count` + `broker_connection.last_disconnect_at` and persists to `Client`. +- DB migration `b1c2d3e4f5a6`: adds `service_failed_at`, `service_failed_unit`, `mqtt_reconnect_count`, `mqtt_last_disconnect_at` to `clients` table. +- Model update: `models/models.py` Client class updated with all four new columns. +- `GET /api/clients/service_failed`: lists clients with `service_failed_at` set, admin-or-higher gated. +- `POST /api/clients//clear_service_failed`: clears DB flag and publishes empty retained MQTT to `infoscreen/{uuid}/service_failed`. +- Monitoring overview includes `mqtt_reconnect_count` + `mqtt_last_disconnect_at` per client. +- Frontend monitoring: orange service-failed alert panel (hidden when count=0), auto-refresh 15s, per-row Quittieren action. +- Frontend monitoring: client detail now shows MQTT reconnect count + last disconnect timestamp. +- Frontend types: `ServiceFailedClient`, `ServiceFailedClientsResponse`; helpers `fetchServiceFailedClients()`, `clearServiceFailed()` added to `dashboard/src/apiClients.ts`. +- `MQTT_EVENT_PAYLOAD_GUIDE.md`: added `service_failed` topic contract. +- MQTT auth hardening: Listener and scheduler now connect to broker with env-configured credentials (`MQTT_BROKER_HOST`, `MQTT_BROKER_PORT`, `MQTT_USER`, `MQTT_PASSWORD`) instead of anonymous fixed host/port defaults; optional TLS env toggles added in code path (`MQTT_TLS_*`). +- Broker auth enforcement: `mosquitto/config/mosquitto.conf` now disables anonymous access and enables password-file authentication. `docker-compose.yml` MQTT service now bootstraps/update password entries from env (`MQTT_USER`/`MQTT_PASSWORD`, optional canary user) before starting broker. +- Compose wiring: Added MQTT credential env propagation for listener/scheduler in both base and dev override compose files and switched MQTT healthcheck publish to authenticated mode. +- Backend implementation: Introduced client command lifecycle foundation for remote control in `server/routes/clients.py` with command persistence (`ClientCommand`), schema-based MQTT publish to `infoscreen/{uuid}/commands` (QoS1, non-retained), new endpoints `POST /api/clients//shutdown` and `GET /api/clients/commands/`, and restart safety lockout (`blocked_safety` after 3 restarts in 15 minutes). Added migration `server/alembic/versions/aa12bb34cc56_add_client_commands_table.py` and model updates in `models/models.py`. Restart path keeps transitional legacy MQTT publish to `clients/{uuid}/restart` for compatibility. +- Listener integration: `listener/listener.py` now subscribes to `infoscreen/+/commands/ack` and updates command lifecycle states from client ACK payloads (`accepted`, `execution_started`, `completed`, `failed`). +- Frontend API client prep: Extended `dashboard/src/apiClients.ts` with `ClientCommand` typing and helper calls for lifecycle consumption (`shutdownClient`, `fetchClientCommandStatus`), and updated `restartClient` to accept optional reason payload. +- Contract freeze clarification: implementation-plan docs now explicitly freeze canonical MQTT topics (`infoscreen/{uuid}/commands`, `infoscreen/{uuid}/commands/ack`) and JSON schemas with examples; added transitional singular-topic compatibility aliases (`infoscreen/{uuid}/command`, `infoscreen/{uuid}/command/ack`) in server publish and listener ingest. +- Action value canonicalization: command payload actions are now frozen as host-level values (`reboot_host`, `shutdown_host`). API endpoint mapping is explicit (`/restart` -> `reboot_host`, `/shutdown` -> `shutdown_host`), and docs/examples were updated to remove `restart` payload ambiguity. +- Client helper snippets: Added frozen payload validation artifacts `implementation-plans/reboot-command-payload-schemas.md` and `implementation-plans/reboot-command-payload-schemas.json` (copy-ready snippets plus machine-validated JSON Schema). +- Documentation alignment: Added active reboot implementation handoff docs under `implementation-plans/` and linked them in `README.md` for immediate cross-team access (`reboot-implementation-handoff-share.md`, `reboot-implementation-handoff-client-team.md`, `reboot-kickoff-summary.md`). - Programminfo GUI regression/fix: `dashboard/public/program-info.json` could not be loaded in Programminfo menu due to invalid JSON in the new alpha.16 changelog line (malformed quote in a text entry). Fixed JSON entry and verified file parses correctly again. - Dashboard holiday banner fix: `dashboard/src/dashboard.tsx` — `loadHolidayStatus` now uses a stable `useCallback` with empty deps, preventing repeated re-creation on render. `useEffect` depends only on the stable callback reference. - Dashboard Syncfusion stale-render fix: `MessageComponent` in the holiday banner now receives `key={`${severity}:${text}`}` to force remount when severity or text changes; without this Syncfusion cached stale DOM and the banner did not update reactively. diff --git a/MQTT_EVENT_PAYLOAD_GUIDE.md b/MQTT_EVENT_PAYLOAD_GUIDE.md index d316a54..5424ea0 100644 --- a/MQTT_EVENT_PAYLOAD_GUIDE.md +++ b/MQTT_EVENT_PAYLOAD_GUIDE.md @@ -50,6 +50,91 @@ Contract notes: - Heartbeat republishes keep `intent_id` stable while refreshing `issued_at` and `expires_at`. - Expiry is poll-based: `max(3 x poll_interval_sec, 90)`. +### Service-Failed Notification (client → server, retained) +- **Topic**: `infoscreen/{uuid}/service_failed` +- **QoS**: 1 +- **Retained**: Yes +- **Direction**: client → server +- **Purpose**: Client signals that systemd has exhausted restart attempts (`StartLimitBurst` exceeded) — manual intervention is required. + +Example payload: + +```json +{ + "event": "service_failed", + "unit": "infoscreen-simclient.service", + "client_uuid": "9b8d1856-ff34-4864-a726-12de072d0f77", + "failed_at": "2026-04-05T08:00:00Z" +} +``` + +Contract notes: +- Message is retained so the server receives it even after a broker restart. +- Server persists `service_failed_at` and `service_failed_unit` to the `clients` table. +- To clear after resolution: `POST /api/clients//clear_service_failed` — clears the DB flag and publishes an empty retained payload to delete the retained message from the broker. +- Empty payload (empty bytes) on this topic = retain-clear in transit; listener ignores it. + +### Client Command Intent (Phase 1) +- **Topic**: `infoscreen/{uuid}/commands` +- **QoS**: 1 +- **Retained**: No +- **Format**: JSON object +- **Purpose**: Per-client control commands (currently `restart` and `shutdown`) + +Compatibility note: +- During restart transition, server also publishes legacy restart command to `clients/{uuid}/restart` with payload `{ "action": "restart" }`. +- During topic naming transition, server also publishes command payload to `infoscreen/{uuid}/command`. + +Example payload: + +```json +{ + "schema_version": "1.0", + "command_id": "5d1f8b4b-7e85-44fb-8f38-3f5d5da5e2e4", + "client_uuid": "9b8d1856-ff34-4864-a726-12de072d0f77", + "action": "reboot_host", + "issued_at": "2026-04-03T12:48:10Z", + "expires_at": "2026-04-03T12:52:10Z", + "requested_by": 1, + "reason": "operator_request" +} +``` + +Contract notes: +- Clients must reject stale commands where local UTC time is greater than `expires_at`. +- Clients must deduplicate by `command_id` and never execute a duplicate command twice. +- `schema_version` is required for forward-compatibility. +- Allowed command action values in v1: `reboot_host`, `shutdown_host`, `restart_app`. +- `restart_app` = soft app restart (no OS reboot); `reboot_host` = full OS reboot. +- API mapping for operators: restart endpoint emits `reboot_host`; shutdown endpoint emits `shutdown_host`. + +### Client Command Acknowledgements (Phase 1) +- **Topic**: `infoscreen/{uuid}/commands/ack` +- **QoS**: 1 (recommended) +- **Retained**: No +- **Format**: JSON object +- **Purpose**: Client reports command lifecycle progression back to server + +Compatibility note: +- During topic naming transition, listener also accepts acknowledgements from `infoscreen/{uuid}/command/ack`. + +Example payload: + +```json +{ + "command_id": "5d1f8b4b-7e85-44fb-8f38-3f5d5da5e2e4", + "status": "execution_started", + "error_code": null, + "error_message": null +} +``` + +Allowed `status` values: +- `accepted` +- `execution_started` +- `completed` +- `failed` + ## Message Structure ### General Principles diff --git a/README.md b/README.md index 4dcbf48..b2e4041 100644 --- a/README.md +++ b/README.md @@ -192,9 +192,18 @@ Rollout strategy (Phase 1): - [AI-INSTRUCTIONS-MAINTENANCE.md](AI-INSTRUCTIONS-MAINTENANCE.md) - [DEV-CHANGELOG.md](DEV-CHANGELOG.md) +### Active Implementation Plans +- [implementation-plans/reboot-implementation-handoff-share.md](implementation-plans/reboot-implementation-handoff-share.md) +- [implementation-plans/reboot-implementation-handoff-client-team.md](implementation-plans/reboot-implementation-handoff-client-team.md) +- [implementation-plans/reboot-kickoff-summary.md](implementation-plans/reboot-kickoff-summary.md) + ## API Highlights - Core resources: clients, groups, events, academic periods +- Client command lifecycle: + - `POST /api/clients//restart` + - `POST /api/clients//shutdown` + - `GET /api/clients/commands/` - Holidays: `GET/POST /api/holidays`, `POST /api/holidays/upload`, `PUT/DELETE /api/holidays/` - Media: upload/download/stream + conversion status - Auth: login/logout/change-password diff --git a/RESTART_VALIDATION_CHECKLIST.md b/RESTART_VALIDATION_CHECKLIST.md new file mode 100644 index 0000000..9b3afd4 --- /dev/null +++ b/RESTART_VALIDATION_CHECKLIST.md @@ -0,0 +1,149 @@ +# Restart Validation Checklist + +Purpose: Validate end-to-end restart command flow after MQTT auth hardening. + +## Scope + +- API command issue route: `POST /api/clients/{uuid}/restart` +- MQTT command topic: `infoscreen/{uuid}/commands` (compat: `infoscreen/{uuid}/command`) +- MQTT ACK topic: `infoscreen/{uuid}/commands/ack` (compat: `infoscreen/{uuid}/command/ack`) +- Status API: `GET /api/clients/commands/{command_id}` + +## Preconditions + +- Stack is up and healthy (`db`, `mqtt`, `server`, `listener`, `scheduler`). +- You have an `admin` or `superadmin` account. +- At least one canary client is online and can process restart commands. +- `.env` has valid `MQTT_USER` / `MQTT_PASSWORD`. + +## 1) Open Monitoring Session (MQTT) + +On host/server: + +```bash +set -a +. ./.env +set +a + +mosquitto_sub -h 127.0.0.1 -p 1883 \ + -u "$MQTT_USER" -P "$MQTT_PASSWORD" \ + -t "infoscreen/+/commands" \ + -t "infoscreen/+/commands/ack" \ + -t "infoscreen/+/command" \ + -t "infoscreen/+/command/ack" \ + -v +``` + +Expected: +- Command publish appears on `infoscreen/{uuid}/commands`. +- ACK(s) appear on `infoscreen/{uuid}/commands/ack`. + +## 2) Login and Keep Session Cookie + +```bash +API_BASE="http://127.0.0.1:8000" +USER="" +PASS="" + +curl -sS -X POST "$API_BASE/api/auth/login" \ + -H "Content-Type: application/json" \ + -d "{\"username\":\"$USER\",\"password\":\"$PASS\"}" \ + -c /tmp/infoscreen-cookies.txt +``` + +Expected: +- Login success response. +- Cookie jar file created at `/tmp/infoscreen-cookies.txt`. + +## 3) Pick Target Client UUID + +Option A: Use known canary UUID. + +Option B: query alive clients: + +```bash +curl -sS "$API_BASE/api/clients/with_alive_status" -b /tmp/infoscreen-cookies.txt +``` + +Choose one `uuid` where `is_alive` is `true`. + +## 4) Issue Restart Command + +```bash +CLIENT_UUID="" + +curl -sS -X POST "$API_BASE/api/clients/$CLIENT_UUID/restart" \ + -H "Content-Type: application/json" \ + -b /tmp/infoscreen-cookies.txt \ + -d '{"reason":"canary_restart_validation"}' +``` + +Expected: +- HTTP `202` on success. +- JSON includes `command.commandId` and initial status around `published`. +- In MQTT monitor, a command payload with: + - `schema_version: "1.0"` + - `action: "reboot_host"` + - matching `command_id`. + +## 5) Poll Command Lifecycle Until Terminal + +```bash +COMMAND_ID="" + +for i in $(seq 1 20); do + curl -sS "$API_BASE/api/clients/commands/$COMMAND_ID" -b /tmp/infoscreen-cookies.txt + echo + sleep 3 +done +``` + +Expected status progression (typical): +- `queued` -> `publish_in_progress` -> `published` -> `ack_received` -> `execution_started` -> `completed` + +Failure/alternate terminal states: +- `failed` (check `errorCode` / `errorMessage`) +- `blocked_safety` (reboot lockout triggered) + +## 6) Validate Offline/Timeout Behavior + +- Repeat step 4 for an offline client (or stop client process first). +- Confirm command does not falsely end as `completed`. +- Confirm status remains non-success and has usable failure diagnostics. + +## 7) Validate Safety Lockout + +Current lockout in API route: +- Threshold: 3 reboot commands +- Window: 15 minutes + +Test: +- Send 4 restart commands quickly for same `uuid`. + +Expected: +- One request returns HTTP `429`. +- Command entry state `blocked_safety` with lockout error details. + +## 8) Service Log Spot Check + +```bash +docker compose logs --tail=150 server listener mqtt +``` + +Expected: +- No MQTT auth errors (`Not authorized`, `Connection Refused: not authorised`). +- Listener logs show ACK processing for `command_id`. + +## 9) Acceptance Criteria + +- Restart command publish is visible on MQTT. +- ACK is received and mapped by listener. +- Status endpoint reaches correct terminal state. +- Safety lockout works under repeated restart attempts. +- No auth regression in broker/service logs. + +## Cleanup + +```bash +rm -f /tmp/infoscreen-cookies.txt +``` diff --git a/TECH-CHANGELOG.md b/TECH-CHANGELOG.md index 0c80098..a37bb5f 100644 --- a/TECH-CHANGELOG.md +++ b/TECH-CHANGELOG.md @@ -5,6 +5,42 @@ This changelog documents technical and developer-relevant changes included in public releases. For development workspace changes, see DEV-CHANGELOG.md. Not all changes here are reflected in the user-facing changelog (`program-info.json`), and not all UI/feature changes are repeated here. Some changes (e.g., backend refactoring, API adjustments, infrastructure, developer tooling, or internal logic) may only appear in TECH-CHANGELOG.md. For UI/feature changes, see `dashboard/public/program-info.json`. +## Unreleased +- � **Crash detection, auto-recovery, and service_failed monitoring (2026-04-05)**: + - Added `GET /api/clients/crashed` endpoint: returns active clients with `process_status=crashed` or stale heartbeat beyond grace period, with `crash_reason` field. + - Added `restart_app` command action alongside existing `reboot_host`/`shutdown_host`; registered in `server/routes/clients.py` with same safety lockout. + - Scheduler: Added crash auto-recovery loop (feature-flagged via `CRASH_RECOVERY_ENABLED`): scans candidates via `get_crash_recovery_candidates()`, issues `reboot_host` command per client, publishes to primary + compat MQTT topics, updates command lifecycle. + - Scheduler: Added unconditional command expiry sweep each poll cycle via `sweep_expired_commands()` in `scheduler/db_utils.py`: marks non-terminal `ClientCommand` rows with `expires_at < now` as `expired`. + - Added `service_failed` topic ingestion in `listener/listener.py`: subscribe to `infoscreen/+/service_failed` on every connect; persist `service_failed_at` and `service_failed_unit` on Client; empty payload (retain clear) ignored. + - Added `broker_connection` block extraction in health payload handler: persists `mqtt_reconnect_count` and `mqtt_last_disconnect_at` from `infoscreen/{uuid}/health`. + - Added four new DB columns to `clients` table via migration `b1c2d3e4f5a6`: `service_failed_at`, `service_failed_unit`, `mqtt_reconnect_count`, `mqtt_last_disconnect_at`. + - Added `GET /api/clients/service_failed` endpoint: lists clients with `service_failed_at` set, ordered by event time desc. + - Added `POST /api/clients//clear_service_failed` endpoint: clears DB flag and publishes empty retained MQTT message to clear `infoscreen/{uuid}/service_failed`. + - Monitoring overview API (`GET /api/client-logs/monitoring-overview`) now includes `mqtt_reconnect_count` and `mqtt_last_disconnect_at` per client. + - Frontend: Added orange service-failed alert panel to monitoring page (hidden when empty, auto-refresh 15s, per-row Quittieren button with loading/success/error states). + - Frontend: Client detail panel in monitoring now shows MQTT reconnect count and last disconnect timestamp. + - Frontend: Added `ServiceFailedClient`, `ServiceFailedClientsResponse` types; `fetchServiceFailedClients()` and `clearServiceFailed()` API helpers in `dashboard/src/apiClients.ts`. + - Added `service_failed` topic contract to `MQTT_EVENT_PAYLOAD_GUIDE.md`. +- �🔐 **MQTT auth hardening for server-side services (2026-04-03)**: + - `listener/listener.py` now uses env-based broker connectivity for host/port and credentials (`MQTT_BROKER_HOST`, `MQTT_BROKER_PORT`, `MQTT_USER`, `MQTT_PASSWORD`) instead of anonymous fixed `mqtt:1883`. + - `scheduler/scheduler.py` now uses the same env-based MQTT auth path and optional TLS toggles (`MQTT_TLS_ENABLED`, `MQTT_TLS_CA_CERT`, `MQTT_TLS_CERTFILE`, `MQTT_TLS_KEYFILE`, `MQTT_TLS_INSECURE`). + - `docker-compose.yml` and `docker-compose.override.yml` now pass MQTT credentials into listener and scheduler containers for consistent authenticated connections. + - Mosquitto is now configured for authenticated access (`allow_anonymous false`, `password_file /mosquitto/config/passwd`) and bootstraps credentials from env at container startup. + - MQTT healthcheck publish now authenticates with configured broker credentials. +- 🔁 **Client command lifecycle foundation (restart/shutdown) (2026-04-03)**: + - Added persistent command tracking model `ClientCommand` in `models/models.py` and Alembic migration `aa12bb34cc56_add_client_commands_table.py`. + - Upgraded `POST /api/clients//restart` from fire-and-forget publish to lifecycle-aware command issuance with command metadata (`command_id`, `issued_at`, `expires_at`, `reason`, `requested_by`). + - Added `POST /api/clients//shutdown` endpoint with the same lifecycle contract. + - Added `GET /api/clients/commands/` status endpoint for command-state polling. + - Added restart safety lockout in API path: max 3 restart commands per client in rolling 15 minutes, returning `blocked_safety` when threshold is exceeded. + - Added command MQTT publish to `infoscreen/{uuid}/commands` (QoS1, non-retained) and temporary legacy restart compatibility publish to `clients/{uuid}/restart`. + - Added temporary topic compatibility publish to `infoscreen/{uuid}/command` and listener acceptance of `infoscreen/{uuid}/command/ack` to bridge singular/plural naming assumptions. + - Canonicalized command payload action values to host-level semantics: `reboot_host` and `shutdown_host` (API routes remain `/restart` and `/shutdown` for operator UX compatibility). + - Added frozen payload validation snippets for integration/client tooling in `implementation-plans/reboot-command-payload-schemas.md` and `implementation-plans/reboot-command-payload-schemas.json`. + - Listener now subscribes to `infoscreen/{uuid}/commands/ack` and maps client acknowledgements into command lifecycle states (`ack_received`, `execution_started`, `completed`, `failed`). + - Initial lifecycle statuses implemented server-side: `queued`, `publish_in_progress`, `published`, `failed`, and `blocked_safety`. + - Frontend API helper extended in `dashboard/src/apiClients.ts` with `ClientCommand` typing plus command APIs for shutdown and status polling preparation. + ## 2026.1.0-alpha.16 (2026-04-02) - 🐛 **Dashboard holiday banner refactoring and state fix (`dashboard/src/dashboard.tsx`)**: - **Motivation — unstable fetch function:** `loadHolidayStatus` had `location.pathname` in its `useCallback` dependency array, causing a new function reference to be created on every navigation event. The `useEffect` depending on that reference then re-fired, producing overlapping API calls at mount that cancelled each other via the request-sequence guard, leaving the banner unresolved. diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..7d61ab3 --- /dev/null +++ b/TODO.md @@ -0,0 +1,55 @@ +# TODO + +## MQTT TLS Hardening (Production) + +- [ ] Enable TLS listener in `mosquitto/config/mosquitto.conf` (e.g., port 8883) while keeping 1883 only for temporary migration if needed. +- [ ] Generate and deploy server certificate + private key for Mosquitto (CA-signed or internal PKI). +- [ ] Add CA certificate distribution strategy for all clients and services (server, listener, scheduler, external monitors). +- [ ] Set strict file permissions for cert/key material (`chmod 600` for keys, least-privilege ownership). +- [ ] Update Docker Compose MQTT service to mount TLS cert/key/CA paths read-only. +- [ ] Add environment variables for TLS in `.env` / `.env.example`: + - `MQTT_TLS_ENABLED=true` + - `MQTT_TLS_CA_CERT=` + - `MQTT_TLS_CERTFILE=` (if mutual TLS used) + - `MQTT_TLS_KEYFILE=` (if mutual TLS used) + - `MQTT_TLS_INSECURE=false` +- [ ] Switch internal services to TLS connection settings and verify authenticated reconnect behavior. +- [ ] Decide policy: TLS-only auth (username/password over TLS) vs mutual TLS + username/password. +- [ ] Disable non-TLS listener (1883) after all clients migrated. +- [ ] Restrict MQTT firewall ingress to trusted source ranges only. +- [ ] Add Mosquitto ACL file for topic-level permissions per role/client type. +- [ ] Add cert rotation process (renewal schedule, rollout, rollback steps). +- [ ] Add monitoring/alerting for certificate expiry and broker auth failures. +- [ ] Add runbook section for external monitoring clients (how to connect with CA validation). +- [ ] Perform a staged rollout (canary group first), then full migration. +- [ ] Document final TLS contract in `MQTT_EVENT_PAYLOAD_GUIDE.md` and deployment docs. + +## Client Recovery Paths + +### Path 1 — Software running → restart via MQTT ✅ +- Server-side fully implemented (`restart_app` action, command lifecycle, monitoring panel). +- [ ] Client team: handle `restart_app` action in command handler (soft app restart, no reboot). + +### Path 2 — Software crashed → MQTT unavailable +- Robust solution is **systemd `Restart=always`** (or `Restart=on-failure`) on the client device — no server involvement, OS init system restarts the process automatically. +- Server detects the crash via missing heartbeat (`process_status=crashed`), records it, and shows it in the monitoring panel. Recovery is confirmed when heartbeats resume. +- [ ] Client team: ensure the infoscreen service unit has `Restart=always` and `RestartSec=` configured in its systemd unit file. +- [ ] Evaluate whether MQTT `clean_session=False` + fixed `client_id` is worth adding for cases where the app crashes but the MQTT connection briefly survives (would allow QoS1 command delivery on reconnect). +- Note: the existing scheduler crash recovery (`reboot_host` via MQTT) is unreliable for a fully crashed app unless the client uses a persistent MQTT session. Revisit if client team enables `clean_session=False`. + +### Path 3 — OS crashed / hung → power cycle needed (customer-dependent) +- No software-based recovery path is possible when the OS is unresponsive. +- Recovery requires external hardware intervention; options depend on customer infrastructure: + - Smart plug / PDU with API (e.g., Shelly, Tasmota, APC, Raritan) + - IPMI / iDRAC / BMC (server-class hardware) + - CEC power command from another device on the same HDMI chain + - Wake-on-LAN after a scheduled power-cut (limited applicability) +- [ ] Clarify with customer which hardware is available / acceptable. +- [ ] If a smart plug or PDU API is chosen: design a server-side "hard power cycle" command type and integration (out of scope until hardware is confirmed). +- [ ] Document chosen solution and integrate into monitoring runbook once decided. + +## Optional Security Follow-ups + +- [ ] Move MQTT credentials to Docker secrets or a vault-backed secret source. +- [ ] Rotate `MQTT_USER`/`MQTT_PASSWORD` on a fixed schedule. +- [ ] Add fail2ban/rate-limiting protections for exposed broker ports. diff --git a/dashboard/index.html b/dashboard/index.html index e4b78ea..14bed27 100644 --- a/dashboard/index.html +++ b/dashboard/index.html @@ -2,9 +2,9 @@ - + - Vite + React + TS + Infoscreen
diff --git a/dashboard/public/favicon.png b/dashboard/public/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..0ffb5249c52dd14b30c96b972a5311649f233349 GIT binary patch literal 230542 zcmXt;cQl*t`^OVfqLjo|N+dBqYPPir4Pq2EYVXmi+Ene)AWE$&QmaL&t@hqEgQB9f zRqYX5QT&|WIp62p&p*#U&pG$Guj{(s@AoTOS6l7Y&D%Et0KhGE3|bEW0RI0ACcFM3 zQrBJ&0Kfq1XeEPJ8A46dBd#Vd=kCXOmDS9V>V3D1cFH#NXMV1GFS+(=b1$v7)uQrm1u69{b1v&A;9BwzVL@O{|^iYY6?WHDkteZq7v)c_$k% zItj&3n5CqEhb1R*u5{HJ=1Z&E+T_r9ksdmGc#V|Ee5yRj0&s>!JGT5#7GljJBxKP# zsw&&Ds%k|&oF2~Y{r2q-1#=fS-S}87lX_^q+f!x18jVB%Qh5`B2vx7{W?hF6?0eqh z-!wo!ein$iJ9wkMW-dC9Abw0Vn=Hz`Z}1k4X_7M68&i0RCB!bfKQn`;3HrP1xmPNo zosm_dFdJBG>txRAL7}?}*}NRQF;viW9^yPe9eN`o`J7u*5HBz7hkVoX!`VF83VX>qXvjBzv9sV8Wze zig>pNpe;#mt8Dui!-dFqQbtl2c%NX;|F*(4h z#n67HKbRs557RfQ>y(sA{! zeK(^j_$Hw{G^~#*ElruH3qIBZ83n~_ZCa?@HFp*e51?_D6?Mpq57YWQjBapyWODwj z=0|9?4ZPWH(?GEG>TS80J_XEv^HB|bwBLyfH5$NZ``9IjkG_g|r&9x-ccYTDTACaV zN5}TLSUC$gDEt*g&K$o_#J#f85!6tb@=F!`{6u-n@j(fOjhM!ShrlExLJeVJW71XW zBa^9Ofz#U0nAt*0G)mrMz|R!9nvR?r7!zBzRT!h?I2Z=R-3sg+$?PAq%{Dygqp7mb zL!&E3@l>Jok98BpsG5E4;YfB>8XE;ukRjt}>`@n5& z7yQ3B3|Mo6o+&VaY(r_IAs}M&{QU#-5tk63z#~uea*iJCld5;A!-wppp8*tnKCJ7s zC_%_@ZPuqJyfy?!^6`SxoVuYQTthMyU??_Wz+7H#W|jl4n+AX<8Fb03J{9g;cSMKB zvMFJ@R8_TofZ=lwcyE3zphRS%$K_!$%*;2iyFg$mo#8MT4I3-iCr$Ie!{zlx6NX-EF6LAgw|4kb=QWtT~5QdpP#j9oFU`Y?3b1|#0pF*k-0iK5{C3TE1w1)#8{zOclcG-#K+MRlVr?Bgvj zi9rA#>PaW*-|@(C3%4zdF!(v097G1SsY_fG?DV1a1wZTtBR}r~0LUlIzLIWzH>p5M z{9UHbEjPLC^7yM%c%`DP)9x>s0y_z#?=(G}=N_%zOiU9Mhbn2#PCiPe@;cDR#b5geHp;R7nLF;%Z#y_;l*#DoMOqO0@GbFLn=(1%6L z7F#SMp71AuwOt4j)*a0WEU|TgDDfUDpd(b8>kd&i$`81}QmT`tZ-(veb)!+%$1Qt# zi$Gwgahe2u@nMTV3V;i8V^)TT;bDwNR!T$WbfAadt4rQg$?$@Os_zFb-mJUsa;K+V zZkHXB^XDsWt>>j~Ehpz~H9OnB!Ap}pKS>I5**fkXR4PCYjR>d6pol^8LxyKzrvMSI zkAg4Q17Ah{7x~mJrTFQ{y#HqDNZ{GK5ruP}K>rN07i#r}QoR}4RY$!KFP>}`-R2>PIbTdA{ zI2?9pQHA1(kBZ4c@kc~wfjkWi0NAI+u>-Jn#X=Gi67YmKu|%RfqhTpiLeE+nie^r& zKI}_TWK?e-sPukfj#+F6q4NdUHTT(i&{HP+=m%r~#k1ZvFjbaPG;om!1d*6B zg~hhqr{9|ccv#Xd+Ay1XZ^?>A@2*!{nsb5+x0}=iU#p4R@g4{LcK}L^#wI}dBVc0U z?K55Uc>2d|h_h#gPX#kLF-hQ9Yr6#Pg;qDdIv^Mu9-GXsk|-r10Tff^ihU-HGca_K zVsUa|{F9h3IDRjFadB9XXr>ywNQlns{%9a?rhHgKzSw6iFzKWH(M2)Fb936M%Jk4v zm9L?}Pi!{u=5D20_p__N>}?)bqrR4{Cvzh~+sHK6rOCwTs(?P6W$y|}YN_%Q1O%MfQ-#n!-j z{j4!D>;IN3lViG}2;=(rC#%%)iBMt*qrkz~v^FjbMU}PF6(;)by9<{~Sxvk}fm500 zNBYEHp&ZyP8!)3IVJ*x>HmT?tl@}I^LD?IED&2W$=zQP7X~s$!sDWwhuR*NPR8#*= zDiqemg2Cp)N*@iiJ^Z(r3`$&#N)4{W3qnS`#qb*fvv?7}k5N?Ws%eyBQ2~eA%J?vT zDs_JK=tM_y`n;RY4uAu9-;be-EwWUNekQ!6o^BY94gDc4{5SM3ngGB<76p^>wLUQV z_q!k^nr`2ar2@9-YB?KFRVe!gIyPt?kIMoueJj-zu%mh0ABrJOmtniXPxv^Ex)yE4(0 zqR3aQqAX9S*;^#&6Br%P&o`x`p#U-RuNa5K(exvE1Q4JCM+l+qt1Xi6(v$mq80?0E z@aqb2+iDs<;$c_`xWBXHJ>&&zB{tyh2YeSLZ!HnSGuxFJ;f#le>rNZL|2BiWFCHV_ z-HV(i@;a2N49f;?99WITNYYCu)H$}DzI6+}ApM(f+aFZC=pbF3sgYXd;|e5&7xdl8 zSdUV66+DF;e@3nH`?4n`fV>7-E=}R{kSKx8`oIbX^ue}5 z$pngencC-=QDa04^s0q&#!l{bG0@{hR$@vs8Qe4CU}RJ|Orl-NMJqUaq0jYCN#}AR zt_Yxt{ZCjQC=YGx(brn8qJoYzi%o>9+$TG0Mf|=u@}Ti4QBO`NOcVyK(I%!>eDFc} zPqj4=N+l_K5ituHqlaf|SGz_bORHBqBLB9o4*qM~jVUMX__>Wd{X6q?q4cS)aDyP~NqD~m zijRv57a!m2L`)e@U^(cJ@5pE{HZ3#+0oMLLf1jiWf)ChC8Ex9Ic)4u$zXm`4>}n^g zRrOhZM&8^)!B;bbEUJJpg0(`zsdjWm)M8U8=fK7>EI5Sb{j=4%y}IDlzdLHy6eoAG z+Wh`rlFompw*KP_-pxL*Q#@%<^!H=iU`N03ZS%bv{m*t;oLv-3Kl+nM!_GM%H2ym` zSCktc@BWk#1+#@=;>BL}34M+mtrl*AZPe3z9tgKO9w$26v(~Mw0~t2GA}Y!5WthIA9hPd*m3m{ z2wk_yxF5TfGH;ir%AC3jo98#w$Vok4 z8W2YiE?MtB914ej?t3;2ie{q%0r1^h7^;rYxyXkokTZ8)H@HfyBw23&i_YE73QL3E zP$`Le+X7Z|L<3NfRnO6m`ESVbnxdaP`Bit_1Ogcn}3*Y{2)qDt8}0KGqxYo>s`|`$cvo*>5G5U6rH@?OmB~0@qRdZ$O$#p zFvSQ!c7p5yOsa4j4S@3AVfC6_g(wyp(^Lf}^XKZrg1cnl64ZciYs`^}{GD*NL^kEy zY~3KLmiwTRgFn#Rqe@04v8r7dnW`&sMjxV7@6z-J%~~m#3e)igsw%;5bb{!pQM?0R zCDFG_i*Z`_VO_W6Bw7ejeTOz+;gaaJ*HJCusVgeq8gm;H^gPCa+ zOuZQSO~tC-3g@H&GOX1Fg~lfDLxG6^lh1B<_zV2@c1NG2i0ecJS%oo4oV6A`2_^I; zBINP^ITAnrtw7UMp#^Ii(~ru=$!vCn;Sg4clcN|+zlO|%H;)~L$?R$6CaY4VfDIjN zDZo74T5uG9)>I*!kDm_#y1m&Gz7z7Dc*Di~>!ELO_KxTurcFMe3_2DUH!KF!C-)$J zjez@%J-MgMkN=OF_jn@mrT^aV(*1vTLQgO9Hz`S1Z?E>t+fD-7jx`mp4yw-0L>3N6 z)=Yty(GBLU4n09!)2*c5_sKB{2=hC_lP-6dy_g+?I$^oDwTZ&UH$=3KA8rB1It-T*D)CIb1h z_-g*-Wq_CbU6!ED_WnN1tL6}7&gS&s$y`vj@lfoI`B!_yy-OReP4;gsOZ z3bKD4bvj%vos)EU!6==O#les>t4pJhKUQZuBEb#9`Ah(sLp1qclNZcrRSDwycpGWWw3DkE3(9*pnx(hxw?Bw(Jk0HMJ3d%xy-Sj|D=U3S%(nuecRei__1$-e!Tk*8hs znRQhP2O2aggPN0`Uyr1NfBO!S?xB8q7~M1k6@r zu+e}H!acIJc@viw9ScF3!o$Ia#Ze~nFe;e3;5+3wt({t?$8gplo|d#!&)}cp4;`7y zrW-w96quJ6EFT|BoNbLqj~5$lU;T@|T6%g}esx+Gyh1vx?m!C)%1(*NYj@>T5buE|n6>sO#S+!mbRp_?h^YLNtzVg3V zT?sBaB1?>VYEP$TZ_n7Pyt45 z@_-;RMMEPuXEbwGB#BvJNK@coEx=xMVb{hE$BcnN+XHPcMV-%0G=1tg%JjD%z$yt| zs`TRY#?BZmIA;fTLAvLQ$~)09v9ZQ5jYbtmLlvUJZSJC!plO5}YlTLk&*78m%05vq zuACdNE`4mTXdQ2#-n|6KSR>QKBJqT&?O@2t7A)ZLY)v`CY5TiOY zp7jl1qEU+>9nAS9>SM3mm4jWvg@!ZU7Ee@NFsKRXoqTDUT$7QotW4|#I>?AET-TLUjmRRnVovp*i>$uqE+1FQk zA48|}_}8(E*@&0?%$%iS2B&x)n8!h2{4_AmW^ls6E!{KdH*S#}fT$#0h~7Qet|3u` z6)g3CY}s4+O`*>n8Z^7*+j{Eme@kS<=k(9htIoFF)XS~5zquiY2O~MNZ@1}43a{$Q zpX8@o)916M;Iba)Z**~oQ>nrJqB?gmT4>nsYTP|W=o1`)2J%FiD~u4DmIwm_LBEJT zSQe(zLH)JjZL`7~7;r)|)p(<3A$|u+)t`sFSDTMazt0qn6HQO`llui^U3ay+ZhvaZ zkrFJWAAnll)hJPLpa$s{7=Q0YtXP`L8|55PH#^7@gE?iiOEII;M;F=mGSSJ4uYu4o z9Q@ISw?gTkPc<-ZUSrjH-#TMqVe!bCVbk5N`3rxy7VZ0hz-i!P&3JuW}01m_-Fw#$FATayQ8NO$p^4kCgVoDYbszbie&HZear>8SI3tw6or7pc?#;Z z(F!HWNqw$w*$|_MG@co}G*Fxa29)YFzKQrCtNg+D{a7yyZQ-ay+fGA{l;n$@lZ8p> zH^vCYI8p$nSrb1EK-j48a{)$7$}c5L4eJj_kGBoH&wlzR{GR)8gJQzr+VCDSYd$y) zA)~8{OD$J?Me4n(YddRJZu({L>BfZk zh|4_L_kePr#`@N`u`f*vVwPVuw>38hUz`oMwAK4no%Xt>Fc^Gq>TSyL4mjrk?^r5S z*xG{!*IF_sPgfqiChb@SU2fNnxHL6|_?gYV>y2ysC%c*&zxyx7>TEG&MdYF{{*rwp z%Sxqb>moq4`+*EJQF~pU@FMN0);L_H6AlFc z0TQu=k-j==_M7qu6I`CSb)7sS0$;F^Tc8GD6v(+Te>Ie?ZX2GgCl{_hquLmN#mRls zh9eL)$?oxsdVI>>ZaF?Q2r~4(H;o3zz)kfyPUaG_u$4$H{Zg0dXuL|0u_|cknFY+) z+nDd;wGIHVlm=O;I8WE<73e+3(n*pYzR}ecrzeBHUBrAb6_=Pn=)Z?bi%-=ukX3?z zzkAr-iKXw0=0;C1A*dw0@cG+^Yb()(GBKjCSR0(^h^BLpaRC6N%KPDcqU{Tpy1|&~ zMLIg)^C9-}3BI=WqoH%z^S!I(kTb5!n2@X2ZY@{6b76}6ey>&;+)A9|&1ZU+Z@Kjb zS(eq+ipgs1!8($@GU=_NJH=uFb?>o>rLj^g5?uvubI6t#$AT z);-oN66L3uo$sgdr}sQ{)aJmYQObO{j<$UXNpDxTbaAYIW$hg%61W!-@<-%qS@DV` z_%O#?FX#S)GHYP0Cj~fInWk1Bos6NDKpnc1sdi|kRr{mu zdC~sX^k4=Ml>G5w3OE{&RdSnC$5tFjp&WT_tv1|Z=J`!%k0nkoZpcWexh+~gk3b=J zwUpt^C$4sk0z2dhB`!HkiS**+pKk)oT#TML()#x8x}Qq54`A;JRr4z!wm@SNWTbdi zX`xCE3I%K>1mX} z^@X_DTspWi7Ke5m&_bfY2dL_xUeM!PfX6pL1P}~f8FZo*^T`2@rbF;*Xw!hm9Hw77 zs-mJocVEs8$T3ih(d&^)Pb@XLks~0aAy0m6th+tXi=5#j2IpRvMa9)-!~4!%0Ge+~ zE-|BW58l%#({t~R5Pc^U26M&(tY>uw?#}{eCT%|1w1uvLFFj@C0*>(_ zw2h}{BmXYA&daYarI)2w7nwIherxWo$Qt4Fy-8s3H?fKU_SLS%H#hD!+$5bl0tdgD z+iS40;=ufeySw#5n*OSJ-z*0_1A|hNjV4FXYnMC0f`)I*t*qJtPY1f3OX+-jb%HmB zh!=tW6|RHd$f!7l30dwk+1y(jXCJ~eCYa|&*rAr@R@t1KkG(%QZaPY{_DMj+>8V~A z1KKBsB)FRcUP);UnEQ;KPx|JdLwkEm8B5<8BkL#p8Ul{orGyZ@YfGXdrcbNI0wEJo zy|VO;L6=*X6TdEA)%zIxU~XY@+@@teXz82R&jYK;`@3z>gu%Z?`rTL ztE+Lv-KR;@XJ_f#M@_4_shiGN{?AjmAa92~EoLeS+W|C_iYkEBE{~00Ur+gi4?#-@ zz(4-N5d(sceqdI~k9DVGk4zT4nYbb>sDiRRHFEqU1E8;F@%)u*L+b%KuY z7dkZ!dUXj27AAIz#e^4gSi?5}y?OT5vz2)rRcFM^jJPNNS1oaw1~NQlpz}H>r2m*(@NMl@M6@9qJ?Jm9vWD!`+qY|K?d%85ybx6Y z_pA9tYWm6_+kr{@-Y<32zSPJA-{6}1$2+u;RK^nVp{VbL;5$|q+~|;ls`;S5-KqXn zS37Q(CyK|}mlIuQbJHV>=fS%-61hgkyEfxDhNz!Sd)HGKCyHiDkW<{^Y8QJX!LpY) z-H13_4or-gZnPAN^07)?u5g=vCj5@^Zl`BZK#j}v`rcOV?5v`eA!`l6FpV5bMYYh( zIje6-mzuh|lo$b}igqn4-yvC=`NA%{r?dYp%$|(cwY4@Q-yD7~q@mulD|??-+mg9M z`b=bTYxcQvr*vXTl`OZKfApD?bbXvp;Y0AM>#|<|yBiATbpJ~5$)eh=&g2n;kTVFy z#m5gGMI>2T%+9p6>MtU>nC)g$i>GJfBB;rJxuerpc);=T@lId_>&S?!NSBV))x`0f z+U}MF>0cq&nbBow+dirJ^jKnsWO;SzR=!I4!4(XIG2kOqy8T1;9Zn9UCGv$FSPn2*~H*1ROS(v_3Z#T;F)(7L;QgX~2&C0IuxEum^FR5)QX%WGHR|B6874v4_Ua?9N{m0PUvP zD*_S>iSz^ujp*3J>-@<Lv3jetZjnd)T{8K3uDp~2gcEKnuRp{cJtvl@$n_~ zG;0B{SSMSULC+NMoeIX#SatgI7c;WI{xVP~H}}_+{_)O-gC&MlC&ZT+KNY+#k+XlEElEta-GqLVrD7}+TQW?UlT58)>+{f z-hml;=H}+x0U|2&OMm}jjLY20Zk-%09=@6pDHom(-27HdXQdk3_013|H`lf!|K$x? z;P35`*q3^xrW;(rn{?HTwqHka$)W?E?Xe&Zd17zV=5+eODN+mNP;L0$knh95`Xy)P zRBKX>)-_xKKMQpz8*i4ol}kS&^vo^QH#kd6a~zaMZ6ctLD8IvTo&Eoc^Q-{+W*XMIVaJUQHFF;c$VA))}4$w4GD7?Wga$_L43-xGw*@nzOnXFbZ)Wd-?CU{Kz%&SK~w>{PFZl zxa}gJp_Pg}IaH#PAkg75?iangizW1-L0Tv#B{A@H`WIMY(Xm3B)7D!<2o(dQqg~~u z)GHbk1$&?Ej^VeiFneO-v7te4ZTzSTBd-2Sd>DXDTMv35fUxHTn;0Nfq;@LN1ux(Cb22Vpx_eR%AS$Hj)|k8#)BXm-m;wfh8c-%mI z@S^DGEsI;m$@bz=rsxgz*vtmx!o3N<50Ejjl;zu0ivFb87E)9UJ9psG22Y`okD$Yw zIr3)>SzXP3*?DD~{vIBq$5r7E!ZngxTCO_IN5hyV@AiKZ&1-ieXH0u)+k3DRfpU~u z`(e7|l=U3Xa-BwG#!X9c-q{N@ji;lafm%~&xqGOrqHliL`uLfjW5r+Ui%{z9!p6qi zudb_2ldIJ-cT*I|pEqb6)gFysJpS)anPpn}pRO}jV!259v}oby1RBgdyXUBe%X~mx z;zN$eCSR+v6+eZHxPtV-7?=6lAcicAK2s3;bkupmS9fl0vz? z>Nb0^*hKMp*kQV^PzZgm2cIO{OQ4KznTCWGq&o>PK}zW8!{#cOl_RgkposxBz*c$s zgfAQbN7t^gnXG&p-o)s`%D$;d#^?>Q>KIX@qMP38-K^lJhNB&0X>+jM02_Hg+UJ^E z9Js_mEgY3t?OOx6Fx+e-{ab=*KDNN1kX%y$otL{Ejl2fMn3mDLbO)d)Yyr7FB@94{ z%-gXj&X+A-XlmQ@`pk%fO@I}-xHhW|mK~Y1)_IltlGXtk_?qOSKmz8jk3CqK0@ueX z`_A6hHUZ3VS|Jt-Qps=pfurHtZjOGoS-f%Y?)@-{t|3TRQudY)9L78?E3Wb?jT|oK zNCrKstx7H!Lcl{4@IU~o*vwsqk43z{=N@46pm2_$t@9@uP`I^{tt}w0)_0&!3zNPo zjdIzd1rZrUXTF5qsgs=ddl0f;e9o_UxfF8Hb`gJ-`|>0pJDBovhORm2SWZE%andxO zn0|`d-F$itXk@qf`?b}1Vb2v(iU$XapPv7056H{1YRf2dmFJL{QR*A0MkamK2pjiT ze5sAy+$^px8KsVb;t_wj0dxsS-Dpc}z4)*V=V0|dNY;btL z`KhV%cF?e2ftx{yUfqwk&!|nvVmh=Q^gV$&LR5ay@aq0d9yH+TK!v;T%XK5Qp;|ZC zDb1~;Kus`iZJeEv`wL)7$4^^b1gO$<_Sx26dfQ3Rh;#s{hA8nTYWyEvfbphZ*?Hqh zs_#zgzfSMHnU(;${3u^x&dj#I(R4@4fpa=_RvE(-AMZ<{$`g#d4_54M%EcANI@HZ? zb1JY#<)p<;@;O%B7g4iwvyiHJaN?)js>kYn@C=D3t0rUrWtRI0&TkET_}4c z1P)8UbGM$&wE4b%QVf1?v+auMNP_frT9}bOSf2m%Mp7I&;Gd+AcTLQnX(fS2XWwey6$?YzTtC{-1|a_f1O)WRPy@YH7FxTuiB+Eyg>_T?gMTKqlq!rBINSOgaYk z*gBK~3ipt7P^HQU7)eSO#PX{51^$fS_#M<)Qd;;Q&{l(yzqq94?t#HR4h>23axFr! zn&z7J27CIyJnl9-@%dYDHBNWcef4j?I%;rQ@s`5oxa-jii-vS$%7u3vv?z%Gzb@SF z(DQ?}j?;?%O;7tqx#ORuYSV+)CdjRag{78FFE_Hd=GrK29K4WNm*U5*VA zz0c(uUL6jc=Egkv^e7ZmF8hA*aHWPX+kfd@$lpI_Zwu!3hpT$C@;$Vc1SkM-5SenZ zL~c~AUz5dD*^X~?*cgf!-aaFmrb{MMq1AMepb4s=K?*NXMQWH7R6bvkF4t3)q*%su zgf1TazC0MY9B#YpbGwjP^Y*w}S}kw>w>DQsX%L!wD=RC*-NYv+S6tNJSVh`@cbAij zqdeY(n16n-om-V#taMMOX&1#@m(}Bwy=tJ5Bm{nR9z3<0ta)2>(Z6cW%`8(S^MX@Mq{(BCv9D?(A@@ z)7l51+^-cW7LkS}d~kP6Dl2s^0QW*PW`#;t^#~oa8XAkh&W6~cMy;k~tT8S5R#yhs z?1H7ur z83d%s!YDkr-R;&U7Yi`a*{L>P%#^1;7ra3gP>L;Y+4f*f{FCv@eKVsf zs?JF#ym+F&lE6vJ42Z;7$W|p_tOV(8>IZiMA-LBQZp)TN+i8}f-#jx~?Z2^ETy*u8OTJ55Eyg1)*K2!dPrTic9 zJa})o__}l^#C7IZ9C~&R5-DzFB_I)?J;TZ&v?W zx%_(H>3mf1Uwqf5=*T*-o-_5rYRtIc?rm(SH3hHN4N6;^ypIb8XO*~w{(C%0&n#OX93H_^#n>-l9GIDRtPwv<-6T6Bx}Rh@Uye8H75V zOL35swz-x~P%>EkLyh$cH{ES>aSAdS1XPnQW+xP|kRc8|BGw4IYImwv1|;N_NqDK6 zRmS!N1)OxO|2o?kx6<`Ec&E5`y(BLOO5bg-wz$l2lp7T<8F5)8FJz_3+@E(7SD(Y! zGXAo1Wk}4;OM2orvm<;~o$Le5KRQD;Pvo;|8whjFlB+vdX!IR>##=r*T->&JW9%r^ z>VM2*wRbB%gt)4BbjULBYe;W6t^G%?rKQ}fPnMQ%x{p26HC6zyg5*s4XAnNvy_0Sz zHoRF#q6(eR^tqcBGz$g(>_E@}V7T@>_%u;1z3+>!`De1wWOHcHTM! z?XrF76YmyRW_SBbMg=RhrZJl{WB#&RBxG&A?c{Xce}qplxQ(Wr%56?&b|N@%S(SFo z`fYxVn3U+qfxaO^#4kj#nP!gCMocP-1XvTx1939L(hKWr! zKPzE2NgPo1H)Nvk@Iy_S?GTL>1x^=bL$e!s$yF+AWA*Myl|nJ1jWQD49?tJg0E_PL z)EN7>n9(n0_Y?d{E%)86ZTkDk+cUA;Ig&g!*R9bl^gs2DmPT^!TMzf$C7p}%wOPBB zkx6;Zb{de4l_{SZ%ZaHpNE#l*TI0jJ(IV!>stb3?Oyfv^l->_TF={N(;?f2IJ1u5a zAEX)d>V`3pMF1cU8^tCB=cc9?`)E?`VnYzv3%k1E=v}JG2jqU~MIGgypBy#jV{%-z z%dLB-tAYx!{T4(H`y}K-*1i2=GH&eGJCdm7#lMk@7_Pr%ZWcoI9rWZQIKZVAet3k^ zN4|c|;;&lp21Jfi|LZUYDHxaaM+cJ<5%Jh1UCa|dR-SUv<;uqOe6OHWebSXt+ZpxM zU*EZt)+3xIwaTmCZ*@3IwQZOlJ6Va8P8^9EuT_`hteH9I0`3gBdk5MwN7)>V@xHh# z_$mfAetnVLrf2-ra_%mRPNL4eVvMzEA*Q5bFE`P?Hz&m9-4 za6`-U4j(0BJ<}9zP`xNZUGV%nV*Rsa}@OsZkm2Z>kKKS)A%w-6tU z6uN65t|`Ky7!{6cBoYlvt`eq(sKXUs!Acm!C47x|wKH~y586ikHF+e&VdSC5JeSs1 z*^!L11e)(q5QGfzSUG3y5io%)(NJCs2)TAhS7)l7&E%{()a<3(`vq2s%rMsdFB{P& zxlWS$ccRAb!aEU3c?J|-JT#LycON?qt)BtopYsjLUdy=}q(@N=+Ayu<=bQF?a*kR^ z9+%r5l0)QF)DBCh3+R#2@Ft0>MOG9(N}t%sYpS!DnBdxxdvwX6IC zZJ8gLvdWfr;oKM{r=Uu*&erN=3bbO#ZsQy$@iV97X#wpnp~;ampkp8hlB z)5eQ2zRhLVx|n-?PlM;06wU`*FZ=64PS@Y|nxT%@e-9Vr$7rq?Z)Ax*W2Lgz;xjC0 zHGr*TaD`lDF5vPE6b}(wS1RL_$)uEWCn7fKm?**|P4rP0$y;My|13_nKfRV`=&psSSd@JEeR!L7@ z{M*A&JU+?BJA2I?{_B=#``Z?Bh_^cZ88z2ds!Mc#`5?>OuUWdLQKZXsGk3~VJJ;@G zZmpwhmsF&g7vkf8M&&hgcEq50E{lejOMeEojV$LLHNgg*Xh7Z=^{H!-pvkj)%%_-`qvy=Ga|v>>83+|Fb&6{#*~SV&!5P0Kv)412zt0i zgMkG+JY1TpzEGgPsYxv_nTLhc;>?miciThb;W0RqMg2D_jh|cOyyaL`8k*=%c2YJ# zK(!}0%3SD8tJv+33ZcC86+GKrA=~gaQAY=EMo; zdICE|C+7e;b&*h~`{HP$rz^Vv_@r=Q+VN}TYvUBBY}Oz0JlqiS%zSFYkbiG=;w3M? zQ)c@eH(eEzE`PYSt?FEP)cY+>5hqV}=7xBz zI&pESPgZ9gK%yGAdn(zYnjJd_2k%;^kBpqeAD7Rac!Y|aM=D+|&;QLVn9L5{6ZB56 zI|&SSUp*cy_R)6Waai)ni4zh(;zYkN3+<>dS&%B5eO}6&5n;g8K|qa*a%z2=8+sHr zLq@5-xbUf_PKYrQhNaaq?|O&Pc)d;^SL&o5&2YDd+#hakY85Y#@q=6Jxo<9OZ(@5f zT&yti)tAY8WuqMaLbZ^D*oG6;Zbs!VQ$8ju&pnQMHsiI+R&$F%4@6eEuG-rIcO-)s zFRqTQW}2)fZ$x^?HTdPm+L8SG@M#aw%pJ3*%=mb1?e-GG*-3f7yd#Qc2}cRpR{vVT z^lfkHZDF>)8f}TU>@u<|Y1g}#dKPo5b-5{IA>eXj-hSsqVZWonD#b(J`Jp>_ID9rp z=wUH6)JKNlsDZdR^@1EppY2CuEd{FhD4)m%?Z`+F z@JLXDNnt-QltlVem6h7otHo|2Buh1!)aT4Jwlct>m=D5p%pf2bm242JEcby8z#(S@ zR#HZfJtwEx+$a)Yag@&BVg*2*%K-~3Pgt%eaflSSC;^X2`+E2O43dsH4Ga5t6vvry zV@AdPLu%KwP|{*W@%V7YX63b~r4(!M#AfzUSB{9pt)v6f0CM!fFuL;>d35^>KgNRR zsq&#K^M)&czjK9oB`hP&%vpd>d28h%+lrqWPP&}Kd^)xrNE^1It=vE}s262mTu;2n z(Xbd7RKIOj72A#S<(g{>-dvgB%bIy))Jh6y-JHLux?1+iE^_np53G(Y-C^U=l#!QK zXqzE^rnRH8#?7S!2l`hw)&J3hl+yf1w!2okAsUMWX>acRvGrBVAkF(ZHRx`Lnh#$4 zPOV2(mp_f>|HUfedl9o%J9Q^E4fTQUI;sN2cIBFkR4Q;R;Nw>9N8lg=mw#2S}H2Mp27=FeYWezzcl}imQ9B;0M`e$De@^?;^&M4Nr>Hq z7a5{o?$!$NuviNgzb_{FuExe@z~vg@kJ)-SiDsJU_Sn#cqT3jLczz~M2sWc9EZ|V* zw=Txnvh!!a5YtPXzDNyT#h+ZxkS;c^{5;N&%QnvLS+pHR5(zR~*&N%x@d*giEnocs zEx9}k2DF$BJT|`hWQ(jGG zpBMSIHMx-F&i+Vz(2m@zj(R;zOaYPU$u%Bhin+y_3L6l)_a7yt%6QT5R?O{-LZ~Uy zeUR=auZbxtorv~?yp!9^TH)8+RbMI{7i_TJ61r>7x9J}m4nYk-RwzGJedJh6tDuF3 zq5GmZTnD7v&5XbhN27Ncz%RsKk0g5wS|pW0Qr!Wg+@Y*p46Co9G|-eMd&dWDv^JbJ zrMZ~l*S*~LSwmHYqU4b`pKTZAx4gIMTgumNde~Dofd`U{!I3QeH!Pgp2EFlqe>wrN zp`id==hm!Hvv2@hLz)ZOym^%GI}WbnrJN8`%6tk>K>~iH3fv;6Z>mMT*%7Caj#j}w zKt2GA-a>1$k(-v|f6}qUua)}P5V|50SFFv1$E_#l4&H^E~N|8EjHhE9fm= zY9YS7tPmc5-K1&I=j?ZOaJiO!s;TI|ealT#@5QhAmJ@;cz_mPEh!*UdsT!HS9WMan z4p*1vPD-C3>J3Po)HkdCbiV+&WYK1$s3o^QHOk0P*iQ_1 z&juM%SXUC|X2?AS4=VY5ttmp2-rRtBSvjiH+awL#vrs(|F9T+n7&J98fIL-E{hoah zAOM*y+W9?}>JRMQtqO-C*xHn)F!SQa-*TK5ceKk3_I`-X2kq5e4JlrlU6Za>0Y{%> zdR_l`2R`Z<-3WXcxb5jX+}AYw(!4Yif{J_(3LU;ho+^`_kxBFuuh|ZkOpZl8l8qww zwC?|9<$G|5;QF^K!4<(bcjhpE^-%Hb$9VPw`M~4fJDEv31PhAwRDEAN$Gk3K%faWf zLt3S=znYwip=nW_a4j+d3msQ-GL?GnIQQ$$a7wCLztH&NqhLjqzq<7zf;=iP)wH6A z;3S4muOq}I#A+JCL=mq*+$WYMV+oXMUj`Y8RMK)%pOf{S?@+<6q13#S{8B_>1y)ys zia^KEJ>@u|5cd$^L_*`r1J{i9+-a4Np=HbqtTv7dKnTGT5DulnTfFB_sKVTocn!W! z4^1pG41X-usX5H4B%qHv*_owMufYqx(}klIerf|UJ|DK$f0LpK?i=bmTbG{mH;d z;hoD%les_VLCPMPS(NHLN=MWSMu*-Azdk}rILiL&4~>P#DW0{j*}|!2Y1L)HlzHTV z!w%I?pzR2B(rcU0V^?S_hyYFDk?pCTPO(AJ*?QD{qN#aM6Ix;gBm{5+YFUX8E7r#EQJ;FH1`#r-(4=npC^hRUv&I`#%f=xV!GYVdh!7$<#9;)e+J|vO<^Zs& zlGz+Qn$9hkfs;h|H0_5x5W#*w5fe*JIlJlMV#@&GQN|4>GSyc5Fc$6F<~@e-Fd(vZ zHFrVM@G=yUq`kJ<11<4tz23}iwmJhbBBms!Ugy0d=|l<>vh z`?mzHhU02w42dudnrbG&oZWhOv}NDsnUY8v4mK;20N{hZ1Og#|!hxAn>dVrWnNvn) zB(D1{BXO3T$KGpBvebo$0~y^##KVEuU3;6o11C8^K|n-8+FHqla}Gdl9YPNT)i|z@ z(6rauh!B881cWFer_mzpH-QV(gU-2FJ;nV2nIXTLet2Rc4x& z1p&~&(Nie|+}cFMIp^LKIWP#2)H*Yp_5;TOK_wqBbunumn1xl9a>npJFMycG0Rhb^ zNz8R2pj>i*TT``2Su6leITZ`g-o3Y+7!aCuTc%-i`q)#S(`cCX4}SZXfA(kp{$DOCk_ciq9fp&oUara-nOytB z^ZBvsoB8~~GtX^bswaQxd;j4#{rPWb+n4Tq{I|>cI)p+bL}+X4|Nc^I;G1qejiPrJ?f z_^`JEU;t;5K*E4!-nn%kI^>8WKvKqtB)MsAwG!u)83CA*6M9!pVruSs@Y%4WI`78S zruW4x$~a;mK78Q=svUvrvQJccUyw16tG1sEAdgvVCuZ!ON+#qWa(4)i2sf9Un55S# zh*gcik(q^E4Ve0V-cJ|DXIH^#smp3TcmNZ6cMFwNR9hKWX4dEV!0n|{0FeM%tHj78 zhqJT;n)Q@YxC4S}kANZPK=R%#f*3sr5rm~pJM&1xNQ6Tfy9ER9cb~X&>Zp7Vzm=RPf zm26#Iy`*9ob!mb@k}w=Z5+^h3y(4py+}e~RxmhZSfLp6h2nI-i7_~O^;3PoQ>)d-k zJG*8P^RypVW2mVaNuog0j_7J3BE-mC%zKj5)Z7#Ug23I}Va%m1jT2H5L~&Pe0|@hj z-T^5cyr|G?H}4q0!o#rEa5p>L@q6uv=50eB_MiN{ zi`^`e8i4jCr6CEnrmxwSgxmr#Tphr>`9afQt(zNX27gV#=5}Dc9SNBThK-Q{u$T)~lII$-Ou4Axwzwk{P3U&#m8h z%O7rf<2%0d+kf@{_(xy)MezE^Z$5a?jF1Jq^JXZB0FtNa9E08Z@$u2Lv|$`ePP^TH zzuzCPj}}$oq(-Zu-+txpqdR(XN?-lW-~PwG?)%5%TWtN#%fHhvr?eV~q<1BzLt49} zJhzD@9j}frcRLJ6rj!QN+ItmZ77k07s;tgydim z5N+wiEW#1)VTT9Nv>reyv!Fy}Si4#8ohTpxkgawkK#`o&vR^o388%s@sn7du?=y2s zWndt8#YBm71kTge0n4~<)53#5Uvf&8hU;&5D}`}zO-AN;NJhkAT9pIy6h za^?EHd#@aAu6Q)-DpDlLem8}AP;A!62iD!g_Pc4bUiGdKy4~+c()E)o+NP56SAO%A z4}Su_^{;&YU;qBU4fgQWkNkEF#kDt$qm%2s_2`XISe%%E$UU^TBnc6hdCGY}N!l9( z5JT&o5oB1I^&zbQyj#Pr`*|_d)zNw=8wcH9Tns}&Br{JX_1ZZn1cCq((5AVR)~cDM zTrk|sNm9;bglX$|=eghD z#BS;kZC;QN1G(fwjg0n2ND%=J!sG{^6(bW8vGslcdnhLg>`ehWaUv3g=%yl)%W9gY zB#aonx15J%zXLq1Liu1{p6g63%xLa8k8RpnZ>bEajED$`y>-M7y#(zZk7{! zU|(jGM8qsi!o4*_$|Y;-uh}pVthWeQA0J!S)|w0jEwoi&4)dJE!GW{ZIZWYTl@S4T z6e7;rd!S1y81UMo=H9fWTtwv1Y|1Hh?V*80YF*oWd~(f9RU7f4M2v{At7gfZk~@HP z1IR;O_7enj?G&EJbG(>2mwp#Ss#?~Whe_^`$zxFeXl|8!-**4Fsw#hztO&E;(h> zPK1z>TWG5khLn(SnddU(G_1^Yy4;QHA>|<)Jc1FrH9$&|T5CcKfWV+#56UnC1~gMs za~6sS1Wdz#8u;q7|G~VkHpijb!jdF+w{G4$VjOaNoN}ldz~P6Jaz;de=(Tdr;0NLk zGv~l)y+Jr3d4!o0VnpO|aJAkS2w>zi6qaOep&AbR-KB>Sb4dfI!_f)cXHl!??Fka8J%tJ>;n zb5vUe@H*G^dR@lx^88$gf+!JnZFQPb8KeyE3W!94#EjskNEt$FoiHek>$=|oG9_e6 z%$$p~rsm3rC@EqDv_mwNS+#R1gy^bnu4aL>TCc1saV+yZti}io2;f8!)|+sNu-+Sh zzNS&jL$yUrf+SEFETt6TDW_WZlfmZf=8c`igRas5@Epi?=!^NXZV$U)dAVE;3{k{iiGEX^Oy?OHD^Dn>m zcX#jnns52rKmOCnrrRHVzxKmHIY~-j-n02EGc1_Y)S)^b^Qa|>e>5$JVU z_InTC933N)sq;|6-Im1(gxMVeB8dv;d;&!{#8bXOqO-jRTS!lC5suC;eT zHV9%OWJ+SK5+X7quv!NQ2ny>Co^nno`(?riVj*F4a!`_l5lgE?D4g6K1GLu&8;?$c zkOSN>TvfYBR`2eSOKwYL5i{q5CkCn3#>goNr4;Ij5tv%rnMKY~YqOwLIhyB-@$BU6 z>L2<2XFvEI-}>&ec;ySf`094SA&<-AM9duf{pBD<8CPlC&zFcq0X<@%VLxA%p#;;& zFk3%5x^esC&tE=_C*S<|pZ(sSdedipDcW-PQyT(v0hQz-xd#9AvbBpxi3A>3TGBguGt9A>WC>q5*9fe?&I!@M^pLKcx^ZoM{O z7+34M%oqfCxLpb$Ec1SSa!Q1{G~{ptj6gs@!YtN%U*-ssByf8U`MbaI&;Q{^ z|Kmrl+#0UmeCpA|yLmH?! z00;q*h$&^&n)1+>YP}JWV}zS^E9+6KYO68~7)~UwrBNJEh!C|l5>^8QlO!Zk=SC1* z24v*GR%c;Z>XL*3*byNJB!$fT;6AiP1d}g)=Zl>&$Er2qZacZ`wOa%0*hOz4nxZgp!PA z8Wa%@j_yu}yC||@s99n%)!zCrj3}b59>{?_4ule*({3h&yjmk-^f@>Sk|BAwdD(-T zF!6A_O4NOxY`XEfr&q%pfAGKizMuZ_ANta_;!|(Gx?Hk(AIoNGRYWvk$m9rNv?_yI zKR&zCYQKMR=ZMGk$*H+t&X10dZ@lpAvk&j?-}LEcU-z9q^BG_8l{)Pny!v5pZr%kk zkHrAN$=agUi3u?wd2~zTugs|F3#7xaii1Rpl#GwSvOk{=x_T-=F`<|N6rpeDUVZeC4snWPNnH&f)oD`)JV6~IzK>4 zqCzF7oV?dGZ0bA#Am{AnB21LJE^VGuDPFd_{jhIO?A z2|<9b)pF%5DJ`uPNyL086ayghjsSTWmt~58oW)ebEQka-2*V+4=^Z2y0!dB?fXG^_ zGafqM567`(ngkdHhSla!OLCY+=Th9mtml$@>*0n>T6I_rh*bBN<{(KVC66FLB%)O2 z{Y9VaxE_(YEfs}C5_8VusxP(I1;|6y1LFVZ>CK;Q+s^X5uN`B~wbtHepL=er2K53G zC<+o3APfSYS-R!6v54vPg`+i5fWGu z0s*p+3e;Umw{Onb&02GgF}@~0%xnJvyK3#4YpyxR_r34)L=u8$h|~=Zn-HyaW+IUs zW1G}w`WS|+gekSU*%%I(9xja0*UagmSAP!6^gD0+k$?7s-*LKkZ>n^D@kmEk<~p52 z93!&32@8=-<>1h~Hz4xi*Eg4k!$oT+(Ry}%wmx^))DDzC|7*{k%8UN_+ur-&L+95Y zdDq?RW!_zw4V3}_7zxDdawW_o@^W4WE4>`=xeOP0XVNJF6)@?o2 z-PxAW%5)@|>NLFPX8H9fT2U$5z@mwm?!w}+c|ZVyr4VFT1f)Zvl7=8;`fxWZwID!i zO;i|pq=z{PhxITAKz!yS@4-v}^zboEOK}?!wmY0>!dM&e*0sQ4Yb4Kyb6-!ruPUYN zcYRs<+R8LB)7nNY1sOLs7-pKSHlmbSsz5RqN%Jw9AhQ-rTd%KG)idjEUu$j0Q)}&o z_^~LnwsFGrOtKNX{b8%nL5^*b+}o0fRb}n5pXBCreQ~inJoLH0`i|dv;~W0y=f1?h z^pCydnVXveJ4mot5eqOJ&M&-=o8$GY^)%KU&W;9d0Z8O^-tBf5&ph#wCm*|+4*FH! z^f$iXzx_5sy8GnEuJ1iwRTG$5B7!&c67<%+sw9BBb5S8)mlFa`yR)_)GgG7xvyHYL z-P*Dm7)Fw4VKEQPth?P<)&u}1*=m*UZl2&=DtNF+Z!OZNX-7%Hu!*!iQyK@lI zhh+c|uuvey7*dLv^M8OHLqHPv7(i4@*}Us+ecqqB4ML*AqYXDhfN9=mZj5QvBAGC( zr+a#^R?iU*bDeB-6d_>|^|d=C(+g7C437jUVrGm2m~O#>Qc75Ox^*WEL?J|E>}@r( z-MnvoBzSt1GO@Cc(S{3iQ2~Si32#$5950_Z-1o@sPx;K{xP0tA|NNi4#~@apc*>t!JNo!dpL_?V0Oik3V+*{dabU{ms=f+5)9c)9&Wpy%*h2 z?|JW2@BBpmg*U(DP2crb{rKT0{@{bVvxkVl)-HuGBS3Am#E1Zu0ubHCJnhnQc!LOw z9KA8ZyxRwW^ z8`+8#?!efWqpC>VVS0ifWNT|bo$7uTq&|8nlhg??@$nD5gNQ*m&CE=-0HLys*18*S zAgeMnK>{doYiouv3lU%6d!AJRlC@A05)!?D*@+mfHAR$CfHWBepsg#b0wN}i(TR}| zdmCCNR_UiD(N!mKcSs0R-3@DzS*N|IY>PizPXJ*a9(g#s7{12pfPvKSJn-_^z5IXq z>u>(a@BjX<{gT_|zNy_<66~hw{`((z_U_Xd7!e}nxUM2lRL^c*VAxZSJ+(jVcKbbI zaa%s|!4KV>@(bVi-+%RA{9cyb@tKbu?>^Pjck>?IsZKFQxB)TY3rmCq7^9b}2;y#- zz^v{g0k{e#MH8T?Isjz4S!*puOGpG{-YO)U-Ef=xaBB@BY%#suu~(dh2U%%X0hn?aS*^t@_9#k6d0|U)_7o5CwRy z=j(d8KTLpyC`H!e@$%<>;rL}=|Bc`Ie|+D8#@&y6pjnYp65JtZ04t4%snpT?x?JxM zhtWrZNRdtSZ0kBAq}GBmG8ic!!@AaqSbJ}j?vVr>z2v2s(TQ;5beU}AN{Sqj!m)$;Tg!PW}XB^ zlvFlmNCb?r0w4m0ISbWNT0|uZgtRmW1cXc*4FR}JOq9&&gA-d@0&*(*ZcR%if#dZ_ z0LpyG^kksOwj4<`5q((E3Lb{ZgaG8}kU_+di4KUK#AEdR;VdmJjpFXv%U=J?qHp|y z*THk|`>$WW>w)cBXw^gBzzlT)`%Vs;oZhSB4$x3Nx(qeMwuoIa3Ai2NZh=) z(>$L=nn%=mAK{EJ+%s&forEdK0U<`Ilg@M608+CS!+=E?ZFFG9gx>r9a6qEt)jc=c z?GN3C=mu#>6?S*)z0{eC5Rix0X&!y;y}OO6P8kgBV;z7Yq?;=q2}_-jxG$&7fuspp zbRy<yYy@*GBE;^NfdG75kB8f5 z(ON&=OuOBDe*eWouXZ^7!asWRzkcgmTfj?S`J&y$p`R|N-L2LEbHRl0=wbWAnQ~d% z;@+p-Ty^TlyQg;2{hb2Q`zbO0?XNt&mOKCOr+)1-Kl>%mf8+yTqeGFJ{n*1SmBEt; z5CEAA>(%wOYGGkFN6KyG1{iL>4eD(e2?-U8(br|#?R{84#uz%)|A^m?1cq=+6p}Kf zI})_hiA2M#mYN7-_{PXH>mo|bF$@tBm@*xbnKHrCHoUbEx8n&v`M$T;S`$1FF%eWT zY(3tvC;+A>5#%PBMnu6xB*JtgN()dHATl4`do2}+#%P%v=hH;1))MA`34mj5Ton?$ zx9w0@cKhXYgwcpZtFEWcN}?s*h&VElF+5e&#t4GZTG%){zdc4Dy-%tLO@+>1@w#`v z_Yc4M%fH~$Ze9MDuYcg#>&0D}GohUA4@h=6ob3j__LckeD8%z$)HB3yy99VZG1uTy$F3bzP3E zf~2KL3=0HEPlN4nYt}QMPSeN$GnNuzBAVu>^@N0FgPb9+%Slv3E1>`(P_Eb4nM9lb z$f|%8?z%lo%`9C}+=otw2v7G27cG#%h?cfRA-HIouj`Q_sLtumtU%eiO{$}6!Hm|IvL_tp{Iq`P7I1pTGH4zyGe^Kb*^pUh@h=V!Zn|H#u|DC`4t$+EukB@!e^4`Zu z&pkr5h?cS3NZI>X+UQzmX7RQF2_lTvh-DK@WJK?)l5m~U<9Ks#nkFLILa&U%MfA2d}aNjDpVRS3|++p;JVlCm-)nYl0}BqEv_0BqtP(upOlHDm@4$V?)9 z_9O3c^I@&l$=v}dJu(6jfD(YxL5f&gMkFI*lFWsOBO~2x7ytyM5`_cl3yCg5NuuBo}zu@C>|STE1c?m+7;8f0;VdER9#%Q_el z(nX4A5OKzUL=n2VxnYv|umhyNba$&o%$=DxaoWGNjV}NIAOJ~3K~(4iFo_Ex_P%BU zAqf#HM*Z6QQ9@A`(5Ru-oXP*D`6DAk94^Jlr?Ti$Ufv5{itltVKMMiMiIWPDEqa zCYB-Ah!MjavJek5K(KXXEjrcF20@bYayspI``#N6b3*sAdEjd)qYnUZPcEgkwU(l_ z08+Yzxo=o>gn<~nyZhmAPDIfiiHsX2kDmK!cYVA&2?Ots{dy;Q@#4>{XXhV(@6Z0= zU;oDI<@oB?z9K+I8_nCTvs*}UdG{V8-&`-dTRT-cJKQ=uzw_v$kDedqwO#j!XYZaK zJR?;e-v7U!zx-og`DJhUzkfFD>gf-CuF9= zC1es(dUP{{4M~BFm;j>>oht#k+ceF6^i1Chso)k!8Ia(NXyKHwjlj$!8I>es8}G2#fFL82nl&LFqissu zOvFejY7yZHVN$rQt&}N*-kcJK9K|NFEVBxE4r=6G>1gXoripNiwp*F@W^Qer=Q#mAZ0o45*C$X# z%v$DQRv69P#ww)(N!Vaz8=etTB`gRKRkp8A?<+DAvGLVbNC@o?vjg*6EWdR~fucfHWkijCYH5SgqEiV_tlTbtv(#8O01n}Wpr1w=(G6O`I znTcaKaAGo`MP^^ujiitSVNOW+#0TGL<75_fs=!oq8odD$10{x#S`0``1_A(pbQ-qS zI*r~@2!Y(T&=T1&E}RY#n@A9CU9}WZJzZZX(SE;++`s|2o{lUkD&9LF0gEtgD*y)C zzIh}HiLI{+uzK6g=QhSv$VWB2eDKwui+28%U;1A>^?UFB@4oI+t}ai>1j(`05+KSv z9p;0L(JYw~w6^1o*>W~jDF-g{+{d3-uCL$l=fC~G`r5ypFmXZTOxL2lbq^y%k)kT$v8|?i>j6IPX3T9`o4q>`CIDuNC?hOw zgbh~Nuogt*V+4fiq(q23qQeM-5aLbaf|-blg5d^9gp?SFfJhNp>eR;yzDb8E5H`C* zxLZGEK$$0RowZ`7S(DxDEqt_L10l4|9#QKA+t&+d^Iq~)(H((%*i_WbBLV=3V5^jD zCyT^nWJgq1A{b+A74A~!zOH~MA4tGh*2YQ4F8 zg^Z=JmII5NPS=zmQj#EtXWH7Dux{=0fMnWo?v16&Mo%J`c9ReHF%Z$lYQvRvBT#{0 zDN}}z-XT+qj?wU$5C2w%C8e!nLkF9iZ~=sE#z2s$GlZLUW)Wc|GIJvIEoKyE=FAL$ zX#@t41|~jjD*nF z9%0N37Q?!hZMk45g@rJ(S+6>|4ah)7;$nS-ch#a=l4-fV288X&Uv$Hbgxdflohw1w z@a42Hr3XxPas(-wBUIVUBMcz>dYTV+e5}LP?T|de9(m>G&4=@U@sHm8^Z)q!AA0yg zXF<++x^Tq9u0HeBvs_q^%e>p~508EP<4CmI*GOz_o#uVXUDPHXPd)nRzj%lJoqzIU zf8wjZ;rWlfy|>hI0RYI*Pp39UDN@SL`pF@Mh?vtM5ilZ>@n-wYmj@1rPwFMSzoN znwO$LtSaleAQJ$DClbaO#6kc7!~mHYEJ~TZt(!%7Q%#C!TUTU0>~~1CjD;-D&vv5I+DB||wmu&D)X)3q z6UWd0jF-Re#m|5K=iUF*y%jlTsgaQG9@)leIgRUk_f*6;+ir|?WXW;g122B;LmzzZ z>APR?jc@*Qf9sbE;?s}*yD*Fh1INTV-Eu!=IwBm8C)Fw<%jqbp8!ruPWPb=MC&7!77xfYHs$IwO}!$zGZCIY~=EPZX6 zj*yvwC+6_dNuV-tV5SuAe_1A9~<{PyZD7 zhu{BG&piM58~)Ur==ALMDqQmKVgW1WiO7 zZUlk2WyFI4m*t4t%915)WS}qxKt`5Y7;a3++gl87?B@C+sjaE4T! zkfn#=(;xax>#JD<5=u-)6(#1eZZ=;8q<&f)y_Sl^VZl|vJ&{GY^+AXkgIPUMUa(91 zXk$LRh2WcqkO+sHNI`%wGaEKqnRjKH8R&StDpIf%@Yt*+5iyokrm9THzASy3&cX*_ zd+@cdfBQTB-8X#x7k$MSkJmnY-zTo%>}+n!2_bj$*|60|lu{t$>UiAM!dm;$9(>vT zg!T07(;xWY;}1PD{mmbF_w>+bKKbGI#PPZP#RKcf2$Mv{a&ubO`Rp+Is4_{F^>`|^ zAf^LF9}$)iW~0`fmb&RWw?lh)>O55~>*!!6Dy@w&mGo$RWx&V)#LY|&iOA&M3560- zm`TD%LIP7nOtp@+1yLzt9%F4Ry7`ZL>wv5(T&4^s0vluAoh|FJFRN4~AW_}mqGlZs zRSE$oZv2rKb~B>IGop7S0SFR7&k-)f;R9LRoe+p{T^6ZRQ63|iXp9ya({2~twxIEL z(M3vYM|ej5z2WQr%wPNaKfZqQ51+qzzA|dLu(&>6U5m&x&440BGDZYvnEUWCYMpxT5g|n} z0Ek40rMBe+VW=|3$P7eO-9^|~7iOhWgs81cDU;dS$Es403o@c72oW&_MtUDzRo6C3 z5fO1W2ypA8n@$x0@zM9a4I!AANH(&~SQkK)srVS+&QjdMGZ4L$dAn4QSm%l;7MAYH z1sQr<0TnWcsi=+yy|m)=!gI1-+j}c{`xoQVLHEegj*Li+}-)&;_&?4=OZ1NZk^pZ9d8m+OA*B7 z=4vXtwDExtJo#nc@O5AR?QiuKPyE5Vbtehfd#`1uf_=0i)rTda3Xk4;UvI8&YMske zF@Z{f+*C>$9=)~wZVv?Ox`byfVwp^YfB`@NeYCXjbWEtz3``J-m@&e`xfUTMWwVY! z)|X)&Qs!MXcM-OY(cVVc?{j!U_~@hxky@v9JrMw_Flld1R5KvaZ46O%b3t`50!5{C zCjun3-osrg=kQJY#oMA3kcqZ>_O@}TQd)1w6bZ_LnAVMz0Z5pa(}HPIcM+~rwWR@L zsZ(2SATrYfiKVENKDv+q<>uj-s#*XcMnvzcAiD*sL>M9U-h?F65m>7Fa6}A9z$9Q6 z4tEkpL^sa}ciZM(D$@F@THG8m5P_t&eyTdTnUfdV*;vMa{n>fiV%F|F{0cdH=-H3| z)(`*HKl${tPd@OX+e!P=dSpgO+RX<-x_fm`RL;+CC1yW0QMaCT-ami!>^jDs-7T8r zci#E&m%r{y-txo0*zUc5eg0}7j5e5=L`L5-EHe=xAS9?(B<4m)EL*9nwDsgR5LrbMNs9J$ z$#8u91HVy9$#jn#V=xj5f+I1yIY5%Ib%P8kGP<#rjr##aV`)j7aB=`9BbaPd9%h6X6_DtBq>A2M~J;TdXeOP#OjPw23diC72=lsP_KlbQ) zJbml``ZJ&UxnFa<_vpKInQ7?|!~jV`(#8sqyWL*3o^GxpbDk%21PF{U zkAzf}KDv)F&u2-{+nO6pw)8j+q3;ZkQegLDx=5~Mlu? zY1T$!Om%k8=!<7qW-SF#5CJe+YX~$=J0OTik1cK85hxKvh(*nMSeg$;oaX(=5C8%k zqapDa#zl!REy4|$#%TovDHAgJ+RIcdycBiI#PHD*uxQaTuYDP7Q&D8faAEd5T8A0GNNuKH7d z`;EWxi@$K+BZvKc52X9m)$?_lOPPcT)32|u>-l`TZo9*+$gv)8!gF^EE_qm2kv1A3Yt`ONRhHusAXKWT1QH2YcZ3M@ z?N@<{wA0PJzs15tJVqaFO~kqGx1goBHGzt1>ni~OriEMUQYs7ewE+?#1w>(W2vJ26 zMp%{;KJ(%Cc<)#Q0AlnAU{R#>;fN3cOo_-yWElv^lw*txAOvR3L{cI^GZ!JxP}bw| zilnM)WHqV)zy2#;c3Q3==OlaX`AelzcIrz9mO9ntbgFeOqBfR5I-K3{SU>Q-M_>Eu z7k$?cy;sY{;~)F=;aKWU%z6xz%`qBEibcE+z{KG)S4eapZCzEAMG}#jeGJ5OxAkU` z%C$@~@6z0CgoRGEuZ!8JrS{%8BRaE~1z>EZnnE0mHU^@EXGX8{F5Jzm&huCsAk;GV z-iaY1>O2FY`+%fzT6X6LAC{X@Iks%preUUyPMryeDXQ)<+6n+#Dl@I^q&yL`g+nH8 zP1^KfE=)cKi7@MEjqrthmb)Qt889SbWCB7)9PWq&TNVru7&CLU)yE)VW{{3J7$shTj|7x57K!8=8YS%-}k21&?- z@Ir0}fdIOVj8G{oGR&AD3A~L2thKJAGcf@~`ZP`E9vdv`zik!N53hOhcl-}BRPee=wRep~942?4I8 zBU1~tb)BcZd14x?hHWxv07UR)k%;hcK@Q5ctV~pEiOAlYFs;2ucMuX~0oriB#Hl<7 zVnRT`hFcxWN%x1M+SjJc$TYf{cgRh_CO$TDQ)J+5s3hq7|B{jTReO>A3zuqn7Owj0P8e)ABf~ICvg$fSutNMjggYyuSv!vHN}fK?IxlAXAEFf)5j>KI{eTJTn3z=5|6RK_+2h1lZmM+PaFWPCGYe zfpuBE4^o=u0~7V$!^ie!MM%f1yZgh%o!7kX=IKwq)~#kqA`*JZaqtD@a}U5^6+hrjrN^WT2|WAFL{e%t#$dH=aQ`G@bc>0-h%qF0%j z?54R=s1(n%)&Uac+|EqzBO{0r5b^O3{yGyQlKT*yMqd!9kB$I36-b06qA)jN z6;U6?2%sh0AX7xmdj^QmaylX~WDFl?XBWdqM*PR+yk&rU0uV8gw6+w{4RuzvoZ1P9 z6(CU%p<4(+`gq{J`}0Mg`j`IfxBmLi{pg?gqCHj=xWxn`;jo*p?p;~>*={*0K#nSGduA|gV1q%ikB0D_4Uzzxex0JL0q-4ts@Z$ z5>(%F0ASBH)o2-UeyasW1s*y5(4o zz@R!?3_=vCqphS;r)gc9_nsIrI&mRY2uD^Hgs`@B;Zo*lIUNz9)@o*LX{5s28yA-` z7GUx=xD+kwkxJqoq~fEEb~?P|l?uE6>#u$FyWjba*SzupMBkksGWhQ0B}1yPmR+VV z>*;KN)`#`=1PoMm*#jf@hxX{Br(b+;{M+CA&`TfQJ^6d@9`m`BB7Lj@1qmcJVIFJQ ztQEw}$dR@lZvY4g8FDpKQASK4RdGnOK?zg~Bac4Hw5>dcmfF{rnOq7p`f#a*5&P1U zG9n}qd4&2=i2xUS9J@DuNUn@ULl(b|_&Qi>E6*<$CJxm{h=GR(%>h>}zRkb#p) zMYh|!)*VrpyLa=^K_uN7MT(f4n4KAOVv^!!)3C96Hw!Q!MNE>i`LnDy22Mi6-0jcX zXk&~bf{313M1d$gOBFX?*Jh((0++%?(!+XZVo*wVOk`!V#BGMp;yx^VnhxO>ZfOoo zQYTC|3m*f8V;B)nyLnws$+XF5fypcglcfMe1_EZ-5MqxQ=ESPyg~(I5ZRvVQ?>447 zfy3HXDH0>=JR^jS0Ym}8C^0&)z{Y6;A_4$ON<_#+BqSiWPDB}5M7wp*U=cpQ^WR|C^uq-+%5$zvrPxZqMg;$a~qH-@EsGB8_z*dnuE*ZW%9o`O7aa zuP&dtt21A>hDM+IX%D~mz3=~(cjnLj#vi`&)%(Xj_%7@hv}0?_et$9g>ee@aN~u%P znh=rc7KA{;qZ@81bPqxzp|*C+7YI55L9C);eI$giAS)37&{nJ@Ez#F8Mw#dI%yfth zMle&Y$!N`#*~9Ru553ceSvO)-El6Z;3BZV+xkdRRLxce`EQlyPSUHL7G~avfsZ!=z z%F>2l>Qo~kBBtHW&D!bYW~`JE8K_k;!Q4q$Pyu5MV<89tkE1ODgvuObp3{sEyy|t| z{T<)&D?jxQzUB+gFBc5dBB1-*)^3*L&0#(V5=hfYm(vN*H8hYe?z{cuhd*@jz(fDT z_r2rx`F&44@j*d=U|R>WZ+KsiNCfXT?GLm?b0~yMgsEyqF6*L2BXTa&sjVCUoMuUa zN*s{ww6G{^#TNv?T}4J8NjOit%z(r;R?)Jp$ITNVJq!~FArm}PYYj_}A*`(GeT_^c z_ONgVU=d;FfGnz*FnkbEYfVUY`~B6`y>+>n%A}=aqK8|CG9!@t=)KoE0Z_OL6Bh=o z5FXLoL4?~2cppU(B(l-n42YBjl@Jg-JPZ<0#QNA46q!CvX9(!%EG5m_dQvWF-66K) zIv9)>nj}sL!?FkwD-b7mJ1xLeSU{K%`f`FmDe94;)njA;69EJeF>x|PGY{WhueJwf zdQS5c2QaGZUK) z(HVmUTYE8us4joOk+8HKvkOQbkAw zNDM&}h5#}OB47{*D70V$(#-{^O$*W@O$3`!5L&#DmZlX@8&Hv;fFy*35DXz?PR*(5 zJH5jh_TFo)XXqbJ^}lf5b$-7{`MQyR)0O9(?)h|A&A4X29~)BkwW8MW4xy zDQw3L0s@D~T*@3{Ac*P>+yxL?3n?R$n;`%a64Cw=g@FT;skd4kG3OLRDs_&`&J2dq zDx$a6x~|tIA~p95Krj<2l{s=^l_t#$91&8=DGef3Ron%PV&3!EkkDHbCLj)K%8dJ1 zz`pWiRXC(l=G`*gDVu67GX-v?4Xc5i#MMmq+{HG}6j;@Wup1VwHY8*Y=(d~Z;O+o1Ml+|J ztVs+J2+BAj?9+r8i1Tu#CUq`m%7iiXL@eH{6d>Z5Kx~?}{jj7EciW4|F+^5(bOdvA zMTgXP;MmL}2Xj+V2M70<6Ckv4Qh>nh+K^byVvKE`nn4KM^+TCP5zR3+cQi{~=WYbN z*AQ{aK|?_bcfIJ9&prF}KmV@RKl8C?AA0${N&u@sRKK)a@A}1IYYjaRIi={z zCeyy2JWmst3Wi$d!!F;vasAI83;*|9p1SwW)sv6>e(DxvA%?Eixv9mF(8L}0SACUQ zY8zHd6$3Rl1po{TE`Ur-%`9=0DltVwA9p)2ja?s8s-*!M0V8AX`dTZvORb2G%0vz+{Oe&JPA*L8&ns;-R zNn7pu!OW3}kVG01NYmwV=}2ORZu_=K}`?<0cn4iL1#p5 z0s*~gh|v+mq|SA@Sap$$NRa|!9Fv0sva3EXVsNJzm{>(>DQ2?gb!wAZCXbwB?*Pnf ztr-G=Au@)*7=diw?l=Y97jUApgOqu?*S>C4~!+i!i@t6pjpHtSoCT;|rw zoI{9GO?`y<5_?2s@^rqRgR54KcV# zY0`Ba$ApNYEkKyd?09i&(Sf#K=c%KX5JeMuuzCD8849V1lQ$&m)ju6N4vHL#ZbAR#Y z{@vgI=CA$cuN+FtQ=vpY8y*}T#Q^hqJUl)?gc~=m$9>i@4JiK9A3{m^P)iVi8p1{TPrXpEhLaUj?O5}Z_;f+M&CTWy>eD1e)bHL1Su zv4h9h)w!Bk9FoQ?v_(l zFa$Rz;#k`bjRKPb5+kT00uYJ1REdevA@-flb-h08`UM9v^IA&kx|}*iCzT@31R^zX zM1uV}APO<&TE;zbk&r?cttmz%jOJ=0Dw1s730zfiPG3D5I(iEBY*K=mF zCao2quviYN)~15be(?AA)*N&f(+~nN*LkE6h+-?X?}i3Kh=d?b+&sicDed2-%!Gj4 z%)KFn$dPiIHXAchwG;v})>$|(W7uuaLk=lsq=13Jd{1sG)%wK}WLlT)i$DHT|L}L- z{kfm_+Q0jy11>L}UF-;9==xezWOsCO1)!-qp>=(KusYg|7a)Fde&gv!H*fsT@BOT= z`o8PW{CBy10o>i9NsIKrm533fs-P>-y!v!MNLab&eilYE?J{VjOqt#qtE1w9Rdr_cmxX z$*HHnQtLR6sXHK4Ek(V>5Rnszj%6NJhc1-}5um9sQ0RI9G;I!hCAPIDgcJgcR5y-8 zF57LlSa4v1FqcA%(kdY!qcs?g4y4xcY@LS;A*7VGHg}!pIfZ1;7q$a{13{C<`$3-r zf~pu27_gLG*9{`3ZcONE0zNF4sw$k?(!BDlOt_vZGDwx%|;Qrr!AOqb0BDY%i zc*6aupvVYFjBY5k5TU73NJs%drD=@p=ET^VFcRh5YKcIU^Ipp+rm4&GZj@GI3K$a_ zQXmx(RWfL8CgK>gS{2cl0z)vw7-61TGarUUt#i<}xO{cE^0MFjx$pe7pZ?MNKJF+C ztLTj#s=eC?jA+s$-%a8S!Utd;@zY&|kJW>DwlvKvpgPds+x zJAUxzKmN17?)t-jVs(>p&lp;(=vrDu)O}OA?#9#~I03k%#2u5z&MAV5ip9Q<6y~`C zIK{U z(0R-QfoMYn3T__DZk-psjxF}7HVdiW55=|Zma(rU0l27z9Oh}$q>)N90t7JYhZO?M zbz)?#1tSCa*}5Dqh$u|zF%UrrO^a4^BV zoNOA4n{q%ehLOPyfgD`T`sLE4mQr&}Rm4O>il&aHj+k<+^Q?x%)OB4cbE_?eXh=j> z%N#=RjVF=k&8-`q`ErUbq98k+HrXBzhNu=$6VFW4_Yol0IF*u0zPX;>~yfjN}AO`N-aaDdjv%=(9Kd8S;w|JQ%- z;qmsvyIW6qJa8jNO|h4{8|RDV>Pl_1dJCLli~)JGIS-sLu!y7-YncHlhg4flF->)5 zBn$!3O+18vxPS7O7^0ZQ5D~Df_D3LecXIx)j`JNA8-x|~8nv}%ZL zqL_DQ=g&QH{y+ct&%WZ*zTws*e>knjuvqN3Vyc?E4C26uG0JXte8=70m3wyU+Q$P4 zExL(TtrEGbtr>a*YF1_1HIpGPoViKO++$#qHkPUHmJDdbtrq5xQ-X-&IQD&BU);QO z`HosEQyRCEYZ+E6W)$_-YR4=@UFyf(7RiacRS*?xWpQvME~eH*`mRUtalNJ#Q|!Ag z&*Ki#qKotTR^>dcpL?7FFt`QQ;;v$5Auxvy!BtA+AkfMvNPEdO&TU={M=ERQe%kB+ zCC3E@HYuu(L>as)_&(JfW9GG$3Y+_2HLXKjps)U4$uJ(LZi6bJGT4D@knuy$qQFULpq_&uQz+hU?QFOlJMX!1EBX9eGZ~Ti#E4*~~3u5Hc)6)<;5WxT{*mR~C zVu-toT?jEo0tZO>+DZO{-+7Ggd+jg2`EBs*2cJE?k<-#m<~qB>aJZ0JnaBZKs|XJ6 zx^K~eGxIo3A#e&&_HU(t9OLtS?GVH&iiu;_EmY>Y7Dj$vFiEVnx+_H{WJe$)HBo8! z+=t#_xaadzWF9wLN}cKlc@8j(L2M^C{mU(lyI%?|6 zei)}sYbIu0PJQ34&(C;>D;{U88 z-4qEzEQj~~`S19auYdpBe>EQtXV)#F8Skm1txr*fTa!f}D+_bn?n)qU7#0CtwU)X% z=v=tUJme)Hn9I8#V98?O94$B&_#tg*ERn*+G% zz5+-Ta+jM3Aziul038IKFb;=cB06*EP@7>hPOJ`;+znhPc3=+sU;rv&a91<|bTH9Y zwr9_pc$u~i7E-9ioOwvuU8}a>D5^jNX0k`E0&u`C_f^`oUZ=hfDepF$$ZX&_#U>I$ z3L!aYE6rV*$=vExLrQ%=Ad%V}FwC`bfMz!2YbW=;1du`7UNsA<0D%Glxiuu397w&f zM~6u65QFQ4j)v$&Oc`Ll@yG{do~ucYgib}KML($d_Tqf8S^*Lu5n`(?b}^=I+-(>U zIiQh2F@sAldiCv_Prm8vUa{(MI6mIaC2{JP%Q{!H7Gh)~3{WnnZna8d@;L?># z9ZkRU`_I1gb$|VbfA(LUedNE*4Ir-uij|#!L}pXnrz(Lka58r@4T)=MfteUAW+;cWH zbzN{a^q8WlOKXGx?oCCjI+(lVoPBSl25hwvQ_6$Z*^mRXSmnLbGv(d-3=l(%3XmxR zh;y9h-7pMVrpx!eV77Zd`Joqo%E_}2e&U@s&UH03Raguwv**)1u6npST#xG*lI%9e zm#?mG-CmFDzxPjn_s*Ao`m>L`!%Bk?hP+T~bJ_K&o3)kg+%JcSQi|lmE4McK`LFwP zU-}v|!}a7Zy(dfs*V|?UF@!`8>`0+(Eq7fGFqT?H7CmpL*5wpaRGFF6*{x~lxF6!K zC>qYtP8Ne&t*u=;UTt;-fTnQ{07KW`KA$kaa@Eh{)DZhVm02AiML;Kl1?|(;CU9XhD!o^%{j zXHrGA@0UpIsI4{?-BVwPm~#w7wM<$i_kEdmtyYQ<uyMXO~(pJ^0Y>f`0smzRSz{ ze5dRenO11h6fZUxDR*~VIjs2(0ButV5eX_)$|-E;X?c7=6#Lk#?@lfqyy5Hq&hD9y z3P`^?hB`a)G*@O$AylcRwrBX5Dud5eI0Tc*%*YhOQtO#%yL{iLv^M>tzwxqwbARQo z?ZsJ&$&nZ%1&tK7wPu2VO&nYiY&Xf};{(_BtG{{U>;J(IeCv09=fz|1-tKn&>R1{= zn+zEk!By(MA{@*VfPt}xtV(4M7^h+MVi#+?lDAk zg3L&yE{+fwkT6g*(-64Tl2VS{fZ(8}WT1rN2dm4qo?g0q+4>iK)~i1C#rfF>Kk4N3 zZ0uqeDaRPL+wF3(%!?t3iL_d4j6o+>SlQj;|Fb7^~ z^UPz9xWaN6tW*xFI>#8B2w9*2241VrW%`rf{FV2;^QqUr;;BUsv(ZGxim1+j-u&q& zA@@)!3<-*YH6(BfAOckY6#5Q^4v)9~OTX|FU-6CKethZLW^=|l5)fm6K8Q3`&s|s@ zq;b35j+=g1A+fodB1dEe^q?+Htn0c`$69Nj2UP*X)OEIRTuhS@xmjSYwK#J(EcWo_ z)MX@$-C^&`zxIY#oPG5AUwke4#vI_>pzm|7I<6(I_}QDVos=1t8Hn-rZ4VvnwvZF_ z89IjD3^9ktpOKRn{<$yxk}tUZ=ttW$*D`m*vd_WX9bvURsI5t>u9j1-T4Ur86Xo1W zZKCWGrA454^NDv~y6=_W_y4^A+rR9g^1jDD{u5pT1{d2ibx&!bTAN7M_hwjid-=*8 zL_X{`#H3BW{0onN@_WAfju+nbWnc2ufFYt| zsS^Q}aZ52eG_D018~~b`tC7ok_JeRWPZ@I1Iz){5+=^bFQ^=h+vL4AAh%u zBgTO0h7L?RONa~<7)+!|t+8K7t3=rKL#xtcM&iAVF!g<_mCz}7i0HL$)@MwKVjzc< zQz^yW1EIR_(eWvEh_21Y(hde)-2bWH^2WdU^gI9Mr+wy~&pkJ-VzNX464FxVZ9w7} zyKdO5&lZbiZ8IU(ZQ1S4zvo~4@yUG;KJmWynC^z-JEN6(nrdsR(&sD=47g8vY0=a2sVPrl}pt~_^h92T7za7v4zKR7r%y>Y8)+ius2aI!ku?6!a?3X6j! zna)Lyk1w6yey+;In1Eop=)ugK^3b)ZNt-vjHsrh;r{kl8n90x`xRx>u3sc>U+m!lN zs)@uLFn|eLts&y^mCH{*bCX0;w^|(!>(dKBE;26rgSkvi>oRvvOs(A7g`fDhkI$P& zcjxQHVlg!_YY51MZq7`5iRj|mU2lKu|NCEl;ium9&X3%A1oyqe5Y zyQE?ckh|mt29Q&3O#pPe-NnEFPJkiz2-a$CRYGP)0!Km@ab#>FOd-VRQd_MM2#Er4 z%-!4?BZwk#|IJ_d8S``JuY3URc)`hbjv-g-4xWAHxsxk*iqFBJZZASNn7NiRBb*!_ z6rHKTm8*BH$L)N+U7j30@$e(>zkc-F@40#L^q;k9vX}y>sUooff=P_sG*1X_wghEs<b>s_DO;czqMGePoGzk@l&H_cG>su8K$2Q> z&O||^0jNpazdpto6d-V94)Z*6h;CBK6k_{_ubJ+3)(QFFY=rgW1MrmKOuKiej6$%cCVUE2WLQ&Fb)Q zxw?FM_ObP|+i(A=x4h_~KY#Y{pQ%lGakSgrY-$IKWu0e7%z1&RYGAFlTDl>El}q=% z`WwFHtC#be@4M@TyRCZWF5>B}+fAJVYOO~nm$k`a7^Z2S%CzYEH1s;PDs|R6q&_k) z7X9(b$#c(L-)y%jrS;}?u~;#8nfYRKk-E;!PmYd&eSN-(F&oD42eRy%W z+Iy%u^T3t|A9wBE(t?BZip9 zX+(f7^-d0koTG|?OKZYmkLVRMRRaLaDH$NTihy}bDW{MX6+M{D=B`bs@0n<^I_QsI zG~W8dfBnz?(Qp6iZ>#yWyW#$q-M_tlvwGle2%($KZZ%^dOo>Gb0*0737Z?390)z-L zlARnce*JeI|IQ!z<yaXW2>ZeiY}Nz5HH&(kDzCIaL{=rQ)D>ZX8* zfn$iZw!plP=wgasdp3jyj2=)`naD)yJmIZJ|3p9;K~20`NJ(0e*7i%jkb5m zDCU$8maDVz90iyH0y;?Oq7<8DS`LSE*|u3O-+9O8e8U7~9>FQ(4nQx?*NelYiJNI) z?qWC27hQiKHh$o}H-GZi-hX(wy7}~@6hq2+|5roi6rc2mkF)|HDi7ToaiAa$58{?)v3HNPK#F6M?{hQWt|0(8cLR%As2voIU;I2X69j zy|+I3!FLB#Bz2XLVoF1kYOTc3moh>K0U1!G3dX=3MHN(HO#7;ggYTKbt+u@xQcY9J z?rMg60=%i1n~4BG92U@~rj27?TFJRPx%{eh+}F+dn}6jee(7hve--r=uX^##TNe;I zLa0+)trp!tx6gMct6`d@wmHP;;-yTeni3zZ`j0+-o$q|rkNx~Rx6k|uhq&8rK==Al z71fjyA~f|Fn24UQJR?oxmMAzX5%tTZjZ-mYMxD#ioezZ7;dgw|-Q31|UV861mATBT z#etfe=-ehyJUTeUOxw*mB%Zc2hj8Wc!GHhbM;^Z!e)j{dTs*QqeY#bPAyAGnb|xw^ zyJ1RQ(OFxdR7s6Bot6%mEP1E#}BfsBY#VWpMZXlK?`$SRw~i zakC;7q(u(Edu*uhaq=Rf;J42&?z{iqon8AYuYc$>ADD)tLo+19<-w}!bKmC};=$^0 z+HG_0=Q4#D!RoZzZJyqJ&riSk{#Sm{^+*4Zz+xAx&Zafx%tRq&j@evQ1knPs0T73* zF|B%9-2Wqg|64!#FWeiWOt-f~=VjDp5}5XO4|HP2HZ^n5D&{h7cVM2=B8HH2#vHe| z&%1sx*WGT~td<89V%@E0mGg^jpZgF8%E=wZ-S)=y8|b1ADJ4#6pAia-DfLB+2#znE zY%VsX)Ya-hOkxUZs%|g1|NgV*o^=8@Lo`m2!yr26$TyxFKkW@)d-clQ>l=?D$3V=; z7;q1kq!5^q5FDJOIMQ}~Yrgd~)b)d}{i>@EzWQzd{ul1Pc2Gm57S$^=&tu`h%V9x* zkB(O%Z&#=11T8wzu7L=W^&`2MMDwfrx=A1`_r#+yhtTy+B*sWFfJ00P9jny78&pJ0 zDNyYC5Xjt_n20e106+-+;^6eg6MZ`VtT%k?zyF0Fp3g6?UA`Q_Fvj{k?gEiX!HH6t zb^r)5F8dB3-gVdEvyYzs!CT+_C13mZM4!=009j>HHCH2aFmBS&HKbJMQB_@KFQ%$< z1N2ggn5C5de_<6cFglouiZBNbi77ZDfQpKnGX(>v+pPlfxBacJL2X+0M^4*Z)uikD znDd_Es_y%_(NxgE9b1{jjG3lsr_#UxfFj3f-l+&703xZS#ge-mV~-9ZWuGx&8mz64 zE_I8mulnN8c-dus@{?bEQl=2li8-a={PtF4k$Irg=C;rVn zul|Ck-}jan34u#3CX#Z8&T0+70nkJv5poPUAp*G1Wp{Y#lEKlx{Hg!_fqQ-2lx}fI zsXsnBSq;n6TepTq*Y`_p1rQI9kH&GGx-M}FU0$t@OwEG@&WWSQyxCslVK_d%Vo1|& z6lq6CM@?-Sx0kP6LcnP=0mJ31S5nuhY7?Wt;O-9na+s#6&2`*vDWs$0V`5%koTl9O z!wOWGXtN%*w5xaAox2>v5OSW!aY#$i>0^&?@4Ekik9*mxPH#QeFOJ-78jEYgz=24l zs;HT`7?8UY$G~w>cQ;ONe(d94^LbA_`Sg3=`FmIIIMe_+F1i?r3D9a4sTBdoX=-M} za;4S|4lb|Hwmoh?^!PKM`^Dcdo<5;~fphZ_965wq+a8`Pt(h5t17hw52i)gj+uXEP zk&47+Dj{H`q~HL67~I`e_PWa$lQ~G6+FYh-bVN5p5OZkFV+c$E+{cS&r}Yz7x3B!- zZ{414|Mcy@eaDOLEy%%H47$GShko2_TWOp1Zm!dAvpG4r6qr+&Rek7(a(=O^^>v^B zRdR6~(A+8PoiWSZW!%lkNJk;qclQeZxQa ztzZ5>$LZF|9e3~6XNL!iCJYlsK!H6iwuIm>B-s%MAF$7{Z*T5llo%=qj zZ_FejNEC?7Eu^$OIx!IbU;pmk7Io1Y2r(pP1H?MlI_;VnI_(Fu)@sVV6Q-17N=OLq zfn!d|fha~I+$XmXNt!qsy0x*mr~rf%7mEXgS!z+Ocf9oVKlXoo_n-XwuYJL%9iCqx zh77@DPW{j`Q4<>$Jvli@7gI>t%Y5_6vp0V4k3RGn-+1#wZ-tmZEcHW*Ddygq7^)jU zE2@S}VBSPr%^ls05UD#j-^{=KFMj-UKkJ3-5hFD)oTgoAlc*8VjnkXbN(>z^xRI$I z9vycD+d<@*X+pl5&n5F4pT-W&j@!Ryn5i=Hlq+L|wW* zmvJ&FA@`{phDEM*F4K10T--i8jVS_!zVA(Bnx{n{hoj?hy%jCHX*|1i+euoRRe0X+ z4lf_S>eJq^J%46AUz;oMm-uR`PDE}-Kn9+2pS!N9kvXRo`P63VuRicQ|KaEEz56iG zFl{y!EO2VIr4#^2rRBaG7OUOGMUx6%Q_8tZANcU2U+~6nx^{eLk!_3}5{Hzm3b;tE zO{PFhj2yFp?Q6Q(VQ!<8-|b`Q?B9i~sdM`yuu*hI)FoCI(`ZTKd&8L^?b?BJj<{nwU1* zbsMJ`1IH}$t%o0Z{4c-pJ7jkqDG~((pvYa<5k&$LwN^(a-lOGW>dLfssR+1_7^T!P zvAJ*{KucY>yY=MB@e4lbbH4i5e*Pz>^IHcemk*9Exrs`V(sI8X$F;dHR+p`A*YgOT zWFAx3U%jjS=D+*Bm%sM2A9(0ho7<0ZT;y)qamOio6G2y1(}wQSgotto)3oEj9Fv-g zmR3p%fjCxc;2uIoB9ZFufr6PzEpD*q$cGqG;LUcEQ%|7CDK8HXL(Z*^d!Qw`b<0&w zIdomu^?(#Yns%Fcx1F||S{oQ(;E?0onp+D5YQ{08lvRWnbJrUJhZJ&*0l{#6ekNNk z+wlbtz3w-D<2QcpAHV7IJ}aDW7=UsY>e$LSpWZsHa}5DbZ`=%t+iWh9yY%!Er+@96 z|F^&JMc;n=`g_uFIP?SMu9jIuRfThAj)Z8OV@hU!R-a>v2dkshrK8nJ$h164FMiSDV;}hd z1S6!?@ln6%Lx|NRq#RNV9Ab7h#dluU`lA(%Q7$j?=D*AjiHt$X$;VK#T$F zG>zN!#rm8>Tpb($f~od>c5N=QIzCa=xs)~*DUD-{F%UB|n=1i2x|(AQ%hds=XozY8 ziqkxmQi!!_UJO^ypZ?I0_<>h``t6$=qIk4g%2dl#V$5YT&GWq5Tx>Vn@s4?v| z((^Qr#2hF^5Nj5O<@KlDefM2={=hH1%_-0Gt?k7b2PjQqzZjN1#dP)BwE((0xV#)z zF=Ll_nx-zFyyn&XZEyIzU6IT8e4#HF+~?ZR8O5zNtBrtj zzYM8&H!=`uqN*ZDVgKbLYv2?Vkx&)nL^A9Q&-=qDkHUd zHqCwq)mAQFzU$WQTeZrt=nhU!h(pSC!udn*dg{|(`-z|V_1}B* ziTCF3Tj8Uxr-x4xJc zi|6{jJHB+OIZd0nU*e^sRTWJXRKjMQpS|&z!O#s$8OP>|?m6|UT5I9J{bJc_BOs3q zZn2Ldrsh*Ad7uw`_(Qi&L2SnDc6o3#jcvTSPBHDaGcv9Y55{Q(6LY_Ga(SB8;vo)v za&Y^%e(7gE=S#oo_RDAw*3K1ozJk2Axa3H2MO|yeDxQMFN?b$X)5RA7^AH8}h zpd$o|T?!$>Ui@Cv+0u{zq>DVwb4Zy2p-JwCyD!s!{x?7Rs@MIeE@R&%Q7ly;APQ-# zCN1W{UED2p2`B(y?w;3Ia?09TCIpA3bt*daX}8@V`BU$I&wVd{^*4O~zj@QQeAxpp zdbU4U9t?-OvRyy5J-m9?G*>IrW*kB4;lZV`jok5O2Y26n$15+M{>sn(q+k904=x54 z$B+){e1;qYvICHlh!PMHn1g~fcXFnbnYq;>2(4A35HOgCsl^nSu$rNxxx}GswKS2w z&&=FftzLp7gpkZM#$cw?#W^yEn9yvlV@i>77rQ*qJFhVGOV8H zwWyc`CQ2c6Sxal$Ld=Yzi8!=_qZ(EZeEz4t_)}jZS1%vzs*0R?H>|hYgTtjLbX~t( zWoG>UXnNCs-Lk5_cZ@OHwbs+^y-$CtP89_RD2OO13fQ1VG#X7}ymkqAO*D#KKG!DA zHAcO;F`6ieCefG}jiSZ|b0cCyKtVJ%6rGe*Lp7&P)#-Ng?B{t_GutupVsrn5`C0SD zTx0xx-*2tpyG{vw;QdcM?@IfP|Nei)_01f6E_&vgF%@Ghh@(bvEisg)@qpCtb}a0T zt|=!jM9ykax=quGnC9m7kAjB2-{exe<=iPA*IUADjI?bxTQ=)Ehj(utl?q(p;>B*( z&iqWTk2m?WXO@^U7nrr$dKE)#&Rrdjj=F<$%3&Bel{A|*#x|{iu^$xbtJm(EEoMU~ zC`mY-KRDlByL9h!KF-hDn5Hz@+W2Ye5e0;65!ZNu?0u(QH%-$$&p&wogO5LZ z{UcW|T)6%01~I=YL@q2$GEF;(96$I8vDa^raGnL%B?@6w0g^VAQ0_qY7x zH~pvMN8Ul46|^x9pp9{kl#aVmfV4H%Sa6nWt;|w#C`l^?fQpQ<1^|HT86ayKdIU0E zBf`Lns2ZXYiy$j)Q<$`KF6XS2Ms3o#)6QAnlw3-V#yaIZmy|=~Dr6{^QgSBJ#yjmB zE`=*0Vy+yfel}lr&Zbh;*<4MbF>ahj6tYHz60hF>x_|PIzBL{``H7!%F^C3&#knh* zM6ygV5{sx%P|Zx9X@-~TKI==ewh1w^XAj4l!|@7D}L5g&QrfztoDsCfK=9z6Ys-fZ>vXKl)p0tRyGto%GGtvdje~F7 z*4VQoWJwtmqcTdnsb5>N0_}U>{d>>31V=Y^)-M_O;yII2J>9J5%LDJ+I0b`e5TOdF zuibOsUq1dJ4ye2aD!>1hAOE_4_#g1bb**g70nSXd%v?&YRcXzYhbc6jR2C(S+6XB} zK9-zgKoLd600I@D8gFG1U@4p#fSA2^A_#)M>rfky7>6lyt|bwoCF=IirIeL$je~#S zlivPwzrh8Hu(!#!OY52`gr=Q!ZIfb1HTNgmrfFFKRLv!m_QT03X^eSS$MPFL|Gi)F z)&DmfK54q8trA0wVI*Z|-JB6?N)cW>)AhdL60L9l z$}3m@?Pve4ea>gU=E)ELp=&QX*NTD^0++0v_r8lcj8kuoLuE)9mBLbt)+rZl zEg+zPA|;4~;Cv@sftgdx7@>;R2CY?6Ypq6GJ@#7wC5?v^m2cBDQo@{TDOr_##xmC0 zdh6l;ublxR)EcJV`PLX;D{HFD-KpQs=>yzW~!@u}P^TqkcA9|0sGu_O% zn5LWMF$xz$F2@*T)XF&1lpJdnmTb`G8VC{b%pbxjq?*I=@g0M(-R_Rpcgb3=S*fXC z=izkxqQCuZpYb=pWB=eeP#Nl&(-1-*#x)Y2b)g&?Qgdhy3SgFSaBvQ^-|pbR&xTm~ zJYBkU3AiR*Z#{cQxGJXrz-b-BPR*{J?DDQ>mVEos_a)KJh+;_qoU3b^k0E?D)jay9 zv37p>@+W@zxBRCseEq-pl^=b>ga7llA9(3Y_S<{6n^Ojg365@kls@@06Wg&L?U`bj zz&AdHDTdHAZ7oHJfvZ$ZaXy|MegD7uYDNLHkTEncBs--C1)?7eg z6+xCb^%>Fmne}cOw=4`<1R&N41xRZo$hi<87$txrl^Fmejid3d)F`#2T60duwK0Xp z&noaV1cvsp_x-Q??}2u9@pQVy!pG|)t+XN4Z?|GpjhT$qXh3IWW-aM7g*@v>#LX^V zyZX#K-tx;|^$kCi^CZ1zC~fP7H3-EL8JG)8t;V?&W16CInrcN-0GLw{Mj%kAr#>`o z8&ZlXS>sHzzuD|^djH@1fj9qOU;M{^@qwqF`@CzX+Y_UWGLRvSV=%@yzFVKJ)i5CW z*{s{Gdu!ZheHwiC*L?XqpWNPi|4W~}^^`TOa5c6C&nd?3c11WrM zRtMkxy#1oFn}wlELtP+~B~uu{rdkM+deULL#z<}2lB z;TOJJoE+ZL#^#})t!9mHPPg0TVs5nBY<7o-H@JXn_BkijnU*z*0E&f5-YR2DDk51j zbEzfPIG&syJ|i_ATzuXezW;4k?)l#T_T&H0OFs2pnNn3FS9s)u?|to;eN&^*x0*!} zGglB1t#vKTCC4cMDc|{0v)2^}{r&^L{h$BK1vRMV!s+q!yyrf*-|VJo!z#7G^FCGB-n!wHS>Jy8p%4A#i(hj8&4+KhMi&4yRBOfpVD*$jV|}fa ziHJm@Mr|6cYppSjswnR}5C9ZZ6myg*hB(<~=Gs|JL4;C{HD{%XOV!rLAs~uY21qYf zdm5V7{-#S%^Xv1ywe0J z7H#V4@`XQp=k=3kAHMS3SFLZnSNnq;leQE?FwPsLm9-&+!o?_snrf+Jyi!OgC5vfQ zo>Gdjn=Mj~QpH=#QjzT3wZe&ZM?duD2jBSD@82CBoxixKxB#u<7-KjUVyMuybER~F z3Jj!Fb~sei3vT9bl>foV(tfPlt&QiR0JHK$1tY180hE}>MY z3JDQ}SxWGYm5OL}E=giB#wv|Du(lc%an7jDxGkv1DU>)NK*t#LQf3 zDN;Z7O@)w1X=Ai&jk9CF3H>(o!)~_;V! zz2s7i;)&G9{*|C2XvwX%?R@DP0|M=8e*DDIXMFw_zv8cc$L`5L&oyx+fs)IVLor0I z?W9&_5x_9*&WtWbH#O%9IqY^N1P~G6oQIf3t(i#rlS9rjW=qngarfB6H$LtKFZ%AE zyFTB4{`C*PZ5r=1?S8e`w5|%e`5v>YfJ&QYF<+cJXXcBhv5Hg?kt_h_=*})yjz!(P zd24sN1*xuSYD_ifl5-gPqq|2eIA1QO1dD@n+W49UgqU+F0$@t1X&iG7IaL63)(Mv! zch)s_esQyX_SRqi;cNfi55DeO{@HsUyn!&zeRpsHpL*myP(*i2A`K#h%4!vJ#xpWf z4n}KlAWlPxsfrB!;QjJFZ~vvml2I?LwNKr6db2y#P3smNkV52CA_460EnRCE@!-Pw z{r!EwdhTG~H(fJ7KcB(>_wzqtn@h?$0Cv`EAWen|q!dRcgsIl*j4@=DaUxPnAt2Yx zm9;U45XTt%;dBG3kkRdOUmL?E5fXDLrOcKq-z}TjQdw)PQr0SCl%f>!?sOeOpSIJm zU3bkrfBdI^vfb20H@ki7_Owe{(QLLLMN=6#7mh$kyJ3q68fm-P5vgekK!92Uh-u6N zm*MTd_8*nAF;b;Oq*4(UMuc2SDM=9;rE96RRsxY6^E9QDK|zTHNs-bajyV-hF{i+# z)I7zM0N|-7A0&hS>dg;6b2|*fR8u@&-$E*itR`i&B26JpDU4wp0C46Us`(4=bszoE z|NQk|_^%i5eR<5AuE;oyq>Qm{wpd~LW*kv zAZs0*srIziMkBzu9hI)L`D!+wLz3-qeD#6P{h6Qp>Fa;~d!P4t&w1>bwL&o3td(hHyWwcq!an@@a@%V?0`Oty}yLh0imh?SX3wMMn= z0*Q<vbNsYmVl&8D6Lj-`ox1@`3>Lu z1z+*?cTRW1>G8ai+c%FxW$RoFJt5?pQye?jSkg_~%vSr&Vos_dwBA?|X!p*I89ws< z4;-9_SQ6sU^hODbZZP!3|>384yn=iNrHZ`pF7JfD#dA#zRYpr+f zd_iqDmVTVpVVnvV0h!I(JjIXgv^bTLOW2*;gLnM*Um(-@wozh5WfWM<_Y@gI$Wtge zYh#r*L|6f|x2|p5c1C~#)i~|FX`7kRimZ0Fu}$mz+`ATPvG)A4kAG-!aQWN*)h|Es zP`}wdt3XR0jn&533Q(#bqA^};tu^Ul4ASc0{AYi9_iw-dn~sjh`=9%wO3?V3)XKHQ z5LhZ1?OfA#ZPT>ETw*xW%$1l@il`7!2^mt>ww*TiOwGc=0!X961cHnW{4WeHHsVz(QP57(=M zWg4f)9=Y|>SAG6>{JTHe-FP(Yt}{0=vqrJbHO5roIQA(FyUixW$iT6LX&gWh3@Ymc zYE2Q4NGT9)TU)D)VQ|KdVH~G`sNyu8uGc9SO-d^}b9e@j8IQL{*~T|YnOah zOHJMxU(3|Av%P~QN4|OM=~6N6NLf{5eb;Zlb^p@6O1PFpXXZT+Qdp%pV@O+JL505S zl(7K9r3{bxbBrbIt z`f(Vx!%j#k<(RO<8m4WGTVZ8Qu~yxF;(afF&6mFRE5GsZ*|NWA0I7=Pm}+IGmG|B` zZ=G#h-v>@pu4sUod(YK>`CWgf!c{?>hLK2Xq%)s76b@6r?RUG)8kwzakW4#Yc;9M6 zEtoj~03ZNKL_t)e5fq6?O^A3Fc#dHTVc?o;6{!UgV+biF6rf>yT2kbaOJd35{d|6K z&Nhtz7~d&lkyMER3Zo)zz4HxliPI3qk#nj!13;X{Vcf=X$T8*^xlBwDC#JkH&9!g+ z`+xoAS28Rw)}ll!<$W$Hq<)$P$=uCmpfRRwy`yP-=#j&J_S1i${lz;sKCHDPL=n}_ z_pCC;IOAL9s;y-rr8S~xz+t-$+bsw==d{)$9MagYPr_~oC81JAtKH^qQQ#y!@fWwQ ze%z(M|Ns8(_V}q`^NdxqG;N{wDWx(+AqFn4odId3bvs)qBx~$!zBJC20%;t9lrUGp zTB_@G+pV7Sz;m0X9mbut&b1BzN{-GpSo3bzmn@(JYC&i9Y#D1BhCYO`?YeHUtXy)g zLK=Whk55idH>63pC~ZZXk~2$%{t&iLeb-OD|K_CYG_)*Zu-&3*x;CbS2-aDJ$~P^N z3`1{yD>R+;UsLb@#sLu!rKE?x2}K#*Eg+>1L0T9?N@|mwfHX>o$WU-}3!_H}qZ=7g zqlXMehd4r{nZS47A3p!Vd7N|ZbME_kUDxx9M<3~{+%(+o;%{G>W?^=#y$u(^@v+63 z`Fmml!Y+4+vj{VR8|uR@wo0TrnTg3rZ#6HRU3+y$_@df`S0aMdlqR~=y^9v}Ny|BL^;J1ldiTj{?KQNmaO(>C~1H^g&&B|})~M}58( z4(;J+s2+-8M7GaT>zdYDi3wVA>%qmJM-~M4+4{1F=-4-SKbnx&r2e$?K|68)^QRqi z?Ap`y+8M{1lBOLa#gX~Ow*q*TXi;n_$Vz;P=oMPKog)5LmytyQ1!u zQY^xc@A~@y5aZzdeT0|VF8GeIBbB5~yzsMF`_~14kz0ujVmWhxTVn0ohEo3~r!|7l zJ!`>CnPJmjXzhE5$H4m@LmW<|$qEyMkHFFCu^<1*c|!*&+pEd@R2U(=J&gQ9qZ<+D zHroXEuYn}V*3eZ7K3#DM02FDJlyR_rPJ$t1P&1C6XnGf#Qpk$DDD8rxkoGpzp*e+v zy0}|7Mg|3e3(}w;1L>oZ^YLFlGvMmhm_*lRjcC(gk~2!}B=7<*%d4Biw^E3sdk>S^ ze%269@?dG-KGs;$J3GM@EqH)9`~8~!)Sf1N1|i}qFfNL_PoI9$sJU34?%3a8kr(>LBxnKw1p!4MLuarwT79T zBDTMcwh|fq|7QA@cTP^$*;m(P9(kOH?2c2Ofw5C;;p@^V1%#JNEI&Ltl?VRBGE%mL zb@cPM-j}PU{+TN)E9@?f-g`pRH~k}p21*O1d%^32mMT2=M1ao|2oZ{4L0*rBOycHV z4MTI0d;FuXk%PyO#*pZ;h*)n+hIraM(Xtk$|8`tzt4?z+y;odJZrG^m`zq+8p8!QM zj1HPdSf#08@g;g)|uh&Awuqu*P)G0?;mw8X6H!a;u`A0V3od#Y@`(n2~{A*&MLk%|D2sJDuv{yFgAaup_$lo7#g{tK%9hi zk10**jUk8Q7foX6f>?)JbcU-L_hN1hmSlLgJ@9Oa&b2!k8p%BGYzWy70uX(Df;NCc zeUOJ%*`@Lz-m~`Am;KNcMEwTN?p2HDjka$HSuTwu8nbTGrlcbRKuEUoA*x$GC8=Z; ztV63kJ)D@%%0L@UEgfGl`3iB*bU+^C+cCRi#A0>>0_BMrc!l0|xlWfA!`c(8YpZ1D zQY6n0Y33qb1hns!zu52p#^-^vosh^XKJC7)BYFd5J*`}I)qV=c!jgO5$ZdOSvvB0a z!hcy!Uz6|{=%nT>0a$U(_EZ--E0O~xvS#(B#3D^&vmEU?P==tU)hGtUu1on^>n7-S zPT0X-L5p{Q#KMJBN8Rri2AUay9xEL7CGrrf~Y$M5A>h0O=>35N5JH#t3ECS&Gm$OD>^v(s9 zqthi^7-X4Rq_;pu?ms=BC3JljG6jnjAjXCCSes8#J$>_s86#mm8zGw{P2bzO=Y#Ze zf}-UJy*$$-XPPV?R&`=936f1VZuqEAawywimSG?eQn!ozT<$# zt6bKiF(pZKW0b|h3ih|1joQ?71&K{G2E~=`8lVS^B~X4IO1ZiYLU8m!HG6}O2X-gCBi(aK% zmFj;?&9U{B?yl|b23@T81*~#3?NCLm{+WgyDrIH7ihGGHmz(n!oRO1_$%mD)Vjfu@ z#)*Ci49<$hv>|iCeGbKek;MgIZRqYw-dJr-2KPA(LR5MkS{_mhj_Qj!tq@&XtoIEy z|KZgF3>f*|8k;Urz|^#+Hw5(9wGhCV38JdpkR^BJi>H@=b3=9Dd+bM+$FJw>2Ol`| zX;Y4$oU?V0li{WQ+CSV1mH9%u(jj4snIJjx08TBN_#+yq>?c z@PPKDMe~o`amR_l_8yaD{g1mzt?sVd74GQc_Dl(!TOp{t(w5;l?g=pZajdAJ(+?dB zN}-F}Fy~`ql}VQxNKVIVUZ$<8Bl-LQ&zdaW^1Expn@4k9U8f%h1XAP?JyyPe`dOa_ zJq|>Mu^3A1u~KI4rt9CdWK$7_WNNBw4&=c<&uQ?IcD<`kSY^dg=cg^$Onac&R4FD5^`!7`V!I7WRk=Bq-nyh$DqOz_%W8iqs>}LOJ#h`Tj>T?gw`;wTDDA1yS@y9_9?ukSLb2y-?H&; z=+gc6-Lby;bH0wh0qrFUL2N^KxA;V)zoq+%4T4aIx8AOV;F68_TlTR07gC1s5wq~q zqaqUHR=2b}%*1^WO3To1aaPNGu3wP{xG!YJ zgOnAft5hIG01%2VD^^WUhQ8!CzFRfs$jM0{fZ{gqho4GR9`-5a2rR@guzj_^Q6M{z z76YqXzVYd)qnh?N&13%XEp|JGpxvCm2XfjpH^P<>Nj)unb`;kmmBnuOo><2IpsbPq zj#$o_2W#ksTH?CrT=a{fZ-*!_RQZE!Nl=2L~FX28$F zQ%96>!B>Z$w-+9WW|gkX_nJC3REJ;eEC68h9ln0ep-CS1I)P8Y?k3GhQ+N^dS`*`w z5Bxn$FI^t`G9!;(T&Xn$u{KJ_3P2ASO<*!ZH;jw6*Z+*scWh;bFXkVgkDcN9=i!%E zip?~)@xZ70O8aYBWW?dHCy!%%_tnQm6*-sPJLLA$>+^r@Rl`pQqz@pNsp~)9CVn>P z--}1k3jk!|+h|^v6ux%y0I?~vzxAf;ymcqZ6D%eUa_|4(_(9h``~Fp*xmBKOOR9bu z!*dR}!;*UgiHycriVb4EOJ9qf1Vf1xpPKG%NC>X;n3n1{%mXZkr#p6PHICP~rZj%L zV631-(MOegZv$&Js4cL={VrRm$zsieMDF(GwcIm;(p=Elr_3qHW4QA)?%Ejd1G@^= z&wBB-cm+b!t)dLmpWEXcoas{+Dimm$WnzXwe>z;FT-bM5n<4Nx?%Cx~)n%p91M&&| z*~Fb`e~$)veH5!%mT7;Cxg04Xzi`~27=qC*%ra$UqrT3~H3=>da#gpaqW(5oU(e{_ zuU4Kh^Qw}=51qL^JnJbF{U#(tM3|jh(t*kOkJA_x=#?3KTHvsiY^$Pv&j&sh{#Guv^Q z-gEe9{{T)i9j#kS{T@+H z$#cFf*gz~!a6|bp)VMi2;17P`WYcB0>~Ts)w#8STSlYx23E&iDS)S5~5Hx${qBsqR zW|U^X$!_kzIw13SIBNvoiI^0nAq26C*F;g=pI?M%hC5NrJooKShwaX)MuMrDJu9!y z>P`E3OiNVX2#gpx%aVGZas8LSX8W?i<%sKlbP1!o^$Hsfad;k}Z%|N@S%Ub--c}D+ zH;bQBeg6gRItJPa8ArWytn)|TkKnjZJB4%cRJ?CU7^%RxAt(x7)9-aTTJ?#}>mLN9 zH?o}2O-4P955^d0{T1!9N$d0|W=aIE;o^^%ZO4b{+$IP2SLcqHR;^glYyhLv0*h%a zzRaB~I*F&0@RUK8fZ6YjFNsTGSe1g>WXukwKpb#v|*j+3ZP)9YHkw{(m zEmr@E2!Tg74qhsg(6Y^Q1xP#l8tiUfH>as&bNgpyDT*}-);1nP%T1=Q?bWz$Ug@C} zF#Htwt^JOCd$vz95S!0WW`#d-AY_MkiCvs$hC#13RN4NE!81`S(Pp6!sz-I+kq=ze{&hY09Sc*tQOk7z{@&; zE?hrUif{5pT)*?#o2@eLaop;yhfpJhLXdz((FBpJvEBnt#w}HEi}j^_wInDKU)2a{ z(LDT+3>n(o+0JYs-b)C@0hzAISuLbt`CNIaOk{2fu_X#6>j3JDX{;L=3#JmT77 zzqO;ZmLDe{4s(YRND27G3MV{v)-<|*4!!`l9Gp^)+1^Z)GgQjmjwuk^nAId_=ayZI zhedsE0_jyYA;p^_wEC?Zp+qgTlO`2aES(^%4W6S*Ehi#~dhAZcIN^=oE$bAY!!^rE z8kYJ2uvy*8G0Mrk^A|xqnFaS>{Mk!oVIgbiQQdlvKtAEDk${j-o{gLjC%qxehpoXb zvD^RA7I|tcRJ_k9?&-0qczHS3G&TR9_caHe?nbK}TlG%6e@d4;XrKDqIs(!vMe;XR ze)4&QP8ad`zrSzHn~%-%9Z-qhI}zFPS26Ut#W>Aj!1P6ypj?ICOd)@4yTZZKaKGh? z&O67~!TbFzL+eYkm#5EOgul+p7jksXv%Fhe_f@odlU&13_K}Nu+wXDqCotE4Qz?9% zKH<|RNsWzmzi?I0VA{!-Ry+)?a-3;8qB5vz9g*~rodhBDm=y{^^+i!I{(BabPFvd~ z%KYPZpR(j8UnAjSq}e^)N|jzMW8bVIpQkETu9!nXdYp`5{z7Hnw!R=>R)ltA)^lEw z(S`oG=N#lRlmFuAd514=&0gH-(5u-~m;9?L_mh50Od0ElW(>+bi|RNU)*LVE-IM#| zUmLnm&2futE6JA3DS+}&vwOYIneQk$z(ZvU{(>Kx;-%CCGN>mM981s`2^Mo0Jk{TH zQFN_TN%omr1J@U~Y5&0s>XbTX#E3ox=TZc7QuZ!j+B<=v(W+v7FmlG%NIyBrG_EN{ zRF6vQMxu}*0Wd57aqSjHvT3MUKdsmYYEztSiQAB-)*cd~VQ}`sG<)wsTdX9Ha81}$ zaFZ{nhD#r%MTIe!6iRaZJW%QGGW;}=*+JAj&9MUFF_8k$$xx=(y-S1mk9tL(3#W45 zywGa;<`^z~L*jC;)-EB_gX-^KuMj0MOms~(?77mhY?13#=4)6>d~n)icVh8sac9Xd zdZ-j^VSwv7`uth+Pjvb7xmJ@ntyc{lmqP_45`Um~;01LBuQhv(kip}6kH>7lQT&x9 zUZVg&P)b~^@N)+v_nV2eZBjs|wt)FKQ!RU>0^xh9)J-@VWsXX&$Eht8`-E+_F9eai zY$6J5?r?i%vCC&6n+c$AkI3 z0K2nA)J39xfcnC0yrRWp8q~lTv1fSGB*E-yO_5`k>r$34P=(2+K%VPwyCAEsDel|j zAA-|yj4h`xru{L0t$iPo^lfZ>)|uJ?PC}jz@vLcIQRzjcDs}o+pcXQkPjPEqo7ERbIUR4kD$&hH46Bk9yAj zyk_jjx$Fnd*8jmZT)qn40Y5GE{s44Nz#yw2eyl>IuyF7*` zKv-x2r)-{2Hp3Mv8hGhDkeDm72KzW#8ByZxSUe?hfB{eQi?>+7Ep%Q(IEFmz9<6!}93y zIde3tp95ReY3o$|L_2T~A8_$QZJN(&p}8LbOUfGeZ_z_jG-Csvv!wmaB{uUc{l?e% z=N~o0M`ptgH>B*stU>qVfRhs$*EeJjLT?mI;Z+O>oGD;~wRad0LP$KrWj~T(F1Hdc zidmIs@&VL83bFj`Mz~pObx&OQJ+PR9xKhX}#(|cXftnA$N&)%IGnVpb z_di*EwI`Yt=`B1i6K|%uddn22l3WkVba6n}c<2xqDE@rFSq8|6jTQ=0p~dl{^+bTe zP7nv_j5O+Nqdcg)I|Cf{p{+(vA}a5JbMWml|04dPqHTgpJy;x$h;YWZ7QVxTUCy1# z4WuI(0R})~g^lF%IQg&rsKN|0P)UhT`>G>C81+FfA3!Gd@8vVxaT-?WwPl!5plk*S zfoxjFAW~80W@S?-nKom}CT!bP^Fp@Tllx<&f6e`@*_pSnmhC+^N%5z_n$71CgC+_oDpJD^5t zEvul#?6UW*nUm;78rniBt0@gGRQEarKuy`5H#ZEf*Xy9{Whete_an)TMR+jdi z*x1hqrXSs`ufKNMiw_`K1!x!}JE5PEq3t2N?Xy|%qxte|IR5>|=UbQddRq$9R!;G` z)Do7nNq-xEPY68=`^EBKV1Et9LDB#OH`D_{>Yo?G_D}A2sCYCys1CpeUsnk`N)0)j zde9h5-uX5Z+kE|PlXrnk{9xNh=MG~W);R94hJKqozaN@6=>jio(_8W2i3L@@tl;iP zIXmc*;t$L6IW6Ge@hNU6%-n7JB`56a+R1<@s55^+OIKZ}6j|>mD(SMhCuYYCF!Xd^1 z2E=2WYzCb!SX>l36&MicUo*t+9en0Q_PWRkNb4F&eI`2fk#0)N>AV^+-BZSISsod= zsERUfOuor>_w^kBKZFI4D$g!^joQ2{dBrh%JH{j*3W+Y47QB;;_alWVPuj=cP2pHE zhlv8so@g@_=6@T2y42(GrY3wI5pjlH*S1#=PH zcz2vFgA3I3WX{6q4)d(&cp<+M{w9H*V0i~rb}B(nvVM7oephTI-Co%L_9(bVx)(UD zYbY^@<82^LOI;AW;6+o9aek3%Wt+eKyiVs-x~C?jVr5ZALwZrk7=0TTp-yalVHvt@ zuk&0=y@6b?5PZ3Qkk1o9CTb%@lE>qy@}>em_;h^QN5Wsm4c1+ubwXUf%}7;7>g`56 zq1QaPeXT}7gtm(^`p1BDs0tLE)LM6wJI-x(H=H+>+BrYSq%=n{PiJZv?QgC~EzDj1 z*e=!>F!wF`{^q#%Q|n*O+;M(oqd@hatf?P^QwcFbtw?8x*O|p5h1tavS{D}K3+dFM(#ZRDPAfOhPrK**aHLae{9eF@DWx82YOJ%R86_{_K# zX};RJ%^_tfk3o;KKh0B9amn5mSt)-QgiRCdiB~g!G@36SQ9@Nl81=0_vxqmQIAQilI=yje# z=`Rl5pb_;%GmK&GmKlph`{Zcx(VFM!CR=L+0m&QJp6z+SB z0<%Lp^wFawf$ieX4uW~A3G@^m57)YBCKcaC)HUSmM?b^==LNu9-hr;j$MQH;D8%@A zNJ#oLEI`o+Utvy@t@jaNcVX~m-S;fBKVb@$IpIq)fiEhK$>&3ct#DjG6qvWJTZTAob?Ur0bya0TYERom#b(YVc zYzj)hr}B7p=^$J4qCWG!bhZ@wMZ$^?gYDJOKx;|KHI&zhJQCw$V4=KXRIfUI%9y=&qoG3jI+xR^2*0MYD}J z9#+Am*`P68TGCzY@M5&7@@~M>)e4VEsL>7cMv(&5g_+7b@90Wyo0^p-fp`8D@Si=y zsKst@W1CDJ8CHffkBI>b!w!%u6Gy*&)9>HjT08o0`p=12cT4yp{c@O!a>!It3Ub}@ z3!N^CI+?P3j0W3R&CPD=-!!JGpjC(`85a0&f0rA0x2Ukt!oX&bc^oXjl>)wwftYd$ z^1WlqU$}LH{t;<{efm>7{2di28LUMH$jLpAJ|_P8ieETW@CqRnM{HZh*b45P`6wim zARa>Fm(Lc1rvqJy8h;naU3U4K+9{~1SC}aR=!!FkK;cb;c$YPFsulAvco797O9m7v5p1|G89=JH*2y zya`Tt@+A}s<`cPFq8G&zmCvt=mY}-H#xR``^+6XY!|360w<0ENjY7ZjqBV4~#2OE$ z-;9%Y*d~_~!*Vtj;~7v8tKV-{I)8Mjg&)xKtG@M=Zz1B`{R!SU?11|2`ITc2EMMwI zgUOhz$8l<^u#`^9^AP-ka=6Cr*PdA{g^rC&sDp>4R4yvYy|m&__O#@ zg}Kr+E=d<%W+U27t@rWS*$=`MY%4!8BlKN+)jmD$fkq2hG)(2UpLf~m#T4+d6)?#m>lj#t;7(239&IgM6hN?h0Vx)6lmC51?~?Tp92i z!JFT5BD)w8@DCHd;lY3Qhx2<&WdnXDu$`l`aqDc@+n01tu8KV6Qmfhm!G4)|7MgzA zzI*z9>3{?IcL3;&IL7_P-k`zAz+hPcp;fKDuoZ5nCpF1i*;m-8?FIAo5y`ucCiOE; z7SV?U9{wO_L!L*ZD|W#<@Pz%a8QWg>K13Tv+X4iYc&kmijZs(si{&RLhIh=R^M}0h z^J(IXSqvSGiQb&gR&&qE9>bAS#$_ob_x92b8TBJg($nCp*JOvDHt}F5q zgYWL*ZP;5eIZ+1I6!&br3~u5-6KLuDRTmu4_m1(sHzPD9eiGKK9Ys#*)Nu z5f!F`12)D%0e+mR?gS5*q)Kun$z}-`9rl1VRk;g#tIXSZ6Z;T`svE~`6*nOr5hzdu zqaJ1eM_3H~0-CI_(0H>F(qFoI7xGY7tabgdpoL`9amqu-A&8AwVyPomR95P_xdE*?l`(7( z{kR{Fk%!c503{I$ zb$0+M2fb^r#oPnpWl!aeYTIqS{tg5*7v5b_hapyeFM@BxPrSGy(0N$1(t12^my4aZ z(gh}e&_nW`b-FV4h$sv~Zx z_(#95cPyy|G~`kXIe%_Et>nH}b`|koALjNM&vo`q*o4eMA>ZyJ;i zGcUGIQQ=QEq3_=bRn%?w4;0g7ZV-h zJ;MW55t*`2-OzFaGHdJKr`|Q% z-R#>^RXYS+7;ZXb7761aPc7AqJ7NI%71>=|o=_`~gal^hXV0C&-}pN5toC#qepzHe zJe)>X?3c_0yodk~OB6d3de3PYZGWW%#$+@hTg59$6x!AV6F$%qEW<-euE*0;)1l*Q z(25jZmIjAgDJsRM|M}@M`@)`7*wSfR%lW@A`1kpm-ja|^-D7TU8NJ&fFBsXZP%NLk zh^p^)FB7UJd)@@L=fZW%ZD4~tqA9--7IX#@d8`;u3?2d>K*GxnuhfG_cIR_8Zi_w! zmVwJGMN^cwr9MB7&{>cvfl#Jk6l$rWy$PePObPZ^8;P9HhaVNOk_hDYZ2P-?xtgm{ z?V=EG$PmTjM%XoAH;#Mj3!3m0yP==*adzL1T;Bi;t=0_J2Tl}?$Li`DBQag4SPN<^ zrzX5*lJ-ku^9(qFz8K>t$trh+ccnAUv<$D$_XX~_ZdpDa$Er1|wxazfUuQJ7BHO*Q z`)2X^x7>fM$hYnp@c~3At%>f``-F#g08SFuPjM{g10tlB`9nduwzxkz#*ySU>4#SR^_Z{L}IX>PZ=hN*kJ0{QD1Jmb4QXIz?8%P{p+ z0ZRb$sA|UdKrO&CJ2>=l&sk@b5fz__7;hS^9Mq^7=WGe!GYnJymmhvnwQwM64~y~K z?wk(l=hA(hlQP|UH09~{W_Q!I(m%M!5zTfpCB!d~JM_4yA>=<{V}ffXX_(-ySDMg6 zhk3=Ubc4!L&fD-{+-4`V;VtV`VD3#J`(|-53pT0##xo2 zWIp7L-z$2I209iX{8IR8yp@3F?w;-0+^@F5)s!mtS&BJU6Pvl- zoNt^Pf^jjCU+BKpl_4j-JsYGYz}ER#G|pU<}g|Y9C)eX`$F)#(|fT?%)$k606BRj<15OSs+%gBQUPHCC-acDKg#VP|5Lt8?17ysa{KRCz!muizk$^La{)fUvU=S0|V+FUn^W_g2w7)1OYu_6k+T=j%9fJJP+q zl~#bx1p8(KtspR+rA6)Fz!d{F6HUAd^XOjH@j^Ek<+I9IZ|IfO(`X6&c>23JsJ5Qu z)zCr35uMTK)(cXqj@Kp=z#no9|5!$)roqa{fi?I;odu>gN*H6>3lEAk`C;02?1tF8 z{k3NPN*uiCdKfDN)JP|13PsueL~Lc}a0VbtkTFU@%1s5^I0lBz!b9J3@U)YvqRw=Egq( z8xS}2i-1ST9yIk0$-#J0p#X}x?ql<4vmsKySWC~g*5J6{-FW_tO$;u7? zE!Mud`-#3UwWXVd{x1>E~>mPWo6IL{=8Ah z^0q|B`g?7H@h`VcrY}S-9 z4NU(kzNOtRQgMFPIbDT&1wHkmG$_!^WX0fejpcHM9`8eG^HNn+w<=vkn_8ut4zyj* z-=`Tp#C?6x(igtpauL~0vkB>3H3fqZ`N@tLGe(RFPI==2*v!;upE<475AY{LDFN)> zx?Xwh>$fgG=lJoaRZEH}Ny|ZFR*n&5@G-%nv7ADaf!8Eg>+%%AHiA@RMtnjKsRSP; zX5C!V6+%2V2AT`Lj*Tn9=ra4E4f7vWW!IO;6xC&>1|~e6IzBo$h?%T!nM*+)PgG!< z8iNBgsb$=5*@)o*?iRiSGGD1`tTxW8f4?^ooJ>O51{2EU!GC3UD3fPRf0$o>?5!jyaH$2n)8F$NpvivYiGjLW;MlPQ!ImwY)9xDR3EwOK_!6~m~)9Y%Rh>t^!o=~+^Iz)H;t1%aK|XoEUXc>$)%liD0CY_@RyI`74TH%- zDWVbZP$S^_4=R0_?o0ibf{yuR6-wAx%So$9?d|x0`E(BU>nDrUR8&zSsp10DQD(Q* zMe_$huu;|2rx;B4-LGg8Spob$0$WDvmneFQx%Sb-N^IFQLiC#mo1VpH49%my%*aS5 z=U1@8`2Uo|XpS@rwcpd<&rBX@s!K`tN#97S6&WK)v)ht1o4z|gx4B9?I&J-`6p}?^ zPWbfU8V{ga#y`}XH3o4#8*>j=D_|v$;amxUn6@@wAuc`pS5Cp5@WtESu8ibm{)Z+% z>sHRgOc3{{HTHhI`TK^TK|D*-mT>PE|T)v~$`{|!%hxo5Ehp9$)7;oX!a;Q>#B%C?<>R9&;JYXZ5 zRvzlXLBkkR`6$w|5E;eD<1=@Q&^|JeuBUwK;B3`9XOn{ba`hyda&Jg%+*Yz)j?%*p zyYMd|ynZnBZvb7_ZUE8( z)Y$0k&EpE+E@v22dO*-%_S;XDDzH<~TI*=$*vveQ;NQ>eC}Yn+(wbC%DmT_TN;yjK z^~wx!c0`?A$cjm+NWrJbe%K?5QRpEByZ>6iRdtF~)OC)Y8o$ztNBxX!Tw5{in4-4| zgqsFNiH&}?^bz}E1}bHBg_armU*M3Ua-LLSBjE-Ta|GyRJgE9j6}Jw38QZVys*m zUNH68%hN_;q1!h^%*R=+Y@!E5&<}q!R%@s-6792;6yHo+mtV_}akQn1Q684K$42Q; zs7}7+YQ6b7XA1h4h9n%iXxugN+vm$x80Ty}9v%Ani7WIEW z)En&nmFBki7)bH_VZJF|i zRMY$$|N5L2OR07lEH78yH7-5u5{wh^ipRH{{>JsTt$djnq$D~Quk70K6GaY1r=$6< zXrUE~&Bsj*Dj`-S*g%45As|-7{_9$$k2;{bHiGPzfjJ5(vX6!~Udvl7Z^gulQqZ zpmSENfGpV2*>J?YokHksTbl2~%WUI;}TIX~IdsS+mWT#+w(I^)is z+GP6-(k-OWHxx&~L^dRuQqn&!rm$&HD<%lRa{4bR-4v59?H#KXbRq4{(W%rqcJSJ36{=!n*trt zt-^-E7iwz$Qf_pFW(d!Riu!nw5*rD9F*N27KVxsJRH3+XHu{UC@z@uXzIavnWHEjk zOUs|)EQzAJ6Ib?8nSX2jV1v@q%HKpS|FJ6?po!jH!2?aqEHQF5yP=IJ$M?dE1|HQrCgc&?78Wa;qN`Im<$U96Z0m@5S>52cqMtg2yWb*Qo#X;p< zPIJ3X5aiU;x`h|WG-~8G#e)ZNT=r;=JU{!Gsh>?+=3&=f%~ZVt6yrv!PylT@V3x5rV!OAc&SZMi==j=WK8+P?b;#>wyu zj_{H6amgROO3M%$@FwIPBdniv2*s#cdnvkDWw(v4JJSid-&~m-m-nhVnk{#3_%s*= zvY8hAZFrDxI1!@ zIr7E>hQ6M^`#c-p45!vwA3S&Z^y&o}E2SR3K62v(4*=^Sw|h0mE(Ao4-3}L_cxoEA9t~MX20&bomYzoiep?G?BpO5t9~`!jEh}wTYkj zpz7$CiM>t7F-}2C)~97&XYVEYmh9Vx5B!A-L?1+)feB)upR?&Ezzd@CJ-`odaTNlwe>+(Riix8p1-D>wgXkj zDdB$#W?NWQ`QIP_wA)>KUF+T!Da}tOX0mHNw-OSJJRck^_ON8vaa<0LT%K(98#qES zoG8vDo;wO7J`K?zA&6*S7OYI75B%*WHJ689xgO9XUST{9`cVE+u~PEB#)&0V;;L1= z90NhsAP)58E(+MWsFYa%KCHT2oy~dUcf1P!Sl3O1Wq|9LchR}3dDYZeGHCAA%z9m9 zbP$;yNogG3KmW~V?qU>))%v5DDNAU1MV)^a4ECf1Z@M0BBPKItnzc9ZD@9HwQWn=F9Z%q4 z4|0k$F#@vL!*0{C%1>WqmBA6(pDVf{oh9v!KSnU($#^KbA8fNs6M>$Z6WOTiXnhWo zA@KhPbYtU4k`~IdY~NK~XsSF8^{Ttd0YhS50H>nI0=jsd+ssNz6_pM;2YAgkPPa~7 znVkYsl972JZ`Bz zQ-H%1?mPLFI&;2@khfhQY$EA9_nCeNZ0NlI;e$hL9<+b4VVUn3`Z57h!_0a%96Ro| zSo%mRjGSgDpmc(GNfm&C) zBSfVJzYS3+CCzLqP6OYzDkTfKywtC$DCH2(_g5nM!vEjdOrcuz`y60r#+OfLY=jMG4iefwHe8Q&XH(UwG!JzyUCqunHzl@%udFb8{Lc#@4bojhYPrA0(>?lr4)ya6 zfoA#Ldoxks<7V5S^2?Gh8eA(fYkivgeFBzO4X>P(?~9hDmNmJ(F+j}^(`9m)0zS6>(pV9{f zwOBx(2m#$TGTb~P+knodX@zU z3`C~{2DmGTMuYe?nLQt5utpT(m*W-YYpXGFQBnO?vwj7Zk2@KwzEl~5uMr2xi4Waj z%ajYQVJ5^0D8!n*LNk|7U152!tnD^5(K^yJudMRdwhHNwH+&vo>degyPGJ1*m%?jP zcH{0`iH|#F$>{!ojAs=+pfN4qKk}XTK#Ymxp$8_iz&5vUDQpUG3*BQ4haqvSh5O0{6WEA3?`S(jbx3KB$*( zo+AEcHMn=ozKGcGQ9fEpgNLs77yHIJ{BnBwe>9!@Ba`p{#}SgVawexJb0()rIhQld zc}_XAIfRfi<$OM$Nn(y0V$Q6>5GKinIlSet$~ie3-+g}g{tLTzZ?EflJztN zSzx!{4b&B)^Ugdb_(p8UQ&UplO1e^s$${1@V8QvM?yvfP-}Tdy!jRyzDDQMr>wOg? zdV4iSdlNWC8`+(fK;K{C?H!7=uK|sd7HbklNS|&!KC~C-CBreYW^yQ^JRjSJ` zZB?9-9jWV5_?k?W){z7XtN|bPZFgAFzTiCv6{tS@@vFZR9TMsBre|-HI2*ioxx9Co zhWk?f+m0(>j-m9w!LMOTe0SfezFzq~NdoyT{^@_W>+s~DIJ*C1x5~As5*T!cI$dle zv?3TlC=s-&xm<@ONdYt(idJM66ON2``+i1 zUajDw)i_jUW1l)AlM(x8z09M)ZQ0f(VF0K9`1b{?{F zxEzXkHo_#vDw0M6{%~jvcr+PU5(4jd^6#k^;73R#*tx%sX-n{y?T*u z`RmyONgKU)*KF6~mZN#?-|=m!#Hd8WAI>EN1b_W=AG?57Uxnku=Fm&*#-RrRKfZQh z+zbP5NaWkrPcT^9xb*LEYTK-pPqu>KXs%&{5g#)(~&-M4WF_=3Qb{$M=vM@DWAtUCptjQ^vUnD zlRlwFf26~sEteE@sM;Xt@4YqEp}A@HH}~{0lm=VMBmtZ^xhKs72vgV*&>H|a~s<9)Z#A) zM5|ay!uL8Jy=T&vJk8KaJ6H<4i$;c2KrO&rP_18=GBGd3pJ)AExLE;$GO!fYJ-;PL zNg!X4n=&tz)wg|^A>#uHq(Is0D+C(*nVxvQyVN_S zdUnu0q$#%(g6-m|RWg_a)`ljZJU-nYcX*<@|H%anHvJ^=g_oe0MER1_YN7YLm>3x) z$D5C6&xfS*RM-+^Q#4;+plo6_WvsUbI(BNKF@?+B zF@slKmPH7uHz!`jBS)XlU-q}#0FWyEEP#8pGZ)x5`;O5&<-|zodzgG{8rBl9t&dKU zt&3AORDlURHXQH3Wd`vMo%fNJ-dmbCvL)lR{Gg<~vm4LTd|AG7y(Dkcp>)ycg9$`& zpFg3*2j=bS_OB;P8y1Enw$vl5zL1veaJ>@d;w|o$ZeTl(31_8HB3<7;#9_;V z1o|XW>6WRPgz_Qaf}#RTKyCGO3|6A7px73Q6iTAIYoQN_)#Ol}HHWe`44E3h<_a2X z2QGQ84-^N7Q%y3^w>jR9vujV)urA$$_c*Y) z`KPLH?cei2G$4IyW-@2r@78s)7Bxr~XUsg%pi=W!m%ob;xlGU2GdM$~GL9=AQ)G-k z`c3TNUq0L$xHf9GOou{KvE?uAH>Ng_((@PW*Y-N5xlY z;Im4Q1VCq#bP0x9@XpA%jxJel_rh$t!-7$F*`D$kYG95Cmqnk?x1IrhZ zyhFizR|GY+1es)53Elg8P1i3DZD+%cSxn%M|7Ojx3fbE94A1u$K zbthfx?C-v5O^~Sj2=_fYy?Hg+Jg}=L`;u&#pSMEUHw-6rfrtz3%$Z4xO zh~LA;Cj?5B(u$c5{yrfD2ET^qfnyV?&`vWA5N&|9D71O zw``){sn{v<-5JZq#%kDI7hTyTGG?xhl%%)@b#)^=7Nl>i^J!uCA9gJgWyvGav?7V%f5-bN{A%|4XGuOfC-4Q-d`N*$!yKATAq4uHKzn~IepDi)Vd-*!ze3dEkb@G zLBMIfu~#9GCHv(~!eT<+}vBasoXxF`(V%C26tX6`5aaI4=*;Gw9*k4$6zEzN}t4L$rD5y1R2*Zj2IO@^eU@=*WdRcUl zv9>;(j3#BkYtELfJTY`}*CJNj03-V~Z#FFab;N^-i#Mk=JX@X|^xw z4qqy6OY2u~{df6u`n;vrpU~>&2QB*eQuMdQRFhOi_`x!VTF6DQ(n(?>X{K{d7rfE2 z(|_1q*U7+RfDRh)yKrM~^^qpSDzmTv$UNsgV|{~sDmFr%oG#b;skc5O{uw%Ir0;oR zgJ8-C34guy=t@i@&5OdTeY2}HxOcpPHtA4#V#ZV-(5*jaoyAhB(nnOgdSd=fUhR5= zz`xX6`cpoA&9_$&z14}ZS@D(%iTlxG%;KvS3=Ijns6}1-qx#ATlWeLLENN~YWe+#? zS&d>wb(0KD1NtIfOQVyM)%5{vw{FST2!oL{cZA*_`pg7@pq$S%+b3v$w2!-WvL>0c zeLp+79DKJ#|04Z3RdHh!-OPy1T)mYVxJzLXI+jU-)n@Zuuf({fOgvFIX#;QkY4&}F%IDjH69dVVk)Ij4vPU^u5 zHak2X=06a)ochCyQ#-a`YhVYQs%cJXIUe<>ro+p)`EIk7cn{>A<=xuu^#y)j;W~fR z34_th&(CJ5dD-*1%Z@2g(9FH51vYo7| z<7ATPWtaw!o!XR%L$%RXo2~X2PDM%EQA&wGKK}A9#$yhAXVi=mc<<{=;D|RpiwpWy z{=-5Qcp*kwWra)8S(2-{apUi9;X6_WaHhCZaJV@k#i3r^<{R?pt-G3W=_J*onPUUV zv+d8o{q)?KOFaLD+U4re(%Zy=Ro_`2Ex^#^*$qL>FAWV~1&?X4hz3B!gS>1|)g%z3 z#|BC+#2*fHAH@^Y?v)<{r93>DYU!VQAv5`$o@H=b%vSE*xxX>|(*@g?g zn!tvwC@n#L+nWW-!_d6hJ%t1R=9T(ZeWdMQa`N@91~ua+b44W!=@i+`8Mss9D*e?+ zvrz{jGnnHHBY8>!%sxl;c=aL*tj(l^ggDpiAVEGJW%13rl{^I7ttKCC>MZbk+L(g8 z*il&?_jr|IJ&0rtOt}|*fv}ps?Ez;CNCTuSR9*gxR|7_DkIgiP%>+tUzt^7YdPbu50G)CR*9D0KW`cMD)5m%x&)8Q%D zuUJmug`EVV%sQ_E+e@RAmjm6=S7ZY_enf%M;IGs2SO2ZI@AQDS^3~e63P-WbxDrr` zcNq%oC3l~#t~&;F3t;SWD{xk<=Jr9AZF{5{xd8^PlsXJ*9}2akVhKW!Td3FNn9+jc z?+*JjI>{Ltt=xFXedE`{1_Y_f3I`8gu$w0kKY5>%Tpi%xs0wO;PWyV!{CQN6XhV)O zz^g=4N0bH_e-vOrwY$`R{om?`P|Ol!TB;`(I)ZCR1LIDC!kEJfOzCIa`E`0w|#r~}6hyUgYl$AC4y8X%3@}=|!KG z#`G1Hk&WdehLU+`2NR-l_hkZlgU1GuEGZJlE4bWd{4qC*ISFM@fQ)q0shmm z!aXg{!~xq^saR`V*quNw7vGDHop5;?!+KA}FrrGt&l`J2^?dwlzWq0kW11RBFrXPi zUcjW_1TfBPE-^IakTt=+Uy~bbU}f|urkX8gv`44&73SWjSx>v?jU)!Am|$YJXZ~$l z6L8Hg21RmePD@AL3Sd(pbp>D0-{T@Ilx=;uIN4o_^y2I&9X~8sI%kf)yor+L=m|ay zt2jxUdu5OMWv`Xg1?Fqtx}hBduKZ(_XPq>Jy#Y!IoGi{xa~pJDWE%8!Nt&*tZK#5J zTWS;>?_<<1My_`neJu$lBcY@Ii}nt%q6M^7eI@vPa7J%UZ^Y{Z1;>l+wv~M!!b!y= zPb&r7l6%{d-EFeQH_jOH=VLulp>x4^b&b|4bzssMcxG!lDidgCrHH-^;6>32sKpJV$LWASAnoLZdb|tT58jYx1v% z0lZ378uEfXzLLNd3HG{51xG;<{Os2rY_BD)Zat{Rzku_P-#&iGej}zi<0B4{~WOR#f&0a;=+M_?OWhd~m-lk+j z%oPeDv$x2nc=ox}-yTVB>By+z%%mc@|hZGES6V>OJ|ZT*C_UUqo|48ia;h z0zR1j-apPQ+Pa8deb+m?qkMB~-Z~fyV+IwQ(SuS2(*WEa#ir|DKcE}H137YNZi-v? zNcuft4OX(XQt;Pyd|n<$Vy%5=FyiRf0>YsC>G0WhJT7v_HuGIi==C_E)3?0LL2c0o z#(6v=KO}1dt9=)7{_EFkzQn`SXE+l^)cytP`YT=ZS0ckmlMn#sRid9Qrcj;bqAjo$ zQ2^H8?HOpzU}hv42vbt&CDXL(*H^==E%URvf_%>Jezl-QAkL63)Y+02O z42mU&O@}-d1b6tL1Yn$lQGvy^e~qFynhVD#gWECz1Ry62w$*e*;aQq6z0Ntb8Y^4?`P`fuOREjl?=PR{Y^3mVwm zroUH(HP>&DRUt7+#n{Z%D}|keaIf)U?wG^vULBmYK3tHJd<>BD0)LXKg90lKke*7c zf4Ze;2E9{KJ-dyB704BZC;ba*6*ak0(+d`uFLd0b@~fP9C?48b2XdK2Be4o#dB%&- z1xr(pCMXBCgfrKbWelqfpr6P{{^JoX|49iS8>DT-Kb+aCIjN>^R zF3v-ue%IK@PdGUS2sHFgYC(yAewns6VINQzv+$t~yF#K?S9AH#mHGxUmJ1F7)Qog7 zV(~JK5Znp6CzN}HxZEO(*;euZP8zK!B9L%#^$Y*)49CM&$LsWifIaZkHn*QNhR>ot zl2E4PcloDT#rm#eMI;NrPl;*pwc3=&OnW)Tp*!N#x%|OsAh)7h7d?Jl^>L4CxLe58 z-}$q*(vim}El)1#R?f8EMQa4WL-@ikH<7mL0byJ0Vyb;1*jEgeW8HEx%JQz68UM|o z&cr=?L8;i3sv2!)0kS>#31j+BPkho)5vkzOL-o*h1-v5Di?9v8dDAEQ=$R-uPP@`l zuU?FEEn=ot9+v}>GAq-<#Laa_DU~6WBlpzKSNQBc9ZwUdB98+TpV28=8vHJFh==h8 zq$G|!gB>h={)q)d{r6bSt0zira@`inn@i5->gu8%%a9MolW^vUb=BSDr)%STIH~h#J6Z^%q%DeM=6O1pz}iyqU4Cgccr)ub2+?>X z#`EoXe zYE8>cp<$7!LMFLglGkuMYDzZw3E;hmGKd$@mI%cO(J04)i~OCp)Q;33m}FQb`3uE)S527Idt zpdx4cWJ4|dg7(mm41EHUCOmKAbgz5H2xeQ zJ@}VG+j>M;WP#aOMmgEpsNM+W&$l=U zr8ylhOS5m82N<2DJas3*`_uALf8_K(JZ!nS;ya74sj$4|HUKIJpQ}!Pkhr))G9(Kz!;znxYRMOsx{K>TR zVti1$Bww5QjEAZ(Xlu+Q;!pe}vC#L)aYOi2_Ufr zJ}cP8sh|FEbN`p6@06r^&zHfHsC#5F#cx3>)gF-6RX5df@`qG zBc?cD-u%h4yDk%Vvgu@PTqMtObR*x=0QQVch+pNWB6lBZ7PDm_9Pt(9op)QEpqIST z=e`<=_lMSb)C}MM?qN?{qh7vfjy`>Ky?m`&rpBA_5LyQIo3AO;$tNSLHm@$gZ^)&+ zFZfkH1!$4A$61lVsm35ifM%*EB`)%HidY8=yVb>0r$%)HCkt3fIHz)j_sNpC4m?H|uO)XCq&sewO@Qx2!B z_+47yG%`}#lI-~lbpc6GB#xOom&DaIz+lhH9+GW}DclrVi?|_HGoh32X5$zQQ5uqt z_|v&UL=2}&FYYl}rd536mB4k|x5$D;Qn#fs38~T;+t7<%)C1#_Po^nc=Gm_?ETrAo zJYINP6)VWuqaamhOd%ngSsS^-4xFSk4`h?9`UBUI@xGO%U8xTTx>c1uWMQG8AZ6i@ z$#~s>bVH1co|qnMmU#~ORKhK8cg92S@#69@DUW9KIqyqpF{8f!_d$Sh>Vd9X5d;<`f@a$V_>s`Acr9 zBo2A^k!%5=YVSAtAZ=%H9nO7PrZ`$k^G2H6a$=Q7=}7ra;`zTL{ajdV&^gM$jEh zN_g(o%qK)>=ew%`+KT7U`rEWI7=KM2I7?lB4|TaLaMB6mgPS;%H^fdKW|`=>HAl>#(0%z$FR(9vO?mslqV@nI9G&fKf6~PhR%`D&| z9Tm*_DLNm`O=)6;Q-UPDQme8e~ z`cfq!tHp+k(3NrB@;9e0=a??PrTllgP~Y+<&uIZT<37zh)57n!G%$cqe^85axV)q@ z893hj3|D$`{6p~%m7f$^2A6zOR z^?;f@s`{-3m^~s)pq&JlD@d_MuFEJ4+$7i3_0O20*TpV?Qxyv66P*wuZ3;2WFqWj} zvcp_O7aipzt=8YFTdc4DhDTk(o8R~^Y?@Dtx^@&8;)i&x8+RrGr>-@UqqC^=soaYw zM9J9I>4eEB!Ij+V@oNfs6c$d?S|sokA0yL$>%Uxkr4yBglbJ5w?-VWrLDtQTWqu)e zgQqjdyIfFK{%FYI75{a%+VxI!=kM^Q;X`M7TiokWDmrBpWxcn=ds;G}8Xn&xfE+NQleRD}GPa9jSZ3OwttX@Ymw&MlaeU%$uwvB0nK40lW?bre$1#rCgA>2fD3vU?>i-qw8UIaN|f>5i%& z^mCrO;^MKjLBvT2@le%>>W9lXJ@(8Avz#4MC6)^S#sN8VpR?6q07B*hiZO!JIx-Up zMzjwp?i8}N9cCmqGlwEmGR%^vt+mLIteOmh@v>crL(Ch|oFM99eZA@V0`Ms5z$ZUG zV3`f-?VtnV8njLIi$)gP87KrH*(sHa@*ssm0ihaGsX6)WVT)ir@vM5rfYEelf~*oz zGeBsofKF|;M{-=yI?)xeA0`~;V7Q-grCX&!JwtK`5wN9!0o(}`C`Cj)npm@-I#dIjVlO)pg z2!s#C)UZ#AXy|FA0f5Zm&`Eg=wfe31w6fwK0*$M)4z&N+rwVHRXnU5aIYFPL$(!I6 z+4V}+KTB6T**rSa`H_cf6k+!3e2X-U%Q0gF7JLk;nE+B*^YZ3*Wj^%x zICi;vk90OE(vM#b;_ztQQw`;xu@l@RTZWgQOW&jpMztz;&Dkx7T^@vTClZHMp<2H}+*+GiT~ndsK*X)i0$QHPax2>& z{wW5@YRXyrnW7Yf$JXX#Yf#l~(tk=|X5ezC>|oR`9VmurP<`(bS-PG?9iBv=UR<|z zmIb@}%hU6?C!|gG7zuKG!zz%1-KRH;3MTuduMQrlT@p}7es;c57cW}rLlJ@suv9r5 znANDBB9vMuUSD^;WLa6-q6GGEl!9BMn1Q9uf1OKI?P_R;u(!0dSXzoMcg*P4hAVJz z1&8m|?^zM6{Os^YiJ@)M^izFj_7|yEkVy>#sUXx!pV01wIEns-IdLWQuE_-vbm5P7K{tpazj~r>dnZrGYe|C z3n*^K)vQa1MzzYLm39LBTNq`T)lt9FNLFg2l?_n(q0tN&IuEY@lF+wjhreMvV(Khq zQJhWZg$VIoQC4-Z1R$~gsCS#nCO24uiMJc13h%PYVwiL8o8Ex5u#W+%y%0!cT$U{Q z3>?ULQU#&UL8cCWm0wbwa-(g?pSW(84zBoSZIF=d$qq&CCcHRmqmyS#islV6llv7Kxa ztE=f~u#Do(&{tQU;ZX&Ft(Xwv$33exnLN-MuW;?K^ZcnhAA0NNelpV)xNna4o$8ZB zo6$F|7J*~h38e^0+1k8@h8X>BmA1|V&Kh>bR}o?TkF2=}HR?tzF~z_*a|5ZG0@Xw( zC5DTfKwbZe5_>Hi>>!0}LgS#wX5Bl2(ZmZOPH^==sL3&O~MuDRu1$A7%(JYsn z;nBS1558Huj14O|)T^wKYlC!Ud6CZ|RkV(3XZDM&jWY1%rX|UjWkpvqm=5!$z z+gMtvZzUa$$bp~^1KC<`<}BtG3L$U@@~diwY=%==vP7$%EY8lnn)S%u7BX+=5a*>S zMBs6OlVODl=8^p>2Go7BNK2O^gDI4%VVmqaJD@@b>tp4|J^o?2 za4|Um)|`rkTpdBJ+3lX}uCKQ{-lcaT8^bE34mcvPMk{dk#0RyBKKRA1 zP~tflTn1vM-0@kQc;mP}CnIztDp=rec{^CtQ+@?OGGm z%BN&7xUGEi<<)=Wr<*2MgL`US*NdxTE}=B8hIPg2srphjY<+>tN^$hUluaT}wb*a5 z0c+LMbV*}`^c26yE%YP2&Bouc%&YG_6&k|PEh@fY)#B0PRa-r8X}|K>b#f;53hf_< zT<>&+{`js5rM9qij|*Ln-6W}BTG14hNYZsrO+nFpd78@5jC%GJBpTRWFlWbQZYnq; z3eKXjAo$Dwukp$AOnYE!v?K=tXKTzO#W3iDGKRJQ_NElEuZUKxqyI%#w-1kd9bi2N zB!+#oq6iXaQ82>E8)5tx{YcbMNpL`#h6pexl(Bb*FI|@12-keE2D5)w)G+U6x%9700|pXjT%nzB+=nt-q3z1 ze)k`mA(-9h;Z$zx?!V2WysoWYT>oaPbo5mSo5xC+X-<_@co`X|nZAXyp+X_+9o<>^ zL{-d%ee^4++MwSx`l9*W9Ksf;Wu`j`@fjmLfbr5j!wvRQMiva4+^s@w+{Q<-eS60--=}g5d-SUI=muq;4RIv8mu`4{;jzHv7>LCz!mG9Cc9Y{FliAO zzFkEI!2#Xhg*UB?rnhQ49}n_J{{1ixol3avEGkm7 zBR}OYCTIf<#L$xJmWQrQK06wPE=?|SR^q-Cao+a$nVy*i8x(4K-#KMQYmRLi92&~TZuB{e}!Mbo=!2KXnhpNd*qVh(mae{{Q5&|@px;3nDBor)v zzm_>50Bhpn>H*%G153nMm}F*{F*s#l=5uo2$dt~0Ua*PwzpA4JYmp6isdp?hOldElClK{4D~x@>NAMwKR&4aJ8JsiOh46w zUlH8FgLxi<6Ks!zmfqiF^<&av9NIrMGhyg8VA1|Jp}=rRFO?onsH1vkCaEA*V1&^( z#J3qWoc(&iK)DuGn%a?|y@?VdNnWIfA7A{D_Hevej!F%+u7I3hej}sqmo@g8rvuv=Tvl| zGbJ?IRSv?3lmHSJ`FVlkA|jHkW@ObP{Jq3qpWi;q#8@_7ZC^U!gOx~u-9aW1CH?p$ z{=vJ5&CBt3$=b4L*a3}bCGm4?fu?t$UaPQ>gdVf-tSkNP1NqFHT;wumF2u498~QXj zBb7pEgV}sl9DLK_6?-;S9KFxpDi)N35+D?%_D~-CbJpen#E{Nlo#|#I`jq8DD*QbXDSGRWoEq+weD^ zjknDWp>7Da2)tzxE7AI}sQ^qP=-{CGr=b7v32dJY5C(W*kiunQgab_?Q{KD|K9r$o zIDzF=11%Ihd1da@y=leXy<4%kQakAzxPzHl8|0v$&->5@v!t2P9;L5L!2~cqbU4VY zfks%bH;MW`b+K;)(Hpz=cn4y ztID(a-rSaqQo{D;5RGzXn-8ztNDhy8OjZ&D4;273U6fEFu667D3C!96E=F%1ppnIT z8zk9&xj%LF&vyC8Vt>~IuT@@S7dyqBY7UUmN+-Upt#I$jM;aY+R%Ug&ELk6@%fzfV z8fZ)I@tMUcrx4X7s3rD^a-6zFH|#81xh&!1KW19H-XL|Xc75f~COp%OstGnsPcN=` zj#TJ(#^1k5{K8MfSfO^e8ZGPGDr$2cvIM6(h$T_F@c5MiGbF|o)92O;#+fevsXZQm zr8{mP&!6;IOFjK!f!oySj^1B z9B%1?&HLs+;%R2rC!_N6n3yv5D(oxD&ZCe;q+SDXGS{WhKr^54F5FlDuxc;SKdCj% z#nS-g>lA)05=DeM-|DdK0U`5*^NS2f3eC-GvE{HA)E0`cO;wY7T=v1@pTD{WBvaN6 zWEWLGtw*W{4LE6udHURl0ZdI8>r~SA1kb5jt68E}?W2#nSI&z|P2e1GD9M*u6i6X2 zIIKJZoN#^ld*XBQ^(Em+?q(qV9gJvC23cT?A^nWOM2{c_pF>8*eDHU_dOfZwZXecseS8@1DA1ud zWEt0Bk_G+~D~jcBl#_5d7tql{!iZHX3;M z=OPvsLaei34dFIFHZh?}9kCjA=B5Svi;BI|O`ID3GDPis%R_o1okPYBLiHc)H$+lM zQvj?lHU`YJD5h7T{_KTPm`E`X(4=%WsOn}P&uC&Y1wMPutS&wKQKMF@9lsM(?^n73 zA^x{(Um55zzc64cJzp%7iM&s^OEIaU#};TI)FdQSQ*DPBs<*#YUneuY`Ex?PA{MSx zV)1@mEN%Uf9sj`jYQL33#96oB>oxym1D}+w(8&D;|4xoch=3jaTGvI#-s!H#!130h zonr4eBCv$?zb7FBvG@S6dhs!CzKPM&f4K*C0wb4jHR4>$ayRuy=YcVN%h&7AqWt?J z&VHTm0Jq~jr;nUULJnUr7i{HUTw8}t)vok5KJkMiw>mEu?2Xk-w203&bTz4;SxwIwoAq{P+IgdisOpHQ_Z5drugg_BA@9Hx;i;k^uVf@yY*wTnId+|rDyR;{rAiAcjx!;%lcX} zim9I2yqXr(Wb+y#KIB}?oUCFmo~N*8>YecEGk+)s;JzZn`TbO*_v-Yr&B zzE|sIIw4RLpfft)qlYh|AeYq^aE+Ct*RIa9z}T$w8m#67Qg~TwdsziOWm#<)s-@g` zdG=QAa^+eHwLi1h7Z`T6{`~R!#vf0Pz=`)hy$EPwqnk0Hh6Zd_p>~Al4<&R)O`WaA zB7NX?KpC@{R}>bew*trXE(WB5G3rM3=9bh@cb7;)cKWKkWI-(f-?%eB-?sAVaJZ2A z<*Vb<~PSQVl(tDQUuLvxB6TdE5+(42FLwN^-$D zZ8UD#06xGAzRt*SLP2+J;;c>Uv0FkGKst3F=cZ0$V1nua2&lG!W;NKH_^M^lMR?|7Q+4iFGPzImBMmE08U z+O8=PuNTK#$(cz95};*$uK;%pHV>UX@B1&*STK#i;bBB*4=Qsi3b_bX-5&4pqj^9$Y)4MZ={ zoh9@>cC!%Y+{Xm=wnqZQP$agdiIzciwB5u5~4X zS~SO4Xs-7=uSnXbsjIE7arc>#T`_~F_vEjyb-$#ze? zO6-l0F4)T+T2n?&&YXsf5hc^NWw{U6KIMLe@Z-(wwR#u6yYt`#hkw9y9;{&KAKv|L zt;L4KdMB&yK?An*8y1?3GZL7t#`&X9!cl4u?9WW~Ejs-A^ntA{mUhl{c0pzLU67TY zHR8`$-iWQfbhA$cn#>$yp{P~D@=0p>8?S)MYNYz^59^>gdx%{HZ!;);ZMJ2xOcyYP zS_}V125T*7Xku8?w!HB>bh*$v4NB$ugf~y$0P{KM4BCp+w+OtzDEMO+ zM17d^f28RHpfuTm&Mf4n`d*N-r!0L1t!Qw%LUnr95bt5x0>Xk)>&qwl!8wJi;X*!% zo{U>`Rz`{&w|{ywC=4h)jdbc;oQgr-o}%~VW}@+II!skG^5T4u{G6PMQ)&+QHv0~y4pRJDJ};X-Vo^@}zaP7t*Dsw=(MFbMLm zPtnZ}VLK(fnloI8pdxp@das2+CzQ1m!3aXPd7jR`7{{mO`cVZ4_*KXE%kJbF< z(C6A(y87$3S=Ekv8aNg|Z}Vs>kl`sa@|YAjVUo~F3|WR&Z#DBFq@4+~l`H#|i?-`e z4yqAzci-iBHZ$PmFn4aObOqgmNlY|O-Z!duT$I@zBRshdrTvYbX*LMu(Ng#kLuH;5 zu*q%EfWeF97i9}$a{q+=;MPcMtzidnL3Df|V_$5%8uPen-|rPBr*`;0;Z5V{dm{9v zC}k)$cB`f5;`ou@b!zj~G%D;^p>b{a=eA5&N4b`F3E%VMi`cUl{nL>IY}k`)-cTHT z`)oL>`$=mIg{c8W6(^k>=hmo7u4dL(LE}T!b><{7F%APtiT)GNd;3+0-9`#y-8*?qnWoOyDj5V^WWmaBlnv%DvW?juicZIGRzt@x98?VTIPJAP)l2G?yJ=CdG@a_p0CH zn-+$DA8=f)=ay2bzLt6NI>&fd)zNJobgR{?OwJfemx7dyhCME)xYq*{jQy&k{xi?H zh^`If+{8}pB=tV04$}!&sG+cQIJRGbQ@J7?c;8iUe%bi=&@$Acd{)WWFDP}^8R^d* zP^Mli;cB~bG>&i-ppZL-c{QyG4Laq3Av7XGWw&qKqo6=@YAX{AW6-BEn9`#qj;Vp; zU*e8^k$dtJq!PB+M9AKv@<7lVbVZZWvYf}|c}V2S>R&fgo$!^C8vlZJtA*gD`W<#Q zT^fzJQO+4@cBO@2a?k#~1z)$ay@HY22P5`)c^?9id+Tbqzq3LOQ9_p0s!cE_=~J{t zvI5-JI}Qj00_8CVZtHUcbsT$=4O~jMQXeI=i;pG9B+6K23$qmihI30hL&C2HO)eMu zYw5++b{PeYoXW%t-ThtL7Z`tI%~oW#`R0f9I#t2m#@SiSOjd;=9w2HOGJPHSj%kcS zYX0$E$m7hn$f(eWL2HHdUQnZ^tJ59_(CF*gNyHvgImwsijFBzu>tCp0W&7<^1YIrB z$@VR@m(q!ay9}s=|99?2`(BO<-X3W8oM)z4aeNYFJ18M5@*U>UO{2zyR!aEo8B%`QP8lpTE^IC1!=S*5BDh*)1HyXE=K-y z`~NNgSY43(Xz7dcWJ(+}ZKA)o)p*gLLbao_3ng{a_v&MdihLSmq2aQGGO13EwOwZw zyMeRG=3e3=9=&siV5P|oS4yAOt}*Ya==AxJhI@LW5O{y5%%4#~sA{?Z3J2z~>O1S% z8O-YHHc*JVfavi%I%S00E}~=S1vF5@ncyj)An8s0`g*qL_D6`Z+C}k7e{0Md+vRV+ zD4KHeZ(luY(kgGW?9c;v*64*MX02^O%k~av&i7beis!K)HnKM?FQi&w$=y{iS-A)- zAW5p@#GT;qde(CoJT8#FcTjtQELGOGXtQmE3B8p6kogOJ42@jIo!c8_HgQk}r!>H; zaW|A51Zp&6XEo*tHx*}L5nk!hLA~sNyJ_22MxPT_PVV@HvD5CCk;i4vD!USMD_YqvamkJ0upJ)=(AU#jec9C{BCahc3S1{WUCu%I zwD&^|Z1xVSz84>6E*?1Xrzv2=zwfrJY%rVYry|CHL0fD1kX>`ei`4NC@nNfLYVX9G zGWPfwZJo$p)N7K?#?KqPdP?4Z(coV>`!t0@iP+p-u`~@F8+@rQB$0Q7_b2bBdpG#q@}EK|Wm)Ynd+{$`Wr75Gs*HLsyqN0U;Cgr5YYLeIK-ceR+DV z4PpChC^};YVN7Zoec4T|^g(I;?<7{3Zyw?iF*B<9EI$@vM z;FtkI4MH<7N8i83Wy>MeY1^>wa|8bpI#F(j``FuNyd!{odfMKB$Nixn(0@-4)<8pV ztj%imq7DdG&wB@U>fkwfe;0apHfDGK^ufO5!)4d;@lbYc*RmTQ!M1)Xtp4^lOc#b{ zP*>BLEO9dU-whB8%G)&ju)D2A)WZlZG#a+4^pr#0L#zWMU9$OwMuJ`nklul_MWkd zn)#u`-fgX#rDgBM{942#40I4w6@rUC;u1Eo4n0^9JzDf-|M9`J7|oGe58~QTyoxLQF2wl3Abf|E zBpS+O&Rdi(*6_dE{#ssw5&3PyuzrVc-pk-f>&&JX-lFlL>i>ZZj zZjZrnx9i>bU-jL`o>c50edYD)hSC!~suGqwO#f!NwdhjKYpFMlsFX%}o=sH(43`Zp zZCjh%#9X<~`1YFUI{xr#VX>bExIm; zPljiJ+}u&|P0X3;J(%xR-0?213R8!JksDFHse-p|$!n6YFOqcTaY~ICUQFM{_}`HA2Bb zm#xjBd&^hTiS`d9h*+`z>4AfN05ljW11xq)Kwl4iLEzq^6+0c2F6tulOl|!JZ5HHh z!S`@u2ojYUKWzsJ2X-2q9O{AL3_I3lR4y4&Do*eVacqd+&o-dJx<&(q_%gw|<)4Xj zhFIYUjUM+!tX!tlhv(WVUD}`H_OIXH9&FU=+B&7`>sR{>sRHLnfK^Q=sJ!I}&0mUV zNsCJ7FA17>HjN^F)6tVO&0)6(9ix@a}0ze^CPs04+_RyKiUl*a88- zN-J*c5H7#cJoE?n#H=YhJE;Ug=-#Sshk{-bkg)6N;2hr^ru69%R*kpszh}INBT8rb z3oH!?$Hf>#9v^$24ax&mHRk}M@H9o$)_f>N@z@o||(ii(klp{PhTDh{pt}p%U zFCH&v>Uu3~Dr$ zrtk(%&{JfET)Sn*(8j&7-A+n2DR&;L=sJ9i&bJ&G1!EBVSwAd|HGQCE0t2QwYN7%Y zh_}^INhOTw5)*(WI)1vW^j6-_y3r+ubUM}9shn4+<%r$F&;uk7pQ$(>>?9;OtiJVj zw$rr`-*#oNY=8WELY_Z_efux|i)i-We~xus`J=q89=lmpPfRmA?!`&N z006cK)#RQbvzYTh@_{9YsgiLso#6@FHeC+*J)}01^@n3PDBP*j;xjjHqs7AT<{x1` z#9d4^hv`MKPeWe3)vX8*TfWghC{$3h%8fMWyUx9IH(Tq zD?{L;eS~Z;JIpx837$ihXI>U|zDCs@j#X3$I z@8vdkU=In~GA2<#b<1e&RR^U3DF*tqdOc9G#TWo4I9;2Xd;(1n2g9Yap~f5kpm6G; z2bl8AiHfhrgc}75c}{Fu1tt`2tXR;z7sLgjDDf?JcVba->|od+Vq^1{b{geh)t<(- zLqDzI7MjdgyGa4sAmgS@U2Q=ses#4EtR~!Rpz%B&Q+1RZ^N>V=y%!;vIyu8V*1+TT zGFp5cs7BaI0F6svgQ>GESOARJ>URtF1&?Dr`QJsH3Z@IAMj?)xk_6{4j2*0>Fnf~Z znkKF{BObkKsLe-C#6QL_4BV6=GT+_1`NE0kEcE0W1L)JYoDZ-Eq}qpHY&Zl0CVO3# zA}_*~&YzS#<(CwwpZGKY9dvK50%$UARw6XiVNyIE#YBgbBx7XpWL$V(cTJeo23>7H zJ7JgG;p{-i8^7t@CHGN|Lp3MA9*}|y;`%}T=aEu!uYwoh6id?QSQyHzbp8H}ShHJU z4FXQ#{cNJZ-qd^ZIks+YYbh`C;HuD#Xs6PuX)tla#XszuCFFE#VEKyTsY}p|Y~=px z8f+sZS$#$h1-jHLK}Za+lGJ2U7)4Vk%V7xz1A)Ha6o<8Ra`ArFGCFJ;;igcHu@Qp3 zzWqsi8<@yJor$ajy}bJaKJ{6 z@)v?6K=2#fXGFBU>3+7@a@S#-BLtV&X% z*Y_2u%g+%r&|PKiZ91p4Z$@8?78eJAwVn-DjT5YBKZ>DXAiN;>aJB#79ONB#W4Hf^ zd3wMSUSdjatUbLBm0fJ4os2D8Io|^craw=Z`fPmXp4LJDq9f%{sH#_iuy?0K!;56I z-UN)1((`+22@sJP&(&;8B_rykMKgzbf+<_c$cU81OU(-gxMR5E>c+qYA5Cizi#4M; z=7HWF$7>cLBuLOuBJSBLIP!YiP1bplAP9og9f|MsN@2zIdmR=zzB6xLx;C=##j3k0 zyL0vXc2Xq0dgNN+lgMsT;gnWQ%$#3aSO3=4MuaDxqy;GK2x%g$PwplWqHP!j3RdPC zn?{+5rUAKK_9)h+@86X@Au-&Ts~YR$8h3KJK~vw)z+@h(fEmyK+95GF>#`Alx6!S< zLL~l5?4SRe@xvRTMcX1e*B%xGUp14^2|7UtEW=4H5AJdjig-2wn5hb5nkl~-;{yu+ z-5$9zDO(H-pz%NgCBmH8)PQ%?cvZ-bZauqfcTE-38fEOo#HHQ?jDcO3JsNx`@}Vn@ z`VTX$L04MV@W@joVix6_)1%zfg~uTbm? zy8b65rUez{P;B$LT|b_XbnY7A!ARN4rwEx7ar0F0P@`nx+bG=TY;>$N=@aQFxOhBN zo%2c$;YKwX4@z}^Bt`%@I23V<%^IFgj;Im>{;@VSMSJ8Ei1khxbjCDOsVlfAbBQBz z+ae;trXA2`4}KURzv`G&mDxZcNJcL4jFjx5u`XGLaU~&rXeVzFzw9_{M zpjHnH)U9qUW33er}K}|gvWri_GaD)veVwI=GQX*hetA@D5%s zED}f(SbfCuSx;ibrxFAY`2*wcaBmi!?MH^`PHiDU`ZG6=BUkSZgu@O!LL(6JI|I8} zbcA%E*QHmhRh)3o!TDjaLtyI*D;fiizF?c`5KyN-)ozFr+}m0$nj#t~pbgD5@A72L z1rhT;sD$<9^vKb_8P<5enW3ZGWt|ySJ@vZ9w>33?T?e(cqPRKsdC0aJ8wCiEqu&Eb+M4LrvlXxfhdf z4l#w?nMlx49BY>{Fl0SGYUswL%`L~^c8f~hvr}q~AmzIiUjVE1t3iTuRf&ngvBja@ zP_bHG3s_p?gV+bfO{FeGtHwGBV9h%5bz_1@SeUFlMg(jh{^*J$3`MZ z{)0N*qjh3ZX!@H#pA?N+<{CslS|tOK?33>{vqjN_ALESN+%8a?q7m=fl)fZf#*q4j+=E{YABKA#dKn7sTN7s)V}ky|ASg_~2g*|u{t0BA^Mz|loHLL9g3mEiEE1{G}d00*7X zjn=~&B@lj#0pK>|X&I5Q_9J+Xno+F3Q{-MDekgY*e=Jh$Q`@P{|~gp8{@d)!CVD^^y)k1YWz zU242Mj^#7rk#o(R*#2j4#Mzd<{^B-}*r?g%e5trpupb8)tKca8*fim#@8!K^al3Fk zyfG!>rIVun$W@c!bhrKldD5(OAQn-5_f_ECe?^{)tfT@QH0?dT?Essa=zaF#;P9mXTphm_w`PYL#b@{owk5dQRlHkv{0a_4S?H&Y1<7(L*wE zuU`P2{p%*q>uZ*pjKLERuy0tgb&c%cGlH|5fJP!NcX7ACuZc$!RZ}RQODe_gN)CMA zW`jwky&oxWG}d>AX%Q!KMFUD~k{WuTAa!D6-Z5=%0djt(7%Ad8prbxL-BOcnZ7RG$ zGd|u?^ZgxqJ(u71nLc52@+-I_^52TUtF8upQk)+vbK>3#-govE;rE7>nykEr#NO?KNDuI}DJ7PhrJAXQsNs856vZ5?&IC|)LTUgwb6L7g$ygCZ z6iuf(#%C_TjfGMP`l1&4d-$IZd>|rTA0wQ66OFSdI{h_cbsRt2U=&gX#%7QT6XZFH zhmVxNXN-l=OkMW|yW5}76kE{|aNEKGOM=f*l--15GZ=#T30yi3?|v}>7(686T9JvL zmGTs2Y8?P1#T60a{FT)F38Uu+YfP1YEb>*j4jgELCoHl)a-}vTk-Z0c;S#NDFG+C zcwr=P(4m|lOEp#XXg6m-+Gq<{QktgVi1~y(tNj<}NADH_LN1f-&q`<9W!*BHQ#t-s zFV*DrHd=`a&f#!zsEYA_(L$3F5ah28c;wOF$(EB(r98ZGeG;w{cO6N+2$|W_GxUWE z_;fuC`Yprp@?;nuN}z)T)HK8EI>URHy1*?hZjcyTVOUm^wQ!b>tF;D^vfd1jJFx_6 zlc#grN^s5pOS!qS14Jb%b5G`Vye6T8-DWCG4yJV9T)sgzhhqX6ojG2v^aC@cC1P!k zpr!b|qazOsk9bUu@9vVz0(|(#e%N{VNCi{}N-IGadw!Z>AAsV%FQxb}Gx99UCTgG= z2uWMZU8A&uW2>X21WSds|n&B5% zAUf1Z#0my;;r%}FjC71#wkg{ZzfLCk=eAN2fR7`bi9b_& zTq5zEAYg+_c+BiQD=71qH2LNj7e&+@+kNAxD1JD$xP7wgcugo;8V z;_$w=Qh2ZIjk2PG(Kj7!L!BJbqEpXMTPa>dA0VEBs}Df@A#kF#=`V0rFk2x`V1r0K zCdR1o`3LGAKE4|A_aeWVZzBv2AGN`otdpYrmGlqZ2byQkk0Y)-s94}JhaG~ki<6gA7z(yzFu zP#-k0FX{agCmwPJvV z_DB+;oSbD>R?kUOewdszuvBw0U^)AgGXN-E8+sU5%TW5{F-g>S0 zAGe1Zgs$GwiDrheG?;CGv0--PmBVJ&d!@4#&Lj+9DIkuYt7+E*9xvR33U%7?e@#;K z93|zMstz-E7!ueZM68+IqbQ}HNNWBv{}hE-0eZu6o{`JRMvf%vrmyTL=~W0SEjIEN z^N9*8#ghfG@XNjXa*U?V)6!Hu)^@3!w|~0dRXwtG;;tKW8sC8s277Rm*bF1(lA%6< z5{Fl+YSEf_-@kdHnNUy#IbFlIO3mXn)WY1&ai5Co3(UCOojKto95{!p$o3NTf|*Rk zd)S)6B>V&^Lcsy|=EjB1+j@$5IL`$sn?f?CEe+p0)<`?YjGT*7755PM7IIP%$eO+D z9i`hr^ttF8FlK#IWJ#uK<1pDe5O#ENGh&T9=vP6={`!HuD9+)VD~Inhj|xJ;O{h&9 zR!afC9?kM35+YS$sEM|ELWcWQrgygbTL^Q8xi&q&Xd3M6SE7x>pG)2N+Pzb{wLLoL zoY64W)D-hlFzTubhwBw*KfSWBi)ejXOd8kw){J?eWVPo{6p`*tVbLORFLwTuq zl7oV&=m3zXzlTp_RkZ{WFfyNB74?9Qj?hRq*Bl8NNzaJ*xNx&=v05Lcw{3v=10Jx);Zu_>F{Yz^)l+&8&~1qrH+d zC@~at!x%nN{l|j;whQ;v5HP)CNJZ>!svZMc)g^KuG-esgTivQa!=Jv06Pfbz9bVf; zu6k!s+bk@`CM~dwwR!ophOb#i)~~GRq{?oa{+lsqRk}H^-FCsW)p&=#S*bSf+9jql zq}PEP*CgKxWJu8h7ZO?%E#|>HWX5JUQHJwbwe{2NL|w2ZrjW4aXVNu^(P=yu}i5p1GlmU zjL$9NOOilKfDwmwJqh4G{K&FM+e}+i?S37l>t>HHoK%HaG9{zSTv3D4z|6;f*P7=QKXE$tn^%ML@U}7w zD9>>o)YM1&2s;O$tT))q^!HzOKDvUo z$cACgs>UeaHUR!wjG8)9It>E`b4>QyXg)=0T6(OqOA=5^X(J`cD7GlTT~VVP9cr@A zjw5`D#v^XA+Fv=8LN5FR3tI7u?UhGu(gSU!o`f&(;3zgh$src1y??i09!0VSU>Nhf zzEDN(G%E)A?E`4k%9!4F-o|2UYf91A8)w~kJ=xjXM8)*e3MpoPQTcEnHJMe*4Sdl6Bd^ zuSootHH)|GAKX9|RZn#Bb(R8H_a_rjLmhVBiy*3Y0vc)>jI?)WjbO}hjD;fG7n$V>h=ESaTWS&H@qN*mKUv>!#J!yT zFTPQR2kNxm1I!C9@U8GfV~xWwj!A@M9eoY7;d_v)-V7TyQzDvz8oDh-0~lD|Xf>Wa z6Pf*&=U;P^9dS9hkM;`bV|NJlRgLGa-;n#y{zcm@P@|G03&MzO+SQs-*g3lrs`*4E zNmL{XK&DfWwE&Cn6`8&w@ytX)G;>Q^!;Usi9)CF!l;odoa;L~>K)D%4jgBu$yC~?I zK(&P>bbI;2Ho%c@nP2;M-a9cP{lQj7Vdh2?TRYJg@Ov_5g?9AfA6L-Pct6 z@*WOTQy252ZW8b-`>F0&0*@Y4esCiz~AL7 zUP(8v|IY$6@2xQKWvYpMGc04B6t#Q#!qfijGSE}ZUS*u|Nb0_}H4%5RHmEpB5|ztL zW~nK#+(>vK>R)_sugpXs--c1H0?kw@lm}jvRXe{?DIJNGd~YIHxYF^`MpYAlY*bV{ zTK;A$x(aF843T3u9_kAzcnWg}cRCs5yh>gj{IuE`P;vU*GMSTYIfvdhQ516(TH0Kq^$6jShUty!i@s!#pTLA8Okrg2?*5+ zgg*C}L%MP1wDg?#B$s2j6I-`pYg(d>LWn}@)y6Wm>)^sbjIJPL4-vPj{YW=1@k0FK zQK#eix!6s~{FmhorwmGAwrC??QQ`IQs%njr(!Xsdi-C#89u(japJp`ej0*kpS>yLO z!fK+t>2+)2O<7fwzO(g_4QxFphcAZJsgg_E1p0K@u=(%ubo<`=Lg>K;i=yL+Ry^Q7 zgWz&wXA^Hv61nsPH{-TL9jEb=$P|hDpLjgWt%yW#SC!5~y+a>=^^_9NfNbtMwCB^b zQN4*<1d++#@m4wK0zY#Yh74td152QyK6%;8=oJF9Vi9}xZ&>)yfnhvXw zmq)`3@9L12Q1`119^~&(_v0HZ6R#vjyx+tjO8Qnar}#y}y-I_sE!ui%4>qq4NTz^EtfKjvp~Z92^pK2@Glj2NXZm zX{3K<^szELvlQ2relz3zUYYCtB2vU#-gzIdFzErFxPCWOc#w2hjo1W&Ba{j;GkLmC z7w20sn7F;JgF5`e@*~-}jD!a-0t8sN?BKF-y^65b?Ynu|cMfJpl@R(^YU&z)#VD5w z&lUTru_gNmP1h6xaEeaQw%daI zEj?<#67&l7Q)M)NT^u;~Cdxi| zGW9iQ^PE?eE4k--M`CXa4R^w)isx$%%}e%BBqR>mwtqDqvYX#4RQ&9Fg=z84fTTTs zuIreuD$Ex@bk8d!vhk(wPkM0tO3{-w)VgC}&Z%VOv!0Hz?fW;Y-B;u#|JwAL7HX!x z{=m2VYl~>>5I5l(f|Qgr@}8=|-cc_uB4*+uZVd|Hgn|-|8Q+^w3|Lm9MXwXhc`vsG zU?69pHPQ-_!9Z0d50&<7uYFjg~S)6B0~>uZ*Ma&mjB$M1)cD2O!D+*(aNvr?~g z*?GJ|YqUZWMCP{GD6Rgk0OZ6#uy;l#&~S$Dq>Y~fky2&0B2*BsR0Gwy2Fz2DKJn8> z0(YDLT~XZPZV&D|bS|S?9~pREH*nq*wNFNbg<@9u#vk|NH>_qfJX4n2p4)Z@eAKlM zdlw7-wER_5W8ow`_n8?vk;yoq4j112Z*bhD{X{Vfpapum$zE&j6t7=G{GF1=a5Oiq zX1PV0rFAtUO-)_b20Xis)JZP6wY*gA=O7EnXv>i69m@O?8jfI#f`OTpo$sFa) zpf8irJHIa9eS^LFGv8l~L53iyq?DfoT;YFzEv2lzJ`UUq2n+xlyYuun#FwMt zo$}Dbsp!oI`%b9m(BISRzo6?pW2b{xp0ff%$hQZi2M@JX22&P%Dq0(RCP<06G&s$| z=k7C-hKk)sAjdD?PV6<`oTPSbIxqJxhlZDEFtf;&HSL_$hAzO*Zf{<5Kwm~4O)=2x z34BGhjp0_mmijphsFC&nJ#63^7|LvD@Q0y zFYy3zVpPdyF1#Ywze;={V$pV`!$17-OACQu3C-_{5&O0H&He8Evx1-tCvCF&ulo!1 zro%I1HEIUH24@&6G!QrgJ-WHRIUTrH&2iz~y_Sf+7UbZJ@C|eOEj9`k`>hwV>6}~o zG9CTjm3YA^fd#OS_kR`~C`U?2(LhicOi5;WNQFr^Aeeyq6Mq~)nM|jgY->nlL>{0! zw(biz_FxHlI_KfPp2qc}u#%Mt4UfF)?DmD9xn9}oNj@j~Xet1~Hge8uF=Rq;zPCI5 zqlwEG(cU5W>L4Wq$8&vnnT=$mewpX}u%m4=Cr67O+P;bQm1^5V( z#J|r9!UMRsNN0PgQEAVPAAbN={g|N7&Z3=B9B;^pVQsH-3h+?vbD8pR!ZzNMKjQud zE=mjrPAhCw1Rrzv=_RH9RwRyBx}CWlg`034uonf+#*}jrBCmhH$phgk(6$b>d=&e9HHW6D<6usQdtQ9n(SHr$`Eg1|q&@7}DF zqPWORy|nv&xDV6ry&U4BU2d`R{#I7MwA{7q-&-8-jNo9R$bUUo<_^iO&?3cNos3*f z6lDW*^K;*;ykQoRe#YZEHgQ4M4$-lC$}>7j&0~>A%ALD_lqNPM7)IqdraydzXLP(( zXET3H!M+@}M!Rz68d%UB8dm>OiSU-YS5}ftHN4{8ta4I#yLDz92p72Ha>^0TInFT5d72aj)m#1 zR|6%Xb*nd;q*&>87}T}X=Ed-^X=%GQMD0xJCfnZ6$g5x_q&};pemyuhOM_{Ed)~Nh zHNN<+gIPhT^UhpEfA6^K&0hoj_0gxZu78MUlS*ffFUEe({4$zc$wzkw`UI3q59?-Q zvuD!LxL!0VhN0O@A&KmV+6$?Zc-qf z{SHJ2ou~QOc*ptp`(3sz9qf7ERJOYw!6UbIRt{Jdu8&+jAO;3CPp(*>u;tNzA&7Z) z)tFg04-xo2+8fNxFWHyJ2dG1@+^nwsv|=AABWdSBTk5fRK`iYa=LAP1l>V2pZ^eX$ z^1sWW30ZgeYgHH~EoZ+^1&ev81sAt?z*=5aQ2`kSstM<+>va z)3Cbf`n!nIX(Eby7?0f!SI$AC(bhoeW~Xb96eo}Yn@@W)qCA<>x(&|ky#R}a_K$Kz zzJ*STMfr8-;r!m!HM``EZxKb6FR{l&G${Vti`>B{e9rcr&wqT%qBt;q8XbSnFx$Nn zAek0SL)=cM68z$-uQ;_RP`H1?d4V%PWFwDuIf0w2C8Hq+S}0=BweyhcWjpxwtnzyS zxq!ND3TPm4HNvL9QPYG$oRo(*a-Oq96AnX-jVY5we~UTSo?_Cl{_<*q^g~nZ9YYX2 zSj(hfbV2iz z)iKVTop#JG^X<`!Jg@4|s6%A%O5YE|JV-H3aq~!0YUd4{^P(?5@Ff^WwL+#qGbJCe z`nd#MeL5O3GMcVkP`1b>!Jhn)_N5H+_vMPf-_;fT`01rnb9VRj-{w>=h47=TQNLCl z)|d6=A_94V!I2BbzijU3S=ZJ;w3+q-{7CRMCkPY!mM zQeUokFbk;-)`o42yDA;LzTH~7S@~7$O_5^B$qM_EW27l^+}k0XGE77Re%??D*TUs{ zPiuQVMR|C_GBa?`>FK`i$7Rxu!weVp#W)p?W@j+N@Y|i*utRBnsp{A-A`EBY)qG{k z_}u_5J%2zPBrABNbyiBE8NU~%G%gl-@g!h>u4{8QJajJ`eT2#mJH~T%1Y&RYj;_~s z#H`5^ z#Qm1W?5z!apcvD&UxI|xyBm2<9}#M1riFv|s+>@;h^$D3sJo(#!p%tF^+4^-=2^sU zE`Jlyg)te z^@tf0VUci9bv#W`qkyx?{{c1czvP##LCYyFq*TFa3v$wWT5MCdLDeB=6I46PdpFKP z&#Pr^W!dfJ@2!aaRh~FR-~8*|oVwUD`$_6Uk+Sp-|Fs?O!G-M^~bWyDmIfryV-O_wi$`%IT@zO2Mtw zw|jHfNU^Ky*R=P`a3A;OE2cGA+HWR4-ou?@b<_y6rfh2DJV2qhSORPy6z$Ao7I4`*zy zBv6z={?%ve)b2{9h~aGfVC3JMt6L;X_>Ggh*N$6r`_PqD(ZdH{l@?dXp1du{UTFW* zVeLZql=D}sSHqsS9Vchx(H%0!9aS`-lrGq^w&YAK-|d9DLTxP?od3Z#FBY-LI1orRJ<=FzJ7Hht3Tke!n9 z3f&*PQrzgH;A=BTd<7~qfNq_IpPfbf1YzX68aW3P9{UQ|qC>GTGc$rbecz^n$+WQ& z!c)&JZZeJ}ij8nCC#kHg;N|2=2QAJI_5YzLPK8OEa6RRkR`9OrUdQi8-Y(pYJX;-1 z*^GIt=Dt8w=BMfI4z@FP8T2_g+n*@BvbYgaHxZeMcy1(Z*zWOP6cOEE`PC_-U+cdAuSEz`> z)&*gjRnvkA4~V`-vtI6gL0{|Hr7D8=`^LL+Aj}R1Xq`Se!CL1lgk}D&PqJa{V7p-UU1^%`7tg2;PzCl@WlA~ceX+hR^I;dy0bwvUCkP< zs{Uy1<)E;;hx02nPBl;0K#mgQjT7&PV@4G7Q*G+)5nl)qB#93+XPP_*Y~K$DIqA__XYc zwVzazdNU4@a^8GTJjQjjOe9ouSI$eDSCl>ysJC+6XMgrSLOgcD(6#j8tb<#cB`-K_ zML2`@CsPNTC-#)~U$*@DNV~$la(l4KUegW z4rpd0cUMY-X- zm6qo1RH_G@5o@qILwpVRr!-}&JU57ViA9sYHw#pLusKnGd6+TyWSM+ zS?1{L*yb+0z(qBIg`KJN;*tFO*q=sBc?lnST*#fam=3<@Su9P-ciAlZmcIu5p`28V zLZ8!J_ZdW@C2MaoLS0?slTe>?e)L7<_Qby4RTk8g8 z8FE~Pi_ynFbVxETv8m8LH%%|C`}d>1GP^M~ZA;MjcMIjkbK=Xxum9W@MxJ@IRxp3E zEyR6$Ix0mz=O6C94qOCq4u=w%dZv&>3FPE{xcAKh4q$oea@zNnXa(@rLpnQSyS^nN z8bxpd^Kpi*UJIcQAmwvf57!@cuWon$%^aMnwP@?Rar4^|6`jnqB_jLi z!(R2we3_ay^CD)SFj~RE(fDqH=y!GZlsCs6K1yJ(Zq##G9gy-P%@f>{>QjzQluq}u zU*f+X{;O7HI7laM>kaeSJulgy6qSn;;nS!@NUXXv4lCCsLW&yh~={rotyT~=TPw<&#axy7Vmi}XlXX-FXEm(+pl%?3wi()x=& zSDAUG8?_>Rm+8LGJ0tCjibDKfb207TK^3;ml>i6J%z#yi1f!2^j7Pr*PAC^hRpBCJ zHCo^$2L{}F_1CsU{L8d}#+NQ=Ee+{=c(&?H-a90BIIIP!E@+6!qy5b`R?r=Xv*|Rp z&A_bQkOng(gazB-2BJ`mHRltNB;xU)=%kbyr`}QkQ~U%-zT6Sb3F=n5?Fx(>O}4Jn z{k;6Q?e3ZxaF4bZZfwost{=>9A!rQydL-^;gobl8#;G_COb%dVY zr^t4vrHo*0>(ZG~uw98>*lp2HA!Rm8GlE{kdr<}ryZ z1!`n&_dNsurGfvKYkFaF&6sh+234G+}hW^Bu_9 zdA>F{fJ`1g6%g4MqnuC{q`QJNBr1v}?#I#5&F9GwQ^$+A@?<>aVM>~hSKxLWCuf(f zNP(`aM#K4(eHr^0nZ~2%VSGsQHm?JEh&iwQOy{F!`}WbpvKsus>^< zL-;dq?iPoIQ2VmpS^v3KHK7qvj?(bAZv*%6r@!XHFSpDQLmo(XP|8P2n~$-}e~6f7 zKO6eiBo?oJZv3eJCU$F99M!Dk1Otikb*g zT2;%vPxT=IY`AGPmuK>0n?l8At8Ge`qJKo7bx8NlI7RN2`7=6FCl&4k-9PcLhnFsJ znW9I%QE*{)qZPziarYZYdL6 z!Bj|3)bB*+RM#Xw68vaIuJQI&m5GwL`SBl!?^58 zeJgXHFBHZ77$SKEN2C+`2vCv4&eNw^ zB2hNf`R|t$k66&B0tRqc`{C40#CBW|VIR{@=mUv^sEprlX%AyR=B^c;mXpfC&HbB{ z+AG56uYg-8m#-R~Wlt~GKFe0stuOX-2vs#^SikH1=hfBGZlUt@N4fFT>==}mhd`cD zM@VBc3v3{6!$neA#X*dZs2m6vT4RG&y`;go>+<_GC&I1g>)u4zBnDG-#RU zQ{YE5$*;}7H={Elqmz98D$ZqSN?rM9a4l>i_@3hD{k#&^3G2e}@h5S0V(!K!o+pBRB(W}U z%!mAStd*H^>F6^|Gjb0o*Y3qqU|Kx*gyPHeHNX&(P;`H~O#Iox0G?@_Bw)269!LC8Jd4a+GMGLo^Sj6JpcbJKnva2tlT8aU>=5V97eDdi}eV09F%o$ z!jqP^OZ)4*;aO1Ik=)z^4+Z^d>l31Pr#n|(o(?OM6=s7Z=`WNTp{4G7mn(Ut&NYcp z#p?%C##t3;uX}H`%;3R;<>HSgKGFR|Lac`;UXv#J@B`?5N3CF5nR3)on(8=(X7dsm z-?vmPbXh1U?N`d!1S!9lbll(<>>u`txF^pzLN{D*)(vjAaZ1X4cbWf9=fv0g^|&FVTzpqxjo)+kFHw~_ zY+P)_FlKIU~i46BxcR^?oUyCnrA4Q z)gFq6ZZ1^c?~P5<&D$@Q9%?=Ay^}Niz2du?)4H#N7I?DpSYB|76&ght*_J+fRsEPPS&sr?lM#a;=V8ooEL%w`^aFel4!C zho_|2Znb-fXNsP7;e6PsKdCVi!4dAbI&dBK&xVJ8F4u!79;+=L1}RBtIUBoJI$dkf zXt6=(VJpk}cK0<{@oYQ@@GM4oq7mW1%6?_TY{F{3P3Jg6=y$Tll`fXUT08zC4cPu>hf|f}@mGno z^3Nop`;PW`HWKuz$Lft^p|5onD=TGwEfW12tVs+Pnl9-Vmtz^KBU;ZGEG~v%73`ZJ z2vo*XG9Jf$OKMf6^9W_EZvo=%+x?EahE4=>C~|^Uy~M?qMVUIS`u_m;KnTA{o)>bC zokuh>C@Hm40C4DfVk*;I@_ab#1reD`YpTi=RMb>bS`a|Rh%u03=VR9~BB17y%VAP2 zhQN+n&Zf;XmMI|t`+!W+nlxeODRw}i8^&7FZg;=dyvz$K0zxUZ)@rThlrApM5oug+ z;Lj_9E_Q=~F8e94Z>0gSBX;0qnWa@`=Y1H50h|N1w#-eMX}hANh8U)0O3UO!U>{3O ztD|k~21IC0wPaIiS`ic!B^{o4%^Q^9XmwY4i^{q@7}wA@9yQ(v?^(|B|oyOGf~F&7!Bs|FwY_zTxgkF;VxxQ8FP-D)+1{cd-5ep#1R ziV;Ob*xa~{El2MNIp@U;92$E-auh>rVydmyQWIQF4{B@D%95lu6hMQP7im>QBD0p_ zLl?U4>PIJ{R?3pfH19-3Y9+^;&01AzODET#n(FC)_*qZC?RD<-;p2Uw2pBmC>0!TX z(sHiLJbQNc?mbYYVOZ7aQ1>}TeE5+kZ#?wG&Exu&U-CLWKHZ$Y1oIr@F793bn8vG8V!}1T^CzZ)kfa2_o-$yhGCAjh_pN2H+V!0$)*|e@3V7GmlC>5^VseZ`M5Z8BT23Nu=vU;ZmcmTL zobxhIyZLaQN@^vQG$XW9QY+2*aC~yJRmmwicENixx=Q%elCKbGChB7khG4|8_soD~ zie#k5gk*@wF2ug%ra(j_rt9^QHX~+qUL-SvC#rRx=UHk&Mk0>hL2X)!)=Y?{WFKPa z``~>_i=dM0rZTAt7}Zwtl991rANg)@K8h*;lvYz(1hk3OoYJz0SSdND8QPU2!-9*e zaT`~ge!cF8k%+*Qh(H=TTyHmKa(QvK`s+~RmC*#reZdj!}A1-#L;v^+8A;cmQoOhlu+vVJb?aF4y z1oOo*u8-z4dEW!%d74T|`-=k_o!odL@6TQAeQ+uc9XGXFGIH%)AH4%4)ds=_BE=lA zni--RgjG~QYAmgqnwS!!+Leb6#I%cD4Z*cyh^^G<12oAhDzMpJ?@yojl6Su06dycs z>)P%BPh4N$-Rn4ZW_JJXnQINqwx_2q1ZG!q`RIo~nwNdQT7l4==U?atdG#}=|MHig z`=RgrdtdqW-*NV*|8|=0un$god%V>mL|1}}3cwYch!IpV3G4wtksY~as?wy^;Cs+U zu`?-VnDfG}GqBo92)%;N)6PU4Mfu3}eV`+scIx<8HSDbV&5#ofn2x+}^m=>YQfy=m+2T_$%HFz#)vPIj8Kh zq`qI*R)(${#+9<V6Cp zo)Aq+Ds9{zgIX=wAvjMCtQj~LQchhzT+KZpApjGBDmbE+6EcbzJ2ccP6&)?h%xrM9|x=<)lXcz)G`_cY}zD6N~8!T`*;*^Cz# zdsV~MER5_|HQ(XT=bX)q8Hp%(pKEE7x_-zhg9>|(1g6T40AR_b>qa7PfMM)gTCU!c ziWFirsh5|@F%c1ku!?;uNez#$T^DVl;t8hd{6F~2Pyg(%~zxTo){>GdB z=(n^l9+jLS+Zc|aG%1CM%ryE4gdBYrjzC4K6-hCA09R5$1Q)sxBc(-KtF1X=BCE~j z-K7f=h*ULp(KD3Vgj(<&IiK64T3zrV^sQD$W(a2LLWiX+)86^e4Xb6It^x~8vXqL3 zXJ_|g>|)oM7_dldgeV9hL;z^DHqp@guHR~FOP(xr&=e6_&;>{2(2)v&)T$XO8YqGZ zIWSB$`QU*N3G%#1lWw)rR?Tdl55&H-N-EwtB2)ta6v?$Hk!ov*RH%sxfO!r@#8k-n z5W3nH6Gaz?t}`=fiIDPqKq62B#9_7Z-nCM5tu6#;a^-|Ym6FT)Xj5u!wTfit0|JxQ znip!MV&u>Tj}}@hWuM3OdW>Bziz1Gzqgtx8+OQB)&RJSrZ?`!W06IE8S?0;W961F= zmDsJ-AeU^dId(_{h}v4W8nv`5@rFk)wTP%6Vwo0-!2!qE<(eEhY0XIUVL~t?P?+bb z5^(+IkJ! zlem;T#MN}z^>MiI@Xh6LC{>QH9f{cP;tZus1@gRCh^R}MC5~%H;&NVm^w9-`qxrDg z#_VV-tf*HH|JSXHd);joKw z)%B|;;({aegicI|xGswknkayHPqnsXcVTvQm|3kg#E68^F_ROmt%(n4A`bAdy9BV< zk3?>{c>YbF@fNU6YD<%;GuN#?b@Sw};qU*d5&CYG<~@)jP(VCdA3yu-hr6Ng!|HH( z|HCgnzq`om$Z_+MXWss%PyQlT+kEG_4>vJ1=US~&F%YPQ7|lRLq||y?)Bp*HxiyfM zgNsdtz^20v?FxoWrqB>T8WICxU`FR#ks>X42aGu_W_xDO?40*0O+Iu)3s0MW9Pvnl}t$91u-4H7eHyn`#yG{YJiB$#Qz^lZ~tpwo8NVP9f zd>`)np3dAeQhEmER{ zCu`Ca5HP@4B8Im1k%=>%d**)ne$P33@3q!-U7rVkIQpnNUd+*?mC5JS{eS-!IcNI|388ZHpLXsy>{Vn8k)8da`k~%!nwWEb85o zQmgjfR7*T@=me$C7KY)3CR#rA=}*1;`Ja00lP?QTQ1JY4?QOlidp364#>P4yj`Z@y zm%i|&_qZ~X=u)kR2=Sh_NzQ6y@ zhwr{?U-hehDKS%8cqSmkfDA;IGMjbbr(w5ft!-N~!qUsKM5cRhdsme)LP{l-^hl58 zxP$=-$LQv}4+kM-c(f7KNaQ1b_r6I%lSo`n2N(WG#geSKA3N6QoqYv@zCoLjY!-iawnJ)8W8K7ADMP zIgZi7b3U9r0s%+sdtaA%*0PMzkjXqSvn~s(f@ipyjcHyy;_157!ia@bq-sV$T0)ZO zb-gMJWMuk!d5DC_B$ZMNF=rtW8Lf@B69SdPzO5O_$beX?Mvv?Dsw&LWyp+dtzuw=U zZl4iz8-tbFc0rQEv_yap-&#Xnk$9daVu3_q8pE5f0vW{1e9VN=b_5vKNyJ7>Qys(F zzSm{pECGO{>wJKS@N_pqEK@}yM2p}IBVm5B8u4V*z%jrRVV)+pX!|zL3kZjMt<&E2 zd72q@WcOi&SeV@-A&4jwnI$}k@x1R$I3H$YY}=|V$T}^HHG2X~nR(wgK&bOF+V1-n z*nrWtMnYqx@ue|ik)w5k7E~A$-+AZr z%Y3*w-I#mdsWMQ1HzTK;H@vsRew?b5`2fTrupFlJ`=t8t+MXTwbUb2EH#gGl`dF2b zQE+yT+uOs3UxdSpH{AQaUMtNa6yb!D39LNK!o$Pe&Bg`^$lCj!>26WyS`RfcCa&Ru zkz?O8Wm>|cX!0}@pxHnq7734t=v$K_n5;?>F>LJHD@Lft8)oi4G6Ew;YuoD6yhH{> zYMBx++#nN$l-bRQm;^n2wA}_W5><`X`*zJpo#!IN7;TKpWfhTHm-HCEc_0T4vm_+I zjPwXbl4+8;rrGE{!jMRqQH26W>j)sswN!H>Vp(SJ+_y`3kl=py6q>b0+PYpvI1?~& znim2}0?Ld40NB?HAsHeNsmO5a>xEfJIT81L7m}h?kO8t^*A)Vo=HrcL_SS^Jx=(e^ z$ks=l>oVPC*tT8+qW2N86ANZA$?{*T6A6(OQ1wW$TIcq z>X{-^=fWrvwq4GMEL9l<5{PiyH}?$@o^SvF9NV5qsu}=32Dx=lVcH%ZBeRr=iVo`p zQ0D`rua~Q;4j*7)frsVbZV_WVHd0BXVNSy>qiV@Sa983n#@JhLt(H=z1Li0<+|4ja zm4^-C0trubCYUz50i+bN)^W z%W@#%ecj|Z$JRupOmn+jkcos7j8H~EW_D-)@G{@M*qi-tKln#}{V!dtl*{$ehA9;Q z7-q|I&^iU%dRY_m^{X$v`R22K{P+HP8~>>u?*Mz-_u(BZ4yT*eHtSZj6e$qi+J-De zD~R}TV6kqZ6hRrrB&X*u0098J_otVw&f0q)+a5)=*31#Z5Ku&j6$r<^+x0vhj^+ar z5u;x>QLPx zq^VATxUbi~KORnZM2v*P0@73`s`Iv9M<4|us9^vRi;Q9JRzxx zNb6MedbwaavN9_XPt$A=7yy|7Fd*hqd)wB_BQaqj5@a&Z)3Ho@@7v|VjHJaz2LOOb z1O`F|OYi$S%;xzZM@@H&BtqhPK-tHNKmg3l$~xMrs#>T0dJc5vQp)7!9?qf=QRgMn z`@ULh0VE<%h(3vIkM}_F^5buR_HV)c!@K)1XjoMW)T-6oy!Gw8UDva)eDU3%hrac; zs#YW&y-Asi8Hx`*%<}Mn$?v@T1tJ)&zIye}CqDL(3FUF?Eal5yyoo%uu>tnJ!+d;( zVP*p*BDxZ{-rBwvozepokfim_M8wqgD{}!(60z2r?IJ3wVIE_6#Jn8$c7>@98*b|~ zAKJd3of!ymfiTVBdw=tjU-viu_rE!{^~GB+ANJ|`xXpDw-9B3% z@6CM!XzPv|bD5j(^>BE0I{nN~|I;7+`+w`3zUkY)^mCs<)zj&2?9GSOX%=pM7zRo$ z6`^l?hmEyj6-k20JGres)k%%xSfM6olx*&r2==&}r$?P!*#YYE15``2D z!~m@Y5LtvsP+QSDx`~zmZ`(>hNQ?+7wfCKnfGFKt8j~XO7(+TK?PYXHzko3!?IE7Rmjh={;R zKDL!vEE5stc?P6}TyAd>C?dUePbX$;n-&FTVaZHdWEj^iqNrzi_FY}B@%(R`)T2xDch)59D$Lo2S52fhkd@jn# zh5$l>1c)%&INsil(X3mh6O+3$kV6a`f(RDpc0WzaJk3aAX~LY5Bw}W&b$Ye{VP97p zLztH14I)Rl+Yr=lG1X$$dNU>zRc3Ox5KyTxy0ym3oMr&1%8`LYf=D769$-QR2>>7f z-9{<1NG-J>!ufht5jTk8W4pZZ#*3}N>E?wj-mhmLKvcT#=Z8m?64q~Cd}KZ7&wp-D zj`{Ykuj{t2C@F2Ks=tS4_oa* z#DU4k#MD}cgwc0I4r{6t5_;x#eIyZ`Dv-jsmOAad{=wh>uYc25(Cdfwboa9A8<+F- z;oJU$nNgoywtUAn)&__{xU#=GfV3PB`PV)?owM^mO)=ih$x<8#)B88qHi|_y>N-5KIJ&Tk`0CWqF5ecdD z($`(*$|OKyeMnIx2G4dq_qNq#QdJ-nmcBQ4muV7^Hg?EBAOXUn0OHVC%5*$2;;^Ap z$;5uSjLny6=?T8B#N-xJnXYYZ*Gnxm5h#Ho9TRLPVTjnT7h(~@-p64%baMxP@`x}n zForn+w$W{G`*taHo~H7Ybs?lSIuR8S&zMgqOUv#+Xx5X_BN@_dAhY=Z2125_%%N@H z_qGdAWJs+5FsZ`u#CZ1n#k#Fw0}R4_AC^8a6N&coIRS*L2aeuLDMGmSUR0EY2=~5W z5K>bvd)qQ%j17VaFfp1rs!;A<`Afd;^#feDbvs`U%Yj(iX#H_Kd-2xgazEACgHZ_) z5^A;)-IztW2qC49;nufYP7DrayE1rxc zVL5syKp%sdBVpfG71pvupjjdg8=Xbx!^uZm_iHHy6Wn_|k!mdfA#GqqPO*$qr=`fY z?__Av5Xr1ZrfQj|gWCWYjvCQF{)@ly`~Sk9>+ihxkze)ly?2NE>-lgnA4quf>=rTK zfA3v@94=M(c=Pi1`DygcpC3Q_`OA;~-#_wee&siQ{{Q`qgD5izn+;50QG<-|X+A(E zxHlVu&?6u*69plFsMK`Z#=6u)04Y+sLt@eDVZ;mor0D4kPYgU0CS+Ef+TK+-5~srn zkwy^ zDq-uL!j!1*E3y!wLoT;BLfZCi_#j1@rln|W9l$BkEJO$xBC&5*W+4U$8;KbK0|G}` z7V0|?)r2kF(}nfCr*oEu001BWNkl2O$1V>HMNkKTtACKg}{3t|P2zHbC+W~>EOpB}J##IR%{gcuzW zGZAWKRdfTuh_u#*b=9TT5?g<`e_vQjoqY^cp0rr+#G39Aj?9S2NK7=$P)UoLc|zpa z5}fK1-iCp&0>$31oNr1tHkh+f8x{5aHtOBrg<;UEcI~Ewq7vua5%9tv!2fm zwolWvZyl6vxG=GlDYv!l+t@rDl>Km5q|6oxl%ut6+oz?fl-udf(%-wjLkQ2`_^9_L zBAJ*#WuEqR-S&nAK&*8pRgXbP1SQkVMvT5JCqQ&aH?P7}iub`qTKX7+RAJ`_uepOsDVr{r}EC|I1-*Ca2lawr%x{Q@uG~A3yf?C;PHK zJUo;cm!*2p{fC!H<@VXjk37Hpz#sbezu~X__`KXSYXr<9$fESLTpdNUZF$-a}Oq9Ed*%2ArUEs znOl@vs+J5N`xar-GKY=PT`J|%f5r$Wo|Y-XlQ7Hx5dc^exu5`LLO#KN6EU`ZTX$RL zL!_?{k9AsRCeQ5VtVKm2qP4w>I#HM-7bLVk5(p(BZC|%O`XqH)PL>hop1_E0bXG;e zF$OYA!nSSGG+Vdf4Vf6I_boSQ3@W7qcmyLPrtiJX^Kv*0>i}ZmlaxK}a(zuws+Kw} z`@Zgbt5c1HKKfIW7cupHLqAX6AYVPrrML?8g_67N)2yk`@?AM52rYW?r;lLa$Dc}(ZQ!;&y!p{j)Tx+{&;Ng)J!*Ra(LJ}1kZTOeO09ZWECbQVa&8)@l@uAYUWxh5e>6i=Q>SeSh$n0mI48zkI3HkCRGWE zSa?}R>qI%#rHze9$T1S8qgo%xQc5xCtW3=N4!&zvz=HI{FA35isuWYr!f&5XEV`csF!Jm929G`(021qAI<9&3(K13Ha|{&>15gr}9Ak_g<{_j=!CFrzNbqn* zOiU8NkW4plX35BH+m1I+uAy-_-N@t^NY=xR62~wU3WVNQcT4xMEdUj=_n77yNgkur zNkkwrxLUU)Qf5ydZ6M^>_c|}j@wWASbQ6`#Oh_W$_l*d3)^ePS)QDIwkLSl%QWjzr z|)s{+-vi&+di| z0&8X;dHG^r_w{D$5B*h8}orDw-K-1k%chAg2Q~)r;5onAqn9dw-0I5Y1ayy?%lv$_4 z+wypp`Wi0@9pn>Zph6v*Q1xs_gt@93Pxif))^rqqt>NuYjEmRF{!oP z%~Yt?i5SxziL7-NVdXLQ(IOFQEs%L#ujG(`DsnhXd+$D+lx41AfS9(ftjb6p;bV6T z(MhW^Ga&ge44&qqMb>pKr9x)EUZ;6EoMu3d%(1V&wWm_C?RvY1+}?=JvV! z`0(|ss>(n-NyA=79u9zB2FB{4hnkQ=D5Xkx0(zMh41~b*KAB)H^156^?_PvyXi2d4=VeDOngz3B* zP)<4(DfLv+(s~EsDlH)cu%9}YY|@|C~hvtRh&?|lCs{XO6D<~#4+OIfbxOW)Vi zv)j3p58izr5#IR7TaOR-eQyas82i3wA0o0$la%tscfN3#>eqhF{D=O7-}#q*BEP$ z%j1Pas_1rJr4&SfL=v{)@U&QuQI z%XCW&ZCx!a#yH$POOHgb-bQO>Dy1H9S<=ErA7fY1ME5b=_9q#;*_aPUAaFN9?7e3? z0EC%{dIDDwQ4jBZ&xAV9z4s&>;F{!Zq^A@_fN&Ej5l<7r82c`D3Phw(r1N!!dO9=t*xa@R9DSeWqn6S}i#7-;%z$Ys6^XX3 z31ehhA5^r|`qXzOM5We*3V>23uIOV}AE+cuW@EXz6Or}&n3hDew<`my3K&YMX(KaZ zI3hq25>?f<^&Q}rbyx;515x1-Lr~s$@e%^|?HWMa{li8W0Ky?mXLBr)FvQcmcT6<5 zO2yIJW(o1^?(X`~Zf|e8+3o3hLOy)(B?he1@%-Wa&5M)P!}bwf`3X1gqens!et3O8-rgJz4uIF|Hbzq^(>(XReev}d zn23a4eB=%IxzGJKf8gKzegD<}`1;5G0a7DiW%e*p3Ba%+z!9nCNjJKIjYJScw{C8P zKrBd;3YlkFmecfXJzw_6vsA$Zl_!;G%y%bnh%syoCW?UNFqwHG<~ERXS}If7x0RTI zz|5yw`anV?;lAy}Saixlh!|!OK1MI|EQJJSpQrWVH4=#^5D`E?W(?&T;lr%dxwXyB z9lXwmg4%Pf^C>a`028e@1UwvXBEljJf>_$V&dZXKDHM^`Ix#@9rw1VeQPFNAw*4t_ zAOb-IpeX9@ghBv5wq+@odq4Y^UVQBByI=h24_|%ohrj3d{=g6XPr3I`ynv4^@ZOKZ zKl*Fm|KI)RzwaBr<=ejZ5C4g~x4$mSkFWP<4Dd3Ma0F!pWFmklXF^yHi!xUPjEE6d zOAU7xfmC3=?=C zk$E^h_pz_*TILBOXsVb7Ne09stcW(c84F?JzHRQ~c)IaSL_`Q59-|pVCK59Nm>IEb z+e%NNsD!qyRZ9{M>qrdf9!>Uo$T!@u<2yBKA`X{vI5{IJOU?8RH| zqxWswdZn2`TeEIG88DvKy(pz1;dWhFn2EF~0&Uwia${8y#!{ak={82G)4n$(Odr#< zK*qXW0cf5lA01h@^BEACkcE(eaKirjjj#UZANc+s`N@Cse|-19^5*MTcKb}g^>&%p zHo``or(w~|mZd(vzUK^Qo~I=t0pa!Q*IMggnN>6)FX#B?um8vo|F3`cw|wWn`>C(` z%6ESDr{`(e+qQ348mwg^WS|u0035B=sixF~h+(bms)dL>oP~gdAxGaMhG!69Brt0@ zMn?4B_x(v+r*-PBsYoIrL-Z}FWTt?&eRCg7K+0+1?(;H7H+P$AX>M*72vs#O)5mnY zrAz}x#D2NVr(0$nKH7GjN=2aka^CG>zB`s>-mk3`)oEeoZCgVTN=>3Pk6~q=10XRX zVDGK!%$y12M7sqzB;;XU`sk5CtT8;y%RE0Zg?iAoH}u%nAypuq3p4PDQ7G4Q5C34A z@4oDp|N8q6$AA6%fA63AKZu--aCXp!D_DG~vb7Hh`J5YSrFB4wTv*hd4T zQn>XYC~b^7&11AcnWyavW0eY;;y&t-&B z^K2Bq5dSSfgv1H3ceuHEc6;}{*4oW3+qySPf>#gk_weKG%bS~->e9pN#B*J|xer$@ zPsfH?5P}dZmKYWoTD8oxh>UGycmkApnGXj5sLMRhOCp$$1YAxxhr`lZi*U0pD%0_h zkVMj209t*1y0qm#{_gMim%f?XDk(DY(E?vTyxPuZtD%=J>4>>+>vnxi zcibK4MCQ9^FF*En znNJZQgrn`?-q$VqFzZSTk+n=RP3g|*x%;}EG1DphXJDMw4CndWf{Xp?*Kqjw#Q4~w@hOqCaQH#R)Z8k-y8VI41|$S z+fKkIv>pLLB$Mjr$1tOmeRLvFk#H|k*LCfEBNP>CHrn3y z^-_RZ_($J9edaS?cy)jImT&)^9DdV^I;dzF}gWYMNix3kzEx%nTkPT5An- zAFiS-Y~wmfy!_PH&BtH%$N$V9`F+3RU;WJA{43x3HTjKS{l?bwyvMsA+_NH~W;zPa zweI^STAxkJ8y}hb>kt0ipZ$}o<2QcmZ0xOWeeCTzh96RB@8d9^tnFiTE%ou? zArXhSx$2Mo%T`x81z=;Rb_ucu+2j2y)4^+h=zl zeE2?4LE(0J#Hrprd;a>>xbD}(@mS}n_3iO;UCJ~lN){)YgdzB!{dF(@=Rfnue(Uf2 zuD3t_tH1Qqf4>}FK)SG+3J@^rQ+F64$~+Gr;1CIJMpDB9Gh5qzSgn(AG3Z1B6kOEZ zRkRHo>n@B~r1d_{3nI7=7=%DV3>6|B5{s1mdL^OL-L198S|glA+TKX0Xzi_KxV16H z5Jo8)8TEK%g!A><#vr8LFN8EN(^CZUbbDtuA{@-6%qiAm)Vd@CwULPHb}p(wguzI8 zJwIS3i>%j?ZX}Gz`?fN1osNJq#vTA=D&BpX4`D->Lmy*!uRMix+c)IAy?g8U@vr^S zfBe~h>pOo9z4Nob@$2FF^B3=K>*ufI?(pK2tt``+Q&=KMM@Y-6_QblVb z5HY|n@WL*JfKNgzcZ&#Mjc~0qM5wATldAPbOko}wTnG`ox5Ru>y}97wuyCCS$;=@w z0}?QOAS6<>wnD=CxFLdyEQb@a0;aY7a{ow7rPKfb2SZ~KcSlx=2zON)dsEi=?%>0F zTet1Xq*@D4q)B_b5~MPS3c5#xyCpzt9U%!+NQg@XzrVj<=IPn9c)+s#0vu%58 za2=P4K6ZY5eLUS25=t{qj}a_Ysv*aQe7q@Gr>rGVTeoL7x6AF_i)Xjfe1ne9ZW-o@ z*&(8q!*$yu>~J`xdkjOwG2A0OEW7z|WB`a5uB9+*5y1c-X67Ejq^-B!$FeN{KTU7` zY+ZWQ_pRZX_TFc>_r3Rhdv3LcZb=N1K#jy838@hhLQx?ZMNEnfI7|V@2H_aWE?k8x z0xAJV;6MS6Q{WPmEh&@(ssd~iV~iX*LXnZt0BSUJ_uKDyhcoQGpJA<)9}f8o&JU-~ zskNT<{eC_^Bv0&^&8(HCCATb9VZWOmc=WLwcj$+{{f*!A_Vv;61KaamgM51B>eZ9e zR_3rsKDzVsi&`bcpwgTVj$JElp62y>ZHjl#&P9ol_Tv~`biuXVrOc%@TCay!e>uPF zJ6Ja&X+qMkYDoX4rml@VN*I647E07I)Kt4XV+<_gTJAf~xU=z38yc zrB;#PW9T~*C3XgCh)i5dZ6&Lr86Ci6xy(%=rQU}aqEi6Rfqg1dnRmNVW>p}E3JQR+ zTZGt~DI$B%F-GUT0?hMXTSg>iP9Y%}p#mx~SE*>yN+tsr0=WRlRZ7lNli7vTFP0%C zFx{SCOyfR;PMfrn$Ng0E+(ZBZATt!JmM3Yg45{xHy+|%4Z!Ry5G{z1AV+!V%_a6aK zM^G2bm17bVa;YaIW@xQJtz^30i+Pq@xq*AmHj@N(LS3R8jc(UDWPEJl&>jj(bchfYFXSZ*8qQ#^XDa_M`HBB zhnNUBPxGN(DkfdulXFd~s%WkAxHl8$sZR5}-_%xzzFVzNQa_Mm0|S888Z#LvF@(_N zaVp~+Lqu|cg8={`8n$-$2qX56Q1WbQ0O**n>e+!AB8sV@)taT2IgiL_2IT$8 z>ckEf916@{nAfY*Zm|mHAW;bJIWKzChd#Y~`3tXo?vH-+yWYGwI=v@TLI!P8nw(#p zZ!UK+^<-E}>sM<)+U+h?bwB25x05;zfod+bfnTg%`HK4=e^lT7KYZIi`a5s;tLH!b zoX4`9mx@J;A*FuVaxK%;YL2lZ7sOf!X?f+S)l&1sK;5Db980ZI#7sOHJ927G91(d# zZ~#hZpr9s-pj7~beCIhx%Vx~Mm1$-|Bc1Dvfb3H(bM!ujSZi$}Y7+awM6@+*hTc17 zL1}pcfZ&6g&f|R})HP>BD`oEbUW!_k&ZXTrVJ)rg z?|>`IJK!f^DGiB zhJ}H~)ZwBZce}Imb8xuZOg;{KD<`K%?|9IC@cke7#K->Q&%ggaJaGS)o!$73{{qAqQa`&>`_i&hV zDtR)6FbocD(epf&Teoi?9UWi0@9NzS(A``wJ_nq23dQ)?U!5fp&d z5NR4mM)Jcz(p>a}v6)&^bgmTvve+jn0;b;ioM-2K@FAC(nPTemJdq))~qvWSKd&6?&igBii&uUy>z{Pn-~(ii;P?|kU-mwnm!XFf@&&86Mu zEX4%CpD5(0D!6k8>~5UC8- zt&dFY^5Vik!MWJ^+t;7k@ApU~Dj?FDddEP#IXg!Y=OZHe7+rAEgn`8eQ;{a@h<%b+ zv4NpAF#~B0m??zrKv{M^B9chikGlt+^Ch4D?EKAdc=X#|hr6>19L_K8XcZV_I65+s zk|npg%X^P>=hjUHT@K4Yu9@_GH%{}k-J6;7K6U+i9dF;c?T`TEKpVe0WI)@Emnn2U zF*OL3ccU%R;?WnBcYNLBzxAJP^oytF&CXGpTJDD)4Vo$mb$3g*2pC8uubVhthGoI#;KI-d6XGS3dNwAQ4B;9Q6z8oT7s z)z+kBpHiv0)*917r7|BtVGS9jS*2r(9Orpr z#?9phkkMnL&UZa&7Ba$cez7Z4ji>JMd+on_!&mwj8`EQci!kTlf> zVA3oGpF+%eXOLowd77EX$0I}l(2{HHk~9aRVA@(iVjmdEsy0L-$6~_HMZW@v49>u` zd1lLVBgVe(OiY-ak7ni^dTC8HPgCCy<9^TV%uu8eN|WZ8?O@b2K%`;0^gbXQ+!$fp zkB-r&7-DaSaZ$kX+yD+#asV_iQ@cDni!to;Jk5>W?x&4ZRd+Fp=Kl8u*%CG+9_kGK&{BUybT;<-=^#=FI;DDE_ z1p?MqQ{S(Sj#Zi}l`_@VQtU(W1T@bz^+O7Nv%PFZyyK9fS>0_9?HWU;*RGzJHL>#5 zFS+F>8x;XSHqzF={T-V1dBy!$?2Jc6i)cL+kOXSY#H0{TJ(U)A@0q`VZfF><= zodHQJ%tWAOKtx8?%t8#MWUU4Ytr>vDKB*|7%#*DnH}G=w*tfmqP51Gqzxu1b zjIrb_N{dxL&lScBBu1=?vF~2|@alsf{O7;tt$p7&6=d#XFvOBaQ*q3#T5_(LC?IoSXgN;RxlqmR+8RsiOSTAmb*i3$EH*MW%I)KKS|5rD`8ag~BXCKV9sG>U-|A+nBh z8V2vfxV>QZVBnk+RS-o2ZH1W?EKoQIEn00>#rsI84Ft{Sd3GF+kB=)>E3HoB{SQBX zjL-X)H@*EYKlFkB=FNRro^H;jzVlXSk%CKuF`eIdHl*Z(E2ZQznPHi;ii*iFEXrI< zol{I`R_5HLzB}sXalgAfH*)LM2^!wGdn-Cm0i-&GE?63cg|LMKmYv26l z&wuofTpt`SzzCheT-Xn*RWL%|lw(k}akud?#n4q!X}PL6A07MT0|4ZCJfvgiT2e?N z;)qIZ{je~DTC#(X+l=T`w3Z@LyMAFNdEB*DL+HsJrj4cyZJ%9;7QF9zl?G5$(WhQo z3;h5HWg5$F&)!pT&WAFMgnV#GkUL<=H0RNW07#+pd77j)1a<7RiA#YD*(st%fGcZW zJ#eqoSxZHb;JfEO@43MK#xqa1mR)px1ZQ2IciZiS^A3QV5~h%X;-q<+%9T?xgPQkQ z6fA^*!MQxP?Req28Sowx()IF`!Cq1}Zfd$4#$T>FC*mqSF zh(RZ0ZY|4L)8c5^4cYYTUOxWxkNy2uzwV6>ed+6;`uM+dDT10v@yVHinn(pA1a`~- z0Pb)v=prG_(@siZ@A5nuL7=1t1mL`5j8&Rrw;x9!BXEF9gwe;UVgT%%7?PQ&5SoBt zYmCsU5R*0osB_t-*cqaLdLIoMC`c1W7Z~2yQ zzx|01X|2q@!%wd;CO32smn ztToS*G~pCtKj27*gIrPV`+hDnIVN<6$b(i1A>=$_G2cZKm1!jQ$PPKGiL^FPBM^BP zo$vc=t6FRDp|w_PMJHCo2pLH%A83nOT16l;Ms!_7!<;81Yq?Skj=42%(iFh^s3jvi z6+1h>xN~-9f+6~0SYrr2f)N;+i(POY$T@3u+zAtq^T`A7U_Fc(*T*L%@3*^Yy&Qb! z>#lMl0Nd5NJic;ycA;3!$mUWHMQpUiFbL>4PAT<1BB&CPwhAh0=7R%1Kmvt5A-I@Q zignI2YG`F57gUJYuiVYyi{A8}U;M@2`QP62jo*0f&N&hsitDlSu_l_PLV#78_fb@u zDKi)XK=UcYJ`nNwW*cJ6bq4j+>O9uDwdA8qUR6phfT*R8lDlqQ>c+>RTl&rR{F$4% zk4N#Od)?zV{>GahfA1guZ!qv4@}TKW_PxYpu5IPRJ%A%dV11fL8r z&wB$ya@w}eCqqJ_Y1|_D;6n%z2nkU|)Xc}AqQ<0E5DcATQ&nYRM#|#^L}sWW#MJjo zF*P&iQgEw%aJh!4ydhfZ24&POnlJY^lntvDYVJ0em*5H(cCv9-#~ zfQ)Pc%E-0V5TYuWHX~#V{W3@`V#JJPI*-HgA_mfhn$#w#lhAjUch9BPan74(pINTJ zRN6dg%SI&H2moAg-X{|%bw&@~hu}jONfm3^@5a6N>DoQ_Xti8Qh(7u>Pr1xfhzT9} zF0vyq-Cl0;I6LApk1fw;Vj_lUNQ^F*I_-DXz`L&1YSPB-O zt;=Ddt>(GZnrka5##P@D8k*&~fYEZX4BoXSV-a?~s`}uUM_0_!W_S0_?OVt9UVG_% z@cnOm@vVv054f~`A@)5HHB%yX-h;Nt%*d@4 zCfD^xj$@1+vO~}FoaekZG*v0BGBJ5pFc3u~M}FE(^L|9rgZ3k)j?jGYu^(JNgf!G< zS+P#0#dx69ErMHtZ&&Ym!IgVn^0Pnp&;Rb5U-j0ngfD*l%I80Sr(5;oxMzo5pGqn7 zZc>2ddUfsI`%9Lps#4b7ppEut``v!4c^^?vt`7ZbNXrEvNNrtO2#82qtX5)rak1^a z8&X2@SFWB6>mzpQXnkyEd6VyKbLOj0ytIDb+rH^zA9?cr$6hV@HW~WRp{vwX)f^Ea zq9Qwo^E~f1XEo<0QfoypX%z^)_aS&j&bgQYAgYLnw7GQs zLQGmK>==*`up5@c>L_;Iuv~Oq@7U)&Pn&Ja8GsQG5zxDoq7U()7Xh4*TbriMZr5sC483VHgFM!u@0Y6uGoPHE zcn86C(o9r|9Vp<9PkpFKWn?wzk{cd=#S>q&eBz6Feg8e@cQ%~5I+rehOI@2sX$T&+ zoBiF-UH-!FT?d+Xx4t062;iJ21S@|0(}A%KDo+AS02c% zEtY=V&(p5CKxa=~Y~7Wg|G=Mmr1NJ!9{S}x??53&FMur_9-I8mM!wl~PL$ApkL< zH5E^Ozu%H`-UlE6I}pqaftng40D|{PK~yuChSO!%QS&BazPuoq@~HV`|b-zFZkO(@PmK!FMsLnU%xJC0aaoecAE=E-0Y{6 zVhl+%8=~fRe7YXGqo#6selEGJ)=)h4C_Xfbu|uYZOM5*SL=u3OGIUtV0K z5K@9WySw*ZyV`2=Lq~w6j?t%Kv8Zz{b(E}4TL=-kS7|I%@(hS*f)%rjpi1P$YMq7q zUJ&X$A1xpJ+>>8;*E`?z&0h_{;wL}xiD_eXKHrV7T*CTfasR{5ZS~H@+3w2G>BTtp zi)*=z(xz6)Gpp#d-8KXRGfOEsZ#>DAqA#-m=xTMW(&oG$ccXK3e!lyXuleS$dd=6| zeCFouC;#8iJ^7Kp`18+e(`ka^1*6FNboKCaS0Db!_3wJ(1^@H=K6>q*d(LiNANoZ@ z1OQS9j)=*WT#N>IbTkxcs3cIDNQ{Y0O_4qA_oH{AUmlsNiOu6q)necI6muzBi?!+( z3lN+38zhMAg``czIcDd{`OsU<6-~v=4Ape8x}w_5R1L^60hBzelwq-)=iF*Ra$qJR zhM-7Y>J*gJb&J@VxEPI?$c_68GFvPbKwR=f=v&EI#kqjQ96g8>QCI5y4?PCyr}y(M@P=NJZ|Uxly|v;^*%`7o!x!* z#;x5~yy7+A`&0kwJ^$csw?6x^)XBx&isrgS*AKllEmF6a+fwG(bv}TPAutE?qZ*m^ z!*aX1>=(-}f@;fCb?JFu_4p_M_FsG1ul!$6UVZSnw?F%-uvqMC_R@@i$q}=tYRTxl znj%0*oz`|Z{TL|&90K_!REjw7ocF}8N(ED?6~P)RpqY_MA*L#dwE$2q6$~76ZNW{cDp@! z@7Sf-mpl`*f_T@>W#eguK6U`=`o&sQ z0pRH5O5Qb$x;S3V`B`%5_+(k=OT$v=4Xx$)xmoz%M! z6G$b;-bEh`nxwi_ZK$a*=*0h@XX~aUw-=j-@EhvfBF+Y@)JLU&dpU?Eog1ISZj*oIH#^> zByDZVQ}7{|3}imUGEO1-rfQ`fG?*fW0J-LrIs;Gv!GzV^b`-`tefJ@U{atJ~l4#=hdh#4QH5 z-_>VsJsa8Ax;G%_!?k-(n$Ui~2f(HnhDfP`%)k5!UD=`G`{mmQCx)4PqPxS?lK5x6-c4;Z4qL1jJ&1Jv8?p%L$ zwg28XJ@y-ac=Pn3M{Yg)bQi7w7f@}j5(Nh~i>L_z#uQs?h~S770f0qCXK|6qu|v>y zP;>gu^|jSjGy6cy44`UNgo#?6gO7HYodilXWJJVT3m7s|mCD4>nrC-V63@9HigUpL zk+5YAu>&|LBh-{kDl^u+Cn6t1sSOD^hGp#81vAMtn<#LqZPP8T+_`;5VyolD&D&=V z!tv=TJ2=04$FYksq9>C|27#T6!+d_BrqVJ%b!LjpXolz<0SfiYms6o<i%)(1*M9jEkCc}_{=BEI zpT*8I7Ds%1+|_BWG~a*U0}7x;Ynd-DFC3E(iGsAOS6AJ$&s;uu?cTrj-VgWdC7kV_ z9pO_S`@=7OY*`x*Ls(r|?YG;!n@f|>$7#1eIzF0nna16)JUTvJUv9S7PSSq2&2#Sh zba8g>VgPc~sv%bEUi@WO`M-SbN8a{=U-;nX7DH+Com@a#OR=wcM)t^#9s3ZZ)>bPr zv14h1hTeJW9GKRUoezZG% zE$24Qa3VsKz@TVtxe+^SI_5nya|~64h&J18m#pp!I$vuxiik=M0kD*6Sxp+Ei$vHt zX-WVl+M0sdHgA0NQ=7HvuvjemWvzLvm81rCpyTE3>+@&-bh*A-V6M|N&$F}^hILfr zM!{w9tAF~7Kj+5l4?p*jXFj)CtXFd_7u)lrAzo}}l?G~O=XXPl-n+$e!TbFv^1F!*f9VFLy(riFviXZ>;QBJ0R>S(B6O~) zXl=~wLr@i9WOm-WS}K@19}YM}Bx*IASqQ1MQuA!;wKcGco(o*acR8?b4idOPGm0~7fT1|TZS%pK%S@3J1+wJ-OeG}=Hk+{s9H#YJl}ucgHL}J9-z3s zc6xdHCOMw!h=sVeeznwAHv3Cfw>x{50YdENT7599jNY|8s#F&{MXR+qZDv+g%^;exUu-PLGcP{ri6TAHL*a zzVYnTO1|80=3K}$OSkNX))d*U9G`5*t(1EE)~y&~pCYktcNf|$rFgzQ3o*q`Pwrp; zhky5Pu0Qpe2cP?r^QS)BZ0`FN*qmVDL$}#pOc%F_(`I*(JmunJzpAbF%bt+5)^0HX zA*hn~t;{AGW60C&954}kZ>{X}o{Ryhsm9c)St~PuBf|b@&B&^f=WMMx@5!+P4`?6& ztwBHoJj_iYEn^n}u@z7gtH@y6{RoKcgChb{P*7<|3|2V|>|J%tNCJl7Vq%r%lT$VC zBchc#i-M1lV=~Zr+-s`wj>%_4JLKpMLm( z7v8;H{?#wP`_(T$EltVl-OWV=3?cUG#eUp(!_alZ{_`PQ?m)zS>8!Cb}{KL6TBKK{vn{A2I@(Vu$n7e4;)newnas`I2`Ky4l; z#}4Vhxd#AwOF*10ks#04_y_lK+hk#{fCTWcj2_Xg# zpwau*YAeNvQuMhfrv;@e>fqx1(kPuCpWe9rOr6`+d#;+`c)Wy&mhZI$W00}@4QcM7-t&F=lx$A&$)+w>3{r@cmK)LKn74)tycsb7+u$usV;`~W_LF2cWHh4Y{l%X!sz?%|oL>YdIxchVhX zNw#De$+EGr@tkNOL*jvXbj)C|F{WbzA%q0N>UKgm1Xds<5FqVN7OX`=FbM=$G^Fh` z*u-XV8{61IvTRwpx>wS@=iD>A?_0w&?cE=a^R-retW{Od-v9sa$55i=EM3DGC%3Ym7C)N!FAnHa6#2rzn6#M1ZjtLQ`RN~j_Y0pR>r$wuI8KIi>PIr%&1Y#G zRdjQ*sihPZ%j}JFoy$p(unl1vCSqok#1rUDRdo_A}KLjVpFjY8fMZM4H-ma=S9jiP1Z1IoiCRq%Q$STa|#3~Oa_fXgH@|R zYtY0TfmKr;at5NN>(VsUl5@>9M(cc^<7_rJY>_C9(0(G(9 zKYQh_WUjpP+um^Xqwjy)H(!XGoCeb^mOhThN1L|m2w;8dAWK0&@0vEuvu+}i0hDAj z#iu{}OAtO1(T#ccevjUug4v3907LjtCv{WU- z-c!ygMIu8A6)F}HBGwG5xt7!G2#|&eh!It^hPG9JoD%^dgRza|!h}=5Eu|oHE{Vu8 zF%Y()Rid_OQ%*>Lm5@k@#-T?AXKl$vaG98e+3TE~fp z?RGo#&bg*p>|NR$j!r0;)tNhvZa$T#sWRqVp;mHW5lz=w8^)opQWd0%Ai*?71a(a( zscieTwcZ7fin&%TrPKn<0I*%3Fj704m!eflPT9LKq+93jc-e3K)^Fc@{8L~4(7D5H zY-T18GEGCOYahDM1VlzwVpQcC6B1R>I7a7OjA=cMBBxq9H7=}n7CF|F&C%`-{_B7G zuAlyuzdSlQ32pG))}koMG>yoqAX#dyqKZ{r)3RYLA^<5xHl7XEX+lM0NHJ2aNK8zs zvfXT~F~+#5-;!3-1R~10oZP%=y=l5G#~k}4D1;UfoNr5sDq3qHptkG0al<(D+ciqH z#v*`#Fhdp9I3W`%B`sNt3qEJbnvHj=P*SS38fIfcWrmVM2&D=UtHN@3Pk;cVmYU*3 zsJUpWxpOvFwE5QM`(JVG#`M-VKBpURKKSb0;h>sX%Q}tQ-Wr}QmMJC2*v*&2W&={I zRzxi1jh1Hb{QA~2kA3pwjoty?=9ecNL3B`F!YJ^R6HGj?44W(Af+C z1ORoM#ws~Z(fKgnSr~7(M<;zc$|=vAc6WEa*=(g$W9>MMIgiHMSvR8+r*Yi&JzFQW z8fWqp*>Zn;fQ#mfUs!+qAG|AEyzu(J`B49<4<=|kHxreVi=v+g4pg~nagsI;e@11LEjfg~ur^7Z;6$J)nGDV8_KIUu;0ssT2X;RQr8zQFK_eZ2i zhLPC%Q**-d)WC^A$TSTD2>7-WttpLyYOFJcwTdXBK~@vvLoVLVOH09w|=fIem zw}fDU4OZE1*I8&WYmKo`b+ZMwja0GBrBnm~0E1GIZLUQ$`_M^}JD+#&lwsQ+hi2}K zsS3Al?)%_~NT$NBsT%u!Iyzc!)*DErq=?L9Y}Yzq%45zop-Rmeh#YbOWJbxgA{oP_ zWB^>Oc2%LOh!#Swtid;>OmT{gM9isVs71wc;9=Uf?Y!EZpZ$@yeffQ0oLwv$5hORw zLpNVuJlH>!Qt5PqBH}EYYz4G!TI=jIY`t|S>&>D*n7imANZ4x zf8xWxci)R&_PM|Mlij^bL};8NCaptC*}29bo2IQ=bDZ*o#0UyRELs`NFph{o&T17a zbt+;EpNgSAs^Tv8K zGLDnAN@TKBQ1C7wK-KE(EakX8KC(7AZ-MYs-r}2<2-7shVPx+*--y;)RiSQ9jzlYy zsU>U8ZV|MY&haqvtW7(2z4~K+`NY?M#Y-Q2UcUSO^9Ltu=a;Ht$Yx5$T0&Z_W>t+r zZfCQ`x5M^0VFevbmYa`1K5f(Y|MQ={?{9zKaPw2c{`DrD?T4)vxp?nu9=~z(xBu<0 z{_uC~Zcfly*R~xR6cuJ0Tqh`L+MaCkxb64Oo?EQuF_vK*+WD+$Lci&|CX^COD#!hC zzu7ij!)${OAZbc@zFLmskYerJa(S`)vRBJbyya`2`=P(Smxn{+oED9F z({u~VCfCSBQ=ACYHw#3lC2Or(i3}oeGh4-RBTxuP>}kWpfoLv?2@x3(Epr?uQXoVk zl3W<5N>(PrRtQ_`YOSCm0LUz$s!)m=OK{5P69pp4@f4DYOlgY5Je5SG-uYZ=H(LN< zPH`4yZL=6-J=}ltj-@RHOCBOwpb8*O({*f1Oi_#THW=7DcbSj@>NISPHHMAxO_Q@W z&AgV9SX8BPAx2n)`Q_)`e{}GS=6vJuFr{8p0jO>_I5<&r^{L_H`eQpgX8_R{a!gYm z>QIdh*87r^0ASTr3KvKs&1QjYICa$SCXKJn1`J6L| zh+>V2m}|+kqP3@6WnSzpN{Ymo<7kWtJ`gjpF^rn40_2iYPOeG_4Yi;oU@+(6U0a}} zaZpvZZoYe_#JD~>WW&ZdViHkf`P7r^JTq9bLrq)Xl-=jP=DqLzwV(chcf9@e#jnox zl|s`1f?)=fz$Rz$P3SlMFm5d~vPz;n{Q>@j5 zU9A(;Z@x32YR94}nFgoFqC`zgoZ+vRd^vpG!T$j*8PheyZr_Uubv`sCXm zdcp5MJS;E2_~6k$_siQ-K4A{Cuqr7f5v{_`wYDia<~V|i5{ZfdEwN70d*4{+YE|pF zR@IbpEmg#Y28n$Lc^ZfrP>r)CX9QFfL@aG@Qc`hF zV;owyyImhC;m-NXGWE*F8>dKY0}b0=YAI4miE+Cg`&+9sx77>)5yIpr)GB6umRkxFt4HMI<3$Nin4~^u1c1 z@9Y`J+s#QFdeNFwL`69LlQgE5qNNV~nBo-2QHlUt1Y+ZfZ7nh9Nvk+x5h&-WYEE%v z<5Q}o7E#QxACA|hWJR#X%-T*$8v2bgh-itG74y_1strv$TR9iTVH49hU#$o+rKlp# z8zPg&2?6_)P0l%&Tl2ju=kI>yPyYB%{mUQv_P2edK(lwWMT6E;M+~T1YONC@q#c9}XPEcyj1r?F43n8?D3`woq<6F7p z&3c0ffE=2c@y;3xRT06G+pdq}uwgV>3*;gx#%WX#Kwx%ce5^&0o2C;~<6RZ41jJSp zy>pDv54{VX5K~GbMT!_^G2LR;JE2=xW@9~5NJ*NeIe-3w3tmbzSiR-dxjRaY zNS=MGP;(g#GF45OkeCC_Ea=XK-Ln^#N;0iCZRj-2ePC7q1Y?{JHpLvATP*SOpZc>q zUhujyO~Lsrww{95Q}n(CO)(C&6jUhvMns|2T8eS5h>(gRb=|Dgnsc=cTOX!zpi--# zf)Ze=iO90GVaER4l^^|k-}=Q@Ogrc8$tH_U{e)2- z7E(%1Noqo40LWU4%sHlU9LQNz3f4%e)(4VWa%oSSb_j^s7)sk4=kB`S&Ytr_?|#>x z|L!k++gI(~>M7WyYBwW0hz2kvnNoGekYI^PTd)Rm6l3(_#oLY!Z+`64$1nTR2j2ek zAE`%hc;k-^&p>4wQ_VeL=oX=d<-5M~@4n%caB$<9-QBr>FM@YJ|Ne2=uDIrgP`cJ$3CWAugAzrkTY!F;XsZu{>KbHsLt42i*LPg9#eE= z#%U}m8SB`YTFNv{O}9X;RaHwBOjQL7kYNN(<0iB-0WqFytu;;=iP)%=BH}GiLvM}q z?Q9q}3}BnK*2>I=4XET84O#Deo~B8Qa}5%b5CfH*T@%L5MoEdVq(V$6HJ8km$+>{- zqgNlFwjjj2JF~vW6vw7*cP?MrY`0H8bv-nVb?tVYxQV_!yFU1Q(*UK4Wk$<}&);?#i)7tmw|wP`>@WQ2*Y|bV&KFv$R&Cn_kyWj#xzwVHc{=T( ztF=C~Gw0i!CNjo%4O;6%FvcN39H%PU%;rFt<4|iUDI=;iuBJRq1D%S)5Ug#ONkCP# z8utj>dZ?8mFnoJdJ&=Ip$PTiPM-$HkO@looi6Bl+tfEwWJs) zCew7CvJutOYPX1_acY{`VzujSW4*g{_e)K9&Kn+j$p_!}OK*Aez1I)=5ISVT#H}~W zy)$jQ(9DfxNx9}UU(P|x(aA>Qkfwa&#{Tsuj=%n$Kk&Uj|B2&go_*qp4_J2w{9Fwe zCLpJEe|7r)7Y;|p8As$>w`DJT5qf~)&)OnnhsURVbFH=@JBzf6QJ+=!<+k{1xXQT z7_xT(5Mmt1X|rAr3Wy+O>Nm$n{n1IkiDfGM{qOwtniHUergPpN_2ZdKcYo*){>%E- zwL9;=)E}?o*jsCoW)L||hPTbaI)CHoYgWg<@m25akB_w!sbYgO%%@FnPzA+O3ZS4U zG1ZbvDI!%$wZ@cmiU%-kEdUh}U{C^$SqTX0G)|!X?&8czbLpEO{F1MIl}xoGj;mSM zcJtufV!0rs4eoSUz}D0(NPsA%X4R^|r}?m& zCsjd0>%0Ou=ZFwg2w6a^Z;YX7>zxs-N3>$=%ABw&XCO&6Yd;_1U@ z`tSN*fA-711x0^9eQ9#mEto3o|Q_4A}Vdzs#aT>SV0Z~h= zQ=M`V-vsMinj$;5d-mLRyNP++-C51%_VDp*ANt@2zWN>iD6jW#t#5eiz19LCF(MG4 zWp8~GQc5Xh?~Jo8otB!7A;KJEE!i-CVVhHnYg$AkYlyI<3{X`BjX8b9n3)ib^(J^J z8Bn3tlp;!&B6vE&XFPR78}H*Z5g{34*!tj+5s;kswN}xhr4T{QRS6Bt{OmnH`4j(e zkDgra`c$zsHWsng7^1#EOygK8`@l2~=kI*r)nD~J>w~K{cs358SRbh5SX8_ZIb{_> zg<5s@w#z5Spell3z1?gFDaG61tl6$N!_-g1#=7SC>XRv!5DYRBQ%+OgAG5`2oB)X| zgNQMP*~Vc4WkXg)zVKNC0WB4D8v4`^1O-JPD=-2hkg*OiclNe>e(by6x@=BX%Uvv~ zb?rE&O@CsYJ3c;KZ;xk9heQBqj3bu)gTpe#icg`3Gs3j&9$+@I7s??_KtTmz~cn>JTXxLce5Xg`N(ozdBI!B~X zN@ikIVP}zT%7VybjB{2>QB_g!KFpT8#+*u#q+}^E#i>WFaeJD^ZNqX|N=|(*QsJ~x zpOW#dYiBG>MyEgi}&ta|C=YC^4345cJotejZJI&#(Q_%ir|<2Twfayx}Gcanny5LLqYFG|Fuocdt`@Xc(wSS^tF*8Z?Kb3sKl9oM8tL~Nu~a!u!(4v-8fq5%|v>C)|YR0YjC z#?kwh8MWv%4SnA`%iDhQvud(U6S^h4V7ztKMl`{+v(A8RW*wm}_IBrYy!zk#+|PXND_-*YFP&ce;@$lNg(8+s+jdxO&X#}{ z%ROtb7GrED#udQzkg5S|!?nksn1$vi-uJ|n=fCF0=l)2xM^nEw6^-=(=v)xRB58T& ztKRcZet7%jr_bJb+cpvMObuVWa$(*q%_*(6sx#=jHbF(hv&EwA8bZt^pFMvrSYw<= zB2)m?;9PKa@9deye7;;Pf_G_*#9T^Q%@?z#QGpVtrs+1D^>&j)u%E_pJCcD?%=2Eb zc=V$mc=z9Vd79t7xO^%1C)<9gF#;6lgXT;KRcb9I#|cn#9HnHfia@z$8(Iad3JO4k ziq!Ypm`13lhAJUcGS(SqgSXZwP%Xteo6}fQ-?cO6ERsWjG)*OyoMN6vMIt0XB?e;S zeB-)coMY#i4G@FnJdSbLNE#)^5Spayc{mxzb(BguN|qu_v^_aMsTC?aZhY%J-1)*+ z0D@w$mizUQmYQqgAN;;|5aKj$h^^F8a-27dQRvU#|Gqo#SoD3$DK%~DL#rUjwAwlI z{O7+A4OY$9Z*1=WlGk0j{lzCYpJd;~G$A07M^)p4bG9Ex0ARM(21S5c;xvj#jafle zGP4&VMk*D6z*x%2hRwJ=K6mB*TLXW?t6uuGZ*s;tRqGoIz_aHpX<`E z|GQs#AyF+^5y%+tn^TH88(*c06l;xjfsJ*}6P~6pEHM)i zB9vT_kU)W01w?CAMXlDkJjGH^IaXh|S(#825u9(d3NU=(ppKNsK~#NcOz;3ARf2Dj zIG1G62m+`!zO{~G%%v6}Y?_wXim24;xGDMM{2ecd?)Ja?wzvMyul|c~e(3D{%q-`2 z8heGZSnj6WC&|u0&eK$?)Ht&)#wkwgmR-ACQjU*(=K8B&_s|di(#HYojmJMuLTtgg zpllJ;f>r0W-n#wnmwezOpZ)Rg`L=I*(*nkjG{<2(tT$TiV9UKD5O$O8B&PMD86aDcQ^NM#VI*&YspR9?(FVl z$r97K+s^Im>=2q#&F$wG2iHIIyTAEcZ+hF0%}6#U*Vr#O03xFz8rQTlLIwgrC@BhP z2n`U#X-aXj-Zk@;VXLZ5+X8`QPt1hS__pMHO4=f8k%-u)lxs{?1P~BarIr+@I2hwX z@NpcC_13yta!y%mImKxZp*2 z#u#>?In@pI>-BbfeCvAr=6Cz^Yb=T)N2oa1qG#8Op_)FkT2 zIBRN^zCW?f7M0*THm0Zm^gs*0U@3sEmTE(&aU>!X95pu-rha?r${o}E;&;5{xv##j z?(AGXImpWkZS0GqT=K|dkeo$qd^=9-X)_L+wRH_>MsiqXIO(fat%a!_BwARu!!Exg=)iEEg>ximIF8#IdWOH4C4P zE(nU8E=C{(ck;|(SPd8Mecjb-!yEs`ofqlqSG{&IP$@T32xBwrm^h|w+s<=Ln_*-{ zSK>6*iPbtHUL=qkPhS1(Uk~5?kH7m(Z~w*N)Kf6%_En zOO+}LF&fK7OFTMuZ9s-vMMOa|F*)0w26O=tL>PpEH`eAUmQ>IfGVHlwq+#fdbK7AZ zifb3ER-r10ii*Z*8q4IpkL$_LTg^gLnH@5#6zVQK{HMQ@Pr$Z;b*32GHUJ0^8*5V> z1*%`noYAK~chc@%jE6UfIOQa{pfiME05F#96hxIQ=9s2qxN!D4Oi*P?Lo^m{JadDX z=F9n}-?VL~npTy4|Ezg`QRfW`fpSC@-Vb4{O2%QhA>a8{2P1|ji zVr>8b1&PDPHC-A<=iHo3>8Ep7&ZX{o-}jcUG2`Q}eEr$|9@Lg$-Jcw7mW!qo6wS+> zRZaQ$cr7(`-8^L^OT)U5DbD(oaxQ5$TM%MeZ%Es2HrG;I=nNZGo#F_<&a&@X9j7LA z##zjE*|gI*tZ&sj?%bVSJNWzG_~Kvqtw-m(aXakiM8J{QSi_1y#u{S*ux6}P*>Fio ztB`S2MVaF?FflQU2pOv)DW_l!8kffyOH3d}K@n5Tvz-+H1T&v4jMTc>95Y#KOq@n7 zb?mG4&Jt%)%N7XYxHZP)m@)waiV6{X-;^AY4HJPt@J7+*IHW07#XKGFUfx|^`qJP1 z{r~$Py!9&|d_lSIfh*T<92xS>+^{hKnGM9r7;H@I#{PJ=TI7^gi@7tQ-{e%{jc1`|&GYA-+k>mK^D=XXc-vut3(%MbgyA5=%E*rWj|7PNWV~#BIO3vlFMOh>ra*?`9Q%t&t+!m|Ti=ei*jC z>1NBtwAoH+lqyeRBX_*;i?4p_qoJHFfWbPjrj*3SGSe4odLTrUOOgsqfC43!5Sl3CGz?7aEHkks z$_P?5ivScR5Cyir)GW0cS&134lYqSU( zmLkJ6ffmXU#65lZ?A}@ELbu%vwN~O z-+S-zru$#s{EG7IUp#Q%^2S<*@pxwaI7Pu&pxtcpYGKB4Tpu4X5h#_|5dlVswKAx2)+%PuDpGPqRgzLl z1=Xn(V}%_D4umA2hDmBaZF&JAV3*w(ClsXsMG6v`z;PUzY$}>(p;;dtj{Ri4L82I= zQc~ZPoPr6KIF<|yzH^pM71*q|wqPzrg`T}WT-cjKIsVEox$ukc`?D{3`QJLY`rpgw zteXKi=e%g0QY0ln@u8_X16hk&wSr-THs!7TBMeg;sk^>7vBUxO1UBtBL){z zid74tWkta{Zbl#l5miKHVCE1ASp|r&NOIPiZl)rH7^fjeb#3bb35*yEh)?|WpPs)& zRg_JxidrQmMXlpF#vI4(xDDynjqL+p`YK`2k`bw>?CkB9A|k^7=F?|%R9{)c$$+Uo3{PPLu4wU$~7pn^z|7>BW2%vRl*Kl<>KZ}>ak)dZV|wN%Io zMo~$nil|t!T5HKAr=%q-inYf3V2lx!esf~b5Sdb=vCVpYeD1cpw|)MeZ+U1pe)`-6 zaBZ7onRm1KVo~de#?QjM$~3IEUDwT(%2vG-=t=C;mULJKxU3NAOGyf{`?PL|Mfo< z0`$ixY+a3$)M}ioP<&_&8zl~1V~iz2QZm-2oDHLh5TW&!*s`&{orx-2Qh{2F)B?5S zQi6BP_LSw2N(^n+&K3#;fX?}nvvp2E*;uw_+V(k3#FSDDZO7IdGAL-gBO&&e2=uGglG9BM`*UKOI z%;&%J>%aQ#U%T2rULZv>vS=1aY@8cQB++pk8|RkGnQI$oY-rk&vNJqitx#pPyNkp` z*mWHsh}5R*c2>(xzs)HllJ`EB?2W5cd~j_$E2XGNoW?4m0!0fNveYp0Y`Hu*xDlsl zy(4F*oOA5E#rZ{RFSY64|GQrf^W7J{_8Ut)s&SZ7iZP)UA``4HTBm8N3W&%|%=R>$ zDY>xos516jMCoP=4nBlH#-%s`U=xB?NhPYDTBs1U#8_g#aiOs;u%V`#{m=A{f&vqt z?rT%3X_}mGsuVynWE6nNj>C4gb6chEm;d>@UwX&cF^gKX#!J=KdmjurC%LFdn9b69 zd*5sS*8MMe^V3g#%A0oBo=6puT1(*{diUFN$t7o11!eX&4#zuZuUx%${qawI=#GnL z*f+zr-}dWqv&lIlVsNv?&dOTeZnm3~;k6Gwc;?LI!{cj6h*AWyv4M%L4^<_lsg%r2 zh*)b)wJJ)H0;LjSC6ZE!+iidF+y@@muIV>^DWI5fFj!l|$MwkxlVL`wMM{k+uGiZdvvFQT z`o0fMGfmU+(NSod#ok_t+s)=EmBCxO?fki`kNwpLe&;{D{w+V!g!%gDssONWPKK@5 z#^D@x570BR~ono|tzTm?!gpdv-#3vS`lXcK@%LLxF!i^8c`x-eUw z&T1J`S`T$pZ?58yEYhO>*>rYc!O#UWi|b@&Z%6ue5RVque$G!cYO7p4Q(B^^X1OG zTL|N<^B_9*10&T^rX2gek10$@^_#7V)LMfJRqHg4 zmRJ!=E&ytbnTClhDIy6?B^u^g+t!kb)cu>ch>U=?Apodz?br{yJF^NYVY%=5^WXcm z-}%_5KlSFfynD9Wq~oVu2vE5|QAS3OqG}Oqt}2>ObG3{Jh)8V7SVgS0N=hP9OJU=k zF=Lt-sqJQfXstU)m*3TI;WKKNXlfmXh9_JTWc9Xa-IxQ&9z9WlBH(H z-0to?eK7pmzxo&VJhzz=o8X$JWi&$^P+%OB@y>dWr95?Y{WWj@!Nty5iQ8s2ueCBe z%aqb&#%Z+H!|7l|%Cyc!8KB;F#}^GfjB%5T&yvnwxKMMcrIcKm*^(`goB90QV*exm z?KkfEkvF!-Pp4E7D-nRIs>Z&rwK{)lR^lq^JgXES16sLpS|p{xHul!RQ+M6-%BLQA z`Xhh*pTc)+ z6QVWl;NW03n>B6QE<5i6Ax>jPg<%|)v*qqi3sh(Gh3D|>v)8@%A$Ve=xg72vEawa3 ziIffxZ}o6=-wQ53@>idG+v`@p^!uN^{e`c8`r(g58FuZW*qo*z3y5-<%}Odj07T3< z4ufr)9LHLV^9=wN$tA{IGBE>?`~PWr^S9s9sy=hAXWqkic!x9ANKsXQOeF4f7h#+c+jV5Uaj4f>(+KwUtAtq`>lZb5+qlqC-7y)f(8B}BtL5jkfPSvS%&O3dF zJw3x(tABXy|6pHxU;BE7_4(Yl)fx>mu_UWqrUFp3GTJ-JMNm1BlwywAIfqIcrHT?$ zQgEhZZ2|_ZrYUNrHB+!wkWtmC@N8k%Cx^kzu7BEHpl3h%vp@CIKltXEhu6Kdv5o3a zwxQXDAf@Eiw#M0{G+)fS?aDgCqEC(w>!!8F z^y8qF-dpT~l4+WJ-BL~`o0F;v2x>8(X9A!?IGt=)amw?mB3+oVoh>A1Ey^$2LNtvr zv>}aCoT9IM-)%!1LR~k|=2E6H6X!T3Z=EwrYqwsnv~djm-u^Cvsa!j%v^weUx_kE7 zPyG7J@7(#hU-{!F-Tva^hyR>ZoQ374-)c#|ZctRTQA$ArDJ4ROg>>agiU6!ol{6U_ z6sjo?-kZPoKuOM=H6RM2(Tqw17~_mJ&ef+Tk5WoXld{G3zn^AtjDZ9pzWtTh_j z0I)`zr&Y6DeD2Tx=IjD?_jX+3ZJ$pL4-3J3UgwmWW!%{` zg{lUWQmAg{`g_0QO-h~eAPftDfG`^X+noQM_rCk$Qe{-mSSmSAscEWy(yLfF`^JR;Vij;=vRbF95Em+?u7h(9frl|JW-~agNg~jcMFUet zR2p?V-)WnA7)NK^cGX#DkoAO439&;)&3rfzp0681Bn@x!n>X63dgCB=MfDu(0GXs0$ z0WnRZLVf1q1qCWo$tfD|Z@TGbD7jh8*V{D+=wOz!bA`)IH=kRK*WU4t{}5*8u6tkK zHsnKA}ZL_}LFh4JXYj}bHFl5;Y) z1{B1p4pn{TjyJyi<|pj;)nZYKSZzS7Zr!CE>!vbFuU6}3v9D>m{|g82{=G-1@#KL^_y5(y_y1+x1k7%t z&ed}S1`U<-!3XVh8X_qzL;#E;DVLnGwc5BK4Bi(=_uUkyQK2!$TBEj8H}&gVZuz2i@A__aeDdgn zFZ@0FODCcraN;~J°PDx~XR+XYH`xj#HY9*23V7 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ɀL_iVDINzSPb{?|Vimf&vm`j|r-566T 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 literal 0 HcmV?d00001 diff --git a/dashboard/public/program-info.json b/dashboard/public/program-info.json index 64a84b8..32ca43b 100644 --- a/dashboard/public/program-info.json +++ b/dashboard/public/program-info.json @@ -25,10 +25,6 @@ { "name": "Alembic", "license": "MIT" } ] }, - "buildInfo": { - "buildDate": "2025-12-29T12:00:00Z", - "commitId": "9f2ae8b44c3a" - }, "changelog": [ { "version": "2026.1.0-alpha.16", diff --git a/dashboard/src/apiClientMonitoring.ts b/dashboard/src/apiClientMonitoring.ts index 7254dba..c8772a8 100644 --- a/dashboard/src/apiClientMonitoring.ts +++ b/dashboard/src/apiClientMonitoring.ts @@ -39,6 +39,8 @@ export interface MonitoringClient { }; latestLog?: MonitoringLogEntry | null; latestError?: MonitoringLogEntry | null; + mqttReconnectCount?: number | null; + mqttLastDisconnectAt?: string | null; } export interface MonitoringOverview { diff --git a/dashboard/src/apiClients.ts b/dashboard/src/apiClients.ts index 11095b2..777f56b 100644 --- a/dashboard/src/apiClients.ts +++ b/dashboard/src/apiClients.ts @@ -24,6 +24,62 @@ export interface Group { is_active?: boolean; clients: Client[]; } + +export interface CrashedClient { + uuid: string; + description?: string | null; + hostname?: string | null; + ip?: string | null; + group_id?: number | null; + is_alive: boolean; + process_status?: string | null; + screen_health_status?: string | null; + last_alive?: string | null; + crash_reason: 'process_crashed' | 'heartbeat_stale'; +} + +export interface CrashedClientsResponse { + crashed_count: number; + grace_period_seconds: number; + clients: CrashedClient[]; +} + +export interface ServiceFailedClient { + uuid: string; + description?: string | null; + hostname?: string | null; + ip?: string | null; + group_id?: number | null; + is_alive: boolean; + last_alive?: string | null; + service_failed_at: string; + service_failed_unit?: string | null; +} + +export interface ServiceFailedClientsResponse { + service_failed_count: number; + clients: ServiceFailedClient[]; +} + +export interface ClientCommand { + commandId: string; + clientUuid: string; + action: 'reboot_host' | 'shutdown_host' | 'restart_app'; + status: string; + reason?: string | null; + requestedBy?: number | null; + issuedAt?: string | null; + expiresAt?: string | null; + publishedAt?: string | null; + ackedAt?: string | null; + executionStartedAt?: string | null; + completedAt?: string | null; + failedAt?: string | null; + errorCode?: string | null; + errorMessage?: string | null; + createdAt?: string | null; + updatedAt?: string | null; +} // Liefert alle Gruppen mit zugehörigen Clients export async function fetchGroupsWithClients(): Promise { const response = await fetch('/api/groups/with_clients'); @@ -79,9 +135,11 @@ export async function updateClient(uuid: string, data: { description?: string; m return await res.json(); } -export async function restartClient(uuid: string): Promise<{ success: boolean; message?: string }> { +export async function restartClient(uuid: string, reason?: string): Promise<{ success: boolean; message?: string; command?: ClientCommand }> { const response = await fetch(`/api/clients/${uuid}/restart`, { method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ reason: reason || null }), }); if (!response.ok) { const error = await response.json(); @@ -90,6 +148,58 @@ export async function restartClient(uuid: string): Promise<{ success: boolean; m return await response.json(); } +export async function shutdownClient(uuid: string, reason?: string): Promise<{ success: boolean; message?: string; command?: ClientCommand }> { + const response = await fetch(`/api/clients/${uuid}/shutdown`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ reason: reason || null }), + }); + if (!response.ok) { + const error = await response.json(); + throw new Error(error.error || 'Fehler beim Herunterfahren des Clients'); + } + return await response.json(); +} + +export async function fetchClientCommandStatus(commandId: string): Promise { + const response = await fetch(`/api/clients/commands/${commandId}`); + if (!response.ok) { + const error = await response.json(); + throw new Error(error.error || 'Fehler beim Laden des Command-Status'); + } + return await response.json(); +} + +export async function fetchCrashedClients(): Promise { + const response = await fetch('/api/clients/crashed', { credentials: 'include' }); + if (!response.ok) { + const err = await response.json().catch(() => ({})); + throw new Error(err.error || 'Fehler beim Laden der abgestürzten Clients'); + } + return await response.json(); +} + +export async function fetchServiceFailedClients(): Promise { + const response = await fetch('/api/clients/service_failed', { credentials: 'include' }); + if (!response.ok) { + const err = await response.json().catch(() => ({})); + throw new Error(err.error || 'Fehler beim Laden der service_failed Clients'); + } + return await response.json(); +} + +export async function clearServiceFailed(uuid: string): Promise<{ success: boolean; message?: string }> { + const response = await fetch(`/api/clients/${uuid}/clear_service_failed`, { + method: 'POST', + credentials: 'include', + }); + if (!response.ok) { + const err = await response.json().catch(() => ({})); + throw new Error(err.error || 'Fehler beim Quittieren des service_failed Flags'); + } + return await response.json(); +} + export async function deleteClient(uuid: string) { const res = await fetch(`/api/clients/${uuid}`, { method: 'DELETE', diff --git a/dashboard/src/monitoring.css b/dashboard/src/monitoring.css index 0d3c45e..16f91ee 100644 --- a/dashboard/src/monitoring.css +++ b/dashboard/src/monitoring.css @@ -370,4 +370,49 @@ .monitoring-log-dialog-actions { padding: 0 0.2rem 0.4rem; } +} + +/* Crash recovery panel */ +.monitoring-crash-panel { + border-left: 4px solid #dc2626; + margin-bottom: 1.5rem; +} + +.monitoring-service-failed-panel { + border-left: 4px solid #ea580c; + margin-bottom: 1.5rem; +} + +.monitoring-crash-table { + width: 100%; + border-collapse: collapse; + font-size: 0.875rem; +} + +.monitoring-crash-table th { + text-align: left; + padding: 0.5rem 0.75rem; + font-weight: 600; + color: #64748b; + border-bottom: 1px solid #e2e8f0; + background: #f8fafc; +} + +.monitoring-crash-table td { + padding: 0.55rem 0.75rem; + border-bottom: 1px solid #f1f5f9; + vertical-align: middle; +} + +.monitoring-crash-table tr:last-child td { + border-bottom: none; +} + +.monitoring-crash-table tr:hover td { + background: #fef2f2; +} + +.monitoring-meta-hint { + color: #94a3b8; + font-size: 0.8rem; } \ No newline at end of file diff --git a/dashboard/src/monitoring.tsx b/dashboard/src/monitoring.tsx index 7ba6f75..b1ec075 100644 --- a/dashboard/src/monitoring.tsx +++ b/dashboard/src/monitoring.tsx @@ -7,6 +7,16 @@ import { type MonitoringLogEntry, type MonitoringOverview, } from './apiClientMonitoring'; +import { + fetchCrashedClients, + fetchServiceFailedClients, + clearServiceFailed, + restartClient, + type CrashedClient, + type CrashedClientsResponse, + type ServiceFailedClient, + type ServiceFailedClientsResponse, +} from './apiClients'; import { useAuth } from './useAuth'; import { ButtonComponent } from '@syncfusion/ej2-react-buttons'; import { DropDownListComponent } from '@syncfusion/ej2-react-dropdowns'; @@ -156,6 +166,12 @@ const MonitoringDashboard: React.FC = () => { const [screenshotErrored, setScreenshotErrored] = React.useState(false); const selectedClientUuidRef = React.useRef(null); const [selectedLogEntry, setSelectedLogEntry] = React.useState(null); + const [crashedClients, setCrashedClients] = React.useState(null); + const [restartStates, setRestartStates] = React.useState>({}); + const [restartErrors, setRestartErrors] = React.useState>({}); + const [serviceFailedClients, setServiceFailedClients] = React.useState(null); + const [clearStates, setClearStates] = React.useState>({}); + const [clearErrors, setClearErrors] = React.useState>({}); const selectedClient = React.useMemo(() => { if (!overview || !selectedClientUuid) return null; @@ -197,9 +213,37 @@ const MonitoringDashboard: React.FC = () => { } }, []); + const loadCrashedClients = React.useCallback(async () => { + try { + const data = await fetchCrashedClients(); + setCrashedClients(data); + } catch { + // non-fatal: crashes panel just stays stale + } + }, []); + + const loadServiceFailedClients = React.useCallback(async () => { + try { + const data = await fetchServiceFailedClients(); + setServiceFailedClients(data); + } catch { + // non-fatal + } + }, []); + React.useEffect(() => { loadOverview(hours, false); - }, [hours, loadOverview]); + loadCrashedClients(); + loadServiceFailedClients(); + }, [hours, loadOverview, loadCrashedClients, loadServiceFailedClients]); + + React.useEffect(() => { + const id = window.setInterval(() => { + loadCrashedClients(); + loadServiceFailedClients(); + }, REFRESH_INTERVAL_MS); + return () => window.clearInterval(id); + }, [loadCrashedClients, loadServiceFailedClients]); React.useEffect(() => { const hasActivePriorityScreenshots = (overview?.summary.activePriorityScreenshots || 0) > 0; @@ -308,6 +352,194 @@ const MonitoringDashboard: React.FC = () => { {renderMetricCard('Fehler-Logs', overview?.summary.errorLogs || 0, 'Im gewählten Zeitraum', '#b91c1c')} + {crashedClients && crashedClients.crashed_count > 0 && ( +
+
+

+ Abgestürzte / Nicht erreichbare Clients +

+ + {crashedClients.crashed_count} + +
+ + + + + + + + + + + + + {crashedClients.clients.map((c: CrashedClient) => { + const state = restartStates[c.uuid] || 'idle'; + const errMsg = restartErrors[c.uuid]; + const displayName = c.description || c.hostname || c.uuid; + return ( + + + + + + + + + ); + })} + +
ClientGruppeUrsacheProzessstatusLetztes SignalAktion
+ {displayName} + {c.ip && ({c.ip})} + {c.group_id ?? '—'} + + {c.crash_reason === 'process_crashed' ? 'Prozess abgestürzt' : 'Heartbeat veraltet'} + + {c.process_status || '—'}{formatRelative(c.last_alive)} + {state === 'loading' && Wird gesendet…} + {state === 'success' && ✓ Neustart gesendet} + {state === 'failed' && ( + + ✗ Fehler + + )} + {(state === 'idle' || state === 'failed') && ( + { + setRestartStates(prev => ({ ...prev, [c.uuid]: 'loading' })); + setRestartErrors(prev => { const n = { ...prev }; delete n[c.uuid]; return n; }); + try { + await restartClient(c.uuid, c.crash_reason); + setRestartStates(prev => ({ ...prev, [c.uuid]: 'success' })); + setTimeout(() => { + setRestartStates(prev => ({ ...prev, [c.uuid]: 'idle' })); + loadCrashedClients(); + }, 8000); + } catch (e) { + const msg = e instanceof Error ? e.message : 'Unbekannter Fehler'; + setRestartStates(prev => ({ ...prev, [c.uuid]: 'failed' })); + setRestartErrors(prev => ({ ...prev, [c.uuid]: msg })); + } + }} + > + Neustart + + )} +
+
+ )} + + {serviceFailedClients && serviceFailedClients.service_failed_count > 0 && ( +
+
+

+ Service dauerhaft ausgefallen (systemd hat aufgegeben) +

+ + {serviceFailedClients.service_failed_count} + +
+

+ Diese Clients konnten von systemd nicht mehr automatisch neugestartet werden. + Manuelle Intervention erforderlich. Nach Behebung bitte quittieren. +

+ + + + + + + + + + + + + {serviceFailedClients.clients.map((c: ServiceFailedClient) => { + const state = clearStates[c.uuid] || 'idle'; + const errMsg = clearErrors[c.uuid]; + const displayName = c.description || c.hostname || c.uuid; + const failedAt = c.service_failed_at + ? new Date(c.service_failed_at.endsWith('Z') ? c.service_failed_at : c.service_failed_at + 'Z').toLocaleString('de-DE') + : '—'; + return ( + + + + + + + + + ); + })} + +
ClientGruppeUnitAusgefallen amLetztes SignalAktion
+ {displayName} + {c.ip && ({c.ip})} + {c.group_id ?? '—'}{c.service_failed_unit || '—'}{failedAt}{formatRelative(c.last_alive)} + {state === 'loading' && Wird quittiert…} + {state === 'success' && ✓ Quittiert} + {state === 'failed' && ( + ✗ Fehler + )} + {(state === 'idle' || state === 'failed') && ( + { + setClearStates(prev => ({ ...prev, [c.uuid]: 'loading' })); + setClearErrors(prev => { const n = { ...prev }; delete n[c.uuid]; return n; }); + try { + await clearServiceFailed(c.uuid); + setClearStates(prev => ({ ...prev, [c.uuid]: 'success' })); + setTimeout(() => { + setClearStates(prev => ({ ...prev, [c.uuid]: 'idle' })); + loadServiceFailedClients(); + }, 4000); + } catch (e) { + const msg = e instanceof Error ? e.message : 'Unbekannter Fehler'; + setClearStates(prev => ({ ...prev, [c.uuid]: 'failed' })); + setClearErrors(prev => ({ ...prev, [c.uuid]: msg })); + } + }} + > + Quittieren + + )} +
+
+ )} + {loading && !overview ? ( ) : ( @@ -393,6 +625,16 @@ const MonitoringDashboard: React.FC = () => { Bildschirmstatus {selectedClient.screenHealthStatus || 'UNKNOWN'} +
+ MQTT Reconnects + {selectedClient.mqttReconnectCount != null ? selectedClient.mqttReconnectCount : '—'} +
+ {selectedClient.mqttLastDisconnectAt && ( +
+ Letzter Disconnect + {formatTimestamp(selectedClient.mqttLastDisconnectAt)} +
+ )}
Letzte Analyse {formatTimestamp(selectedClient.lastScreenshotAnalyzed)} diff --git a/dashboard/src/programminfo.tsx b/dashboard/src/programminfo.tsx index 04f7c0a..c1a112a 100644 --- a/dashboard/src/programminfo.tsx +++ b/dashboard/src/programminfo.tsx @@ -12,10 +12,6 @@ interface ProgramInfo { frontend: { name: string; license: string }[]; backend: { name: string; license: string }[]; }; - buildInfo: { - buildDate: string; - commitId: string; - }; changelog: { version: string; date: string; @@ -85,30 +81,30 @@ const Programminfo: React.FC = () => {
-
-

- Version: {info.version} -

-

- Copyright: {info.copyright} -

-

+

+
Version: {info.version}
+
Copyright: {info.copyright}
+
Support:{' '} {info.supportContact} -

-
-

Build-Informationen

-

- Build-Datum: {new Date(info.buildInfo.buildDate).toLocaleString('de-DE')} -

-

- Commit-ID:{' '} +

+
+
Build-Informationen
+
Build-Datum: {new Date(__BUILD_DATE__).toLocaleString('de-DE')}
+
+ Umgebung:{' '} - {info.buildInfo.commitId} + {__BUILD_ENV__} -

+
+
+ Node.js:{' '} + + {__NODE_VERSION__} + +
diff --git a/dashboard/src/vite-env.d.ts b/dashboard/src/vite-env.d.ts index 11f02fe..723020a 100644 --- a/dashboard/src/vite-env.d.ts +++ b/dashboard/src/vite-env.d.ts @@ -1 +1,5 @@ /// + +declare const __BUILD_DATE__: string; +declare const __NODE_VERSION__: string; +declare const __BUILD_ENV__: string; diff --git a/dashboard/vite.config.ts b/dashboard/vite.config.ts index 2cc4125..934e61f 100644 --- a/dashboard/vite.config.ts +++ b/dashboard/vite.config.ts @@ -6,6 +6,11 @@ import react from '@vitejs/plugin-react'; export default defineConfig({ cacheDir: './.vite', plugins: [react()], + define: { + __BUILD_DATE__: JSON.stringify(new Date().toISOString()), + __NODE_VERSION__: JSON.stringify(process.version), + __BUILD_ENV__: JSON.stringify(process.env.NODE_ENV ?? 'development'), + }, resolve: { // 🔧 KORRIGIERT: Entferne die problematischen Aliases komplett // Diese verursachen das "not an absolute path" Problem diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 8131fa2..8adce3b 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -45,15 +45,37 @@ services: image: eclipse-mosquitto:2.0.21 container_name: infoscreen-mqtt restart: unless-stopped + command: > + sh -c 'set -eu; + : "$${MQTT_USER:?MQTT_USER not set}"; + : "$${MQTT_PASSWORD:?MQTT_PASSWORD not set}"; + touch /mosquitto/config/passwd; + chmod 600 /mosquitto/config/passwd; + mosquitto_passwd -b /mosquitto/config/passwd "$${MQTT_USER}" "$${MQTT_PASSWORD}"; + if [ -n "$${MQTT_CANARY_USER:-}" ] && [ -n "$${MQTT_CANARY_PASSWORD:-}" ]; then + mosquitto_passwd -b /mosquitto/config/passwd "$${MQTT_CANARY_USER}" "$${MQTT_CANARY_PASSWORD}"; + fi; + exec mosquitto -c /mosquitto/config/mosquitto.conf' volumes: - - ./mosquitto/config/mosquitto.conf:/mosquitto/config/mosquitto.conf:ro + - ./mosquitto/config:/mosquitto/config + - ./mosquitto/data:/mosquitto/data + - ./mosquitto/log:/mosquitto/log ports: - "1883:1883" - "9001:9001" + environment: + - MQTT_USER=${MQTT_USER} + - MQTT_PASSWORD=${MQTT_PASSWORD} + - MQTT_CANARY_USER=${MQTT_CANARY_USER:-} + - MQTT_CANARY_PASSWORD=${MQTT_CANARY_PASSWORD:-} networks: - infoscreen-net healthcheck: - test: ["CMD-SHELL", "mosquitto_pub -h localhost -t test -m 'health' || exit 1"] + test: + [ + "CMD-SHELL", + "mosquitto_pub -h localhost -u $$MQTT_USER -P $$MQTT_PASSWORD -t test -m 'health' || exit 1", + ] interval: 30s timeout: 5s retries: 3 @@ -125,6 +147,11 @@ services: DB_PASSWORD: ${DB_PASSWORD} DB_NAME: ${DB_NAME} DB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD} + API_BASE_URL: http://server:8000 + MQTT_BROKER_HOST: ${MQTT_BROKER_HOST:-mqtt} + MQTT_BROKER_PORT: ${MQTT_BROKER_PORT:-1883} + MQTT_USER: ${MQTT_USER} + MQTT_PASSWORD: ${MQTT_PASSWORD} networks: - infoscreen-net @@ -141,7 +168,18 @@ services: environment: # HINZUGEFÜGT: Datenbank-Verbindungsstring DB_CONN: "mysql+pymysql://${DB_USER}:${DB_PASSWORD}@db/${DB_NAME}" - MQTT_PORT: 1883 + MQTT_BROKER_HOST: ${MQTT_BROKER_HOST:-mqtt} + MQTT_BROKER_PORT: ${MQTT_BROKER_PORT:-1883} + MQTT_USER: ${MQTT_USER} + MQTT_PASSWORD: ${MQTT_PASSWORD} + POLL_INTERVAL_SECONDS: ${POLL_INTERVAL_SECONDS:-30} + POWER_INTENT_PUBLISH_ENABLED: ${POWER_INTENT_PUBLISH_ENABLED:-false} + POWER_INTENT_HEARTBEAT_ENABLED: ${POWER_INTENT_HEARTBEAT_ENABLED:-true} + POWER_INTENT_EXPIRY_MULTIPLIER: ${POWER_INTENT_EXPIRY_MULTIPLIER:-3} + POWER_INTENT_MIN_EXPIRY_SECONDS: ${POWER_INTENT_MIN_EXPIRY_SECONDS:-90} + CRASH_RECOVERY_ENABLED: ${CRASH_RECOVERY_ENABLED:-false} + CRASH_RECOVERY_GRACE_SECONDS: ${CRASH_RECOVERY_GRACE_SECONDS:-180} + CRASH_RECOVERY_LOCKOUT_MINUTES: ${CRASH_RECOVERY_LOCKOUT_MINUTES:-15} networks: - infoscreen-net diff --git a/docker-compose.yml b/docker-compose.yml index c1923c1..de69ac1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,6 +19,10 @@ services: - DB_CONN=mysql+pymysql://${DB_USER}:${DB_PASSWORD}@db/${DB_NAME} - DB_URL=mysql+pymysql://${DB_USER}:${DB_PASSWORD}@db/${DB_NAME} - API_BASE_URL=http://server:8000 + - MQTT_BROKER_HOST=${MQTT_BROKER_HOST:-mqtt} + - MQTT_BROKER_PORT=${MQTT_BROKER_PORT:-1883} + - MQTT_USER=${MQTT_USER} + - MQTT_PASSWORD=${MQTT_PASSWORD} - ENV=${ENV:-development} - FLASK_SECRET_KEY=${FLASK_SECRET_KEY:-dev-secret-key-change-in-production} - DEFAULT_SUPERADMIN_USERNAME=${DEFAULT_SUPERADMIN_USERNAME:-superadmin} @@ -70,6 +74,17 @@ services: image: eclipse-mosquitto:2.0.21 # ✅ GUT: Version ist bereits spezifisch container_name: infoscreen-mqtt restart: unless-stopped + command: > + sh -c 'set -eu; + : "$${MQTT_USER:?MQTT_USER not set}"; + : "$${MQTT_PASSWORD:?MQTT_PASSWORD not set}"; + touch /mosquitto/config/passwd; + chmod 600 /mosquitto/config/passwd; + mosquitto_passwd -b /mosquitto/config/passwd "$${MQTT_USER}" "$${MQTT_PASSWORD}"; + if [ -n "$${MQTT_CANARY_USER:-}" ] && [ -n "$${MQTT_CANARY_PASSWORD:-}" ]; then + mosquitto_passwd -b /mosquitto/config/passwd "$${MQTT_CANARY_USER}" "$${MQTT_CANARY_PASSWORD}"; + fi; + exec mosquitto -c /mosquitto/config/mosquitto.conf' volumes: - ./mosquitto/config:/mosquitto/config - ./mosquitto/data:/mosquitto/data @@ -77,13 +92,18 @@ services: ports: - "1883:1883" # Standard MQTT - "9001:9001" # WebSocket (falls benötigt) + environment: + - MQTT_USER=${MQTT_USER} + - MQTT_PASSWORD=${MQTT_PASSWORD} + - MQTT_CANARY_USER=${MQTT_CANARY_USER:-} + - MQTT_CANARY_PASSWORD=${MQTT_CANARY_PASSWORD:-} networks: - infoscreen-net healthcheck: test: [ "CMD-SHELL", - "mosquitto_pub -h localhost -t test -m 'health' || exit 1", + "mosquitto_pub -h localhost -u $$MQTT_USER -P $$MQTT_PASSWORD -t test -m 'health' || exit 1", ] interval: 30s timeout: 5s @@ -169,13 +189,18 @@ services: environment: # HINZUGEFÜGT: Datenbank-Verbindungsstring - DB_CONN=mysql+pymysql://${DB_USER}:${DB_PASSWORD}@db/${DB_NAME} - - MQTT_BROKER_URL=mqtt - - MQTT_PORT=1883 + - MQTT_BROKER_HOST=${MQTT_BROKER_HOST:-mqtt} + - MQTT_BROKER_PORT=${MQTT_BROKER_PORT:-1883} + - MQTT_USER=${MQTT_USER} + - MQTT_PASSWORD=${MQTT_PASSWORD} - POLL_INTERVAL_SECONDS=${POLL_INTERVAL_SECONDS:-30} - POWER_INTENT_PUBLISH_ENABLED=${POWER_INTENT_PUBLISH_ENABLED:-false} - POWER_INTENT_HEARTBEAT_ENABLED=${POWER_INTENT_HEARTBEAT_ENABLED:-true} - POWER_INTENT_EXPIRY_MULTIPLIER=${POWER_INTENT_EXPIRY_MULTIPLIER:-3} - POWER_INTENT_MIN_EXPIRY_SECONDS=${POWER_INTENT_MIN_EXPIRY_SECONDS:-90} + - CRASH_RECOVERY_ENABLED=${CRASH_RECOVERY_ENABLED:-false} + - CRASH_RECOVERY_GRACE_SECONDS=${CRASH_RECOVERY_GRACE_SECONDS:-180} + - CRASH_RECOVERY_LOCKOUT_MINUTES=${CRASH_RECOVERY_LOCKOUT_MINUTES:-15} networks: - infoscreen-net diff --git a/implementation-plans/reboot-command-payload-schemas.json b/implementation-plans/reboot-command-payload-schemas.json new file mode 100644 index 0000000..5f96392 --- /dev/null +++ b/implementation-plans/reboot-command-payload-schemas.json @@ -0,0 +1,149 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://infoscreen.local/schemas/reboot-command-payload-schemas.json", + "title": "Infoscreen Reboot Command Payload Schemas", + "description": "Frozen v1 schemas for per-client command and command acknowledgement payloads.", + "$defs": { + "commandPayloadV1": { + "type": "object", + "additionalProperties": false, + "required": [ + "schema_version", + "command_id", + "client_uuid", + "action", + "issued_at", + "expires_at", + "requested_by", + "reason" + ], + "properties": { + "schema_version": { + "type": "string", + "const": "1.0" + }, + "command_id": { + "type": "string", + "format": "uuid" + }, + "client_uuid": { + "type": "string", + "format": "uuid" + }, + "action": { + "type": "string", + "enum": [ + "reboot_host", + "shutdown_host" + ] + }, + "issued_at": { + "type": "string", + "format": "date-time" + }, + "expires_at": { + "type": "string", + "format": "date-time" + }, + "requested_by": { + "type": [ + "integer", + "null" + ], + "minimum": 1 + }, + "reason": { + "type": [ + "string", + "null" + ], + "maxLength": 2000 + } + } + }, + "commandAckPayloadV1": { + "type": "object", + "additionalProperties": false, + "required": [ + "command_id", + "status", + "error_code", + "error_message" + ], + "properties": { + "command_id": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string", + "enum": [ + "accepted", + "execution_started", + "completed", + "failed" + ] + }, + "error_code": { + "type": [ + "string", + "null" + ], + "maxLength": 128 + }, + "error_message": { + "type": [ + "string", + "null" + ], + "maxLength": 4000 + } + }, + "allOf": [ + { + "if": { + "properties": { + "status": { + "const": "failed" + } + } + }, + "then": { + "properties": { + "error_code": { + "type": "string", + "minLength": 1 + }, + "error_message": { + "type": "string", + "minLength": 1 + } + } + } + } + ] + } + }, + "examples": [ + { + "commandPayloadV1": { + "schema_version": "1.0", + "command_id": "5d1f8b4b-7e85-44fb-8f38-3f5d5da5e2e4", + "client_uuid": "9b8d1856-ff34-4864-a726-12de072d0f77", + "action": "reboot_host", + "issued_at": "2026-04-03T12:48:10Z", + "expires_at": "2026-04-03T12:52:10Z", + "requested_by": 1, + "reason": "operator_request" + } + }, + { + "commandAckPayloadV1": { + "command_id": "5d1f8b4b-7e85-44fb-8f38-3f5d5da5e2e4", + "status": "execution_started", + "error_code": null, + "error_message": null + } + } + ] +} diff --git a/implementation-plans/reboot-command-payload-schemas.md b/implementation-plans/reboot-command-payload-schemas.md new file mode 100644 index 0000000..7612eed --- /dev/null +++ b/implementation-plans/reboot-command-payload-schemas.md @@ -0,0 +1,59 @@ +## Reboot Command Payload Schema Snippets + +This file provides copy-ready validation snippets for client and integration test helpers. + +### Canonical Topics (v1) +1. Command topic: infoscreen/{client_uuid}/commands +2. Ack topic: infoscreen/{client_uuid}/commands/ack + +### Transitional Compatibility Topics +1. Command topic alias: infoscreen/{client_uuid}/command +2. Ack topic alias: infoscreen/{client_uuid}/command/ack + +### Canonical Action Values +1. reboot_host +2. shutdown_host + +### Ack Status Values +1. accepted +2. execution_started +3. completed +4. failed + +### JSON Schema Source +Use this file for machine validation: +1. implementation-plans/reboot-command-payload-schemas.json + +### Minimal Command Schema Snippet +```json +{ + "type": "object", + "additionalProperties": false, + "required": ["schema_version", "command_id", "client_uuid", "action", "issued_at", "expires_at", "requested_by", "reason"], + "properties": { + "schema_version": { "const": "1.0" }, + "command_id": { "type": "string", "format": "uuid" }, + "client_uuid": { "type": "string", "format": "uuid" }, + "action": { "enum": ["reboot_host", "shutdown_host"] }, + "issued_at": { "type": "string", "format": "date-time" }, + "expires_at": { "type": "string", "format": "date-time" }, + "requested_by": { "type": ["integer", "null"] }, + "reason": { "type": ["string", "null"] } + } +} +``` + +### Minimal Ack Schema Snippet +```json +{ + "type": "object", + "additionalProperties": false, + "required": ["command_id", "status", "error_code", "error_message"], + "properties": { + "command_id": { "type": "string", "format": "uuid" }, + "status": { "enum": ["accepted", "execution_started", "completed", "failed"] }, + "error_code": { "type": ["string", "null"] }, + "error_message": { "type": ["string", "null"] } + } +} +``` diff --git a/implementation-plans/reboot-implementation-handoff-client-team.md b/implementation-plans/reboot-implementation-handoff-client-team.md new file mode 100644 index 0000000..f8c984b --- /dev/null +++ b/implementation-plans/reboot-implementation-handoff-client-team.md @@ -0,0 +1,146 @@ +## Client Team Implementation Spec (Raspberry Pi 5) + +### Mission +Implement client-side command handling for reliable restart and shutdown with strict validation, idempotency, acknowledgements, and reboot recovery continuity. + +### Ownership Boundaries +1. Client team owns command intake, execution, acknowledgement emission, and post-reboot continuity. +2. Platform team owns command issuance, lifecycle aggregation, and server-side escalation logic. +3. Client implementation must not assume managed PoE availability. + +### Required Client Behaviors + +### Frozen MQTT Topics and Schemas (v1) +1. Canonical command topic: infoscreen/{client_uuid}/commands. +2. Canonical ack topic: infoscreen/{client_uuid}/commands/ack. +3. Transitional compatibility topics during migration: +- infoscreen/{client_uuid}/command +- infoscreen/{client_uuid}/command/ack +4. QoS policy: command QoS 1, ack QoS 1 recommended. +5. Retain policy: commands and acks are non-retained. + +Frozen command payload schema: + +```json +{ + "schema_version": "1.0", + "command_id": "5d1f8b4b-7e85-44fb-8f38-3f5d5da5e2e4", + "client_uuid": "9b8d1856-ff34-4864-a726-12de072d0f77", + "action": "reboot_host", + "issued_at": "2026-04-03T12:48:10Z", + "expires_at": "2026-04-03T12:52:10Z", + "requested_by": 1, + "reason": "operator_request" +} +``` + +Frozen ack payload schema: + +```json +{ + "command_id": "5d1f8b4b-7e85-44fb-8f38-3f5d5da5e2e4", + "status": "execution_started", + "error_code": null, + "error_message": null +} +``` + +Allowed ack status values: +1. accepted +2. execution_started +3. completed +4. failed + +Frozen command action values for v1: +1. reboot_host +2. shutdown_host + +Reserved but not emitted by server in v1: +1. restart_service + +Validation snippets for helper scripts: +1. Human-readable snippets: implementation-plans/reboot-command-payload-schemas.md +2. Machine-validated JSON Schema: implementation-plans/reboot-command-payload-schemas.json + +### 1. Command Intake +1. Subscribe to the canonical command topic with QoS 1. +2. Parse required fields: schema_version, command_id, action, issued_at, expires_at, reason, requested_by, target metadata. +3. Reject invalid payloads with failed acknowledgement including error_code and diagnostic message. +4. Reject stale commands when current time exceeds expires_at. +5. Ignore already-processed command_id values. + +### 2. Idempotency And Persistence +1. Persist processed command_id and execution result on local storage. +2. Persistence must survive service restart and full OS reboot. +3. On restart, reload dedupe cache before processing newly delivered commands. + +### 3. Acknowledgement Contract Behavior +1. Emit accepted immediately after successful validation and dedupe pass. +2. Emit execution_started immediately before invoking the command action. +3. Emit completed only when local success condition is confirmed. +4. Emit failed with structured error_code on validation or execution failure. +5. If MQTT is temporarily unavailable, retry ack publish with bounded backoff until command expiry. + +### 4. Execution Security Model +1. Execute via systemd-managed privileged helper. +2. Allow only whitelisted operations: +- reboot_host +- shutdown_host +3. Optionally keep restart_service handler as reserved path, but do not require it for v1 conformance. +4. Disallow arbitrary shell commands and untrusted arguments. +5. Enforce per-command execution timeout and terminate hung child processes. + +### 5. Reboot Recovery Continuity +1. For reboot_host action: +- send execution_started +- trigger reboot promptly +2. During startup: +- emit heartbeat early +- emit process-health once service is ready +3. Keep last command execution state available after reboot for reconciliation. + +### 6. Time And Timeout Semantics +1. Use monotonic timers for local elapsed-time checks. +2. Use UTC wall-clock only for protocol timestamps and expiry comparisons. +3. Target reconnect baseline on Pi 5 USB-SATA SSD: 90 seconds. +4. Accept cold-boot and USB enumeration ceiling up to 150 seconds. + +### 7. Capability Reporting +1. Report recovery capability class: +- software_only +- managed_poe_available +- manual_only +2. Report watchdog enabled status. +3. Report boot-source metadata for diagnostics. + +### 8. Error Codes Minimum Set +1. invalid_schema +2. missing_field +3. stale_command +4. duplicate_command +5. permission_denied_local +6. execution_timeout +7. execution_failed +8. broker_unavailable +9. internal_error + +### Acceptance Tests (Client Team) +1. Invalid schema payload is rejected and failed ack emitted. +2. Expired command is rejected and not executed. +3. Duplicate command_id is not executed twice. +4. reboot_host emits execution_started and reconnects with heartbeat in expected window. +5. restart_service action completes without host reboot and emits completed. +6. MQTT outage during ack path retries correctly without duplicate execution. +7. Boot-loop protection cooperates with server-side lockout semantics. + +### Delivery Artifacts +1. Client protocol conformance checklist. +2. Test evidence for all acceptance tests. +3. Runtime logs showing full lifecycle for one restart and one reboot scenario. +4. Known limitations list per image version. + +### Definition Of Done +1. All acceptance tests pass on Pi 5 USB-SATA SSD test devices. +2. No duplicate execution observed under reconnect and retained-delivery edge cases. +3. Acknowledgement sequence is complete and machine-parseable for server correlation. +4. Reboot recovery continuity works without managed PoE dependencies. diff --git a/implementation-plans/reboot-implementation-handoff-share.md b/implementation-plans/reboot-implementation-handoff-share.md new file mode 100644 index 0000000..aaa6505 --- /dev/null +++ b/implementation-plans/reboot-implementation-handoff-share.md @@ -0,0 +1,214 @@ +## Remote Reboot Reliability Handoff (Share Document) + +### Purpose +This document defines the agreed implementation scope for reliable remote reboot and shutdown of Raspberry Pi 5 clients, with monitoring-first visibility and safe escalation paths. + +### Scope +1. In scope: restart and shutdown command reliability. +2. In scope: full lifecycle monitoring and audit visibility. +3. In scope: capability-tier recovery model with optional managed PoE escalation. +4. Out of scope: broader maintenance module in client-management for this cycle. +5. Out of scope: mandatory dependency on customer-managed power switching. + +### Agreed Operating Model +1. Command delivery is asynchronous and lifecycle-tracked, not fire-and-forget. +2. Commands use idempotent command_id semantics with stale-command rejection by expires_at. +3. Monitoring is authoritative for operational state and escalation decisions. +4. Recovery must function even when no managed power switching is available. + +### Frozen Contract v1 (Effective Immediately) +1. Canonical command topic: infoscreen/{client_uuid}/commands. +2. Canonical ack topic: infoscreen/{client_uuid}/commands/ack. +3. Transitional compatibility topics accepted during migration: +- infoscreen/{client_uuid}/command +- infoscreen/{client_uuid}/command/ack +4. QoS policy: command QoS 1, ack QoS 1 recommended. +5. Retain policy: commands and acks are non-retained. + +Command payload schema (frozen): + +```json +{ + "schema_version": "1.0", + "command_id": "5d1f8b4b-7e85-44fb-8f38-3f5d5da5e2e4", + "client_uuid": "9b8d1856-ff34-4864-a726-12de072d0f77", + "action": "reboot_host", + "issued_at": "2026-04-03T12:48:10Z", + "expires_at": "2026-04-03T12:52:10Z", + "requested_by": 1, + "reason": "operator_request" +} +``` + +Ack payload schema (frozen): + +```json +{ + "command_id": "5d1f8b4b-7e85-44fb-8f38-3f5d5da5e2e4", + "status": "execution_started", + "error_code": null, + "error_message": null +} +``` + +Allowed ack status values: +1. accepted +2. execution_started +3. completed +4. failed + +Frozen command action values: +1. reboot_host +2. shutdown_host + +API endpoint mapping: +1. POST /api/clients/{uuid}/restart -> action reboot_host +2. POST /api/clients/{uuid}/shutdown -> action shutdown_host + +Validation snippets: +1. Human-readable snippets: implementation-plans/reboot-command-payload-schemas.md +2. Machine-validated JSON Schema: implementation-plans/reboot-command-payload-schemas.json + +### Command Lifecycle States +1. queued +2. publish_in_progress +3. published +4. ack_received +5. execution_started +6. awaiting_reconnect +7. recovered +8. completed +9. failed +10. expired +11. timed_out +12. canceled +13. blocked_safety +14. manual_intervention_required + +### Timeout Defaults (Pi 5, USB-SATA SSD baseline) +1. queued to publish_in_progress: immediate, timeout 5 seconds. +2. publish_in_progress to published: timeout 8 seconds. +3. published to ack_received: timeout 20 seconds. +4. ack_received to execution_started: 15 seconds for service restart, 25 seconds for host reboot. +5. execution_started to awaiting_reconnect: timeout 10 seconds. +6. awaiting_reconnect to recovered: baseline 90 seconds after validation, cold-boot ceiling 150 seconds. +7. recovered to completed: 15 to 20 seconds based on fleet stability. +8. command expires_at default: 240 seconds, bounded 180 to 360 seconds. + +### Recovery Tiers +1. Tier 0 baseline, always required: watchdog, systemd auto-restart, lifecycle tracking, manual intervention fallback. +2. Tier 1 optional: managed PoE per-port power-cycle escalation where customer infrastructure supports it. +3. Tier 2 no remote power control: direct manual intervention workflow. + +### Governance And Safety +1. Role access: admin and superadmin. +2. Bulk actions require reason capture. +3. Safety lockout: maximum 3 reboot commands per client in 15 minutes. +4. Escalation cooldown: 60 seconds before automatic move to manual_intervention_required. + +### MQTT Auth Hardening (Phase 1, Required Before Broad Rollout) +1. Intranet-only deployment is not sufficient protection for privileged MQTT actions by itself. +2. Phase 1 hardening scope is broker authentication, authorization, and network restriction; payload URL allowlisting is deferred to a later client/server feature. +3. MQTT broker must disable anonymous publish/subscribe access in production. +4. MQTT broker must require authenticated identities for server-side publishers and client devices. +5. MQTT broker must enforce ACLs so that: +- only server-side services can publish to `infoscreen/{client_uuid}/commands` +- only server-side services can publish scheduler event topics +- each client can subscribe only to its own command topics and assigned event topics +- each client can publish only its own ack, heartbeat, health, dashboard, and telemetry topics +6. Broker port exposure must be restricted to the management network and approved hosts only. +7. TLS support is strongly recommended in this phase and should be enabled when operationally feasible. + +### Server Team Actions For Auth Hardening +1. Provision broker credentials for command/event publishers and for client devices. +2. Configure Mosquitto or equivalent broker ACLs for per-topic publish and subscribe restrictions. +3. Disable anonymous access on production brokers. +4. Restrict broker network exposure with firewall rules, VLAN policy, or equivalent network controls. +5. Update server/frontend deployment to publish MQTT with authenticated credentials. +6. Validate that server-side event publishing and reboot/shutdown command publishing still work under the new ACL policy. +7. Coordinate credential distribution and rotation with the client deployment process. + +### MQTT ACL Matrix (Canonical Baseline) +| Actor | Topic Pattern | Publish | Subscribe | Notes | +| --- | --- | --- | --- | --- | +| scheduler-service | infoscreen/events/+ | Yes | No | Publishes retained active event list per group. | +| api-command-publisher | infoscreen/+/commands | Yes | No | Publishes canonical reboot/shutdown commands. | +| api-command-publisher | infoscreen/+/command | Yes | No | Transitional compatibility publish only. | +| api-group-assignment | infoscreen/+/group_id | Yes | No | Publishes retained client-to-group assignment. | +| listener-service | infoscreen/+/commands/ack | No | Yes | Consumes canonical client command acknowledgements. | +| listener-service | infoscreen/+/command/ack | No | Yes | Consumes transitional compatibility acknowledgements. | +| listener-service | infoscreen/+/heartbeat | No | Yes | Consumes heartbeat telemetry. | +| listener-service | infoscreen/+/health | No | Yes | Consumes health telemetry. | +| listener-service | infoscreen/+/dashboard | No | Yes | Consumes dashboard screenshot payloads. | +| listener-service | infoscreen/+/screenshot | No | Yes | Consumes screenshot payloads (if enabled). | +| listener-service | infoscreen/+/logs/error | No | Yes | Consumes client error logs. | +| listener-service | infoscreen/+/logs/warn | No | Yes | Consumes client warn logs. | +| listener-service | infoscreen/+/logs/info | No | Yes | Consumes client info logs. | +| listener-service | infoscreen/discovery | No | Yes | Consumes discovery announcements. | +| listener-service | infoscreen/+/discovery_ack | Yes | No | Publishes discovery acknowledgements. | +| client- | infoscreen//commands | No | Yes | Canonical command intake for this client only. | +| client- | infoscreen//command | No | Yes | Transitional compatibility intake for this client only. | +| client- | infoscreen/events/ | No | Yes | Assigned group event feed only; dynamic per assignment. | +| client- | infoscreen//commands/ack | Yes | No | Canonical command acknowledgements for this client only. | +| client- | infoscreen//command/ack | Yes | No | Transitional compatibility acknowledgements for this client only. | +| client- | infoscreen//heartbeat | Yes | No | Heartbeat telemetry. | +| client- | infoscreen//health | Yes | No | Health telemetry. | +| client- | infoscreen//dashboard | Yes | No | Dashboard status and screenshot payloads. | +| client- | infoscreen//screenshot | Yes | No | Screenshot payloads (if enabled). | +| client- | infoscreen//logs/error | Yes | No | Error log stream. | +| client- | infoscreen//logs/warn | Yes | No | Warning log stream. | +| client- | infoscreen//logs/info | Yes | No | Info log stream. | +| client- | infoscreen/discovery | Yes | No | Discovery announcement. | +| client- | infoscreen//discovery_ack | No | Yes | Discovery acknowledgment from listener. | + +ACL implementation notes: +1. Use per-client identities; client ACLs must be scoped to exact client UUID and must not allow wildcard access to other clients. +2. Event topic subscription (`infoscreen/events/`) should be managed via broker-side ACL provisioning that updates when group assignment changes. +3. Transitional singular command topics are temporary and should be removed after migration cutover. +4. Deny by default: any topic not explicitly listed above should be blocked for each actor. + +### Credential Management Guidance +1. Real MQTT passwords must not be stored in tracked documentation or committed templates. +2. Each client device should receive a unique broker username and password, stored only in its local [/.env](.env). +3. Server-side publisher credentials should be stored in the server team's secret-management path, not in source control. +4. Recommended naming convention for client broker users: `infoscreen-client-`. +5. Client passwords should be random, at least 20 characters, and rotated through deployment tooling or broker administration procedures. +6. The server/infrastructure team owns broker-side user creation, ACL assignment, rotation, and revocation. +7. The client team owns loading credentials from local env files and validating connection behavior against the secured broker. + +### Client Team Actions For Auth Hardening +1. Add MQTT username/password support in the client connection setup. +2. Add client-side TLS configuration support from environment when certificates are provided. +3. Update local test helpers to support authenticated MQTT publishing and subscription. +4. Validate command and event intake against the authenticated broker configuration before canary rollout. + +### Ready For Server/Frontend Team (Auth Phase) +1. Client implementation is ready to connect with MQTT auth from local `.env` (`MQTT_USERNAME`, `MQTT_PASSWORD`, optional TLS settings). +2. Client command/event intake and client ack/telemetry publishing run over the authenticated MQTT session. +3. Server/frontend team must now complete broker-side enforcement and publisher migration. + +Server/frontend done criteria: +1. Anonymous broker access is disabled in production. +2. Server-side publishers use authenticated broker credentials. +3. ACLs are active and validated for command, event, and client telemetry topics. +4. At least one canary client proves end-to-end flow under ACLs: +- server publishes command/event with authenticated publisher +- client receives payload +- client sends ack/telemetry successfully +5. Revocation test passes: disabling one client credential blocks only that client without impacting others. + +Operational note: +1. Client-side auth support is necessary but not sufficient by itself; broker ACL/auth enforcement is the security control that must be enabled by the server/infrastructure team. + +### Rollout Plan +1. Contract freeze and sign-off. +2. Platform and client implementation against frozen schemas. +3. One-group canary. +4. Rollback if failed plus timed_out exceeds 5 percent. +5. Expand only after 7 days below intervention threshold. + +### Success Criteria +1. Deterministic command lifecycle visibility from enqueue to completion. +2. No duplicate execution under reconnect or delayed-delivery conditions. +3. Stable Pi 5 SSD reconnect behavior within defined baseline. +4. Clear and actionable manual intervention states when automatic recovery is exhausted. diff --git a/implementation-plans/reboot-kickoff-summary.md b/implementation-plans/reboot-kickoff-summary.md new file mode 100644 index 0000000..c739640 --- /dev/null +++ b/implementation-plans/reboot-kickoff-summary.md @@ -0,0 +1,54 @@ +## Reboot Reliability Kickoff Summary + +### Objective +Ship a reliable, observable restart and shutdown workflow for Raspberry Pi 5 clients, with safe escalation and clear operator outcomes. + +### What Is Included +1. Asynchronous command lifecycle with idempotent command_id handling. +2. Monitoring-first state visibility from queued to terminal outcomes. +3. Client acknowledgements for accepted, execution_started, completed, and failed. +4. Pi 5 USB-SATA SSD timeout baseline and tuning rules. +5. Capability-tier recovery with optional managed PoE escalation. + +### What Is Not Included +1. Full maintenance module in client-management. +2. Required managed power-switch integration. +3. Production Wake-on-LAN rollout. + +### Team Split +1. Platform team: API command lifecycle, safety controls, listener ack ingestion. +2. Web team: lifecycle-aware UX and command status display. +3. Client team: strict validation, dedupe, ack sequence, secure execution helper, reboot continuity. + +### Ownership Matrix +| Team | Primary Plan File | Main Deliverables | +| --- | --- | --- | +| Platform team | implementation-plans/reboot-implementation-handoff-share.md | Command lifecycle backend, policy enforcement, listener ack mapping, safety lockout and escalation | +| Web team | implementation-plans/reboot-implementation-handoff-share.md | Lifecycle UI states, bulk safety UX, capability visibility, command status polling | +| Client team | implementation-plans/reboot-implementation-handoff-client-team.md | Command validation, dedupe persistence, ack sequence, secure execution helper, reboot continuity | +| Project coordination | implementation-plans/reboot-kickoff-summary.md | Phase sequencing, canary gates, rollback thresholds, cross-team sign-off tracking | + +### Baseline Operational Defaults +1. Safety lockout: 3 reboot commands per client in rolling 15 minutes. +2. Escalation cooldown: 60 seconds. +3. Reconnect target on Pi 5 SSD: 90 seconds baseline, 150 seconds cold-boot ceiling. +4. Rollback canary trigger: failed plus timed_out above 5 percent. + +### Frozen Contract Snapshot +1. Canonical command topic: infoscreen/{client_uuid}/commands. +2. Canonical ack topic: infoscreen/{client_uuid}/commands/ack. +3. Transitional compatibility topics during migration: +- infoscreen/{client_uuid}/command +- infoscreen/{client_uuid}/command/ack +4. Command schema version: 1.0. +5. Allowed command actions: reboot_host, shutdown_host. +6. Allowed ack status values: accepted, execution_started, completed, failed. +7. Validation snippets: +- implementation-plans/reboot-command-payload-schemas.md +- implementation-plans/reboot-command-payload-schemas.json + +### Immediate Next Steps +1. Continue implementation in parallel by team against frozen contract. +2. Client team validates dedupe and expiry handling on canonical topics. +3. Platform team verifies ack-state transitions for accepted, execution_started, completed, failed. +4. Execute one-group canary and validate timing plus failure drills. diff --git a/implementation-plans/server-team-actions.md b/implementation-plans/server-team-actions.md new file mode 100644 index 0000000..a28faa8 --- /dev/null +++ b/implementation-plans/server-team-actions.md @@ -0,0 +1,127 @@ +# Server Team Action Items — Infoscreen Client + +This document lists everything the server/infrastructure/frontend team must implement to complete the client integration. The client-side code is production-ready for all items listed here. + +--- + +## 1. MQTT Broker Hardening (prerequisite for everything else) + +- Disable anonymous access on the broker. +- Create one broker account **per client device**: + - Username convention: `infoscreen-client-` (e.g. `infoscreen-client-9b8d1856`) + - Provision the password to the device `.env` as `MQTT_PASSWORD_BROKER=` +- Create a **server/publisher account** (e.g. `infoscreen-server`) for all server-side publishes. +- Enforce ACLs: + +| Topic | Publisher | +|---|---| +| `infoscreen/{uuid}/commands` | server only | +| `infoscreen/{uuid}/command` (alias) | server only | +| `infoscreen/{uuid}/group_id` | server only | +| `infoscreen/events/{group_id}` | server only | +| `infoscreen/groups/+/power/intent` | server only | +| `infoscreen/{uuid}/commands/ack` | client only | +| `infoscreen/{uuid}/command/ack` | client only | +| `infoscreen/{uuid}/heartbeat` | client only | +| `infoscreen/{uuid}/health` | client only | +| `infoscreen/{uuid}/logs/#` | client only | +| `infoscreen/{uuid}/service_failed` | client only | + +--- + +## 2. Reboot / Shutdown Command — Ack Lifecycle + +Client publishes ack status updates to two topics per command (canonical + transitional alias): +- `infoscreen/{uuid}/commands/ack` +- `infoscreen/{uuid}/command/ack` + +**Ack payload schema (v1, frozen):** +```json +{ + "command_id": "07aab032-53c2-45ef-a5a3-6aa58e9d9fae", + "status": "accepted | execution_started | completed | failed", + "error_code": null, + "error_message": null +} +``` + +**Status lifecycle:** + +| Status | When | Notes | +|---|---|---| +| `accepted` | Command received and validated | Immediate | +| `execution_started` | Helper invoked | Immediate after accepted | +| `completed` | Execution confirmed | For `reboot_host`: arrives after reconnect (10–90 s after `execution_started`) | +| `failed` | Helper returned error | `error_code` and `error_message` will be set | + +**Server must:** +- Track `command_id` through the full lifecycle and update status in DB/UI. +- Surface `failed` + `error_code` to the operator UI. +- Expect `reboot_host` `completed` to arrive after a reconnect delay — do not treat the gap as a timeout. +- Use `expires_at` from the original command to determine when to abandon waiting. + +--- + +## 3. Health Dashboard — Broker Connection Fields (Gap 2) + +Every `infoscreen/{uuid}/health` payload now includes a `broker_connection` block: + +```json +{ + "timestamp": "2026-04-05T08:00:00.000000+00:00", + "expected_state": { "event_id": 42 }, + "actual_state": { + "process": "display_manager", + "pid": 1234, + "status": "running" + }, + "broker_connection": { + "broker_reachable": true, + "reconnect_count": 2, + "last_disconnect_at": "2026-04-04T10:30:00Z" + } +} +``` + +**Server must:** +- Display `reconnect_count` and `last_disconnect_at` per device in the health dashboard. +- Implement alerting heuristic: + - **All** clients go silent simultaneously → likely broker outage, not device crash. + - **Single** client goes silent → device crash, network failure, or process hang. + +--- + +## 4. Service-Failed MQTT Notification (Gap 3) + +When systemd gives up restarting a service after repeated crashes (`StartLimitBurst` exceeded), the client automatically publishes a **retained** message: + +**Topic:** `infoscreen/{uuid}/service_failed` + +**Payload:** +```json +{ + "event": "service_failed", + "unit": "infoscreen-simclient.service", + "client_uuid": "9b8d1856-ff34-4864-a726-12de072d0f77", + "failed_at": "2026-04-05T08:00:00Z" +} +``` + +**Server must:** +- Subscribe to `infoscreen/+/service_failed` on startup (retained — message survives broker restart). +- Alert the operator immediately when this topic receives a payload. +- **Clear the retained message** once the device is acknowledged or recovered: + ``` + mosquitto_pub -t "infoscreen/{uuid}/service_failed" -n --retain + ``` + +--- + +## 5. No Server Action Required + +These items are fully implemented client-side and require no server changes: + +- systemd watchdog (`WatchdogSec=60`) — hangs detected and process restarted automatically. +- Command deduplication — `command_id` deduplicated with 24-hour TTL. +- Ack retry backoff — client retries ack publish on broker disconnect until `expires_at`. +- Mock helper / test mode (`COMMAND_MOCK_REBOOT_IMMEDIATE_COMPLETE`) — development only. diff --git a/listener/listener.py b/listener/listener.py index d730e6d..ef6b8fb 100644 --- a/listener/listener.py +++ b/listener/listener.py @@ -4,11 +4,12 @@ import logging import datetime import base64 import re +import ssl import requests import paho.mqtt.client as mqtt from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker -from models.models import Client, ClientLog, LogLevel, ProcessStatus, ScreenHealthStatus +from models.models import Client, ClientLog, ClientCommand, LogLevel, ProcessStatus, ScreenHealthStatus logging.basicConfig(level=logging.DEBUG, format='%(asctime)s [%(levelname)s] %(message)s') # Load .env only when not already configured by Docker (API_BASE_URL not set by compose means we're outside a container) @@ -32,6 +33,16 @@ Session = sessionmaker(bind=engine) # API configuration API_BASE_URL = os.getenv("API_BASE_URL", "http://server:8000") +MQTT_BROKER_HOST = os.getenv("MQTT_BROKER_HOST", "mqtt") +MQTT_BROKER_PORT = int(os.getenv("MQTT_BROKER_PORT", os.getenv("MQTT_PORT", "1883"))) +MQTT_USERNAME = os.getenv("MQTT_USER") or os.getenv("MQTT_USERNAME") +MQTT_PASSWORD = os.getenv("MQTT_PASSWORD") +MQTT_TLS_ENABLED = os.getenv("MQTT_TLS_ENABLED", "false").strip().lower() in ("1", "true", "yes", "on") +MQTT_TLS_CA_CERT = os.getenv("MQTT_TLS_CA_CERT") +MQTT_TLS_CERTFILE = os.getenv("MQTT_TLS_CERTFILE") +MQTT_TLS_KEYFILE = os.getenv("MQTT_TLS_KEYFILE") +MQTT_TLS_INSECURE = os.getenv("MQTT_TLS_INSECURE", "false").strip().lower() in ("1", "true", "yes", "on") + # Dashboard payload migration observability DASHBOARD_METRICS_LOG_EVERY = int(os.getenv("DASHBOARD_METRICS_LOG_EVERY", "5")) DASHBOARD_PARSE_METRICS = { @@ -376,8 +387,11 @@ def on_connect(client, userdata, flags, reasonCode, properties): client.subscribe("infoscreen/+/logs/warn") client.subscribe("infoscreen/+/logs/info") client.subscribe("infoscreen/+/health") + client.subscribe("infoscreen/+/commands/ack") + client.subscribe("infoscreen/+/command/ack") + client.subscribe("infoscreen/+/service_failed") - logging.info(f"MQTT connected (reasonCode: {reasonCode}); (re)subscribed to discovery, heartbeats, screenshots, dashboards, logs, and health") + logging.info(f"MQTT connected (reasonCode: {reasonCode}); (re)subscribed to discovery, heartbeats, screenshots, dashboards, logs, health, and service_failed") except Exception as e: logging.error(f"Subscribe failed on connect: {e}") @@ -387,6 +401,72 @@ def on_message(client, userdata, msg): logging.debug(f"Empfangene Nachricht auf Topic: {topic}") try: + # Command acknowledgement handling + if topic.startswith("infoscreen/") and (topic.endswith("/commands/ack") or topic.endswith("/command/ack")): + uuid = topic.split("/")[1] + try: + payload = json.loads(msg.payload.decode()) + except (json.JSONDecodeError, UnicodeDecodeError): + logging.error(f"Ungueltiges Command-ACK Payload von {uuid}") + return + + command_id = payload.get("command_id") + ack_status = str(payload.get("status", "")).strip().lower() + error_code = payload.get("error_code") + error_message = payload.get("error_message") + + if not command_id: + logging.warning(f"Command-ACK ohne command_id von {uuid}") + return + + status_map = { + "accepted": "ack_received", + "execution_started": "execution_started", + "completed": "completed", + "failed": "failed", + } + mapped_status = status_map.get(ack_status) + if not mapped_status: + logging.warning(f"Unbekannter Command-ACK Status '{ack_status}' von {uuid}") + return + + db_session = Session() + try: + command_obj = db_session.query(ClientCommand).filter_by(command_id=command_id).first() + if not command_obj: + logging.warning(f"Command-ACK fuer unbekanntes command_id={command_id} von {uuid}") + return + + # Ignore stale/duplicate regressions. + terminal_states = {"completed", "failed", "expired", "canceled", "blocked_safety"} + if command_obj.status in terminal_states: + logging.info( + f"Command-ACK ignoriert (bereits terminal): command_id={command_id}, status={command_obj.status}" + ) + return + + now_utc = datetime.datetime.now(datetime.UTC) + command_obj.status = mapped_status + if mapped_status == "ack_received": + command_obj.acked_at = now_utc + elif mapped_status == "execution_started": + command_obj.execution_started_at = now_utc + elif mapped_status == "completed": + command_obj.completed_at = now_utc + elif mapped_status == "failed": + command_obj.failed_at = now_utc + command_obj.error_code = str(error_code) if error_code is not None else command_obj.error_code + command_obj.error_message = str(error_message) if error_message is not None else command_obj.error_message + + db_session.commit() + logging.info(f"Command-ACK verarbeitet: command_id={command_id}, status={mapped_status}, uuid={uuid}") + except Exception as e: + db_session.rollback() + logging.error(f"Fehler bei Command-ACK Verarbeitung ({command_id}): {e}") + finally: + db_session.close() + return + # Dashboard-Handling (nested screenshot payload) if topic.startswith("infoscreen/") and topic.endswith("/dashboard"): uuid = topic.split("/")[1] @@ -506,6 +586,43 @@ def on_message(client, userdata, msg): logging.error(f"Could not parse log payload from {uuid}: {e}") return + # Service-failed handling (systemd gave up restarting — retained message) + if topic.startswith("infoscreen/") and topic.endswith("/service_failed"): + uuid = topic.split("/")[1] + # Empty payload = retained message cleared; ignore it. + if not msg.payload: + logging.info(f"service_failed retained message cleared for {uuid}") + return + try: + payload_data = json.loads(msg.payload.decode()) + failed_at_str = payload_data.get("failed_at") + unit = payload_data.get("unit", "") + try: + failed_at = datetime.datetime.fromisoformat(failed_at_str.replace("Z", "+00:00")) if failed_at_str else datetime.datetime.now(datetime.UTC) + if failed_at.tzinfo is None: + failed_at = failed_at.replace(tzinfo=datetime.UTC) + except (ValueError, AttributeError): + failed_at = datetime.datetime.now(datetime.UTC) + + session = Session() + try: + client_obj = session.query(Client).filter_by(uuid=uuid).first() + if client_obj: + client_obj.service_failed_at = failed_at + client_obj.service_failed_unit = unit[:128] if unit else None + session.commit() + logging.warning(f"event=service_failed uuid={uuid} unit={unit} failed_at={failed_at.isoformat()}") + else: + logging.warning(f"service_failed received for unknown client uuid={uuid}") + except Exception as e: + session.rollback() + logging.error(f"Error persisting service_failed for {uuid}: {e}") + finally: + session.close() + except (json.JSONDecodeError, UnicodeDecodeError) as e: + logging.error(f"Could not parse service_failed payload from {uuid}: {e}") + return + # Health-Handling if topic.startswith("infoscreen/") and topic.endswith("/health"): uuid = topic.split("/")[1] @@ -531,6 +648,26 @@ def on_message(client, userdata, msg): screen_health_status=screen_health_status, last_screenshot_analyzed=parse_timestamp((payload_data.get('health_metrics') or {}).get('last_frame_update')), ) + + # Update broker connection health fields + broker_conn = payload_data.get('broker_connection') + if isinstance(broker_conn, dict): + reconnect_count = broker_conn.get('reconnect_count') + last_disconnect_str = broker_conn.get('last_disconnect_at') + if reconnect_count is not None: + try: + client_obj.mqtt_reconnect_count = int(reconnect_count) + except (ValueError, TypeError): + pass + if last_disconnect_str: + try: + last_disconnect = datetime.datetime.fromisoformat(last_disconnect_str.replace('Z', '+00:00')) + if last_disconnect.tzinfo is None: + last_disconnect = last_disconnect.replace(tzinfo=datetime.UTC) + client_obj.mqtt_last_disconnect_at = last_disconnect + except (ValueError, AttributeError): + pass + session.commit() logging.debug(f"Health update from {uuid}: {actual.get('process')} ({actual.get('status')})") session.close() @@ -589,9 +726,29 @@ def main(): mqtt_client.on_connect = on_connect # Set an exponential reconnect delay to survive broker restarts mqtt_client.reconnect_delay_set(min_delay=1, max_delay=60) - mqtt_client.connect("mqtt", 1883) - logging.info("Listener gestartet; warte auf MQTT-Verbindung und Nachrichten") + if MQTT_USERNAME and MQTT_PASSWORD: + mqtt_client.username_pw_set(MQTT_USERNAME, MQTT_PASSWORD) + + if MQTT_TLS_ENABLED: + mqtt_client.tls_set( + ca_certs=MQTT_TLS_CA_CERT, + certfile=MQTT_TLS_CERTFILE, + keyfile=MQTT_TLS_KEYFILE, + cert_reqs=ssl.CERT_REQUIRED, + ) + if MQTT_TLS_INSECURE: + mqtt_client.tls_insecure_set(True) + + mqtt_client.connect(MQTT_BROKER_HOST, MQTT_BROKER_PORT) + + logging.info( + "Listener gestartet; warte auf MQTT-Verbindung und Nachrichten (host=%s port=%s tls=%s auth=%s)", + MQTT_BROKER_HOST, + MQTT_BROKER_PORT, + MQTT_TLS_ENABLED, + bool(MQTT_USERNAME and MQTT_PASSWORD), + ) mqtt_client.loop_forever() diff --git a/models/models.py b/models/models.py index 38e5e40..2a9ebeb 100644 --- a/models/models.py +++ b/models/models.py @@ -147,6 +147,14 @@ class Client(Base): screen_health_status = Column(Enum(ScreenHealthStatus), nullable=True, server_default='UNKNOWN') last_screenshot_hash = Column(String(32), nullable=True) + # Systemd service-failed tracking + service_failed_at = Column(TIMESTAMP(timezone=True), nullable=True) + service_failed_unit = Column(String(128), nullable=True) + + # MQTT broker connection health + mqtt_reconnect_count = Column(Integer, nullable=True) + mqtt_last_disconnect_at = Column(TIMESTAMP(timezone=True), nullable=True) + class ClientLog(Base): __tablename__ = 'client_logs' @@ -164,6 +172,33 @@ class ClientLog(Base): ) +class ClientCommand(Base): + __tablename__ = 'client_commands' + + id = Column(Integer, primary_key=True, autoincrement=True) + command_id = Column(String(36), nullable=False, unique=True, index=True) + client_uuid = Column(String(36), ForeignKey('clients.uuid', ondelete='CASCADE'), nullable=False, index=True) + action = Column(String(32), nullable=False, index=True) + status = Column(String(40), nullable=False, index=True) + reason = Column(Text, nullable=True) + requested_by = Column(Integer, ForeignKey('users.id', ondelete='SET NULL'), nullable=True, index=True) + issued_at = Column(TIMESTAMP(timezone=True), nullable=False) + expires_at = Column(TIMESTAMP(timezone=True), nullable=False) + published_at = Column(TIMESTAMP(timezone=True), nullable=True) + acked_at = Column(TIMESTAMP(timezone=True), nullable=True) + execution_started_at = Column(TIMESTAMP(timezone=True), nullable=True) + completed_at = Column(TIMESTAMP(timezone=True), nullable=True) + failed_at = Column(TIMESTAMP(timezone=True), nullable=True) + error_code = Column(String(64), nullable=True) + error_message = Column(Text, nullable=True) + created_at = Column(TIMESTAMP(timezone=True), server_default=func.current_timestamp(), nullable=False) + updated_at = Column(TIMESTAMP(timezone=True), server_default=func.current_timestamp(), onupdate=func.current_timestamp(), nullable=False) + + __table_args__ = ( + Index('ix_client_commands_client_status_created', 'client_uuid', 'status', 'created_at'), + ) + + class EventType(enum.Enum): presentation = "presentation" website = "website" diff --git a/scheduler/db_utils.py b/scheduler/db_utils.py index 35f0799..853a57d 100644 --- a/scheduler/db_utils.py +++ b/scheduler/db_utils.py @@ -1,13 +1,14 @@ # scheduler/db_utils.py from dotenv import load_dotenv import os -from datetime import datetime +from datetime import datetime, timedelta, timezone import hashlib import json import logging from sqlalchemy.orm import sessionmaker, joinedload from sqlalchemy import create_engine, or_, and_, text -from models.models import Event, EventMedia, EventException, SystemSetting +import uuid as _uuid_mod +from models.models import Event, EventMedia, EventException, SystemSetting, Client, ClientCommand, ProcessStatus from dateutil.rrule import rrulestr from urllib.request import Request, urlopen from datetime import timezone @@ -454,3 +455,167 @@ def format_event_with_media(event): # Add other event types (message, etc.) here as needed... return event_dict + + +# --------------------------------------------------------------------------- +# Crash detection / auto-recovery helpers +# --------------------------------------------------------------------------- + +_CRASH_RECOVERY_SCHEMA_VERSION = "1.0" +_CRASH_COMMAND_TOPIC = "infoscreen/{uuid}/commands" +_CRASH_COMMAND_COMPAT_TOPIC = "infoscreen/{uuid}/command" +_CRASH_RECOVERY_EXPIRY_SECONDS = int(os.getenv("CRASH_RECOVERY_COMMAND_EXPIRY_SECONDS", "240")) +_CRASH_RECOVERY_LOCKOUT_MINUTES = int(os.getenv("CRASH_RECOVERY_LOCKOUT_MINUTES", "15")) + + +def get_crash_recovery_candidates(heartbeat_grace_seconds: int) -> list: + """ + Returns a list of dicts for active clients that are crashed (process_status=crashed) + or heartbeat-stale, and don't already have a recent recovery command in the lockout window. + """ + session = Session() + try: + now = datetime.now(timezone.utc) + stale_cutoff = now - timedelta(seconds=heartbeat_grace_seconds) + lockout_cutoff = now - timedelta(minutes=_CRASH_RECOVERY_LOCKOUT_MINUTES) + + candidates = ( + session.query(Client) + .filter(Client.is_active == True) + .filter( + or_( + Client.process_status == ProcessStatus.crashed, + Client.last_alive < stale_cutoff, + ) + ) + .all() + ) + + result = [] + for c in candidates: + recent = ( + session.query(ClientCommand) + .filter(ClientCommand.client_uuid == c.uuid) + .filter(ClientCommand.created_at >= lockout_cutoff) + .filter(ClientCommand.action.in_(["reboot_host", "restart_app"])) + .first() + ) + if recent: + continue + crash_reason = ( + "process_crashed" + if c.process_status == ProcessStatus.crashed + else "heartbeat_stale" + ) + result.append({ + "uuid": c.uuid, + "reason": crash_reason, + "process_status": c.process_status.value if c.process_status else None, + "last_alive": c.last_alive, + }) + return result + finally: + session.close() + + +def issue_crash_recovery_command(client_uuid: str, reason: str) -> tuple: + """ + Writes a ClientCommand (reboot_host) for crash recovery to the DB. + Returns (command_id, payload_dict) for the caller to publish over MQTT. + Also returns the MQTT topic strings. + """ + session = Session() + try: + now = datetime.now(timezone.utc) + expires_at = now + timedelta(seconds=_CRASH_RECOVERY_EXPIRY_SECONDS) + command_id = str(_uuid_mod.uuid4()) + + command = ClientCommand( + command_id=command_id, + client_uuid=client_uuid, + action="reboot_host", + status="queued", + reason=reason, + requested_by=None, + issued_at=now, + expires_at=expires_at, + ) + session.add(command) + session.commit() + command.status = "publish_in_progress" + session.commit() + + payload = { + "schema_version": _CRASH_RECOVERY_SCHEMA_VERSION, + "command_id": command_id, + "client_uuid": client_uuid, + "action": "reboot_host", + "issued_at": now.isoformat().replace("+00:00", "Z"), + "expires_at": expires_at.isoformat().replace("+00:00", "Z"), + "requested_by": None, + "reason": reason, + } + topic = _CRASH_COMMAND_TOPIC.format(uuid=client_uuid) + compat_topic = _CRASH_COMMAND_COMPAT_TOPIC.format(uuid=client_uuid) + return command_id, payload, topic, compat_topic + except Exception: + session.rollback() + raise + finally: + session.close() + + +def finalize_crash_recovery_command(command_id: str, published: bool, error: str = None) -> None: + """Updates command status after MQTT publish attempt.""" + session = Session() + try: + cmd = session.query(ClientCommand).filter_by(command_id=command_id).first() + if not cmd: + return + now = datetime.now(timezone.utc) + if published: + cmd.status = "published" + cmd.published_at = now + else: + cmd.status = "failed" + cmd.failed_at = now + cmd.error_code = "mqtt_publish_failed" + cmd.error_message = error or "Unknown publish error" + session.commit() + finally: + session.close() + + +_TERMINAL_COMMAND_STATUSES = {"completed", "failed", "expired", "canceled", "blocked_safety"} + + +def sweep_expired_commands() -> int: + """Marks non-terminal commands whose expires_at has passed as expired. + + Returns the number of commands updated. + """ + session = Session() + try: + now = datetime.now(timezone.utc) + commands = ( + session.query(ClientCommand) + .filter( + ClientCommand.expires_at < now, + ClientCommand.status.notin_(_TERMINAL_COMMAND_STATUSES), + ) + .all() + ) + if not commands: + return 0 + for cmd in commands: + cmd.status = "expired" + cmd.failed_at = now + cmd.error_code = "expired" + cmd.error_message = "Command expired before terminal state was reached." + session.commit() + return len(commands) + except Exception: + session.rollback() + raise + finally: + session.close() diff --git a/scheduler/scheduler.py b/scheduler/scheduler.py index 0c390c4..cd762cd 100644 --- a/scheduler/scheduler.py +++ b/scheduler/scheduler.py @@ -8,12 +8,28 @@ from .db_utils import ( compute_group_power_intent_basis, build_group_power_intent_body, compute_group_power_intent_fingerprint, + get_crash_recovery_candidates, + issue_crash_recovery_command, + finalize_crash_recovery_command, + sweep_expired_commands, ) import paho.mqtt.client as mqtt import json import datetime import time import uuid +import ssl + + +MQTT_BROKER_HOST = os.getenv("MQTT_BROKER_HOST", os.getenv("MQTT_BROKER_URL", "mqtt")) +MQTT_BROKER_PORT = int(os.getenv("MQTT_BROKER_PORT", os.getenv("MQTT_PORT", "1883"))) +MQTT_USERNAME = os.getenv("MQTT_USER") or os.getenv("MQTT_USERNAME") +MQTT_PASSWORD = os.getenv("MQTT_PASSWORD") +MQTT_TLS_ENABLED = os.getenv("MQTT_TLS_ENABLED", "false").strip().lower() in ("1", "true", "yes", "on") +MQTT_TLS_CA_CERT = os.getenv("MQTT_TLS_CA_CERT") +MQTT_TLS_CERTFILE = os.getenv("MQTT_TLS_CERTFILE") +MQTT_TLS_KEYFILE = os.getenv("MQTT_TLS_KEYFILE") +MQTT_TLS_INSECURE = os.getenv("MQTT_TLS_INSECURE", "false").strip().lower() in ("1", "true", "yes", "on") def _to_utc_z(dt: datetime.datetime) -> str: @@ -224,6 +240,19 @@ def main(): client = mqtt.Client(callback_api_version=mqtt.CallbackAPIVersion.VERSION2) client.reconnect_delay_set(min_delay=1, max_delay=30) + if MQTT_USERNAME and MQTT_PASSWORD: + client.username_pw_set(MQTT_USERNAME, MQTT_PASSWORD) + + if MQTT_TLS_ENABLED: + client.tls_set( + ca_certs=MQTT_TLS_CA_CERT, + certfile=MQTT_TLS_CERTFILE, + keyfile=MQTT_TLS_KEYFILE, + cert_reqs=ssl.CERT_REQUIRED, + ) + if MQTT_TLS_INSECURE: + client.tls_insecure_set(True) + POLL_INTERVAL = int(os.getenv("POLL_INTERVAL_SECONDS", "30")) # 0 = aus; z.B. 600 für alle 10 Min # initial value from DB or fallback to env @@ -238,16 +267,21 @@ def main(): POWER_INTENT_HEARTBEAT_ENABLED = _env_bool("POWER_INTENT_HEARTBEAT_ENABLED", True) POWER_INTENT_EXPIRY_MULTIPLIER = int(os.getenv("POWER_INTENT_EXPIRY_MULTIPLIER", "3")) POWER_INTENT_MIN_EXPIRY_SECONDS = int(os.getenv("POWER_INTENT_MIN_EXPIRY_SECONDS", "90")) + CRASH_RECOVERY_ENABLED = _env_bool("CRASH_RECOVERY_ENABLED", False) + CRASH_RECOVERY_GRACE_SECONDS = int(os.getenv("CRASH_RECOVERY_GRACE_SECONDS", "180")) logging.info( "Scheduler config: poll_interval=%ss refresh_seconds=%s power_intent_enabled=%s " - "power_intent_heartbeat=%s power_intent_expiry_multiplier=%s power_intent_min_expiry=%ss", + "power_intent_heartbeat=%s power_intent_expiry_multiplier=%s power_intent_min_expiry=%ss " + "crash_recovery_enabled=%s crash_recovery_grace=%ss", POLL_INTERVAL, REFRESH_SECONDS, POWER_INTENT_PUBLISH_ENABLED, POWER_INTENT_HEARTBEAT_ENABLED, POWER_INTENT_EXPIRY_MULTIPLIER, POWER_INTENT_MIN_EXPIRY_SECONDS, + CRASH_RECOVERY_ENABLED, + CRASH_RECOVERY_GRACE_SECONDS, ) # Konfigurierbares Zeitfenster in Tagen (Standard: 7) WINDOW_DAYS = int(os.getenv("EVENTS_WINDOW_DAYS", "7")) @@ -275,8 +309,15 @@ def main(): client.on_connect = on_connect - client.connect("mqtt", 1883) + client.connect(MQTT_BROKER_HOST, MQTT_BROKER_PORT) client.loop_start() + logging.info( + "MQTT connection configured host=%s port=%s tls=%s auth=%s", + MQTT_BROKER_HOST, + MQTT_BROKER_PORT, + MQTT_TLS_ENABLED, + bool(MQTT_USERNAME and MQTT_PASSWORD), + ) while True: now = datetime.datetime.now(datetime.timezone.utc) @@ -390,6 +431,51 @@ def main(): power_intent_metrics["retained_republish_total"], ) + if CRASH_RECOVERY_ENABLED: + try: + candidates = get_crash_recovery_candidates(CRASH_RECOVERY_GRACE_SECONDS) + if candidates: + logging.info("event=crash_recovery_scan candidates=%s", len(candidates)) + for candidate in candidates: + cuuid = candidate["uuid"] + reason = candidate["reason"] + try: + command_id, payload, topic, compat_topic = issue_crash_recovery_command( + client_uuid=cuuid, + reason=reason, + ) + result = client.publish(topic, json.dumps(payload), qos=1, retain=False) + result.wait_for_publish(timeout=5.0) + compat_result = client.publish(compat_topic, json.dumps(payload), qos=1, retain=False) + compat_result.wait_for_publish(timeout=5.0) + success = result.rc == mqtt.MQTT_ERR_SUCCESS + error = None if success else mqtt.error_string(result.rc) + finalize_crash_recovery_command(command_id, published=success, error=error) + if success: + logging.info( + "event=crash_recovery_command_issued client_uuid=%s reason=%s command_id=%s", + cuuid, reason, command_id, + ) + else: + logging.error( + "event=crash_recovery_publish_failed client_uuid=%s reason=%s command_id=%s error=%s", + cuuid, reason, command_id, error, + ) + except Exception as cmd_exc: + logging.error( + "event=crash_recovery_command_error client_uuid=%s reason=%s error=%s", + cuuid, reason, cmd_exc, + ) + except Exception as scan_exc: + logging.error("event=crash_recovery_scan_error error=%s", scan_exc) + + try: + expired_count = sweep_expired_commands() + if expired_count: + logging.info("event=command_expiry_sweep expired=%s", expired_count) + except Exception as sweep_exc: + logging.error("event=command_expiry_sweep_error error=%s", sweep_exc) + time.sleep(POLL_INTERVAL) diff --git a/server/alembic/versions/aa12bb34cc56_add_client_commands_table.py b/server/alembic/versions/aa12bb34cc56_add_client_commands_table.py new file mode 100644 index 0000000..957f28d --- /dev/null +++ b/server/alembic/versions/aa12bb34cc56_add_client_commands_table.py @@ -0,0 +1,63 @@ +"""add client commands table + +Revision ID: aa12bb34cc56 +Revises: f3c4d5e6a7b8 +Create Date: 2026-04-03 12:40:00.000000 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = 'aa12bb34cc56' +down_revision: Union[str, None] = 'f3c4d5e6a7b8' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.create_table( + 'client_commands', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('command_id', sa.String(length=36), nullable=False), + sa.Column('client_uuid', sa.String(length=36), nullable=False), + sa.Column('action', sa.String(length=32), nullable=False), + sa.Column('status', sa.String(length=40), nullable=False), + sa.Column('reason', sa.Text(), nullable=True), + sa.Column('requested_by', sa.Integer(), nullable=True), + sa.Column('issued_at', sa.TIMESTAMP(timezone=True), nullable=False), + sa.Column('expires_at', sa.TIMESTAMP(timezone=True), nullable=False), + sa.Column('published_at', sa.TIMESTAMP(timezone=True), nullable=True), + sa.Column('acked_at', sa.TIMESTAMP(timezone=True), nullable=True), + sa.Column('execution_started_at', sa.TIMESTAMP(timezone=True), nullable=True), + sa.Column('completed_at', sa.TIMESTAMP(timezone=True), nullable=True), + sa.Column('failed_at', sa.TIMESTAMP(timezone=True), nullable=True), + sa.Column('error_code', sa.String(length=64), nullable=True), + sa.Column('error_message', sa.Text(), nullable=True), + sa.Column('created_at', sa.TIMESTAMP(timezone=True), server_default=sa.func.current_timestamp(), nullable=False), + sa.Column('updated_at', sa.TIMESTAMP(timezone=True), server_default=sa.func.current_timestamp(), nullable=False), + sa.ForeignKeyConstraint(['client_uuid'], ['clients.uuid'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['requested_by'], ['users.id'], ondelete='SET NULL'), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('command_id') + ) + + op.create_index(op.f('ix_client_commands_action'), 'client_commands', ['action'], unique=False) + op.create_index(op.f('ix_client_commands_client_uuid'), 'client_commands', ['client_uuid'], unique=False) + op.create_index(op.f('ix_client_commands_command_id'), 'client_commands', ['command_id'], unique=False) + op.create_index(op.f('ix_client_commands_requested_by'), 'client_commands', ['requested_by'], unique=False) + op.create_index(op.f('ix_client_commands_status'), 'client_commands', ['status'], unique=False) + op.create_index('ix_client_commands_client_status_created', 'client_commands', ['client_uuid', 'status', 'created_at'], unique=False) + + +def downgrade() -> None: + op.drop_index('ix_client_commands_client_status_created', table_name='client_commands') + op.drop_index(op.f('ix_client_commands_status'), table_name='client_commands') + op.drop_index(op.f('ix_client_commands_requested_by'), table_name='client_commands') + op.drop_index(op.f('ix_client_commands_command_id'), table_name='client_commands') + op.drop_index(op.f('ix_client_commands_client_uuid'), table_name='client_commands') + op.drop_index(op.f('ix_client_commands_action'), table_name='client_commands') + op.drop_table('client_commands') diff --git a/server/alembic/versions/b1c2d3e4f5a6_add_service_failed_and_mqtt_broker_fields.py b/server/alembic/versions/b1c2d3e4f5a6_add_service_failed_and_mqtt_broker_fields.py new file mode 100644 index 0000000..ff60540 --- /dev/null +++ b/server/alembic/versions/b1c2d3e4f5a6_add_service_failed_and_mqtt_broker_fields.py @@ -0,0 +1,43 @@ +"""add service_failed and mqtt broker connection fields to clients + +Revision ID: b1c2d3e4f5a6 +Revises: aa12bb34cc56 +Create Date: 2026-04-05 10:00:00.000000 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = 'b1c2d3e4f5a6' +down_revision: Union[str, None] = 'aa12bb34cc56' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + bind = op.get_bind() + inspector = sa.inspect(bind) + existing = {c['name'] for c in inspector.get_columns('clients')} + + # Systemd service-failed tracking + if 'service_failed_at' not in existing: + op.add_column('clients', sa.Column('service_failed_at', sa.TIMESTAMP(timezone=True), nullable=True)) + if 'service_failed_unit' not in existing: + op.add_column('clients', sa.Column('service_failed_unit', sa.String(128), nullable=True)) + + # MQTT broker connection health + if 'mqtt_reconnect_count' not in existing: + op.add_column('clients', sa.Column('mqtt_reconnect_count', sa.Integer(), nullable=True)) + if 'mqtt_last_disconnect_at' not in existing: + op.add_column('clients', sa.Column('mqtt_last_disconnect_at', sa.TIMESTAMP(timezone=True), nullable=True)) + + +def downgrade() -> None: + op.drop_column('clients', 'mqtt_last_disconnect_at') + op.drop_column('clients', 'mqtt_reconnect_count') + op.drop_column('clients', 'service_failed_unit') + op.drop_column('clients', 'service_failed_at') diff --git a/server/init_defaults.py b/server/init_defaults.py index 7be8eb8..12e8105 100644 --- a/server/init_defaults.py +++ b/server/init_defaults.py @@ -1,4 +1,4 @@ -from sqlalchemy import create_engine, text +from sqlalchemy import create_engine, textmosquitto.conf import os from dotenv import load_dotenv import bcrypt diff --git a/server/routes/client_logs.py b/server/routes/client_logs.py index f208728..53438ba 100644 --- a/server/routes/client_logs.py +++ b/server/routes/client_logs.py @@ -421,6 +421,8 @@ def get_monitoring_overview(): }, "latest_log": _serialize_log_entry(latest_log), "latest_error": _serialize_log_entry(latest_error), + "mqtt_reconnect_count": client.mqtt_reconnect_count, + "mqtt_last_disconnect_at": client.mqtt_last_disconnect_at.isoformat() if client.mqtt_last_disconnect_at else None, }) summary_counts["total_clients"] += 1 diff --git a/server/routes/clients.py b/server/routes/clients.py index 653cead..a39e464 100644 --- a/server/routes/clients.py +++ b/server/routes/clients.py @@ -1,7 +1,8 @@ from server.database import Session -from models.models import Client, ClientGroup -from flask import Blueprint, request, jsonify +from models.models import Client, ClientGroup, ClientCommand, ProcessStatus +from flask import Blueprint, request, jsonify, session as flask_session from server.permissions import admin_or_higher +from server.routes.groups import get_grace_period, is_client_alive from server.mqtt_helper import publish_client_group, delete_client_group_message, publish_multiple_client_groups import sys import os @@ -9,13 +10,196 @@ import glob import base64 import hashlib import json -from datetime import datetime, timezone +import uuid as uuid_lib +from datetime import datetime, timezone, timedelta sys.path.append('/workspace') clients_bp = Blueprint("clients", __name__, url_prefix="/api/clients") VALID_SCREENSHOT_TYPES = {"periodic", "event_start", "event_stop"} +COMMAND_SCHEMA_VERSION = "1.0" +COMMAND_TOPIC_TEMPLATE = "infoscreen/{uuid}/commands" +COMMAND_TOPIC_COMPAT_TEMPLATE = "infoscreen/{uuid}/command" +LEGACY_RESTART_TOPIC_TEMPLATE = "clients/{uuid}/restart" +COMMAND_EXPIRY_SECONDS = 240 +REBOOT_LOCKOUT_WINDOW_MINUTES = 15 +REBOOT_LOCKOUT_THRESHOLD = 3 +API_ACTION_TO_COMMAND_ACTION = { + "restart": "reboot_host", + "shutdown": "shutdown_host", + "restart_app": "restart_app", +} +ALLOWED_COMMAND_ACTIONS = set(API_ACTION_TO_COMMAND_ACTION.keys()) + + +def _iso_utc_z(ts: datetime) -> str: + return ts.astimezone(timezone.utc).isoformat().replace("+00:00", "Z") + + +def _command_to_dict(command: ClientCommand) -> dict: + return { + "commandId": command.command_id, + "clientUuid": command.client_uuid, + "action": command.action, + "status": command.status, + "reason": command.reason, + "requestedBy": command.requested_by, + "issuedAt": command.issued_at.isoformat() if command.issued_at else None, + "expiresAt": command.expires_at.isoformat() if command.expires_at else None, + "publishedAt": command.published_at.isoformat() if command.published_at else None, + "ackedAt": command.acked_at.isoformat() if command.acked_at else None, + "executionStartedAt": command.execution_started_at.isoformat() if command.execution_started_at else None, + "completedAt": command.completed_at.isoformat() if command.completed_at else None, + "failedAt": command.failed_at.isoformat() if command.failed_at else None, + "errorCode": command.error_code, + "errorMessage": command.error_message, + "createdAt": command.created_at.isoformat() if command.created_at else None, + "updatedAt": command.updated_at.isoformat() if command.updated_at else None, + } + + +def _publish_client_command(client_uuid: str, action: str, payload: dict) -> None: + import paho.mqtt.client as mqtt + + broker_host = os.getenv("MQTT_BROKER_HOST", "mqtt") + broker_port = int(os.getenv("MQTT_BROKER_PORT", 1883)) + username = os.getenv("MQTT_USER") + password = os.getenv("MQTT_PASSWORD") + + mqtt_client = mqtt.Client() + if username and password: + mqtt_client.username_pw_set(username, password) + + mqtt_client.connect(broker_host, broker_port) + + # Primary topic for contract-based command handling. + command_topic = COMMAND_TOPIC_TEMPLATE.format(uuid=client_uuid) + result = mqtt_client.publish(command_topic, json.dumps(payload), qos=1, retain=False) + result.wait_for_publish(timeout=5.0) + + # Transitional compatibility for clients that still consume singular topic naming. + compat_topic = COMMAND_TOPIC_COMPAT_TEMPLATE.format(uuid=client_uuid) + compat_result = mqtt_client.publish(compat_topic, json.dumps(payload), qos=1, retain=False) + compat_result.wait_for_publish(timeout=5.0) + + # Transitional compatibility for existing restart-only clients. + if action == "restart": + legacy_topic = LEGACY_RESTART_TOPIC_TEMPLATE.format(uuid=client_uuid) + legacy_payload = {"action": "restart"} + legacy_result = mqtt_client.publish(legacy_topic, json.dumps(legacy_payload), qos=1, retain=False) + legacy_result.wait_for_publish(timeout=5.0) + + mqtt_client.disconnect() + + +def _issue_client_command(client_uuid: str, action: str): + if action not in ALLOWED_COMMAND_ACTIONS: + return jsonify({"error": f"Unsupported action '{action}'"}), 400 + + command_action = API_ACTION_TO_COMMAND_ACTION[action] + + data = request.get_json(silent=True) or {} + reason = str(data.get("reason", "")).strip() or None + requested_by = flask_session.get("user_id") + + now_utc = datetime.now(timezone.utc) + expires_at = now_utc + timedelta(seconds=COMMAND_EXPIRY_SECONDS) + command_id = str(uuid_lib.uuid4()) + + db = Session() + try: + client = db.query(Client).filter_by(uuid=client_uuid).first() + if not client: + return jsonify({"error": "Client nicht gefunden"}), 404 + + # Safety lockout: avoid rapid repeated reboot loops per client. + if command_action in ("reboot_host", "restart_app"): + window_start = now_utc - timedelta(minutes=REBOOT_LOCKOUT_WINDOW_MINUTES) + recent_reboots = ( + db.query(ClientCommand) + .filter(ClientCommand.client_uuid == client_uuid) + .filter(ClientCommand.action.in_(["reboot_host", "restart_app"])) + .filter(ClientCommand.created_at >= window_start) + .count() + ) + if recent_reboots >= REBOOT_LOCKOUT_THRESHOLD: + blocked = ClientCommand( + command_id=command_id, + client_uuid=client_uuid, + action=command_action, + status="blocked_safety", + reason=reason, + requested_by=requested_by, + issued_at=now_utc, + expires_at=expires_at, + failed_at=now_utc, + error_code="lockout_threshold", + error_message="Reboot lockout active for this client", + ) + db.add(blocked) + db.commit() + return jsonify({ + "success": False, + "message": "Neustart voruebergehend blockiert (Sicherheits-Lockout)", + "command": _command_to_dict(blocked), + }), 429 + + command = ClientCommand( + command_id=command_id, + client_uuid=client_uuid, + action=command_action, + status="queued", + reason=reason, + requested_by=requested_by, + issued_at=now_utc, + expires_at=expires_at, + ) + db.add(command) + db.commit() + + command.status = "publish_in_progress" + db.commit() + + payload = { + "schema_version": COMMAND_SCHEMA_VERSION, + "command_id": command.command_id, + "client_uuid": command.client_uuid, + "action": command.action, + "issued_at": _iso_utc_z(command.issued_at), + "expires_at": _iso_utc_z(command.expires_at), + "requested_by": command.requested_by, + "reason": command.reason, + } + + try: + _publish_client_command(client_uuid=client_uuid, action=action, payload=payload) + # ACK can arrive very quickly (including terminal failure) while publish is in-flight. + # Refresh to avoid regressing a newer listener-updated state back to "published". + db.refresh(command) + command.published_at = command.published_at or datetime.now(timezone.utc) + if command.status in {"queued", "publish_in_progress"}: + command.status = "published" + db.commit() + return jsonify({ + "success": True, + "message": f"Command published for client {client_uuid}", + "command": _command_to_dict(command), + }), 202 + except Exception as publish_error: + command.status = "failed" + command.failed_at = datetime.now(timezone.utc) + command.error_code = "mqtt_publish_failed" + command.error_message = str(publish_error) + db.commit() + return jsonify({ + "success": False, + "error": f"Failed to publish command: {publish_error}", + "command": _command_to_dict(command), + }), 500 + finally: + db.close() + def _normalize_screenshot_type(raw_type): if raw_type is None: @@ -280,45 +464,148 @@ def get_clients_with_alive_status(): "ip": c.ip, "last_alive": c.last_alive.isoformat() if c.last_alive else None, "is_active": c.is_active, - "is_alive": bool(c.last_alive and c.is_active), + "is_alive": is_client_alive(c.last_alive, c.is_active), }) session.close() return jsonify(result) +@clients_bp.route("/crashed", methods=["GET"]) +@admin_or_higher +def get_crashed_clients(): + """Returns clients that are crashed (process_status=crashed) or heartbeat-stale.""" + session = Session() + try: + from datetime import timedelta + grace = get_grace_period() + from datetime import datetime, timezone + stale_cutoff = datetime.now(timezone.utc) - timedelta(seconds=grace) + clients = ( + session.query(Client) + .filter(Client.is_active == True) + .all() + ) + result = [] + for c in clients: + alive = is_client_alive(c.last_alive, c.is_active) + crashed = c.process_status == ProcessStatus.crashed + if not alive or crashed: + result.append({ + "uuid": c.uuid, + "description": c.description, + "hostname": c.hostname, + "ip": c.ip, + "group_id": c.group_id, + "is_alive": alive, + "process_status": c.process_status.value if c.process_status else None, + "screen_health_status": c.screen_health_status.value if c.screen_health_status else None, + "last_alive": c.last_alive.isoformat() if c.last_alive else None, + "crash_reason": "process_crashed" if crashed else "heartbeat_stale", + }) + return jsonify({ + "crashed_count": len(result), + "grace_period_seconds": grace, + "clients": result, + }) + finally: + session.close() + + +@clients_bp.route("/service_failed", methods=["GET"]) +@admin_or_higher +def get_service_failed_clients(): + """Returns clients that have a service_failed_at set (systemd gave up restarting).""" + session = Session() + try: + clients = ( + session.query(Client) + .filter(Client.service_failed_at.isnot(None)) + .order_by(Client.service_failed_at.desc()) + .all() + ) + result = [ + { + "uuid": c.uuid, + "description": c.description, + "hostname": c.hostname, + "ip": c.ip, + "group_id": c.group_id, + "service_failed_at": c.service_failed_at.isoformat() if c.service_failed_at else None, + "service_failed_unit": c.service_failed_unit, + "is_alive": is_client_alive(c.last_alive, c.is_active), + "last_alive": c.last_alive.isoformat() if c.last_alive else None, + } + for c in clients + ] + return jsonify({"service_failed_count": len(result), "clients": result}) + finally: + session.close() + + +@clients_bp.route("//clear_service_failed", methods=["POST"]) +@admin_or_higher +def clear_service_failed(client_uuid): + """Clears the service_failed flag for a client and deletes the retained MQTT message.""" + import paho.mqtt.client as mqtt_lib + + session = Session() + try: + c = session.query(Client).filter_by(uuid=client_uuid).first() + if not c: + return jsonify({"error": "Client nicht gefunden"}), 404 + if c.service_failed_at is None: + return jsonify({"success": True, "message": "Kein service_failed Flag gesetzt."}), 200 + + c.service_failed_at = None + c.service_failed_unit = None + session.commit() + finally: + session.close() + + # Clear the retained MQTT message (publish empty payload, retained=True) + try: + broker_host = os.getenv("MQTT_BROKER_HOST", "mqtt") + broker_port = int(os.getenv("MQTT_BROKER_PORT", 1883)) + username = os.getenv("MQTT_USER") + password = os.getenv("MQTT_PASSWORD") + mc = mqtt_lib.Client() + if username and password: + mc.username_pw_set(username, password) + mc.connect(broker_host, broker_port) + topic = f"infoscreen/{client_uuid}/service_failed" + mc.publish(topic, payload=None, qos=1, retain=True) + mc.disconnect() + except Exception as e: + # Log but don't fail — DB is already cleared + import logging + logging.warning(f"Could not clear retained service_failed MQTT message for {client_uuid}: {e}") + + return jsonify({"success": True, "message": "service_failed Flag gelöscht."}) + + @clients_bp.route("//restart", methods=["POST"]) @admin_or_higher def restart_client(uuid): - """ - Route to restart a specific client by UUID. - Sends an MQTT message to the broker to trigger the restart. - """ - import paho.mqtt.client as mqtt - import json + return _issue_client_command(client_uuid=uuid, action="restart") - # MQTT broker configuration - MQTT_BROKER = "mqtt" - MQTT_PORT = 1883 - MQTT_TOPIC = f"clients/{uuid}/restart" - # Connect to the database to check if the client exists - session = Session() - client = session.query(Client).filter_by(uuid=uuid).first() - if not client: - session.close() - return jsonify({"error": "Client nicht gefunden"}), 404 - session.close() +@clients_bp.route("//shutdown", methods=["POST"]) +@admin_or_higher +def shutdown_client(uuid): + return _issue_client_command(client_uuid=uuid, action="shutdown") - # Send MQTT message + +@clients_bp.route("/commands/", methods=["GET"]) +@admin_or_higher +def get_client_command_status(command_id): + db = Session() try: - mqtt_client = mqtt.Client() - mqtt_client.connect(MQTT_BROKER, MQTT_PORT) - payload = {"action": "restart"} - mqtt_client.publish(MQTT_TOPIC, json.dumps(payload)) - mqtt_client.disconnect() - return jsonify({"success": True, "message": f"Restart signal sent to client {uuid}"}), 200 - except Exception as e: - return jsonify({"error": f"Failed to send MQTT message: {str(e)}"}), 500 + command = db.query(ClientCommand).filter_by(command_id=command_id).first() + if not command: + return jsonify({"error": "Command nicht gefunden"}), 404 + return jsonify(_command_to_dict(command)), 200 + finally: + db.close() @clients_bp.route("//screenshot", methods=["POST"])