From 094d02ff7fa4454ce6054e467f1c8aee75e7e0cb Mon Sep 17 00:00:00 2001 From: Kevin Midboe Date: Wed, 5 Aug 2026 20:53:08 +0200 Subject: [PATCH] umami analytics play & role --- host_vars/umami.yml | 37 ++++++++ plays/umami.yml | 10 +++ roles/umami/defaults/main.yml | 7 ++ roles/umami/docker/defaults/main.yml | 11 +++ roles/umami/docker/handlers/main.yml | 6 ++ roles/umami/docker/tasks/main.yml | 98 +++++++++++++++++++++ roles/umami/handlers/main.yml | 7 ++ roles/umami/tasks/main.yml | 50 +++++++++++ roles/umami/templates/docker-compose.yml.j2 | 40 +++++++++ 9 files changed, 266 insertions(+) create mode 100644 host_vars/umami.yml create mode 100644 plays/umami.yml create mode 100644 roles/umami/defaults/main.yml create mode 100644 roles/umami/docker/defaults/main.yml create mode 100644 roles/umami/docker/handlers/main.yml create mode 100644 roles/umami/docker/tasks/main.yml create mode 100644 roles/umami/handlers/main.yml create mode 100644 roles/umami/tasks/main.yml create mode 100644 roles/umami/templates/docker-compose.yml.j2 diff --git a/host_vars/umami.yml b/host_vars/umami.yml new file mode 100644 index 0000000..784018a --- /dev/null +++ b/host_vars/umami.yml @@ -0,0 +1,37 @@ +--- +# --------------------------------------------------------------------------- +# Global variables for the docker + umami deployment +# --------------------------------------------------------------------------- +custom_firewall_ports: + - 3000 +description: Umami web analytics + +# --- Docker role --- +docker_users_to_add: + - "{{ ansible_user }}" # add the SSH user to the docker group + +# --- Umami role --- +umami_base_dir: /opt/umami +umami_image: "ghcr.io/umami-software/umami:latest" +umami_container_name: umami +umami_port: 3000 # host port Umami will be exposed on + +# Address the umami_port is bound to on the host. +# - "127.0.0.1" if your reverse proxy runs on this same host (loopback only) +# - "0.0.0.0" if your reverse proxy is elsewhere (another host/container) and +# needs to reach this port over the network +umami_bind_address: "0.0.0.0" + +# Postgres settings (used by the umami-db container) +umami_db_container_name: umami-db +umami_db_image: "postgres:15-alpine" +umami_db_name: umami +umami_db_user: umami +# IMPORTANT: override this in inventory/vars, --extra-vars, or (better) Ansible Vault. +# Do not leave the default password in production. +umami_db_password: "fc7tBTxsuAulmYmSl8AE0q13eq3f94f7u3u6" + +# A random string used by Umami to encrypt/salt data. Generate your own, e.g.: +# openssl rand -hex 32 +# Override this in production via --extra-vars or vault. +umami_app_secret: "dichotomizing-triads-cosmochemistry-amidols" diff --git a/plays/umami.yml b/plays/umami.yml new file mode 100644 index 0000000..259fbf8 --- /dev/null +++ b/plays/umami.yml @@ -0,0 +1,10 @@ +--- +- name: Install umami using Docker + hosts: all + vars: + umami_version: "9.4.4" + roles: + - role: roles/firewall + - role: roles/docker + - role: roles/env + - role: roles/umami diff --git a/roles/umami/defaults/main.yml b/roles/umami/defaults/main.yml new file mode 100644 index 0000000..22e2e92 --- /dev/null +++ b/roles/umami/defaults/main.yml @@ -0,0 +1,7 @@ +--- +umami_python_docker_sdk_packages: + - python3-pip + - python3-docker + +umami_restart_policy: unless-stopped +umami_docker_network: umami_net diff --git a/roles/umami/docker/defaults/main.yml b/roles/umami/docker/defaults/main.yml new file mode 100644 index 0000000..47eaf24 --- /dev/null +++ b/roles/umami/docker/defaults/main.yml @@ -0,0 +1,11 @@ +--- +docker_apt_arch: "{{ 'amd64' if ansible_architecture == 'x86_64' else 'arm64' }}" +docker_packages: + - docker-ce + - docker-ce-cli + - containerd.io + - docker-buildx-plugin + - docker-compose-plugin + +docker_service_state: started +docker_service_enabled: true diff --git a/roles/umami/docker/handlers/main.yml b/roles/umami/docker/handlers/main.yml new file mode 100644 index 0000000..2c78aa6 --- /dev/null +++ b/roles/umami/docker/handlers/main.yml @@ -0,0 +1,6 @@ +--- +- name: restart docker + ansible.builtin.systemd: + name: docker + state: restarted + daemon_reload: true diff --git a/roles/umami/docker/tasks/main.yml b/roles/umami/docker/tasks/main.yml new file mode 100644 index 0000000..7a8acc0 --- /dev/null +++ b/roles/umami/docker/tasks/main.yml @@ -0,0 +1,98 @@ +--- +- name: Ensure required system packages are present + ansible.builtin.apt: + name: + - ca-certificates + - curl + - gnupg + - lsb-release + state: present + update_cache: true + cache_valid_time: 3600 + +- name: Ensure /etc/apt/keyrings directory exists + ansible.builtin.file: + path: /etc/apt/keyrings + state: directory + mode: "0755" + +- name: Check if Docker GPG key already present + ansible.builtin.stat: + path: /etc/apt/keyrings/docker.asc + register: docker_gpg_key + +- name: Download Docker's official GPG key + ansible.builtin.get_url: + url: https://download.docker.com/linux/ubuntu/gpg + dest: /etc/apt/keyrings/docker.asc + mode: "0644" + when: not docker_gpg_key.stat.exists + +- name: Add Docker apt repository (deb822 format) + ansible.builtin.deb822_repository: + name: docker + types: [deb] + uris: "https://download.docker.com/linux/ubuntu" + suites: "{{ ansible_distribution_release }}" + components: [stable] + architectures: "{{ docker_apt_arch }}" + signed_by: /etc/apt/keyrings/docker.asc + state: present + register: docker_repo_added + +- name: Update apt cache after adding Docker repo + ansible.builtin.apt: + update_cache: true + when: docker_repo_added.changed + +- name: Install Docker Engine and plugins + ansible.builtin.apt: + name: "{{ docker_packages }}" + state: present + notify: restart docker + +- name: Ensure docker service is started and enabled + ansible.builtin.systemd: + name: docker + state: "{{ docker_service_state }}" + enabled: "{{ docker_service_enabled }}" + +- name: Ensure docker group exists + ansible.builtin.group: + name: docker + state: present + +- name: Add user(s) to the docker group + ansible.builtin.user: + name: "{{ item }}" + groups: docker + append: true + loop: "{{ docker_users_to_add }}" + register: docker_group_added + +- name: Notify about needing to re-login for group changes + ansible.builtin.debug: + msg: >- + User '{{ item.item }}' was added to the docker group. They will need to + log out and back in (or start a new SSH session) for this to take effect + without using sudo for docker commands. + loop: "{{ docker_group_added.results }}" + when: item.changed + +- name: Verify Docker is installed and working + ansible.builtin.command: docker --version + register: docker_version_output + changed_when: false + +- name: Show installed Docker version + ansible.builtin.debug: + msg: "{{ docker_version_output.stdout }}" + +- name: Verify Docker Compose plugin is installed + ansible.builtin.command: docker compose version + register: docker_compose_version_output + changed_when: false + +- name: Show installed Docker Compose version + ansible.builtin.debug: + msg: "{{ docker_compose_version_output.stdout }}" diff --git a/roles/umami/handlers/main.yml b/roles/umami/handlers/main.yml new file mode 100644 index 0000000..9970ff9 --- /dev/null +++ b/roles/umami/handlers/main.yml @@ -0,0 +1,7 @@ +--- +- name: recreate umami stack + community.docker.docker_compose_v2: + project_src: "{{ umami_base_dir }}" + state: present + recreate: always + listen: recreate umami stack diff --git a/roles/umami/tasks/main.yml b/roles/umami/tasks/main.yml new file mode 100644 index 0000000..cc417e2 --- /dev/null +++ b/roles/umami/tasks/main.yml @@ -0,0 +1,50 @@ +--- +- name: Ensure pip and Docker SDK for Python are installed (needed by community.docker modules) + ansible.builtin.apt: + name: "{{ umami_python_docker_sdk_packages }}" + state: present + update_cache: true + cache_valid_time: 3600 + +- name: Ensure Umami base directory exists + ansible.builtin.file: + path: "{{ umami_base_dir }}" + state: directory + mode: "0750" + owner: root + group: root + +- name: Deploy docker-compose.yml for Umami stack + ansible.builtin.template: + src: docker-compose.yml.j2 + dest: "{{ umami_base_dir }}/docker-compose.yml" + mode: "0640" + owner: root + group: root + notify: recreate umami stack + +- name: Flush handlers so compose file changes apply before we verify status + ansible.builtin.meta: flush_handlers + +- name: Start (or ensure running) the Umami + Postgres stack + community.docker.docker_compose_v2: + project_src: "{{ umami_base_dir }}" + state: present + register: umami_compose_result + +- name: Show compose deployment result + ansible.builtin.debug: + var: umami_compose_result.changed + +- name: Wait for Umami HTTP endpoint to become available + ansible.builtin.uri: + url: "http://127.0.0.1:{{ umami_port }}/api/heartbeat" + status_code: 200 + register: umami_health + until: umami_health.status == 200 + retries: 5 + delay: 10 + +- name: Umami health check result + ansible.builtin.debug: + msg: "Umami is up and responding on port {{ umami_port }}." diff --git a/roles/umami/templates/docker-compose.yml.j2 b/roles/umami/templates/docker-compose.yml.j2 new file mode 100644 index 0000000..d097c9b --- /dev/null +++ b/roles/umami/templates/docker-compose.yml.j2 @@ -0,0 +1,40 @@ +{{ ansible_managed | comment }} +# Based on: https://github.com/umami-software/umami/blob/master/docker-compose.yml +services: + umami: + image: "{{ umami_image }}" + container_name: "{{ umami_container_name }}" + ports: + - "{{ umami_bind_address }}:{{ umami_port }}:3000" + environment: + DATABASE_URL: "postgresql://{{ umami_db_user }}:{{ umami_db_password }}@db:5432/{{ umami_db_name }}" + APP_SECRET: "{{ umami_app_secret }}" + depends_on: + db: + condition: service_healthy + init: true + restart: always + healthcheck: + test: ["CMD-SHELL", "curl http://localhost:3000/api/heartbeat"] + interval: 5s + timeout: 5s + retries: 5 + + db: + image: "{{ umami_db_image }}" + container_name: "{{ umami_db_container_name }}" + environment: + POSTGRES_DB: "{{ umami_db_name }}" + POSTGRES_USER: "{{ umami_db_user }}" + POSTGRES_PASSWORD: "{{ umami_db_password }}" + volumes: + - umami-db-data:/var/lib/postgresql/data + restart: always + healthcheck: + test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"] + interval: 5s + timeout: 5s + retries: 5 + +volumes: + umami-db-data: