mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-28 03:57:43 +02:00
chore(analytics): Umami aus i18n, CSP, website-blocks-Feature, infra (Welle D)
i18n×5 (settings-footnote → 'kein Web-Analytics'), security-headers CSP (stats.mana.how raus, GlitchTip bleibt), website-blocks (Provider-Enum 'umami' raus, plausible bleibt; Analytics/Inspector/Test), privacy-faq DE/EN, infra gpu-box .env/compose/README. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
394e520a26
commit
3ea8703a94
14 changed files with 20 additions and 36 deletions
|
|
@ -320,7 +320,7 @@
|
|||
"total_entities": "Gesamt-Entitäten",
|
||||
"total_entities_desc": "Datensätze über alle Apps hinweg",
|
||||
"projects_with_data": "Projekte mit Daten",
|
||||
"footnote_pre": "Keine Tracking-Cookies — anonyme Analyse via Umami. Details in der ",
|
||||
"footnote_pre": "Keine Tracking-Cookies — kein Web-Analytics. Details in der ",
|
||||
"footnote_link": "Datenschutzerklärung",
|
||||
"footnote_post": ".",
|
||||
"auth_title": "Authentifizierung",
|
||||
|
|
|
|||
|
|
@ -320,7 +320,7 @@
|
|||
"total_entities": "Total entities",
|
||||
"total_entities_desc": "Records across all apps",
|
||||
"projects_with_data": "Projects with data",
|
||||
"footnote_pre": "No tracking cookies — anonymous analytics via Umami. Details in the ",
|
||||
"footnote_pre": "No tracking cookies — no web analytics. Details in the ",
|
||||
"footnote_link": "privacy policy",
|
||||
"footnote_post": ".",
|
||||
"auth_title": "Authentication",
|
||||
|
|
|
|||
|
|
@ -320,7 +320,7 @@
|
|||
"total_entities": "Entidades totales",
|
||||
"total_entities_desc": "Registros en todas las apps",
|
||||
"projects_with_data": "Proyectos con datos",
|
||||
"footnote_pre": "Sin cookies de seguimiento — analítica anónima vía Umami. Detalles en la ",
|
||||
"footnote_pre": "Sin cookies de seguimiento — sin analítica web. Detalles en la ",
|
||||
"footnote_link": "política de privacidad",
|
||||
"footnote_post": ".",
|
||||
"auth_title": "Autenticación",
|
||||
|
|
|
|||
|
|
@ -320,7 +320,7 @@
|
|||
"total_entities": "Entités totales",
|
||||
"total_entities_desc": "Enregistrements toutes apps confondues",
|
||||
"projects_with_data": "Projets avec données",
|
||||
"footnote_pre": "Pas de cookies de suivi — analyse anonyme via Umami. Détails dans la ",
|
||||
"footnote_pre": "Pas de cookies de suivi — pas d.analyse web. Détails dans la ",
|
||||
"footnote_link": "politique de confidentialité",
|
||||
"footnote_post": ".",
|
||||
"auth_title": "Authentification",
|
||||
|
|
|
|||
|
|
@ -320,7 +320,7 @@
|
|||
"total_entities": "Entità totali",
|
||||
"total_entities_desc": "Record su tutte le app",
|
||||
"projects_with_data": "Progetti con dati",
|
||||
"footnote_pre": "Niente cookie di tracciamento — analitica anonima tramite Umami. Dettagli nell'",
|
||||
"footnote_pre": "Niente cookie di tracciamento — nessuna analisi web. Dettagli nell'",
|
||||
"footnote_link": "informativa sulla privacy",
|
||||
"footnote_post": ".",
|
||||
"auth_title": "Autenticazione",
|
||||
|
|
|
|||
|
|
@ -5,17 +5,12 @@
|
|||
|
||||
# ─── Postgres-Credentials ────────────────────────────────────
|
||||
# Mini-Postgres-Passwort (gleiches wie .env.macmini POSTGRES_PASSWORD)
|
||||
# Wird von Forgejo + Umami genutzt, die ihren DB-Host auf 192.168.178.131:5432 zeigen.
|
||||
# Wird von Forgejo genutzt, die ihren DB-Host auf 192.168.178.131:5432 zeigen.
|
||||
POSTGRES_PASSWORD=
|
||||
|
||||
# ─── Grafana ─────────────────────────────────────────────────
|
||||
GF_ADMIN_PASSWORD=
|
||||
|
||||
# ─── Umami ───────────────────────────────────────────────────
|
||||
# Identisch mit dem Wert auf dem Mini halten, sonst werden Sessions invalidiert.
|
||||
# Hexlich aus `openssl rand -base64 32`.
|
||||
UMAMI_APP_SECRET=
|
||||
|
||||
# ─── Telegram-Notifier (alert-notifier) ──────────────────────
|
||||
TELEGRAM_BOT_TOKEN=
|
||||
TELEGRAM_CHAT_ID=
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ Hilfsdienste vom Mini abgegeben — siehe [`docs/PLAN_OPTION_C.md`](../docs/PLAN
|
|||
|---|---|---|
|
||||
| `grafana` | `:8000` → `grafana.mana.how` | Dashboards (Phase 2a) |
|
||||
| `forgejo` | `:3041` → `git.mana.how` | Git-Mirror (Phase 2b) |
|
||||
| `umami` | `:8010` → `stats.mana.how` | Web-Analytics (Phase 2b) |
|
||||
| `victoriametrics` | `:9090` (intern) | Metrics-Store (Phase 2c) |
|
||||
| `loki` | `:3100` (intern) | Log-Store (Phase 2c) |
|
||||
| `pushgateway`, `blackbox-exporter`, `vmalert`, `alertmanager`, `alert-notifier` | (intern) | Metrics + Alerting (Phase 2c) |
|
||||
|
|
@ -78,7 +77,6 @@ Aktive Public-Hostnames (Stand 2026-05-07, config v28):
|
|||
| `gpu-ollama.mana.how` | `:11434` | Ollama API |
|
||||
| `grafana.mana.how` | `:8000` | Phase 2a |
|
||||
| `git.mana.how` | `:3041` | Forgejo (Phase 2b) |
|
||||
| `stats.mana.how` | `:8010` | Umami (Phase 2b) |
|
||||
| `glitchtip.mana.how` | `:8020` | Glitchtip (Phase 2d) |
|
||||
| `status.mana.how` | `:8090` | Status-Page (Phase 2e) |
|
||||
| `photon.mana.how` | `:2322` | Photon Geocoder (cross-LAN-Workaround für mana-geocoding's Probe + privacy-local Provider) |
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
# Production-Hot-Path bleibt unverändert auf dem Mini.
|
||||
#
|
||||
# Architektur:
|
||||
# - Apps hier (Grafana, Forgejo, Umami, Glitchtip-future) lesen Postgres
|
||||
# - Apps hier (Grafana, Forgejo, Glitchtip-future) lesen Postgres
|
||||
# auf 192.168.178.131:5432 als SoT.
|
||||
# - VictoriaMetrics scrapt Mac-Mini-Services via 192.168.178.131:<port>
|
||||
# (siehe monitoring/prometheus/prometheus.yml) und GPU-Box-eigene
|
||||
|
|
@ -40,7 +40,7 @@ services:
|
|||
retries: 3
|
||||
|
||||
# ============================================
|
||||
# Phase 2b — Forgejo + Umami
|
||||
# Phase 2b — Forgejo
|
||||
# ============================================
|
||||
forgejo:
|
||||
image: codeberg.org/forgejo/forgejo:11
|
||||
|
|
|
|||
|
|
@ -69,8 +69,8 @@ export function getPrivacyFAQs(locale: string, options: PrivacyFAQOptions): FAQI
|
|||
? 'Wie unabhängig ist Mana von großen Tech-Konzernen?'
|
||||
: 'How independent is Mana from big tech companies?',
|
||||
answer: isDE
|
||||
? '<p>Mana ist bewusst <strong>technologisch unabhängig</strong> aufgebaut:</p><ul><li><strong>Eigene Server</strong>: Alle Dienste laufen auf einem eigenen Mac Mini Server — kein AWS, kein Google Cloud, kein Azure</li><li><strong>Eigene KI</strong>: Lokale KI-Modelle (Gemma, Qwen, LLaVA) laufen auf unserem eigenen GPU-Server mit NVIDIA RTX 3090 — deine Daten verlassen nie unsere Infrastruktur</li><li><strong>Keine Google/Apple-Anmeldung</strong>: Eigenes Auth-System (Mana Core Auth) — kein OAuth über Drittanbieter, keine Tracking-Cookies von Google oder Facebook</li><li><strong>Eigene Suche</strong>: SearXNG Meta-Suchmaschine statt Google Search API</li><li><strong>Eigener Speicher</strong>: MinIO (S3-kompatibel) statt AWS S3 oder Google Cloud Storage</li><li><strong>Eigene Datenbank</strong>: PostgreSQL auf eigenem Server statt Cloud-Datenbanken</li><li><strong>Keine Tracking-SDKs</strong>: Kein Google Analytics, kein Facebook Pixel, kein Amplitude — eigene Analytics mit Umami</li></ul><p>Das Ziel: Ein digitales Zuhause, das dir gehört — nicht Big Tech.</p>'
|
||||
: '<p>Mana is deliberately built to be <strong>technologically independent</strong>:</p><ul><li><strong>Own servers</strong>: All services run on a dedicated Mac Mini server — no AWS, no Google Cloud, no Azure</li><li><strong>Own AI</strong>: Local AI models (Gemma, Qwen, LLaVA) run on our own GPU server with NVIDIA RTX 3090 — your data never leaves our infrastructure</li><li><strong>No Google/Apple login</strong>: Own auth system (Mana Core Auth) — no OAuth via third parties, no tracking cookies from Google or Facebook</li><li><strong>Own search</strong>: SearXNG meta-search engine instead of Google Search API</li><li><strong>Own storage</strong>: MinIO (S3-compatible) instead of AWS S3 or Google Cloud Storage</li><li><strong>Own database</strong>: PostgreSQL on own server instead of cloud databases</li><li><strong>No tracking SDKs</strong>: No Google Analytics, no Facebook Pixel, no Amplitude — own analytics with Umami</li></ul><p>The goal: A digital home that belongs to you — not big tech.</p>',
|
||||
? '<p>Mana ist bewusst <strong>technologisch unabhängig</strong> aufgebaut:</p><ul><li><strong>Eigene Server</strong>: Alle Dienste laufen auf einem eigenen Mac Mini Server — kein AWS, kein Google Cloud, kein Azure</li><li><strong>Eigene KI</strong>: Lokale KI-Modelle (Gemma, Qwen, LLaVA) laufen auf unserem eigenen GPU-Server mit NVIDIA RTX 3090 — deine Daten verlassen nie unsere Infrastruktur</li><li><strong>Keine Google/Apple-Anmeldung</strong>: Eigenes Auth-System (Mana Core Auth) — kein OAuth über Drittanbieter, keine Tracking-Cookies von Google oder Facebook</li><li><strong>Eigene Suche</strong>: SearXNG Meta-Suchmaschine statt Google Search API</li><li><strong>Eigener Speicher</strong>: MinIO (S3-kompatibel) statt AWS S3 oder Google Cloud Storage</li><li><strong>Eigene Datenbank</strong>: PostgreSQL auf eigenem Server statt Cloud-Datenbanken</li><li><strong>Keine Tracking-SDKs</strong>: Kein Google Analytics, kein Facebook Pixel, kein Amplitude — und gar kein Web-Analytics</li></ul><p>Das Ziel: Ein digitales Zuhause, das dir gehört — nicht Big Tech.</p>'
|
||||
: '<p>Mana is deliberately built to be <strong>technologically independent</strong>:</p><ul><li><strong>Own servers</strong>: All services run on a dedicated Mac Mini server — no AWS, no Google Cloud, no Azure</li><li><strong>Own AI</strong>: Local AI models (Gemma, Qwen, LLaVA) run on our own GPU server with NVIDIA RTX 3090 — your data never leaves our infrastructure</li><li><strong>No Google/Apple login</strong>: Own auth system (Mana Core Auth) — no OAuth via third parties, no tracking cookies from Google or Facebook</li><li><strong>Own search</strong>: SearXNG meta-search engine instead of Google Search API</li><li><strong>Own storage</strong>: MinIO (S3-compatible) instead of AWS S3 or Google Cloud Storage</li><li><strong>Own database</strong>: PostgreSQL on own server instead of cloud databases</li><li><strong>No tracking SDKs</strong>: No Google Analytics, no Facebook Pixel, no Amplitude — and no web analytics at all</li></ul><p>The goal: A digital home that belongs to you — not big tech.</p>',
|
||||
category: 'privacy',
|
||||
order: 96,
|
||||
language: isDE ? 'de' : 'en',
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* Shared security headers for SvelteKit web apps.
|
||||
*
|
||||
* Sets standard security headers (CSP, X-Frame-Options, etc.)
|
||||
* with Umami analytics and GlitchTip error tracking pre-configured.
|
||||
* with GlitchTip error tracking pre-configured.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
|
|
@ -33,7 +33,7 @@ interface SecurityHeadersOptions {
|
|||
|
||||
/**
|
||||
* Set standard security headers on a Response object.
|
||||
* Includes Umami (stats.mana.how) and GlitchTip (glitchtip.mana.how) by default.
|
||||
* Includes GlitchTip (glitchtip.mana.how) by default.
|
||||
*/
|
||||
export function setSecurityHeaders(response: Response, options: SecurityHeadersOptions = {}): void {
|
||||
const {
|
||||
|
|
@ -67,10 +67,10 @@ export function setSecurityHeaders(response: Response, options: SecurityHeadersO
|
|||
// WebAssembly compilation, NOT eval()/new Function() — much narrower
|
||||
// than the legacy 'unsafe-eval' source. Supported by all evergreen
|
||||
// browsers.
|
||||
`script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval' https://stats.mana.how https://glitchtip.mana.how ${scriptSrc.join(' ')}`.trim(),
|
||||
`script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval' https://glitchtip.mana.how ${scriptSrc.join(' ')}`.trim(),
|
||||
"style-src 'self' 'unsafe-inline'",
|
||||
`img-src 'self' data: blob: https: ${imgSrc.join(' ')}`.trim(),
|
||||
`connect-src 'self' https://stats.mana.how https://glitchtip.mana.how ${connectSrc.join(' ')}`.trim(),
|
||||
`connect-src 'self' https://glitchtip.mana.how ${connectSrc.join(' ')}`.trim(),
|
||||
`font-src 'self' ${fontSrc.join(' ')}`.trim(),
|
||||
mediaSrc.length > 0 ? `media-src 'self' ${mediaSrc.join(' ')}`.trim() : '',
|
||||
"object-src 'none'",
|
||||
|
|
|
|||
|
|
@ -11,11 +11,6 @@
|
|||
if (block.props.scriptUrl) return block.props.scriptUrl;
|
||||
return 'https://plausible.io/js/script.js';
|
||||
});
|
||||
|
||||
const umamiSrc = $derived.by(() => {
|
||||
if (block.props.scriptUrl) return block.props.scriptUrl;
|
||||
return 'https://cloud.umami.is/script.js';
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if !isPublic}
|
||||
|
|
@ -30,8 +25,6 @@
|
|||
{:else if configured}
|
||||
{#if block.props.provider === 'plausible'}
|
||||
<script defer data-domain={block.props.siteKey} src={plausibleSrc}></script>
|
||||
{:else if block.props.provider === 'umami'}
|
||||
<script defer data-website-id={block.props.siteKey} src={umamiSrc}></script>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
if (provider === 'plausible') {
|
||||
return 'Trage hier die Domain ein, die du bei Plausible registriert hast (z.B. "meineseite.de"). Keine Cookies, DSGVO-konform.';
|
||||
}
|
||||
return 'Umami Website-ID (UUID). Keine Cookies, DSGVO-konform.';
|
||||
return 'Keine Cookies, DSGVO-konform.';
|
||||
});
|
||||
|
||||
const keyLabel = $derived(provider === 'plausible' ? 'Domain' : 'Website-ID');
|
||||
|
|
@ -25,7 +25,6 @@
|
|||
onchange={(e) => onChange({ provider: e.currentTarget.value as AnalyticsProps['provider'] })}
|
||||
>
|
||||
<option value="plausible">Plausible</option>
|
||||
<option value="umami">Umami</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
|
|
|
|||
|
|
@ -2,16 +2,15 @@ import { z } from 'zod';
|
|||
|
||||
/**
|
||||
* Analytics block — injects a tracking snippet into the published
|
||||
* page. Opt-in, no cookies by design (Plausible / Umami are
|
||||
* cookieless).
|
||||
* page. Opt-in, no cookies by design (Plausible is cookieless).
|
||||
*
|
||||
* The block renders nothing visible in edit/preview; in public mode
|
||||
* it emits a single <script> tag. No PII collection (no visitor IDs,
|
||||
* no fingerprinting), no admin UI access required.
|
||||
*/
|
||||
export const AnalyticsSchema = z.object({
|
||||
provider: z.enum(['plausible', 'umami']).default('plausible'),
|
||||
/** Plausible: the domain property; Umami: the website id (UUID). */
|
||||
provider: z.enum(['plausible']).default('plausible'),
|
||||
/** Plausible: the domain property. */
|
||||
siteKey: z.string().max(128).default(''),
|
||||
/**
|
||||
* Optional script-host override for self-hosted instances. Leave
|
||||
|
|
|
|||
|
|
@ -202,8 +202,8 @@ describe('moduleEmbed', () => {
|
|||
});
|
||||
|
||||
describe('analytics', () => {
|
||||
it('accepts both providers', () => {
|
||||
for (const provider of ['plausible', 'umami']) {
|
||||
it('accepts the provider', () => {
|
||||
for (const provider of ['plausible']) {
|
||||
expect(safeValidateSchema('analytics', { provider }).success).toBe(true);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue