{"id":"699f7bfce0b9ae881d3e409f","title":"2026 02 25 Issue 146","path":"how-to/committee/release-notes/2026-02-25-issue-146","contentMarkdown":"# 25-Feb-2026 — secure and harden Environment Setup and Backup & Restore [ref #146](https://github.com/nbarrett/ngx-ramblers/issues/146)\n\n## [build 499](https://github.com/nbarrett/ngx-ramblers/actions/runs/22418830262) — [commit 8ea4a2e](https://github.com/nbarrett/ngx-ramblers/commit/8ea4a2efc262baadd2710c339b333cdc869b463f)\n\n_____\n\n## Platform admin gating\n- Backup & Restore and Environment Setup now check `PLATFORM_ADMIN_ENABLED` on\ninit and display a consistent \"Not Available\" alert (icon + bold title) when\nthe flag is absent, matching the alert style used across the admin section\n- Backup & Restore skips loading environments, backups, sessions and the\nWebSocket connection when the flag is absent, avoiding unnecessary API calls\n- Added the ngx-ramblers environment entry to the EnvironmentsConfig DB document\nwith `PLATFORM_ADMIN_ENABLED=true` in its secrets block so the flag survives\nfuture deploys via `loadSecretsForEnvironmentFromDatabase`\n## Setup API-key authentication\n- Environment Setup routes now accept requests authenticated by either a valid\nadmin JWT (member/content/file/walk/social/treasury/finance admin roles) or\nthe `x-setup-api-key` header, enabling logged-in admins to use the page\nwithout a separate setup key\n- `isAdminRequest()` added to server routes — verifies the Bearer token with\n`jsonwebtoken` and checks any admin-role claim before granting access\n- `EnvironmentSetupService` gains a `setupApiKey` field and an `opts` getter;\nall HTTP calls now forward the key in `x-setup-api-key` when present\n- Component gains `requiresApiKey` / `setupApiKeyValue` / `setupApiKeyEntered`\nstate and a `submitSetupApiKey()` method so the UI can prompt for the key\nwhen the server signals it is required (`status.requiresApiKey`)\n## Defaults loading improvements\n- `loadDefaults()` now loads system config first (normal session auth, never\n401) and then loads environment defaults separately so a 401 on the defaults\nendpoint does not prevent the Ramblers API key from being pre-filled\n- `EnvironmentDefaults` model extended with `ramblers.apiKey` field\n- `/defaults` endpoint populates `ramblers.apiKey` from\n`national.walksManager.apiKey` in the system config so the Ramblers API key\nis offered as a default without manual entry\n## Mongoose reconnection fix\n- `closeMigrationConnection()` calls `mongoose.disconnect()` after running\nmigrations, which left subsequent operations (`seedBrevoTemplatesFromLocal` ->\n`configuredBrevo` -> `config.findOne`) failing with \"Client must be connected\nbefore running operations\"\n- Added `ensureMongoConnection()` after `initialiseDatabase()` returns so\nmongoose reconnects to the main database before Brevo template population runs\n## GitHub Secrets panel (Environment Settings → GitHub Secrets sub-tab)\n- New `EnvironmentGitHubSecrets` standalone component shows the GitHub\n`CONFIGS_JSON` secret staleness relative to the database, with per-environment\ndiff detail (new / removed / changed fields) and a one-click Push button\n- `GET /api/environment-setup/github/status` compares DB environments against\nthe live `CONFIGS_JSON` value; matches by name first then appName fallback to\navoid cross-matching where multiple DB documents share the same fly.io app\n- `POST /api/environment-setup/github/push` pushes DB → GitHub directly without\ntouching configs.json; after a successful push, `process.env.CONFIGS_JSON` is\nupdated in-process so subsequent status checks reflect what was pushed\n- On first status check locally (no `CONFIGS_JSON` env var), the value is\ninitialised from the DB — same runtime behaviour as production where fly.io\ninjects it at startup — eliminating configs.json as a third piece of state\n- `transformDatabaseToDeployConfig` now filters out orphan DB environment\ndocuments (no flyio config), deduplicates by appName (first occurrence wins),\nand is exported for reuse in the status route\n- `EnvironmentSettingsSubTab.GITHUB` added; heading uses `with-vendor-logo`\nstyle with the GitHub FA brand icon; `.thumbnail-heading.with-vendor-logo`\ntop offset corrected to -34px in frames.sass\n## Dockerfile: GitHub CLI\n- `gh` CLI installed in Docker image so the GitHub Secrets panel can fetch\nsecret metadata and push `CONFIGS_JSON` from the deployed environment\n- `GH_TOKEN` and `PLATFORM_ADMIN_ENABLED=true` set as fly.io secrets on the\nngx-ramblers (staging) app to authenticate `gh` and enable the admin pages","contentHtml":"<h1>25-Feb-2026 — secure and harden Environment Setup and Backup &amp; Restore <a href=\"https://github.com/nbarrett/ngx-ramblers/issues/146\">ref #146</a></h1>\n<h2><a href=\"https://github.com/nbarrett/ngx-ramblers/actions/runs/22418830262\">build 499</a> — <a href=\"https://github.com/nbarrett/ngx-ramblers/commit/8ea4a2efc262baadd2710c339b333cdc869b463f\">commit 8ea4a2e</a></h2>\n<hr>\n<h2>Platform admin gating</h2>\n<ul>\n<li>Backup &amp; Restore and Environment Setup now check <code>PLATFORM_ADMIN_ENABLED</code> on\ninit and display a consistent &quot;Not Available&quot; alert (icon + bold title) when\nthe flag is absent, matching the alert style used across the admin section</li>\n<li>Backup &amp; Restore skips loading environments, backups, sessions and the\nWebSocket connection when the flag is absent, avoiding unnecessary API calls</li>\n<li>Added the ngx-ramblers environment entry to the EnvironmentsConfig DB document\nwith <code>PLATFORM_ADMIN_ENABLED=true</code> in its secrets block so the flag survives\nfuture deploys via <code>loadSecretsForEnvironmentFromDatabase</code></li>\n</ul>\n<h2>Setup API-key authentication</h2>\n<ul>\n<li>Environment Setup routes now accept requests authenticated by either a valid\nadmin JWT (member/content/file/walk/social/treasury/finance admin roles) or\nthe <code>x-setup-api-key</code> header, enabling logged-in admins to use the page\nwithout a separate setup key</li>\n<li><code>isAdminRequest()</code> added to server routes — verifies the Bearer token with\n<code>jsonwebtoken</code> and checks any admin-role claim before granting access</li>\n<li><code>EnvironmentSetupService</code> gains a <code>setupApiKey</code> field and an <code>opts</code> getter;\nall HTTP calls now forward the key in <code>x-setup-api-key</code> when present</li>\n<li>Component gains <code>requiresApiKey</code> / <code>setupApiKeyValue</code> / <code>setupApiKeyEntered</code>\nstate and a <code>submitSetupApiKey()</code> method so the UI can prompt for the key\nwhen the server signals it is required (<code>status.requiresApiKey</code>)</li>\n</ul>\n<h2>Defaults loading improvements</h2>\n<ul>\n<li><code>loadDefaults()</code> now loads system config first (normal session auth, never</li>\n</ul>\n<ol start=\"401\">\n<li>and then loads environment defaults separately so a 401 on the defaults\nendpoint does not prevent the Ramblers API key from being pre-filled</li>\n</ol>\n<ul>\n<li><code>EnvironmentDefaults</code> model extended with <code>ramblers.apiKey</code> field</li>\n<li><code>/defaults</code> endpoint populates <code>ramblers.apiKey</code> from\n<code>national.walksManager.apiKey</code> in the system config so the Ramblers API key\nis offered as a default without manual entry</li>\n</ul>\n<h2>Mongoose reconnection fix</h2>\n<ul>\n<li><code>closeMigrationConnection()</code> calls <code>mongoose.disconnect()</code> after running\nmigrations, which left subsequent operations (<code>seedBrevoTemplatesFromLocal</code> -&gt;\n<code>configuredBrevo</code> -&gt; <code>config.findOne</code>) failing with &quot;Client must be connected\nbefore running operations&quot;</li>\n<li>Added <code>ensureMongoConnection()</code> after <code>initialiseDatabase()</code> returns so\nmongoose reconnects to the main database before Brevo template population runs</li>\n</ul>\n<h2>GitHub Secrets panel (Environment Settings → GitHub Secrets sub-tab)</h2>\n<ul>\n<li>New <code>EnvironmentGitHubSecrets</code> standalone component shows the GitHub\n<code>CONFIGS_JSON</code> secret staleness relative to the database, with per-environment\ndiff detail (new / removed / changed fields) and a one-click Push button</li>\n<li><code>GET /api/environment-setup/github/status</code> compares DB environments against\nthe live <code>CONFIGS_JSON</code> value; matches by name first then appName fallback to\navoid cross-matching where multiple DB documents share the same fly.io app</li>\n<li><code>POST /api/environment-setup/github/push</code> pushes DB → GitHub directly without\ntouching configs.json; after a successful push, <code>process.env.CONFIGS_JSON</code> is\nupdated in-process so subsequent status checks reflect what was pushed</li>\n<li>On first status check locally (no <code>CONFIGS_JSON</code> env var), the value is\ninitialised from the DB — same runtime behaviour as production where fly.io\ninjects it at startup — eliminating configs.json as a third piece of state</li>\n<li><code>transformDatabaseToDeployConfig</code> now filters out orphan DB environment\ndocuments (no flyio config), deduplicates by appName (first occurrence wins),\nand is exported for reuse in the status route</li>\n<li><code>EnvironmentSettingsSubTab.GITHUB</code> added; heading uses <code>with-vendor-logo</code>\nstyle with the GitHub FA brand icon; <code>.thumbnail-heading.with-vendor-logo</code>\ntop offset corrected to -34px in frames.sass</li>\n</ul>\n<h2>Dockerfile: GitHub CLI</h2>\n<ul>\n<li><code>gh</code> CLI installed in Docker image so the GitHub Secrets panel can fetch\nsecret metadata and push <code>CONFIGS_JSON</code> from the deployed environment</li>\n<li><code>GH_TOKEN</code> and <code>PLATFORM_ADMIN_ENABLED=true</code> set as fly.io secrets on the\nngx-ramblers (staging) app to authenticate <code>gh</code> and enable the admin pages</li>\n</ul>\n"}