8cc7d7d14a
* feat(external-media): watch reference folders and import new files automatically Managed uploads dropped into storage/events/active are picked up by the chokidar watcher; external media had no equivalent, so a NAS folder that keeps growing needed an admin to open the event and press Import every time. Relates to issue 1187. - The import pass moves out of the route into services/externalImportService.js. The watcher and the Import button now run the identical function; the route only validates and maps errors to status codes. - Mutual exclusion is the per-event claim from maintenanceJobState (`external_import:<id>`, seeded on demand by the new ensure()) instead of the in-process Set. The Set stopped a double-click in one process; the claim also stops the watcher on a second replica, or an admin clicking while the watcher is mid-run elsewhere. The run heartbeats so a claim from a dead process is taken over. - services/externalMediaWatcher.js: per-event opt-in via the new events.external_watch column (migration 208), chokidar with awaitWriteFinish so a copy in flight is not imported half-written, debounced full pass per change, a timer sweep every 15 minutes as the fallback for NFS/SMB mounts that deliver no inotify events, optional stat-polling via EXTERNAL_MEDIA_WATCH_POLLING. The set of watched events is re-read every minute, so the toggle works from any replica. A watcher that just started runs one pass immediately. - Deletions are ignored on purpose: a file vanishing from a NAS is at least as likely to be a reorganisation or a dropped mount as an intentional removal, and acting on it would delete a guest-visible photo. Rows whose file is gone stay, as they do today. - Not gated on STORAGE_BACKEND: EXTERNAL_MEDIA_ROOT is always local. - Quiet system passes stay out of the activity log; runs that imported something are logged with actor external-media-watcher. - Frontend: "Watch folder for new files" checkbox under the external folder picker, status line in view mode, EN/DE strings. * fix(external-media): close the review gaps in the folder watcher Codex review of the watcher, round 1. All six findings were real: - Enabling the watcher, or pointing an enabled one at another folder, now requires photos.upload — the permission the manual Import already requires. events.edit alone was a way around it. Only the transition is checked, so a role without photos.upload can still edit an already-watched event. The checkbox is disabled for such roles. - Automatic passes defer files that are still changing: anything modified inside the stability window, or whose size moves across one wait of that window, is left for the next pass. chokidar's awaitWriteFinish only settles the file that fired the event, and the sweep sees no events at all, so a sibling still being copied could be inserted half-written and then skipped forever. - Photos an admin deleted are not brought back by the sweep. The delete routes record the file in external_import_exclusions (migration 209); automatic passes skip the list, the manual Import ignores it and clears it for what it imports. - The six EXTERNAL_MEDIA_WATCH* variables are forwarded in all three compose files; they were documented but the backend services use explicit environment lists, so the kill switch did nothing. - A pass re-checks is_active / is_archived at run time, not only in the minutely reconcile. - The lease is renewed on a timer for the whole run, walk included, and ownership is checked before the event row is touched. * fix(external-media): make automatic passes follow the row, not rewrite it Codex review round 2, four findings, all applied: - The event update route drops non-canonical spellings of external_watch and external_path before the permission guard. SQLite resolves column names case-insensitively, so `External_Watch` reached the column while the guard only looked at the lowercase key. - Exclusions are checked per file at insert time, not against a snapshot taken before the settle wait. A photo deleted during the wait was present in the snapshot and got re-inserted by the loop. - An automatic pass no longer writes source_mode / external_path. It re-reads the row after the walk and the settle wait and stops if the folder changed or the event went managed; the manual Import is the only writer. The options are now `automatic` + `settleMs`. - A pass that deferred files re-arms the debounced import, so a file copied just before the watcher started is not stranded when the sweep is disabled. * fix(external-media): keep exclusions for replaced photos, stop a pass whose event stopped qualifying Codex review round 3, both findings applied: - recordExclusions keys on external_relpath alone. A replaced external photo becomes managed but keeps its relpath on purpose, and deleting that replacement must not republish the NAS original. - An automatic pass checks the full watcher predicate (reference mode, same folder, watch on, active, not archived) before it inserts and on every heartbeat tick during the loop, and stops as soon as the event no longer qualifies. --------- Co-authored-by: Paul Nothaft <paul@MacStudio-von-Paul.local>
356 lines
16 KiB
Bash
356 lines
16 KiB
Bash
# PicPeak Environment Configuration
|
|
# Copy this file to .env and update with your values
|
|
|
|
# Environment
|
|
NODE_ENV=production
|
|
|
|
# JWT Secret — OPTIONAL. Leave unset and it is auto-generated on first run
|
|
# (Docker: the secrets-init service writes it to a private volume and reuses it
|
|
# across restarts). Set it explicitly only to pin your own value.
|
|
# Generate one with: openssl rand -base64 64
|
|
#JWT_SECRET=your_very_long_random_jwt_secret_here
|
|
|
|
# How long a gallery guest stays recognised (#1210). Default 30d. It was 24h,
|
|
# which meant a client reviewing a gallery across two weekends registered again
|
|
# in between — and each re-registration is a separate guest whose likes and
|
|
# favourites no longer join up with the first visit's. Takes any jsonwebtoken
|
|
# duration ('7d', '12h'); shorten it if your galleries hold sensitive work.
|
|
#GUEST_TOKEN_TTL=30d
|
|
|
|
# OIDC SSO for admins (#798) — configured in the admin UI; only these two
|
|
# values live in the environment:
|
|
# Key encrypting the OIDC client secret at rest (defaults to JWT_SECRET).
|
|
#OIDC_ENCRYPTION_KEY=
|
|
# Break-glass: 'true' re-enables local password login even while the SSO
|
|
# settings disable it (recovery when the IdP is down or misconfigured).
|
|
#OIDC_BREAK_GLASS=false
|
|
|
|
# Auth cookie Secure flag
|
|
# unset - default: follows NODE_ENV (production=true, dev=false)
|
|
# true - always set Secure (HTTPS-only cookies; breaks plain-HTTP access)
|
|
# false - never set Secure (allows HTTP; cookies not protected on HTTPS)
|
|
# auto - decide per request: Secure on HTTPS, not on HTTP
|
|
#
|
|
# Use COOKIE_SECURE=auto if your deployment is reachable over both HTTPS
|
|
# (via reverse proxy like Nginx Proxy Manager, Traefik, Caddy) AND plain
|
|
# HTTP (e.g. LAN access at http://192.168.x.x:3010). The backend reads
|
|
# req.secure from Express, which respects the X-Forwarded-Proto header
|
|
# when the proxy is in the trust list.
|
|
#
|
|
# Requirements for auto mode:
|
|
# 1. Your reverse proxy MUST send X-Forwarded-Proto: https on HTTPS
|
|
# requests. Standard configs for NPM/Traefik/Caddy do this by default.
|
|
# 2. The proxy must be on a trusted IP range. By default PicPeak trusts
|
|
# loopback and private networks (127.0.0.1, 10.x, 172.16-31.x,
|
|
# 192.168.x, link-local). Proxies outside those ranges need custom
|
|
# trust proxy configuration.
|
|
# COOKIE_SECURE=auto
|
|
|
|
# Cookie SameSite attribute (Lax | Strict | None). Default: Lax
|
|
# COOKIE_SAMESITE=Lax
|
|
|
|
# Cookie Domain — set this if serving auth cookies across subdomains.
|
|
# Leave unset for same-origin setups.
|
|
# COOKIE_DOMAIN=.example.com
|
|
|
|
# Database Configuration (PostgreSQL)
|
|
DATABASE_CLIENT=pg
|
|
DB_USER=picpeak
|
|
# DB_PASSWORD — OPTIONAL. Leave unset and it is auto-generated on first run
|
|
# (Docker). Set it explicitly to pin your own, e.g. for an external database.
|
|
# IMPORTANT: Avoid $ character in passwords - Docker Compose interprets it as variable substitution
|
|
# If you must use $, escape it as $$ (e.g., Pass$$word instead of Pass$word)
|
|
#DB_PASSWORD=your_secure_postgres_password_here
|
|
DB_NAME=picpeak_prod
|
|
|
|
# Redis Configuration
|
|
# REDIS_PASSWORD — OPTIONAL. Leave unset and it is auto-generated on first run (Docker).
|
|
# IMPORTANT: Same warning applies - avoid $ or escape as $$
|
|
#REDIS_PASSWORD=your_secure_redis_password_here
|
|
|
|
# Admin Account (initial setup) — OPTIONAL
|
|
# Leave these unset (default) to create your admin IN THE BROWSER on first run:
|
|
# open /admin and PicPeak shows a setup screen. The one-time setup token is
|
|
# written to data/SETUP_TOKEN with mode 0600 — read it with
|
|
# `docker compose exec backend cat /app/data/SETUP_TOKEN`. It is NOT logged
|
|
# unless that write fails, so it never sits in `docker logs`.
|
|
# The all-in-one image keeps it at /data/db/SETUP_TOKEN — inside the volume,
|
|
# in the db/ subdirectory (#1218). On a NAS with no shell, set ADMIN_PASSWORD
|
|
# below instead: it needs no file at all.
|
|
# Set ADMIN_PASSWORD to auto-create the admin on first boot instead (legacy;
|
|
# credentials written to data/ADMIN_CREDENTIALS.txt).
|
|
#ADMIN_USERNAME=admin
|
|
#ADMIN_EMAIL=admin@yourdomain.com
|
|
#ADMIN_PASSWORD=your_secure_admin_password_here
|
|
|
|
# Email Configuration — OPTIONAL, and normally left alone.
|
|
# SMTP is configured in the setup wizard / Settings -> Email and stored in the
|
|
# database (email_configs); that is what the mail queue actually sends with.
|
|
# These variables are a legacy path kept for config-as-code deployments: when
|
|
# SMTP_HOST is set, the initial migration seeds the database row from it.
|
|
# Developers running the `dev` compose profile want SMTP_HOST=mailhog here so
|
|
# that seed points at the mailhog container.
|
|
# For Gmail: use app-specific password
|
|
# For SendGrid: SMTP_USER=apikey, SMTP_PASS=your-api-key
|
|
#SMTP_HOST=smtp.gmail.com
|
|
#SMTP_PORT=587
|
|
#SMTP_SECURE=false
|
|
#SMTP_USER=your-email@gmail.com
|
|
#SMTP_PASS=your-app-specific-password
|
|
#EMAIL_FROM=noreply@yourdomain.com
|
|
|
|
# Webhook email transport (#1225) — OPTIONAL, an alternative to SMTP entirely.
|
|
# When EMAIL_WEBHOOK_URL is set, PicPeak stops sending mail itself and POSTs
|
|
# each composed message as JSON to that URL instead; something downstream
|
|
# (n8n, Make, a self-hosted relay) delivers it. Useful when SMTP is the part
|
|
# you cannot get working — app passwords, blocked ports, a NAS with no
|
|
# outbound 25.
|
|
#
|
|
# Deliberately environment-only, not an admin setting: it redirects every
|
|
# outbound message including password resets, so it should not be changeable
|
|
# from a compromised admin session.
|
|
#
|
|
# EMAIL_WEBHOOK_SECRET is REQUIRED. The body is signed with it and sent as
|
|
# X-PicPeak-Signature (HMAC-SHA256, hex) — the same scheme as gallery
|
|
# webhooks, so a receiver verifies both the same way. Set the URL without a
|
|
# secret and the transport stays OFF and says so in the log, rather than
|
|
# posting unauthenticated mail to the internet.
|
|
#
|
|
# Payload: { from, to[], cc[], subject, html, text, attachments[] }, where each
|
|
# attachment is { filename, content_type, content_base64 }. Attachments are
|
|
# included rather than dropped; a message whose attachments exceed 10 MB fails
|
|
# and stays in the queue instead of arriving without its invoice.
|
|
#
|
|
# The receiver must be a public https:// address unless you opt in — a container or LAN
|
|
# address is refused by the SSRF check otherwise. Running n8n beside PicPeak is
|
|
# normal, so set EMAIL_WEBHOOK_ALLOW_PRIVATE_URLS=true for that.
|
|
#
|
|
# A mail account with its own SMTP host (Settings -> Mail accounts) keeps
|
|
# sending through it; this replaces the global transport only.
|
|
#
|
|
# Set EMAIL_FROM above as well. A webhook-only install never gets an
|
|
# email_configs row (that is seeded only when SMTP_HOST is set), so EMAIL_FROM
|
|
# is where the sender address comes from.
|
|
#EMAIL_WEBHOOK_URL=https://n8n.example.com/webhook/picpeak-mail
|
|
#EMAIL_WEBHOOK_SECRET=generate-a-long-random-string
|
|
#EMAIL_WEBHOOK_ALLOW_PRIVATE_URLS=false
|
|
|
|
# Application URLs — OPTIONAL. Leave unset for the normal install.
|
|
# The public origin is captured by the setup wizard (it proposes the address
|
|
# you opened the browser at) and stored as the `general_site_url` setting, so
|
|
# you can change it later in Settings -> General without touching this file.
|
|
# Setting FRONTEND_URL here OVERRIDES that setting and makes the field
|
|
# read-only in the admin UI - use it only for config-as-code deployments.
|
|
# Use full origin with scheme, no trailing slash.
|
|
# Admin UI is served by the frontend at /admin.
|
|
#FRONTEND_URL=https://yourdomain.com
|
|
#ADMIN_URL=https://yourdomain.com
|
|
|
|
# Static HTML title + description used for social link previews when the
|
|
# fetcher doesn't trigger the per-event OG endpoint — most notably the
|
|
# WhatsApp Business API and various 3rd-party preview-service caches
|
|
# (#521). Set these to your brand so link previews aren't generic.
|
|
# Substituted into index.html at frontend-container start, so changes
|
|
# take effect on the next `docker compose up -d frontend` — no rebuild
|
|
# required.
|
|
BRAND_TITLE=PicPeak
|
|
BRAND_DESCRIPTION=Photo gallery shared with PicPeak.
|
|
|
|
# API URL for email assets (logos, images in notification emails)
|
|
# OPTIONAL: when unset this is derived from the resolved public origin + /api,
|
|
# so the wizard's answer covers it. Set it only for split-origin deployments
|
|
# where the API lives on a different host than the gallery.
|
|
#API_URL=https://yourdomain.com/api
|
|
|
|
# Frontend API base
|
|
# For pre-built images and production behind a reverse proxy, keep '/api'.
|
|
# If you rebuild the frontend yourself, you may set a full URL at build time.
|
|
VITE_API_URL=/api
|
|
|
|
# Port Configuration (optional)
|
|
# BACKEND_PORT=3001
|
|
# FRONTEND_PORT=3000
|
|
# DB_PORT=5432
|
|
# REDIS_PORT=6379
|
|
|
|
# File watcher (watch-folder auto-import, local storage only)
|
|
# Max photos processed in parallel — raise on hosts with memory headroom,
|
|
# lower to 1 on very small hosts. Default: 2
|
|
# FILE_WATCHER_CONCURRENCY=2
|
|
|
|
# External-media folder watcher (issue 1187). Reference-mode events can opt in
|
|
# per event (Event → Source Mode → "Watch folder for new files"); new images in
|
|
# the folder are then imported without pressing Import. Deleted files are
|
|
# never removed from the gallery.
|
|
# EXTERNAL_MEDIA_WATCH=true # global kill switch
|
|
# EXTERNAL_MEDIA_WATCH_POLLING=false # true = stat-polling instead of inotify (NFS/SMB mounts)
|
|
# EXTERNAL_MEDIA_WATCH_POLL_INTERVAL_MS=5000
|
|
# EXTERNAL_MEDIA_WATCH_SWEEP_INTERVAL_MS=900000 # timer-driven pass over every watched event; 0 disables
|
|
# EXTERNAL_MEDIA_WATCH_DEBOUNCE_MS=10000 # quiet period after the last change before the import runs
|
|
# EXTERNAL_MEDIA_WATCH_STABILITY_MS=5000 # how long a file must stop growing before it counts as written
|
|
|
|
# Release Channel
|
|
# Options: 'stable' (default), 'beta', or specific version like 'v2.3.0'
|
|
# 'stable' uses the :stable tag (same as :latest on main)
|
|
# 'beta' uses the :beta tag for pre-release versions
|
|
PICPEAK_CHANNEL=stable
|
|
|
|
# Update Check Configuration
|
|
# Set to 'false' to disable update notifications in admin UI
|
|
UPDATE_CHECK_ENABLED=true
|
|
|
|
# Timezone
|
|
TZ=UTC
|
|
|
|
# Analytics (Optional - Umami)
|
|
VITE_UMAMI_URL=
|
|
VITE_UMAMI_WEBSITE_ID=
|
|
VITE_UMAMI_SHARE_URL=
|
|
|
|
# Storage variables (host paths)
|
|
# These control where data is stored on the host. Defaults are local folders.
|
|
APP_STORAGE=./storage
|
|
APP_DATA=./data
|
|
LOGS=./logs
|
|
|
|
# ─── Storage Backend ────────────────────────────────────────────────────────
|
|
# PicPeak can store photos, thumbnails and archive zips on the local filesystem
|
|
# (default) or on any S3-compatible object store (AWS S3, MinIO, Cloudflare R2,
|
|
# Backblaze B2, Wasabi, DigitalOcean Spaces, …).
|
|
#
|
|
# STORAGE_BACKEND=local (default)
|
|
# Uses STORAGE_PATH on the local filesystem. Backwards compatible — every
|
|
# existing deployment keeps working unchanged.
|
|
#
|
|
# STORAGE_BACKEND=s3
|
|
# Reads STORAGE_S3_* below. Auto-import via the filesystem watcher is
|
|
# disabled in this mode (S3 has no inotify) — every photo must enter via the
|
|
# admin upload UI/API. Run `node backend/scripts/migrate-storage.js` to copy
|
|
# existing local content to S3 before flipping the env.
|
|
#
|
|
# STORAGE_BACKEND=local
|
|
#
|
|
# STORAGE_S3_BUCKET=picpeak
|
|
# STORAGE_S3_REGION=us-east-1
|
|
# STORAGE_S3_ACCESS_KEY=AKIAxxxxxxxxxxxxxxxx
|
|
# STORAGE_S3_SECRET_KEY=xxxxxxxxxxxxxxxxxxxxxxxx
|
|
# Custom endpoint — set this for MinIO / R2 / B2 / Spaces. Leave unset for AWS.
|
|
# STORAGE_S3_ENDPOINT=https://s3.us-west-002.backblazeb2.com
|
|
# Optional namespace prefix inside the bucket — useful for multi-deployment buckets.
|
|
# STORAGE_S3_PREFIX=picpeak
|
|
# STORAGE_S3_FORCE_PATH_STYLE=false # MinIO needs true; auto-on when endpoint is set
|
|
# STORAGE_S3_SSL=true
|
|
#
|
|
# Minimum IAM policy (AWS S3) for the bucket above:
|
|
# {
|
|
# "Version": "2012-10-17",
|
|
# "Statement": [{
|
|
# "Effect": "Allow",
|
|
# "Action": [
|
|
# "s3:GetObject", "s3:PutObject", "s3:DeleteObject",
|
|
# "s3:ListBucket", "s3:GetBucketLocation"
|
|
# ],
|
|
# "Resource": [
|
|
# "arn:aws:s3:::picpeak",
|
|
# "arn:aws:s3:::picpeak/*"
|
|
# ]
|
|
# }]
|
|
# }
|
|
#
|
|
# EXTERNAL_MEDIA_ROOT (above) always lives on the local filesystem regardless
|
|
# of STORAGE_BACKEND — reference-mode galleries are not migrated to S3 in v1.
|
|
|
|
# ─── Outbound Webhooks (#327) ────────────────────────────────────────────────
|
|
# PicPeak POSTs event/photo lifecycle notifications to URLs you configure
|
|
# under Settings → Webhooks. Each delivery is signed HMAC-SHA256 with a
|
|
# per-webhook secret in the X-PicPeak-Signature header.
|
|
#
|
|
# WEBHOOK_ALLOW_PRIVATE_URLS (default: false)
|
|
# Block URLs resolving to private IPs / loopback / .local etc. as an
|
|
# SSRF mitigation. Set to "true" ONLY in dev when your receiver is on
|
|
# the same docker network or localhost. Production deployments must
|
|
# leave this OFF.
|
|
# WEBHOOK_ALLOW_PRIVATE_URLS=false
|
|
#
|
|
# WEBHOOK_DELIVERY_INTERVAL_MS (default: 5000)
|
|
# How often the worker polls webhook_deliveries for pending rows.
|
|
# WEBHOOK_DELIVERY_INTERVAL_MS=5000
|
|
#
|
|
# WEBHOOK_DELIVERY_CONCURRENCY (default: 5)
|
|
# Maximum in-flight deliveries per worker tick. One slow consumer can
|
|
# monopolize all 5 slots — bump this if your receivers are slow OR ship
|
|
# a separate webhook-only deployment.
|
|
# WEBHOOK_DELIVERY_CONCURRENCY=5
|
|
#
|
|
# WEBHOOK_HTTP_TIMEOUT_MS (default: 10000)
|
|
# Per-request timeout. Beyond this, the delivery is recorded as a
|
|
# network error and retried.
|
|
# WEBHOOK_HTTP_TIMEOUT_MS=10000
|
|
#
|
|
# WEBHOOK_MAX_ATTEMPTS (default: 5)
|
|
# Total attempts before a delivery is marked failed. Backoff between
|
|
# attempts is exponential: 1m, 5m, 30m, 2h, 12h.
|
|
# WEBHOOK_MAX_ATTEMPTS=5
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Face recognition — "People in this gallery" (#1074, optional)
|
|
# -----------------------------------------------------------------------------
|
|
# Requires the optional picpeak-ml sidecar container:
|
|
# docker compose --profile faces up -d
|
|
#
|
|
# NONE of these variables do anything until the `faces` feature flag is
|
|
# enabled in Admin → Settings, AND the per-event "Detect people in this
|
|
# gallery" toggle is switched on. Both default to OFF. With the flag off the
|
|
# backend never contacts the sidecar, so leaving these at their defaults on an
|
|
# install without the container is completely inert.
|
|
#
|
|
# Face embeddings are biometric data (GDPR Art. 9 special category in the EU).
|
|
# The photographer is the controller and needs a lawful basis for the people
|
|
# in their photos — read https://docs.picpeak.app/features/face-recognition
|
|
# before enabling.
|
|
#
|
|
# NOT AVAILABLE ON THE ALL-IN-ONE IMAGE. The single-container build sets
|
|
# PICPEAK_SINGLE_CONTAINER=true and the backend refuses to enable face
|
|
# recognition there regardless of these variables or the feature flag: that
|
|
# image runs the backend, frontend, database and every worker in one
|
|
# container, with no ML sidecar to talk to, and face detection would compete
|
|
# with image processing for the same CPU and memory. Use the standard
|
|
# multi-container deployment if you want this feature.
|
|
#
|
|
# FACE_ML_TOKEN (no default — REQUIRED to run the sidecar)
|
|
# Shared secret between the backend and the sidecar. The sidecar refuses to
|
|
# start without it rather than serving anonymously, so an accidentally
|
|
# published port is never a free face-detection API. Generate with:
|
|
# openssl rand -hex 32
|
|
# FACE_ML_TOKEN=
|
|
#
|
|
# FACE_ML_URL (default: http://picpeak-ml:8000)
|
|
# Defaults to the sidecar's compose service name, so the standard
|
|
# deployment needs no configuration here. Only change it if you run the
|
|
# sidecar outside the default compose network.
|
|
# FACE_ML_URL=http://picpeak-ml:8000
|
|
#
|
|
# FACE_PROCESSOR_CONCURRENCY (default: 1)
|
|
# Face-detection workers in the backend. Defaults to 1 deliberately: face
|
|
# scanning shares a host with Sharp image processing, which is the real
|
|
# memory pressure (see UPLOAD_PROCESSOR_CONCURRENCY). Raise only on hosts
|
|
# with headroom to spare.
|
|
# FACE_PROCESSOR_CONCURRENCY=1
|
|
#
|
|
# FACE_ORT_THREADS (default: 1)
|
|
# ONNX Runtime threads inside the sidecar. More threads mean faster
|
|
# per-photo inference and higher RSS.
|
|
# FACE_ORT_THREADS=1
|
|
|
|
# Note on FRONTEND_API_URL (documentation only):
|
|
# When using pre-built frontend images, runtime env vars cannot override the built JS.
|
|
# Do NOT rely on FRONTEND_API_URL in Compose. Instead, keep VITE_API_URL=/api and
|
|
# let the frontend Nginx proxy /api to the backend. Only if you rebuild the frontend
|
|
# should you change VITE_API_URL at build time.
|
|
|
|
# Optional product usage (#1110): disabled until explicit in-app consent.
|
|
# USAGE_COLLECTOR_URL=https://usage.picpeak.app
|
|
# Backend signing-key encryption (32+ characters); defaults to JWT_SECRET.
|
|
# Keep this value stable until participation has been deleted.
|
|
# USAGE_ENCRYPTION_KEY=
|