Skip to content

fix(scripts): resolve backup/restore config the way the application does - #1042

Merged
rmyndharis merged 1 commit into
mainfrom
fix/backup-honours-dashboard-config
Aug 1, 2026
Merged

fix(scripts): resolve backup/restore config the way the application does#1042
rmyndharis merged 1 commit into
mainfrom
fix/backup-honours-dashboard-config

Conversation

@rmyndharis

Copy link
Copy Markdown
Owner

The failure

backup.sh and restore.sh read every path from the process environment alone. The application
fills the same settings from three layers (src/config/load-env.ts):

  1. the process environment
  2. ./.env
  3. <data dir>/.env.generated — written by Dashboard > Infrastructure

So an install configured through the dashboard was backed up at the default paths.

That is not reliably loud. backup_sqlite already fails hard when a database is missing, which
covers the obvious case. The dangerous shape is the other one: a database left at a default path from
before the operator switched. Then the run archives the abandoned file and exits 0 — a backup that
announces itself as wrong only during a restore.

backup.sh was already copying .env.generated into the archive without ever reading it, and
docs/11-operational-runbooks.md pushed the gap onto the operator, telling them to restate
MAIN_DATABASE_NAME / DATABASE_NAME by hand "when the app overrides them".

The change

scripts/lib-env.sh resolves a key through the three layers in the application's order. Both scripts
use it for the database paths, the state directories, and the Postgres connection details pg_dump
needs — so a dashboard-provisioned database no longer needs its credentials restated in the
operator's shell.

Deliberate constraints:

  • An explicit environment value still wins. Inverting the precedence would be a worse bug than the
    one being fixed, so the new smoke case asserts that direction too.
  • Nothing is exported. Each key is looked up by name, so a stray entry in an operator's .env
    can never reach the script's own environment.
  • Only plain KEY=value lines are honoured. A value carrying quotes or a trailing # comment is
    reported and skipped rather than guessed at — a silently mis-parsed path is precisely the failure
    this exists to prevent. .env.generated is machine-written and always in this form; the warning
    exists for hand-edited .env files.
  • OPENWA_DATA_DIR and BACKUP_DIR steer the script rather than the application and are never
    written to an env file, so they stay environment-only.

Tests

New smoke case (f) reproduces the dangerous shape exactly: a stale database sitting at the default
path while data/.env.generated names the live one. It asserts four things — backup takes the live
database, restore writes to the path the same layer names, and an explicit environment value still
overrides both.

Watched failing first, on the real defect:

==> (f) data/.env.generated supplies paths the environment does not
FAIL: (f) backup archived the stale default main DB instead of the one data/.env.generated names

The precedence assertion is separately mutation-proven, since the first failure short-circuits before
it runs — inverting the lookup order in lib-env.sh produces:

FAIL: (f) an explicit environment path lost to data/.env.generated — precedence is inverted

Verification

  • shellcheck scripts/*.sh (0.11.0, the CI version) — clean, including the new lib-env.sh
  • backup/restore smoke test — 6/6 cases pass
  • backend suite 4048 tests, unchanged — nothing on the gateway is touched

Both scripts read every path from the process environment alone, while the app
fills the same settings from three layers (src/config/load-env.ts): the
environment, then ./.env, then <data dir>/.env.generated — the file Dashboard >
Infrastructure writes. An install configured through the dashboard was therefore
backed up at the DEFAULT paths.

That is not reliably loud. backup_sqlite already fails hard on a missing database,
but a database left at a default path from BEFORE the operator switched is
archived instead and the run exits 0. A backup that captured an abandoned database
announces itself only during a restore. backup.sh was already copying
.env.generated into the archive without ever reading it, and the runbook pushed
the gap onto the operator by telling them to restate the paths by hand.

lib-env.sh resolves a key through the three layers in the app's order, so an
explicit environment value still wins — the new smoke case asserts that direction
too, since silently inverting the precedence would be a worse bug than the one
being fixed. Nothing is exported: each key is looked up by name, so a stray entry
in an operator's .env cannot reach the script's environment. Only plain KEY=value
lines are honoured; a value carrying quotes or a trailing # comment is reported and
skipped rather than guessed at, because a silently mis-parsed path is exactly the
failure this exists to prevent.

The Postgres details pg_dump uses resolve the same way, so a dashboard-provisioned
database no longer needs its credentials restated in the operator's shell.

Smoke case (f) reproduces the dangerous shape specifically: a stale database at the
default path while .env.generated names the live one. It failed before this change
with the stale file archived and a zero exit.
@rmyndharis
rmyndharis merged commit 783eb01 into main Aug 1, 2026
8 checks passed
@rmyndharis
rmyndharis deleted the fix/backup-honours-dashboard-config branch August 1, 2026 14:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant