From 680fb18f720b39994872cd45ebd13d7b32b775d6 Mon Sep 17 00:00:00 2001 From: okxlin <61420215+okxlin@users.noreply.github.com> Date: Wed, 15 Jul 2026 02:26:30 +0800 Subject: [PATCH] Add wakapi app Maintainer-workflow: appstore-pr-maintainer --- apps/wakapi/2.17.4/.env.sample | 6 ++ apps/wakapi/2.17.4/data.yml | 95 ++++++++++++++++++++++++ apps/wakapi/2.17.4/data/.gitkeep | 0 apps/wakapi/2.17.4/docker-compose.yml | 30 ++++++++ apps/wakapi/2.17.4/scripts/.gitkeep | 0 apps/wakapi/2.17.4/scripts/init.sh | 62 ++++++++++++++++ apps/wakapi/2.17.4/scripts/uninstall.sh | 2 + apps/wakapi/2.17.4/scripts/upgrade.sh | 3 + apps/wakapi/README.md | 45 +++++++++++ apps/wakapi/data.yml | 31 ++++++++ apps/wakapi/latest/.env.sample | 6 ++ apps/wakapi/latest/data.yml | 95 ++++++++++++++++++++++++ apps/wakapi/latest/data/.gitkeep | 0 apps/wakapi/latest/docker-compose.yml | 30 ++++++++ apps/wakapi/latest/scripts/.gitkeep | 0 apps/wakapi/latest/scripts/init.sh | 62 ++++++++++++++++ apps/wakapi/latest/scripts/uninstall.sh | 2 + apps/wakapi/latest/scripts/upgrade.sh | 3 + apps/wakapi/logo.png | Bin 0 -> 3467 bytes 19 files changed, 472 insertions(+) create mode 100644 apps/wakapi/2.17.4/.env.sample create mode 100644 apps/wakapi/2.17.4/data.yml create mode 100644 apps/wakapi/2.17.4/data/.gitkeep create mode 100644 apps/wakapi/2.17.4/docker-compose.yml create mode 100644 apps/wakapi/2.17.4/scripts/.gitkeep create mode 100755 apps/wakapi/2.17.4/scripts/init.sh create mode 100755 apps/wakapi/2.17.4/scripts/uninstall.sh create mode 100755 apps/wakapi/2.17.4/scripts/upgrade.sh create mode 100644 apps/wakapi/README.md create mode 100644 apps/wakapi/data.yml create mode 100644 apps/wakapi/latest/.env.sample create mode 100644 apps/wakapi/latest/data.yml create mode 100644 apps/wakapi/latest/data/.gitkeep create mode 100644 apps/wakapi/latest/docker-compose.yml create mode 100644 apps/wakapi/latest/scripts/.gitkeep create mode 100755 apps/wakapi/latest/scripts/init.sh create mode 100755 apps/wakapi/latest/scripts/uninstall.sh create mode 100755 apps/wakapi/latest/scripts/upgrade.sh create mode 100644 apps/wakapi/logo.png diff --git a/apps/wakapi/2.17.4/.env.sample b/apps/wakapi/2.17.4/.env.sample new file mode 100644 index 000000000..d29ebede9 --- /dev/null +++ b/apps/wakapi/2.17.4/.env.sample @@ -0,0 +1,6 @@ +PANEL_APP_PORT_HTTP=3000 +WAKAPI_PASSWORD_SALT=replace-with-a-random-32-character-salt +WAKAPI_INSECURE_COOKIES=true +WAKAPI_ALLOW_SIGNUP=true +APP_DATA_DIR=./data +CONTAINER_NAME=wakapi diff --git a/apps/wakapi/2.17.4/data.yml b/apps/wakapi/2.17.4/data.yml new file mode 100644 index 000000000..782366078 --- /dev/null +++ b/apps/wakapi/2.17.4/data.yml @@ -0,0 +1,95 @@ +additionalProperties: + formFields: + - default: 3000 + edit: true + envKey: PANEL_APP_PORT_HTTP + labelEn: Web Port + labelZh: Web 端口 + label: + en: Web Port + zh: Web 端口 + zh-Hant: Web 連接埠 + ja: Web ポート + ko: Web 포트 + ru: Веб-порт + ms: Port Web + pt-br: Porta Web + required: true + rule: paramPort + type: number + - default: change-me + edit: true + envKey: WAKAPI_PASSWORD_SALT + labelEn: Password Salt + labelZh: 密码盐 + label: + en: Password Salt + zh: 密码盐 + zh-Hant: 密碼鹽 + ja: パスワードソルト + ko: 비밀번호 솔트 + ru: Соль паролей + ms: Garam kata laluan + pt-br: Sal de senha + random: true + required: true + rule: paramComplexity + type: password + - default: "true" + edit: true + envKey: WAKAPI_INSECURE_COOKIES + labelEn: Allow Insecure Cookies + labelZh: 允许非安全 Cookie + label: + en: Allow Insecure Cookies + zh: 允许非安全 Cookie + zh-Hant: 允許非安全 Cookie + ja: 非セキュア Cookie を許可 + ko: 보안이 적용되지 않은 Cookie 허용 + ru: Разрешить небезопасные cookie + ms: Benarkan kuki tidak selamat + pt-br: Permitir cookies inseguros + required: true + type: select + values: + - label: "true" + value: "true" + - label: "false" + value: "false" + - default: "true" + edit: true + envKey: WAKAPI_ALLOW_SIGNUP + labelEn: Allow Sign-up + labelZh: 允许注册 + label: + en: Allow Sign-up + zh: 允许注册 + zh-Hant: 允許註冊 + ja: ユーザー登録を許可 + ko: 회원가입 허용 + ru: Разрешить регистрацию + ms: Benarkan pendaftaran + pt-br: Permitir cadastro + required: true + type: select + values: + - label: "true" + value: "true" + - label: "false" + value: "false" + - 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: Diretório de dados + required: true + type: text diff --git a/apps/wakapi/2.17.4/data/.gitkeep b/apps/wakapi/2.17.4/data/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/apps/wakapi/2.17.4/docker-compose.yml b/apps/wakapi/2.17.4/docker-compose.yml new file mode 100644 index 000000000..83c36a85b --- /dev/null +++ b/apps/wakapi/2.17.4/docker-compose.yml @@ -0,0 +1,30 @@ +services: + wakapi: + image: "n1try/wakapi:2.17.4" + container_name: ${CONTAINER_NAME} + init: true + restart: unless-stopped + networks: + - 1panel-network + ports: + - "${PANEL_APP_PORT_HTTP}:3000" + environment: + - WAKAPI_DB_TYPE=sqlite3 + - WAKAPI_DB_NAME=/data/wakapi.db + - WAKAPI_PASSWORD_SALT=${WAKAPI_PASSWORD_SALT} + - WAKAPI_INSECURE_COOKIES=${WAKAPI_INSECURE_COOKIES} + - WAKAPI_ALLOW_SIGNUP=${WAKAPI_ALLOW_SIGNUP} + volumes: + - "${APP_DATA_DIR}:/data" + healthcheck: + test: ["CMD", "/app/healthcheck"] + interval: 60s + timeout: 3s + start_period: 120s + retries: 3 + labels: + createdBy: "Apps" + +networks: + 1panel-network: + external: true diff --git a/apps/wakapi/2.17.4/scripts/.gitkeep b/apps/wakapi/2.17.4/scripts/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/apps/wakapi/2.17.4/scripts/init.sh b/apps/wakapi/2.17.4/scripts/init.sh new file mode 100755 index 000000000..2b1b96b3a --- /dev/null +++ b/apps/wakapi/2.17.4/scripts/init.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)" +ENV_FILE="${ENV_FILE:-$ROOT_DIR/.env}" +APP_DATA_DIR_RAW="${APP_DATA_DIR:-}" +WAKAPI_UID=65532 +WAKAPI_GID=65532 + +if [[ -z "$APP_DATA_DIR_RAW" && -f "$ENV_FILE" ]]; then + APP_DATA_DIR_RAW="$(grep '^APP_DATA_DIR=' "$ENV_FILE" | tail -n 1 | cut -d '=' -f 2- || true)" +fi +APP_DATA_DIR_RAW="${APP_DATA_DIR_RAW:-./data}" + +case "$APP_DATA_DIR_RAW" in + \"*\") + APP_DATA_DIR_RAW="${APP_DATA_DIR_RAW#\"}" + APP_DATA_DIR_RAW="${APP_DATA_DIR_RAW%\"}" + ;; + \'*\') + APP_DATA_DIR_RAW="${APP_DATA_DIR_RAW#\'}" + APP_DATA_DIR_RAW="${APP_DATA_DIR_RAW%\'}" + ;; +esac + +if [[ -z "$APP_DATA_DIR_RAW" ]]; then + echo "APP_DATA_DIR must not be empty" >&2 + exit 1 +fi + +case "$APP_DATA_DIR_RAW" in + /*) + APP_DATA_DIR_ABS="$(realpath -m -- "$APP_DATA_DIR_RAW")" + APP_DATA_DIR_SCOPE="absolute" + ;; + *) + APP_DATA_DIR_ABS="$(realpath -m -- "$ROOT_DIR/${APP_DATA_DIR_RAW#./}")" + case "$APP_DATA_DIR_ABS" in + "$ROOT_DIR"/*) ;; + *) + echo "Relative APP_DATA_DIR must stay inside the application directory" >&2 + exit 1 + ;; + esac + APP_DATA_DIR_SCOPE="application" + ;; +esac + +DATA_DIR_EXISTED=false +[[ -d "$APP_DATA_DIR_ABS" ]] && DATA_DIR_EXISTED=true +mkdir -p "$APP_DATA_DIR_ABS" + +if [[ "$APP_DATA_DIR_SCOPE" == "application" || "$DATA_DIR_EXISTED" == "false" ]]; then + chown -R "$WAKAPI_UID:$WAKAPI_GID" "$APP_DATA_DIR_ABS" +else + CURRENT_UID="$(stat -c '%u' "$APP_DATA_DIR_ABS")" + CURRENT_GID="$(stat -c '%g' "$APP_DATA_DIR_ABS")" + if [[ "$CURRENT_UID" != "$WAKAPI_UID" || "$CURRENT_GID" != "$WAKAPI_GID" ]]; then + echo "Existing absolute APP_DATA_DIR must be owned by ${WAKAPI_UID}:${WAKAPI_GID}" >&2 + exit 1 + fi +fi diff --git a/apps/wakapi/2.17.4/scripts/uninstall.sh b/apps/wakapi/2.17.4/scripts/uninstall.sh new file mode 100755 index 000000000..ab8a8d674 --- /dev/null +++ b/apps/wakapi/2.17.4/scripts/uninstall.sh @@ -0,0 +1,2 @@ +#!/bin/bash +docker-compose down --volumes diff --git a/apps/wakapi/2.17.4/scripts/upgrade.sh b/apps/wakapi/2.17.4/scripts/upgrade.sh new file mode 100755 index 000000000..9879df4c3 --- /dev/null +++ b/apps/wakapi/2.17.4/scripts/upgrade.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +set -euo pipefail +exit 0 diff --git a/apps/wakapi/README.md b/apps/wakapi/README.md new file mode 100644 index 000000000..89e3b967a --- /dev/null +++ b/apps/wakapi/README.md @@ -0,0 +1,45 @@ +# Wakapi + +## 产品介绍 + +Wakapi 是一个极简、自托管且兼容 WakaTime 客户端的开发统计后端,可用于记录并分析项目、语言、编辑器、主机和操作系统维度的编码活动。 + +## 主要功能 + +- 兼容 WakaTime 客户端和插件 +- 项目、编程语言、编辑器和主机统计 +- REST API、统计徽章和 Prometheus 指标导出 +- 周报邮件和多种数据导入方式 + +## 访问说明 + +- 默认 Web 端口为 `3000`,可在安装表单中修改。 +- 首次访问后注册账户,然后在 Wakapi 页面中获取 API Key。 +- 在 WakaTime 客户端的 `~/.wakatime.cfg` 中将 `api_url` 设为 `http(s):///api`,并填入账户的 API Key。 +- 创建首个账户后,建议在 1Panel 应用参数中将“允许注册”改为 `false`。 + +## 数据与安全 + +- 此应用包使用 Wakapi 官方 Docker 镜像的默认 SQLite 模式,数据库位于持久化目录中的 `wakapi.db`。Wakapi 上游也支持 PostgreSQL 和 MySQL,但本应用包未启用外部数据库联动。 +- “密码盐”用于密码哈希,安装时应使用随机值;创建账户后不要修改,并在备份时一并保留应用参数。 +- 官方镜像以 UID/GID `65532:65532` 的非 root 用户运行。应用内的默认相对数据目录会在安装时自动设置权限;如果指定已存在的绝对路径,请先将该目录属主设为 `65532:65532`。 +- 直接通过 HTTP 访问时保持“允许非安全 Cookie”为 `true`。如果通过带 SSL 证书的 HTTPS 反向代理访问,应将其改为 `false`。 + +## Introduction + +Wakapi is a minimalist, self-hosted WakaTime-compatible backend for coding statistics. It tracks activity by project, language, editor, host and operating system. + +## Features + +- WakaTime client and plugin compatibility +- Coding statistics across projects, languages, editors and hosts +- REST API, badges and Prometheus exports +- Weekly e-mail reports and data imports + +This package uses the official image's SQLite mode and persists `/data`. Keep the generated password salt unchanged after accounts are created. Disable sign-up after creating the initial account, and set insecure cookies to `false` when serving Wakapi through HTTPS. + +## 参考资料 + +- 项目主页: +- 安装与配置: +- 源码仓库: diff --git a/apps/wakapi/data.yml b/apps/wakapi/data.yml new file mode 100644 index 000000000..3335b729c --- /dev/null +++ b/apps/wakapi/data.yml @@ -0,0 +1,31 @@ +name: Wakapi +tags: + - 开发工具 +title: 一个自托管的 WakaTime 兼容开发统计后端 +description: 一个自托管的 WakaTime 兼容开发统计后端 +additionalProperties: + key: wakapi + name: Wakapi + tags: + - DevTool + shortDescZh: 一个自托管的 WakaTime 兼容开发统计后端 + shortDescEn: A self-hosted WakaTime-compatible backend for coding statistics + description: + en: A self-hosted WakaTime-compatible backend for coding statistics + zh: 一个自托管的 WakaTime 兼容开发统计后端 + zh-Hant: 一個自託管、相容 WakaTime 的程式開發統計後端 + ja: コーディング統計向けのセルフホスト型 WakaTime 互換バックエンド + ko: 코딩 통계를 위한 자체 호스팅 WakaTime 호환 백엔드 + ru: Самостоятельно размещаемый WakaTime-совместимый сервер статистики программирования + ms: Backend serasi WakaTime yang dihos sendiri untuk statistik pengekodan + pt-br: Backend compatível com WakaTime e auto-hospedado para estatísticas de programação + type: tool + crossVersionUpdate: true + limit: 0 + recommend: 0 + website: https://wakapi.dev/ + github: https://github.com/muety/wakapi + document: https://github.com/muety/wakapi + architectures: + - amd64 + - arm64 diff --git a/apps/wakapi/latest/.env.sample b/apps/wakapi/latest/.env.sample new file mode 100644 index 000000000..d29ebede9 --- /dev/null +++ b/apps/wakapi/latest/.env.sample @@ -0,0 +1,6 @@ +PANEL_APP_PORT_HTTP=3000 +WAKAPI_PASSWORD_SALT=replace-with-a-random-32-character-salt +WAKAPI_INSECURE_COOKIES=true +WAKAPI_ALLOW_SIGNUP=true +APP_DATA_DIR=./data +CONTAINER_NAME=wakapi diff --git a/apps/wakapi/latest/data.yml b/apps/wakapi/latest/data.yml new file mode 100644 index 000000000..782366078 --- /dev/null +++ b/apps/wakapi/latest/data.yml @@ -0,0 +1,95 @@ +additionalProperties: + formFields: + - default: 3000 + edit: true + envKey: PANEL_APP_PORT_HTTP + labelEn: Web Port + labelZh: Web 端口 + label: + en: Web Port + zh: Web 端口 + zh-Hant: Web 連接埠 + ja: Web ポート + ko: Web 포트 + ru: Веб-порт + ms: Port Web + pt-br: Porta Web + required: true + rule: paramPort + type: number + - default: change-me + edit: true + envKey: WAKAPI_PASSWORD_SALT + labelEn: Password Salt + labelZh: 密码盐 + label: + en: Password Salt + zh: 密码盐 + zh-Hant: 密碼鹽 + ja: パスワードソルト + ko: 비밀번호 솔트 + ru: Соль паролей + ms: Garam kata laluan + pt-br: Sal de senha + random: true + required: true + rule: paramComplexity + type: password + - default: "true" + edit: true + envKey: WAKAPI_INSECURE_COOKIES + labelEn: Allow Insecure Cookies + labelZh: 允许非安全 Cookie + label: + en: Allow Insecure Cookies + zh: 允许非安全 Cookie + zh-Hant: 允許非安全 Cookie + ja: 非セキュア Cookie を許可 + ko: 보안이 적용되지 않은 Cookie 허용 + ru: Разрешить небезопасные cookie + ms: Benarkan kuki tidak selamat + pt-br: Permitir cookies inseguros + required: true + type: select + values: + - label: "true" + value: "true" + - label: "false" + value: "false" + - default: "true" + edit: true + envKey: WAKAPI_ALLOW_SIGNUP + labelEn: Allow Sign-up + labelZh: 允许注册 + label: + en: Allow Sign-up + zh: 允许注册 + zh-Hant: 允許註冊 + ja: ユーザー登録を許可 + ko: 회원가입 허용 + ru: Разрешить регистрацию + ms: Benarkan pendaftaran + pt-br: Permitir cadastro + required: true + type: select + values: + - label: "true" + value: "true" + - label: "false" + value: "false" + - 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: Diretório de dados + required: true + type: text diff --git a/apps/wakapi/latest/data/.gitkeep b/apps/wakapi/latest/data/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/apps/wakapi/latest/docker-compose.yml b/apps/wakapi/latest/docker-compose.yml new file mode 100644 index 000000000..1522730cf --- /dev/null +++ b/apps/wakapi/latest/docker-compose.yml @@ -0,0 +1,30 @@ +services: + wakapi: + image: "n1try/wakapi:latest" + container_name: ${CONTAINER_NAME} + init: true + restart: unless-stopped + networks: + - 1panel-network + ports: + - "${PANEL_APP_PORT_HTTP}:3000" + environment: + - WAKAPI_DB_TYPE=sqlite3 + - WAKAPI_DB_NAME=/data/wakapi.db + - WAKAPI_PASSWORD_SALT=${WAKAPI_PASSWORD_SALT} + - WAKAPI_INSECURE_COOKIES=${WAKAPI_INSECURE_COOKIES} + - WAKAPI_ALLOW_SIGNUP=${WAKAPI_ALLOW_SIGNUP} + volumes: + - "${APP_DATA_DIR}:/data" + healthcheck: + test: ["CMD", "/app/healthcheck"] + interval: 60s + timeout: 3s + start_period: 120s + retries: 3 + labels: + createdBy: "Apps" + +networks: + 1panel-network: + external: true diff --git a/apps/wakapi/latest/scripts/.gitkeep b/apps/wakapi/latest/scripts/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/apps/wakapi/latest/scripts/init.sh b/apps/wakapi/latest/scripts/init.sh new file mode 100755 index 000000000..2b1b96b3a --- /dev/null +++ b/apps/wakapi/latest/scripts/init.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)" +ENV_FILE="${ENV_FILE:-$ROOT_DIR/.env}" +APP_DATA_DIR_RAW="${APP_DATA_DIR:-}" +WAKAPI_UID=65532 +WAKAPI_GID=65532 + +if [[ -z "$APP_DATA_DIR_RAW" && -f "$ENV_FILE" ]]; then + APP_DATA_DIR_RAW="$(grep '^APP_DATA_DIR=' "$ENV_FILE" | tail -n 1 | cut -d '=' -f 2- || true)" +fi +APP_DATA_DIR_RAW="${APP_DATA_DIR_RAW:-./data}" + +case "$APP_DATA_DIR_RAW" in + \"*\") + APP_DATA_DIR_RAW="${APP_DATA_DIR_RAW#\"}" + APP_DATA_DIR_RAW="${APP_DATA_DIR_RAW%\"}" + ;; + \'*\') + APP_DATA_DIR_RAW="${APP_DATA_DIR_RAW#\'}" + APP_DATA_DIR_RAW="${APP_DATA_DIR_RAW%\'}" + ;; +esac + +if [[ -z "$APP_DATA_DIR_RAW" ]]; then + echo "APP_DATA_DIR must not be empty" >&2 + exit 1 +fi + +case "$APP_DATA_DIR_RAW" in + /*) + APP_DATA_DIR_ABS="$(realpath -m -- "$APP_DATA_DIR_RAW")" + APP_DATA_DIR_SCOPE="absolute" + ;; + *) + APP_DATA_DIR_ABS="$(realpath -m -- "$ROOT_DIR/${APP_DATA_DIR_RAW#./}")" + case "$APP_DATA_DIR_ABS" in + "$ROOT_DIR"/*) ;; + *) + echo "Relative APP_DATA_DIR must stay inside the application directory" >&2 + exit 1 + ;; + esac + APP_DATA_DIR_SCOPE="application" + ;; +esac + +DATA_DIR_EXISTED=false +[[ -d "$APP_DATA_DIR_ABS" ]] && DATA_DIR_EXISTED=true +mkdir -p "$APP_DATA_DIR_ABS" + +if [[ "$APP_DATA_DIR_SCOPE" == "application" || "$DATA_DIR_EXISTED" == "false" ]]; then + chown -R "$WAKAPI_UID:$WAKAPI_GID" "$APP_DATA_DIR_ABS" +else + CURRENT_UID="$(stat -c '%u' "$APP_DATA_DIR_ABS")" + CURRENT_GID="$(stat -c '%g' "$APP_DATA_DIR_ABS")" + if [[ "$CURRENT_UID" != "$WAKAPI_UID" || "$CURRENT_GID" != "$WAKAPI_GID" ]]; then + echo "Existing absolute APP_DATA_DIR must be owned by ${WAKAPI_UID}:${WAKAPI_GID}" >&2 + exit 1 + fi +fi diff --git a/apps/wakapi/latest/scripts/uninstall.sh b/apps/wakapi/latest/scripts/uninstall.sh new file mode 100755 index 000000000..ab8a8d674 --- /dev/null +++ b/apps/wakapi/latest/scripts/uninstall.sh @@ -0,0 +1,2 @@ +#!/bin/bash +docker-compose down --volumes diff --git a/apps/wakapi/latest/scripts/upgrade.sh b/apps/wakapi/latest/scripts/upgrade.sh new file mode 100755 index 000000000..9879df4c3 --- /dev/null +++ b/apps/wakapi/latest/scripts/upgrade.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +set -euo pipefail +exit 0 diff --git a/apps/wakapi/logo.png b/apps/wakapi/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..e237621fd2acf46ca0626804baa5c93564193921 GIT binary patch literal 3467 zcmY*cXH*kf){QhF6zL*02uceg5Q_9r1?imx0R^ej^5|7Ul}M8g4-7p@lM)~V5Tq(Z zP->{sr3L8P9OjPpx3>xX?n?xe-1VIWd*Vv zUsgbwvA((1CEtApmyZ4qVA9brY2^RnvipxB8v~cRH2m@Z*CQ1JmyG$Z=T8?2llsRa z6$6uu{`d7VB*ymtqyG5ekUz@5H~y3PXDS8;iGoWZFJb+ETPE6`!!92x>t||c2Dr*D zByZsqol)98wm>>qq+k?=V!P*6&Iv^zC-pSt_c5y_wg%u+u(X$gy_(+?XK931XnL{6V|@t_U#h-3SQqctAX za{LAeA$~?g^>~nL6BxfUl6*FGR1Y3(Bbnu=wcc4fA)BEI_Pde~H&li;{C41oCpAzv zcc8gSKr{jz-j`bTWKKmcVw~WN{SWACVWL4~|Uf3x{q+!26a**J7J zRHUMg2b`DhYfC%QQO(|PGWEQ9SMdUwI!@*{YU0+=KN;XLI-s1z`OF!r6E*dV!*Kom zwV~*FF>`?eVmvM^yAP@g{_J`Oft38=Ivoey}8GvgspmTMRy|0dEUvi9qI4xGDZsQK6QZ3o67x7kVwr+tP^s^(*vzA)t7 zzybwS?jZ=jIIWHS$z3(}`GC_jsnbbw;hKu6ON|03>la$bh>Bm;8bXj#d7mPG8{oX& z%f0g1XM#`cg;>XfOOM*o$g^ zFN3u{E-ZSbeY)_)Pgh|N4qA$py@yss^dE><>*MCxk7Hh{s|ww|dqNI0Dd#}2bqLJO z?mq>|IGNBQ#={2|F znim6lJW9`vmPvjh-!SQMMc;D6KUW9ocB4n*MgfmCy-}O*BJM7>*qgt7Wgpw~qJN7 zFi1l1u5fosicy&gFdipff{dW>C;FoF=2)nml;jeE8GzFd)=;#=zf9d9acOH)Dg)qp zLFuW>=pExJjx?zyaoWZtV(HWR~bO zjX9`lqaF*hf$>QFJr*UD%k?GLHERXI@Fn$k;G;&L?-G;Z+O}PRY3Ud(0w)0bE}=@W zoEU|S0DgSW0s}x|howyHPMsvdn<7jy;1VoYfNPTB8Mmt~{_~nLI5jX+q|!u(=fz{u zUt^~QG#_cF3C!9FlmRMOi)K9KEzQ-C;qsusf~2fEtAxB;+4qNDS+qaQ`J~POOE6lB z12?-*dhHwCieAn78bc)808{SUW>wr_ge*zD1A5;6jdT;GOd^t9W0TQ$DZ1}!2aYDsw0%;Q0IT+iwt1*hebe=tXb|u$mCVaAR(my(utF~x zx)e{d-Xu2*ef(}e>Tp3wq>HbKP3y~%i z{!ldUhJ0P)#R$7P$*<99plf^%KCm3)cvd`(s{n3N*(&bhN3GP8(H@&*q+&LUw<*54 zd&btr_qObWO-Gga$*qR%J8y4#(&S^7%yRk54CRw^JTuD5A)U7A+qp~%sFeYfl&yeG z-c{qV;Vf9v-^#~Ry_$%71>)d4rCC!?bZxu}er$oCPPrmiQ9bn6yshPiEpMDftpRBo z24SQ_vbR=85lJaIn8^q&1+Jb~BiqvaM!CG=$a`9gl7s603zBe4R{z?JPby*kxbxUl z7lRRr)lxE_lRc(}Y|5Ivj4wpKh807as0a3H!1|0Ob<05>G;6bkw#xz8hl1`Mrk5MY zxZ!gSO!WkFMauy&Lp2b1VtfS>B{vv2dE+UTl{K)aHuz^a6aAZKcZynsek=%?>iIU8 zg(`VRy@|3kvu*0Rz~>g7lxVqd{S?E;1G(yAmTp>ll=&FEilVeFD05tS?`5>GlD32g zeZjJfJDZx!`nts!`zez$QqAqZaP;mJZ8Yo>O3^KrZd33^__movqSuKmy>vB*u7_4+iD7PHz}X61wlZ3ArXz4J?Nu{>AbHRa4UoAYvLvpK6=q2s)OHx`us zZ5#Wo3X~?xVqoEinFcXp@7kH^zn7U#?b}+3ikUHhm{E%+Zf*Nlv0o9sb95x-RcT&t zQEgsQ$+1=6D?FaOTzHNA7gU|fhiK#7!VKI+7zLJS^^_0{q93TvE z(%h%)HL$1IlQ}Tbl0AOvwzfry>eq=$UDmPe4WrJ_O%`)Hy=}6`^qNd#EXeleM5%kF z&Zo3e%S=Jr-vcB@`VsiY?e}F-FkyST2hjBdsw0!iakH60ABxj_{fH|S-^f+4HC4u? zjM(Pb)|owpu9V!pr#uR@si@(}X4y9PhWP}vx4CBoR4I_m-gFyk)Mn zsM%4}>p?ZnPWo7lIGCy`brtoMpnb+uWDx2f1 zyJPNz@ngh?)F3UrAQW^rrJH>w3X&H-Uoxbe@x`XcZlA@HDqBB0HG$B6Dq`(!wJcs7 zd_LjDXsPbfCF&>SAuZ?)>d!%*uIlH1$M_va>pRmHdtfO-KU_aum4b>cl25?X2@r*? z6I2OR0i~i?++T=^s!xd-0(|ncr$sYO&#kK`rJ`T+aZri;rt7UfubU(bhneY#lu75= z@9bw|iK##j*^&%~+z>8ct#M-YKnUSzJM4JaW#MFa{ov<*{O&9NklA(IvP9(1^@GAp zw(BewVn`w>1l-ATrrW#zm0=SA(;r1$(XO_0Qlk*Z4esd^f;U0m40G8ewr;m~Gam>7 zw*H>jlo7^Up>QZLZPVr(dYn}+!gzyOV7bPVy)lQ6fQ?51Y&ODpb`BEgbS$N4ZfuRK- zakwiPE0Fh%WYDl9gP--^TAGPW~0XJ4>+v;<&9j-^8Jve0o zX*9%_UcHUF9~SHoNinA{kJ$g7;wTZoHwBR?q)UExHCzeZz^i<^?z@yPt1$4v zpX?$KeUd)a+rd4SRI65&$+wLT*tKRmtizT4(5(EXV3$qF-LAQYOU5d0MyrtQc*g0B ze}{;-e*WwGhQH0kYO?aT2E<5}>Xd@5zi5hG9qW|D*{l(3Uw5x-N_3$&m1QXVYnLqT z$tRpoO@BWs(=nV0@R|M@!P}@h&x71S5K6wV#B^bEF>69qUlQUWfu#>Lc*_)C{Xk{O zC4jiZrwLw7N}RKC-%~sz#D-=&c*s4Q=HP0{OBY_cswvji$MF`N5W6lY)u&|({?V_z zf02&U^%jcFYbyMGYCngaS;9{=%Jfi+ed(cV9X6v37noVM9B$G)jWom30%~U2*TMNE zXvT)&BDM`bd&BUCWK%L8&7)$$*peOa*uw<@*wC84Ca%cvkpB8Yl0tI(G~~WA`xYOv zhb9;p=|3B482lrxt+3Dk%6W-szTIHGV?09c*C}`S<~~uuuu9kaFN4e