From 1b5b413ced6ae5eefb4e2b8aee14cd4e78de0a8a Mon Sep 17 00:00:00 2001 From: okxlin <61420215+okxlin@users.noreply.github.com> Date: Tue, 28 Jul 2026 04:08:52 +0800 Subject: [PATCH] Add LubeLogger app --- apps/lubelogger/1.7.0/.env.sample | 6 + apps/lubelogger/1.7.0/data.yml | 86 ++++++++++++++ apps/lubelogger/1.7.0/data/.gitkeep | 1 + apps/lubelogger/1.7.0/docker-compose.yml | 46 ++++++++ apps/lubelogger/1.7.0/scripts/init.sh | 123 +++++++++++++++++++++ apps/lubelogger/1.7.0/scripts/uninstall.sh | 2 + apps/lubelogger/1.7.0/scripts/upgrade.sh | 4 + apps/lubelogger/README.md | 71 ++++++++++++ apps/lubelogger/data.yml | 30 +++++ apps/lubelogger/logo-LICENSE.txt | 25 +++++ apps/lubelogger/logo.png | Bin 0 -> 12117 bytes 11 files changed, 394 insertions(+) create mode 100644 apps/lubelogger/1.7.0/.env.sample create mode 100644 apps/lubelogger/1.7.0/data.yml create mode 100644 apps/lubelogger/1.7.0/data/.gitkeep create mode 100644 apps/lubelogger/1.7.0/docker-compose.yml create mode 100755 apps/lubelogger/1.7.0/scripts/init.sh create mode 100755 apps/lubelogger/1.7.0/scripts/uninstall.sh create mode 100755 apps/lubelogger/1.7.0/scripts/upgrade.sh create mode 100644 apps/lubelogger/README.md create mode 100644 apps/lubelogger/data.yml create mode 100644 apps/lubelogger/logo-LICENSE.txt create mode 100644 apps/lubelogger/logo.png diff --git a/apps/lubelogger/1.7.0/.env.sample b/apps/lubelogger/1.7.0/.env.sample new file mode 100644 index 000000000..30ea2f982 --- /dev/null +++ b/apps/lubelogger/1.7.0/.env.sample @@ -0,0 +1,6 @@ +PANEL_APP_PORT_HTTP=8080 +LUBELOGGER_ROOT_USERNAME=admin +LUBELOGGER_ROOT_PASSWORD=LubeLogger_Admin_2026! +TIME_ZONE=Etc/UTC +APP_DATA_DIR=./data +CONTAINER_NAME= diff --git a/apps/lubelogger/1.7.0/data.yml b/apps/lubelogger/1.7.0/data.yml new file mode 100644 index 000000000..53a507e42 --- /dev/null +++ b/apps/lubelogger/1.7.0/data.yml @@ -0,0 +1,86 @@ +additionalProperties: + formFields: + - default: 8080 + edit: true + envKey: PANEL_APP_PORT_HTTP + labelEn: Port + labelZh: 端口 + label: + en: Port + zh: 端口 + zh-Hant: 埠 + ja: ポート + ko: 포트 + ru: Порт + ms: Port + pt-br: Porta + required: true + rule: paramPort + type: number + - default: admin + edit: true + envKey: LUBELOGGER_ROOT_USERNAME + labelEn: Root Username + labelZh: 根管理员用户名 + label: + en: Root Username + zh: 根管理员用户名 + zh-Hant: 根管理員使用者名稱 + ja: ルート管理者ユーザー名 + ko: 루트 관리자 사용자 이름 + ru: Имя корневого администратора + ms: Nama Pengguna Pentadbir Root + pt-br: Nome do Administrador Raiz + required: true + rule: paramCommon + type: text + - default: LubeLogger_Admin_2026! + edit: true + envKey: LUBELOGGER_ROOT_PASSWORD + labelEn: Root Password + labelZh: 根管理员密码 + label: + en: Root Password + zh: 根管理员密码 + zh-Hant: 根管理員密碼 + ja: ルート管理者パスワード + ko: 루트 관리자 비밀번호 + ru: Пароль корневого администратора + ms: Kata Laluan Pentadbir Root + pt-br: Senha do Administrador Raiz + random: true + required: true + rule: paramComplexity + type: password + - default: Etc/UTC + edit: true + envKey: TIME_ZONE + labelEn: Time Zone + labelZh: 时区 + label: + en: Time Zone + zh: 时区 + zh-Hant: 時區 + ja: タイムゾーン + ko: 시간대 + ru: Часовой пояс + ms: Zon Masa + pt-br: Fuso Horario + required: true + type: text + - default: ./data + edit: true + envKey: APP_DATA_DIR + labelEn: Data Directory + labelZh: 数据目录 + label: + en: Data Directory + zh: 数据目录 + zh-Hant: 資料目錄 + ja: データディレクトリ + ko: 데이터 디렉터리 + ru: Каталог данных + ms: Direktori Data + pt-br: Diretorio de Dados + required: true + type: text diff --git a/apps/lubelogger/1.7.0/data/.gitkeep b/apps/lubelogger/1.7.0/data/.gitkeep new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/apps/lubelogger/1.7.0/data/.gitkeep @@ -0,0 +1 @@ + diff --git a/apps/lubelogger/1.7.0/docker-compose.yml b/apps/lubelogger/1.7.0/docker-compose.yml new file mode 100644 index 000000000..edcdaa6f1 --- /dev/null +++ b/apps/lubelogger/1.7.0/docker-compose.yml @@ -0,0 +1,46 @@ +services: + lubelogger: + image: "ghcr.io/hargata/lubelogger:v1.7.0@sha256:01bdb486af71e641c3ae41499e0412a21f2e04fa31b25c5c6531b42c112938e5" + container_name: ${CONTAINER_NAME} + restart: unless-stopped + init: true + user: "1000:1000" + networks: + - 1panel-network + ports: + - "${PANEL_APP_PORT_HTTP}:8080" + env_file: + - path: "${APP_DATA_DIR}/.lubelogger-auth.env" + required: false + environment: + - HOME=/App/data + - TZ=${TIME_ZONE} + - ASPNETCORE_HTTP_PORTS=8080 + - Kestrel__EndpointDefaults__Protocols=Http1 + - DOTNET_SYSTEM_NET_HTTP_SOCKETSHTTPHANDLER_HTTP2SUPPORT=false + - LUBELOGGER_LOCALE_OVERRIDE=en_US + - LUBELOGGER_LOCALE_DT_OVERRIDE=en_US + read_only: true + tmpfs: + - /tmp:size=64m,mode=1777 + security_opt: + - no-new-privileges:true + cap_drop: + - ALL + healthcheck: + test: + - CMD-SHELL + - >- + bash -lc 'exec 3<>/dev/tcp/127.0.0.1/8080; exec 3>&-; exec 3<&-' + interval: 30s + timeout: 5s + start_period: 15s + retries: 3 + volumes: + - "${APP_DATA_DIR}:/App/data" + labels: + createdBy: "Apps" + +networks: + 1panel-network: + external: true diff --git a/apps/lubelogger/1.7.0/scripts/init.sh b/apps/lubelogger/1.7.0/scripts/init.sh new file mode 100755 index 000000000..144ab4632 --- /dev/null +++ b/apps/lubelogger/1.7.0/scripts/init.sh @@ -0,0 +1,123 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)" +ENV_FILE="${ENV_FILE:-$ROOT_DIR/.env}" + +read_env_value() { + local key="$1" + [[ -f "$ENV_FILE" ]] || return 0 + local value + value="$(sed -n "s/^${key}=//p" "$ENV_FILE" | tail -n 1)" + case "$value" in + \"*\") value="${value#\"}"; value="${value%\"}" ;; + \'*\') value="${value#\'}"; value="${value%\'}" ;; + esac + printf '%s\n' "$value" +} + +configured_value() { + local key="$1" + local default_value="$2" + local value="${!key:-}" + if [[ -z "$value" ]]; then + value="$(read_env_value "$key")" + fi + printf '%s\n' "${value:-$default_value}" +} + +reject_control_characters() { + local name="$1" + local value="$2" + if [[ "$value" == *$'\n'* || "$value" == *$'\r'* ]]; then + printf '%s must not contain line breaks\n' "$name" >&2 + exit 1 + fi +} + +prepare_lubelogger() { + local raw path username password username_hash password_hash auth_file temp_file + + raw="$(configured_value APP_DATA_DIR ./data)" + username="$(configured_value LUBELOGGER_ROOT_USERNAME admin)" + password="$(configured_value LUBELOGGER_ROOT_PASSWORD '')" + + [[ -n "$raw" ]] || { + printf '%s\n' 'APP_DATA_DIR must not be empty' >&2 + exit 1 + } + if [[ "$raw" = /* ]]; then + printf '%s\n' 'APP_DATA_DIR must be relative to the application version directory' >&2 + exit 1 + fi + + path="$(realpath -m -- "$ROOT_DIR/${raw#./}")" + case "$path" in + "$ROOT_DIR"/*) ;; + *) + printf '%s\n' 'APP_DATA_DIR must remain inside the application version directory' >&2 + exit 1 + ;; + esac + + install -d -m 0750 "$path" + path="$(realpath -e -- "$path")" + case "$path" in + "$ROOT_DIR"/*) ;; + *) + printf '%s\n' 'APP_DATA_DIR resolves outside the application version directory' >&2 + exit 1 + ;; + esac + + [[ -n "$username" && ${#username} -le 128 ]] || { + printf '%s\n' 'LUBELOGGER_ROOT_USERNAME must contain 1 to 128 characters' >&2 + exit 1 + } + [[ ${#password} -ge 12 && ${#password} -le 256 ]] || { + printf '%s\n' 'LUBELOGGER_ROOT_PASSWORD must contain 12 to 256 characters' >&2 + exit 1 + } + reject_control_characters LUBELOGGER_ROOT_USERNAME "$username" + reject_control_characters LUBELOGGER_ROOT_PASSWORD "$password" + + username_hash="$(printf '%s' "$username" | sha256sum | cut -d ' ' -f 1)" + password_hash="$(printf '%s' "$password" | sha256sum | cut -d ' ' -f 1)" + [[ "$username_hash" =~ ^[0-9a-f]{64}$ && "$password_hash" =~ ^[0-9a-f]{64}$ ]] || { + printf '%s\n' 'Failed to generate LubeLogger credential hashes' >&2 + exit 1 + } + + auth_file="$path/.lubelogger-auth.env" + if [[ -L "$auth_file" ]]; then + printf '%s\n' 'LubeLogger authentication file must not be a symbolic link' >&2 + exit 1 + fi + if [[ -e "$auth_file" && ! -f "$auth_file" ]]; then + printf '%s\n' 'LubeLogger authentication path must be a regular file' >&2 + exit 1 + fi + + umask 077 + temp_file="$(mktemp "$path/.lubelogger-auth.env.tmp.XXXXXX")" + trap 'rm -f -- "${temp_file:-}"' EXIT + printf '%s\n' \ + 'EnableAuth=true' \ + "UserNameHash=$username_hash" \ + "UserPasswordHash=$password_hash" \ + 'DisableRegistration=true' \ + 'LUBELOGGER_OPEN_REGISTRATION=false' >"$temp_file" + chmod 0600 "$temp_file" + chown 1000:1000 "$temp_file" + mv -f -- "$temp_file" "$auth_file" + trap - EXIT + + chown 1000:1000 "$path" + chmod 0750 "$path" + [[ "$(stat -c '%a' -- "$auth_file")" = 600 ]] || { + printf '%s\n' 'LubeLogger authentication file permissions must be 0600' >&2 + exit 1 + } +} + +prepare_lubelogger diff --git a/apps/lubelogger/1.7.0/scripts/uninstall.sh b/apps/lubelogger/1.7.0/scripts/uninstall.sh new file mode 100755 index 000000000..ab8a8d674 --- /dev/null +++ b/apps/lubelogger/1.7.0/scripts/uninstall.sh @@ -0,0 +1,2 @@ +#!/bin/bash +docker-compose down --volumes diff --git a/apps/lubelogger/1.7.0/scripts/upgrade.sh b/apps/lubelogger/1.7.0/scripts/upgrade.sh new file mode 100755 index 000000000..692e341e2 --- /dev/null +++ b/apps/lubelogger/1.7.0/scripts/upgrade.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -euo pipefail + +"$(dirname "$0")/init.sh" diff --git a/apps/lubelogger/README.md b/apps/lubelogger/README.md new file mode 100644 index 000000000..874bce09b --- /dev/null +++ b/apps/lubelogger/README.md @@ -0,0 +1,71 @@ +# LubeLogger + +## 产品介绍 + +LubeLogger 是一个自托管的车辆管理工具,用于记录车辆保养、维修、加油、里程、提醒、升级、税费、碰撞和相关文档。 + +## 主要功能 + +- 管理多辆车辆及其里程、燃油和费用记录 +- 维护保养计划、提醒和历史记录 +- 保存车辆图片、票据和其他文档 +- 支持 CSV 导入导出、报表和可选的通知集成 + +## 访问说明 + +安装后通过 `http://<服务器 IP>:<端口>` 访问,并使用安装时设置的根管理员用户名和密码登录。安装脚本只把凭据的 SHA-256 哈希传给应用,默认启用认证并关闭开放注册和邀请注册页面。 + +上游根用户认证使用普通 SHA-256,而不是专用密码哈希算法;必须保留 1Panel 自动生成的高强度随机密码,不要使用短密码或复用密码。LubeLogger 1.7.0 的登录接口没有请求限速,认证 Cookie 也没有显式设置 `Secure`、`HttpOnly` 或 `SameSite`。明文 HTTP 端口只适合可信局域网;公网访问必须使用可信 HTTPS 反向代理、阻止公网直接访问应用端口,并在代理层增加登录限速。 + +## 数据持久化 + +`APP_DATA_DIR` 挂载到 `/App/data`,保存 LiteDB 数据库、图片、文档、配置和会话 DataProtection 密钥。该路径必须位于应用版本目录内,默认值为 `./data`;初始化脚本拒绝绝对路径、目录逃逸和认证文件符号链接,并把目录交给 UID/GID `1000:1000`。卸载脚本不会删除绑定目录中的用户数据,升级或迁移前仍应单独备份。 + +## 安全与漏洞说明 + +- 容器以 UID/GID `1000:1000` 运行,根文件系统只读,丢弃全部 Linux capabilities,并启用 `no-new-privileges`;CPU、内存和进程上限由 1Panel 的应用资源设置统一管理。应用只监听内部明文 HTTP/1;TLS 应由 1Panel 或其他可信反向代理终止。可选 WebHook、OIDC、SMTP 和通知集成默认未配置,包内同时禁用 .NET HTTP 客户端的 HTTP/2 支持。 +- 对固定镜像执行的 2026-07-28 Trivy 扫描发现 `0` 个 Critical 和 `5` 个 High,均来自镜像中的 .NET 10.0.9,修复版本为 10.0.10。当前上游 v1.7.0 镜像尚未包含修复,应在上游发布使用 .NET 10.0.10 或更高版本的镜像后尽快更新。 +- `CVE-2026-47302` 是 XML 加密解析导致的资源耗尽。LubeLogger 源码和默认依赖路径没有使用 `System.Security.Cryptography.Xml`、`System.Xml` 或 `EncryptedXml`,默认部署未发现可达入口。 +- `CVE-2026-50524` 是畸形 TLS 握手导致的拒绝服务,`CVE-2026-50528` 是 `SslStream` 授权绕过。此包内 Kestrel 只提供 HTTP/1 明文服务,不处理入站 TLS;默认也未配置 OIDC、Webhook、SMTP 或通知等外部 TLS 集成。用户启用这些集成后应把远端视为额外风险边界。 +- `CVE-2026-50651` 是 .NET HTTP/2 客户端处理 SETTINGS/PING ACK flood 时可能内存耗尽。LubeLogger 有可选的出站 HTTP 客户端路径,但此包通过 `DOTNET_SYSTEM_NET_HTTP_SOCKETSHTTPHANDLER_HTTP2SUPPORT=false` 禁用 HTTP/2,并默认不配置相关集成。 +- `CVE-2026-57108` 是解析特制 X.509 证书时的类型混淆拒绝服务。默认内部 HTTP 服务不解析入站证书;打开 Sponsors 页面或启用外部 TLS 集成时会产生出站证书解析路径,因此该项不能视为完全不可达。只连接可信端点,并在修复镜像可用后立即升级。 + +## Introduction + +LubeLogger is a self-hosted vehicle management application for tracking maintenance, repairs, fuel, mileage, reminders, upgrades, taxes, collisions, and related documents. + +## Features + +- Manage multiple vehicles and their mileage, fuel, and cost records +- Track maintenance plans, reminders, and service history +- Store vehicle images, receipts, and other documents +- Import and export CSV data, generate reports, and configure optional notifications + +## Access And Authentication + +Access the service at `http://:` and sign in with the root username and password selected during installation. The initialization script passes only SHA-256 credential hashes to the application. Authentication is enabled by default, while open registration and the invitation registration page are disabled. + +Upstream root authentication uses plain SHA-256 rather than a password-specific KDF. Keep the high-entropy password generated by 1Panel; do not use a short or reused password. LubeLogger 1.7.0 does not rate-limit login requests, and its authentication cookie does not explicitly set `Secure`, `HttpOnly`, or `SameSite`. Direct plain-HTTP access is suitable only on a trusted LAN. For public access, use a trusted HTTPS reverse proxy, block public access to the application port, and add login rate limiting at the proxy. + +## Data Persistence + +`APP_DATA_DIR` is mounted at `/App/data` and stores the LiteDB database, images, documents, configuration, and DataProtection session keys. It must remain inside the application version directory and defaults to `./data`. The initializer rejects absolute paths, directory escapes, and a symlinked authentication file, then assigns the directory to UID/GID `1000:1000`. Uninstall does not remove bind-mounted user data; back it up before upgrades or migration. + +## Security And Vulnerability Notes + +- The container runs as UID/GID `1000:1000`, uses a read-only root filesystem, drops all Linux capabilities, and enables `no-new-privileges`. CPU, memory, and process limits are managed through 1Panel's application resource settings. The application serves internal plain HTTP/1 only; terminate TLS at 1Panel or another trusted reverse proxy. Optional webhook, OIDC, SMTP, and notification integrations are not configured, and .NET HTTP client HTTP/2 support is disabled. +- A 2026-07-28 Trivy scan of the pinned image found 0 Critical and 5 High findings, all in .NET 10.0.9. The fixes are in .NET 10.0.10. Upstream v1.7.0 has not yet published an image with those fixes; update promptly when it does. +- `CVE-2026-47302` is an XML-encryption parsing resource-exhaustion issue. LubeLogger source and default dependencies do not use `System.Security.Cryptography.Xml`, `System.Xml`, or `EncryptedXml`, so no default reachable entry point was found. +- `CVE-2026-50524` is a malformed TLS-handshake denial of service, and `CVE-2026-50528` is an `SslStream` authorization bypass. Kestrel serves only internal plain HTTP/1 in this package and does not process inbound TLS. OIDC, webhook, SMTP, and notification integrations are also unconfigured by default. Enabling them adds a separate outbound TLS risk boundary. +- `CVE-2026-50651` is an HTTP/2 client SETTINGS/PING ACK flood that may cause an out-of-memory condition. LubeLogger has optional outbound HTTP client paths, but this package sets `DOTNET_SYSTEM_NET_HTTP_SOCKETSHTTPHANDLER_HTTP2SUPPORT=false` and leaves those integrations unconfigured. +- `CVE-2026-57108` is a type-confusion denial of service while parsing a crafted X.509 certificate. The internal HTTP service does not parse inbound certificates. Opening the Sponsors view or enabling outbound TLS integrations does create certificate-parsing paths, so this finding is not considered completely unreachable. Connect only to trusted endpoints and upgrade as soon as a fixed image is available. + +## References + +- Project: +- Release: +- Container source: +- Deployment documentation: +- Configuration documentation: +- License: (MIT) +- .NET advisories: , , , , diff --git a/apps/lubelogger/data.yml b/apps/lubelogger/data.yml new file mode 100644 index 000000000..60a0f08dc --- /dev/null +++ b/apps/lubelogger/data.yml @@ -0,0 +1,30 @@ +name: LubeLogger +tags: + - Tool +title: 车辆维护、保养与油耗记录工具 +description: 车辆维护、保养与油耗记录工具 +additionalProperties: + key: lubelogger + name: LubeLogger + tags: + - Tool + shortDescZh: 车辆维护、保养与油耗记录工具 + shortDescEn: Vehicle maintenance, service, and fuel mileage tracking + description: + en: Vehicle maintenance, service, and fuel mileage tracking + zh: 车辆维护、保养与油耗记录工具 + zh-Hant: 車輛維護、保養與油耗記錄工具 + ja: 車両の整備、サービス、燃費を記録するツール + ko: 차량 정비, 서비스 및 연비 기록 도구 + ru: Учет обслуживания, ремонта и расхода топлива автомобилей + ms: Penjejakan penyelenggaraan, servis dan penggunaan bahan api kenderaan + pt-br: Controle de manutencao, servicos e consumo de veiculos + type: website + crossVersionUpdate: true + limit: 0 + website: https://lubelogger.com/ + github: https://github.com/hargata/lubelog + document: https://docs.lubelogger.com/ + architectures: + - amd64 + - arm64 diff --git a/apps/lubelogger/logo-LICENSE.txt b/apps/lubelogger/logo-LICENSE.txt new file mode 100644 index 000000000..0cb339f3b --- /dev/null +++ b/apps/lubelogger/logo-LICENSE.txt @@ -0,0 +1,25 @@ +LubeLogger logo + +Source: https://github.com/hargata/lubelog/blob/v1.7.0/wwwroot/defaults/lubelogger_icon_192.png +License: MIT +Copyright (c) 2024 Hargata Softworks + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/apps/lubelogger/logo.png b/apps/lubelogger/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..97494140bdb9f37f210b44b47756baf837c65b0b GIT binary patch literal 12117 zcmV-bFRIXqP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>DF6v1{K~#8N?VSgF z6lMPZv%O`rX`~m@A%p}-LPA3CRs5+GD=Jp(o~Nhc>8W>~cmB@*{mw3e3LfVvr>8_I z9*PhkR70-`q}R>%zB~W#X6H89O|tXM%(I2)`^D>SpNWv2XFl_7PvcCVK0RzWA`{!U zJl(+KkxhT&aD=URHBF@t)b9%!xa9v2J4$kl0aFja<%X5!8py`aiD4_C^1MJl&cOf1 zP+)iph|3`+<{HUHpeW8Z@_7W=_}MT#1vEO<93gd(jX)t26LwY254~6{7V_$FXLt%I zSl(n&g~atisZ_c?410#BfXYS%NkEmPfV#W8T_1)$!!V7HkF;)jqKRw_&Aor@UcXgG zHW0XVfo%P=v1DUt3T|pX*I?uEc$JlvT*%FaJHzk}%krqhXM!NU-A^yd-nQT9a8Sp{ z3wyno-ELQ_)vhbUn_(y*lmO`HIZJD|oot}dsdChurVfo4RY+YQekNWlkOB%siYpd4 ziZV=OV`!@OF-h{j-A66W%0iJMoC69?Jq?Q%-3w6s(vlesQpL?&{q zn5rtxHLm?QzFW&DuLg-gSRhc(YpYu`6RgXps6YNy)3V7b0pDLmnsS8vtF_D4Up+uf zObq$SCv|PY4i&Zf-MJX{WFGqM`by}-#v7-pvlA`QtI)UP&w_;C1>{msN-TMwa*5-f z`8^*#+B9dpmczmB)CiOa+ADkx4-XfKL}Y^kPR%(o#PPxWa)~Sb!sx}IuV#CN9$c+eJkUH5>i}eKk|jn?9U6NDK#1nY#Q_8X#VB}_ z3Y?0BM9gZnx-PPzB**afnwF<-=!%c>?#ZBQ$d3XVNBR_w=;`TkU13IUlKD?}w7#;cJu7j@1Q2@o zrJFCyML1tYbo?bCO7*xY$~3)kU(2&AI+CKEx+{Vn6OsFTW^6yB@1HwQ&Sei0D5dR%ZgVbjOzKX|0+!NpyX zGJ541H__oXaA9O)XuSMabaXTaemxy(&QS}fAXAv0;0(7xKwyVl7ZFiN?GG&K{@~%J z8HIZEua=X_4~m9(BS(%TKVpKedfY(ZIV#1nc%nwYBhI&oY<3#Y75>U~m6~yx)GzBp z|J$wB^*zz_^mMwJwo=QxzEVT^XpLr~`h>}9bwou)iN#{_s!zlM$MUJF=T>%%9ckv# zf3;&2+zFZLA5Tgwb^&?G|6r~IF;MT zPt{q*-PTGroR*JMSy{>F(=MgFd4F?mGPSkS=pFz1!LZ|~tkawUzm2>)7+fx~pjdnB zOchEJ;F}ALwx>Q!)|#nOfa>>`~|Ks0s<%+`U1g_xXg zc>czYc@uO}5r(=(fb9@A3xrWUUYuzZt=k@T{4yDNq{doqn5>>vq!$TjclY!)ZPmy7 zGQRBW?39!gvOxz(8{fR2x+Lv|#md=rT=v;sMMJ01b)_KPboYE^ekz_Mv_Ao}LwE$_ zKlQkQ(6#E2eC`BY{gFMl+A@k8GOlS-v5?wyl1!UC?7 zY|#O}f9eP;+)JvTdn_=%jBOep81-PGs+xHLdLSqz>7WyU+@6(y7^p|3b^zegf*D(@J-UxU0eT=PC23roxMaEJ*B#J-ht61_58*?IXoyJ=%|<mK{7 z^1;`X&{yQ8-bmo6A<6X2(qnsyqGMLjOSkJxNBp&;zO|B*j^n<6$6n~Y3m z;=4n}t<^?|dzw1DlWnj)6%`c%0k(dOl$m3=Cl*#n=1dezL~QdCH%g8D)s31rw`j~m zSn7-<{&#b%dU|>yWSl2%Ry?v?E)_F>0Io=R z^G-cTr~i<<7m<;X@T)#EPWDV0i)kxr*ejN$!uwc_@L;e49CU)O8}hxpMq zx{e-dZ<3Xjm71DLHZalD)O7LUMY0hVHX@RH+k)`BmWF4i(6e)PJF#(_{&yd!1{zK` zGMNmdTv^rJc^+V2!g09QR$cT@R_sRemXb57^r#2+3jBGW8Bp`pQ$Bo0`Xl`z9XlSrnt&<8w zf4Dz#a?y}ozw@B+Kn@&E36#M}ymOT`V6XTzg04 zyz(KlwgV^4&%E8Ur*_DDfp`&gpPZb`8)Da^U}=b2KvChgzuXlF^|*n+2s8vv0Up4T z$vjm*#RFc|xxyWi^B+izHOAn=ZYV&dZBKspR2Y_P{}w}Xt@+uOk`xoW5IO1!cr zva--a#OE5U&%LYqV)u{*EZoJ&kt5L!CtVHc432TPD-+YkGPcb~E^Osg z)#|xOlf!YT#|?xO5EylJbxA}VNRq$ZZ?q0=3Ka?kM8{xqXcY-#B5bj8d+*;MxEyET z0v~#R!jMx9gWM(zly$gft2iZ!j3ZO_Nu9fnLhX%gf5jV$)p(>85)YsK|y-e)DF}{*z>l z65>uU^CL!#pc^luR_(SOm0#&VDrvm3CW`!oNj+|2V`EE8OLKE`UBM0fv-$b?P){v$Me_vFq-z)$^1o zF*FiP-!yb^-ml`C`r*Sa!Z(-6WN_Ht=#%Ue!QD&c5GS%+ z*z0d*6=C*Y?|-Q!KlQ?F$p>TRPtc}~Kw91V%-i121soBxY8kzk_*8?q`ZQ9}Y3&_~ za0v%er=ZL~L}Vsb0iCM1=se9?AO$2AIBuJT6gz*j&+y|<-oZQMC}e{sw7tOkol!mt zUvA=}c#q!{q19?TJ8|YX7!e5^#o0LhmzVj0r-E_D2qo85s!>*nI2^>Y?|ADhBO`-x zJV5)eHFOFgBO@C=d2B^QTokvyzTQ0~6jIZS7l496HP@#3`Vk&YQjF*fVwt47hoQ z3T3RxWF}r%=WTwnva;A@e@q0_t>tgo7eO|Ba@RHC*(v= zjg5`urO*Js|N1Iu{_Bp}Jpu%+_ViAWB^&=GGc%L?#1w`C(wn)T{D7p~>!wR5<_nve znnLRQ5xfFAc*;8%514>*lFTU8T>#YMH9fI*~x~Ph5GEFw{Gpv z+ILl6l{l0MvWT#C)yFPL4qreTF@52#NU@M}>Cz>#9VBKJVfJ5}cY65cz?dHakGOp{ zlKI(v%=C@tzM>+L2>ch>U=4;?Z{3b4l<}_Ac>aSo$kl3fcTi6unO*2%Fj%tdu=gap zz|;?ylxY$MY&t#=f*=MRH^nme?*fB>F13ozG$Kv&czC(fya#cFb%er}os2YIjfhwI zhvkkR=v+4mDH1q#-g40Mh$@*(2H88=zySsUrN&xDr66U+8>*b{L$BAjwY9mf1elpv zhcAH7dr#$Y6R`a2nNAR9AQ{X#^bK19#VKqv&|d53 z-c`9bJTRNh&CSiO&jBMb$~rP0r~mS*AF>=&qJ_oA{a^=D{(Jp&X=V~x@6F1}a(%!E zTLE1=NzF$H$bPzD*|^Ok{b_1y3UDNZef;a|y&e&C!2FksUB_i-o2ZL$JRawv-q15V zJUk)-X$YJatOO($Iu@27B{k2!)8nNU2<7lPV@mM)udnuamjmMB;>b?{f`e#;RxJ#V zka2XqkeBemNJu~u5k`zi67zY!gc^epl6Q=R0e zI_lvFxs-eBd}q*o<;oS;6^yVF5Tg0{!$;neSo_4ycKE}+Z=Gs=M@$Ie@J9`xvIaK?B`5h%9Oh)3ZxSpLh! z-qX@fHFYMuY?iDbQ((8-@m6tZ2uQ$pOw2Vrw502kCmP>-s2ODu$%j=suiaSE)6>aM zOyST!>v`S;oc`VdxvT}HMM$VxE3^C0b`nS3yP zO>6b>zTj`fh!JDPj3FCAVuZrB=~s=kWOs@MMWXa z%=9a--qDIUJJAb*2)ocC1Q{JTCxs8R`WEomokJ{iI;mxo)sHOgzM)c6lwpdAaBbbi zggk zb6Q#&n?)pG6oq_(uKCGLHobX2&Si}icefro)Av@y#l_|4=aY?~BA3|z`I`omVI>qS zHfMpL=B%{lyrix7M2Df^oZph2fpv({#X$eX_;^4U5cJvY9I!xS8;$!Hc3)S4a}uqW z{~9-LTp-6#eZ$y``Q~!F0K6OcxYG@yf&I_*hkAf2Pdg9UJG;Gt2)l!}dVmGRIP3667+-Ms5Le?IW_bl>><5JiQ|F?*bTt>>1Kei2Z8i}lQ955vZ0{|A8CybrwF zCs?eG+6x{x72NvaKpt42{Vc_f)>o@z1Rr?J4j+<=*3e=E%1$goX zFm|NrFL&Y0zsB9teCl#vQ8Y0zF)uHVY=js*E|Dl}EE?Ifs6vvHir*$dCLs_AqNAgo zd%80gkkv-)`N>qZ&j`}F*yL+`uC%mNCUcJD1(HXXb}hnZfpkVk)EtiuMv9Az1Ga{c z0Y;;-rltlyqJNPybYuwtzZ&?As22u`%wbF=`Fev58nLE-x=9 z8v$hJ%Q%-&|7ElKsWMxY4i%$*2IlFTocusj@+iGWUA zvTUt3e!I_DbJ~o!V;!H*kBW){Qy=)*dgC(|20y3G2AC8(JA+M@7G_<46r*R$X2Hp{3 z=YZ{U;>EA8>zq3QTMDMs*y9&^%)<(bQ)Y(4;c%@61_n+?OpE6&s+2;gG;O?CEJAf~ z)B^h8Yi(7v@jr)s9VgQtFzK#|J^%=ayc#_4`maBJta|RfzTsGSE2d7JN;a-4z@xd8 z1d{Sv5+fCJW|fG+CaqpV9oSI|sQ-WA19vD6{~O>r&ORu~F}$)0r~k^nzUfLo1DukQ zlAWDRHm**rRx3C-P!c#eufuClLK?OL0_*KEHW3jK0iK+S!ZXX-XB06RmmZY1Zd>Y7 zk2R1JCr%6nD~koNLmG|7WtLp0z0wsBYK#JQ8_Wzuj*wD81dy4T2|gz?aqWxdKg#nkPk#>X@fU#vo-BiX zgQx|RmzNhC8;fyA8NV_Y>+pL+yR}v6QM4Gw0%AsR(ro`RB_HR)x%WMnpF+|Q;s!YI z3@ZUm&d2S)JSPDCVtxiPtOPU)&_ITjfXef5`Y*5Lr=niS#xMMr z;Ul2wW3lI79s$rV=4VibkAUVEyIvc#j|S@?VgA1@id8 ztB7GDpy71<;MK)25zwL%oC2!7U^aU$cKSs?Lopl##OD%)8Cd;S@Qxq6>KI%=d1+Tu ze+{SO2ZKv;4DD5>L(WLN(CAudVdN^x?DQA)1(N@}w|R6b^=5mB96D{TxW{7)rm0h> z25V|%0N`C#R&waPRBZ^kAytvUvGsYR>2HYK z{Yuw|y+<;*kjZ4FrKMzJ7!e`0Pt0|0`&61^K-p@yKlB9T#^DG%(2h*%FdmK+bVkNmz|xfE8Ahj?c}%S;Ljm=ty@!9`oCL&FTQ9cS zf@$90UelDFMB8%@MfGvh^feySVW&--7Mkjccd0 z182c3NtC)khu$)=^0ZGC;cMx*gs0f~{tSpC=b1Kx{)LTv*wO0ttI z5mG$chkKznI=|K$OBTMy<0YI33TaG|3|kNW9k!su{Y zatwxy)J+hNAH+pz4$cK}z?C-JTU#`Xe$^hosPX!zJ3shZ*V^s9MMB?3<0IoNal(@e zDj zk4j-Xq~mZ92Tqxtqgd6(lXcV~W}geY+#&qaCe8FU?MX|TR=wEu&;RIETITj}T#hiK z_oz;lO65S@4~7m)N^x#J|Mgz)clWAOPi%w@vS6}7AC2De`7Zq(FLxzh(=_e=_E$G* zP}CD19zIA6q|`Y6zC(ZW^IeIH8)rY%_WD0G=Np(LJIiJ|mjK)Cc8G*rS8y>xVT+5z zDa!Xem!F2h(FJswTA*i;w_j+q{`FJM+(+8u7dGDb+pbO9^j#{7j^%Q>;8k*Sb0sdTNgH<`Sr*JP*g7dv-KlT0oWBkq)T;*w>;DyJ7?xkvp#ZkyS2>&(9W z!r0^Gzs8Ii!*W~M!1Lz)%_!&p0X7>^ecW8N&j5bvTmxzxDH4f#H`IuM=&=7X<-reI z(dl$9B@Hwl5i-u)GReKy%BN4DUZYvIy7lNe3m7_(DtQS9pFVXtT8=Hb+w#4B<#SzR zqnAPZB$UaBx4icdb+WRj*7VN5wA*WpY8`6tMMOlnbOR5OyyS_;VJmKgLLmq(F)=YY zIawx?aX1`{#o`KO%+|2e_15DTEcaYXy%Ujx2m_hJ-;Ixt$2BQEG1_|5beul%@xLnT zTl$g<=q#2;3I(1Ur>RD#dX#4dW#!t=org?jDmw)RCN?$}{08``l#~=uf{$IA2e&{2 z8Mss0iBFP7rBi4^YjC7r+oUxR85tSukD7C_dX94CG(R5S1UVyUh-_R9hy{+#Pd0{2 zC}NlT7HjTuU+1PtB$8fRLvL@w!7b20zzD%Fh>ng1NkWDq7K=qZ^OPcr>XAsmX;<10 zp0-%6R$Q}cp1ZMKgl~b(4@Zq1N;^b&?mv~qDakR`q5c@mT$9x=)mO$WDA7(SpsoPD zwMF~GQEw{+j};6IJeut6Y8rI(4ZexAm^dhX|Uv$}aB(%2n<#33brz4d!y!NSq2n5$Q`>X`C?~JrdLp{wt zFHLasJQ@1~r$@$_#dJ>n#m=Vh2v-{60~aLvae#HU}MInj^)2MGioK99J47Sd?odtYk34qwMu zr(h)@pz4s^Xr|V3S_1#!<#LnB)X~vFUZPKM{g>Bp2N2v5`y~@KNeJ^13?|3hUwC|T z2%j--G|N^%S|e}kAtZ15cSAp{o_05iqHD`dav_iP*_-sc>*gf-u3hXKyYVF?>QyyP$8`n ze&QyN?+LocyG)zyzg3uFEJNFw@TK)C*U>Fzvl*gk*F{9~Q}Lf>O^Z$2J@(HF`gCcF zjr;K~q&X*ZCQ4?OkhLq24)$-(Qb5Gr^N^Nhoo}#$GbS5hVGRupv~u9!=3iYZ`-w{) z{woxg`|LaxUf+gP27KlINLS7dUz_#ewcfB2(9{A0;vCAg@2dKrr3x;rtIJ~sg>UBI zUVuRhuPPk8$Dwf8rGwynumkBNzX^Gw<+Ge|sy_yanG1-=CGMDwWPT2uG=H(%qhtc_ z0eUH-aAcs@I(gv=-P{D-f4QkS@7$Y!Z208GyCVhsu*OD@V_&hrTtEv;w20fuJoC19 zy(>LE9sTf)#S?>>e>Jw-PS<5XI3fXQchi6 z9p06C39DxJSLbHX!8ya z^CJ?8Ldt)++4y}V%7~;)!hP-zg~?=UY4J6;l@_!GlpJGSREkuUdE;|!Q-_Cbg*1}Z z03`O~2kG*ur~eAs)$%<${^!*Svf-0E7l-Gi@vmID!hUX+3)%v@V@?krA%9Y8?5}S0 z_C6UI8RRE47MBGx|2lKoa^7x5}#ZoUR)_@Yio0z70(7# z0dY9QU2~C&n8z<#KJgs82E&t~DLdDMG+g@o`y%0)LP}4z znSvSv%^9zK@_Hm2u=t7g?FT#zRaRD3YHBLkU<$6Rff*bQ=W03lyePx?n&&2BLvha^ zyEkmrjUPW=p+L&u1_j9i3HT1g`P)AnHF>EA!Za+^TQFlmS{UUElITPgp)WAPviILkLTGIX4 zwO#PkF0=?8SWnZ6Q`lZx-5RSv7+-Z(+BiZ!r>a`Zc&#e6fbN~IjFuyP8$c1xb!#Xx zGLq%SWRNDzD$+lHQ^$-#y;w+OCOAh?Ku_nIM7GdA!fYr+0>@3$RL`vF%uBI|1kQtk z=Zw>V!KiN&5;zWraH%H|p;qT6QqFz*Us?b{RR;6zsRgwEtQ36_3E<4<7^tVN**fphc8#8}+wBmlGFU2!l*WU}g>Qy9|IkH4yX}b&5_=3x#;$Xd z%&X@qCuC9h&9)x?=G}^&$K+OfNXv-NBf!kuI88$XRoZDLY!Wu~6l54ShA zlkNosR9;?AcfSs#p$wyiyZ4lgt{p1R)%%X{4^~)dM(3 z{|F*F8fl`w8ybVDV2571`EpJQjT>uhxBUvu4k@JeTV|*hm1+cxw#>I!IX|9~etTGc z+_`^G&<&FF6jG4F)B-&OI^;Dv*z93&FB`v)>_Iq!4U2kQK*m#ATI$Cg%H$%$tG74j zq|$iElo}EX9M@He$aU6=)s^srFF8lGMtgemiI5Pt>;w{q(^fL-|DBB)TVl;C; zuU34vR{VZUT+_-Uchb-v{2*9WzE|*wXti5%n3O&n12bAPI{qDTvc)i%- z&qFx{d`IC(Q(2y|EZ2ZVH(vDUxSxC%+&QNwbp-YP zNGt*GR(o0e`j@etYDV61XuzjGc5N4Ag#p<1s=1rL;k&5aCuD919SZ6J=*VkCBvnxB7P1Y!6s3AEUc)YOW7+brF&n&y~!QAmWh>=m|XR-&< zm#GEL)0SJ5g0>z(h^Pl(A|QaVhQ?=^@1Cz3fmSEQ2)N7de!{#SdDZM5yhlz^5*uL$ z)s9Xv-!NGn3hDuv2M1cS^r;V%FErA)jnsI4)&0TPbfZb%#b8^Rp^yk2MH#_a zF;bdq3=#DJtfET>8VVLd0VhFvsk*wlwzhWQQ8ElD&Nd1$$5ej7uj&EVrYC{X5J3>% z9W-hUtYW4`6;g-8(bLm|Wp*rtBjhJW=nM|HBe*ZJ?H~ND9)PWY0Do)f`y@PnU0QCS2 zgJ>@p#MCI%Ua+kGL)Q!I)Gb}kUNBjR-N(#t{Y%^4d%8t*1ikzxT*BRUz}VbrH=E5V zDJcv_p55dnTduF9y=D!b=t~>ax4+c2y~boTJ8e&Ew|&z#ea$KJm`q_p49;{eFzNyD z2nfKdp(|~+M_*Sy{1@dV&!v5U)fRT}wB@ZW+M3g5&_q@;pUXupy|;DST?dZH<#L&f z`L^?mCu$2bPy;EOji}mZeB%FJN4~KU(p+n$BT%FfZ$9h2wQWV{5Q8LVie4+Q6SX=U=|R- zSVKm$68RyAc6b~<# zOGLD-QGnG0U~^$^82B=~o%payx9pj&fA294Wq82jfXx8)xF)H=TMKpwdLO#pXf%S1 zAWnL81;y68pqL8{)+6Vfr!U6D#4wxhHlt8CX*6}@`@m~Tm2ODG1J4qA9_|RfhHKFQ zd>OEd;9GlC`fd9)AAO@&$T)@BLUacmc=Z7I1q9I2&{um5E7o>xsM2c`*_@o5 z+}zxO)2ks625Xd@oa}S~8td%hFv?IAEXO}~Iuj^R9us^3td$c?Cs1fL{azplE2{3Dd2= z>w4|)nxT%G1`D5&kpZtM=);G*fgeG?$H&JfL=i=q6rl+`5AZBDyK|>l1`F1Ne8(f# zbW@IhZP=>Yb;Kje4i67cO-;?q%Y$DE-`4l-z<&&H1>AmJqiXXG-S>x0dFg`GxFMs0 zV5tZ2mw*5t8ai{?df%(vPrs!ac+W22)r9WO&(8-Vfu3($ZM+2~6dM8y_?!c$%oiGM z7K;U320SG42WXUJ8|O}--g9{EyB@cem6bJm^k^_S=)VqRusf0I1J~83SvKtz9;d&iGX=U+oz(jDY7_R#pb#4&C{=XPZSRd*m#tAQx^j zs3@Y;49L1wV&7HR3st3TI#t({2XgG1=R72~K zn~6Y_1FstU6SyYlJJ*qsjiH%%XX~-^7I2Q0m6fg}2!+f9^ScjHZxV9TZ#zHR)n};E z($e78$Oda5&xdRua()O5L;{YhM&F-6st53|3v;8lNRvkvLUKX2`vEWLm@#9)NeLKdA6W@4j23S1!VC&r0s?4-K_~%G^>K6mLMV72Hk+pm3iK89 zSZQe~^d9sfc{Ld70Rkr=z^x(h8W0vhYzW~FdXk06fwR=B@k>StSaJ?H|K)*rnK_9yIKM9I@fPe}JaA^o`8w7+Ne9!{r1Vt{FvtW|b z2thKh(;*!NG%?SBBK4|$9{vjiM1JxQ(1+>i>Cm6x*8;B|AV5KMfLZr&6QpBgBY<3J zwC3N~7iEL(si>%6f4F}r6KD$*7;4atamCKoY7g@R(a-Q!4=_9hv>zc1@(14p)_;Wp z!&5+~8$}vJpl?mBbL_4482|c3Kvydc2j}pGL26EZWk-#%|FKJuY)Y?&oq<_0JOuY|Z&V7$L9xD`Q%?t!ovPPHUxv6!BBDn00000 LNkvXXu0mjf@D_Ps literal 0 HcmV?d00001