добавлен goatcounter для веб-аналитики
- один инстанс на два сайта, gc-main и gc-photo: нужный выбирается по Host - домены ведутся в goatcounter_sites (group_vars), оттуда же рендерятся имена в Caddyfile - сайты и первый пользователь заводятся идемпотентно, креды из vault
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
---
|
||||
- name: "Configure goatcounter application"
|
||||
hosts: all
|
||||
|
||||
# goatcounter_sites приходит из group_vars/all/main.yml (его же читает
|
||||
# caddyproxy), goatcounter_admin_email/password — из vault-файла secrets.yml.
|
||||
vars:
|
||||
app_name: "goatcounter"
|
||||
# Родительский сайт: владелец пользователя, к нему линкуются остальные.
|
||||
goatcounter_parent_vhost: "{{ goatcounter_sites[0] }}"
|
||||
app_user: "{{ app_name }}"
|
||||
app_owner_uid: 1110
|
||||
app_owner_gid: 1110
|
||||
|
||||
base_dir: "{{ (application_dir, app_name) | path_join }}"
|
||||
data_dir: "{{ (base_dir, 'data') | path_join }}"
|
||||
|
||||
# Логин и пароль Postbox goatcounter принимает только внутри SMTP-URL, в
|
||||
# userinfo, — значит их надо процентно экранировать. Фильтр urlencode
|
||||
# оставляет `/` как есть (safe='/'), поэтому дожимаем его вручную.
|
||||
postbox_user_enc: "{{ postbox_user | urlencode | replace('/', '%2F') }}"
|
||||
postbox_pass_enc: "{{ postbox_pass | urlencode | replace('/', '%2F') }}"
|
||||
|
||||
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 }}"
|
||||
- "{{ data_dir }}"
|
||||
|
||||
# В data лежит только SQLite-база; бэкапим её restic'ом напрямую, без
|
||||
# промежуточного дампа — как у tududi. Записи редкие (пачка просмотров раз
|
||||
# в 10 секунд), ночной прогон почти наверняка застаёт базу в покое, а
|
||||
# retention страхует от редкого несогласованного снимка.
|
||||
- name: "Create backup targets file"
|
||||
ansible.builtin.copy:
|
||||
dest: "{{ base_dir }}/backup-targets"
|
||||
content: |
|
||||
{{ data_dir }}
|
||||
owner: "{{ app_user }}"
|
||||
group: "{{ app_user }}"
|
||||
mode: "0640"
|
||||
|
||||
- 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"
|
||||
# SMTP-пароль стоит прямым значением в environment: — без no_log его
|
||||
# печатает --diff. Файловых секретов goatcounter не умеет.
|
||||
no_log: true
|
||||
|
||||
- name: "Run application with docker compose"
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ base_dir }}"
|
||||
state: "present"
|
||||
remove_orphans: true
|
||||
tags:
|
||||
- run-app
|
||||
|
||||
# Сайты заводятся по разу, в том же прогоне, что поднимает контейнер: пока
|
||||
# сайта нет в базе, goatcounter отдаёт на его домене открытый мастер
|
||||
# регистрации, и это окно надо закрыть сразу.
|
||||
#
|
||||
# `db show site` различает три состояния по rc и stderr: rc=0 — сайт есть;
|
||||
# rc=1 + "nothing found" — база готова, сайта нет; rc=1 + "doesn't exist" —
|
||||
# serve ещё не накатил миграции. Последнее и есть гонка со стартом
|
||||
# контейнера, поэтому ждём в until, пока ответ не станет осмысленным.
|
||||
- name: "Check which goatcounter sites exist"
|
||||
community.docker.docker_container_exec:
|
||||
container: "{{ app_name }}_app"
|
||||
argv:
|
||||
- "goatcounter"
|
||||
- "db"
|
||||
- "show"
|
||||
- "site"
|
||||
- "-find"
|
||||
- "{{ item }}"
|
||||
loop: "{{ goatcounter_sites }}"
|
||||
register: "goatcounter_sites_state"
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
until: "goatcounter_sites_state.rc == 0 or 'nothing found' in goatcounter_sites_state.stderr"
|
||||
retries: 10
|
||||
delay: 3
|
||||
tags:
|
||||
- run-app
|
||||
|
||||
# Пароль нужен только когда заводим родительский сайт: дочерние идут с
|
||||
# -link и своих пользователей не получают.
|
||||
- name: "Check credentials for the first goatcounter user are set"
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- "goatcounter_admin_email | default('') is match('[^@]+@[^@]+')"
|
||||
- "goatcounter_admin_password | default('') | length >= 8"
|
||||
fail_msg: >-
|
||||
Сайт {{ goatcounter_parent_vhost }} ещё не заведён, а
|
||||
goatcounter_admin_email / goatcounter_admin_password не заданы.
|
||||
Пропишите их в group_vars/all/secrets.yml и повторите прогон.
|
||||
when: "goatcounter_sites_state.results[0].rc != 0"
|
||||
tags:
|
||||
- run-app
|
||||
|
||||
# Вход дальше — своей парой email/пароль на https://{{ goatcounter_parent_vhost }};
|
||||
# Authelia тут не участвует, эндпоинт /count обязан быть публичным.
|
||||
- name: "Create the parent goatcounter site and its user"
|
||||
community.docker.docker_container_exec:
|
||||
container: "{{ app_name }}_app"
|
||||
argv:
|
||||
- "goatcounter"
|
||||
- "db"
|
||||
- "create"
|
||||
- "site"
|
||||
- "-vhost"
|
||||
- "{{ goatcounter_parent_vhost }}"
|
||||
- "-user.email"
|
||||
- "{{ goatcounter_admin_email }}"
|
||||
- "-password"
|
||||
- "{{ goatcounter_admin_password }}"
|
||||
when: "goatcounter_sites_state.results[0].rc != 0"
|
||||
changed_when: true
|
||||
# Пароль стоит прямым значением в argv — без no_log его печатает --diff.
|
||||
no_log: true
|
||||
tags:
|
||||
- run-app
|
||||
|
||||
# -link отдаёт дочернему сайту пользователей родителя и ставит его в
|
||||
# переключалку сайтов в шапке. Данные при этом раздельные: всё, что видно
|
||||
# в дашборде, скоупится по site_id.
|
||||
- name: "Create linked goatcounter sites"
|
||||
community.docker.docker_container_exec:
|
||||
container: "{{ app_name }}_app"
|
||||
argv:
|
||||
- "goatcounter"
|
||||
- "db"
|
||||
- "create"
|
||||
- "site"
|
||||
- "-vhost"
|
||||
- "{{ item.item }}"
|
||||
- "-link"
|
||||
- "{{ goatcounter_parent_vhost }}"
|
||||
loop: "{{ goatcounter_sites_state.results[1:] }}"
|
||||
loop_control:
|
||||
label: "{{ item.item }}"
|
||||
when: "item.rc != 0"
|
||||
changed_when: true
|
||||
tags:
|
||||
- run-app
|
||||
Reference in New Issue
Block a user