From bf6ed83fd67946a7a45459c39136fd91488ed4a5 Mon Sep 17 00:00:00 2001 From: Ana Custura Date: Fri, 8 Aug 2025 12:00:26 +0100 Subject: [PATCH 1/3] Feat: add cleaninsights role --- roles/podman_cleaninsights/defaults/main.yml | 7 ++ roles/podman_cleaninsights/handlers/main.yml | 26 +++++ roles/podman_cleaninsights/tasks/main.yml | 90 +++++++++++++++++ .../templates/cleaninsights.network | 2 + .../templates/cleaninsights.slice | 8 ++ .../templates/frontend.network | 2 + .../templates/matomo.container | 23 +++++ .../templates/mysql.container | 15 +++ .../podman_cleaninsights/templates/nginx.conf | 99 +++++++++++++++++++ .../templates/redis.container | 9 ++ 10 files changed, 281 insertions(+) create mode 100644 roles/podman_cleaninsights/defaults/main.yml create mode 100644 roles/podman_cleaninsights/handlers/main.yml create mode 100644 roles/podman_cleaninsights/tasks/main.yml create mode 100644 roles/podman_cleaninsights/templates/cleaninsights.network create mode 100644 roles/podman_cleaninsights/templates/cleaninsights.slice create mode 100644 roles/podman_cleaninsights/templates/frontend.network create mode 100644 roles/podman_cleaninsights/templates/matomo.container create mode 100644 roles/podman_cleaninsights/templates/mysql.container create mode 100644 roles/podman_cleaninsights/templates/nginx.conf create mode 100644 roles/podman_cleaninsights/templates/redis.container diff --git a/roles/podman_cleaninsights/defaults/main.yml b/roles/podman_cleaninsights/defaults/main.yml new file mode 100644 index 0000000..322e06a --- /dev/null +++ b/roles/podman_cleaninsights/defaults/main.yml @@ -0,0 +1,7 @@ +podman_cleaninsights_podman_rootless_user: cleaninsights +podman_cleaninsights_web_hostname: "{{ inventory_hostname }}" +podman_cleaninsights_mysql_database: matomo +# podman_cleaninsights_mysql_password: +# podman_cleaninsights_mysql_root_password: +podman_cleaninsights_php_memory_limit: 2048M +podman_cleaninsights_mysql_user: matomo diff --git a/roles/podman_cleaninsights/handlers/main.yml b/roles/podman_cleaninsights/handlers/main.yml new file mode 100644 index 0000000..667381f --- /dev/null +++ b/roles/podman_cleaninsights/handlers/main.yml @@ -0,0 +1,26 @@ +- name: restart mysql + ansible.builtin.systemd_service: + name: mysql + state: restarted + scope: user + daemon_reload: true + become: true + become_user: "{{ podman_cleaninsights_podman_rootless_user }}" + +- name: restart matomo + ansible.builtin.systemd_service: + name: matomo + state: restarted + scope: user + daemon_reload: true + become: true + become_user: "{{ podman_cleaninsights_podman_rootless_user }}" + +- name: restart redis + ansible.builtin.systemd_service: + name: "{{ podman_cleaninsights_systemd_service_slice }}" + state: restarted + scope: user + daemon_reload: true + become: true + become_user: "{{ podman_cleaninsights_podman_rootless_user }}" diff --git a/roles/podman_cleaninsights/tasks/main.yml b/roles/podman_cleaninsights/tasks/main.yml new file mode 100644 index 0000000..965624a --- /dev/null +++ b/roles/podman_cleaninsights/tasks/main.yml @@ -0,0 +1,90 @@ +--- +- name: create service configuration directories + ansible.builtin.file: + path: "/home/{{ podman_cleaninsights_podman_rootless_user }}/{{ item }}" + state: directory + owner: "{{ podman_cleaninsights_podman_rootless_user }}" + group: "{{ podman_cleaninsights_podman_rootless_user }}" + mode: "0755" + become: true + with_items: + - mysql + - matomo + +- name: install podman quadlet for rootless podman user + ansible.builtin.template: + src: "{{ item }}" + dest: "/home/{{ podman_cleaninsights_podman_rootless_user }}/.config/containers/systemd/{{ item }}" + owner: "{{ podman_cleaninsights_podman_rootless_user }}" + mode: "0400" + with_items: + - matomo.container + - mysql.container + - redis.container + notify: + - "restart {{ item | split('.') | first }}" + become: true + + +- name: install network quadlets for rootless podman user + ansible.builtin.template: + src: "{{ item }}" + dest: "/home/{{ podman_cleaninsights_podman_rootless_user }}/.config/containers/systemd/{{ item }}" + owner: "{{ podman_cleaninsights_podman_rootless_user }}" + mode: "0400" + with_items: + - frontend.network + - cleaninsights.network + become: true + +- name: verify quadlets are correctly defined + ansible.builtin.command: /usr/libexec/podman/quadlet -dryrun -user + register: podman_cleaninsights_quadlet_result + ignore_errors: true + changed_when: false + become: true + become_user: "{{ podman_cleaninsights_podman_rootless_user }}" + +- name: assert that the quadlet verification succeeded + ansible.builtin.assert: + that: + - podman_cleaninsights_quadlet_result.rc == 0 + fail_msg: "'/usr/libexec/podman/quadlet -dryrun -user' failed! Output withheld to prevent leaking secrets." + +- name: set up nginx + ansible.builtin.include_role: + name: irl.wip.podman_nginx + vars: + podman_nginx_frontend_network: frontend + podman_nginx_podman_rootless_user: "{{ podman_cleaninsights_podman_rootless_user }}" + podman_nginx_primary_hostname: "{{ podman_cleaninsights_web_hostname }}" + podman_nginx_systemd_service_slice: cleaninsights.slice + podman_nginx_systemd_service_requires: ["matomo"] + +- name: create nginx configuration file + ansible.builtin.template: + src: nginx.conf + dest: "/home/{{ podman_cleaninsights_podman_rootless_user }}/nginx/nginx.conf" + owner: "{{ podman_cleaninsights_podman_rootless_user }}" + group: "{{ podman_cleaninsights_podman_rootless_user }}" + mode: "0644" + become: true + +- name: install services slice for rootless podman user + ansible.builtin.template: + src: "cleaninsights.slice" + dest: "/home/{{ podman_cleaninsights_podman_rootless_user }}/.config/systemd/user/cleaninsights.slice" + owner: "{{ podman_cleaninsights_podman_rootless_user }}" + group: "{{ podman_cleaninsights_podman_rootless_user }}" + mode: "0655" + become: true + +- name: make sure services are started on boot + ansible.builtin.systemd_service: + name: "cleaninsights.slice" + enabled: true + state: started + daemon_reload: true + scope: user + become: true + become_user: "{{ podman_cleaninsights_podman_rootless_user }}" diff --git a/roles/podman_cleaninsights/templates/cleaninsights.network b/roles/podman_cleaninsights/templates/cleaninsights.network new file mode 100644 index 0000000..f95a29b --- /dev/null +++ b/roles/podman_cleaninsights/templates/cleaninsights.network @@ -0,0 +1,2 @@ +[Network] +NetworkName=cleaninsights diff --git a/roles/podman_cleaninsights/templates/cleaninsights.slice b/roles/podman_cleaninsights/templates/cleaninsights.slice new file mode 100644 index 0000000..71c7a82 --- /dev/null +++ b/roles/podman_cleaninsights/templates/cleaninsights.slice @@ -0,0 +1,8 @@ +[Unit] +Description=Cleaninsights Slice +Before=slices.target +Requires=matomo.service +Requires=nginx.service + +[Install] +WantedBy=default.target diff --git a/roles/podman_cleaninsights/templates/frontend.network b/roles/podman_cleaninsights/templates/frontend.network new file mode 100644 index 0000000..379c059 --- /dev/null +++ b/roles/podman_cleaninsights/templates/frontend.network @@ -0,0 +1,2 @@ +[Network] +NetworkName=frontend diff --git a/roles/podman_cleaninsights/templates/matomo.container b/roles/podman_cleaninsights/templates/matomo.container new file mode 100644 index 0000000..c5e9c70 --- /dev/null +++ b/roles/podman_cleaninsights/templates/matomo.container @@ -0,0 +1,23 @@ +[Unit] +Requires=mysql.service +After=mysql.service +Requires=redis.service +After=redis.service + +[Container] +ContainerName=matomo +Environment=MATOMO_DATABASE_HOST=mysql +Environment=PHP_MEMORY_LIMIT={{ podman_cleaninsights_php_memory_limit }} +Environment=MATOMO_DATABASE_DBNAME={{ podman_cleaninsights_mysql_database }} +Environment=MATOMO_DATABASE_PASSWORD={{ podman_cleaninsights_mysql_password }} +Environment=MATOMO_DATABASE_USERNAME={{ podman_cleaninsights_mysql_user }} +Image=docker.io/matomo:5-fpm +Volume=/home/{{ podman_cleaninsights_podman_rootless_user }}/matomo:/var/www/html +#Volume=/home/{{ podman_cleaninsights_podman_rootless_user }}/cleaninsights.php:/var/www/html/cleaninsights.php:ro +#Volume=/home/{{ podman_cleaninsights_podman_rootless_user }}/cleaninsights.ini:/var/www/html/cleaninsights.ini:ro +Network=cleaninsights.network +Network=frontend.network + +[Service] +Restart=always +Slice=cleaninsights.slice diff --git a/roles/podman_cleaninsights/templates/mysql.container b/roles/podman_cleaninsights/templates/mysql.container new file mode 100644 index 0000000..71e6dd6 --- /dev/null +++ b/roles/podman_cleaninsights/templates/mysql.container @@ -0,0 +1,15 @@ +[Container] +ContainerName=mysql +Environment=MYSQL_ROOT_PASSWORD={{ podman_cleaninsights_mysql_root_password }} +Environment=MYSQL_DATABASE={{ podman_cleaninsights_mysql_database }} +Environment=MYSQL_USER={{ podman_cleaninsights_mysql_user }} +Environment=MYSQL_PASSWORD={{ podman_cleaninsights_mysql_password }} +Image=docker.io/mysql:9 +PublishPort=127.0.0.1:3306:3306 +Volume=/home/{{ podman_cleaninsights_podman_rootless_user }}/mysql:/var/lib/mysql +Network=cleaninsights.network + +[Service] +Restart=always +Slice=cleaninsights.slice + diff --git a/roles/podman_cleaninsights/templates/nginx.conf b/roles/podman_cleaninsights/templates/nginx.conf new file mode 100644 index 0000000..4a3d231 --- /dev/null +++ b/roles/podman_cleaninsights/templates/nginx.conf @@ -0,0 +1,99 @@ +# {{ ansible_managed }} + +upstream php-handler { + server matomo:9000; +} + +server { + listen 80; + listen [::]:80; + + server_name {{ podman_cleaninsights_web_hostname }}; + server_tokens off; + + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } + + location / { + return 301 https://{{ podman_cleaninsights_web_hostname }}$request_uri; + } +} + +server { + listen 443 ssl; + listen [::]:443 ssl; + http2 on; + + server_name matomo {{ podman_cleaninsights_web_hostname }}; + server_tokens off; + + ssl_certificate /etc/letsencrypt/live/{{ podman_cleaninsights_web_hostname }}/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/{{ podman_cleaninsights_web_hostname }}/privkey.pem; + + add_header Strict-Transport-Security max-age=15768000 always; + add_header Referrer-Policy origin always; # make sure outgoing links don't show the URL to the Matomo instance + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + + root /var/www/html/; # this is the default matomo image path + index index.php; + + location ~ ^/(index|matomo|cleaninsights|piwik|js/index|plugins/HeatmapSessionRecording/configs).php { + # regex to split $uri to $fastcgi_script_name and $fastcgi_path + fastcgi_split_path_info ^(.+\.php)(/.+)$; + + # Check that the PHP script exists before passing it + #try_files $fastcgi_script_name =404; + + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param PATH_INFO $fastcgi_path_info; + fastcgi_param HTTP_PROXY ""; # prohibit httpoxy: https://httpoxy.org/ + include fastcgi_params; + fastcgi_pass php-handler; + fastcgi_read_timeout 60s; + } + + # deny access to all other .php files + location ~* ^.+\.php$ { + return 403; + } + + # deny access to all cleaninsights.ini file + location ~ cleaninsights.ini { + return 403; + } + + # disable all access to the following directories + location ~ ^/(config|tmp|core|lang) { + return 403; # replace with 404 to not show these directories exist + } + + location ~ /\.ht { + return 403; + } + + location ~ js/container_.*_preview\.js$ { + expires off; + add_header Cache-Control 'private, no-cache, no-store'; + } + + location ~ \.(gif|ico|jpg|png|svg|js|css|htm|html|mp3|mp4|wav|ogg|avi|ttf|eot|woff|woff2)$ { + allow all; + # Cache images,CSS,JS and webfonts for an hour + # Increasing the duration may improve the load-time, but may cause old files to show after an Matomo upgrade + expires 1h; + add_header Pragma public; + add_header Cache-Control "public"; + } + + location ~ ^/(libs|vendor|plugins|misc|node_modules) { + deny all; + return 403; + } + + # properly display textfiles in root directory + location ~/(.*\.md|LEGALNOTICE|LICENSE) { + default_type text/plain; + } +} \ No newline at end of file diff --git a/roles/podman_cleaninsights/templates/redis.container b/roles/podman_cleaninsights/templates/redis.container new file mode 100644 index 0000000..aa85fb3 --- /dev/null +++ b/roles/podman_cleaninsights/templates/redis.container @@ -0,0 +1,9 @@ +[Container] +ContainerName=redis +ExposeHostPort=6379 +Image=docker.io/redis:7.2-rc1-alpine +Network=cleaninsights.network + +[Service] +Restart=always +Slice=cleaninsights.slice -- 2.47.2 From 7ea092737adfd086c31bec53e0680a272ad8d6af Mon Sep 17 00:00:00 2001 From: Ana Custura Date: Fri, 8 Aug 2025 12:42:31 +0100 Subject: [PATCH 2/3] Add cleaninishgts script and config file --- roles/podman_cleaninsights/defaults/main.yml | 1 + roles/podman_cleaninsights/handlers/main.yml | 2 +- roles/podman_cleaninsights/tasks/main.yml | 19 + .../templates/cleaninsights.ini.j2 | 22 ++ .../templates/cleaninsights.php | 341 ++++++++++++++++++ .../templates/matomo.container | 4 +- 6 files changed, 386 insertions(+), 3 deletions(-) create mode 100644 roles/podman_cleaninsights/templates/cleaninsights.ini.j2 create mode 100644 roles/podman_cleaninsights/templates/cleaninsights.php diff --git a/roles/podman_cleaninsights/defaults/main.yml b/roles/podman_cleaninsights/defaults/main.yml index 322e06a..eeff222 100644 --- a/roles/podman_cleaninsights/defaults/main.yml +++ b/roles/podman_cleaninsights/defaults/main.yml @@ -5,3 +5,4 @@ podman_cleaninsights_mysql_database: matomo # podman_cleaninsights_mysql_root_password: podman_cleaninsights_php_memory_limit: 2048M podman_cleaninsights_mysql_user: matomo +podman_cleaninsights_matomo_token: "" \ No newline at end of file diff --git a/roles/podman_cleaninsights/handlers/main.yml b/roles/podman_cleaninsights/handlers/main.yml index 667381f..e0735da 100644 --- a/roles/podman_cleaninsights/handlers/main.yml +++ b/roles/podman_cleaninsights/handlers/main.yml @@ -18,7 +18,7 @@ - name: restart redis ansible.builtin.systemd_service: - name: "{{ podman_cleaninsights_systemd_service_slice }}" + name: redis state: restarted scope: user daemon_reload: true diff --git a/roles/podman_cleaninsights/tasks/main.yml b/roles/podman_cleaninsights/tasks/main.yml index 965624a..296d7e0 100644 --- a/roles/podman_cleaninsights/tasks/main.yml +++ b/roles/podman_cleaninsights/tasks/main.yml @@ -11,6 +11,25 @@ - mysql - matomo + +- name: install cleaninsights script + ansible.builtin.copy: + src: templates/cleaninsights.php + dest: "/home/{{ podman_cleaninsights_podman_rootless_user }}/matomo/cleaninsights.php" + owner: "{{ podman_cleaninsights_podman_rootless_user }}" + group: "{{ podman_cleaninsights_podman_rootless_user }}" + mode: "0755" + become: true + +- name: install cleaninsights configuration file + ansible.builtin.template: + src: "cleaninsights.ini.j2" + dest: "/home/{{ podman_cleaninsights_podman_rootless_user }}/matomo/cleaninsights.ini" + owner: "{{ podman_cleaninsights_podman_rootless_user }}" + group: "{{ podman_cleaninsights_podman_rootless_user }}" + mode: "0755" + become: true + - name: install podman quadlet for rootless podman user ansible.builtin.template: src: "{{ item }}" diff --git a/roles/podman_cleaninsights/templates/cleaninsights.ini.j2 b/roles/podman_cleaninsights/templates/cleaninsights.ini.j2 new file mode 100644 index 0000000..01ea4b4 --- /dev/null +++ b/roles/podman_cleaninsights/templates/cleaninsights.ini.j2 @@ -0,0 +1,22 @@ +; A Matomo token for authentication to enable retrograde setting of timestamps. +; Needs to be set for CIMP to work. See https://matomo.org/faq/general/faq_114/ +token_auth = '{{ podman_cleaninsights_matomo_token }}' + +; Allowed CORS scheme/domain/port tuples. Defaults to none allowed. +; cors[] = 'https://example.com:3000' + +; The base URL of your Matomo instance. Only needs to be set if CIMP is +; not installed inside the Matomo directory.ca +matomo_base_url = 'http://nginx' + +; Number of created tracking requests to send to Matomo at once. + chunk_size = 1000 + +; Number of seconds to wait before retry, if Matomo responded with an error. +delay_after_failure = 2 + +; Number of maximum retries on error. +max_attempts = 3 + +; Set to true to have a debug output in 'ciapi.log' in the same directory. +debug = true \ No newline at end of file diff --git a/roles/podman_cleaninsights/templates/cleaninsights.php b/roles/podman_cleaninsights/templates/cleaninsights.php new file mode 100644 index 0000000..d72a67a --- /dev/null +++ b/roles/podman_cleaninsights/templates/cleaninsights.php @@ -0,0 +1,341 @@ +idsite < 1) { + debug('Malformed body. Last JSON error:' . json_last_error() . ' ' . json_last_error_msg()); + http_response_code(400); + exit(1); +} + + +// Build output for Matomo Tracking API and send in chunks. + +$output = []; + +$reporting_start = time(); +$reporting_end = 0; +$count = 0; +$result = false; + +if (property_exists($data, 'visits') && gettype($data->visits) === 'array') { + foreach ($data->visits as $visit) { + $count += renderVisit($visit); + } +} + +if (property_exists($data, 'events') && gettype($data->events) === 'array') { + foreach ($data->events as $event) { + $count += renderEvent($event); + } +} + +// Send the rest. +send(false); + +debug('result = ' . ($result ? 'success': 'failure') . ", count = $count"); + +if ($result === false) { + debug('There were no events and visits or the Matomo Tracking API refused all requests.'); + http_response_code(400); + exit(1); +} + + +// Instruct Matomo to re-analyse the complete period of this request +// on the next auto-archiving run. + +$start = new DateTime(); +$start->setTimestamp($reporting_start); + +$end = new DateTime(); +$end->setTimestamp($reporting_end); + +$query = http_build_query([ + 'module' => 'API', + 'method' => 'CoreAdminHome.invalidateArchivedReports', + 'idSites' => $data->idsite, + 'period' => 'range', + // Need to use `dates[]` instead of `dates`. Otherwise, some automagic happens in Matomo and breaks the call. + 'dates[]' => $start->format('Y-m-d') . ',' . $end->format('Y-m-d'), + 'token_auth' => $TOKEN_AUTH, +]); + +$result = callMatomo(null, $query); + +if ($result === false) { + debug('Matomo could not be triggered to reprocess data!'); +} +else { + debug('Matomo was triggered to reprocess data!'); +} + +http_response_code(204); + +exit(); + + +// Helper functions. + +function renderVisit(stdClass $visit): int { + $action_name = $visit->action_name; + + return render($visit->period_start, $visit->period_end, $visit->times, + function() use ($action_name) { + return ['action_name' => $action_name]; + }); +} + +function renderEvent(stdClass $event): int { + return render($event->period_start, $event->period_end, $event->times, + function() use ($event) { + $e = ['e_c' => $event->category, 'e_a' => $event->action]; + + if (property_exists($event, 'name') && $event->name) { + $e['e_n'] = $event->name; + } + + if (property_exists($event, 'value') && $event->value) { + $e['e_v'] = $event->value; + } + + return $e; + }); +} + +function render(int $period_start, int $period_end, int $times, callable $callback): int { + global $reporting_start, $reporting_end, $output; + + $reporting_start = min($reporting_start, $period_start); + $reporting_end = max($reporting_end, $period_end); + + $interval = ($period_end - $period_start) / $times; + + // Offset the first record by half an interval, so records are neatly "centered" in the period. + $period_start += $interval / 2; + + for ($i = 0; $i < $times; $i++) { + $request = $callback(); + + $request['cdt'] = $period_start + $interval * $i; + + $output[] = buildRequest($request); + + send(); + } + + return $times; +} + +function buildRequest(array &$request): string { + global $data; + + $request['idsite'] = $data->idsite; + $request['rec'] = 1; + if (property_exists($data, 'lang') && $data->lang) $request['lang'] = $data->lang; + if (property_exists($data, 'ua') && $data->ua) $request['ua'] = $data->ua; + + return '?' . http_build_query($request); +} + +function callMatomo(string $path = null, string $query = null, string $body = null): string { + global $MATOMO_BASE_URL, $MAX_ATTEMPTS, $DELAY_AFTER_FAILURE; + + $options = [ + 'method' => 'POST', + 'header' => 'Content-Type: application/json; charset=UTF-8']; + + if (isset($body)) { + $options['content'] = $body; + } + + $context = stream_context_create(['http' => $options]); + + $url = "$MATOMO_BASE_URL/"; + + if (isset($path)) { + $url .= $path; + } + + if (isset($query)) { + $url .= "?$query"; + } + + $counter = 1; + + debug("Call Matomo at $url"); + + do { + $result = file_get_contents($url, false, $context); + + $counter++; + + if ($result !== false || $counter > $MAX_ATTEMPTS) { + break; + } + + debug("Request error. Sleep $DELAY_AFTER_FAILURE seconds and retry!"); + + sleep($DELAY_AFTER_FAILURE); + + } while (true); + + return $result; +} + +function sendToMatomo(array &$output): bool { + global $TOKEN_AUTH; + + $body = json_encode([ + 'token_auth' => $TOKEN_AUTH, + 'requests' => $output]); + + if ($body === false) { + return false; + } + + $result = callMatomo("matomo.php", null, $body); + + debug("Called tracking API " . (!$result ? 'un' : '') . "successfully with " + . count($output) . " lines of data: " . substr($body, 0, 256)); + + return !!$result; +} + +function send(bool $onlyIfChunkSizeReached = true) { + global $output, $CHUNK_SIZE, $result; + + if (!$onlyIfChunkSizeReached || count($output) >= $CHUNK_SIZE) { + if (sendToMatomo($output)) { + // If at least one request succeeded, we consider this good. + // Otherwise, the client would re-send already processed data. + $result = true; + } + + // Remove already sent requests from memory again. + $output = []; + } +} + +function debug($message) { + global $DEBUG; + if (!$DEBUG) return; + + error_log(print_r($message, true)); +} \ No newline at end of file diff --git a/roles/podman_cleaninsights/templates/matomo.container b/roles/podman_cleaninsights/templates/matomo.container index c5e9c70..fd9d168 100644 --- a/roles/podman_cleaninsights/templates/matomo.container +++ b/roles/podman_cleaninsights/templates/matomo.container @@ -13,8 +13,8 @@ Environment=MATOMO_DATABASE_PASSWORD={{ podman_cleaninsights_mysql_password }} Environment=MATOMO_DATABASE_USERNAME={{ podman_cleaninsights_mysql_user }} Image=docker.io/matomo:5-fpm Volume=/home/{{ podman_cleaninsights_podman_rootless_user }}/matomo:/var/www/html -#Volume=/home/{{ podman_cleaninsights_podman_rootless_user }}/cleaninsights.php:/var/www/html/cleaninsights.php:ro -#Volume=/home/{{ podman_cleaninsights_podman_rootless_user }}/cleaninsights.ini:/var/www/html/cleaninsights.ini:ro +Volume=/home/{{ podman_cleaninsights_podman_rootless_user }}/matomo/cleaninsights.php:/var/www/html/cleaninsights.php:ro +Volume=/home/{{ podman_cleaninsights_podman_rootless_user }}/matomo/cleaninsights.ini:/var/www/html/cleaninsights.ini:ro Network=cleaninsights.network Network=frontend.network -- 2.47.2 From 09cbf70e134cb70f996a2b6b70160ef30c984716 Mon Sep 17 00:00:00 2001 From: Ana Custura Date: Fri, 8 Aug 2025 17:08:43 +0100 Subject: [PATCH 3/3] Feat: mount /var/www/html to the nginx container and add config to protect against CVE-2019-11043 --- roles/podman_cleaninsights/tasks/main.yml | 4 ++++ roles/podman_cleaninsights/templates/nginx.conf | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/roles/podman_cleaninsights/tasks/main.yml b/roles/podman_cleaninsights/tasks/main.yml index 296d7e0..7550975 100644 --- a/roles/podman_cleaninsights/tasks/main.yml +++ b/roles/podman_cleaninsights/tasks/main.yml @@ -79,6 +79,10 @@ podman_nginx_primary_hostname: "{{ podman_cleaninsights_web_hostname }}" podman_nginx_systemd_service_slice: cleaninsights.slice podman_nginx_systemd_service_requires: ["matomo"] + podman_nginx_additional_volumes: + - src: "/home/{{ podman_cleaninsights_podman_rootless_user }}/matomo" + dest: "/var/www/html" + options: "ro" - name: create nginx configuration file ansible.builtin.template: diff --git a/roles/podman_cleaninsights/templates/nginx.conf b/roles/podman_cleaninsights/templates/nginx.conf index 4a3d231..3c20147 100644 --- a/roles/podman_cleaninsights/templates/nginx.conf +++ b/roles/podman_cleaninsights/templates/nginx.conf @@ -41,8 +41,8 @@ server { location ~ ^/(index|matomo|cleaninsights|piwik|js/index|plugins/HeatmapSessionRecording/configs).php { # regex to split $uri to $fastcgi_script_name and $fastcgi_path + try_files $fastcgi_script_name =404; fastcgi_split_path_info ^(.+\.php)(/.+)$; - # Check that the PHP script exists before passing it #try_files $fastcgi_script_name =404; @@ -59,6 +59,10 @@ server { return 403; } + location / { + try_files $uri $uri/ =404; + } + # deny access to all cleaninsights.ini file location ~ cleaninsights.ini { return 403; -- 2.47.2