Skip to content

Commit 99f3304

Browse files
committed
Added a basic umami setup for visitor statistics
1 parent a367c4d commit 99f3304

6 files changed

Lines changed: 100 additions & 2 deletions

File tree

app/views/layouts/application.html.slim

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ html data-theme="light"
2727
= stylesheet_link_tag "application", "data-turbo-track": "reload"
2828
= javascript_importmap_tags
2929
script[src="https://kit.fontawesome.com/92fb6aa8ba.js"]
30+
- if ENV["UMAMI_WEBSITE_ID"].present?
31+
- umami_host = ENV["UMAMI_HOST"].presence || "https://umami.hackorum.dev"
32+
script[async defer data-website-id=ENV["UMAMI_WEBSITE_ID"] src="#{umami_host}/script.js"]
3033
body
3134
- if user_signed_in? && current_user.username.blank?
3235
.global-warning

deploy/.env.example

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,18 @@ GOOGLE_REDIRECT_URI=https://hackorum.example.com/auth/google_oauth2/callback
2929
# Hostname for Caddy (update deploy/Caddyfile too)
3030
APP_HOST=hackorum.example.com
3131

32+
# Umami analytics (self-hosted)
33+
# Generate secrets with: openssl rand -hex 32
34+
UMAMI_DB=umami
35+
UMAMI_DB_USER=umami
36+
UMAMI_DB_PASSWORD=change-me
37+
UMAMI_DATABASE_URL=postgresql://umami:change-me@db:5432/umami
38+
UMAMI_APP_SECRET=change-me
39+
UMAMI_HASH_SALT=change-me
40+
UMAMI_WEBSITE_ID=
41+
# Optional override; defaults to https://umami.hackorum.dev
42+
# UMAMI_HOST=https://umami.hackorum.dev
43+
3244
# Mail configuration via Mailgun
3345
# Sign up at https://mailgun.com and verify your domain (use subdomain mg.example.com)
3446
# Get credentials from Mailgun dashboard → Sending → Domain settings → SMTP credentials

deploy/Caddyfile.example

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,10 @@ hackorum.example.com {
88
output stdout
99
}
1010
}
11+
12+
umami.hackorum.dev {
13+
reverse_proxy umami:3000
14+
log {
15+
output stdout
16+
}
17+
}

deploy/README.md

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ This is a minimal, single-host setup for running Hackorum on a VPS (e.g., Hetzne
55
- IMAP runner (continuous)
66
- Postgres with WAL archiving to a local volume
77
- Caddy for TLS / reverse proxy
8+
- Umami analytics (self-hosted)
89
- Autoheal watchdog to restart unhealthy containers
910
- Local base backups + WAL retention scripts (no external storage)
1011

@@ -28,8 +29,14 @@ This is a minimal, single-host setup for running Hackorum on a VPS (e.g., Hetzne
2829

2930
3) Update Caddyfile domain:
3031
- Edit `deploy/Caddyfile` and replace `hackorum.example.com` and contact email.
32+
- Ensure the Umami host is set to `umami.hackorum.dev` (see `deploy/Caddyfile.example`).
3133

32-
4) Build and start:
34+
4) Configure Umami analytics:
35+
- Set `UMAMI_APP_SECRET` and `UMAMI_HASH_SALT` in `deploy/.env`.
36+
- Set `UMAMI_DB_USER` and `UMAMI_DB_PASSWORD` for the dedicated Umami database user.
37+
- Confirm `UMAMI_DATABASE_URL` points at the shared Postgres service.
38+
39+
5) Build and start:
3340
```bash
3441
cd deploy
3542
docker compose up -d --build
@@ -39,19 +46,45 @@ This is a minimal, single-host setup for running Hackorum on a VPS (e.g., Hetzne
3946
- `imap_worker`: continuous IMAP ingest
4047
- `db`: Postgres 18 with WAL archiving to `/var/lib/postgresql/wal-archive`
4148
- `caddy`: TLS + reverse proxy on :80/:443
49+
- `umami`: self-hosted analytics UI/API on port 3000 (internal)
4250
- `autoheal`: restarts containers whose healthchecks fail
4351

44-
5) Verify:
52+
6) Verify:
4553
- Browse to your domain; or `curl -f http://localhost:3000/up` from the host (`docker compose exec web ...` inside the network).
4654

4755
## Observability
4856
- Query stats: pg_stat_statements is preloaded via the Postgres config and created on first init via `/docker-entrypoint-initdb.d/01_pg_stat_statements.sql`. For existing databases, run `CREATE EXTENSION IF NOT EXISTS pg_stat_statements;` once. PgHero is available at `/pghero` for signed-in admin users.
4957
- Request-level profiling: rack-mini-profiler is available; in production it renders only for signed-in admin users.
5058

59+
## Analytics (Umami, self-hosted)
60+
Umami runs as a separate service and uses the same Postgres container (recommended: separate database).
61+
62+
Initialization:
63+
- Fresh install: the init script creates the Umami database (`UMAMI_DB`, default `umami`) on first Postgres boot.
64+
- Existing database: create it once manually (adjust user/db if needed):
65+
```bash
66+
docker compose exec db psql -U postgres -d postgres -c "CREATE ROLE umami LOGIN PASSWORD 'change-me';"
67+
docker compose exec db psql -U postgres -d postgres -c "CREATE DATABASE umami OWNER umami;"
68+
```
69+
70+
Access:
71+
- Add a dedicated hostname in Caddy (example in `deploy/Caddyfile.example`).
72+
- Visit `https://umami.hackorum.dev` and log in (default `admin` / `umami`, then change the password).
73+
- Create a website in Umami and copy the `website_id` into `UMAMI_WEBSITE_ID` in `deploy/.env`.
74+
5175
## Environment variables (deploy/.env)
5276
- `SECRET_KEY_BASE` (required)
5377
- `DATABASE_URL` (defaults to local Postgres via env interpolation)
5478
- `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB` (for the db container)
79+
- Umami:
80+
- `UMAMI_DB` (database name for init script; default `umami`)
81+
- `UMAMI_DB_USER` (dedicated Umami database user)
82+
- `UMAMI_DB_PASSWORD` (dedicated Umami database password)
83+
- `UMAMI_APP_SECRET` (required, 32+ random hex chars recommended)
84+
- `UMAMI_HASH_SALT` (required, 32+ random hex chars recommended)
85+
- `UMAMI_DATABASE_URL` (defaults to local Postgres with `UMAMI_DB`)
86+
- `UMAMI_WEBSITE_ID` (required for client-side tracking)
87+
- `UMAMI_HOST` (optional override; defaults to https://umami.hackorum.dev)
5588
- IMAP:
5689
- `IMAP_USERNAME`, `IMAP_PASSWORD`, `IMAP_MAILBOX_LABEL`
5790
- Optional: `IMAP_HOST`, `IMAP_PORT`, `IMAP_SSL`

deploy/docker-compose.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ services:
88
POSTGRES_USER: ${POSTGRES_USER:-postgres}
99
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
1010
POSTGRES_DB: ${POSTGRES_DB:-hackorum}
11+
UMAMI_DB: ${UMAMI_DB:-umami}
12+
UMAMI_DB_USER: ${UMAMI_DB_USER:-umami}
13+
UMAMI_DB_PASSWORD: ${UMAMI_DB_PASSWORD:-umami}
1114
volumes:
1215
# Postgres 18+ stores data under /var/lib/postgresql/<major>/main
1316
- pgdata:/var/lib/postgresql
@@ -66,6 +69,22 @@ services:
6669
labels:
6770
autoheal: "true"
6871

72+
umami:
73+
image: umami/umami:postgresql-latest
74+
env_file: .env
75+
environment:
76+
DATABASE_URL: ${UMAMI_DATABASE_URL:-postgresql://${UMAMI_DB_USER:-umami}:${UMAMI_DB_PASSWORD:-umami}@db:5432/${UMAMI_DB:-umami}}
77+
APP_SECRET: ${UMAMI_APP_SECRET}
78+
HASH_SALT: ${UMAMI_HASH_SALT}
79+
depends_on:
80+
db:
81+
condition: service_healthy
82+
expose:
83+
- "3000"
84+
restart: unless-stopped
85+
labels:
86+
autoheal: "true"
87+
6988
imap_worker:
7089
build: ..
7190
command: ["bundle", "exec", "ruby", "script/imap_idle.rb"]
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
UMAMI_DB="${UMAMI_DB:-umami}"
5+
UMAMI_DB_USER="${UMAMI_DB_USER:-umami}"
6+
UMAMI_DB_PASSWORD="${UMAMI_DB_PASSWORD:-umami}"
7+
8+
psql -v ON_ERROR_STOP=1 \
9+
-v UMAMI_DB="$UMAMI_DB" \
10+
-v UMAMI_DB_USER="$UMAMI_DB_USER" \
11+
-v UMAMI_DB_PASSWORD="$UMAMI_DB_PASSWORD" \
12+
--username "$POSTGRES_USER" \
13+
--dbname "postgres" <<'EOSQL'
14+
DO $$
15+
BEGIN
16+
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = :'UMAMI_DB_USER') THEN
17+
EXECUTE format('CREATE ROLE %I LOGIN PASSWORD %L', :'UMAMI_DB_USER', :'UMAMI_DB_PASSWORD');
18+
END IF;
19+
END
20+
$$;
21+
22+
SELECT format('CREATE DATABASE %I OWNER %I', :'UMAMI_DB', :'UMAMI_DB_USER')
23+
WHERE NOT EXISTS (SELECT 1 FROM pg_database WHERE datname = :'UMAMI_DB')\gexec
24+
EOSQL

0 commit comments

Comments
 (0)