diff --git a/AGENTS.md b/AGENTS.md index 5b4c05e..d810c2f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -64,7 +64,8 @@ uv run ansible-galaxy install --role-file requirements.yml - `playbook-wakapi.yml` — трекинг времени. - `playbook-memos.yml` — заметки. - `playbook-outline.yml` — вики/база знаний. -- `playbook-homepage.yml` — дашборд (образ из Yandex Registry). +- `playbook-homepage.yml` — кастомная домашняя страничка на apex `vakhrushev.me` (образ из Yandex Registry). +- `playbook-dashboard.yml` — дашборд со списком сервисов (gethomepage.dev) на `start.vakhrushev.me`, закрыт за Authelia. - `playbook-rssbridge.yml` — RSS-агрегатор. - `playbook-netdata.yml` — мониторинг. - `playbook-dozzle.yml` — просмотр Docker-логов. @@ -99,6 +100,7 @@ uv run ansible-galaxy install --role-file requirements.yml - Большинство приложений определяют переменные inline в плейбуке. Отдельные файлы переменных только у homepage и transcriber (`vars/homepage.yml`, `vars/transcriber.yml` + `vars/transcriber.images.yml`). - Общие переменные из `vars/secrets.yml`: `application_dir`, `bin_prefix`, `primary_user` и др. - Каждое приложение: `app_name`, `app_user`, `app_owner_uid`, `app_owner_gid`, `base_dir`, `data_dir`. +- UID/GID сервисов: новое соглашение — диапазон `11xx`, причём `app_owner_uid == app_owner_gid` (одно число на сервис). Новому приложению берём следующий свободный номер по возрастанию. Старые сервисы ещё сидят на легаси-нумерации `10xx` (часто с разными uid/gid) — их не трогаем, но новые заводим только в `11xx`. ## Линтинг и CI diff --git a/files/authelia/configuration.template.yml b/files/authelia/configuration.template.yml index 197d305..fca2494 100644 --- a/files/authelia/configuration.template.yml +++ b/files/authelia/configuration.template.yml @@ -720,6 +720,9 @@ access_control: rules: ## Rules applied to everyone + - domain: "start.vakhrushev.me" + policy: "one_factor" + - domain: "status.vakhrushev.me" subject: "group:admins" policy: "two_factor" diff --git a/files/caddyproxy/Caddyfile.template b/files/caddyproxy/Caddyfile.template index fb78a61..468327c 100644 --- a/files/caddyproxy/Caddyfile.template +++ b/files/caddyproxy/Caddyfile.template @@ -61,6 +61,22 @@ vakhrushev.me { } } +# Дашборд со списком сервисов (gethomepage). Закрыт за Authelia. Это отдельный +# сервис, не путать с кастомной домашней страничкой homepage_app на apex выше. +start.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 { + to dashboard_app:3000 + } +} + matrix.vakhrushev.me { tls anwinged@ya.ru import access_log diff --git a/files/dashboard/config/bookmarks.template.yaml b/files/dashboard/config/bookmarks.template.yaml new file mode 100644 index 0000000..a641f96 --- /dev/null +++ b/files/dashboard/config/bookmarks.template.yaml @@ -0,0 +1,8 @@ +# https://gethomepage.dev/configs/bookmarks/ +- Внешнее: + - Yandex Cloud: + - abbr: YC + href: https://console.cloud.yandex.ru/ + - Timeweb: + - abbr: TW + href: https://timeweb.cloud/my diff --git a/files/dashboard/config/services.template.yaml b/files/dashboard/config/services.template.yaml new file mode 100644 index 0000000..47edb8c --- /dev/null +++ b/files/dashboard/config/services.template.yaml @@ -0,0 +1,102 @@ +# https://gethomepage.dev/configs/services/ +# href — внешний адрес через Caddy (для браузера); +# siteMonitor — внутренний адрес контейнера в web_proxy_network, по нему +# homepage рисует индикатор up/down (быстро, без выхода наружу). +# icon — бренд-логотипы Dashboard Icons (.png, грузятся браузером +# с CDN jsDelivr); где бренда нет — mdi-* из комплекта homepage. + +- Работа и знания: + - Gitea: + href: https://git.vakhrushev.me + description: Git-репозитории + icon: gitea.png + siteMonitor: http://gitea_app:3000 + - Outline: + href: https://outline.vakhrushev.me + description: База знаний / вики + icon: outline.png + siteMonitor: http://outline_app:3000 + - Memos: + href: https://memos.vakhrushev.me + description: Заметки + icon: memos.png + siteMonitor: http://memos_app:5230 + - Tududi: + href: https://tududi.vakhrushev.me + description: Планировщик задач + icon: tududi.png + siteMonitor: http://tududi_app:3002 + - Wakapi: + href: https://wakapi.vakhrushev.me + description: Трекинг времени в коде + icon: wakapi.png + siteMonitor: http://wakapi_app:3000 + +- Медиа и хобби: + - Miniflux: + href: https://miniflux.vakhrushev.me + description: RSS-ридер + icon: miniflux.png + siteMonitor: http://miniflux_app:8080 + - RSS-Bridge: + href: https://rssbridge.vakhrushev.me + description: Генератор RSS для сайтов без фидов + icon: rss-bridge.png + siteMonitor: http://rssbridge_app:80 + - Calibre: + href: https://calibre.vakhrushev.me + description: Электронная библиотека + icon: calibre-web.png + siteMonitor: http://calibre_web_app:8083 + - Gramps: + href: https://gramps.vakhrushev.me + description: Генеалогия + icon: gramps-web.png + siteMonitor: http://gramps_app:5000 + - Wanderer: + href: https://wanderer.vakhrushev.me + description: Пешие маршруты + icon: wanderer.png + siteMonitor: http://wanderer_web:3000 + - Remembos: + href: https://remembos.vakhrushev.me + description: Интервальное повторение + icon: mdi-brain + siteMonitor: http://remembos_app:8080 + - Matrix: + href: https://matrix.vakhrushev.me + description: Мессенджер (Tuwunel) + icon: matrix.png + siteMonitor: http://tuwunel_app:6167 + +- Инфраструктура: + - Netdata: + href: https://status.vakhrushev.me + description: Мониторинг сервера + icon: netdata.png + siteMonitor: http://netdata:19999 + - Dozzle: + href: https://dozzle.vakhrushev.me + description: Логи контейнеров + icon: dozzle.png + siteMonitor: http://dozzle_app:8080 + - GoAccess: + href: https://goaccess.vakhrushev.me + description: Веб-аналитика логов Caddy + icon: goaccess.png + siteMonitor: http://goaccess_app:8080 + - Bifrost: + href: https://bifrost.vakhrushev.me + description: LLM-шлюз (DeepSeek) + icon: mdi-transit-connection-variant + siteMonitor: http://bifrost_app:8080/health + - Authelia: + href: https://auth.vakhrushev.me + description: SSO / аутентификация + icon: authelia.png + siteMonitor: http://authelia_app:9091 + - Wanderbase: + href: https://wanderbase.vakhrushev.me + description: PocketBase — бэкенд Wanderer + icon: pocketbase.png + siteMonitor: http://wanderer_db:8090 diff --git a/files/dashboard/config/settings.template.yaml b/files/dashboard/config/settings.template.yaml new file mode 100644 index 0000000..81bc459 --- /dev/null +++ b/files/dashboard/config/settings.template.yaml @@ -0,0 +1,22 @@ +# https://gethomepage.dev/configs/settings/ +title: Сервер vakhrushev.me +headerStyle: clean +theme: dark +color: slate + +# Порядок в layout = порядок групп на странице (сверху вниз). style: row + +# columns: 3 — каждая группа во всю ширину, карточки по 3 в ряд. Блок закладок +# «Внешнее» указан последним, поэтому остаётся внизу. +layout: + Работа и знания: + style: row + columns: 3 + Медиа и хобби: + style: row + columns: 3 + Инфраструктура: + style: row + columns: 3 + Внешнее: + style: row + columns: 3 diff --git a/files/dashboard/config/widgets.template.yaml b/files/dashboard/config/widgets.template.yaml new file mode 100644 index 0000000..8723bd9 --- /dev/null +++ b/files/dashboard/config/widgets.template.yaml @@ -0,0 +1,8 @@ +# https://gethomepage.dev/configs/info-widgets/ +# Метрики хоста homepage читает из /proc внутри контейнера — в docker это +# общий с хостом kernel, поэтому cpu/memory отражают сервер целиком. +- resources: + label: "{{ host_name }}" + cpu: true + memory: true + expanded: true diff --git a/files/dashboard/docker-compose.template.yml b/files/dashboard/docker-compose.template.yml new file mode 100644 index 0000000..c1aeb26 --- /dev/null +++ b/files/dashboard/docker-compose.template.yml @@ -0,0 +1,27 @@ +# dashboard (gethomepage.dev) — стартовая страница со списком сервисов. +# Контейнер в web_proxy_network, слушает {{ homepage_port }}, наружу НЕ +# публикуется — доступ только через Caddy как https://{{ homepage_domain }} +# (закрыт за Authelia). Это отдельный сервис, не связан с кастомным homepage_app +# на apex vakhrushev.me. + +services: + + dashboard_app: + image: "{{ homepage_image }}" + container_name: dashboard_app + restart: unless-stopped + user: "{{ app_owner_uid }}:{{ app_owner_gid }}" + networks: + - "web_proxy_network" + volumes: + - "{{ config_dir }}:/app/config" + - "/etc/localtime:/etc/localtime:ro" + - "/etc/timezone:/etc/timezone:ro" + environment: + # homepage 1.x требует явный whitelist Host за реверс-прокси, иначе отдаёт + # «Host validation failed». Пускаем только адрес из Caddy. + - HOMEPAGE_ALLOWED_HOSTS={{ homepage_domain }} + +networks: + web_proxy_network: + external: true diff --git a/playbook-all-applications.yml b/playbook-all-applications.yml index 2a30838..f7caf48 100644 --- a/playbook-all-applications.yml +++ b/playbook-all-applications.yml @@ -54,6 +54,9 @@ - name: "Configure homepage" ansible.builtin.import_playbook: playbook-homepage.yml +- name: "Configure dashboard" + ansible.builtin.import_playbook: playbook-dashboard.yml + - name: "Configure transcriber" ansible.builtin.import_playbook: playbook-transcriber.yml diff --git a/playbook-dashboard.yml b/playbook-dashboard.yml new file mode 100644 index 0000000..bdb5c40 --- /dev/null +++ b/playbook-dashboard.yml @@ -0,0 +1,76 @@ +--- +- name: "Configure dashboard start page" + hosts: all + + vars_files: + - vars/secrets.yml + - vars/vars.yml + + vars: + app_name: "dashboard" + app_user: "{{ app_name }}" + app_owner_uid: 1109 + app_owner_gid: 1109 + base_dir: "{{ (application_dir, app_name) | path_join }}" + config_dir: "{{ (base_dir, 'config') | path_join }}" + + # gethomepage.dev — проверь актуальный тег перед обновлением. + homepage_image: "ghcr.io/gethomepage/homepage:v1.13.2" + # Внутренний порт контейнера; наружу не публикуется, ходим только через Caddy. + homepage_port: 3000 + # Домен, на котором Caddy отдаёт дашборд. homepage 1.x требует явный whitelist + # Host за реверс-прокси, иначе «Host validation failed». + homepage_domain: "start.vakhrushev.me" + + config_files: + - "settings" + - "services" + - "widgets" + - "bookmarks" + + 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 }}" + + - name: "Render dashboard config files" + ansible.builtin.template: + src: "./files/{{ app_name }}/config/{{ item }}.template.yaml" + dest: "{{ (config_dir, item ~ '.yaml') | path_join }}" + owner: "{{ app_user }}" + group: "{{ app_user }}" + mode: "0640" + loop: "{{ config_files }}" + + - 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 + pull: "always" + tags: + - run-app