diff --git a/tasks/main.yml b/tasks/main.yml index 1d421aba2966c8b54eaa638d09cdaceb6b292213..18a3744db2a4219fe35cdb9477e8c52054f6b0a3 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -2,6 +2,7 @@ - name: "Set empty lists to trigger actions on instances" tags: + - "docker_metabase" - "docker_metabase_restart" - "docker_proxy" set_fact: @@ -87,7 +88,7 @@ template_metabase_instance_setup: "{{ metabase_instance_setup }}" ansible.builtin.template: src: "backup.yaml.j2" - dest: "/home/docker/backups/backup-metabase_{{ template_metabase_instance.key }}{{ template_backup_account.key }}.yaml" + dest: "/home/docker/backups/backup-metabase_{{ template_metabase_instance.key }}_{{ template_backup_account.key }}.yaml" owner: "root" group: "root" mode: "0400" @@ -107,15 +108,29 @@ item: "{{ item_account_instance.1 }}" template_backup_account: "{{ item_account_instance.0 }}" template_metabase_instance: "{{ metabase_instance }}" + # Compute a timestamp: time_start + index * (time_slot_duration - time_max_duration) / (#instance_to_backup - 1) + template_backup_timestamp: > + {{ + ( + ('1970-01-01 ' + metabase_backup_config.time_start) | to_datetime + ).timestamp() + + ansible_loop.index0 * ( + ( + (metabase_backup_config.time_slot_duration | community.general.to_seconds) + - (metabase_backup_config.time_max_duration | community.general.to_seconds) + ) / (ansible_loop.length - 1) + ) + }} ansible.builtin.cron: - name: "backup metabase_{{ template_metabase_instance.key }}{{ template_backup_account.key }}" - minute: "{{ '%H' | strftime((('1970-01-01 ' + backup_time_start) | to_datetime).timestamp() + (swift_metabase_accounts | length - template_backup_account.key) * ((backup_time_slot_duration | community.general.to_seconds - swift_metabase_accounts | length * backup_time_max_duration | community.general.to_seconds) / (swift_metabase_accounts | length - 1) + backup_time_max_duration | community.general.to_seconds) | int) }}" - hour: "{{ '%H' | strftime((('1970-01-01 ' + backup_time_start) | to_datetime).timestamp() + (swift_metabase_accounts | length - template_backup_account.key) * ((backup_time_slot_duration | community.general.to_seconds - swift_metabase_accounts | length * backup_time_max_duration | community.general.to_seconds) / (swift_metabase_accounts | length - 1) + backup_time_max_duration | community.general.to_seconds) | int) }}" - job: "/usr/bin/docker compose -f /home/docker/backups/backup-metabase_{{ template_metabase_instance.key }}{{ template_backup_account.key }}.yaml run --rm backup_metabase" + name: "backup metabase_{{ template_metabase_instance.key }}_{{ template_backup_account.key }}" + minute: "{{ '%M' | strftime(template_backup_timestamp | int) }}" + hour: "{{ '%H' | strftime(template_backup_timestamp | int) }}" + job: "/usr/bin/docker compose -f /home/docker/backups/backup-metabase_{{ template_metabase_instance.key }}_{{ template_backup_account.key }}.yaml run --rm backup_metabase" loop: "{{ swift_metabase_accounts | dict2items | product(metabase_instances | dict2items) }}" loop_control: label: "account {{ template_backup_account.key }} for metabase {{ template_metabase_instance.key }}" loop_var: item_account_instance + extended: true when: > (metabase_instance.value.backup.enabled | default(false)) and (inventory_hostname in groups.maintenance_contract) diff --git a/templates/backup.yaml.j2 b/templates/backup.yaml.j2 index dd016f41c6ae4273533333e00d254de977ecd1a1..20719d7f8bba0dc5abc2c052e108b57a71604969 100644 --- a/templates/backup.yaml.j2 +++ b/templates/backup.yaml.j2 @@ -1,58 +1,62 @@ {% set database_type = template_metabase_instance.value.database.type | default(default_metabase_database_type) %} -version: "2.1" services: - backup_metabase: - image: ghcr.io/tecnativa/docker-duplicity-postgres:master - hostname: {{ inventory_hostname_short | lower | regex_replace('_','') }}-metabase_{{ template_metabase_instance.key }} - environment: - DST: "swift://metabase_{{ template_metabase_instance.key }}_{{ inventory_hostname | lower }}" + backup_metabase: + image: sources.le-filament.com:5050/lefilament/duplicity_image/duplicity:latest + hostname: {{ inventory_hostname_short | lower | regex_replace('_','') }}-metabase_{{ template_metabase_instance.key }} + environment: + SRC: "/mnt/backup/src" + DST: "swift://metabase_{{ template_metabase_instance.key }}_{{ inventory_hostname | lower }}" {% if database_type == 'postgres' %} - PGDATABASE: "{{ template_metabase_instance.value.database.name }}" - PGUSER: "{{ template_metabase_instance.value.database.user }}" - PGPASSWORD: "{{ template_metabase_instance.value.database.password }}" + PGHOST: "db" + PGDATABASE: "{{ template_metabase_instance.value.database.name }}" + PGUSER: "{{ template_metabase_instance.value.database.user }}" + PGPASSWORD: "{{ template_metabase_instance.value.database.password }}" {% endif %} - PASSPHRASE: "{{ template_metabase_instance.value.backup.passphrase }}" - SWIFT_USERNAME: "{{ template_backup_credentials.username }}" - SWIFT_PASSWORD: "{{ template_backup_credentials.password }}" - SWIFT_AUTHURL: "{{ template_backup_account.value.authurl }}" - SWIFT_AUTHVERSION: {{ template_backup_account.value.authversion }} - SWIFT_TENANTNAME: "{{ template_backup_account.value.tenantname }}" - SWIFT_TENANTID: "{{ template_backup_account.value.tenantid }}" - SWIFT_REGIONNAME: "{{ template_backup_account.value.regionname }}" - JOB_200_WHEN: "never" + PASSPHRASE: "{{ template_metabase_instance.value.backup.passphrase }}" + SWIFT_USERNAME: "{{ template_backup_credentials.username }}" + SWIFT_PASSWORD: "{{ template_backup_credentials.password }}" + SWIFT_AUTHURL: "{{ template_backup_account.value.authurl }}" + SWIFT_AUTHVERSION: {{ template_backup_account.value.authversion }} + SWIFT_TENANTNAME: "{{ template_backup_account.value.tenantname }}" + SWIFT_TENANTID: "{{ template_backup_account.value.tenantid }}" + SWIFT_REGIONNAME: "{{ template_backup_account.value.regionname }}" + volumes: + - metabase_{{ template_metabase_instance.key }}_{{ template_backup_account.key }}_backup_cache:/root/.cache/duplicity/:rw {% if database_type == 'h2' %} - JOB_300_WHAT: "backup --full-if-older-than 6D" + - metabase_{{ template_metabase_instance.key }}_metabase:/mnt/backup/src/metabase:ro {% endif %} + networks: {% if database_type == 'postgres' %} - JOB_300_WHAT: "pg_dump --no-owner --format c --file $$SRC/$$PGDATABASE.pgdump && backup --full-if-older-than 6D" + - metabase_{{ template_metabase_instance.key }}_default {% endif %} - JOB_302_WHAT: "dup remove-all-but-n-full 5 --force $$DST $$@" - JOB_302_WHEN: "daily" - volumes: - - metabase_{{ template_metabase_instance.key }}{{ template_backup_account.key }}_backup_cache:/root/.cache/duplicity/:rw -{% if database_type == 'h2' %} - - metabase_{{ template_metabase_instance.key }}_metabase:/mnt/backup/src/metabase:z -{% endif %} - networks: + - public + command: > + /bin/ash -c " + echo \"info: start backuping\" {% if database_type == 'postgres' %} - - metabase_{{ template_metabase_instance.key }}_default + && echo \"info: creating '$$SRC' directory...\" + && mkdir --parents $$SRC + && echo \"info: dumping '$$PGDATABASE' into '$$SRC/$$PGDATABASE.pgdump'...\" + && pg_dump --no-owner --format c --file $$SRC/$$PGDATABASE.pgdump {% endif %} - - public - command: - - /etc/periodic/daily/jobrunner + && echo \"info: backuping '$$SRC' to '$$DST'...\" + && duplicity backup --full-if-older-than 6D $$SRC $$DST + && echo \"info: removing all but 5 full backups in '$$DST'\" + && duplicity remove-all-but-n-full 5 --force $$DST + && echo \"info: done\"" networks: {% if database_type == 'postgres' %} - metabase_{{ template_metabase_instance.key }}_default: - external: true + metabase_{{ template_metabase_instance.key }}_default: + external: true {% endif %} - public: - driver_opts: - encrypted: 1 + public: + driver_opts: + encrypted: 1 volumes: - metabase_{{ template_metabase_instance.key }}{{ template_backup_account.key }}_backup_cache: + metabase_{{ template_metabase_instance.key }}_{{ template_backup_account.key }}_backup_cache: {% if database_type == 'h2' %} - metabase_{{ template_metabase_instance.key }}_metabase: - external: true + metabase_{{ template_metabase_instance.key }}_metabase: + external: true {% endif %} diff --git a/templates/docker-compose.yaml.j2 b/templates/docker-compose.yaml.j2 index 3142c70f6bbeac856277eead3a0292fe901b3b38..c1e2475af6c7ab2088cf22d704b8d4eb01b63fe1 100644 --- a/templates/docker-compose.yaml.j2 +++ b/templates/docker-compose.yaml.j2 @@ -1,132 +1,131 @@ {% set database_type = template_metabase_instance.value.database.type | default(default_metabase_database_type) %} {% set default_database_setup = template_metabase_instance_setup.database[database_type] %} -version: "2.1" services: - metabase: - image: "metabase/metabase" - container_name: "metabase_{{ template_metabase_instance.key }}" - environment: + metabase: + image: "metabase/metabase" + container_name: "metabase_{{ template_metabase_instance.key }}" + environment: {% if database_type == 'postgres' %} - MB_DB_TYPE: "postgres" - MB_DB_HOST: "db" - MB_DB_PORT: "{{ template_metabase_instance.value.database.port | default(default_database_setup.port) }}" - MB_DB_USER: "{{ template_metabase_instance.value.database.user }}" - MB_DB_PASS: "{{ template_metabase_instance.value.database.password }}" - MB_DB_DBNAME: "{{ template_metabase_instance.value.database.name }}" + MB_DB_TYPE: "postgres" + MB_DB_HOST: "db" + MB_DB_PORT: "{{ template_metabase_instance.value.database.port | default(default_database_setup.port) }}" + MB_DB_USER: "{{ template_metabase_instance.value.database.user }}" + MB_DB_PASS: "{{ template_metabase_instance.value.database.password }}" + MB_DB_DBNAME: "{{ template_metabase_instance.value.database.name }}" {% endif %} {% if database_type == 'h2' %} - MB_DB_TYPE: "h2" - MB_DB_FILE: "{{ template_metabase_instance.value.database.file | default(default_database_setup.file) }}" + MB_DB_TYPE: "h2" + MB_DB_FILE: "{{ template_metabase_instance.value.database.file | default(default_database_setup.file) }}" {% endif %} - networks: - default: - inverseproxy_metabase: + networks: + default: + inverseproxy_metabase: {% if template_metabase_instance.value.extra_networks is defined %} {% for network in template_metabase_instance.value.extra_networks %} - {{ network }}: + {{ network }}: {% endfor %} {% endif %} {% if restrict_internet_access %} {% if whitelisted_urls is defined %} - whitelists_proxy: + whitelists_proxy: {% endif %} {% if template_odoo_instance.value.whitelists is defined %} - extrawhitelists_proxy: + extrawhitelists_proxy: {% endif %} {% endif %} - labels: - co.elastic.logs/enabled: "false" - traefik.enable: "true" - traefik.docker.network: "inverseproxy_metabase" - traefik.http.services.metabase-{{ template_metabase_instance.key }}.loadbalancer.server.port: "3000" + labels: + co.elastic.logs/enabled: "false" + traefik.enable: "true" + traefik.docker.network: "inverseproxy_metabase" + traefik.http.services.metabase-{{ template_metabase_instance.key }}.loadbalancer.server.port: "3000" {% for route in template_metabase_instance.value.routes %} - traefik.http.routers.metabase-{{ template_metabase_instance.key }}-{{ loop.index }}.entrypoints: "{{ template_traefik_entrypoints | default(default_traefik_entrypoints) }}" - traefik.http.routers.metabase-{{ template_metabase_instance.key }}-{{ loop.index }}.rule: "(Host(`{{ route.domain }}`)){% if route.prefix is defined %} && PathPrefix(`{{ route.prefix }}`){% endif %}" + traefik.http.routers.metabase-{{ template_metabase_instance.key }}-{{ loop.index }}.entrypoints: "{{ template_traefik_entrypoints | default(default_traefik_entrypoints) }}" + traefik.http.routers.metabase-{{ template_metabase_instance.key }}-{{ loop.index }}.rule: "(Host(`{{ route.domain }}`)){% if route.prefix is defined %} && PathPrefix(`{{ route.prefix }}`){% endif %}" {% if route.prefix is defined %} - traefik.http.middlewares.metabase-{{ template_metabase_instance.key }}-{{ loop.index }}-stripprefix.stripprefix.prefixes: "{{ route.prefix }}" + traefik.http.middlewares.metabase-{{ template_metabase_instance.key }}-{{ loop.index }}-stripprefix.stripprefix.prefixes: "{{ route.prefix }}" {% endif %} - traefik.http.routers.metabase-{{ template_metabase_instance.key }}-{{ loop.index }}.middlewares: "norobot-headers@file{% if route.prefix is defined %},metabase-{{ template_metabase_instance.key }}-{{ loop.index }}-stripprefix{% endif %}" - traefik.http.routers.metabase-{{ template_metabase_instance.key }}-{{ loop.index }}.service: "metabase-{{ template_metabase_instance.key }}" + traefik.http.routers.metabase-{{ template_metabase_instance.key }}-{{ loop.index }}.middlewares: "norobot-headers@file{% if route.prefix is defined %},metabase-{{ template_metabase_instance.key }}-{{ loop.index }}-stripprefix{% endif %}" + traefik.http.routers.metabase-{{ template_metabase_instance.key }}-{{ loop.index }}.service: "metabase-{{ template_metabase_instance.key }}" {% endfor %} - restart: unless-stopped + restart: unless-stopped {% if database_type == 'h2' %} - volumes: - - metabase:/metabase-data:rw + volumes: + - metabase:/metabase-data:rw {% endif %} {% if database_type == 'postgres' %} - db: - image: "{{ template_metabase_instance.value.database.image | default(default_database_setup.image) }}" - container_name: "metabase_{{ template_metabase_instance.key }}_db" - environment: - POSTGRES_DB: "{{ template_metabase_instance.value.database.name }}" - POSTGRES_USER: "{{ template_metabase_instance.value.database.user }}" - POSTGRES_PASSWORD: "{{ template_metabase_instance.value.database.password }}" + db: + image: "{{ template_metabase_instance.value.database.image | default(default_database_setup.image) }}" + container_name: "metabase_{{ template_metabase_instance.key }}_db" + environment: + POSTGRES_DB: "{{ template_metabase_instance.value.database.name }}" + POSTGRES_USER: "{{ template_metabase_instance.value.database.user }}" + POSTGRES_PASSWORD: "{{ template_metabase_instance.value.database.password }}" {% if template_metabase_instance.value.database.custom_command is defined %} - command: {{ template_metabase_instance.value.postgres.custom_command }} + command: {{ template_metabase_instance.value.postgres.custom_command }} {% endif %} - labels: - co.elastic.logs/enabled: "false" - volumes: - - db:/var/lib/postgresql/data:rw - restart: unless-stopped + labels: + co.elastic.logs/enabled: "false" + volumes: + - db:/var/lib/postgresql/data:rw + restart: unless-stopped {% endif %} {% if restrict_internet_access and template_metabase_instance.value.whitelists is defined %} {% for service in template_metabase_instance.value.whitelists %} - {{ service.domain }}: - image: tecnativa/whitelist - container_name: {{ template_metabase_instance.key }}_{{ service.domain }} - labels: - co.elastic.logs/enabled: "false" - networks: - extrawhitelists_proxy: - aliases: - - "{{ service.domain }}" - extrawhitelists_public: - environment: - PORT: "{{ service.port }}" - TARGET: "{{ service.domain }}" - PRE_RESOLVE: 1 - restart: unless-stopped + {{ service.domain }}: + image: tecnativa/whitelist + container_name: {{ template_metabase_instance.key }}_{{ service.domain }} + labels: + co.elastic.logs/enabled: "false" + networks: + extrawhitelists_proxy: + aliases: + - "{{ service.domain }}" + extrawhitelists_public: + environment: + PORT: "{{ service.port }}" + TARGET: "{{ service.domain }}" + PRE_RESOLVE: 1 + restart: unless-stopped {% endfor %} {% endif %} networks: - default: - internal: true - driver_opts: - encrypted: 1 - inverseproxy_shared: - external: true - inverseproxy_metabase: - external: true + default: + internal: true + driver_opts: + encrypted: 1 + inverseproxy_shared: + external: true + inverseproxy_metabase: + external: true {% if template_metabase_instance.value.extra_networks is defined %} {% for network in template_metabase_instance.value.extra_networks %} - {{ network }}: - external: true + {{ network }}: + external: true {% endfor %} {% endif %} {% if restrict_internet_access %} {% if whitelisted_urls is defined %} - whitelists_proxy: - external: true + whitelists_proxy: + external: true {% endif %} {% if restrict_internet_access and template_metabase_instance.value.whitelists is defined %} - extrawhitelists_proxy: - driver_opts: - encrypted: 1 - internal: true - extrawhitelists_public: - driver_opts: - encrypted: 1 + extrawhitelists_proxy: + driver_opts: + encrypted: 1 + internal: true + extrawhitelists_public: + driver_opts: + encrypted: 1 {% endif %} {% endif %} volumes: {% if database_type == 'postgres' %} - db: + db: {% endif %} {% if database_type == 'h2' %} - metabase: + metabase: {% endif %}