From c9be797e746b4e6b28f3dde72cab7d8626c9dba3 Mon Sep 17 00:00:00 2001 From: matthewberry Date: Wed, 28 Oct 2026 21:02:40 -0500 Subject: [PATCH 1/2] Add a dormant Matomo proxy mode, switched by MATOMO_MODE With MATOMO_MODE=proxied, entrypoint.sh writes nginx locations that serve matomo.js at /px/s.js and forward hits from /px/h to matomo.php, and the app initializes the tracker against those paths with cookies disabled. Any other value keeps today's direct tracking. The locations are written only when the flag is on, because nginx won't start if a proxy_pass host doesn't resolve. The tracker is now configured in deferred mode, after envvars.json loads. --- chart/templates/deployment.yaml | 8 +++++ chart/values.yaml | 2 ++ entrypoint.sh | 20 +++++++++++ nginx/default.conf | 4 +++ src/app/models/envvars.ts | 4 +++ src/app/testing/environment-service.stub.ts | 1 + src/assets/config/envvars.json | 4 ++- src/assets/config/envvars.tpl.json | 4 ++- src/main.ts | 37 ++++++++++++++------- 9 files changed, 70 insertions(+), 14 deletions(-) diff --git a/chart/templates/deployment.yaml b/chart/templates/deployment.yaml index eb23a85..ce6d928 100644 --- a/chart/templates/deployment.yaml +++ b/chart/templates/deployment.yaml @@ -73,6 +73,14 @@ spec: # Optional: config without it means no banner, not a failed pod optional: true + - name: MATOMO_MODE + valueFrom: + configMapKeyRef: + name: {{ .Release.Name }}-config + key: matomoMode + # Optional: config without it means direct tracking, not a failed pod + optional: true + ports: - containerPort: 80 imagePullPolicy: Always diff --git a/chart/values.yaml b/chart/values.yaml index 945616c..18995b7 100644 --- a/chart/values.yaml +++ b/chart/values.yaml @@ -14,4 +14,6 @@ config: frontendOnly: "false" environment: local prodUrl: https://test.platform.moleculemaker.org + # 'proxied': Matomo hits go through this app's nginx, without cookies. Anything else: direct. + matomoMode: direct extraDeploy: [] diff --git a/entrypoint.sh b/entrypoint.sh index fdcecaf..690f850 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,3 +1,23 @@ #!/bin/bash +# With MATOMO_MODE=proxied, serve the Matomo tracker from this app's own origin, at paths +# ad blockers don't recognize: no "matomo" or "piwik" in the path, and no .php extension +# (EasyPrivacy blocks *.php?action_name=). Hits keep the visitor's IP in X-Forwarded-For. +mkdir -p /etc/nginx/matomo-proxy +rm -f /etc/nginx/matomo-proxy/*.conf +if [ "$MATOMO_MODE" = "proxied" ]; then + upstream="${MATOMO_UPSTREAM:-http://matomo.matomo.svc.cluster.local}" + cat > /etc/nginx/matomo-proxy/locations.conf < /usr/share/nginx/html/assets/config/envvars.json && exec nginx -g 'daemon off;' diff --git a/nginx/default.conf b/nginx/default.conf index a7ab02f..40d6147 100644 --- a/nginx/default.conf +++ b/nginx/default.conf @@ -48,6 +48,10 @@ server { #error_page 404 /404.html; + # Matomo proxy locations, written by entrypoint.sh only when MATOMO_MODE=proxied. + # nginx won't start if a proxy_pass host doesn't resolve, so they can't live here. + include /etc/nginx/matomo-proxy/*.conf; + # redirect server error pages to the static page /50x.html # error_page 500 502 503 504 /50x.html; diff --git a/src/app/models/envvars.ts b/src/app/models/envvars.ts index 3296c9a..abab364 100644 --- a/src/app/models/envvars.ts +++ b/src/app/models/envvars.ts @@ -13,4 +13,8 @@ export interface EnvVars { environment: string; // The released tool, linked from that banner. Empty for local development. prodUrl: string; + + // 'proxied' sends Matomo hits through this app's nginx without cookies; anything else + // (including empty) sends them straight to matomo.mmli2.ncsa.illinois.edu. + matomoMode: string; } diff --git a/src/app/testing/environment-service.stub.ts b/src/app/testing/environment-service.stub.ts index af0e84b..92f513c 100644 --- a/src/app/testing/environment-service.stub.ts +++ b/src/app/testing/environment-service.stub.ts @@ -15,6 +15,7 @@ const envConfig: EnvVars = { frontendOnly: 'true', environment: 'prod', // no staging banner in other components' specs prodUrl: 'http://environment-service.stub', + matomoMode: 'direct', }; export function provideEnvironmentServiceStub() { diff --git a/src/assets/config/envvars.json b/src/assets/config/envvars.json index 4f214c8..749c290 100644 --- a/src/assets/config/envvars.json +++ b/src/assets/config/envvars.json @@ -8,5 +8,7 @@ "frontendOnly": "false", "environment": "local", - "prodUrl": "" + "prodUrl": "", + + "matomoMode": "direct" } diff --git a/src/assets/config/envvars.tpl.json b/src/assets/config/envvars.tpl.json index 028f4b8..5016240 100644 --- a/src/assets/config/envvars.tpl.json +++ b/src/assets/config/envvars.tpl.json @@ -8,6 +8,8 @@ "frontendOnly": "${FRONTEND_ONLY}", "environment": "${ENVIRONMENT}", - "prodUrl": "${PROD_URL}" + "prodUrl": "${PROD_URL}", + + "matomoMode": "${MATOMO_MODE}" } diff --git a/src/main.ts b/src/main.ts index b6e0f0e..9a0760d 100644 --- a/src/main.ts +++ b/src/main.ts @@ -3,7 +3,7 @@ import { importProvidersFrom, inject, provideAppInitializer } from '@angular/cor import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; import { bootstrapApplication } from '@angular/platform-browser'; import { ReactiveFormsModule } from '@angular/forms'; -import { MatomoModule, MatomoRouterModule } from 'ngx-matomo-client'; +import { MatomoInitializerService, MatomoModule, MatomoRouterModule, MatomoTracker } from 'ngx-matomo-client'; import { MenuModule } from 'primeng/menu'; import { ApiModule, Configuration } from '@api/mmli-backend/v1'; import { DialogModule } from 'primeng/dialog'; @@ -15,10 +15,6 @@ import { APP_IDENTITY } from './app/app-identity'; import { definePreset } from '@primeuix/themes'; import Aura from '@primeng/themes/aura'; -const initAppFn = (envService: EnvironmentService) => { - return () => envService.loadEnvConfig("/assets/config/envvars.json"); -}; - const MyPreset = definePreset(Aura, { semantic: { colorScheme: { @@ -38,10 +34,8 @@ const MyPreset = definePreset(Aura, { bootstrapApplication(AppComponent, { providers: [ importProvidersFrom( - MatomoModule.forRoot({ - siteId: APP_IDENTITY.matomoSiteId, - trackerUrl: 'https://matomo.mmli2.ncsa.illinois.edu/' - }), + // configured once envvars.json has loaded, below + MatomoModule.forRoot({ mode: 'deferred' }), MatomoRouterModule.forRoot(), MenuModule, ApiModule.forRoot(() => new Configuration()), @@ -50,9 +44,28 @@ bootstrapApplication(AppComponent, { AppRoutingModule, ), EnvironmentService, - provideAppInitializer(() => { - const initializerFn = (initAppFn)(inject(EnvironmentService)); - return initializerFn(); + provideAppInitializer(async () => { + const envService = inject(EnvironmentService); + const matomo = inject(MatomoInitializerService); + const tracker = inject(MatomoTracker); + await envService.loadEnvConfig("/assets/config/envvars.json"); + + if (envService.getEnvConfig().matomoMode === 'proxied') { + // Through this app's own nginx (locations written by entrypoint.sh), which ad + // blockers don't recognize, and without cookies, so no consent banner is needed. + tracker.disableCookies(); + matomo.initializeTracker({ + siteId: APP_IDENTITY.matomoSiteId, + trackerUrl: '/px/', + trackerUrlSuffix: 'h', + scriptUrl: '/px/s.js', + }); + } else { + matomo.initializeTracker({ + siteId: APP_IDENTITY.matomoSiteId, + trackerUrl: 'https://matomo.mmli2.ncsa.illinois.edu/', + }); + } }), provideHttpClient(withInterceptorsFromDi()), provideStore(), From be06b025c13faa4ade032fcca4d12bc74346d32c Mon Sep 17 00:00:00 2001 From: matthewberry Date: Wed, 28 Oct 2026 21:06:03 -0500 Subject: [PATCH 2/2] Don't forward this app's cookies or credentials through the Matomo proxy --- entrypoint.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/entrypoint.sh b/entrypoint.sh index 690f850..0a92f72 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -11,11 +11,18 @@ if [ "$MATOMO_MODE" = "proxied" ]; then location = /px/s.js { proxy_pass ${upstream}/matomo.js; proxy_ssl_server_name on; + # Don't pass this app's cookies or credentials to Matomo, or let Matomo set cookies here. + proxy_set_header Cookie ""; + proxy_set_header Authorization ""; + proxy_hide_header Set-Cookie; } location = /px/h { proxy_pass ${upstream}/matomo.php; proxy_ssl_server_name on; proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; + proxy_set_header Cookie ""; + proxy_set_header Authorization ""; + proxy_hide_header Set-Cookie; } CONF fi