mirror of
https://github.com/ether/etherpad-lite.git
synced 2026-07-17 16:47:05 +00:00
Env-var overrides for offline/air-gapped installs (update check, plugin catalog, updater) (#7917)
* feat(settings): env-var overrides for update-check/plugin-catalog/updater (offline installs)
Air-gapped and firewalled deployments could not disable Etherpad's outbound
calls (the hourly version check, the admin plugin catalogue, and the
self-updater) without editing settings.json inside the container image — the
shipped settings.json.docker hardcoded updates.tier and omitted the privacy
block entirely, so there was no env-var to flip.
Wire the relevant keys through the existing ${ENV:default} substitution in both
settings.json.docker and settings.json.template:
- PRIVACY_UPDATE_CHECK (privacy.updateCheck, default true)
- PRIVACY_PLUGIN_CATALOG (privacy.pluginCatalog, default true)
- UPDATES_TIER (updates.tier, default notify; "off" = no calls)
- UPDATES_SOURCE / UPDATES_CHANNEL / UPDATES_CHECK_INTERVAL_HOURS /
UPDATES_GITHUB_REPO / UPDATES_REQUIRE_ADMIN_FOR_STATUS (docker)
- UPDATE_SERVER (updateServer endpoint)
Document the full set in doc/docker.md (new "Updates & privacy" section) and
cross-link from doc/admin/updates.md. Add backend regression tests that parse
the shipped settings.json.docker and settings.json.template and assert the
overrides apply with correct boolean/numeric coercion, so a future edit that
drops the ${ENV} placeholders fails loudly.
Addresses #7911 (item 2). No code changes — config + docs + tests only.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: address Qodo review — point to PRIVACY.md, clarify tier=off scope
- Outbound-call docs/comments referenced doc/privacy.md (the storage/logging
doc); the canonical outbound-call inventory is repo-root PRIVACY.md, which the
runtime messages in UpdateCheck.ts / Settings.ts also reference. Re-point
settings.json.{template,docker} and doc/docker.md there.
- doc/admin/updates.md said updates.tier="off" means "no HTTP request will leave
the instance", but the legacy UpdateCheck.ts call to ${updateServer}/info.json
is gated by privacy.updateCheck, not updates.tier. Clarify that air-gapped
installs must set PRIVACY_UPDATE_CHECK=false too.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: link PRIVACY.md via absolute URL to satisfy VitePress dead-link check
PRIVACY.md lives at the repo root, outside the doc/ tree VitePress builds, so a
relative link to it (../PRIVACY.md / ../../PRIVACY.md) is flagged as a dead link
and fails `docs:build`. Use the absolute GitHub URL instead, matching how
doc/configuration.md already links settings.json.template.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
662678275a
commit
f79cabe74c
5 changed files with 130 additions and 12 deletions
|
|
@ -102,7 +102,9 @@ The notice auto-fades after 8 seconds and can be dismissed immediately. The publ
|
||||||
|
|
||||||
## Disabling everything
|
## Disabling everything
|
||||||
|
|
||||||
Set `updates.tier` to `"off"`. No HTTP request will leave the instance and no banner or badge will render.
|
Set `updates.tier` to `"off"`. The self-updater goes silent — no request to the GitHub Releases API leaves the instance and no banner or badge renders. Note this does **not** cover the separate legacy version check in `UpdateCheck.ts`, which still fetches `${updateServer}/info.json` until you also set `privacy.updateCheck` to `false` (see [PRIVACY.md](https://github.com/ether/etherpad/blob/develop/PRIVACY.md)).
|
||||||
|
|
||||||
|
On Docker / air-gapped installs you can do both without editing `settings.json` inside the image by setting `UPDATES_TIER=off` **and** `PRIVACY_UPDATE_CHECK=false` (add `PRIVACY_PLUGIN_CATALOG=false` to also disable the admin plugin browser's catalogue fetch). See the [Updates & privacy](../docker.md#updates--privacy-offline--air-gapped) table in the Docker docs for the full set of environment variables.
|
||||||
|
|
||||||
## Privacy
|
## Privacy
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -118,6 +118,25 @@ The `settings.json.docker` available by default allows to control almost every s
|
||||||
| `USER_PASSWORD` | the password for the first user `user` (leave unspecified if you do not want to create it) | |
|
| `USER_PASSWORD` | the password for the first user `user` (leave unspecified if you do not want to create it) | |
|
||||||
|
|
||||||
|
|
||||||
|
### Updates & privacy (offline / air-gapped)
|
||||||
|
|
||||||
|
Etherpad makes a small number of outbound calls (a periodic version check and the admin plugin catalogue). In an air-gapped or firewalled deployment these can be disabled entirely without editing `settings.json` inside the image — set the variables below. See [PRIVACY.md](https://github.com/ether/etherpad/blob/develop/PRIVACY.md) and [doc/admin/updates.md](admin/updates.md) for what each call sends.
|
||||||
|
|
||||||
|
| Variable | Description | Default |
|
||||||
|
| --------------------------------- | ---------------------------------------------------------------------------------------------------------- | -------------------------------- |
|
||||||
|
| `PRIVACY_UPDATE_CHECK` | Set to `false` to disable the hourly version check (`UpdateCheck.ts`). | `true` |
|
||||||
|
| `PRIVACY_PLUGIN_CATALOG` | Set to `false` to disable the admin plugin browser (manual install-by-name via CLI still works). | `true` |
|
||||||
|
| `UPDATES_TIER` | Self-updater tier: `off` \| `notify` \| `manual` \| `auto` \| `autonomous`. Set to `off` to suppress the GitHub Releases check entirely. | `notify` |
|
||||||
|
| `UPDATES_SOURCE` | Where update metadata is fetched from. | `github` |
|
||||||
|
| `UPDATES_CHANNEL` | Release channel to track. | `stable` |
|
||||||
|
| `UPDATES_CHECK_INTERVAL_HOURS` | How often (hours) the updater polls when not `off`. | `6` |
|
||||||
|
| `UPDATES_GITHUB_REPO` | Repository the updater checks for releases. | `ether/etherpad` |
|
||||||
|
| `UPDATES_REQUIRE_ADMIN_FOR_STATUS`| Lock `/admin/update/status` to authenticated admins. | `false` |
|
||||||
|
| `UPDATE_SERVER` | Endpoint backing the version check. Point elsewhere (or disable the check above) for offline installs. | `https://etherpad.org/ep_infos` |
|
||||||
|
|
||||||
|
> **Fully offline:** set `UPDATES_TIER=off`, `PRIVACY_UPDATE_CHECK=false`, and `PRIVACY_PLUGIN_CATALOG=false`. The version check is fire-and-forget and already fails closed (a blocked endpoint is caught and logged, it does not prevent startup), but disabling it removes the outbound attempt and the log noise.
|
||||||
|
|
||||||
|
|
||||||
### Database
|
### Database
|
||||||
|
|
||||||
| Variable | Description | Default |
|
| Variable | Description | Default |
|
||||||
|
|
|
||||||
|
|
@ -210,15 +210,17 @@
|
||||||
* tier: "off" | "notify" | "manual" | "auto" | "autonomous"
|
* tier: "off" | "notify" | "manual" | "auto" | "autonomous"
|
||||||
* Default "notify" shows a banner when an update is available.
|
* Default "notify" shows a banner when an update is available.
|
||||||
* Docker installs are read-only — tiers above "notify" are not applied even if requested.
|
* Docker installs are read-only — tiers above "notify" are not applied even if requested.
|
||||||
|
* Air-gapped / offline deployments should set UPDATES_TIER=off to suppress the
|
||||||
|
* periodic check against the GitHub Releases API entirely.
|
||||||
*/
|
*/
|
||||||
"updates": {
|
"updates": {
|
||||||
"tier": "notify",
|
"tier": "${UPDATES_TIER:notify}",
|
||||||
"source": "github",
|
"source": "${UPDATES_SOURCE:github}",
|
||||||
"channel": "stable",
|
"channel": "${UPDATES_CHANNEL:stable}",
|
||||||
"installMethod": "docker",
|
"installMethod": "docker",
|
||||||
"checkIntervalHours": 6,
|
"checkIntervalHours": "${UPDATES_CHECK_INTERVAL_HOURS:6}",
|
||||||
"githubRepo": "ether/etherpad",
|
"githubRepo": "${UPDATES_GITHUB_REPO:ether/etherpad}",
|
||||||
"requireAdminForStatus": false,
|
"requireAdminForStatus": "${UPDATES_REQUIRE_ADMIN_FOR_STATUS:false}",
|
||||||
"preApplyGraceMinutes": 0,
|
"preApplyGraceMinutes": 0,
|
||||||
"drainSeconds": 60,
|
"drainSeconds": 60,
|
||||||
"rollbackHealthCheckSeconds": 60,
|
"rollbackHealthCheckSeconds": 60,
|
||||||
|
|
@ -321,7 +323,19 @@
|
||||||
* https://etherpad.org/ep_infos
|
* https://etherpad.org/ep_infos
|
||||||
*/
|
*/
|
||||||
|
|
||||||
"updateServer": "https://etherpad.org/ep_infos",
|
"updateServer": "${UPDATE_SERVER:https://etherpad.org/ep_infos}",
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Outbound network calls. See PRIVACY.md for what each one sends.
|
||||||
|
* - PRIVACY_UPDATE_CHECK=false : disables the hourly version check (UpdateCheck.ts)
|
||||||
|
* - PRIVACY_PLUGIN_CATALOG=false : disables the admin plugin browser
|
||||||
|
* (manual install-by-name via CLI still works)
|
||||||
|
* Air-gapped / firewalled deployments should set both to false.
|
||||||
|
*/
|
||||||
|
"privacy": {
|
||||||
|
"updateCheck": "${PRIVACY_UPDATE_CHECK:true}",
|
||||||
|
"pluginCatalog": "${PRIVACY_PLUGIN_CATALOG:true}"
|
||||||
|
},
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The type of the database.
|
* The type of the database.
|
||||||
|
|
|
||||||
|
|
@ -216,7 +216,7 @@
|
||||||
* Default "notify" shows a banner when an update is available. "off" disables the version check.
|
* Default "notify" shows a banner when an update is available. "off" disables the version check.
|
||||||
*/
|
*/
|
||||||
"updates": {
|
"updates": {
|
||||||
"tier": "notify",
|
"tier": "${UPDATES_TIER:notify}",
|
||||||
"source": "github",
|
"source": "github",
|
||||||
"channel": "stable",
|
"channel": "stable",
|
||||||
"installMethod": "auto",
|
"installMethod": "auto",
|
||||||
|
|
@ -443,17 +443,19 @@
|
||||||
* https://etherpad.org/ep_infos
|
* https://etherpad.org/ep_infos
|
||||||
*/
|
*/
|
||||||
|
|
||||||
"updateServer": "https://etherpad.org/ep_infos",
|
"updateServer": "${UPDATE_SERVER:https://etherpad.org/ep_infos}",
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Outbound network calls. See PRIVACY.md for what each one sends.
|
* Outbound network calls. See PRIVACY.md for what each one sends.
|
||||||
* - updateCheck=false : disables hourly version check (UpdateCheck.ts)
|
* - updateCheck=false : disables hourly version check (UpdateCheck.ts)
|
||||||
* - pluginCatalog=false: disables admin plugin browser
|
* - pluginCatalog=false: disables admin plugin browser
|
||||||
* (manual install-by-name via CLI still works)
|
* (manual install-by-name via CLI still works)
|
||||||
|
* Air-gapped / firewalled deployments should set both PRIVACY_UPDATE_CHECK and
|
||||||
|
* PRIVACY_PLUGIN_CATALOG to false (or UPDATES_TIER=off, which covers the version check).
|
||||||
*/
|
*/
|
||||||
"privacy": {
|
"privacy": {
|
||||||
"updateCheck": true,
|
"updateCheck": "${PRIVACY_UPDATE_CHECK:true}",
|
||||||
"pluginCatalog": true
|
"pluginCatalog": "${PRIVACY_PLUGIN_CATALOG:true}"
|
||||||
},
|
},
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
||||||
|
|
@ -197,4 +197,85 @@ describe(__filename, function () {
|
||||||
assert.strictEqual(settings.padOptions.fadeInactiveAuthorColors, true);
|
assert.strictEqual(settings.padOptions.fadeInactiveAuthorColors, true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Regression test for ether/etherpad#7911.
|
||||||
|
// Air-gapped / firewalled deployments must be able to disable Etherpad's
|
||||||
|
// outbound calls (version check + plugin catalogue + self-updater) purely
|
||||||
|
// via environment variables, without editing settings.json inside the image.
|
||||||
|
// These assertions parse the *shipped* settings.json.docker so a future edit
|
||||||
|
// that drops the ${ENV} placeholders fails loudly here.
|
||||||
|
describe('offline / air-gapped env overrides (issue #7911)', function () {
|
||||||
|
const dockerSettings = path.join(__dirname, '../../../../settings.json.docker');
|
||||||
|
const templateSettings = path.join(__dirname, '../../../../settings.json.template');
|
||||||
|
const envVars = [
|
||||||
|
'PRIVACY_UPDATE_CHECK', 'PRIVACY_PLUGIN_CATALOG', 'UPDATES_TIER',
|
||||||
|
'UPDATES_SOURCE', 'UPDATES_CHANNEL', 'UPDATES_CHECK_INTERVAL_HOURS',
|
||||||
|
'UPDATES_GITHUB_REPO', 'UPDATES_REQUIRE_ADMIN_FOR_STATUS', 'UPDATE_SERVER',
|
||||||
|
];
|
||||||
|
const saved: {[k: string]: string | undefined} = {};
|
||||||
|
|
||||||
|
before(function () { for (const v of envVars) saved[v] = process.env[v]; });
|
||||||
|
afterEach(function () {
|
||||||
|
for (const v of envVars) {
|
||||||
|
if (saved[v] == null) delete process.env[v];
|
||||||
|
else process.env[v] = saved[v];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps shipped defaults when no env vars are set', function () {
|
||||||
|
for (const v of envVars) delete process.env[v];
|
||||||
|
const s = exportedForTestingOnly.parseSettings(dockerSettings, true);
|
||||||
|
assert.strictEqual(s!.privacy.updateCheck, true);
|
||||||
|
assert.strictEqual(s!.privacy.pluginCatalog, true);
|
||||||
|
assert.strictEqual(s!.updates.tier, 'notify');
|
||||||
|
assert.strictEqual(s!.updates.checkIntervalHours, 6);
|
||||||
|
assert.strictEqual(s!.updateServer, 'https://etherpad.org/ep_infos');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('disables all outbound calls when the offline env vars are set', function () {
|
||||||
|
process.env.PRIVACY_UPDATE_CHECK = 'false';
|
||||||
|
process.env.PRIVACY_PLUGIN_CATALOG = 'false';
|
||||||
|
process.env.UPDATES_TIER = 'off';
|
||||||
|
const s = exportedForTestingOnly.parseSettings(dockerSettings, true);
|
||||||
|
// Coerced to real booleans, not the strings "false".
|
||||||
|
assert.strictEqual(s!.privacy.updateCheck, false);
|
||||||
|
assert.strictEqual(s!.privacy.pluginCatalog, false);
|
||||||
|
assert.strictEqual(s!.updates.tier, 'off');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('honours the remaining updates.* and updateServer overrides', function () {
|
||||||
|
process.env.UPDATES_SOURCE = 'gitlab';
|
||||||
|
process.env.UPDATES_CHANNEL = 'beta';
|
||||||
|
process.env.UPDATES_CHECK_INTERVAL_HOURS = '24';
|
||||||
|
process.env.UPDATES_GITHUB_REPO = 'acme/etherpad-fork';
|
||||||
|
process.env.UPDATES_REQUIRE_ADMIN_FOR_STATUS = 'true';
|
||||||
|
process.env.UPDATE_SERVER = 'https://mirror.internal/ep_infos';
|
||||||
|
const s = exportedForTestingOnly.parseSettings(dockerSettings, true);
|
||||||
|
assert.strictEqual(s!.updates.source, 'gitlab');
|
||||||
|
assert.strictEqual(s!.updates.channel, 'beta');
|
||||||
|
assert.strictEqual(s!.updates.checkIntervalHours, 24); // numeric coercion
|
||||||
|
assert.strictEqual(s!.updates.githubRepo, 'acme/etherpad-fork');
|
||||||
|
assert.strictEqual(s!.updates.requireAdminForStatus, true); // boolean coercion
|
||||||
|
assert.strictEqual(s!.updateServer, 'https://mirror.internal/ep_infos');
|
||||||
|
});
|
||||||
|
|
||||||
|
// The source-install template carries the same placeholders so non-Docker
|
||||||
|
// deployments get the offline knobs too.
|
||||||
|
it('settings.json.template exposes the same offline overrides', function () {
|
||||||
|
for (const v of envVars) delete process.env[v];
|
||||||
|
const dflt = exportedForTestingOnly.parseSettings(templateSettings, true);
|
||||||
|
assert.strictEqual(dflt!.privacy.updateCheck, true);
|
||||||
|
assert.strictEqual(dflt!.privacy.pluginCatalog, true);
|
||||||
|
assert.strictEqual(dflt!.updates.tier, 'notify');
|
||||||
|
assert.strictEqual(dflt!.updateServer, 'https://etherpad.org/ep_infos');
|
||||||
|
|
||||||
|
process.env.UPDATES_TIER = 'off';
|
||||||
|
process.env.PRIVACY_UPDATE_CHECK = 'false';
|
||||||
|
process.env.PRIVACY_PLUGIN_CATALOG = 'false';
|
||||||
|
const over = exportedForTestingOnly.parseSettings(templateSettings, true);
|
||||||
|
assert.strictEqual(over!.updates.tier, 'off');
|
||||||
|
assert.strictEqual(over!.privacy.updateCheck, false);
|
||||||
|
assert.strictEqual(over!.privacy.pluginCatalog, false);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue