diff --git a/docker-compose.yml b/docker-compose.yml index 4f72948..a524f1d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -91,6 +91,164 @@ services: networks: - postguard + # GlitchTip — open-source, Sentry-compatible error tracker. + # Reports posted by the website's CrashReport button land here. + # UI at http://localhost:8000 (override via GLITCHTIP_PORT if 8000 is taken). + # On first boot, create an account and a project; the DSN goes into + # APP_CONFIG.GLITCHTIP_DSN (see src/lib/env.ts). + glitchtip-postgres: + image: postgres:16-alpine + environment: + - POSTGRES_HOST_AUTH_METHOD=trust + - POSTGRES_DB=glitchtip + volumes: + - glitchtip-pg:/var/lib/postgresql/data + networks: + - postguard + + glitchtip-redis: + image: valkey/valkey:8-alpine + networks: + - postguard + + glitchtip-web: + image: glitchtip/glitchtip:v5 + depends_on: + - glitchtip-postgres + - glitchtip-redis + environment: + - DATABASE_URL=postgres://postgres@glitchtip-postgres:5432/glitchtip + - REDIS_URL=redis://glitchtip-redis:6379/0 + - SECRET_KEY=${GLITCHTIP_SECRET_KEY:-dev-only-change-me} + - PORT=8001 + - DEFAULT_FROM_EMAIL=noreply@postguard.local + - GLITCHTIP_DOMAIN=http://localhost:8001 + - ENABLE_OPEN_USER_REGISTRATION=true + ports: + - '8001:8001' + volumes: + - glitchtip-uploads:/code/uploads + networks: + - postguard + + glitchtip-worker: + image: glitchtip/glitchtip:v5 + command: ./bin/run-celery-with-beat.sh + # `glitchtip-web` runs Django migrations on startup. Without + # depending on it, the worker's celery-beat starts firing + # scheduled jobs (uptime-dispatch-checks etc.) before the + # tables exist and crashes with `relation "uptime_monitor" + # does not exist`. The web container has no healthcheck, so + # this is a `service_started` ordering rather than a true + # readiness gate — good enough on a single host where the + # web container reaches Postgres in a few seconds. + depends_on: + - glitchtip-postgres + - glitchtip-redis + - glitchtip-web + - glitchtip-init + environment: + - DATABASE_URL=postgres://postgres@glitchtip-postgres:5432/glitchtip + - REDIS_URL=redis://glitchtip-redis:6379/0 + - SECRET_KEY=${GLITCHTIP_SECRET_KEY:-dev-only-change-me} + volumes: + - glitchtip-uploads:/code/uploads + networks: + - postguard + + # One-shot: applies migrations and creates the dev superuser if it + # doesn't already exist. Runs to completion and exits. Idempotent — + # `createsuperuser --noinput` errors on a duplicate, which we + # swallow so subsequent `up`s remain no-ops. Override credentials + # by exporting GLITCHTIP_ADMIN_EMAIL / GLITCHTIP_ADMIN_PASSWORD. + glitchtip-init: + image: glitchtip/glitchtip:v5 + depends_on: + - glitchtip-postgres + environment: + - DATABASE_URL=postgres://postgres@glitchtip-postgres:5432/glitchtip + - REDIS_URL=redis://glitchtip-redis:6379/0 + - SECRET_KEY=${GLITCHTIP_SECRET_KEY:-dev-only-change-me} + # python-email-validator (used by GlitchTip's Pydantic response + # schemas) rejects `local`, `localhost`, `test`, `invalid`, + # `arpa`, `onion` TLDs as special-use — so `/api/0/users/me/` + # 500s if the superuser email lands on one of those. `.dev` is + # a real public TLD and not on that list. + - DJANGO_SUPERUSER_EMAIL=${GLITCHTIP_ADMIN_EMAIL:-admin@postguard.dev} + - DJANGO_SUPERUSER_PASSWORD=${GLITCHTIP_ADMIN_PASSWORD:-devpass1} + # Bootstraps: migrations → superuser → PostGuard org → website + # project → prints the DSN. Each step is idempotent so subsequent + # `up`s remain no-ops. Paste the printed DSN into static/config.js + # under `APP_CONFIG.GLITCHTIP_DSN` (or set `GLITCHTIP_DSN=…` in + # .env.dev) — the DSN is stable as long as the postgres volume + # survives, so this is a one-time copy. + command: + - sh + - -c + - | + ./manage.py migrate --noinput && + (./manage.py createsuperuser --noinput 2>/dev/null \ + && echo 'glitchtip-init: superuser created' \ + || echo 'glitchtip-init: superuser already exists') && + ./manage.py shell -c " + from django.apps import apps + from django.contrib.auth import get_user_model + Organization = apps.get_model('organizations_ext', 'Organization') + Project = apps.get_model('projects', 'Project') + ProjectKey = apps.get_model('projects', 'ProjectKey') + + user = get_user_model().objects.get(email='$$DJANGO_SUPERUSER_EMAIL') + + org, org_created = Organization.objects.get_or_create( + slug='postguard', + defaults={'name': 'PostGuard'}, + ) + if org_created: + org.add_user(user) + print('glitchtip-init: org PostGuard created') + else: + print('glitchtip-init: org PostGuard already exists') + + project, proj_created = Project.objects.get_or_create( + organization=org, + slug='postguard-website', + defaults={'name': 'postguard-website', 'platform': 'javascript'}, + ) + if proj_created: + print('glitchtip-init: project postguard-website created') + else: + print('glitchtip-init: project postguard-website already exists') + + # Pin a deterministic public_key so the DSN survives a + # full volume wipe. Without this, a fresh postgres volume + # regenerates the UUID and any hardcoded DSN in + # static/config.js silently goes stale. + # + # GlitchTip auto-creates a default ProjectKey when a + # Project is saved, so on a fresh volume the get_or_create + # below returns that auto-created key (key_created=False) + # and the defaults= clause is ignored. The `not key_created` + # branch catches that case and rewrites public_key to + # DEV_KEY. + import uuid + DEV_KEY = uuid.UUID('1d8ea2a49c904f079b116550780c0ece') + key, key_created = ProjectKey.objects.get_or_create( + project=project, + defaults={'public_key': DEV_KEY}, + ) + if not key_created and key.public_key != DEV_KEY: + key.public_key = DEV_KEY + key.save() + dsn = f'http://{key.public_key.hex}@localhost:8001/{project.id}' + print('========================================') + print('glitchtip-init: DSN (already set in static/config.js):') + print(f' {dsn}') + print('========================================') + " + restart: 'no' + networks: + - postguard + # Nginx reverse proxy nginx: image: nginx:alpine @@ -114,3 +272,5 @@ volumes: cryptify-data: cryptify-target: postguard-pkg-target: + glitchtip-pg: + glitchtip-uploads: diff --git a/package-lock.json b/package-lock.json index 09bba54..881667c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@e4a/pg-js": "^2.0.0", "@iconify/svelte": "^5.2.1", "@privacybydesign/yivi-css": "^1.0.1", + "@sentry/browser": "^10.20.0", "country-flag-icons": "^1.6.17", "libphonenumber-js": "^1.13.3", "postal-mime": "^2.7.4", @@ -42,7 +43,7 @@ "svelte": "^5.55.9", "svelte-check": "^4.4.8", "svelte-i18n": "^4.0.1", - "svelte-preprocess": "^6.0.3", + "svelte-preprocess": "^6.0.5", "tslib": "^2.8.1", "typescript": "^6.0.3", "typescript-eslint": "^8.59.4", @@ -819,7 +820,6 @@ "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", @@ -830,7 +830,6 @@ "version": "2.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -841,7 +840,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -851,14 +849,12 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -1878,6 +1874,81 @@ "win32" ] }, + "node_modules/@sentry-internal/browser-utils": { + "version": "10.56.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-10.56.0.tgz", + "integrity": "sha512-I8tZWAFg8SZpD8BFUpglEtSTzhZjacmcThB5/Mlq/iFiiT8mBPG4ZWDWssSfmIBKvZywJZJ83uDA0+uiJU73Tw==", + "license": "MIT", + "dependencies": { + "@sentry/core": "10.56.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry-internal/feedback": { + "version": "10.56.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-10.56.0.tgz", + "integrity": "sha512-fkRR9JroESTIlErkht3OrH4DXKd/DbPozr2KLdX7boMo31hPu4cL9fuqzwOrwyDPRq9B4j+qEgIWB8JrTbgvmg==", + "license": "MIT", + "dependencies": { + "@sentry/core": "10.56.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry-internal/replay": { + "version": "10.56.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-10.56.0.tgz", + "integrity": "sha512-DjF09hpy3TF7Km/kOZc73YJmBqcbPCxuZ5rtRs+KtVHu3Vq48xeW83qKUcFEZv20ur9UD99OAJ/gaEt//1Qbwg==", + "license": "MIT", + "dependencies": { + "@sentry-internal/browser-utils": "10.56.0", + "@sentry/core": "10.56.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry-internal/replay-canvas": { + "version": "10.56.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-10.56.0.tgz", + "integrity": "sha512-SDg2K0CAZT/TnhrixQGwXoi6ZsWUB+DQy3UUk0bSQm6c/5k5zFBpGOiughQN+DYsDilKREfPKmUEEnqvUjm1HQ==", + "license": "MIT", + "dependencies": { + "@sentry-internal/replay": "10.56.0", + "@sentry/core": "10.56.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/browser": { + "version": "10.56.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-10.56.0.tgz", + "integrity": "sha512-80X3NmsGB6tLmfzXYdjzWWdVAdL5CRukGKLcRWIcNhgGjtskOmnzaGb93egEZGI5bUTbtONJ0oyscQ3Z9yoAtQ==", + "license": "MIT", + "dependencies": { + "@sentry-internal/browser-utils": "10.56.0", + "@sentry-internal/feedback": "10.56.0", + "@sentry-internal/replay": "10.56.0", + "@sentry-internal/replay-canvas": "10.56.0", + "@sentry/core": "10.56.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/core": { + "version": "10.56.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.56.0.tgz", + "integrity": "sha512-L+u1dIz5SANrmST5jhIwETtt4apILgKrylv12X4hKJU0PvZl+NorjeV/ty3MwzpKQPg6b6q6qMOSLc1rLpy3iQ==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@standard-schema/spec": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", @@ -1889,7 +1960,6 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.10.tgz", "integrity": "sha512-4WfKk68eTih+MiJD4fSbxN7E8kVBmTMPWHUPYjvl2N0rMs53YLTT8/YjKU5Dtnz5LqDjl7LEw4U7lXR2W3J5WA==", - "dev": true, "license": "MIT", "peerDependencies": { "acorn": "^8.9.0" @@ -2014,7 +2084,6 @@ "version": "1.0.9", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", - "dev": true, "license": "MIT" }, "node_modules/@types/json-schema": { @@ -2048,7 +2117,6 @@ "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", - "dev": true, "license": "MIT" }, "node_modules/@types/unist": { @@ -2198,7 +2266,7 @@ "version": "8.59.4", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.4.tgz", "integrity": "sha512-F1o7WJcCq+bc8dwcO/YsSEOudAH8RDtaOhM6wcAQhcUsFhnWQl81JKy48q1hoxAU0qrzM89+31GYh1515Zde3Q==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2295,7 +2363,6 @@ "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", - "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -2377,7 +2444,6 @@ "version": "5.3.1", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz", "integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==", - "dev": true, "license": "Apache-2.0", "engines": { "node": ">= 0.4" @@ -2387,7 +2453,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", - "dev": true, "license": "Apache-2.0", "engines": { "node": ">= 0.4" @@ -2579,7 +2644,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -2729,7 +2793,6 @@ "version": "5.8.1", "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.8.1.tgz", "integrity": "sha512-4CXDYRBGqN+57wVJkuXBYmpAVUSg3L6JAQa/DFqm238G73E1wuyc/JhGQJzN7vUf/CMphYau2zXbfWzDR5aTEw==", - "dev": true, "license": "MIT" }, "node_modules/dijkstrajs": { @@ -3059,7 +3122,6 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", - "dev": true, "license": "MIT" }, "node_modules/esniff": { @@ -3126,7 +3188,6 @@ "version": "2.2.9", "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.9.tgz", "integrity": "sha512-4KijP+NxCWthMCUC3qHbE6n4vCjqgJS1uAYKhuT/GWfFTf1Qyive2TgOjep+gzbSzRfnNyaN/UU9YmdOt8Eg0A==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" @@ -3481,7 +3542,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", - "dev": true, "license": "MIT", "dependencies": { "@types/estree": "^1.0.6" @@ -3886,7 +3946,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", - "dev": true, "license": "MIT" }, "node_modules/locate-path": { @@ -3999,7 +4058,6 @@ "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" @@ -4863,7 +4921,6 @@ "version": "5.55.9", "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.55.9.tgz", "integrity": "sha512-fTjjT8cHLDwigcu2j3pv7Jq04LklXevPB8uBgyHNiTXv+RMNvVnrjS4UEYrLMkhuq1vpCodHjiW+z/95SDs/fg==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/remapping": "^2.3.4", @@ -5046,9 +5103,9 @@ } }, "node_modules/svelte-preprocess": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-6.0.3.tgz", - "integrity": "sha512-PLG2k05qHdhmRG7zR/dyo5qKvakhm8IJ+hD2eFRQmMLHp7X3eJnjeupUtvuRpbNiF31RjVw45W+abDwHEmP5OA==", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-6.0.5.tgz", + "integrity": "sha512-sgwew5yV/2eMeQobIWgAxCNarKwiTUDIc3siAUbq3sp0G6ONtzk0W+wJihMdqjbYb3iGU3ubpGv0usnnuXT3qg==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -5066,7 +5123,7 @@ "stylus": ">=0.55", "sugarss": "^2.0.0 || ^3.0.0 || ^4.0.0", "svelte": "^4.0.0 || ^5.0.0-next.100 || ^5.0.0", - "typescript": "^5.0.0" + "typescript": "^5.0.0 || ^6.0.0" }, "peerDependenciesMeta": { "@babel/core": { @@ -5683,7 +5740,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==", - "dev": true, "license": "MIT" } } diff --git a/package.json b/package.json index b3636e0..06595e3 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "svelte": "^5.55.9", "svelte-check": "^4.4.8", "svelte-i18n": "^4.0.1", - "svelte-preprocess": "^6.0.3", + "svelte-preprocess": "^6.0.5", "tslib": "^2.8.1", "typescript": "^6.0.3", "typescript-eslint": "^8.59.4", @@ -73,6 +73,7 @@ "@e4a/pg-js": "^2.0.0", "@iconify/svelte": "^5.2.1", "@privacybydesign/yivi-css": "^1.0.1", + "@sentry/browser": "^10.20.0", "country-flag-icons": "^1.6.17", "libphonenumber-js": "^1.13.3", "postal-mime": "^2.7.4", diff --git a/src/hooks.client.ts b/src/hooks.client.ts index 0de2281..80b787f 100644 --- a/src/hooks.client.ts +++ b/src/hooks.client.ts @@ -1,4 +1,7 @@ import type { HandleClientError } from '@sveltejs/kit' +import { initErrorReporting } from '$lib/errorReporting' + +initErrorReporting() export const handleError: HandleClientError = ({ error }) => { // When a new deployment happens, old JS chunks are gone. diff --git a/src/lib/components/filesharing/CrashReport.svelte b/src/lib/components/filesharing/CrashReport.svelte new file mode 100644 index 0000000..31764ca --- /dev/null +++ b/src/lib/components/filesharing/CrashReport.svelte @@ -0,0 +1,171 @@ + + +
+ {$_('filesharing.crash.failed')} +
+ {/if} +