release: 1.0.0, a changelog, one .env.example and the docs to match
CI / Lint de idioma (R9) (push) Successful in 5s
CI / Go (protocols) (push) Successful in 2m34s
CI / TypeScript (storage) (push) Successful in 15s
CI / Testes (webmail) (push) Successful in 51s
CI / TypeScript (auth) (push) Successful in 2m39s
CI / Trivy (dependências, segredos e Dockerfiles) (push) Successful in 9s
CI / Imagem (migrations) (push) Successful in 5s
CI / Stack completa (push) Successful in 1m11s
CI / Imagem (auth) (push) Successful in 17s
CI / Imagem (protocols) (push) Successful in 7s
CI / Imagem (rspamd) (push) Successful in 5s
CI / Imagem (storage) (push) Successful in 17s
CI / Imagem (webmail) (push) Successful in 1m52s

- CHANGELOG.md and CHANGELOG.pt-BR.md, from the project history.
- .env.example now serves development and production; scripts/init-env.sh
  (make env) creates .env with random secrets. .env.production.example is gone.
- docs/RELEASING.md: the pipeline, how a version is cut and deployed, the runners.
- DEPLOYMENT.md and both READMEs updated for Gitea and the new env flow.
- Service versions set to 1.0.0.
- CI: the proxy network step is idempotent and the stack is torn down always,
  since the runner's Docker daemon now outlives a job.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Lucas RayanandClaude Sonnet 5.5 committed 2026-10-30 11:36:22 -03:00
1 parent f500534ac6
commit 5bbbb85128
17 files changed
+524 -182

No files matched your search

+95 -44
View File
@@ -1,81 +1,132 @@
# --- Container images ---
# Published to the Gitea registry by .gitea/workflows/release.yml, multi-architecture
# (linux/amd64 and linux/arm64). The deploy host pulls these instead of
# compiling: `docker compose pull && docker compose up -d`. Running
# `docker compose build` locally still overrides them with a local build.
# Pin IMAGE_TAG to a released version (e.g. 1.2.3) or a sha- tag to roll back.
# LambdaMail - environment
#
# One file for local development and for production.
#
# scripts/init-env.sh copy this to .env and generate the secrets
# scripts/init-env.sh --production the same, plus a list of what you still
# have to set before this is a real server
#
# Values below are the local-development defaults. Lines marked PRODUCTION say
# what a real deployment needs. In Coolify, paste the finished .env into the
# environment editor instead of keeping a file on the host.
#
# The secrets are left empty on purpose. The script fills them with random
# values, and compose refuses to start while one is empty, because a default
# secret in a published repository is not a secret.
# --- Container images -------------------------------------------------------
# Published to the Gitea registry by .gitea/workflows/release.yml for
# linux/amd64 and linux/arm64. The deploy host pulls instead of compiling:
# `docker compose pull && docker compose up -d`. `make up` builds from source
# through the build overlay and ignores these.
# PRODUCTION: pin IMAGE_TAG to a release (1.0.0) or to a sha-<commit> tag. Never
# leave `latest` on a server: a redeploy then silently changes what runs.
IMAGE_NAMESPACE=git.lucasrguerra.dev.br/lucasrguerra/lambdamail
IMAGE_TAG=latest
# --- Postgres ---
POSTGRES_DB=lambdamail
POSTGRES_USER=lambdamail
POSTGRES_PASSWORD=change-me-in-local-dev
# --- Redis ---
REDIS_PASSWORD=change-me-in-local-dev
# --- Shared secrets (local dev values — never reuse in production) ---
JWT_SECRET=local-dev-jwt-secret-change-me-at-least-32-chars
# Encrypts DKIM private keys and provider tokens at rest. Losing it makes every
# stored secret unrecoverable — back it up separately from the database.
LAMBDAMAIL_MASTER_KEY=local-dev-master-key-32-bytes-minimum-value
# --- Host identity ---
PRIMARY_MAIL_HOST=mail.localhost
# --- Identity ---------------------------------------------------------------
# MAIL_DOMAIN is the domain you send and receive as.
# PRIMARY_MAIL_HOST is the host the MX record points at; its PTR must match it.
# PRODUCTION: MAIL_DOMAIN=example.com PRIMARY_MAIL_HOST=mail.example.com
# WEBMAIL_HOST=mail.example.com PUBLIC_IPV4=<the VPS public IPv4>
MAIL_DOMAIN=localhost
PRIMARY_MAIL_HOST=mail.localhost
WEBMAIL_HOST=localhost:3000
# Optional separate host for /admin.
ADMIN_HOST=
PUBLIC_IPV4=127.0.0.1
# Leave empty unless IPv6 has a matching PTR and is covered by SPF. A half
# configured IPv6 is a common cause of rejection (PLAN.md premises).
PUBLIC_IPV6=
# --- DNS automation (Cloudflare) ---
# --- Secrets ----------------------------------------------------------------
# Filled in by scripts/init-env.sh. To do it by hand:
#
# POSTGRES_PASSWORD, REDIS_PASSWORD openssl rand -hex 24
# JWT_SECRET, LAMBDAMAIL_MASTER_KEY openssl rand -hex 32
#
# The first two are substituted into connection URIs (postgres://user:PASS@db/...
# and redis://:PASS@redis:6379), so they must hold no character that means
# something in a URL: use hex, NOT `openssl rand -base64`, whose alphabet has "/"
# and "+". A "/" ends the authority component, the host is parsed as part of the
# password and every connection fails authentication - which shows up as the
# migrations container exiting 1 while Postgres itself reports healthy.
POSTGRES_DB=lambdamail
POSTGRES_USER=lambdamail
POSTGRES_PASSWORD=
REDIS_PASSWORD=
# Signs webmail and admin sessions. At least 32 characters; the auth service
# refuses to start below that.
JWT_SECRET=
# Encrypts DKIM private keys, TOTP secrets and provider tokens at rest.
# LOSING THIS MAKES EVERY STORED SECRET UNRECOVERABLE: back it up separately
# from the database (PLAN.md risk R9).
LAMBDAMAIL_MASTER_KEY=
# --- DNS automation (Cloudflare) --------------------------------------------
# The token needs Zone.DNS:Edit plus Zone.Zone:Read on the zone. Leave it empty
# to disable reconciliation; the service then logs that DNS sync is off.
# to manage the records by hand; the service then logs that DNS sync is off.
CLOUDFLARE_API_TOKEN=
DNS_SYNC_INTERVAL_SECONDS=21600
# --- TLS ---
# traefik: read the certificate Traefik manages (default, DANE not possible).
# acme: obtain the certificate ourselves via DNS-01. Required for DANE,
# because only this mode can reuse a pre-generated key.
# --- TLS --------------------------------------------------------------------
# traefik: read the certificate the proxy already manages (default; DANE is not
# possible in this mode).
# acme: obtain the certificate here via DNS-01. Required for DANE, because
# only this mode can reuse a pre-generated key.
TLS_MODE=traefik
# Used only when TLS_MODE=acme. Leave ACME_DIRECTORY_URL empty for Let's
# Encrypt production; point it at the staging directory while testing, since
# production rate limits can lock a domain out for a week.
# Used only when TLS_MODE=acme. Leave ACME_DIRECTORY_URL empty for Let's Encrypt
# production; point it at the staging directory while testing, since production
# rate limits can lock a domain out for a week.
ACME_EMAIL=
ACME_DIRECTORY_URL=
# Where the proxy keeps its certificates, and the Docker network it listens on.
# PRODUCTION (Coolify): COOLIFY_PROXY_DIR=/data/coolify/proxy
COOLIFY_PROXY_DIR=./local/acme
COOLIFY_PROXY_NETWORK=coolify
TRAEFIK_ACME_FILENAME=acme.json
CERT_POLL_INTERVAL_SECONDS=60
# Static certificate files, used when no ACME store is available (local dev).
# Static certificate files, used when no ACME store is available (local dev;
# `make gen-dev-cert` writes them).
PROTOCOLS_CERT_PATH=/etc/lambdamail/certs/cert.pem
PROTOCOLS_KEY_PATH=/etc/lambdamail/certs/key.pem
PROTOCOLS_SPOOL_DIR=/var/lambdamail/spool
# --- Transport security policy ---
# --- Transport security -----------------------------------------------------
# Start at "testing": senders honour the policy but do not hard-fail on it.
# Promote to "enforce" only once TLS-RPT reports come back clean.
# Promote to "enforce" only once TLS-RPT reports come back clean; enforce with a
# broken certificate silently blackholes inbound mail.
MTA_STS_MODE=testing
OUTBOUND_MTA_STS_ENABLED=true
# DANE stays off unless TLS_MODE=acme. Under Traefik-managed certificates the
# key changes at every renewal and the published TLSA record stops matching,
# which permanently rejects mail at every validating MTA.
# DANE stays off unless TLS_MODE=acme. Under proxy-managed certificates the key
# changes at every renewal and the published TLSA record stops matching, which
# permanently rejects mail at every validating MTA (PLAN.md section 5.1).
OUTBOUND_DANE_ENABLED=false
DNSSEC_RESOLVER=1.1.1.1:53
MAX_MESSAGE_BYTES=52428800
# ARC seals accepted messages so a downstream receiver can still tell that the
# mail authenticated at the first hop. Only worth enabling on a host that
# forwards mail onward.
ARC_SEAL_ENABLED=false
# --- Smarthost (for hosts where outbound port 25 is blocked) ---
# Setting RELAY_HOST routes all delivery through the relay and adds it to the
# published SPF record, without which every relayed message fails SPF.
# --- Smarthost (hosts where outbound port 25 is blocked) --------------------
# Most VPS providers block port 25. If `make preflight` reports it blocked, set a
# relay here: all delivery goes through it and it is added to the published SPF,
# without which every relayed message fails SPF.
RELAY_HOST=
RELAY_PORT=587
RELAY_USER=
RELAY_PASS=
# The SPF term that authorises the relay, exactly as the provider documents it.
# It cannot be derived from RELAY_HOST, and getting it wrong is worse than
# leaving it out. Brevo's relay is smtp-relay.brevo.com, but brevo.com publishes
# the SPF of their corporate mail: the sending ranges are at spf.brevo.com, so
# the value there is "include:spf.brevo.com".
RELAY_SPF_INCLUDE=
# --- Webmail ---
WEBMAIL_HOST=localhost:3000
ADMIN_HOST=
# --- Storage ----------------------------------------------------------------
STORAGE_DRIVER=local
S3_ENDPOINT=
S3_BUCKET=
S3_ACCESS_KEY=
S3_SECRET_KEY=