6 Commits

Author SHA1 Message Date
3da645f3b8 Mailu/MSMTP: split token mgmt, idempotent reload, safer guards
• Rename: 02_create-user.yml → 02_manage_user.yml; 03_create-token.yml → 03a_manage_user_token.yml + 03b_create_user_token.yml
• Only (re)run sys-svc-msmtp when no-reply token exists; set run_once_sys_svc_msmtp=true in 01_core
• Reset by setting run_once_sys_svc_msmtp=false after creating no-reply token; then include sys-svc-msmtp
• Harden when-guards (no '{{ }}' in when, safe .get lookups)
• Minor formatting and failed_when readability

Conversation: https://chatgpt.com/share/68ebd196-a264-800f-a215-3a89d0f96c79
2025-10-12 18:05:00 +02:00
a996e2190f feat(logout): wire injector to web-svc-logout and add robust CORS/CSP for /logout
- sys-front-inj-logout: depend on web-svc-logout (run-once guarded) and simplify task flow.
- web-svc-logout: align feature flags/formatting and extend CSP:
  - add cdn.jsdelivr.net to connect/script/style and quote values.
- Nginx: move CORS config into logout-proxy.conf.j2 with dynamic vars:
  - Access-Control-Allow-Origin set to canonical logout origin,
  - Allow-Credentials=true,
  - Allow-Methods=GET, OPTIONS,
  - basic headers list (Accept, Authorization),
  - cache disabled for /logout responses.
- Drop obsolete CORS var passing from 01_core.yml; headers now templated at proxy layer.

Prepares clean cross-origin logout orchestration from https://logout.veen.world.

Refs: ChatGPT discussion – https://chatgpt.com/share/68ebb75f-0170-800f-93c5-e5cb438b8ed4
2025-10-12 16:16:47 +02:00
7dccffd52d Optimized variable whitespacing 2025-10-12 14:37:45 +02:00
853f2c3e2d Added mastodon to disc space cleanup script 2025-10-12 14:37:19 +02:00
b2978a3141 Changed mailu token name 2025-10-12 14:36:13 +02:00
0e0b703ccd Added cleanup to mastodon 2025-10-12 14:03:28 +02:00
19 changed files with 138 additions and 95 deletions

View File

@@ -1,8 +1,7 @@
- name: Include dependencies - name: Include dependencies
include_role: include_role:
name: '{{ item }}' name: "sys-svc-msmtp"
loop: when: run_once_sys_svc_msmtp is not defined or run_once_sys_svc_msmtp is false
- sys-svc-msmtp
- include_role: - include_role:
name: sys-service name: sys-service

View File

@@ -17,7 +17,7 @@
name: sys-service name: sys-service
vars: vars:
system_service_tpl_on_failure: "{{ SYS_SERVICE_ON_FAILURE_COMPOSE }}" system_service_tpl_on_failure: "{{ SYS_SERVICE_ON_FAILURE_COMPOSE }}"
system_service_tpl_exec_start: "{{ system_service_script_exec }} --backups-folder-path {{ BACKUPS_FOLDER_PATH }} --maximum-backup-size-percent {{SIZE_PERCENT_MAXIMUM_BACKUP}}" system_service_tpl_exec_start: "{{ system_service_script_exec }} --backups-folder-path {{ BACKUPS_FOLDER_PATH }} --maximum-backup-size-percent {{ SIZE_PERCENT_MAXIMUM_BACKUP }}"
system_service_tpl_exec_start_pre: '/usr/bin/python {{ PATH_SYSTEM_LOCK_SCRIPT }} {{ SYS_SERVICE_GROUP_MANIPULATION | join(" ") }} --ignore {{ SYS_SERVICE_GROUP_CLEANUP | join(" ") }} --timeout "{{ SYS_TIMEOUT_BACKUP_SERVICES }}"' system_service_tpl_exec_start_pre: '/usr/bin/python {{ PATH_SYSTEM_LOCK_SCRIPT }} {{ SYS_SERVICE_GROUP_MANIPULATION | join(" ") }} --ignore {{ SYS_SERVICE_GROUP_CLEANUP | join(" ") }} --timeout "{{ SYS_TIMEOUT_BACKUP_SERVICES }}"'
system_service_copy_files: true system_service_copy_files: true
system_service_force_linear_sync: false system_service_force_linear_sync: false

View File

@@ -39,6 +39,18 @@ if [ "$force_freeing" = true ]; then
docker exec -u www-data $nextcloud_application_container /var/www/html/occ versions:cleanup || exit 6 docker exec -u www-data $nextcloud_application_container /var/www/html/occ versions:cleanup || exit 6
fi fi
# Mastodon cleanup (remote media cache)
mastodon_application_container="{{ applications | get_app_conf('web-app-mastodon', 'docker.services.mastodon.name') }}"
mastodon_cleanup_days="1"
if [ -n "$mastodon_application_container" ] && docker ps -a --format '{% raw %}{{.Names}}{% endraw %}' | grep -qw "$mastodon_application_container"; then
echo "Cleaning up Mastodon media cache (older than ${mastodon_cleanup_days} days)" &&
docker exec -u root "$mastodon_application_container" bash -lc "bin/tootctl media remove --days=${mastodon_cleanup_days}" || exit 8
# Optional: additionally remove local thumbnail/cache files older than X days
# Warning: these will be regenerated when accessed, which may cause extra CPU/I/O load
# docker exec -u root "$mastodon_application_container" bash -lc "find /mastodon/public/system/cache -type f -mtime +${mastodon_cleanup_days} -delete" || exit 9
fi
fi fi
if command -v pacman >/dev/null 2>&1 ; then if command -v pacman >/dev/null 2>&1 ; then

View File

@@ -1,8 +1,12 @@
- name: Include dependency 'sys-svc-webserver-core' - name: Include dependency 'web-svc-logout'
include_role: include_role:
name: sys-svc-webserver-core name: web-svc-logout
when: when:
- run_once_sys_svc_webserver_core is not defined - run_once_web_svc_logout is not defined
- name: "deploy the logout.js" - name: "deploy the logout.js"
include_tasks: "02_deploy.yml" include_tasks: "02_deploy.yml"
- set_fact:
run_once_sys_front_inj_logout: true
changed_when: false

View File

@@ -1,7 +1,5 @@
- block: - name: "Load base for '{{ application_id }}'"
- include_tasks: 01_core.yml include_tasks: 01_core.yml
- set_fact:
run_once_sys_front_inj_logout: true
when: run_once_sys_front_inj_logout is not defined when: run_once_sys_front_inj_logout is not defined
- name: "Load logout code for '{{ application_id }}'" - name: "Load logout code for '{{ application_id }}'"

View File

@@ -14,4 +14,7 @@
- include_role: - include_role:
name: sys-ctl-hlth-msmtp name: sys-ctl-hlth-msmtp
when: run_once_sys_ctl_hlth_msmtp is not defined when: run_once_sys_ctl_hlth_msmtp is not defined
- set_fact:
run_once_sys_svc_msmtp: true

View File

@@ -1,5 +1,6 @@
- block: - name: "Load MSMTP Core Once"
- include_tasks: 01_core.yml include_tasks: 01_core.yml
- set_fact: when:
run_once_sys_svc_msmtp: true - run_once_sys_svc_msmtp is not defined or run_once_sys_svc_msmtp is false
when: run_once_sys_svc_msmtp is not defined # Just execute when mailu_token is defined
- users['no-reply'].mailu_token is defined

View File

@@ -41,7 +41,7 @@
meta: flush_handlers meta: flush_handlers
- name: "Create Mailu accounts" - name: "Create Mailu accounts"
include_tasks: 02_create-user.yml include_tasks: 02_manage_user.yml
vars: vars:
MAILU_DOCKER_DIR: "{{ docker_compose.directories.instance }}" MAILU_DOCKER_DIR: "{{ docker_compose.directories.instance }}"
mailu_api_base_url: "http://127.0.0.1:8080/api/v1" mailu_api_base_url: "http://127.0.0.1:8080/api/v1"

View File

@@ -25,5 +25,5 @@
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}" no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
- name: "Create Mailu API Token for {{ mailu_user_name }}" - name: "Create Mailu API Token for {{ mailu_user_name }}"
include_tasks: 03_create-token.yml include_tasks: 03a_manage_user_token.yml
when: "{{ 'mail-bot' in item.value.roles }}" when: "'mail-bot' in item.value.roles"

View File

@@ -0,0 +1,26 @@
- name: "Fetch existing API tokens via curl inside admin container"
command: >-
{{ docker_compose_command_exec }} -T admin \
curl -s -X GET {{ mailu_api_base_url }}/token \
-H "Authorization: Bearer {{ MAILU_API_TOKEN }}"
args:
chdir: "{{ MAILU_DOCKER_DIR }}"
register: mailu_tokens_cli
changed_when: false
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
- name: "Extract existing token info for '{{ mailu_user_key }};{{ mailu_user_name }}'"
set_fact:
mailu_user_existing_token: >-
{{ (
mailu_tokens_cli.stdout
| default('[]')
| from_json
| selectattr('comment','equalto', SOFTWARE_NAME)
| list
).0 | default(None) }}
- name: "Start Mailu token procedures for undefined tokens"
when: users[mailu_user_key].mailu_token is not defined
include_tasks: 03b_create_user_token.yml

View File

@@ -1,26 +1,3 @@
- name: "Fetch existing API tokens via curl inside admin container"
command: >-
{{ docker_compose_command_exec }} -T admin \
curl -s -X GET {{ mailu_api_base_url }}/token \
-H "Authorization: Bearer {{ MAILU_API_TOKEN }}"
args:
chdir: "{{ MAILU_DOCKER_DIR }}"
register: mailu_tokens_cli
changed_when: false
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
- name: "Extract existing token info for '{{ mailu_user_key }};{{ mailu_user_name }}'"
set_fact:
mailu_user_existing_token: >-
{{ (
mailu_tokens_cli.stdout
| default('[]')
| from_json
| selectattr('comment','equalto', mailu_user_key ~ " - ansible.infinito")
| list
).0 | default(None) }}
- name: "Delete existing API token for '{{ mailu_user_key }};{{ mailu_user_name }}' if local token missing but remote exists" - name: "Delete existing API token for '{{ mailu_user_key }};{{ mailu_user_name }}' if local token missing but remote exists"
command: >- command: >-
{{ docker_compose_command_exec }} -T admin \ {{ docker_compose_command_exec }} -T admin \
@@ -29,7 +6,6 @@
args: args:
chdir: "{{ MAILU_DOCKER_DIR }}" chdir: "{{ MAILU_DOCKER_DIR }}"
when: when:
- users[mailu_user_key].mailu_token is not defined
- mailu_user_existing_token is not none - mailu_user_existing_token is not none
- mailu_user_existing_token.id is defined - mailu_user_existing_token.id is defined
register: mailu_token_delete register: mailu_token_delete
@@ -43,13 +19,12 @@
-H "Authorization: Bearer {{ MAILU_API_TOKEN }}" -H "Authorization: Bearer {{ MAILU_API_TOKEN }}"
-H "Content-Type: application/json" -H "Content-Type: application/json"
-d '{{ { -d '{{ {
"comment": mailu_user_key ~ " - ansible.infinito", "comment": SOFTWARE_NAME,
"email": users[mailu_user_key].email, "email": users[mailu_user_key].email,
"ip": mailu_token_ip "ip": mailu_token_ip
} | to_json }}' } | to_json }}'
args: args:
chdir: "{{ MAILU_DOCKER_DIR }}" chdir: "{{ MAILU_DOCKER_DIR }}"
when: users[mailu_user_key].mailu_token is not defined
register: mailu_token_creation register: mailu_token_creation
# If curl sees 4xx/5xx it returns non-zero due to -f → fail the task. # If curl sees 4xx/5xx it returns non-zero due to -f → fail the task.
failed_when: failed_when:
@@ -57,7 +32,7 @@
# Fallback: if some gateway returns 200 but embeds an error JSON. # Fallback: if some gateway returns 200 but embeds an error JSON.
- mailu_token_creation.rc == 0 and - mailu_token_creation.rc == 0 and
(mailu_token_creation.stdout is search('"code"\\s*:\\s*4\\d\\d') or (mailu_token_creation.stdout is search('"code"\\s*:\\s*4\\d\\d') or
mailu_token_creation.stdout is search('cannot be found')) mailu_token_creation.stdout is search('cannot be found'))
# Only mark changed when a token is actually present in the JSON. # Only mark changed when a token is actually present in the JSON.
changed_when: mailu_token_creation.stdout is search('"token"\\s*:') changed_when: mailu_token_creation.stdout is search('"token"\\s*:')
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}" no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
@@ -66,14 +41,25 @@
set_fact: set_fact:
users: >- users: >-
{{ users {{ users
| combine({ | combine({
mailu_user_key: ( mailu_user_key: (
users[mailu_user_key] users[mailu_user_key]
| combine({ | combine({
'mailu_token': (mailu_token_creation.stdout | from_json).token 'mailu_token': (mailu_token_creation.stdout | from_json).token
}) })
) )
}, recursive=True) }, recursive=True)
}} }}
when: users[mailu_user_key].mailu_token is not defined
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}" no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
- name: "Reset MSMTP Configuration if No-Reply User Token changed"
when: users['no-reply'].username == mailu_user_name
block:
- name: "Set MSMTP run-once fact false"
set_fact:
run_once_sys_svc_msmtp: false
changed_when: false
- name: Reload MSMTP role
include_role:
name: "sys-svc-msmtp"

