Tududi: add task tracke application
Linting / YAML Lint (push) Has been cancelled
Linting / Ansible Lint (push) Has been cancelled

This commit is contained in:
av
2026-07-04 16:56:01 +03:00
parent 8ec0327851
commit 5cd66d01bc
11 changed files with 846 additions and 162 deletions
+2
View File
@@ -75,6 +75,8 @@ uv run ansible-galaxy install --role-file requirements.yml
- `playbook-wanderer.yml` — пешие маршруты.
- `playbook-remembos.yml` — интервальное повторение.
- `playbook-tuwunel.yml` — Matrix-сервер (Tuwunel) с federation-делегацией на apex-домен.
- `playbook-tududi.yml` — планировщик задач (SQLite, OIDC через Authelia, AI-фичи через Bifrost).
- `playbook-bifrost.yml` — LLM-шлюз (OpenAI-совместимый) для AI-фич других приложений, провайдер DeepSeek.
### Агрегатные и служебные
+208
View File
@@ -0,0 +1,208 @@
# Общие Ansible-роли между серверными репозиториями
## Статус
Черновик. Подход обсуждён, реализация не начата.
## Контекст
Серверы управляются изолированными Ansible-репозиториями: по одному на
сервер (pet-project-server, buckland, далее — новый торрент-бокс).
Кастомные роли (`owner`, `eget`, `secrets`) концептуально общие, но
физически продублированы, и копии дрейфуют: `eget` разошёлся
косметически, `owner` в buckland превратился в мёртвый код, пока
актуальная версия развивалась в pet-project-server.
Нужен способ держать роли общими между репозиториями, при этом:
- правка роли применяется мгновенно — без петли commit → push →
install;
- каждый репозиторий остаётся самодостаточным: клонируется и
деплоится без внешних зависимостей, CI (yamllint + ansible-lint)
работает как сейчас;
- обновление роли на конкретном сервере — явное решение, а не
незаметный сайд-эффект.
## Осознанный выбор для pets-серверов
Это решение спроектировано под подход «pets, not cattle» и не
претендует на масштаб. Серверы — питомцы: у каждого своё железо, свой
набор приложений и свой темп жизни. Из этого следуют принципы,
противоположные командной инженерии:
- дублирование допустимо, принудительная консистентность — нет;
- каждый сервер принимает обновление общей роли явно, в свой момент,
через видимый дифф в собственной истории git;
- никакой инфраструктуры публикации (registry, версии, релизы) —
потребитель ролей один человек, который сам же их и пишет.
Если серверов станет много или появятся другие потребители — решение
нужно пересмотреть (см. «Путь миграции»).
## Решение
Вендоринг копий плюс локальная синхронизация через файловую систему.
Каноническая версия ролей живёт в отдельной директории
`~/projects/private/ansible-shared/`. Каждый серверный репозиторий
держит свою закоммиченную копию нужных ролей в `roles/` и
синхронизируется с каноном командами invoke (rsync, git не участвует).
```
~/projects/private/
ansible-shared/
roles/
owner/
eget/
secrets/
pet-project-server/
roles/owner/ # закоммиченная копия
roles/eget/
roles/secrets/
buckland/
roles/eget/ # подписан только на eget
<новый сервер>/
roles/...
```
Ключевое свойство: «пином версии» служит сама закоммиченная копия.
Репозиторий всегда деплоит ровно то, что лежит в его git; обновление
роли — это `roles-pull` + осознанный коммит с читаемым диффом.
### Подписка
Каждый репозиторий объявляет в `tasks.py` список ролей, которые он
синхронизирует:
```python
SYNCED_ROLES = ["owner", "eget", "secrets"]
```
Подписка явная и пер-серверная: buckland может не подписываться на
`owner`, пока живёт в старой архитектуре (всё под `primary_user`).
Роли вне списка синхронизация не трогает — репозиторий может держать
и сугубо локальные роли.
### Команды
Три задачи invoke, одинаковый блок (~30 строк) в `tasks.py` каждого
репозитория:
- `inv roles-pull [-- <role>]` — канон → репозиторий. Для каждой роли
из подписки (или одной указанной):
`rsync -a --delete <shared>/roles/<role>/ roles/<role>/`.
Флаг `--delete` обязателен: удалённые в каноне файлы должны исчезать
из копии, иначе дрейф через «хвосты».
- `inv roles-push [-- <role>]` — репозиторий → канон, тот же rsync в
обратную сторону.
- `inv roles-status``diff -r -q` каждой подписанной роли против
канона; выводит `ok` / `differs` / `missing in shared` по ролям,
ненулевой код выхода при расхождении.
Защита от ошибок:
- обе команды печатают список затронутых файлов (`rsync -i`) — операция
видна, а не молчалива;
- `roles-push` при отличающемся каноне показывает дифф и просит
подтверждение (защита от затирания более свежего канона устаревшей
копией; типовой случай — правили роль в двух репозиториях
параллельно);
- `roles-pull` подтверждения не требует: затирается рабочая копия, её
состояние защищено git — `git diff` покажет, что приехало, и любое
изменение можно откатить до коммита.
### Рабочий цикл
Правка роли:
1. Роль правится прямо в том репозитории, где идёт работа, — тестировать
её всё равно можно только запуском реального плейбука этого сервера.
Петля правка → запуск нулевая.
2. Когда правка устоялась: `inv roles-push` и коммит в серверном
репозитории.
3. В остальных репозиториях — `inv roles-pull`, когда их сервер готов
принять обновление. Дифф виден в `git diff`, коммитится в историю
этого репозитория.
Новый сервер:
1. Создать репозиторий, объявить `SYNCED_ROLES`.
2. `inv roles-pull` — роли приезжают из канона, коммитятся.
Новая общая роль: написать в любом репозитории, добавить в его
`SYNCED_ROLES`, `inv roles-push`; остальные подписываются по мере
надобности.
### Предохранитель в lefthook
Дрейф неизбежен (синхронизация — дисциплина, не механизм), поэтому он
должен быть видимым. В pre-commit каждого репозитория — проверка: если
в staged-файлах есть `roles/<подписанная роль>/` и роль отличается от
канона, печатается предупреждение «роль X отличается от ansible-shared,
не забудь roles-push / roles-pull». Именно предупреждение, не
блокировка: коммит локальной правки до push в канон — нормальный
рабочий момент. Если директории `ansible-shared` нет на машине,
проверка молча пропускается (CI, чужая машина).
### Статус ansible-shared
`ansible-shared` — рабочая директория, а не репозиторий-сервис. Можно
сделать её git-репозиторием для бэкапа и истории канона, но этот git
не стоит в рабочей петле: коммиты туда делаются когда угодно и ничего
не блокируют. Серверные репозитории не знают о её git-статусе — они
видят только файлы.
## Отвергнутые альтернативы
- **Общий `roles_path` в ansible.cfg** (`roles_path =
./roles:../ansible-shared/roles`). Нулевая синхронизация, но
репозитории теряют самодостаточность (CI и клон на другой машине
ломаются), а главное — правка роли молча применяется ко всем серверам
при следующем запуске. Для pets-подхода это анти-свойство.
- **Симлинки на общую директорию, закоммиченные в git.** Та же
семантика плюс ломкость: валидны только при конкретной раскладке
директорий на диске.
- **Git submodule / subtree.** Решают задачу для команд ценой петли
commit → push → update и известной UX-боли; принудительная
консистентность здесь не нужна, а петля — главное, от чего уходим.
- **Galaxy-роли из git-репозитория** (`requirements.yml` с git-URL,
версии тегами). Правильный инструмент при росте масштаба, но сейчас
церемония tag → push → install — это и есть слишком большая петля
обратной связи для одного человека.
- **Монорепозиторий на все серверы.** Отвергнут на уровне организации
проектов: серверы — pets, изолированные репозитории с локальной
историей ценнее общей точки правды.
## Цена и риски
- Синхронизация держится на дисциплине. Забытый `roles-push` — канон
отстал; правка одной роли в двух репозиториях без push — конфликт,
который разрешается руками через дифф. Митигация — предупреждение в
lefthook и `roles-status`; при текущем темпе изменений (единицы в
год) этого достаточно.
- Канон существует в одном экземпляре на одной машине. Потеря машины —
потеря только канона, не ролей: они восстановимы из любой свежей
копии (`roles-push`). Опциональный git в `ansible-shared` закрывает
и это.
- Блок задач в `tasks.py` дублируется по репозиториям и сам может
дрейфовать. Принимаем: это ~30 строк, меняются реже ролей.
## Путь миграции
Если ролей станет десяток, серверов — больше трёх, или появится второй
пользователь, `ansible-shared` уже имеет структуру стандартного
galaxy-источника: повесить теги, перевести репозитории на
`requirements.yml` с git-URL, удалить rsync-задачи. Вендоринг копий
делает переход безопасным — в любой момент репозитории самодостаточны.
## План внедрения
1. Создать `~/projects/private/ansible-shared/roles/` из актуальных
ролей pet-project-server (`owner`, `eget`, `secrets`).
2. Добавить задачи `roles-pull` / `roles-push` / `roles-status` в
`tasks.py` pet-project-server; объявить подписку на все три роли.
3. То же в buckland; подписка только на `eget` (его копия сначала
выравнивается через `roles-pull` — дифф косметический, стиль
кавычек). Мёртвую `roles/owner` удалить.
4. Предупреждение о дрейфе в lefthook обоих репозиториев.
5. Новый сервер с самого начала строится на подписке.
+35
View File
@@ -732,6 +732,10 @@ access_control:
subject: "group:admins"
policy: "two_factor"
- domain: "bifrost.vakhrushev.me"
subject: "group:admins"
policy: "two_factor"
- domain: "wanderbase.vakhrushev.me"
subject: "group:admins"
policy: "two_factor"
@@ -1343,6 +1347,18 @@ identity_providers:
## Authorization Policies which can be utilized by clients. The 'policy_name' is an arbitrary value that you pick
## which is utilized as the value for the 'authorization_policy' on the client.
## Claims Policies (Authelia 4.39+): по умолчанию Authelia НЕ кладёт email/
## профильные claims в ID Token — только в UserInfo. tududi читает claims
## ТОЛЬКО из ID Token (userinfo не вызывает), поэтому копируем их в id_token
## явно (при условии, что соответствующий scope выдан клиенту).
claims_policies:
tududi_claims:
id_token:
- "email"
- "email_verified"
- "name"
- "preferred_username"
authorization_policies:
outline_policy:
rules:
@@ -1401,6 +1417,25 @@ identity_providers:
userinfo_signed_response_alg: "none"
token_endpoint_auth_method: "client_secret_basic"
- client_name: "Tududi"
client_id: "{{ oidc__tududi__client_id }}"
client_secret: "{{ oidc__tududi__client_secret }}"
redirect_uris:
- "https://tududi.vakhrushev.me/api/oidc/callback/authelia"
scopes:
- "openid"
- "profile"
- "email"
response_types:
- "code"
grant_types:
- "authorization_code"
access_token_signed_response_alg: "none"
userinfo_signed_response_alg: "none"
token_endpoint_auth_method: "client_secret_basic"
authorization_policy: "one_factor"
claims_policy: "tududi_claims"
- client_name: "Wakapi"
client_id: "{{ oidc__wakapi__client_id }}"
client_secret: "{{ oidc__wakapi__client_secret }}"
+71
View File
@@ -0,0 +1,71 @@
{# Конфиг Bifrost ИСТОЧНИК ПРАВДЫ. Рендерится в config_dir/config.json (app-dir
Bifrost смонтирован на /app/data), Bifrost читает его при старте.
source_of_truth=config.json: на каждом старте секции этого файла авторитетно
перезаписывают состояние в config.db (git выигрывает у правок через UI).
Раскладка каталогов (по требованию «конфиги/данные раздельно»):
- config_dir -> /app/data : сам config.json (app-dir Bifrost по умолчанию);
- data_dir -> /app/store : config.db + logs.db (пути заданы явно ниже).
Несекретные настройки (base URL провайдера, имя модели) заданы здесь прямо
строками они больше нигде не переиспользуются. Секреты (ключ DeepSeek,
encryption_key) рендерятся строками из vars/secrets.yml, поэтому готовый
config.json секретный файл (плейбук пишет его на 0600).
Провайдер deepseek заведён как КАСТОМНЫЙ OpenAI-совместимый
(base_provider_type=openai + network_config.base_url) нативного провайдера
deepseek у Bifrost нет. ВАЖНО: Bifrost сам дописывает к base_url путь
/v1/chat/completions, поэтому base_url задаём БЕЗ хвостового /v1.
Роутинг декларативно через governance.routing_rules (требует включённого
config_store). tududi ходит на drop-in эндпоинт /openai и шлёт ЗАХАРДКОЖЕННОЕ
имя модели gpt-4o-mini «голым». Правило ловит его по cel и подменяет на
реальную модель deepseek/deepseek-v4-pro. Фолбэка нет единственный провайдер.
Чтобы сменить бэкенд-модель, правим ТОЛЬКО этот файл, конфиг tududi не трогаем.
Эти Jinja-комментарии в готовый config.json не попадают (на выходе чистый JSON). #}
{
"$schema": "https://www.getbifrost.ai/schema",
"encryption_key": {{ bifrost_encryption_key | to_json }},
"client": {
"enable_logging": true
},
"providers": {
"deepseek": {
"keys": [
{ "name": "deepseek-primary", "value": {{ bifrost_deepseek_api_key | to_json }}, "models": ["*"], "weight": 1.0 }
],
"network_config": { "base_url": "https://api.deepseek.com", "max_retries": 2 },
"custom_provider_config": {
"base_provider_type": "openai",
"allowed_requests": { "chat_completion": true, "chat_completion_stream": true }
}
}
},
"config_store": {
"enabled": true,
"type": "sqlite",
"config": { "path": "/app/store/config.db" },
"source_of_truth": "config.json"
},
"logs_store": {
"enabled": true,
"type": "sqlite",
"config": { "path": "/app/store/logs.db" }
},
"governance": {
"routing_rules": [
{
"id": "tududi-gpt4omini-to-deepseek-pro",
"name": "Tududi gpt-4o-mini -> DeepSeek v4 Pro",
"enabled": true,
"cel_expression": "model == 'gpt-4o-mini'",
"targets": [
{ "provider": "deepseek", "model": "deepseek-v4-pro", "weight": 1 }
],
"scope": "global",
"priority": 0
}
]
}
}
+33
View File
@@ -0,0 +1,33 @@
# Bifrost — LLM-gateway (github.com/maximhq/bifrost). Единый OpenAI-совместимый
# шлюз для AI-фич других приложений (сейчас — tududi). За ним провайдер DeepSeek.
# Клиенты ходят в него по имени bifrost_app:8080 в общей web_proxy_network и про
# провайдера/модель ничего не знают — роутинг решает config.json.
# See versions: https://hub.docker.com/r/maximhq/bifrost/tags
#
# В контейнере Bifrost слушает 8080 (дефолт). Наружу host-порт НЕ пробрасываем:
# веб-UI (Dashboard + Logs) и API доступны только через Caddy
# (bifrost.vakhrushev.me, за Authelia forward_auth) и внутри web_proxy_network.
#
# App-dir Bifrost по умолчанию — /app/data; туда монтируется config_dir с
# config.json (источник правды, с секретами, 0600). SQLite-сторы config.db /
# logs.db вынесены в data_dir -> /app/store (пути заданы явно в config.json).
services:
bifrost_app:
image: maximhq/bifrost:v1.6.2 # проверь актуальный стабильный тег в releases
container_name: bifrost_app
restart: unless-stopped
user: "{{ owner_create_result.uid }}:{{ owner_create_result.group }}"
networks:
- "web_proxy_network"
volumes:
# App-dir: config.json (рендерит плейбук).
- "{{ config_dir }}:/app/data"
# Сторы, создаёт и держит сам Bifrost — бекапить не нужно (config.db
# восстанавливается из config.json, logs.db — просто логи запросов).
- "{{ data_dir }}:/app/store"
networks:
web_proxy_network:
external: true
+25
View File
@@ -152,6 +152,17 @@ memos.vakhrushev.me {
}
# tududi сам логинит пользователей через OIDC (Authelia), поэтому здесь без
# forward_auth — иначе двойная аутентификация.
tududi.vakhrushev.me {
tls anwinged@ya.ru
import access_log
reverse_proxy {
to tududi_app:3002
}
}
remembos.vakhrushev.me {
tls anwinged@ya.ru
import access_log
@@ -233,3 +244,17 @@ goaccess.vakhrushev.me {
}
# Bifrost — внутренний LLM-шлюз. Веб-UI (дашборд + LLM-логи + ключи) закрыт за
# Authelia (2FA). Сам API tududi дёргает не через Caddy, а по web_proxy_network.
bifrost.vakhrushev.me {
tls anwinged@ya.ru
import access_log
forward_auth authelia_app:9091 {
uri /api/authz/forward-auth
copy_headers Remote-User Remote-Groups Remote-Email Remote-Name
}
reverse_proxy bifrost_app:8080
}
+75
View File
@@ -0,0 +1,75 @@
# Tududi — планировщик задач (github.com/chrisvel/tududi).
# See versions: https://hub.docker.com/r/chrisvel/tududi/tags
#
# Образ стартует entrypoint'ом от root, chown'ит /app/backend на PUID:PGID и
# сбрасывает права через su-exec — поэтому НЕ задаём `user:`, только PUID/PGID
# (совпадают с системным пользователем tududi, владельцем смонтированных каталогов).
#
# Внутренний порт — 3002 (frontend отдаётся тем же backend'ом). Caddy проксирует
# tududi.vakhrushev.me -> tududi_app:3002. Аутентификация — собственный OIDC-логин
# tududi к Authelia (НЕ forward_auth), поэтому в Caddy домен открыт.
services:
tududi_app:
image: chrisvel/tududi:1.1.1
container_name: tududi_app
restart: unless-stopped
networks:
- "web_proxy_network"
volumes:
- "{{ db_dir }}:/app/backend/db"
- "{{ uploads_dir }}:/app/backend/uploads"
environment:
- PUID={{ app_owner_uid }}
- PGID={{ app_owner_gid }}
- NODE_ENV=production
- PORT=3002
- DB_FILE=db/production.sqlite3
- TUDUDI_UPLOAD_PATH=/app/backend/uploads
- BASE_URL=https://tududi.vakhrushev.me
- TUDUDI_ALLOWED_ORIGINS=https://tududi.vakhrushev.me
- TUDUDI_TRUST_PROXY=true
- TUDUDI_SESSION_SECRET={{ tududi_session_secret }}
# Чистый OIDC: сид-админ не создаётся (TUDUDI_USER_EMAIL/PASSWORD не заданы),
# первый вход через Authelia сам заводит пользователя и выдаёт ему admin по
# OIDC_ADMIN_EMAIL_DOMAINS. Локальный парольный вход выключен.
- PASSWORD_AUTH_ENABLED=false
# OIDC (Authelia). Redirect URI регистрируется в Authelia как
# https://tududi.vakhrushev.me/api/oidc/callback/{OIDC_PROVIDER_SLUG}.
- OIDC_ENABLED=true
- OIDC_PROVIDER_NAME=Authelia
- OIDC_PROVIDER_SLUG=authelia
- OIDC_ISSUER_URL=https://auth.vakhrushev.me
- OIDC_CLIENT_ID={{ tududi_oidc_client_id }}
- OIDC_CLIENT_SECRET={{ tududi_oidc_client_secret }}
- OIDC_SCOPE=openid profile email
- OIDC_AUTO_PROVISION=true
# Домен email, чьим пользователям при auto-provision выдаётся роль админа
# (должен совпадать с доменом email в id_token от Authelia).
- OIDC_ADMIN_EMAIL_DOMAINS=vakhrushev.me
# Почта (Yandex Cloud Postbox, те же секреты, что у outline/authelia).
- ENABLE_EMAIL=true
- EMAIL_SMTP_HOST={{ postbox_host }}
- EMAIL_SMTP_PORT={{ postbox_port }}
- EMAIL_SMTP_SECURE=false
- EMAIL_SMTP_USERNAME={{ postbox_user }}
- EMAIL_SMTP_PASSWORD={{ postbox_pass }}
- EMAIL_FROM_ADDRESS=tududi@vakhrushev.me
- EMAIL_FROM_NAME=Tududi
# AI-фичи через Bifrost (drop-in OpenAI-эндпоинт в общей сети — НЕ localhost,
# это сам контейнер). Модель у tududi захардкожена как gpt-4o-mini; Bifrost
# по governance-правилу роутит её на deepseek-v4-pro. openai-node сам
# подхватывает OPENAI_BASE_URL.
- OPENAI_API_KEY={{ tududi_openai_api_key }}
- OPENAI_BASE_URL=http://bifrost_app:8080/openai
networks:
web_proxy_network:
external: true
+6
View File
@@ -43,6 +43,12 @@
- name: "Configure tuwunel"
ansible.builtin.import_playbook: playbook-tuwunel.yml
- name: "Configure bifrost"
ansible.builtin.import_playbook: playbook-bifrost.yml
- name: "Configure tududi"
ansible.builtin.import_playbook: playbook-tududi.yml
#
- name: "Configure homepage"
+79
View File
@@ -0,0 +1,79 @@
---
- name: "Configure bifrost application"
hosts: all
vars_files:
- vars/secrets.yml
- vars/vars.yml
vars:
app_name: "bifrost"
app_user: "{{ app_name }}"
app_owner_uid: 1107
app_owner_gid: 1107
base_dir: "{{ (application_dir, app_name) | path_join }}"
config_dir: "{{ (base_dir, 'config') | path_join }}"
data_dir: "{{ (base_dir, 'data') | path_join }}"
config_file: "{{ (config_dir, 'config.json') | path_join }}"
tasks:
- name: "Create user and environment"
ansible.builtin.import_role:
name: owner
vars:
owner_name: "{{ app_user }}"
owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"]
- name: "Create application internal directories"
ansible.builtin.file:
path: "{{ item }}"
state: "directory"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0750"
loop:
- "{{ base_dir }}"
- "{{ config_dir }}"
- "{{ data_dir }}"
- name: "Copy bifrost config (source of truth, secret)"
ansible.builtin.template:
src: "./files/{{ app_name }}/config.template.json"
dest: "{{ config_file }}"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0600"
register: config_file_result
- name: "Copy docker compose file"
ansible.builtin.template:
src: "./files/{{ app_name }}/docker-compose.template.yml"
dest: "{{ base_dir }}/docker-compose.yml"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0640"
register: docker_compose_file_result
- name: "Run application with docker compose"
community.docker.docker_compose_v2:
project_src: "{{ base_dir }}"
state: "present"
remove_orphans: true
tags:
- run-app
# Bifrost читает config.json только при старте. Если изменился лишь конфиг
# (а compose — нет), `state: present` не пересоздаёт контейнер, и правки
# (ротация ключа, смена модели/правил) не подхватятся — нужен явный рестарт.
- name: "Restart docker compose services if config changed but not docker-compose.yml"
community.docker.docker_compose_v2:
project_src: "{{ base_dir }}"
state: "restarted"
when:
- config_file_result.changed
- not docker_compose_file_result.changed
tags:
- run-app
+71
View File
@@ -0,0 +1,71 @@
---
- name: "Configure tududi application"
hosts: all
vars_files:
- vars/secrets.yml
- vars/vars.yml
vars:
app_name: "tududi"
app_user: "{{ app_name }}"
app_owner_uid: 1108
app_owner_gid: 1108
base_dir: "{{ (application_dir, app_name) | path_join }}"
db_dir: "{{ (base_dir, 'db') | path_join }}"
uploads_dir: "{{ (base_dir, 'uploads') | path_join }}"
tasks:
- name: "Create user and environment"
ansible.builtin.import_role:
name: owner
vars:
owner_name: "{{ app_user }}"
owner_uid: "{{ app_owner_uid }}"
owner_gid: "{{ app_owner_gid }}"
owner_extra_groups: ["docker"]
- name: "Create application internal directories"
ansible.builtin.file:
path: "{{ item }}"
state: "directory"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0750"
loop:
- "{{ base_dir }}"
- "{{ db_dir }}"
- "{{ uploads_dir }}"
# SQLite-база и загрузки бэкапятся restic'ом напрямую (без gobackup-дампа):
# ночной прогон одного пользователя -> вероятность записи в момент чтения
# ничтожна, а retention (90 дневных / 36 месячных) страхует от редкого
# несогласованного снимка.
- name: "Create backup targets file"
ansible.builtin.lineinfile:
path: "{{ base_dir }}/backup-targets"
line: "{{ item }}"
create: true
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0750"
loop:
- "{{ db_dir }}"
- "{{ uploads_dir }}"
- name: "Copy docker compose file"
ansible.builtin.template:
src: "./files/{{ app_name }}/docker-compose.template.yml"
dest: "{{ base_dir }}/docker-compose.yml"
owner: "{{ app_user }}"
group: "{{ app_user }}"
mode: "0640"
- name: "Run application with docker compose"
community.docker.docker_compose_v2:
project_src: "{{ base_dir }}"
state: "present"
remove_orphans: true
tags:
- run-app
+241 -162
View File
@@ -1,163 +1,242 @@
$ANSIBLE_VAULT;1.1;AES256
37316466386337633433633632333065613137643963356437653030653433353464666636383238
6131616231356633383839653530666333343666316363630a323736656238336633336363656634
35633031623730613966343533393033386538346162333061613135353063323533336135376337
6262346462636361370a356336616330323738383535356238383238656366303833313137613461
39613266616632376433636263653332363661373832316538343530353963363762356534373264
38316134643632386564626662616235316563343833646164336439623035303838316630393030
32636566663835626362666235363432316430633139333935383031303564353633323835616530
39353336366634356530393339633836613563666435323533323731666666656339313431613161
63386639663635393339643965643664303130633234333130663336623738313665366630316539
39373930316434636261646431633666653066373632396136313639633266383036643739323936
63343836666263623966333364616230393338386439616264616437373739373366326430363038
61333237323139336236333366666164313364366663333335646139353061323362313465613865
32376234323936336466356239666162323330323133616166333733613566636635303965316665
61616135333964613862303432323032313533353132303762396539323033636636643539663334
36663866313130303531333961373337316462376564396639656265393663373230623937376337
34656339323864633462633030333730613765653631636233356535643333356661333331303935
33646364636434336162356535393162353533636262333735616463623432653933316230306261
38353766363130333963613830643735663930353162343665323435613435326638363162356438
35666666343165323561396336646264613633323066613665346535313165306435666463353030
33626661326536646264343531653834616563316533386539656431353165633161666366386433
66623662303636393732643330333664373935313932306232306433653038623461393030306562
66663637636534393631633939363037303331383166343561373163666263323563313636326538
31656132376636653336353666316639623230613563333363653965333435323231303161333231
65353539633132373036346233626533356139373938646130376437626532383864623138613463
65373530343563613633323439636638316330663639306438323761306238616539353262326166
61323239666661366665343736323834633065306237643538383238616563353366343936663835
66313066653832636534346136373962653136303435623365376235626463366633623561313231
66343861363336303136666136363531386166316631643565383835333261356237373736386638
32653565623034303939383134613235353237393035336662303432653734636630373235303733
37303234333764303863323733356363353864323038393762373339303239656566363966363832
66373965333362383031636330613637656436653365303930316233396431323338636239333930
62353637333562343534663964636235623363313463613135373639653536636339376435313662
31616465643730336562373463353965616263366137316430303666346532633735333661616337
31303361633437663065326666633437383534366639396661623336643565383336623232666563
64376136303637393330363263666631336461396163373932346138653362326163666331336434
66356232666439633132363537626436613064663562333536303238313834303666363830353632
33633633373738383165363135323232313735393335646238326261643765613061623264343261
37663531396165653838346431666534636365373563366636383165376137396537353031653261
62393437633735366433613166356337303564313538633933633465363331316332656262303439
62383535383666383535326538356431313365663937313065363138636336666631643737343634
61396664383539316261386235643430383436396638303737366632306538636133666239616162
33373066623365353832666139633436343664333434393933646265323132616665623938643061
32663763316639333036343333373632633431373038326666323036333664333332313731313865
33363638616661363763313263623230663735383834353835613861373063666538363764333433
65316333643937366632313335643364333736633936316132313534373862646365323037363963
63353237373730623533633961323038616532643034643939353034383036326461383566346230
61363561666265393536323164323462356237373764306632383034646665613130373030383336
39353235353036323964356139363530636364346666663331643736306361653966636164613830
65353664643733383737663766613965616334356561376538623763613331343334613532386562
33623564636233663234313631623734333934303439383662336665623936643039623039303962
63336433333766376130356339303239626536383364373063663435653236386139613366323938
34653161313061333837373164653238663539326562633237616134623366643039373033353331
31633436323463623661656636383964643362613130336537653731646239313365633662656132
35393262613830323964346662666664346664316662623865663236643065333533313135643063
37343461316564316666343465303539383463613663306136653465383336353232366238316332
31313837323533313863333565643762653362633231333436396238666562363139626336666532
38326331653736393031636264316330643532353862366235613334383266613238326431343066
33666566613061363465376637636636626432383132636330343736653034643965396566373064
37303830393531383131646161613335666631633535383739626130366464313930343262393533
66373137303531623663643339643233373737303831323735623561656165383034373131386639
32333838663264366661616262373439323433386538373261373465343163326438623962333566
30376463646631663330366364626133386131313261396434656336636566323234633864323731
61393864666537656338646462353764356131303836333762333932643130346439356432323231
65373866393263313236353937363031366661666664346362313466356366663336383761623637
66343938303839663630363562353634353938643638373335636530323430656336303963666662
63643031396133646531316162323836616461373863643463663132383764363034663562363365
37383234663939646539643562376532323936616264326533333637323633373330643162623537
63626138616165343938643237313562616234303665303635653830343735326661666235643333
36623333343365613161663663356566656533363736313231336536386339326336373730643630
61386139313064376161303066323162316564313761303734376663653464323138303339323763
35373930366262373936356464613731396136613634663562373737663739613562373333386265
35323161316633373430633833663266633030613430613635356135393161653836366464326236
63373738366661333964623962363035636131656539613139373138643839643138373564333430
34623932396134343762393334373033663132306636633864643235373861386633366639316566
66623439613730353237366366663161363030646663323836653638383330323164333461666334
62333662643631373361643263646537323239643234346333333261623933626166333630356662
61333362313230343134343331303031303634383335313339303363383930373136333336313062
39626532646365663734313462343766333962333266363965373630363831626633623830633733
37633666663361343639366663313237633161353062396337393138613261383935353730353932
32383066386636346433306662326130633662363135313331383530346632356333363238383266
61303138636664303637326137376638333266343132356237383432613238333632356163643232
32333966383164343064643934333765386136323736303138326635663561343964393934316433
37663763653034363039636436346630303664346237616433376462303463633930383430646430
38343762663635373033373135326361393935386566363031366630366561663431333461663332
62393538306163386139656431353235343265373033663530306163316464373432353661343439
35356634323966646263613937663234613431316333656330343765316163626339376430343064
30363335633337623139343464656431353738623230633137633130353430373435303963663333
66646563626336393538613134636162643864316163626361663663313131343063643336396532
36333336386130653537653637653163343031376436383763386163376336623837336365616530
66386262363633353465663664326537343037383838333863366662306131303364626538623763
30376132613861636539393138326131356333613837336365383862383362303233633333396238
34663163363234363564336338656366313931353262303032343334303864386337356437633361
39383366386334303039313439376236663533633035326662626466333539626261643466653966
66383862663635346539396139323138613264373436653338653136366135613962373562373932
62316134636636373264623365613636373132396433373630643531633536333130646130373963
33356538316334336236346532356263363933633933376165666563313161343638656365373137
61656438313063363661316537336538646233326564633934303766383738343130373737653961
32326462626435343363326366663364376431613837646365383933396437383138333665383832
33376563363466333637656233643137333636326535653335393931653063623937373836313863
30363238356466363461313633306565646336633366346461333032643635366336646365643761
37363433623037333838663731376663306134363665383835333164643131646632393639363538
32656539643461653861376436396130316166616265336433356663343235333763636564326464
32663530333030306130366532636439353264303137613366353266363664346433666565303135
34343835623936643631643166636339306231323138356565373663636665303264653161613539
65336230633035336464373431663130383131376430306136333634376435393933623238653136
61633138653339336631313932353264366334616630363464373131376538303630663566653061
30386665383864383838613835613337633937616437376363626263376634643061393937653430
38626539383634616536396665396262633666376438363930383635353936623534353131666333
31616536383662663666333265623032613936383063303636366362346337333337633739393665
62306639376466636161333233656239313964373239336134646434613166646534656466356431
36626162383866643633323938646130366261373366353764646234653236323166363463366435
65636137643639353039616138383265633832373864643438366333393031326363333461376138
65303838626435656634626236663338316632613664646463306561663532353062643637663866
32663236633630383365663235313337373837396531326532333632343736343035643733333262
61633532323330623338393838626333323932376630316239303335313765343039313332316336
32303638356539333136353163396536313830333264613266303139376465613166363730343635
37623532356135613831363137356238373863653933366532346532363130386363616133373764
62353066376631343934356337326465613436363361646431373962656639393566323731316136
30313730386438663261636562643061333664303636373732663464356530336333383739313566
35616566663065313732656532346330636335383664383061323131306530623632353132326432
34313365396430396338363063376534363332306432313531303261353736353138383733346363
37373063666630633632613039353763626165613038616464363333383662356363373133623261
61316130393165633434623334616239666137653831636131393732363330363039306666346363
38353832356138383732306533646133663865663062663862663232323038383532616230386265
38626535313931386533613936343637373064666663336436326464313834333334366264336130
37346438383739313735636139343638373063306162313230366364316136333137653939313032
63303035663465653062613764333434323234323332316365333165383533396430316537623164
37356138623233656436663738393966346662646339626137663533616234653535663663383738
37323564653664653530653533396332333664636261306339663830373730613832363565613363
63333635646339656535366365633638393164663562633562383162373932306233313164386331
37613036323664316237336130366138333863316563666337333464383932353363306231336338
65393537636162316637343262313333386532616632376563333039386131336230316266396265
34626630633038623462346365316438316238663163346136663436393736623464643961303339
35613565393633373861626239663563336633643565383833373632353737393838666161633462
36316634313530663032366430656131373838616161613265633936633930646137646661653738
31356266666162643736363538646464363164633434643161643239333336326262386135633165
65656334313632336138393364356664386237313936626333616638663666396530623464613665
36636537396565343864353465633638613439663663633766356331333036313130383432366136
66383333643938613831386631386537353166653561376161633431306531343033646233343731
34363164316463633133616230316433373966343031353733343762643032356264646631323236
33393231333330303339613634643333346134643035653330343230663035343230626465386339
36323030633037363462343130343333313538393163393263633339323133656239366161323837
30383231323061323538393063316666303534663664623464383634613834326237363532353165
30356561643738373537343831663461623839303363366266356663666664623030333936656436
39616261626464343039623161343234626566623537396264366439666438343739306361383666
33326433383830313933383030393235306261323462323265623862363861653331643736363336
36373561363566346232613631653464613034653438343462383432643962626665303634646230
63656261613463623466393064623534306363663835616138626332356430633165363366343461
66623133666461313934346365623434653433373734376632616435386636613166633935376337
61326437333132306637663436663930396461393131643766643362316434346438316630343833
31343638383339646436363238393634636363303861383266313638623663303732333231316366
37653861663737656631666535383366643265373135396236393936353264613765656534313430
39663431623737346462303035613162393939623239326232326330636562326236323830613238
32303263333864616434316361646162623163306262646131346135613162353833366537666532
39326430306331663736363365386264653634613530376639313336613939363135656638353632
31656430306363396366306539626436356436306233303032623265356337303666363965353964
62373031336438326635386632313365336236303737373832366363333634383835306231623065
62646236643264373036303465663838366565616530306463613563613463323735646430663566
34356263353637386163316639636334616663303935666530396333653633343861393335313966
36383139383835343937393934316330623338646663636466396436353739303464313161376138
61663033623031653361643735366562393431643236636531323061303033313966633838646237
3831653036383565396363663066316432636537376430376165
30616437646339396362313065656236323032383738623765393335333730303638393636326365
6662656665353264383330363464633864313638613762610a643338323866623932636339616137
61396536316165313762643866353336343263386664393930386332323234333831383762323439
3437353835393531350a643032666361666337383562303636373035363533353734656362383165
32306332343230653033663539353431636339343139633162336362383464363137656234303838
36633963613036613932613734613364613835656361333564633833643662653337323837663038
66663265316433646436656235386531303634376464636430386431343765613831333132313666
33383637313939386666363663356162356463613637643431626636366133386638643065633463
33663432336439666438383837313464653233663932326638653664313536373232343263656362
66366234636366326631373466316538653734373537323663646562383730393535383536666538
65336435313136393135306137323833323361633133356437393630316532316366633530616132
33326462323266343139616464653132373034316336333161643032653538613666636562316537
38613031643935383435656365343031343361323364356134626438313433393261383365373832
62616262326362306561396662363266316239646266356661386362333534646238303438326262
32393762383366336334333336663834633834666663656666383264356462346138393936343932
64666131333636316334396562396139623834666432353239366238373266636233636433366539
64613862313764353064303039393463326663333765386334386662326638376163333738636432
61623330313937383830616635616461376334333332363235303861373061353234383533373963
30333966316631616466306533656135313765633033366634663462393330396639333737636132
62343136393262306661646334393036376233303533386261333130653465633238663566356438
64363832356163366264386135363466623031316133626333356230393063636535373139333063
32323736623434346430353532343362643435666439323136383563663066386135643363313765
34333865323635343731303763646639343435626432316363353764373337653266383462633030
33656261626166633366653861663833663739326361666561373039346436613261663138376566
35646137363439363438333435366132383037643135353431623061653739363565393562343965
65353932666361623765343465653735393337636364326263366234303461336662626336336631
39373231646264646364326533333966333631356636333661386262333032613435356165623839
38356639646263376632656436373233613063326433336235363434626666326131353961626566
34373462313265643263346330643535613134366366306539316432653964333332636139396238
36613065346236633331326230623238373533616531393231396134383462346235386265333836
31656566633136636465383537616631353666356465623631306336386136366332303638306635
35666262343964333930653865623461306633633936633066616462366137356262343932376230
30306233633137376264373938383463373835396532306632616539363132356338313966363664
64306363373063323561646439626365363938363936306533626139643637663733353766613237
32396566633337646262383138323365373535663333343537383961316139623437626661643162
31376138356332373066323130333361663765353839356163353439376131333262643637313265
31333432616632396632343135633264333230633963313836346565343562383639306430633338
39636264663633353337323035356461613966343637616238303266303039313463393866663436
66656631356538646362656366626135393338313761366632616230383866666533643366323439
36303538643662303134313838343939616536376531666164663133323933323831343266633835
31613265616136366432396365653164666164373535333532663230383733613538383139396566
37633066396161383239613362336533646337396632653933363831623032616462383164336436
64353930326137306437636264336662343735353737313033316132663132643238616430326333
65336238326638636637656330393937333032303966323036643134656532623136303138313930
61313733336233633764343330633166353030316632613234613634303136396339396264636630
37316434666631376366303430636562326465373139363665363436626131376333653835316530
61366663653239323539396166646163646262623761303939396265333835336634666132396634
35313235653039373937353164313339343562646330643463626533356439613932613766393337
37653835313563633337656639316336323138333863396236636334313730343238613033383164
35643462383635666466613666313836636435616536353939633764636466303065316362643737
65313561343536393835643461643162356536636536356463616232356365346437333934363132
66663233353664366665366365313537633864333134386232323139623637613532343262313838
65343934663033636262653337373231616162366563393238383166646338346233616131336533
35633262356265356534653533663333313532653336333564363764643939616639366563643432
38363163663830636264346535666230363633346430393464643731303665313935366263306635
38623032656262313939343366346233646534616139653533306633326363396262656664326132
32313636366561366230646536323734383739316332613566346165393562396638393365346232
33346339376362393766663664616231626135316332346266633039346239636536383237666334
39646538323262333038643565646134336534303061636661306630643631663737353739623631
30373630343466616332303361626365613237313136336539303464616662376133386135313362
62323630303137396434653965386363353138613031663832373664626633656262646361373330
30376537373034633739313365666537643535306231326235663964373565383432306334383661
66623166396264383735336230303835303366376338633433653761353531336133363438623931
65626237663962666239353339363162663937303936393061633239663362323038353863333061
66616132376135326662663332363661616339363561653631363563323831323136636236326632
31333235626437363561316533613762336563613762653531316233306337356261616563396233
63656232643364383465383638353562313865316335353332656633616566303261653833663666
65303834333561633436633933623234346238336161316631616362386637303665633938396336
33346337343836376534356534373538343235303236333039316538393866323433366564356565
65363630623737633162626265643235623461396163663833636564313333313330613064396437
37303234316466666565613838633663653839353966303163376330323964303238393038383732
30306336306465663766313933643132393636623066623466643130303465353632626538623165
36323839313333376134616131653433316336663363343562336635623337396435663636343633
37306334393439633835646635316437346464393532383535383336356435346266373534393732
31343335626665326430383131333132303239313562356435333633623733323366313833343431
30353466636531653464626462633637306131393037653837383833383664363762363435616436
37373762666662663763613661343935333834316332643439376164316564386564336664363461
32613561306630356437306536666566353533376466613137323830336361633762636433316235
66663166623630653335633963366238343166386361326537666465626238346466326230633961
61633565326565646636393937393034333835643663636464316362366431353166646263623330
38666431646232373163373361353135323862376366666362313235336431333734323264613265
34313330303262323432646634666565343237333431363766376131653138343233303332643636
65316232613734663239646131336230343663353136393262646135663734616134366664373361
63303932393239326330396637353361333337336165663032356533646637363438323633306430
32666266623561343132323730353839316339653464613965376637333132333463303238393838
64313332626633383262633362343465326334343739316462383762633334643465386334646634
36623963386361633139623533333635663663343865323630356566316639353733393137623339
33353363613766653163333633353661616362376130656633323264396162646262376566363437
37616261363234316230656362653562643536366239656261616630376431316532373561653231
61333438353031353639346532393733616561393536363732323039363034353064353138313836
34396564383132373138396466363864323538326538336432313961373663323264343132316537
36656435623232623139626139313032333532356561346531303535303235626466383036353832
35333931633161386662386564396662356432366164663163353539303638653762393761353063
33326666306561363731353435303132326335366334646336353530313264663438346536633030
63373136313430306439613666626239316338646537666236363539656338663238373337323464
32373565666165623062613338616331326232653966373962636639626166396639643961643539
39643732623165653464313861656537373333323566306238356462343462626663616330333830
37376337646635346634663461303363643537363633356339393330323565373266336461386361
31393632386635653137623434663032336631633065323235656232326639326539313463366230
63653235333865333266306162643938643162363766643464653665646131363463326433643662
35346138393433646162366164316331366462346332326137343833313762306635396661346231
32623230373433646237303635343837343063656639313236306563376164386239346262643930
66386465396262353434633937303732653631373766663139306236616130383833383062303338
31613434393138333437646432373531623666363763323564663730393565343930323233643334
66373665386238623066366166616163663362376463646331373838343863643831376165333765
32653561343363666161643762383831356639663737393132616566613230623162616439326562
34313836656136323565343636396530663865366332326436303162386535393736656330326534
61356538643337666432656232613962393139656336373462353636636233363534643137393031
64343965336637663064653563346232323439653437366238353039663932346266626338343038
61353064653730636232666661653830373661303162666539646231373230336362653136643137
65623662303534316530313739333233623865303866623630663136663761666134666663393437
36653331393566353032313036363533383038363937376535626435383939303666316531363266
30353235336333376337393531326661303937353266346164663438633062613166656431663834
33663539343433623564306638366131303032333033656466646134353036666133346236373639
39663136666331613231646639306565306561656439366566653666396133363561643663396632
34316461326562663161633630316531613363616264303036616336313939336465366163366262
31646539363239346637396561306466353338346461626237333633313131316534323661356663
30366434356137333763643937363164393833376261616131336639366231383261333262613637
65646262336439626262353934616530343730353436313232393663363237393034316433663038
31643261663534303639313433626339303562343036303335363539396234333465326538383062
32613266396338646135626462383534323763323835663863646337646139306265633865613933
61623866343636653638373032303732616530326265613366616535376435616266326366386435
39366139613638333237646164326661323362663564313831386437343838383937326362656538
37313032613434646238383064613266623138343166613138353532663231633235653363396162
39623733393161343932663361343462393231666638666166663838373161656631303762346461
36346263386631663839386261636465326337616331646631656366346338306436313066353735
31323431636538613264613536396534363565333939376466353031663939663362646638363261
38323139346630633031316532663538613330626365333234313361356266303261376366643534
66643166363536313066343064346635616465303266336134393065386262613932653434393831
31306165383039643533613439646263316338306562653332323739646335336433646535616362
35393634623533343064373137313431616439316432646635343333636432633634623861393032
64363135373166326533326235363030363732616263313263336566653634393765306533303834
64363035363232663332303262393561626663653430643061326162616535353439316665663561
63643939613037303531323231373330333461636438373561313866353865373733373461366333
35373534306636396563623433663663353938663766656436346430666635326632356235366664
33396237346532613436306531616365646231626138393632356265376337343062616337313665
35616166323266393735323964613536303936323763646135383361346536343931323938616464
32623363623262306135363732333432616131663537313765343961393464656235666539336561
61616631373032393538306138356433303461383235653165363762383161633961386237363338
38356235656563393163343062333365643366633962646432613530346433653838313762623938
31313563323062323564363035386637653730393133393634383734646533323866396535366331
65373539323964383434656138346332633261303861653034336534393266313639663737636362
30616165326230633830633264666366323738396433663132316133326166633139613735653866
36316330636331663338313431656131363863613438353231366166633034343531303638373431
38303061616161353961626364666539303334373965396461346463623065646164656638363130
65646563636563643733306165633038623639333761363239646164616531316137393330323632
64303238626564626130343331663730643361353765633835366338303031363265363362303534
37333963623737323830383764656165613765383036626638323238613464613431363965316236
63366364336665653731613637346639376139643539363038396438333463396636346436366564
31393464316533363333343763343337366365333033343331623265616336383064666438373662
37656239363366633038626366343466353935633832633663356261323237306261313432353830
64353734336535636331636634326332373637643238313465306565626532633665333766633933
37343831323830393838306436306333363138633634393232363730353561626437323061656536
34633535363365303865383961623739393461376233643163643930376163363334393266663839
65353039666339613438396563356637343163383738613238323830643534666166663538633961
36353064393834643035366433613866386531656536353362656435623562386536386662613935
65663464646165376134666163386662363038393132333034316566643366663562643730326663
32626538636538393333323732353339306138393031623336333236323335626365366235393136
66333763303333396233336561376233633431376234303563303637303662303533313166663463
35316464316335333865336432646663376464303235613161333661626363386631313933326261
64653133653763396131383737353734646536343361316466303162323161323763663031356633
66643737616565303337663866346532363861353938636234333337333737623534663437383866
62613364653938626239333165356461333836323764323862393738393237353331306138336231
30613366613430363938306439373766616262373838636331383932303439356333623737633634
66343366306631336631613730646639336236383136656562316262356364356534633731333134
35313433353539353033343862623161653330313138336339326262613531643630623766363334
38666563326537613934633132383033363532343834613066356537396661323966623738343132
63333263333962336665633738643461376635336632393461383234313261616137333966636231
38393933383464353835356330373139313464643733656562356632346465633035326362373664
64373130353033306161376339636531336332663739623063383965623335666236646133663762
34383263366632363832623666656661653261396463383239323437333161656639313562666238
39356361316564393963333039646161656164643264653132653963666362376531626663663362
37656231356132616533646132323064333734646433366330333132373666636461316333656237
30363936383930313962303332303937326239366139303563303639663938613061303339373836
38616331303830363862313639636638393630323161373962343064366664636532663439353935
36633035326165663237623036303839383037636532386164353865633430656135646134643738
34613236373361326634323633333332633535353030343261393838313635343232363764626233
39396437643338313733313434663835336532393062313563653630373931636361656536343637
38356364333432363831623934353161623431353863626438306339393062626338633833633233
39633639383731646431613536363837653862333965326337626230656631623336316435616639
38363063353965383361313464623961616139643166626531646138373261356462633465363962
61356235386363306264376334643031613638366366363438623230303738656162393131393765
36303138316462643465346632326632653131633234643466656266306330613333656534626233
35326262386466363532373763323331323262383635636435346539633033383966376530366666
37333766313235333131643135386130313035636536313266323865653265373432323464373061
66633639363935663432626132623534346339306662366530386136333366346130306639343161
31316161393161303039633130653862326136643563336461666366376535666239306531383566
62363238636634616433646365313461336530396263653764633330333232386134663038306166
62633937306666633665356237616236383263343165646164343030646137373330623934383435
39656633303565376665316639326230336339386364626436383433343366613565303761636237
65373165383864663530343037616632363433663765623538643636386464623363366461306330
61666137316663323931373532306366643432626530323333626532373061333436663362396638
35636638386162343632653439313930346261613863646431623932316262656230303062336463
62646433653231363138623232643539376636663738616163653666633132396461666334633332
64616462303864313030383037663065653665323832383037646133663235613961383738633561
64333265656332653662323434613534313934616434353338656439646131343133353632383863
35663637303666633530396263356131333564323239663966333139313336613435333839343762
31623966306138636463626534313839353439623061363431366636353362633666626465323730
32373937343361623031643466383333636266393166383032393939366331316466336662343161
32613763363965636465363261366432376633346234613730663431373765323736316665313131
36323431323832616335643134653665643434336166653361343533323765323032323232343835
63356265373139386430623564613334666264633665643938643433383237313264643638326462
35656134353561666162643633363761326135643665643030396636636533373031643664306133
64663162313563656435366365343965323363633264326563376663353963356261636661373561
66653963353935386162336332636365313631383066306431633334326538306634636337653838
61653762313034616333326231623666653363343564636433356138323736356433393133643831
38363634363536343231653739333539336362663866643735343332623631336665333834636331
35363638663632356131333335363438313437656133336161393231616636353536356338376534
39613635366562656564666132386238363566623033356536333265356533333136623764663336
61313030653163613063666566306432333338386633343565316365663238353161336364393834
36366166393863633636323433663138666266393732386333306336633163393962383137656339
61386663653164383436356564323561346262633163613339643834623633666266353566386333
30626261656139376335303461623761656438643062393732333463343261633132623233633439
30353830393931386336396130366334623862336234346332313636633832313933303465363534
38653334376231326137633836643636383163613736623061326433636361643965316539383832
66666361663132663932376438653734653135323464323036393933656635353433336636326666
66643939373730346362613361326638393162393137386566623334356265653430343635306636
64353938356633336134333938326630653133666363326530643161363734393830643637303766
39393261353235326133623066626331316537373061363464663438386231613532623234326237
62376463623033613165386639626262333133386438643463613930333332613066386437316134
37646664303264386231376261393963323634666431343637316364616130343339396539666534
65643937386239653561376632316239376236323366386134303339316566316230323936633635
32313432323532363466343238653930363439643332626665333433643863366366316463336265
32643433636139373333653833373439626239316434333132376664373534623939306436613665
61306265313338353365303262663231306264343866626137613864363536623031666133633766
36663839353132643337303737323665643163356134633132343765313231613835333938623761
64336637343762356562653335376132626431373237623964633036376261363532643463316262
63353530373634303262653132656633396464383134343433376463303364356639646134636436
32353162623536363432633136393732613534656531376465336130646364643939323763346361
64333639393563636364396236663530343738643363623365323438363236623466663132663166
64306262623836666536623434363439393537323632653162623934346438646539643638633933
64376136393062643039306535336133346563326466636666626565313366333331343636326532
34653564336637346637353536656166353130333635373836643337316331343630353936396438
39323065363565626136376235313936366630663638343263623564643165613765343838343436
64323635326362313961616634393036643932623133393632346539633036383061396463343461
37636432323734643632376531373336383532393531633762626633613466383165393235336433
32303263353831336364313066353761353134623831616135666438383662323639626337626635
62326637643132393761626364623139666330633264363233613864313965333830303335663761
63393733343035623137383563663433626433356130336635313034636533633037653238316563
31643033626662363030613838373963376138653232383062646330356136366462313866633139
63316139336335316139623231326539326439663933336335323137343866623161