Deploy: fix the two things that broke the Container Manager build
DSM reported "invalid interpolation format for x-backend-env.MASTER_KEY" and
"required variable MASTER_KEY is missing a value". Both were mine.
1. DSM's bundled Compose cannot parse `${VAR:?message}`. Those guards are gone;
interpolation now sticks to `${VAR}` and `${VAR:-default}`, the forms every
Compose understands. Bind mounts keep a `${DATA_ROOT:-/DATA_ROOT-is-unset}`
sentinel so a missing config still cannot scatter directories across the
root of the NAS filesystem, and the header says to keep it this way.
2. Config was at docker/.env.prod with a `.env` symlink beside it. Container
Manager did not pick that up. The real file is now `.env` in the project
folder — what every compose front end reads without being told — and
docker/.env.prod becomes a symlink to it. `init` and `require_env` migrate
an existing install in place, keeping its MASTER_KEY.
Also, while testing the migration: `init` only generated secrets when creating
a file from scratch, so a migrated or hand-written .env with an empty
POSTGRES_PASSWORD started Postgres in trust-auth mode. It now tops up empty
secrets — and never touches a MASTER_KEY that already has a value, since
replacing it would make every stored credential undecryptable. doctor flags an
empty password too.
Verified: parses cleanly with no .env at all (the failure DSM hit), migration
preserves an existing key, and `docker compose up -d --wait` from the project
folder brings the stack up healthy and serving.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nux3LCu4zP9nih4BRhPyHw
This commit is contained in:
+44
-4
@@ -20,7 +20,8 @@ set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
COMPOSE_FILE="${ROOT_DIR}/docker-compose.yml"
|
||||
ENV_FILE="${ROOT_DIR}/docker/.env.prod"
|
||||
ENV_FILE="${ROOT_DIR}/.env"
|
||||
LEGACY_ENV_FILE="${ROOT_DIR}/docker/.env.prod"
|
||||
ENV_EXAMPLE="${ROOT_DIR}/docker/.env.prod.example"
|
||||
|
||||
# Services that must come back healthy after a roll.
|
||||
@@ -58,6 +59,7 @@ load_env_file() {
|
||||
}
|
||||
|
||||
require_env() {
|
||||
migrate_legacy_env
|
||||
[ -f "$ENV_FILE" ] || die "$ENV_FILE not found. Run: ./deploy/webmail.sh init"
|
||||
# The env file is chmod 600 and usually owned by root (init is run as root
|
||||
# on a NAS), and the data directories are root-owned too. Say so plainly
|
||||
@@ -109,6 +111,38 @@ apply_init_flags() {
|
||||
return 0
|
||||
}
|
||||
|
||||
# Earlier versions kept config at docker/.env.prod with a `.env` symlink beside
|
||||
# it. Synology's Container Manager did not pick that up, so the real file now
|
||||
# lives at the project root. Move an existing one across (keeping its master
|
||||
# key) and leave a symlink behind so older notes still work.
|
||||
migrate_legacy_env() {
|
||||
[ -e "$ENV_FILE" ] && return 0
|
||||
[ -f "$LEGACY_ENV_FILE" ] || return 0
|
||||
[ -L "$LEGACY_ENV_FILE" ] && return 0
|
||||
mv "$LEGACY_ENV_FILE" "$ENV_FILE"
|
||||
chmod 600 "$ENV_FILE"
|
||||
ln -sf ../.env "$LEGACY_ENV_FILE" 2>/dev/null || true
|
||||
ylw "moved docker/.env.prod -> .env (Container Manager reads .env from the project folder)"
|
||||
}
|
||||
|
||||
# Fill in required secrets that are present-but-empty (a migrated file from an
|
||||
# older layout, or a hand-written .env). NEVER touches a MASTER_KEY that already
|
||||
# has a value: replacing it would make every stored mail-account credential
|
||||
# undecryptable.
|
||||
ensure_secrets() {
|
||||
local current
|
||||
current="$(sed -n 's/^MASTER_KEY=//p' "$ENV_FILE" | head -1)"
|
||||
if [ -z "$current" ]; then
|
||||
set_env_value MASTER_KEY "$(openssl rand -base64 32)"
|
||||
ylw "MASTER_KEY was empty — generated a new one. Back it up: webmail.sh show-key"
|
||||
fi
|
||||
current="$(sed -n 's/^POSTGRES_PASSWORD=//p' "$ENV_FILE" | head -1)"
|
||||
if [ -z "$current" ]; then
|
||||
set_env_value POSTGRES_PASSWORD "$(openssl rand -base64 24 | tr -d '/+=' | cut -c1-24)"
|
||||
ylw "POSTGRES_PASSWORD was empty — generated one"
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_init() {
|
||||
# Settings can be passed as flags so the whole install can be driven from a
|
||||
# NAS GUI (DSM Task Scheduler / QTS cron), with no editing of a hidden
|
||||
@@ -128,6 +162,7 @@ cmd_init() {
|
||||
esac
|
||||
done
|
||||
|
||||
migrate_legacy_env
|
||||
if [ ! -f "$ENV_FILE" ]; then
|
||||
cp "$ENV_EXAMPLE" "$ENV_FILE"
|
||||
chmod 600 "$ENV_FILE"
|
||||
@@ -138,15 +173,15 @@ cmd_init() {
|
||||
sed "s|^MASTER_KEY=.*|MASTER_KEY=${master}|; s|^POSTGRES_PASSWORD=.*|POSTGRES_PASSWORD=${pgpass}|" \
|
||||
"$ENV_FILE" > "${ENV_FILE}.tmp" && mv "${ENV_FILE}.tmp" "$ENV_FILE"
|
||||
chmod 600 "$ENV_FILE"
|
||||
# The NAS GUIs read `.env` from the project folder (the repo root).
|
||||
# A symlink keeps one source of truth instead of two copies to drift.
|
||||
[ -e "${ROOT_DIR}/.env" ] || ln -s docker/.env.prod "${ROOT_DIR}/.env" 2>/dev/null || true
|
||||
# Compatibility shim for notes that reference the old path.
|
||||
[ -e "$LEGACY_ENV_FILE" ] || ln -s ../.env "$LEGACY_ENV_FILE" 2>/dev/null || true
|
||||
grn "created $ENV_FILE with freshly generated secrets"
|
||||
ylw ""
|
||||
ylw " >> BACK UP MASTER_KEY NOW. Copy it into your password manager. <<"
|
||||
ylw " Without it, saved mail-account passwords cannot be decrypted even"
|
||||
ylw " from a perfect database backup."
|
||||
ylw ""
|
||||
ensure_secrets
|
||||
apply_init_flags
|
||||
if [ -n "$set_data_root" ]; then
|
||||
grn "configured from flags; you can go straight to: webmail.sh up"
|
||||
@@ -157,6 +192,7 @@ cmd_init() {
|
||||
return 0
|
||||
fi
|
||||
|
||||
ensure_secrets
|
||||
if [ -n "${set_data_root}${set_domain}${set_tz}${set_port}${set_bind}" ]; then
|
||||
apply_init_flags
|
||||
grn "updated $ENV_FILE"
|
||||
@@ -187,6 +223,10 @@ cmd_doctor() {
|
||||
grn " MASTER_KEY looks well-formed"
|
||||
fi
|
||||
|
||||
if [ -z "${POSTGRES_PASSWORD:-}" ]; then
|
||||
red " POSTGRES_PASSWORD is empty — Postgres will refuse to initialise"; ok=1
|
||||
fi
|
||||
|
||||
if [ "$(stat -c %a "$ENV_FILE" 2>/dev/null || echo 600)" != "600" ]; then
|
||||
ylw " $ENV_FILE is not chmod 600 (it holds your master key)"
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user