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:
Till JS 2026-05-26 14:56:34 +02:00
parent 394e520a26
commit 3ea8703a94
14 changed files with 20 additions and 36 deletions

View file

@ -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',

View file

@ -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'",

View file

@ -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}

View file

@ -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>

View file

@ -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

View file

@ -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);
}
});