View File

@@ -0,0 +1,19 @@
- name: Check health status of '{{ item }}' container
shell: |
cid=$(docker compose ps -q {{ item }})
docker inspect \
--format '{{ "{{.State.Health.Status}}" }}' \
$cid
args:
chdir: "{{ docker_compose.directories.instance }}"
register: healthcheck
retries: 60
delay: 5
until: healthcheck.stdout == "healthy"
loop:
- mastodon
- streaming
- sidekiq
loop_control:
label: "{{ item }}"
changed_when: false

View File

@@ -0,0 +1,9 @@
---
# Cleanup routine for Mastodon
# Removes cached remote media older than 14 days when MODE_CLEANUP is enabled.
- name: "Cleanup Mastodon media cache older than 14 days"
command:
cmd: "docker exec -u root {{ MASTODON_CONTAINER }} bin/tootctl media remove --days=14"
register: mastodon_cleanup
changed_when: mastodon_cleanup.rc == 0
failed_when: mastodon_cleanup.rc != 0

View File

@@ -1,6 +1,3 @@
- name: "Execute migration for '{{ application_id }}'" - name: "Execute migration for '{{ application_id }}'"
command: command:
cmd: "docker exec {{ MASTODON_CONTAINER }} bundle exec rails db:migrate" cmd: "docker exec {{ MASTODON_CONTAINER }} bundle exec rails db:migrate"
- name: "Include administrator routines for '{{ application_id }}'"
include_tasks: 02_administrator.yml

View File

@@ -1,26 +1,5 @@
# Routines to create the administrator account # Routines to create the administrator account
# @see https://chatgpt.com/share/67b9b12c-064c-800f-9354-8e42e6459764 # @see https://chatgpt.com/share/67b9b12c-064c-800f-9354-8e42e6459764
- name: Check health status of '{{ item }}' container
shell: |
cid=$(docker compose ps -q {{ item }})
docker inspect \
--format '{{ "{{.State.Health.Status}}" }}' \
$cid
args:
chdir: "{{ docker_compose.directories.instance }}"
register: healthcheck
retries: 60
delay: 5
until: healthcheck.stdout == "healthy"
loop:
- mastodon
- streaming
- sidekiq
loop_control:
label: "{{ item }}"
changed_when: false
- name: Remove line containing "- administrator" from config/settings.yml to allow creating administrator account - name: Remove line containing "- administrator" from config/settings.yml to allow creating administrator account
command: command:
cmd: "docker exec -u root {{ MASTODON_CONTAINER }} sed -i '/- administrator/d' config/settings.yml" cmd: "docker exec -u root {{ MASTODON_CONTAINER }} sed -i '/- administrator/d' config/settings.yml"

View File

@@ -18,5 +18,15 @@
vars: vars:
docker_compose_flush_handlers: true docker_compose_flush_handlers: true
- name: "Wait for Mastodon"
include_tasks: 01_wait.yml
- name: "Cleanup Mastodon caches when MODE_CLEANUP is true"
include_tasks: 02_cleanup.yml
when: MODE_CLEANUP | bool
- name: "start setup procedures for mastodon" - name: "start setup procedures for mastodon"
include_tasks: 01_setup.yml include_tasks: 03_setup.yml
- name: "Include administrator routines for '{{ application_id }}'"
include_tasks: 04_administrator.yml

View File

@@ -1,9 +1,9 @@
features: features:
matomo: true matomo: true
css: true css: true
desktop: true desktop: true
javascript: false javascript: false
logout: false logout: false
server: server:
domains: domains:
canonical: canonical:
@@ -19,10 +19,11 @@ server:
connect-src: connect-src:
- "{{ WEB_PROTOCOL }}://*.{{ PRIMARY_DOMAIN }}" - "{{ WEB_PROTOCOL }}://*.{{ PRIMARY_DOMAIN }}"
- "{{ WEB_PROTOCOL }}://{{ PRIMARY_DOMAIN }}" - "{{ WEB_PROTOCOL }}://{{ PRIMARY_DOMAIN }}"
- "https://cdn.jsdelivr.net"
script-src-elem: script-src-elem:
- https://cdn.jsdelivr.net - "https://cdn.jsdelivr.net"
style-src-elem: style-src-elem:
- https://cdn.jsdelivr.net - "https://cdn.jsdelivr.net"
frame-ancestors: frame-ancestors:
- "{{ WEB_PROTOCOL }}://<< defaults_applications[web-app-keycloak].server.domains.canonical[0] >>" - "{{ WEB_PROTOCOL }}://<< defaults_applications[web-app-keycloak].server.domains.canonical[0] >>"

View File

@@ -21,11 +21,6 @@
- name: "load docker, proxy for '{{ application_id }}'" - name: "load docker, proxy for '{{ application_id }}'"
include_role: include_role:
name: sys-stk-full-stateless name: sys-stk-full-stateless
vars:
aca_origin: "'{{ domains | get_url('web-svc-logout', WEB_PROTOCOL) }}' always"
aca_credentials: "'true' always"
aca_methods: "'GET, OPTIONS' always"
aca_headers: "'Accept, Authorization' always"
- name: Create symbolic link from .env file to repository - name: Create symbolic link from .env file to repository
file: file:

View File

@@ -8,7 +8,11 @@ location = /logout {
proxy_http_version 1.1; proxy_http_version 1.1;
{# CORS headers allow your central page to call this #} {# CORS headers allow your central page to call this #}
{% include 'roles/sys-svc-proxy/templates/headers/access_control_allow.conf.j2' %} {%- set aca_origin = "'{{ domains | get_url('web-svc-logout', WEB_PROTOCOL) }}' always" -%}
{%- set aca_credentials = "'true' always" -%}
{%- set aca_methods = "'GET, OPTIONS' always" -%}
{%- set aca_headers = "'Accept, Authorization' always" -%}
{%- include 'roles/sys-svc-proxy/templates/headers/access_control_allow.conf.j2' -%}
{# Disable caching absolutely #} {# Disable caching absolutely #}
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0" always; add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0" always;