Skip to content

Commit 251b0d9

Browse files
committed
Add Umami analytics integration with runtime website ID injection
- Add Umami service to docker-compose with PostgreSQL backend - Add analytics tracking proxy via nginx (/analytics/script.js, /analytics/api/send) - Add WEBSITE_ID and UMAMI_PORT configuration to setup.sh - Add entrypoint scripts to www/web containers for runtime ID injection - Add analytics script tags to all HTML files - Dashboard accessible via direct port (default 3006)
1 parent 63a1ae8 commit 251b0d9

17 files changed

Lines changed: 230 additions & 3 deletions

cloud/deploy/Dockerfile.web

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,5 +65,14 @@ EXPOSE 80
6565
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
6666
CMD curl -sf http://localhost:80/ || exit 1
6767

68-
# nginx runs as non-root by default in this config
69-
CMD ["nginx", "-g", "daemon off;"]
68+
# Create entrypoint script to inject WEBSITE_ID at runtime
69+
RUN printf '#!/bin/sh\n\
70+
set -e\n\
71+
if [ -n "$WEBSITE_ID" ]; then\n\
72+
sed -i "s/data-website-id=\"[^\"]*\"/data-website-id=\"$WEBSITE_ID\"/g" \\\n\
73+
/usr/share/nginx/html/index.html\n\
74+
fi\n\
75+
exec nginx -g "daemon off;"\n\
76+
' > /entrypoint.sh && chmod +x /entrypoint.sh
77+
78+
ENTRYPOINT ["/entrypoint.sh"]

cloud/deploy/Dockerfile.www

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,14 @@ EXPOSE 80
2525
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
2626
CMD curl -sf http://localhost:80/ || exit 1
2727

28-
CMD ["nginx", "-g", "daemon off;"]
28+
# Create entrypoint script to inject WEBSITE_ID at runtime
29+
RUN printf '#!/bin/sh\n\
30+
set -e\n\
31+
if [ -n "$WEBSITE_ID" ]; then\n\
32+
find /usr/share/nginx/html -name "*.html" -exec \\\n\
33+
sed -i "s/data-website-id=\"[^\"]*\"/data-website-id=\"$WEBSITE_ID\"/g" {} \\;\n\
34+
fi\n\
35+
exec nginx -g "daemon off;"\n\
36+
' > /entrypoint.sh && chmod +x /entrypoint.sh
37+
38+
ENTRYPOINT ["/entrypoint.sh"]

cloud/deploy/config.sample.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,5 +174,9 @@
174174
},
175175
"superAdmin": {
176176
"emails": ["admin@example.com"]
177+
},
178+
"analytics": {
179+
"umamiSecret": "YOUR_UMAMI_APP_SECRET_32_PLUS_CHARS",
180+
"websiteId": "YOUR_UMAMI_WEBSITE_ID"
177181
}
178182
}

cloud/deploy/deploy.sh

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,16 @@ else
174174
print_success "nginx configured without SSL"
175175
fi
176176

177+
# ============================================================================
178+
# Step 1.6: Show analytics config status
179+
# ============================================================================
180+
WEBSITE_ID=$(grep -oP '^WEBSITE_ID=\K.*' "$SCRIPT_DIR/.env" 2>/dev/null || echo "")
181+
if [ -n "$WEBSITE_ID" ]; then
182+
print_info "Analytics Website ID: ${WEBSITE_ID:0:8}..."
183+
else
184+
print_info "Analytics Website ID: not configured (run setup.sh to set)"
185+
fi
186+
177187
# ============================================================================
178188

179189
# Step 2: Pull base images
@@ -207,6 +217,15 @@ docker compose stop api web www nginx
207217
print_step "Starting containers..."
208218
docker compose up -d
209219

220+
# Step 5.1: Ensure umami database exists (for analytics)
221+
print_step "Ensuring umami database exists..."
222+
if docker exec lrmcloud-postgres psql -U "${POSTGRES_USER:-lrm}" -d postgres -lqt | cut -d \| -f 1 | grep -qw umami; then
223+
print_info "Umami database already exists"
224+
else
225+
docker exec lrmcloud-postgres psql -U "${POSTGRES_USER:-lrm}" -d postgres -c "CREATE DATABASE umami;"
226+
print_success "Created umami database"
227+
fi
228+
210229
# Step 5.5: Restart nginx to ensure it picks up config changes + refresh DNS
211230
print_step "Restarting nginx..."
212231
docker compose up -d nginx

cloud/deploy/docker-compose.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ services:
1818
- api
1919
- web
2020
- www
21+
- umami
2122
healthcheck:
2223
test: ["CMD", "wget", "-q", "--spider", "http://localhost/health"]
2324
interval: 30s
@@ -56,6 +57,8 @@ services:
5657
# No ports exposed - proxied by nginx
5758
volumes:
5859
- ./data/logs/web:/var/log/nginx
60+
environment:
61+
- WEBSITE_ID=${WEBSITE_ID:-}
5962
healthcheck:
6063
test: ["CMD", "curl", "-sf", "http://localhost:80/health"]
6164
interval: 30s
@@ -75,6 +78,8 @@ services:
7578
# No ports exposed - proxied by nginx
7679
volumes:
7780
- ./data/logs/www:/var/log/nginx
81+
environment:
82+
- WEBSITE_ID=${WEBSITE_ID:-}
7883
healthcheck:
7984
test: ["CMD", "curl", "-sf", "http://localhost:80/health"]
8085
interval: 30s
@@ -130,3 +135,23 @@ services:
130135
timeout: 20s
131136
retries: 3
132137

138+
# ==========================================================================
139+
# Umami Analytics
140+
# ==========================================================================
141+
umami:
142+
image: ghcr.io/umami-software/umami:postgresql-latest
143+
container_name: lrmcloud-umami
144+
restart: unless-stopped
145+
environment:
146+
- DATABASE_URL=postgresql://${POSTGRES_USER:-lrm}:${POSTGRES_PASSWORD}@lrmcloud-postgres:5432/umami
147+
- DATABASE_TYPE=postgresql
148+
- APP_SECRET=${UMAMI_SECRET}
149+
depends_on:
150+
- postgres
151+
healthcheck:
152+
test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:3000/api/heartbeat"]
153+
interval: 30s
154+
timeout: 10s
155+
retries: 3
156+
start_period: 30s
157+

cloud/deploy/init-db.sql

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,6 @@ CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
66

77
-- Grant privileges (database and user created by POSTGRES_DB/POSTGRES_USER env vars)
88
-- Additional setup can be added here as needed
9+
10+
-- Umami analytics database
11+
CREATE DATABASE umami;

cloud/deploy/nginx/nginx.conf.template

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ http {
5252
# Rate limiting zones
5353
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
5454
limit_req_zone $binary_remote_addr zone=api:10m rate=100r/s;
55+
limit_req_zone $binary_remote_addr zone=analytics:10m rate=10r/s;
5556

5657
# Upstream servers
5758
upstream api {
@@ -69,6 +70,11 @@ http {
6970
keepalive 8;
7071
}
7172

73+
upstream umami {
74+
server lrmcloud-umami:3000;
75+
keepalive 8;
76+
}
77+
7278
# =========================================================================
7379
# Security Headers
7480
# =========================================================================
@@ -198,6 +204,33 @@ http {
198204
proxy_set_header X-Forwarded-Proto $scheme;
199205
}
200206

207+
# =====================================================
208+
# Analytics Tracking (Umami) - /analytics/*
209+
# Dashboard accessible via direct port (see setup.sh)
210+
# =====================================================
211+
212+
# Tracking script
213+
location = /analytics/script.js {
214+
proxy_pass http://umami/script.js;
215+
proxy_http_version 1.1;
216+
proxy_set_header Host $host;
217+
expires 1d;
218+
add_header Cache-Control "public";
219+
}
220+
221+
# Tracking API endpoint
222+
location = /analytics/api/send {
223+
limit_req zone=analytics burst=20 nodelay;
224+
limit_req_status 429;
225+
226+
proxy_pass http://umami/api/send;
227+
proxy_http_version 1.1;
228+
proxy_set_header Host $host;
229+
proxy_set_header X-Real-IP $remote_addr;
230+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
231+
proxy_set_header X-Forwarded-Proto $scheme;
232+
}
233+
201234
# =====================================================
202235
# Static Site / Landing Page (/)
203236
# =====================================================
@@ -340,6 +373,33 @@ http {
340373
proxy_set_header X-Forwarded-Proto $scheme;
341374
}
342375

376+
# =====================================================
377+
# Analytics Tracking (Umami) - /analytics/*
378+
# Dashboard accessible via direct port (see setup.sh)
379+
# =====================================================
380+
381+
# Tracking script
382+
location = /analytics/script.js {
383+
proxy_pass http://umami/script.js;
384+
proxy_http_version 1.1;
385+
proxy_set_header Host $host;
386+
expires 1d;
387+
add_header Cache-Control "public";
388+
}
389+
390+
# Tracking API endpoint
391+
location = /analytics/api/send {
392+
limit_req zone=analytics burst=20 nodelay;
393+
limit_req_status 429;
394+
395+
proxy_pass http://umami/api/send;
396+
proxy_http_version 1.1;
397+
proxy_set_header Host $host;
398+
proxy_set_header X-Real-IP $remote_addr;
399+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
400+
proxy_set_header X-Forwarded-Proto $scheme;
401+
}
402+
343403
# =====================================================
344404
# Static Site / Landing Page (/)
345405
# =====================================================

cloud/deploy/setup.sh

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ CURRENT_POSTGRES_PORT=$(env_get 'POSTGRES_PORT' '5432')
9797
CURRENT_REDIS_PORT=$(env_get 'REDIS_PORT' '6379')
9898
CURRENT_MINIO_PORT=$(env_get 'MINIO_PORT' '9000')
9999
CURRENT_MINIO_CONSOLE=$(env_get 'MINIO_CONSOLE' '9001')
100+
CURRENT_UMAMI_PORT=$(env_get 'UMAMI_PORT' '')
100101
CURRENT_MINIO_USER=$(env_get 'MINIO_USER' 'lrmcloud')
101102
CURRENT_MINIO_PASSWORD=$(env_get 'MINIO_PASSWORD' '')
102103

@@ -120,6 +121,8 @@ OLD_MINIO_PASSWORD="$CURRENT_MINIO_PASSWORD"
120121
CURRENT_JWT=$(config_get '.auth.jwtSecret' '')
121122
CURRENT_ENCRYPTION=$(config_get '.encryption.tokenKey' '')
122123
CURRENT_API_KEY_SECRET=$(config_get '.apiKeyMasterSecret' '')
124+
CURRENT_UMAMI_SECRET=$(config_get '.analytics.umamiSecret' '')
125+
CURRENT_WEBSITE_ID=$(config_get '.analytics.websiteId' '')
123126

124127
CURRENT_MAIL_BACKEND=$(config_get '.mail.backend' 'smtp')
125128
CURRENT_MAIL_HOST=$(config_get '.mail.host' 'localhost')
@@ -210,6 +213,26 @@ MINIO_PORT=${MINIO_PORT:-$CURRENT_MINIO_PORT}
210213
read -p "MinIO Console Port (0=internal only) [$CURRENT_MINIO_CONSOLE]: " MINIO_CONSOLE
211214
MINIO_CONSOLE=${MINIO_CONSOLE:-$CURRENT_MINIO_CONSOLE}
212215

216+
# Umami Analytics Dashboard Port
217+
if [ -n "$CURRENT_UMAMI_PORT" ]; then
218+
CURRENT_UMAMI_ENABLED="y"
219+
UMAMI_DEFAULT="Y/n"
220+
else
221+
CURRENT_UMAMI_ENABLED="n"
222+
UMAMI_DEFAULT="y/N"
223+
fi
224+
print_info "Umami dashboard requires direct port access (tracking works via main site)"
225+
read -p "Expose Umami dashboard port? [$UMAMI_DEFAULT]: " ENABLE_UMAMI
226+
ENABLE_UMAMI=${ENABLE_UMAMI:-$CURRENT_UMAMI_ENABLED}
227+
228+
if [ "$ENABLE_UMAMI" = "y" ] || [ "$ENABLE_UMAMI" = "Y" ]; then
229+
DEFAULT_UMAMI_PORT=${CURRENT_UMAMI_PORT:-3006}
230+
read -p "Umami Direct Port [$DEFAULT_UMAMI_PORT]: " UMAMI_PORT
231+
UMAMI_PORT=${UMAMI_PORT:-$DEFAULT_UMAMI_PORT}
232+
else
233+
UMAMI_PORT=""
234+
fi
235+
213236
read -p "Environment [$CURRENT_ENV]: " ENVIRONMENT
214237
ENVIRONMENT=${ENVIRONMENT:-$CURRENT_ENV}
215238

@@ -420,6 +443,7 @@ DEFAULT_JWT_SECRET=${CURRENT_JWT:-$(generate_password)$(generate_password)}
420443
DEFAULT_ENCRYPTION_KEY=${CURRENT_ENCRYPTION:-$(generate_key)}
421444
DEFAULT_API_KEY_SECRET=${CURRENT_API_KEY_SECRET:-$(generate_password)$(generate_password)}
422445
DEFAULT_MINIO_PASSWORD=${CURRENT_MINIO_PASSWORD:-$(generate_password)}
446+
DEFAULT_UMAMI_SECRET=${CURRENT_UMAMI_SECRET:-$(generate_password)}
423447

424448
# Prompt for PostgreSQL password
425449
if [ -n "$CURRENT_DB_PASSWORD" ]; then
@@ -475,6 +499,20 @@ else
475499
fi
476500
API_KEY_SECRET=${INPUT_API_KEY_SECRET:-$DEFAULT_API_KEY_SECRET}
477501

502+
# Prompt for Umami analytics secret
503+
if [ -n "$CURRENT_UMAMI_SECRET" ]; then
504+
print_info "Umami secret exists (hidden)"
505+
read -p "Umami Secret [keep existing]: " INPUT_UMAMI_SECRET
506+
else
507+
read -p "Umami Secret [$DEFAULT_UMAMI_SECRET]: " INPUT_UMAMI_SECRET
508+
fi
509+
UMAMI_SECRET=${INPUT_UMAMI_SECRET:-$DEFAULT_UMAMI_SECRET}
510+
511+
# Prompt for Analytics Website ID
512+
print_info "Website ID is obtained from Umami dashboard after creating a website"
513+
read -p "Analytics Website ID [${CURRENT_WEBSITE_ID:-skip for now}]: " INPUT_WEBSITE_ID
514+
WEBSITE_ID=${INPUT_WEBSITE_ID:-$CURRENT_WEBSITE_ID}
515+
478516
# ============================================================================
479517
# GitHub OAuth Configuration (optional)
480518
# ============================================================================
@@ -626,6 +664,10 @@ NEW_CONFIG=$(cat <<EOF
626664
"enterpriseTranslationChars": ${CURRENT_ENTERPRISE_TRANSLATION_CHARS},
627665
"enterpriseOtherChars": ${CURRENT_ENTERPRISE_OTHER_CHARS},
628666
"maxKeysPerProject": ${CURRENT_MAX_KEYS}
667+
},
668+
"analytics": {
669+
"umamiSecret": "${UMAMI_SECRET}",
670+
"websiteId": "${WEBSITE_ID}"
629671
}
630672
}
631673
EOF
@@ -678,6 +720,11 @@ REDIS_PASSWORD=${REDIS_PASSWORD}
678720
MINIO_USER=lrmcloud
679721
MINIO_PASSWORD=${MINIO_PASSWORD}
680722
723+
# Analytics
724+
UMAMI_SECRET=${UMAMI_SECRET}
725+
WEBSITE_ID=${WEBSITE_ID}
726+
UMAMI_PORT=${UMAMI_PORT}
727+
681728
# Environment
682729
ENVIRONMENT=${ENVIRONMENT}
683730
EOF
@@ -814,6 +861,16 @@ elif [ -n "$MINIO_CONSOLE" ] && [ "$MINIO_CONSOLE" != "0" ]; then
814861
EOF
815862
fi
816863

864+
# Add Umami port if direct access enabled
865+
if [ -n "$UMAMI_PORT" ] && [ "$UMAMI_PORT" != "0" ]; then
866+
cat >> "$OVERRIDE_FILE" <<EOF
867+
868+
umami:
869+
ports:
870+
- "${UMAMI_PORT}:3000"
871+
EOF
872+
fi
873+
817874
print_success "Created docker-compose.override.yml"
818875

819876
# ============================================================================
@@ -924,6 +981,14 @@ until docker exec lrmcloud-postgres pg_isready -U lrm -d lrmcloud &> /dev/null;
924981
done
925982
print_success "PostgreSQL is ready"
926983

984+
# Ensure umami database exists (for analytics)
985+
if docker exec lrmcloud-postgres psql -U lrm -d postgres -lqt | cut -d \| -f 1 | grep -qw umami; then
986+
print_info "Umami database already exists"
987+
else
988+
docker exec lrmcloud-postgres psql -U lrm -d postgres -c "CREATE DATABASE umami;"
989+
print_success "Created umami database"
990+
fi
991+
927992
# Update PostgreSQL password if changed
928993
if [ -n "$OLD_POSTGRES_PASSWORD" ] && [ "$POSTGRES_PASSWORD" != "$OLD_POSTGRES_PASSWORD" ]; then
929994
print_step "Updating PostgreSQL password..."
@@ -1004,6 +1069,11 @@ if [ -n "$MINIO_CONSOLE" ] && [ "$MINIO_CONSOLE" != "0" ]; then
10041069
else
10051070
echo " • MinIO Console: (internal only)"
10061071
fi
1072+
if [ -n "$UMAMI_PORT" ] && [ "$UMAMI_PORT" != "0" ]; then
1073+
echo " • Analytics: http://localhost:${UMAMI_PORT}"
1074+
else
1075+
echo " • Analytics: (internal only)"
1076+
fi
10071077
echo ""
10081078
echo "Configuration: $CONFIG_FILE"
10091079
echo ""

cloud/src/LrmCloud.Web/wwwroot/index.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@
3636
<link rel="stylesheet" href="css/app.css?v=__BUILD_VERSION__" />
3737
<link rel="icon" type="image/png" href="favicon.png" />
3838
<link rel="apple-touch-icon" href="icon-192.png" />
39+
40+
<!-- Analytics -->
41+
<script defer src="/analytics/script.js" data-website-id="lrm-cloud" data-host-url="/analytics"></script>
3942
</head>
4043

4144
<body>

cloud/src/www/docs/cloud.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,9 @@
494494
}
495495
}
496496
</style>
497+
498+
<!-- Analytics -->
499+
<script defer src="/analytics/script.js" data-website-id="lrm-cloud" data-host-url="/analytics"></script>
497500
</head>
498501
<body>
499502
<!-- Header -->

0 commit comments

Comments
 (0)