Files
webmail-client/docker/.env.prod.example
T
lukasandClaude Opus 5 8fce86559e Admin panel on its own port, and a registration gate
Three strands that only make sense together, so they land together.

Registration gate (migration 0016). POST /auth/register was open to anyone
who could reach the port. Now the first account on an empty instance is
always allowed, becomes the administrator, and closes registration behind
itself; after that it takes an admin reopening it. The decision is made
under a row lock on app_settings so two simultaneous first signups cannot
both see an empty table and both claim administrator. Existing installs
keep their oldest account as admin and start closed.

Instance default quota (migration 0017). users.quota_bytes is snapshotted
at creation from app_settings.default_quota_bytes, so changing the default
affects later accounts only and never resizes a mailbox that exists.

The panel is a separate app, not a page in the mail client. admin.html is
its own Vite entry (5.8 KB next to the client's 1.7 MB; they share only the
vendor chunk), with its own administrator sign-in. One nginx now serves two
listeners: :80 is the mail client and 404s both /admin.html and
/api/admin/*, :81 is the panel. docker-compose publishes them as WEB_PORT
and ADMIN_PORT. The boundary is nginx refusing paths on one listener, not
process isolation — CurrentAdmin still guards every admin route — so
ADMIN_PORT must be restricted to the LAN, which the runbook now covers for
both DSM and QNAP.

Also QNAP: deploy/webmail.sh finds Container Station's docker under
/share/*/.qpkg/container-station/bin, which is off an SSH session's PATH,
before anything shells out to it. docs/nas-install.md gains a QuTS hero
runbook (§2.6), splits the DSM firewall section into container-to-container
(§8.1) and the egress case that makes account-add time out while
autodiscovery appears to work (§8.2), and scopes both to Synology.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018SqJYt7jD7oUEkoY68sBZo
2026-09-16 20:34:08 +02:00

121 lines
4.6 KiB
Bash

# ============================================================================
# Webmail production configuration.
#
# Copy to docker/.env.prod, fill in the three REQUIRED values, keep it safe:
# cp docker/.env.prod.example docker/.env.prod && chmod 600 docker/.env.prod
#
# THIS FILE IS A BACKUP ARTEFACT. MASTER_KEY is not stored anywhere else: lose
# it and every saved mail-account password becomes permanently undecryptable,
# even from a perfect database backup. Keep a copy in a password manager.
# ============================================================================
# --- REQUIRED ---------------------------------------------------------------
# Where all persistent data lives on the host. Use a real shared folder your NAS
# backup tool can see — not a Docker named volume.
# Synology: /volume1/docker/webmail/data
# QNAP: /share/Container/webmail/data
DATA_ROOT=/volume1/docker/webmail/data
# 32 random bytes, base64. Generate ONCE, then never change it:
# openssl rand -base64 32
MASTER_KEY=
# Database password. Generate once:
# openssl rand -base64 24 | tr -d '/+=' | cut -c1-24
POSTGRES_PASSWORD=
# --- Identity ---------------------------------------------------------------
# The domain your user handles are minted under (user@APP_DOMAIN) and the issuer
# shown in 2FA authenticator apps. It does NOT have to be publicly resolvable.
APP_DOMAIN=mail.home.lan
# --- Network ----------------------------------------------------------------
# Two published ports, same container. Avoid 5000/5001 (Synology DSM) and
# 8080 (QNAP QTS).
#
# WEB_PORT is the mail app. The admin API is refused on it, so this is the one
# you may expose beyond the LAN.
WEB_PORT=8088
# ADMIN_PORT is the same app with the admin API reachable. Restrict it to your
# LAN in the NAS firewall — see docs/nas-install.md §5.1. Publishing it is not
# what protects it.
ADMIN_PORT=8089
# Bind to one NAS interface instead of every network, e.g. WEB_BIND=192.168.1.185
WEB_BIND=0.0.0.0
ADMIN_BIND=0.0.0.0
TZ=Europe/Warsaw
# --- Search -----------------------------------------------------------------
# pg = Postgres full-text. No extra container, no extra RAM. Default.
# meili = Meilisearch: typo tolerance, better ranking, faster on large mailboxes.
# Costs a container and ~0.7-1 GB RAM. Start it with:
# ./deploy/webmail.sh up --meili
# and after switching an existing install, rebuild the index:
# ./deploy/webmail.sh reindex
SEARCH_BACKEND=pg
# Required if SEARCH_BACKEND=meili — generate with: openssl rand -base64 32
MEILI_MASTER_KEY=
# --- Images -----------------------------------------------------------------
# `local` = built on this host from the repo. To deploy prebuilt images from
# your Gitea registry instead, set both of these (see deploy/build-push.sh):
# REGISTRY_PREFIX=192.168.1.185:8418/claude/webmail
# IMAGE_TAG=v1.0.0
REGISTRY_PREFIX=webmail
IMAGE_TAG=local
# --- Application ------------------------------------------------------------
LOG_LEVEL=INFO
UNDO_SECONDS=10
SESSION_TTL_DAYS=30
MAX_ATTACHMENT_MB=25
# Auto-purge Trash/Spam older than N days (0 disables).
TRASH_RETENTION_DAYS=30
# Server-side sender avatars (Gravatar + favicon), fetched by the server so
# senders never see your IP. Set false to make zero outbound web requests.
SENDER_AVATARS_ENABLED=true
GOOGLE_OAUTH_ENABLED=false
OAUTH_GOOGLE_CLIENT_ID=
OAUTH_GOOGLE_CLIENT_SECRET=
OAUTH_MS_CLIENT_ID=
OAUTH_MS_CLIENT_SECRET=
# Plugins whose schema is migrated on every start. Remove one to stop shipping
# its tables; `hello` is the SDK demo and is intentionally not listed.
ENABLED_PLUGINS="calendar contacts labels filters"
# --- Backups ----------------------------------------------------------------
BACKUP_AT=03:30
BACKUP_KEEP_DAYS=14
# --- Sizing (defaults suit a personal install on a shared NAS) --------------
# Peak worker RAM is roughly PARSE_CONCURRENCY x STREAM_PARSE_MB plus overhead.
INTERACTIVE_CONCURRENCY=8
BULK_CONCURRENCY=1
PARSE_CONCURRENCY=2
STREAM_PARSE_MB=8
PER_HOST_CONN_CAP=10
HOT_BUDGET_PER_WORKER=50
# Container caps. Memory only: CPU limits (`cpus:`) are deliberately absent
# because Synology's Docker daemon rejects them (NanoCPUs).
# Container caps. Total without Meilisearch: ~2.2 GB of *limits*; real steady
# usage for one or two mail accounts is around 700 MB-1 GB.
PG_MEM_LIMIT=768m
PG_SHARED_BUFFERS=256MB
PG_MAX_CONNECTIONS=100
PG_WORK_MEM=8MB
PG_MAINTENANCE_WORK_MEM=128MB
PG_EFFECTIVE_CACHE_SIZE=768MB
REDIS_MEM_LIMIT=160m
REDIS_MAXMEMORY=96mb
API_MEM_LIMIT=512m
WORKER_SYNC_MEM_LIMIT=512m
WORKER_JOBS_MEM_LIMIT=640m
WEB_MEM_LIMIT=96m
BACKUP_MEM_LIMIT=256m
MEILI_MEM_LIMIT=1g