mirror of
https://github.com/ether/etherpad-lite.git
synced 2026-07-17 16:47:05 +00:00
docs: refresh docs for 3.2.0 — correct stale content, document recent features (#7888)
* docs: refresh docs for 3.2.0 — correct stale content, document recent features The hand-maintained VitePress docs under doc/ had drifted behind a lot of recent work. They are authored prose (not generated from the OpenAPI spec), so they need manual upkeep. This pass corrects content that was actively wrong and documents features shipped since they were last touched. Corrections (was wrong / misleading): - cli.md: every command used `node bin/foo.js`, but the scripts are TypeScript run via pnpm — copy-paste failed. Rewrote to `pnpm run --filter bin <script>`, documented ~13 previously-undocumented operator tools, and split running-vs-stopped requirements. Registered the missing `compactStalePads` script in bin/package.json so the documented invocation actually works. - stats.md: described a pre-Prometheus world. Rewrote for the gated `/stats` (JSON) and `/stats/prometheus` endpoints, the live metric set, the opt-in `scalingDiveMetrics` instruments (#7756), and `measured-core`. - admin/updates.md: removed three false "SMTP not yet wired" claims (it is, via nodemailer + the `mail.*` block), documented the `node-engine-mismatch` preflight check and the rollback/preflight failure emails, and stripped obsolete "PR 1 / PR 2" staging language now that all tiers ship. - api/http_api.md: added the undocumented `anonymizeAuthor` (GDPR Art. 17) call, fixed copyPad/movePad version annotations (1.2.8 → 1.2.9), corrected getPadID's param name (readOnlyID → roID), and dropped a reference to a non-existent `getEtherpad` API call. - skins.md: colibris is the current default, not an "experimental" skin for a future 2.0. - localization.md: bare `window._('key')` is unbound and returns undefined; recommend `window.html10n.get(...)` / data-l10n-id instead. - README.md: bumped the v2.2.5 upgrade example to v3.2.0; fixed a docker.adoc link to docker.md. - docker.md: added MAIL_*, ENABLE_METRICS, GDPR_AUTHOR_ERASURE_ENABLED, PRIVACY_BANNER_*, PUBLIC_URL, AUTHENTICATION_METHOD, ENABLE_DARK_MODE, ENABLE_PAD_WIDE_SETTINGS; fixed the SOCKETIO_MAX_HTTP_BUFFER_SIZE default (50000 → 1000000). New documentation: - configuration.md (new): how settings + `${VAR:default}` substitution work, trustProxy, and — the previously-undocumented feature — running under a subpath/ingress via x-proxy-path / X-Forwarded-Prefix / X-Ingress-Path, with the sanitizer rules and Traefik/NGINX examples. Wired into the VitePress sidebar and the index hero. - hooks_server-side.md: ccRegisterBlockElements (the server-side companion plugin authors miss), exportConvert, exportHTMLSend, createServer, restartServer, and clientReady (marked deprecated). - hooks_client-side.md: aceDrop, acePaste, handleClientTimesliderMessage_<name>. VitePress build passes. The legacy .adoc set was intentionally left in place — it still feeds the per-version doc archives published to ether.github.com at release time (bin/release.ts), so it is not dead and is out of scope here. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: address Qodo review — drop .js invocations from configuration.md and CLI help - configuration.md: the settings-override example referenced a nonexistent `node src/node/server.js`. Use the supported launcher instead (`bin/run.sh -s <file>`), and note the runtime is server.ts via tsx. - compactStalePads.ts / compactPad.ts / compactAllPads.ts: their header comments and runtime usage output still printed `node bin/*.js`, which points at files that don't exist. Switched to the documented `pnpm run --filter bin <script>` form so the --help text matches the docs. 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
7b33892a76
commit
2d1a09dbe0
17 changed files with 664 additions and 79 deletions
|
|
@ -172,7 +172,7 @@ volumes:
|
||||||
|
|
||||||
### Docker container
|
### Docker container
|
||||||
|
|
||||||
Find [here](doc/docker.adoc) information on running Etherpad in a container.
|
Find [here](doc/docker.md) information on running Etherpad in a container.
|
||||||
|
|
||||||
## Plugins
|
## Plugins
|
||||||
|
|
||||||
|
|
@ -247,8 +247,8 @@ git -P tag --list "v*" --merged
|
||||||
```
|
```
|
||||||
4. Select the version
|
4. Select the version
|
||||||
```sh
|
```sh
|
||||||
git checkout v2.2.5
|
git checkout v3.2.0
|
||||||
git switch -c v2.2.5
|
git switch -c v3.2.0
|
||||||
```
|
```
|
||||||
5. Upgrade Etherpad
|
5. Upgrade Etherpad
|
||||||
```sh
|
```sh
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,9 @@
|
||||||
* Compact every pad on the instance to reclaim database space.
|
* Compact every pad on the instance to reclaim database space.
|
||||||
*
|
*
|
||||||
* Usage:
|
* Usage:
|
||||||
* node bin/compactAllPads.js # collapse all history on every pad
|
* pnpm run --filter bin compactAllPads # collapse all history on every pad
|
||||||
* node bin/compactAllPads.js --keep N # keep last N revisions per pad
|
* pnpm run --filter bin compactAllPads --keep N # keep last N revisions per pad
|
||||||
* node bin/compactAllPads.js --dry-run # list pads + rev counts, no writes
|
* pnpm run --filter bin compactAllPads --dry-run # list pads + rev counts, no writes
|
||||||
*
|
*
|
||||||
* Composes the existing `listAllPads` and `compactPad` HTTP APIs — there is
|
* Composes the existing `listAllPads` and `compactPad` HTTP APIs — there is
|
||||||
* deliberately no instance-wide HTTP endpoint, because doing this over a
|
* deliberately no instance-wide HTTP endpoint, because doing this over a
|
||||||
|
|
@ -170,9 +170,9 @@ export const parseArgs = (argv: string[]): CompactAllOpts | null => {
|
||||||
// so the test harness can use `runCompactAll` directly without network.
|
// so the test harness can use `runCompactAll` directly without network.
|
||||||
const usage = () => {
|
const usage = () => {
|
||||||
console.error('Usage:');
|
console.error('Usage:');
|
||||||
console.error(' node bin/compactAllPads.js');
|
console.error(' pnpm run --filter bin compactAllPads');
|
||||||
console.error(' node bin/compactAllPads.js --keep <N>');
|
console.error(' pnpm run --filter bin compactAllPads --keep <N>');
|
||||||
console.error(' node bin/compactAllPads.js --dry-run');
|
console.error(' pnpm run --filter bin compactAllPads --dry-run');
|
||||||
process.exit(2);
|
process.exit(2);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@
|
||||||
* Compact a pad's revision history to reclaim database space.
|
* Compact a pad's revision history to reclaim database space.
|
||||||
*
|
*
|
||||||
* Usage:
|
* Usage:
|
||||||
* node bin/compactPad.js <padID> # collapse all history
|
* pnpm run --filter bin compactPad <padID> # collapse all history
|
||||||
* node bin/compactPad.js <padID> --keep N # keep only the last N revisions
|
* pnpm run --filter bin compactPad <padID> --keep N # keep only the last N revisions
|
||||||
*
|
*
|
||||||
* Wraps the existing Cleanup helper (src/node/utils/Cleanup.ts) via the
|
* Wraps the existing Cleanup helper (src/node/utils/Cleanup.ts) via the
|
||||||
* compactPad HTTP API so admins can trigger it from the CLI without
|
* compactPad HTTP API so admins can trigger it from the CLI without
|
||||||
|
|
@ -41,8 +41,8 @@ const apiPost = async (p: string): Promise<any> => {
|
||||||
|
|
||||||
const usage = () => {
|
const usage = () => {
|
||||||
console.error('Usage:');
|
console.error('Usage:');
|
||||||
console.error(' node bin/compactPad.js <padID>');
|
console.error(' pnpm run --filter bin compactPad <padID>');
|
||||||
console.error(' node bin/compactPad.js <padID> --keep <N>');
|
console.error(' pnpm run --filter bin compactPad <padID> --keep <N>');
|
||||||
process.exit(2);
|
process.exit(2);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,9 @@
|
||||||
* Compact every pad on the instance that has not been edited recently.
|
* Compact every pad on the instance that has not been edited recently.
|
||||||
*
|
*
|
||||||
* Usage:
|
* Usage:
|
||||||
* node bin/compactStalePads.js --older-than 90 # collapse history on pads not edited in 90 days
|
* pnpm run --filter bin compactStalePads --older-than 90 # collapse history on pads not edited in 90 days
|
||||||
* node bin/compactStalePads.js --older-than 90 --keep 50 # keep last 50 revisions
|
* pnpm run --filter bin compactStalePads --older-than 90 --keep 50 # keep last 50 revisions
|
||||||
* node bin/compactStalePads.js --older-than 90 --dry-run # list, don't write
|
* pnpm run --filter bin compactStalePads --older-than 90 --dry-run # list, don't write
|
||||||
*
|
*
|
||||||
* Composes `listAllPads` → `getLastEdited` → `compactPad`. Same shape as
|
* Composes `listAllPads` → `getLastEdited` → `compactPad`. Same shape as
|
||||||
* `bin/compactAllPads` (per-pad error tolerance, dry-run, tally), but
|
* `bin/compactAllPads` (per-pad error tolerance, dry-run, tally), but
|
||||||
|
|
@ -255,9 +255,9 @@ export const parseArgs = (argv: string[]): CompactStaleOpts | null => {
|
||||||
|
|
||||||
const usage = () => {
|
const usage = () => {
|
||||||
console.error('Usage:');
|
console.error('Usage:');
|
||||||
console.error(' node bin/compactStalePads.js --older-than <days>');
|
console.error(' pnpm run --filter bin compactStalePads --older-than <days>');
|
||||||
console.error(' node bin/compactStalePads.js --older-than <days> --keep <N>');
|
console.error(' pnpm run --filter bin compactStalePads --older-than <days> --keep <N>');
|
||||||
console.error(' node bin/compactStalePads.js --older-than <days> --dry-run');
|
console.error(' pnpm run --filter bin compactStalePads --older-than <days> --dry-run');
|
||||||
process.exit(2);
|
process.exit(2);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@
|
||||||
"checkAllPads": "node --import tsx checkAllPads.ts",
|
"checkAllPads": "node --import tsx checkAllPads.ts",
|
||||||
"compactPad": "node --import tsx compactPad.ts",
|
"compactPad": "node --import tsx compactPad.ts",
|
||||||
"compactAllPads": "node --import tsx compactAllPads.ts",
|
"compactAllPads": "node --import tsx compactAllPads.ts",
|
||||||
|
"compactStalePads": "node --import tsx compactStalePads.ts",
|
||||||
"createUserSession": "node --import tsx createUserSession.ts",
|
"createUserSession": "node --import tsx createUserSession.ts",
|
||||||
"deletePad": "node --import tsx deletePad.ts",
|
"deletePad": "node --import tsx deletePad.ts",
|
||||||
"repairPad": "node --import tsx repairPad.ts",
|
"repairPad": "node --import tsx repairPad.ts",
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ export default defineConfig({
|
||||||
text: 'About',
|
text: 'About',
|
||||||
items: [
|
items: [
|
||||||
{ text: 'Docker', link: '/docker.md' },
|
{ text: 'Docker', link: '/docker.md' },
|
||||||
|
{ text: 'Configuration', link: '/configuration.md' },
|
||||||
{ text: 'Localization', link: '/localization.md' },
|
{ text: 'Localization', link: '/localization.md' },
|
||||||
{ text: 'Cookies', link: '/cookies.md' },
|
{ text: 'Cookies', link: '/cookies.md' },
|
||||||
{ text: 'Plugins', link: '/plugins.md' },
|
{ text: 'Plugins', link: '/plugins.md' },
|
||||||
|
|
|
||||||
|
|
@ -29,13 +29,22 @@ In `settings.json`:
|
||||||
"requireSignature": false,
|
"requireSignature": false,
|
||||||
"trustedKeysPath": null
|
"trustedKeysPath": null
|
||||||
},
|
},
|
||||||
"adminEmail": null
|
"adminEmail": null,
|
||||||
|
// SMTP transport for the admin notification emails. host=null keeps
|
||||||
|
// log-only behaviour ("(would send email)"); set host+from to deliver.
|
||||||
|
"mail": {
|
||||||
|
"host": null,
|
||||||
|
"port": 587,
|
||||||
|
"secure": false,
|
||||||
|
"from": null,
|
||||||
|
"auth": null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
| Setting | Default | Notes |
|
| Setting | Default | Notes |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `updates.tier` | `"notify"` | One of `"off"`, `"notify"`, `"manual"`, `"auto"`, `"autonomous"`. Higher tiers are silently downgraded if the install method does not allow them. PR 1 only honors `"notify"` and `"off"`. |
|
| `updates.tier` | `"notify"` | One of `"off"`, `"notify"`, `"manual"`, `"auto"`, `"autonomous"`. All tiers are implemented. Higher tiers are silently downgraded if the install method does not allow them (only `"git"` installs can run the write tiers `manual` / `auto` / `autonomous`). |
|
||||||
| `updates.source` | `"github"` | Reserved for future alternative sources. Only `"github"` is implemented. |
|
| `updates.source` | `"github"` | Reserved for future alternative sources. Only `"github"` is implemented. |
|
||||||
| `updates.channel` | `"stable"` | Reserved. Stable releases only. |
|
| `updates.channel` | `"stable"` | Reserved. Stable releases only. |
|
||||||
| `updates.installMethod` | `"auto"` | One of `"auto"`, `"git"`, `"docker"`, `"npm"`, `"managed"`. Auto-detects via filesystem heuristics. Set explicitly to override. |
|
| `updates.installMethod` | `"auto"` | One of `"auto"`, `"git"`, `"docker"`, `"npm"`, `"managed"`. Auto-detects via filesystem heuristics. Set explicitly to override. |
|
||||||
|
|
@ -49,6 +58,11 @@ In `settings.json`:
|
||||||
| `updates.requireSignature` | `false` | When `true`, refuse updates whose tag is not signed by a trusted key. Verification is done via `git verify-tag <tag>` against the user's GPG keyring. Default `false` because Etherpad's release process does not yet sign tags consistently — turning the check on by default would block every Tier 2 update. Set `true` if you run your own builds or have imported a fork's keys. |
|
| `updates.requireSignature` | `false` | When `true`, refuse updates whose tag is not signed by a trusted key. Verification is done via `git verify-tag <tag>` against the user's GPG keyring. Default `false` because Etherpad's release process does not yet sign tags consistently — turning the check on by default would block every Tier 2 update. Set `true` if you run your own builds or have imported a fork's keys. |
|
||||||
| `updates.trustedKeysPath` | `null` | Override the keyring location passed to `git verify-tag` via the `$GNUPGHOME` env var. Useful when the trusted keys live in a dedicated keyring outside the Etherpad user's home. Only meaningful when `requireSignature: true`. |
|
| `updates.trustedKeysPath` | `null` | Override the keyring location passed to `git verify-tag` via the `$GNUPGHOME` env var. Useful when the trusted keys live in a dedicated keyring outside the Etherpad user's home. Only meaningful when `requireSignature: true`. |
|
||||||
| `adminEmail` | `null` | Top-level. Contact for admin notifications. Setting it enables the email nudges below. |
|
| `adminEmail` | `null` | Top-level. Contact for admin notifications. Setting it enables the email nudges below. |
|
||||||
|
| `mail.host` | `null` | Top-level SMTP host. **`null` keeps log-only behaviour** — notifications are logged as `(would send email)` and never delivered. Set a host (and `mail.from`) to deliver over SMTP via nodemailer. The `nodemailer` dependency is lazy-loaded, so installs that leave `mail.host` unset pay no runtime cost. |
|
||||||
|
| `mail.port` | `587` | SMTP port. |
|
||||||
|
| `mail.secure` | `false` | `true` for an implicit-TLS connection (typically port 465); `false` uses STARTTLS upgrade when offered. |
|
||||||
|
| `mail.from` | `null` | Envelope/From address. **Required for delivery** — if `mail.from` is unset (even with a host) the updater falls back to log-only `(would send email)`. |
|
||||||
|
| `mail.auth` | `null` | SMTP credentials object `{ "user": "...", "pass": "..." }`, passed through to nodemailer. Leave `null` for unauthenticated relays. |
|
||||||
|
|
||||||
## What "outdated" means
|
## What "outdated" means
|
||||||
|
|
||||||
|
|
@ -56,14 +70,26 @@ In `settings.json`:
|
||||||
|
|
||||||
## Email cadence (when `adminEmail` is set)
|
## Email cadence (when `adminEmail` is set)
|
||||||
|
|
||||||
|
These are the "nudge" emails sent by the periodic checker when the instance is behind:
|
||||||
|
|
||||||
| Trigger | First send | Repeat |
|
| Trigger | First send | Repeat |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| Outdated (minor or more behind) detected | Immediate | Monthly while still outdated |
|
| Outdated (minor or more behind) detected | Immediate | Every 30 days while still outdated (`SEVERE_INTERVAL`) |
|
||||||
| Up to date | No email | — |
|
| Up to date | No email | — |
|
||||||
|
|
||||||
|
The write tiers also email about **apply outcomes** so admins learn about failures without watching the UI:
|
||||||
|
|
||||||
|
| Outcome | When | Dedupe |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `update-preflight-failed` | An auto/autonomous apply was blocked at preflight (e.g. `node-engine-mismatch`, dirty tree, low disk). Subject: *Auto-update to `<tag>` blocked at preflight*. | Deduped on `<outcome>:<targetTag>` — one email per outcome per target tag. |
|
||||||
|
| `update-rolled-back` | An apply failed mid-flow and Etherpad auto-recovered to the previous version. Subject: *Auto-update to `<tag>` rolled back*. | Deduped on `<outcome>:<targetTag>`. |
|
||||||
|
| `update-rollback-failed` | **Terminal.** The apply failed *and* the rollback failed — manual intervention required. Subject: *Auto-update FAILED and could not be rolled back — manual intervention required*. | **Always sends**, bypassing dedupe, because the admin must learn about it even if a transient failure shared the same key. |
|
||||||
|
|
||||||
|
A different outcome or a different target tag resets the dedupe key and fires a fresh email. Manual (Tier 2) failures surface in the admin UI banner; the outcome emails are tied to the auto/autonomous flows.
|
||||||
|
|
||||||
If `adminEmail` is unset, the updater never sends mail. The admin UI banner and the pad-side notice still work without it.
|
If `adminEmail` is unset, the updater never sends mail. The admin UI banner and the pad-side notice still work without it.
|
||||||
|
|
||||||
PR 1 ships the cadence machinery but does not yet wire a real SMTP transport — emails are logged with `(would send email)` until a future PR adds the transport. The dedupe state still advances correctly so admins are not bombarded once SMTP is wired.
|
SMTP delivery is wired via [nodemailer](https://nodemailer.com/) (lazy-loaded). When `mail.host` and `mail.from` are both set, emails are delivered over SMTP. When either is unset the updater falls back to logging each message as `(would send email)` — the dedupe state still advances correctly, so admins are not bombarded once SMTP is configured. An SMTP send failure is caught and logged (`email send failed: …`) and never disrupts the updater state machine.
|
||||||
|
|
||||||
## Pad-side notice
|
## Pad-side notice
|
||||||
|
|
||||||
|
|
@ -93,7 +119,7 @@ The version check sends no telemetry. Etherpad fetches the public GitHub Release
|
||||||
|
|
||||||
Set the value explicitly if the heuristics get it wrong (e.g., a docker container that bind-mounts a writable git checkout).
|
Set the value explicitly if the heuristics get it wrong (e.g., a docker container that bind-mounts a writable git checkout).
|
||||||
|
|
||||||
In PR 1 (notify only) the install method does not change behavior — every install method gets the banner. From PR 2 onward the install method gates whether the manual-click and automatic tiers can run; only `"git"` is initially supported for write tiers.
|
Every install method gets the Tier 1 banner. The install method gates whether the write tiers (manual click, auto, autonomous) can run: only `"git"` installs are supported for the write tiers — other methods are silently downgraded to notify.
|
||||||
|
|
||||||
## Tier 2 — manual click
|
## Tier 2 — manual click
|
||||||
|
|
||||||
|
|
@ -110,7 +136,7 @@ Etherpad applies an update by **exiting with code 75** so a process supervisor r
|
||||||
### What clicking "Apply update" does
|
### What clicking "Apply update" does
|
||||||
|
|
||||||
1. **Lock acquire** — `var/update.lock` (PID-based, stale locks reaped automatically).
|
1. **Lock acquire** — `var/update.lock` (PID-based, stale locks reaped automatically).
|
||||||
2. **Pre-flight checks** — install method writable, working tree clean, free disk ≥ `diskSpaceMinMB`, `pnpm` on `PATH`, target tag exists at the configured remote, signature verifies (if `requireSignature: true`). On failure, state goes to `preflight-failed` with a typed reason; the admin sees a banner and clicks **Acknowledge** to clear it. No filesystem mutation has happened — nothing to roll back.
|
2. **Pre-flight checks** — install method writable, working tree clean, free disk ≥ `diskSpaceMinMB`, `pnpm` on `PATH`, no lock held, target tag exists at the configured remote, signature verifies (if `requireSignature: true`), and the target's Node engine matches the running Node. The Node-engine check runs *after* signature verification (so the `engines.node` range comes from a trusted tag): Etherpad reads `engines.node` from the target tag's `package.json` via `git show <tag>:package.json` and refuses the update via `semver.satisfies` if the running Node does not satisfy it. On failure, state goes to `preflight-failed` with a typed reason; the admin sees a banner and clicks **Acknowledge** to clear it. No filesystem mutation has happened — nothing to roll back.
|
||||||
3. **Drain** — `drainSeconds` window during which T-60 / T-30 / T-10 announcements broadcast to every connected pad and new socket connections are refused. Click **Cancel** during this window to abort cleanly.
|
3. **Drain** — `drainSeconds` window during which T-60 / T-30 / T-10 announcements broadcast to every connected pad and new socket connections are refused. Click **Cancel** during this window to abort cleanly.
|
||||||
4. **Execute** — `git fetch --tags origin`, `git checkout <tag>`, `pnpm install --frozen-lockfile`, `pnpm run build:ui`. Output streams to `var/log/update.log` (rotated 10 MB × 5).
|
4. **Execute** — `git fetch --tags origin`, `git checkout <tag>`, `pnpm install --frozen-lockfile`, `pnpm run build:ui`. Output streams to `var/log/update.log` (rotated 10 MB × 5).
|
||||||
5. **Exit 75** — the supervisor restarts on the new version.
|
5. **Exit 75** — the supervisor restarts on the new version.
|
||||||
|
|
@ -121,6 +147,7 @@ Etherpad applies an update by **exiting with code 75** so a process supervisor r
|
||||||
| What went wrong | Resulting state | Admin action |
|
| What went wrong | Resulting state | Admin action |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| Pre-flight check fails | `preflight-failed` | Click **Acknowledge** after fixing the underlying issue (free up disk, clean working tree, etc.). |
|
| Pre-flight check fails | `preflight-failed` | Click **Acknowledge** after fixing the underlying issue (free up disk, clean working tree, etc.). |
|
||||||
|
| Target tag requires a newer (or different) Node than the one running | `preflight-failed` (reason `node-engine-mismatch`) | Fails cleanly at preflight with a detail like *"target requires Node >=X, running Y"*. No drain, no `git checkout`, no restart, nothing to roll back — the install is untouched. Upgrade Node to a version that satisfies the target's `engines.node`, then **Acknowledge** and retry. |
|
||||||
| `git fetch` / `git checkout` fails mid-flow | `rolled-back` | Informational. The working tree is back where it started; click **Acknowledge** to clear. |
|
| `git fetch` / `git checkout` fails mid-flow | `rolled-back` | Informational. The working tree is back where it started; click **Acknowledge** to clear. |
|
||||||
| `pnpm install` or `pnpm run build:ui` fails | `rolled-back` | Same as above. The lockfile and SHA are restored. |
|
| `pnpm install` or `pnpm run build:ui` fails | `rolled-back` | Same as above. The lockfile and SHA are restored. |
|
||||||
| `/health` doesn't come up within `rollbackHealthCheckSeconds` | `rolled-back` | Same — RollbackHandler restores the previous SHA + lockfile and exits 75 again. |
|
| `/health` doesn't come up within `rollbackHealthCheckSeconds` | `rolled-back` | Same — RollbackHandler restores the previous SHA + lockfile and exits 75 again. |
|
||||||
|
|
@ -181,7 +208,7 @@ A single `grace-start` notification fires per scheduled tag:
|
||||||
|
|
||||||
> [Etherpad] Auto-update scheduled for 2.7.2
|
> [Etherpad] Auto-update scheduled for 2.7.2
|
||||||
|
|
||||||
with the `scheduledFor` timestamp. Etherpad core does not yet wire SMTP; the message logs as `(would send email)` until a future PR adds a transport. Cadence and dedupe still update correctly.
|
with the `scheduledFor` timestamp. Delivery follows the same SMTP path as every other notification: when `mail.host` and `mail.from` are set the message is sent via nodemailer, otherwise it logs as `(would send email)`. Cadence and dedupe update correctly either way.
|
||||||
|
|
||||||
The right way to give docker admins an in-product Apply button is to delegate to the orchestrator rather than mutate the container. Two patterns to consider in a follow-up PR:
|
The right way to give docker admins an in-product Apply button is to delegate to the orchestrator rather than mutate the container. Two patterns to consider in a follow-up PR:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -465,6 +465,20 @@ also use this to handle existing types.
|
||||||
`collab_client.js` has a pretty extensive list of message types, if you want to
|
`collab_client.js` has a pretty extensive list of message types, if you want to
|
||||||
take a look.
|
take a look.
|
||||||
|
|
||||||
|
## handleClientTimesliderMessage_`name`
|
||||||
|
|
||||||
|
Called from: `src/static/js/broadcast.ts`
|
||||||
|
|
||||||
|
Things in context:
|
||||||
|
|
||||||
|
1. payload - the data that got sent with the message (use it for custom message
|
||||||
|
content)
|
||||||
|
|
||||||
|
This is the timeslider analog of `handleClientMessage_name`. It gets called
|
||||||
|
every time the timeslider receives a message of type `name`. Use it to handle
|
||||||
|
custom message types (or react to existing ones) while a user is viewing the
|
||||||
|
timeslider rather than editing the pad.
|
||||||
|
|
||||||
## aceStartLineAndCharForPoint-aceEndLineAndCharForPoint
|
## aceStartLineAndCharForPoint-aceEndLineAndCharForPoint
|
||||||
|
|
||||||
Called from: src/static/js/ace2_inner.js
|
Called from: src/static/js/ace2_inner.js
|
||||||
|
|
@ -496,6 +510,34 @@ Things in context:
|
||||||
This hook is provided to allow a plugin to handle key events.
|
This hook is provided to allow a plugin to handle key events.
|
||||||
The return value should be true if you have handled the event.
|
The return value should be true if you have handled the event.
|
||||||
|
|
||||||
|
## acePaste
|
||||||
|
|
||||||
|
Called from: `src/static/js/ace2_inner.ts`
|
||||||
|
|
||||||
|
Things in context:
|
||||||
|
|
||||||
|
1. editorInfo - information about the user who is making the change
|
||||||
|
2. rep - information about where the change is being made
|
||||||
|
3. documentAttributeManager - information about attributes in the document
|
||||||
|
4. e - the fired paste event
|
||||||
|
|
||||||
|
This hook is called when content is pasted into the editor, before Etherpad
|
||||||
|
processes the pasted content. Use it to inspect or react to paste events.
|
||||||
|
|
||||||
|
## aceDrop
|
||||||
|
|
||||||
|
Called from: `src/static/js/ace2_inner.ts`
|
||||||
|
|
||||||
|
Things in context:
|
||||||
|
|
||||||
|
1. editorInfo - information about the user who is making the change
|
||||||
|
2. rep - information about where the change is being made
|
||||||
|
3. documentAttributeManager - information about attributes in the document
|
||||||
|
4. e - the fired drop event
|
||||||
|
|
||||||
|
This hook is called when content is dropped into the editor via drag-and-drop.
|
||||||
|
Use it to inspect or react to drop events.
|
||||||
|
|
||||||
## collectContentLineText
|
## collectContentLineText
|
||||||
|
|
||||||
Called from: `src/static/js/contentcollector.js`
|
Called from: `src/static/js/contentcollector.js`
|
||||||
|
|
|
||||||
|
|
@ -856,6 +856,39 @@ exports.exportHTMLAdditionalContent = async (hookName, {padId}) => {
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## exportHTMLSend
|
||||||
|
|
||||||
|
Called from: `src/node/handler/ExportHandler.ts`
|
||||||
|
|
||||||
|
Things in context:
|
||||||
|
|
||||||
|
1. html - the full HTML body of the pad that is about to be sent to the client
|
||||||
|
as an HTML export.
|
||||||
|
|
||||||
|
This hook is invoked via `aCallFirst` for HTML exports, just before the HTML is
|
||||||
|
sent in the response. It lets a plugin rewrite or replace the exported HTML
|
||||||
|
body. Return the modified HTML string; if a non-empty value is returned, it
|
||||||
|
replaces the HTML that would otherwise be sent.
|
||||||
|
|
||||||
|
## exportConvert
|
||||||
|
|
||||||
|
Called from: `src/node/handler/ExportHandler.ts`
|
||||||
|
|
||||||
|
Things in context:
|
||||||
|
|
||||||
|
1. srcFile - the path to the source file (the intermediate file Etherpad has
|
||||||
|
produced) that needs to be converted.
|
||||||
|
2. destFile - the path to the destination file Etherpad expects the converted
|
||||||
|
output to be written to.
|
||||||
|
3. req - the express request object.
|
||||||
|
4. res - the express response object.
|
||||||
|
|
||||||
|
This hook is invoked via `aCallAll` for non-HTML export formats. It lets a
|
||||||
|
plugin handle the export format conversion itself instead of letting Etherpad
|
||||||
|
fall back to its bundled LibreOffice converter. If any plugin returns a value
|
||||||
|
(making the hook result non-empty), Etherpad assumes the conversion was handled
|
||||||
|
by the plugin and skips the built-in converter.
|
||||||
|
|
||||||
## stylesForExport
|
## stylesForExport
|
||||||
Called from: `src/node/utils/ExportHtml.js`
|
Called from: `src/node/utils/ExportHtml.js`
|
||||||
|
|
||||||
|
|
@ -1101,3 +1134,66 @@ Context properties:
|
||||||
value with the user's actual author ID before this hook runs).
|
value with the user's actual author ID before this hook runs).
|
||||||
* `padId`: The pad's real (not read-only) identifier.
|
* `padId`: The pad's real (not read-only) identifier.
|
||||||
* `pad`: The pad's Pad object.
|
* `pad`: The pad's Pad object.
|
||||||
|
|
||||||
|
## ccRegisterBlockElements
|
||||||
|
|
||||||
|
Called from: `src/node/handler/ImportHandler.ts`,
|
||||||
|
`src/node/utils/ImportEtherpad.ts`, and `src/static/js/contentcollector.ts`
|
||||||
|
|
||||||
|
Things in context: None
|
||||||
|
|
||||||
|
This is the **server-side companion** to the client-only
|
||||||
|
`aceRegisterBlockElements` hook (see the client-side hooks documentation), and
|
||||||
|
it is the half that plugin authors commonly forget to implement. Registering a
|
||||||
|
block element only on the client means imports and server-side content
|
||||||
|
collection do not treat your element as block-level.
|
||||||
|
|
||||||
|
The return value of this hook should be an array of tag names. Those tags are
|
||||||
|
added to the set of block-level elements that the content collector and the
|
||||||
|
import path (`.etherpad` and HTML/document imports) recognise, so the
|
||||||
|
collected/imported content is segmented into the correct lines. Plugins that
|
||||||
|
declare block elements via `aceRegisterBlockElements` should declare the same
|
||||||
|
elements here too.
|
||||||
|
|
||||||
|
```js
|
||||||
|
exports.ccRegisterBlockElements = () => ['pre', 'blockquote'];
|
||||||
|
```
|
||||||
|
|
||||||
|
## createServer
|
||||||
|
|
||||||
|
Called from: `src/node/server.ts`
|
||||||
|
|
||||||
|
Things in context: None
|
||||||
|
|
||||||
|
Invoked via `aCallAll` once during Etherpad startup, after the HTTP server has
|
||||||
|
been created. Use it to run any plugin initialisation that needs to happen when
|
||||||
|
the server comes up.
|
||||||
|
|
||||||
|
## restartServer
|
||||||
|
|
||||||
|
Called from: `src/node/hooks/express/adminsettings.ts` (and the plugin
|
||||||
|
installer)
|
||||||
|
|
||||||
|
Things in context: None
|
||||||
|
|
||||||
|
Invoked via `aCallAll` when the server is restarted from the admin interface
|
||||||
|
(for example after settings are saved or a plugin is installed/uninstalled). Use
|
||||||
|
it to re-initialise plugin state that needs to survive an admin-triggered
|
||||||
|
restart.
|
||||||
|
|
||||||
|
## clientReady
|
||||||
|
|
||||||
|
> **Deprecated:** use the `userJoin` hook instead. This hook is fired with an
|
||||||
|
> awkward context (the raw `CLIENT_READY` message rather than a structured set
|
||||||
|
> of context properties) and is kept only for backwards compatibility.
|
||||||
|
|
||||||
|
Called from: `src/node/handler/PadMessageHandler.ts`
|
||||||
|
|
||||||
|
Things in context:
|
||||||
|
|
||||||
|
1. message - the raw `CLIENT_READY` message sent by the client as it joins a
|
||||||
|
pad.
|
||||||
|
|
||||||
|
Invoked via `aCallAll` while a client is joining a pad, before the author and
|
||||||
|
session are fully set up. Because the context is just the raw client message,
|
||||||
|
new plugins should use `userJoin` instead.
|
||||||
|
|
|
||||||
|
|
@ -306,6 +306,18 @@ Returns the Author Name of the author
|
||||||
|
|
||||||
-> can't be deleted cause this would involve scanning all the pads where this author was
|
-> can't be deleted cause this would involve scanning all the pads where this author was
|
||||||
|
|
||||||
|
#### anonymizeAuthor(authorID)
|
||||||
|
* API >= 1.3.1
|
||||||
|
|
||||||
|
Erases an author's identity across all pads they contributed to (GDPR Article 17, the "right to erasure"). The author's name and external/token mappings are removed and their chat messages are cleared, so the author can no longer be re-identified from pad data.
|
||||||
|
|
||||||
|
This endpoint is **disabled by default** and is gated on `gdprAuthorErasure.enabled = true` in `settings.json`. If GDPR author erasure is not enabled, the call returns an error and performs no changes. See [doc/privacy.md](../privacy.md) for the privacy/data-retention context.
|
||||||
|
|
||||||
|
*Example returns:*
|
||||||
|
* `{code: 0, message:"ok", data: {affectedPads: 3, removedTokenMappings: 1, removedExternalMappings: 1, clearedChatMessages: 7}}`
|
||||||
|
* `{code: 1, message:"anonymizeAuthor is disabled — set gdprAuthorErasure.enabled = true in settings.json to enable GDPR Art. 17 erasure", data: null}`
|
||||||
|
* `{code: 1, message:"authorID is required", data: null}`
|
||||||
|
|
||||||
### Session
|
### Session
|
||||||
Sessions can be created between a group and an author. This allows an author to access more than one group. The sessionID will be set as a cookie to the client and is valid until a certain date. The session cookie can also contain multiple comma-separated sessionIDs, allowing a user to edit pads in different groups at the same time. Only users with a valid session for this group, can access group pads. You can create a session after you authenticated the user at your web application, to give them access to the pads. You should save the sessionID of this session and delete it after the user logged out.
|
Sessions can be created between a group and an author. This allows an author to access more than one group. The sessionID will be set as a cookie to the client and is valid until a certain date. The session cookie can also contain multiple comma-separated sessionIDs, allowing a user to edit pads in different groups at the same time. Only users with a valid session for this group, can access group pads. You can create a session after you authenticated the user at your web application, to give them access to the pads. You should save the sessionID of this session and delete it after the user logged out.
|
||||||
|
|
||||||
|
|
@ -609,7 +621,7 @@ token is ignored.
|
||||||
* `{code: 1, message:"invalid deletionToken", data: null}`
|
* `{code: 1, message:"invalid deletionToken", data: null}`
|
||||||
|
|
||||||
#### copyPad(sourceID, destinationID[, force=false])
|
#### copyPad(sourceID, destinationID[, force=false])
|
||||||
* API >= 1.2.8
|
* API >= 1.2.9
|
||||||
|
|
||||||
copies a pad with full history and chat. If force is true and the destination pad exists, it will be overwritten.
|
copies a pad with full history and chat. If force is true and the destination pad exists, it will be overwritten.
|
||||||
|
|
||||||
|
|
@ -629,7 +641,7 @@ Note that all the revisions will be lost! In most of the cases one should use `c
|
||||||
* `{code: 1, message:"padID does not exist", data: null}`
|
* `{code: 1, message:"padID does not exist", data: null}`
|
||||||
|
|
||||||
#### movePad(sourceID, destinationID[, force=false])
|
#### movePad(sourceID, destinationID[, force=false])
|
||||||
* API >= 1.2.8
|
* API >= 1.2.9
|
||||||
|
|
||||||
moves a pad. If force is true and the destination pad exists, it will be overwritten.
|
moves a pad. If force is true and the destination pad exists, it will be overwritten.
|
||||||
|
|
||||||
|
|
@ -646,7 +658,7 @@ collapses the pad's revision history to reclaim database space (issue #6194). Wr
|
||||||
|
|
||||||
When `keepRevisions` is omitted (or null), all history is collapsed into a single base revision that reproduces the current pad text — equivalent to a freshly-imported pad. When set to a positive integer N, the pad keeps only its last N revisions.
|
When `keepRevisions` is omitted (or null), all history is collapsed into a single base revision that reproduces the current pad text — equivalent to a freshly-imported pad. When set to a positive integer N, the pad keeps only its last N revisions.
|
||||||
|
|
||||||
Pad text and chat are preserved in both modes. Saved-revision bookmarks are cleared. **This operation is destructive — export the pad first via `getEtherpad` if you need a backup.**
|
Pad text and chat are preserved in both modes. Saved-revision bookmarks are cleared. **This operation is destructive — export the pad first (for example via the `/p/:padID/export/etherpad` export endpoint) if you need a full-history backup.**
|
||||||
|
|
||||||
*Example returns:*
|
*Example returns:*
|
||||||
* `{code: 0, message:"ok", data: {ok: true, mode: "all"}}`
|
* `{code: 0, message:"ok", data: {ok: true, mode: "all"}}`
|
||||||
|
|
@ -664,10 +676,10 @@ returns the read only link of a pad
|
||||||
* `{code: 0, message:"ok", data: {readOnlyID: "r.s8oes9dhwrvt0zif"}}`
|
* `{code: 0, message:"ok", data: {readOnlyID: "r.s8oes9dhwrvt0zif"}}`
|
||||||
* `{code: 1, message:"padID does not exist", data: null}`
|
* `{code: 1, message:"padID does not exist", data: null}`
|
||||||
|
|
||||||
#### getPadID(readOnlyID)
|
#### getPadID(roID)
|
||||||
* API >= 1.2.10
|
* API >= 1.2.10
|
||||||
|
|
||||||
returns the id of a pad which is assigned to the readOnlyID
|
returns the id of the pad mapped to the given read-only id. The query parameter is named `roID` (the read-only id returned by `getReadOnlyID`).
|
||||||
|
|
||||||
*Example returns:*
|
*Example returns:*
|
||||||
* `{code: 0, message:"ok", data: {padID: "p.s8oes9dhwrvt0zif"}}`
|
* `{code: 0, message:"ok", data: {padID: "p.s8oes9dhwrvt0zif"}}`
|
||||||
|
|
|
||||||
141
doc/cli.md
141
doc/cli.md
|
|
@ -1,68 +1,151 @@
|
||||||
# CLI
|
# CLI
|
||||||
|
|
||||||
You can find different tools for migrating things, checking your Etherpad health in the bin directory.
|
Etherpad ships a set of operator tools in the `bin/` directory for migrating
|
||||||
One of these is the migrateDB command. It takes two settings.json files and copies data from one source to another one.
|
data, reclaiming database space, repairing damaged pads, managing sessions and
|
||||||
In this example we migrate from the old dirty db to the new rustydb engine. So we copy these files to the root of the etherpad-directory.
|
managing plugins. They are TypeScript scripts run through `tsx`, and each one
|
||||||
|
is registered as a pnpm script in `bin/package.json`. Invoke them from the
|
||||||
|
Etherpad root with:
|
||||||
|
|
||||||
|
```
|
||||||
|
pnpm run --filter bin <script> [args]
|
||||||
|
```
|
||||||
|
|
||||||
|
For example `pnpm run --filter bin checkPad my-pad`. Running the `.ts` files
|
||||||
|
directly with `node bin/foo.js` will **not** work — there are no compiled `.js`
|
||||||
|
files and the scripts need the `tsx` loader.
|
||||||
|
|
||||||
|
## Running vs. stopped
|
||||||
|
|
||||||
|
Some tools talk to a **running** Etherpad over its HTTP API (they read the API
|
||||||
|
key from `APIKEY.txt` and connect to `ip`/`port` from `settings.json`). Others
|
||||||
|
open the database **directly** and must be run while Etherpad is **stopped**,
|
||||||
|
otherwise you risk a database lock or a corrupt write. Each tool below is
|
||||||
|
labelled accordingly.
|
||||||
|
|
||||||
|
## Database migration (`migrateDB`)
|
||||||
|
|
||||||
|
`migrateDB` copies every record from one database to another. It takes two
|
||||||
|
settings files — `--file1` is the **source**, `--file2` is the **target** —
|
||||||
|
each describing a database with a `dbType` and `dbSettings`. Both paths are
|
||||||
|
resolved relative to the Etherpad root.
|
||||||
|
|
||||||
|
In this example we migrate from the old `dirty` db to the new `rustydb` engine.
|
||||||
|
Create a source descriptor `source.json` in the Etherpad root:
|
||||||
|
|
||||||
````json
|
````json
|
||||||
{
|
{
|
||||||
"dbType": "dirty",
|
"dbType": "dirty",
|
||||||
|
"dbSettings": {
|
||||||
|
"filename": "./var/dirty.db"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
````
|
||||||
|
|
||||||
|
and a target descriptor `target.json`:
|
||||||
|
|
||||||
|
````json
|
||||||
|
{
|
||||||
|
"dbType": "rustydb",
|
||||||
"dbSettings": {
|
"dbSettings": {
|
||||||
"filename": "./var/rusty.db"
|
"filename": "./var/rusty.db"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
````
|
````
|
||||||
|
|
||||||
|
Then run:
|
||||||
|
|
||||||
|
```
|
||||||
|
pnpm run --filter bin migrateDB --file1 source.json --file2 target.json
|
||||||
|
```
|
||||||
|
|
||||||
````json
|
After some time the data is copied over to the new database. Run this with
|
||||||
{
|
Etherpad **stopped**.
|
||||||
"dbType": "rustydb",
|
|
||||||
"dbSettings": {
|
|
||||||
"filename": "./var/rusty2.db"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
````
|
|
||||||
|
|
||||||
|
|
||||||
After that we need to move the data from dirty to rustydb.
|
|
||||||
Therefore, we call `pnpm run --filter bin migrateDB --file1 test1.json --file2 test2.json` with these two files in our root directories. After some time the data should be copied over to the new database.
|
|
||||||
|
|
||||||
## Pad compaction
|
## Pad compaction
|
||||||
|
|
||||||
Long-lived pads with heavy edit history accumulate revisions in the database. Three CLIs reclaim that space, in increasing scope:
|
Long-lived pads with heavy edit history accumulate revisions in the database.
|
||||||
|
Three CLIs reclaim that space, in increasing scope. All of them drive a
|
||||||
|
**running** Etherpad over the `compactPad` HTTP API.
|
||||||
|
|
||||||
| Tool | Targets | When to use |
|
| Tool | Targets | When to use |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `bin/compactPad.js <padID>` | one pad | you know which pad is fat |
|
| `pnpm run --filter bin compactPad <padID>` | one pad | you know which pad is fat |
|
||||||
| `bin/compactAllPads.js` | every pad | bulk reclaim across the whole instance |
|
| `pnpm run --filter bin compactAllPads` | every pad | bulk reclaim across the whole instance |
|
||||||
| `bin/compactStalePads.js --older-than N` | pads not edited in N days | reclaim the cold tail without touching pads still in active use |
|
| `pnpm run --filter bin compactStalePads --older-than N` | pads not edited in N days | reclaim the cold tail without touching pads still in active use |
|
||||||
|
|
||||||
All three are gated on `cleanup.enabled = true` in `settings.json` and are **destructive**: history is collapsed (or trimmed). Export anything you can't afford to lose with `getEtherpad` first.
|
All three are gated on `cleanup.enabled = true` in `settings.json` and are
|
||||||
|
**destructive**: history is collapsed (or trimmed). Export anything you can't
|
||||||
|
afford to lose with the `getEtherpad` API first.
|
||||||
|
|
||||||
Common flags:
|
Common flags:
|
||||||
|
|
||||||
- `--keep N` — retain the last N revisions instead of collapsing all history.
|
- `--keep N` — retain the last N revisions instead of collapsing all history.
|
||||||
- `--dry-run` — list pads and revision counts without writing.
|
- `--dry-run` — list pads and revision counts without writing (`compactAllPads`
|
||||||
|
and `compactStalePads` only).
|
||||||
|
- `--older-than N` — (`compactStalePads` only, **required**) only consider pads
|
||||||
|
not edited in the last N days.
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
````
|
````
|
||||||
# Compact a specific pad, collapsing all history.
|
# Compact a specific pad, collapsing all history.
|
||||||
node bin/compactPad.js my-pad
|
pnpm run --filter bin compactPad my-pad
|
||||||
|
|
||||||
# Keep only the last 50 revisions of one pad.
|
# Keep only the last 50 revisions of one pad.
|
||||||
node bin/compactPad.js my-pad --keep 50
|
pnpm run --filter bin compactPad my-pad --keep 50
|
||||||
|
|
||||||
# Compact every pad on the instance (per-pad failures don't stop the run).
|
# Compact every pad on the instance (per-pad failures don't stop the run).
|
||||||
node bin/compactAllPads.js
|
pnpm run --filter bin compactAllPads
|
||||||
node bin/compactAllPads.js --dry-run
|
pnpm run --filter bin compactAllPads --dry-run
|
||||||
|
|
||||||
# Compact only pads not edited in the last 90 days, keeping the last 50 revisions.
|
# Compact only pads not edited in the last 90 days, keeping the last 50 revisions.
|
||||||
node bin/compactStalePads.js --older-than 90 --keep 50
|
pnpm run --filter bin compactStalePads --older-than 90 --keep 50
|
||||||
node bin/compactStalePads.js --older-than 90 --dry-run
|
pnpm run --filter bin compactStalePads --older-than 90 --dry-run
|
||||||
````
|
````
|
||||||
|
|
||||||
`bin/compactStalePads.js` is the right tool for periodic operator runs on long-lived instances — hot pads that users are still navigating in timeslider stay untouched, and only the cold tail is rewritten. Per-pad failures (including a `getLastEdited` fault) are counted but do not abort the bulk run; the exit code reflects whether anything failed.
|
`compactStalePads` is the right tool for periodic operator runs on long-lived
|
||||||
|
instances — hot pads that users are still navigating in timeslider stay
|
||||||
|
untouched (staleness is even re-checked right before each compaction), and only
|
||||||
|
the cold tail is rewritten. Per-pad failures (including a `getLastEdited` fault)
|
||||||
|
are counted but do not abort the bulk run; the exit code reflects whether
|
||||||
|
anything failed.
|
||||||
|
|
||||||
See the `compactPad` HTTP API in `doc/api/http_api.md` for the same primitive over the wire (issues #6194, #7642).
|
See the `compactPad` HTTP API in `doc/api/http_api.md` for the same primitive
|
||||||
|
over the wire (issues #6194, #7642).
|
||||||
|
|
||||||
|
## Pad maintenance and debugging
|
||||||
|
|
||||||
|
| Tool | Purpose | Args | Etherpad |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `pnpm run --filter bin checkPad <padID>` | Check one pad's revisions for data corruption. | `<padID>` | stopped |
|
||||||
|
| `pnpm run --filter bin checkAllPads` | Check every pad on the instance for data corruption. | none | stopped |
|
||||||
|
| `pnpm run --filter bin repairPad <padID>` | Repair a pad by extracting all of its data, deleting it and re-inserting it. | `<padID>` | stopped (the script refuses to be useful otherwise) |
|
||||||
|
| `pnpm run --filter bin rebuildPad <padID> <rev> [newPadID]` | Rebuild a damaged pad into a new pad at a known-good revision. The new pad defaults to `<padID>-rebuilt` and must not already exist. | `<padID> <rev> [newPadID]` | stopped |
|
||||||
|
| `node --import tsx extractPadData.ts <padID>` | Export one pad's data to a `<padID>.db` dirtyDB file so a bug can be reproduced in a dev environment. (No pnpm alias — run with the `tsx` loader directly from `bin/`.) | `<padID>` | stopped |
|
||||||
|
|
||||||
|
`checkPad`/`checkAllPads` report corruption but do not modify anything;
|
||||||
|
`repairPad` and `rebuildPad` write. As always, back up first.
|
||||||
|
|
||||||
|
## Database tools
|
||||||
|
|
||||||
|
| Tool | Purpose | Args | Etherpad |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `pnpm run --filter bin migrateDB --file1 <src.json> --file2 <dst.json>` | Copy all records from a source database to a target database (see above). | `--file1 <src.json> --file2 <dst.json>` | stopped |
|
||||||
|
| `pnpm run --filter bin migrateDirtyDBtoRealDB` | One-shot migration of `var/dirty.db` into the real database configured in `settings.json`. Back up `dirty.db` first; may need more memory (e.g. `node --max-old-space-size=4096`). | none (reads target db from `settings.json`) | stopped |
|
||||||
|
| `pnpm run --filter bin importSqlFile <sqlFile>` | Import a SQL dump (rows of `REPLACE INTO store VALUES (...)`) into the configured database. | `<sqlFile>` | stopped |
|
||||||
|
|
||||||
|
## Session and pad management
|
||||||
|
|
||||||
|
| Tool | Purpose | Args | Etherpad |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `pnpm run --filter bin deletePad <padID>` | Delete a single pad. | `<padID>` | running |
|
||||||
|
| `pnpm run --filter bin deleteAllGroupSessions` | Delete all group sessions (useful when a misconfiguration has wedged group access). | none | running |
|
||||||
|
| `pnpm run --filter bin createUserSession` | Create a throwaway group, pad, author and session, printing a `sessionID` you can set as a cookie — handy for debugging session-based configs. | none | running |
|
||||||
|
|
||||||
|
## Plugin tools
|
||||||
|
|
||||||
|
| Tool | Purpose | Args | Etherpad |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `pnpm run --filter bin plugins <action> [names…]` | Manage installed plugins. Actions: `i`/`install`, `rm`/`remove`, `ls`/`list`, `up`/`update`. Install also accepts `--path <dir>` and `--github <repo>` sources. | `<action> [names…]` | stopped |
|
||||||
|
| `pnpm run --filter bin checkPlugin <ep_name> [autofix\|autocommit\|autopush]` | Lint a plugin checkout (a sibling `../ep_name` directory) against the plugin conventions; optional modes auto-fix, commit, or commit+push+publish (the last is dangerous). | `<ep_name> [mode]` | n/a |
|
||||||
|
| `pnpm run --filter bin stalePlugins` | List plugins in the registry not updated in over two years, with maintainer email. Requires `privacy.pluginCatalog` enabled. | none | n/a |
|
||||||
|
|
|
||||||
146
doc/configuration.md
Normal file
146
doc/configuration.md
Normal file
|
|
@ -0,0 +1,146 @@
|
||||||
|
# Configuration
|
||||||
|
|
||||||
|
This page explains how Etherpad is configured and documents the
|
||||||
|
reverse-proxy and subpath behaviour in detail. It is **not** an exhaustive list
|
||||||
|
of every setting — for that, see the fully-commented
|
||||||
|
[`settings.json.template`](https://github.com/ether/etherpad/blob/develop/settings.json.template),
|
||||||
|
which is the authoritative reference.
|
||||||
|
|
||||||
|
## Where settings live
|
||||||
|
|
||||||
|
Etherpad reads its configuration from `settings.json` in the installation root.
|
||||||
|
A new install copies `settings.json.template` to `settings.json` on first run.
|
||||||
|
|
||||||
|
* **Override the file location** by passing the `-s` / `--settings` flag to
|
||||||
|
the launcher, e.g. `bin/run.sh -s /etc/etherpad/settings.json`. This lets
|
||||||
|
you run multiple instances from one installation. (`bin/run.sh` forwards the
|
||||||
|
flag to `pnpm run prod`, which is the supported entrypoint — there is no
|
||||||
|
`server.js`; the runtime is `src/node/server.ts`, loaded via `tsx`.)
|
||||||
|
* **Environment-variable substitution** — any string value may reference an
|
||||||
|
environment variable using the syntax `"${ENV_VAR}"` or
|
||||||
|
`"${ENV_VAR:default}"`. The variable name **must** be quoted, even when the
|
||||||
|
resolved value is a number or boolean. A few rules worth remembering:
|
||||||
|
* `"${PORT:9001}"` → the value of `PORT`, or `9001` if unset.
|
||||||
|
* `"${MINIFY:true}"` → the boolean `true`/`false`, not the string.
|
||||||
|
* `"${UNSET_VAR:null}"` → `null`; `"${UNSET_VAR:}"` → the empty string.
|
||||||
|
* Substitution happens at load time, in memory only — env vars never
|
||||||
|
overwrite `settings.json` on disk.
|
||||||
|
|
||||||
|
When running in Docker, almost every setting is wired to an environment
|
||||||
|
variable in the shipped `settings.json.docker`. See the
|
||||||
|
[Docker page](/docker.md) for the full env-var list.
|
||||||
|
|
||||||
|
## Trusting a reverse proxy
|
||||||
|
|
||||||
|
If Etherpad runs behind NGINX, Traefik, HAProxy, a Kubernetes ingress, or any
|
||||||
|
other reverse proxy, set:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"trustProxy": true
|
||||||
|
```
|
||||||
|
|
||||||
|
This makes Etherpad trust the standard `X-Forwarded-*` headers, so it:
|
||||||
|
|
||||||
|
* uses the real client IP (from `X-Forwarded-For`) in logs and rate limits
|
||||||
|
instead of the proxy's IP;
|
||||||
|
* respects the forwarded protocol and host, so the `secure` flag is set on
|
||||||
|
cookies when the proxy terminates TLS (required for `SameSite=None`).
|
||||||
|
|
||||||
|
Leave it at the default `false` when Etherpad is reachable directly on a public
|
||||||
|
IP — otherwise any client could forge these headers.
|
||||||
|
|
||||||
|
## Running under a subpath / ingress
|
||||||
|
|
||||||
|
Etherpad can be served under a URL-path prefix (for example
|
||||||
|
`https://example.com/etherpad/`) without recompiling anything. The prefix is
|
||||||
|
discovered per-request from upstream headers, so the same Etherpad process works
|
||||||
|
whether it is mounted at the root or under a path.
|
||||||
|
|
||||||
|
Three headers are checked, **in this order**; the first non-empty value (after
|
||||||
|
sanitization) wins:
|
||||||
|
|
||||||
|
| Order | Header | Origin | Requires `trustProxy: true`? |
|
||||||
|
| ----- | ------ | ------ | ---------------------------- |
|
||||||
|
| 1 | `x-proxy-path` | Etherpad's own convention | No — always honoured |
|
||||||
|
| 2 | `X-Forwarded-Prefix` | HAProxy / Traefik / Spring | Yes |
|
||||||
|
| 3 | `X-Ingress-Path` | Kubernetes / Home Assistant ingress | Yes |
|
||||||
|
|
||||||
|
`x-proxy-path` is always honoured because an operator must deliberately
|
||||||
|
configure their proxy to send Etherpad's custom header. The two standard
|
||||||
|
headers (`X-Forwarded-Prefix`, `X-Ingress-Path`) are honoured **only when
|
||||||
|
`trustProxy` is `true`**, because otherwise a client on a public IP could forge
|
||||||
|
them.
|
||||||
|
|
||||||
|
Once detected, the prefix is woven into the responses that would otherwise
|
||||||
|
break under a subpath:
|
||||||
|
|
||||||
|
* `manifest.json` (PWA install metadata);
|
||||||
|
* the social-media meta tags (`og:url` / `og:image`), unless an explicit
|
||||||
|
`publicURL` is configured;
|
||||||
|
* the bootstrap script entrypoint and the asset / reconnect links in the pad,
|
||||||
|
index, and timeslider pages.
|
||||||
|
|
||||||
|
### Sanitization
|
||||||
|
|
||||||
|
The header value is treated as untrusted input even when read from a trusted
|
||||||
|
header, because it ends up inside HTML, JS, CSS, and HTTP `Location` headers.
|
||||||
|
The sanitizer (`src/node/utils/sanitizeProxyPath.ts`):
|
||||||
|
|
||||||
|
* strips every character outside `[A-Za-z0-9_./-]`;
|
||||||
|
* collapses a leading `//+` to a single `/`, so the value can never be read as
|
||||||
|
a protocol-relative URL;
|
||||||
|
* prepends `/` if the result doesn't already start with one;
|
||||||
|
* **rejects** any value containing a `..` path segment (returns empty).
|
||||||
|
|
||||||
|
The output is therefore always either empty, or a string that starts with
|
||||||
|
exactly one `/` and contains only `[A-Za-z0-9_./-]`.
|
||||||
|
|
||||||
|
### Example: Traefik
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
http:
|
||||||
|
middlewares:
|
||||||
|
etherpad-prefix:
|
||||||
|
stripPrefix:
|
||||||
|
prefixes:
|
||||||
|
- "/etherpad"
|
||||||
|
etherpad-headers:
|
||||||
|
headers:
|
||||||
|
customRequestHeaders:
|
||||||
|
X-Forwarded-Prefix: "/etherpad"
|
||||||
|
```
|
||||||
|
|
||||||
|
Apply both middlewares to the router and set `trustProxy: true` in
|
||||||
|
`settings.json`.
|
||||||
|
|
||||||
|
### Example: NGINX
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
location /etherpad/ {
|
||||||
|
proxy_pass http://127.0.0.1:9001/;
|
||||||
|
proxy_set_header X-Proxy-Path /etherpad;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Here `X-Proxy-Path` is used, which works regardless of `trustProxy`. Use
|
||||||
|
`X-Forwarded-Prefix` instead if you prefer the standard header (and set
|
||||||
|
`trustProxy: true`).
|
||||||
|
|
||||||
|
## Self-update, email, database, and metrics
|
||||||
|
|
||||||
|
These areas have their own pages:
|
||||||
|
|
||||||
|
* **Self-update** and **outbound email** (`adminEmail`, `mail.*` SMTP) —
|
||||||
|
see [Updates](/admin/updates.md). The corresponding Docker env vars
|
||||||
|
(`MAIL_HOST`, `MAIL_FROM`, …) are listed on the [Docker page](/docker.md).
|
||||||
|
* **Database** — choose a backend with `dbType` / `dbSettings`. The
|
||||||
|
supported drivers and example settings are documented in
|
||||||
|
[`settings.json.template`](https://github.com/ether/etherpad-lite/blob/develop/settings.json.template),
|
||||||
|
and the Docker equivalents (`DB_TYPE`, `DB_HOST`, …) are listed on the
|
||||||
|
[Docker page](/docker.md). The on-disk keyspace layout is described in
|
||||||
|
[`doc/database.adoc`](https://github.com/ether/etherpad-lite/blob/develop/doc/database.adoc).
|
||||||
|
* **Metrics** — Etherpad exposes Prometheus-compatible metrics; see
|
||||||
|
[Stats](/stats.md).
|
||||||
|
|
@ -112,6 +112,8 @@ The `settings.json.docker` available by default allows to control almost every s
|
||||||
| `DEFAULT_PAD_TEXT` | The default text of a pad | `Welcome to Etherpad! This pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents! Get involved with Etherpad at https://etherpad.org` |
|
| `DEFAULT_PAD_TEXT` | The default text of a pad | `Welcome to Etherpad! This pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents! Get involved with Etherpad at https://etherpad.org` |
|
||||||
| `IP` | IP which etherpad should bind at. Change to `::` for IPv6 | `0.0.0.0` |
|
| `IP` | IP which etherpad should bind at. Change to `::` for IPv6 | `0.0.0.0` |
|
||||||
| `PORT` | port which etherpad should bind at | `9001` |
|
| `PORT` | port which etherpad should bind at | `9001` |
|
||||||
|
| `PUBLIC_URL` | Canonical public origin of this instance, e.g. `https://pad.example.com` (no trailing slash, must include scheme). Used to build absolute URLs in link-preview meta tags. When `null`, falls back to the incoming request's protocol+Host. | `null` |
|
||||||
|
| `ENABLE_DARK_MODE` | Respect the end user's browser dark-mode preference. When enabled this overrides the admin-configured skin variants and skin name for that user. | `true` |
|
||||||
| `ADMIN_PASSWORD` | the password for the `admin` user (leave unspecified if you do not want to create it) | |
|
| `ADMIN_PASSWORD` | the password for the `admin` 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) | |
|
| `USER_PASSWORD` | the password for the first user `user` (leave unspecified if you do not want to create it) | |
|
||||||
|
|
||||||
|
|
@ -210,6 +212,31 @@ For the editor container, you can also make it full width by adding `full-width-
|
||||||
| `DISABLE_IP_LOGGING` | Privacy: disable IP logging | `false` |
|
| `DISABLE_IP_LOGGING` | Privacy: disable IP logging | `false` |
|
||||||
|
|
||||||
|
|
||||||
|
### Email (SMTP)
|
||||||
|
|
||||||
|
SMTP transport used by the self-updater and admin notifications. When `MAIL_HOST` is `null` (the default) Etherpad keeps log-only behaviour and sends no real mail; set `MAIL_HOST` and `MAIL_FROM` to send via nodemailer.
|
||||||
|
|
||||||
|
| Variable | Description | Default |
|
||||||
|
| ------------- | ------------------------------------------------------------------------------------------- | ------- |
|
||||||
|
| `MAIL_HOST` | SMTP server hostname. Leave `null` to keep log-only behaviour (no outbound mail). | `null` |
|
||||||
|
| `MAIL_PORT` | SMTP server port. | `587` |
|
||||||
|
| `MAIL_SECURE` | Use a secure (TLS) connection to the SMTP server. | `false` |
|
||||||
|
| `MAIL_FROM` | The `From` address used on outbound mail. Required (together with `MAIL_HOST`) to send mail. | `null` |
|
||||||
|
|
||||||
|
|
||||||
|
### Privacy banner
|
||||||
|
|
||||||
|
Optional privacy banner shown to users. See `settings.json.template` for full field docs.
|
||||||
|
|
||||||
|
| Variable | Description | Default |
|
||||||
|
| ------------------------------- | --------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| `PRIVACY_BANNER_ENABLED` | Show the privacy banner. | `false` |
|
||||||
|
| `PRIVACY_BANNER_TITLE` | Banner title. | `Privacy notice` |
|
||||||
|
| `PRIVACY_BANNER_BODY` | Banner body text. | `This instance processes pad content on our servers. See the linked policy for retention and how to request erasure.` |
|
||||||
|
| `PRIVACY_BANNER_LEARN_MORE_URL` | Optional URL for a "learn more" link in the banner. | `null` |
|
||||||
|
| `PRIVACY_BANNER_DISMISSAL` | Banner dismissal behaviour, e.g. `dismissible`. | `dismissible` |
|
||||||
|
|
||||||
|
|
||||||
### Advanced
|
### Advanced
|
||||||
|
|
||||||
| Variable | Description | Default |
|
| Variable | Description | Default |
|
||||||
|
|
@ -218,6 +245,10 @@ For the editor container, you can also make it full width by adding `full-width-
|
||||||
| `COOKIE_SESSION_LIFETIME` | How long (ms) a user can be away before they must log in again. | `864000000` (10 days) |
|
| `COOKIE_SESSION_LIFETIME` | How long (ms) a user can be away before they must log in again. | `864000000` (10 days) |
|
||||||
| `COOKIE_SESSION_REFRESH_INTERVAL` | How often (ms) to write the latest cookie expiration time. | `86400000` (1 day) |
|
| `COOKIE_SESSION_REFRESH_INTERVAL` | How often (ms) to write the latest cookie expiration time. | `86400000` (1 day) |
|
||||||
| `SHOW_SETTINGS_IN_ADMIN_PAGE` | hide/show the settings.json in admin page | `true` |
|
| `SHOW_SETTINGS_IN_ADMIN_PAGE` | hide/show the settings.json in admin page | `true` |
|
||||||
|
| `AUTHENTICATION_METHOD` | Authentication method used by the server. Use `sso` for the built-in OpenID Connect provider, or `apikey` for the legacy API-key authentication system. | `sso` |
|
||||||
|
| `ENABLE_METRICS` | Enable the Prometheus metrics endpoint used by monitoring plugins to collect metrics about Etherpad. Disable if you do not use any monitoring plugins. | `true` |
|
||||||
|
| `ENABLE_PAD_WIDE_SETTINGS` | Enable creator-owned pad-wide settings and new-pad default seeding from My View. The pad creator gets a "Pad-wide Settings" section to set/enforce defaults; other users see only their own view options. Set to `false` for the legacy single-settings behavior. | `true` |
|
||||||
|
| `GDPR_AUTHOR_ERASURE_ENABLED` | Enable the GDPR Art. 17 author anonymize/erasure REST endpoint and admin UI. Enable only when an operator process exists to authorise erasure requests. | `false` |
|
||||||
| `TRUST_PROXY` | set to `true` if you are using a reverse proxy in front of Etherpad (for example: Traefik for SSL termination via Let's Encrypt). This will affect security and correctness of the logs if not done | `false` |
|
| `TRUST_PROXY` | set to `true` if you are using a reverse proxy in front of Etherpad (for example: Traefik for SSL termination via Let's Encrypt). This will affect security and correctness of the logs if not done | `false` |
|
||||||
| `IMPORT_MAX_FILE_SIZE` | maximum allowed file size when importing a pad, in bytes. | `52428800` (50 MB) |
|
| `IMPORT_MAX_FILE_SIZE` | maximum allowed file size when importing a pad, in bytes. | `52428800` (50 MB) |
|
||||||
| `IMPORT_EXPORT_MAX_REQ_PER_IP` | maximum number of import/export calls per IP. | `10` |
|
| `IMPORT_EXPORT_MAX_REQ_PER_IP` | maximum number of import/export calls per IP. | `10` |
|
||||||
|
|
@ -239,7 +270,7 @@ For the editor container, you can also make it full width by adding `full-width-
|
||||||
| `FOCUS_LINE_PERCENTAGE_ARROW_UP` | Percentage of viewport height to be additionally scrolled when user presses arrow up in the line of the top of the viewport. Set to 0 to let the scroll to be handled as default by Etherpad | `0` |
|
| `FOCUS_LINE_PERCENTAGE_ARROW_UP` | Percentage of viewport height to be additionally scrolled when user presses arrow up in the line of the top of the viewport. Set to 0 to let the scroll to be handled as default by Etherpad | `0` |
|
||||||
| `FOCUS_LINE_DURATION` | Time (in milliseconds) used to animate the scroll transition. Set to 0 to disable animation | `0` |
|
| `FOCUS_LINE_DURATION` | Time (in milliseconds) used to animate the scroll transition. Set to 0 to disable animation | `0` |
|
||||||
| `FOCUS_LINE_CARET_SCROLL` | Flag to control if it should scroll when user places the caret in the last line of the viewport | `false` |
|
| `FOCUS_LINE_CARET_SCROLL` | Flag to control if it should scroll when user places the caret in the last line of the viewport | `false` |
|
||||||
| `SOCKETIO_MAX_HTTP_BUFFER_SIZE` | The maximum size (in bytes) of a single message accepted via Socket.IO. If a client sends a larger message, its connection gets closed to prevent DoS (memory exhaustion) attacks. | `50000` |
|
| `SOCKETIO_MAX_HTTP_BUFFER_SIZE` | The maximum size (in bytes) of a single message accepted via Socket.IO. If a client sends a larger message, its connection gets closed to prevent DoS (memory exhaustion) attacks. Larger values allow bigger pastes. | `1000000` (1 MB) |
|
||||||
| `LOAD_TEST` | Allow Load Testing tools to hit the Etherpad Instance. WARNING: this will disable security on the instance. | `false` |
|
| `LOAD_TEST` | Allow Load Testing tools to hit the Etherpad Instance. WARNING: this will disable security on the instance. | `false` |
|
||||||
| `DUMP_ON_UNCLEAN_EXIT` | Enable dumping objects preventing a clean exit of Node.js. WARNING: this has a significant performance impact. | `false` |
|
| `DUMP_ON_UNCLEAN_EXIT` | Enable dumping objects preventing a clean exit of Node.js. WARNING: this has a significant performance impact. | `false` |
|
||||||
| `EXPOSE_VERSION` | Expose Etherpad version in the web interface and in the Server http header. Do not enable on production machines. | `false` |
|
| `EXPOSE_VERSION` | Expose Etherpad version in the web interface and in the Server http header. Do not enable on production machines. | `false` |
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,9 @@ hero:
|
||||||
- theme: brand
|
- theme: brand
|
||||||
text: Install
|
text: Install
|
||||||
link: /docker
|
link: /docker
|
||||||
|
- theme: alt
|
||||||
|
text: Configuration
|
||||||
|
link: /configuration
|
||||||
- theme: alt
|
- theme: alt
|
||||||
text: API documentation
|
text: API documentation
|
||||||
link: /api/
|
link: /api/
|
||||||
|
|
|
||||||
|
|
@ -58,8 +58,22 @@ alert ('Chat');
|
||||||
```
|
```
|
||||||
to:
|
to:
|
||||||
```js
|
```js
|
||||||
alert(window._('pad.chat'));
|
alert(window.html10n.get('pad.chat'));
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> **Note:** Etherpad core sets up a `window._` gettext-like shortcut as
|
||||||
|
> `window._ = html10n.get`. Because this assignment does **not** bind `this`,
|
||||||
|
> calling the shortcut bare as `window._('pad.chat')` loses its `this` context
|
||||||
|
> and returns `undefined` (the `get` implementation reads `this.translations`).
|
||||||
|
> Prefer one of the patterns that actually works:
|
||||||
|
>
|
||||||
|
> * Call the method on the object directly: `window.html10n.get('pad.chat')`.
|
||||||
|
> * Bind it once, then reuse: `const _ = window.html10n.get.bind(window.html10n);`
|
||||||
|
> followed by `_('pad.chat')`.
|
||||||
|
>
|
||||||
|
> Wherever possible, prefer marking up your templates with `data-l10n-id`
|
||||||
|
> attributes (see above) instead of translating strings in JavaScript at all —
|
||||||
|
> html10n applies those automatically.
|
||||||
### 2. Create translate files in the locales directory of your plugin
|
### 2. Create translate files in the locales directory of your plugin
|
||||||
|
|
||||||
* The name of the file must be the language code of the language it contains translations for (see [supported lang codes](https://joker-x.github.com/languages4translatewiki/test/); e.g. en ? English, es ? Spanish...)
|
* The name of the file must be the language code of the language it contains translations for (see [supported lang codes](https://joker-x.github.com/languages4translatewiki/test/); e.g. en ? English, es ? Spanish...)
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ and the URL to use if you embed the timeslider in your own page.
|
||||||
|
|
||||||
You can choose a skin changing the parameter `skinName` in `settings.json`.
|
You can choose a skin changing the parameter `skinName` in `settings.json`.
|
||||||
|
|
||||||
Since Etherpad **1.7.5**, two skins are included:
|
Two skins are included:
|
||||||
|
|
||||||
* `no-skin`: an empty skin, leaving the default Etherpad appearance unchanged, that you can use as guidance to develop your own.
|
* `colibris`: the current default skin, used by Etherpad out of the box. This is what you see in a standard installation.
|
||||||
* `colibris`: a new, experimental skin, that will become the default in Etherpad 2.0.
|
* `no-skin`: an unstyled base skin that leaves the default Etherpad appearance unchanged. Use it as a starting point and guidance to develop your own skin.
|
||||||
|
|
|
||||||
157
doc/stats.md
157
doc/stats.md
|
|
@ -1,18 +1,147 @@
|
||||||
# Statistics
|
# Statistics and metrics
|
||||||
Etherpad keeps track of the goings-on inside the edit machinery. If you'd like to have a look at this, just point your browser to `/stats`.
|
|
||||||
|
|
||||||
We currently measure:
|
Etherpad tracks runtime statistics about the edit machinery, the database
|
||||||
|
layer, and the Node.js process, and can expose them over HTTP for monitoring.
|
||||||
|
|
||||||
- totalUsers (counter)
|
There are two endpoints:
|
||||||
- connects (meter)
|
|
||||||
- disconnects (meter)
|
|
||||||
- pendingEdits (counter)
|
|
||||||
- edits (timer)
|
|
||||||
- failedChangesets (meter)
|
|
||||||
- httpRequests (timer)
|
|
||||||
- http500 (meter)
|
|
||||||
- memoryUsage (gauge)
|
|
||||||
|
|
||||||
Under the hood, we are happy to rely on [measured](https://github.com/felixge/node-measured) for all our metrics needs.
|
- `GET /stats` — a JSON dump of the internal `measured-core` collection.
|
||||||
|
- `GET /stats/prometheus` — the same kind of data (plus process/runtime
|
||||||
|
metrics) in the [Prometheus](https://prometheus.io/) text exposition format.
|
||||||
|
|
||||||
To modify or simply access our stats in your plugin, simply `require('ep_etherpad-lite/stats')` which is a [`measured.Collection`](https://yaorg.github.io/node-measured/packages/measured-core/Collection.html).
|
## Enabling the endpoints
|
||||||
|
|
||||||
|
Both endpoints are gated behind the `enableMetrics` setting, which defaults to
|
||||||
|
`true`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"enableMetrics": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
When `enableMetrics` is `false` the routes are **not registered at all** — a
|
||||||
|
request to `/stats` or `/stats/prometheus` returns the normal 404 handling, not
|
||||||
|
an empty response. The admin-panel statistics view is unaffected by this
|
||||||
|
setting.
|
||||||
|
|
||||||
|
## `GET /stats` (JSON)
|
||||||
|
|
||||||
|
Returns the current snapshot of the `measured-core` collection as JSON. The
|
||||||
|
following metrics are collected:
|
||||||
|
|
||||||
|
| Metric | Type | Meaning |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `totalUsers` | gauge | Number of users currently connected across all pads. |
|
||||||
|
| `activePads` | gauge | Number of pads with at least one connected user. |
|
||||||
|
| `connects` | meter | Rate of new client connections. |
|
||||||
|
| `disconnects` | meter | Rate of client disconnections. |
|
||||||
|
| `rateLimited` | meter | Rate of messages dropped by the per-connection rate limiter. |
|
||||||
|
| `pendingEdits` | counter | Edits received but not yet fully processed. |
|
||||||
|
| `edits` | timer | Time taken to process an incoming `USER_CHANGES` edit (full handler span). |
|
||||||
|
| `failedChangesets` | meter | Rate of changesets that failed to apply. |
|
||||||
|
| `httpRequests` | timer | Duration of HTTP requests served by Express. |
|
||||||
|
| `http500` | meter | Rate of HTTP 500 responses. |
|
||||||
|
| `memoryUsage` | gauge | Process resident set size (`process.memoryUsage().rss`). |
|
||||||
|
| `memoryUsageHeap` | gauge | Process heap usage (`process.memoryUsage().heapUsed`). |
|
||||||
|
| `lastDisconnect` | gauge | Timestamp (ms) of the most recent socket disconnect. |
|
||||||
|
| `ueberdb_*` | gauge | One gauge per [ueberDB](https://github.com/ether/ueberDB) database statistic, e.g. read/write counts and timings (`ueberdb_reads`, `ueberdb_writes`, …). The exact set depends on the configured database driver. |
|
||||||
|
|
||||||
|
Under the hood these are provided by
|
||||||
|
[`measured-core`](https://github.com/yaorg/node-measured/tree/master/packages/measured-core).
|
||||||
|
To read or extend them from a plugin, require the shared collection:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const stats = require('ep_etherpad-lite/node/stats');
|
||||||
|
// stats is a measured-core Collection
|
||||||
|
stats.counter('my_plugin_events').inc();
|
||||||
|
console.log(stats.toJSON());
|
||||||
|
```
|
||||||
|
|
||||||
|
## `GET /stats/prometheus` (Prometheus exposition format)
|
||||||
|
|
||||||
|
Served from a dedicated [`prom-client`](https://github.com/siimon/prom-client)
|
||||||
|
registry. This is the endpoint you point a Prometheus scraper at.
|
||||||
|
|
||||||
|
### Metrics exposed by default
|
||||||
|
|
||||||
|
| Metric | Type | Labels | Meaning |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `etherpad_total_users` | gauge | — | Total number of connected users. |
|
||||||
|
| `etherpad_active_pads` | gauge | — | Total number of active pads. |
|
||||||
|
| `ueberdb_stats` | gauge | `type` | ueberDB statistics, one series per numeric ueberDB metric (the metric name is carried in the `type` label). |
|
||||||
|
|
||||||
|
In addition, the registry calls `prom-client`'s `collectDefaultMetrics()`, so
|
||||||
|
the standard Node.js / process metrics are also exposed, including (names as
|
||||||
|
emitted by `prom-client`):
|
||||||
|
|
||||||
|
- `process_cpu_user_seconds_total`, `process_cpu_system_seconds_total`,
|
||||||
|
`process_cpu_seconds_total`
|
||||||
|
- `process_resident_memory_bytes`, `process_heap_bytes`,
|
||||||
|
`process_virtual_memory_bytes`
|
||||||
|
- `process_open_fds`, `process_max_fds`
|
||||||
|
- `process_start_time_seconds`
|
||||||
|
- `nodejs_eventloop_lag_seconds` and the `nodejs_eventloop_lag_*` family
|
||||||
|
- `nodejs_active_handles`, `nodejs_active_requests`, `nodejs_active_resources`
|
||||||
|
(and their `_total` variants)
|
||||||
|
- `nodejs_heap_size_total_bytes`, `nodejs_heap_size_used_bytes`,
|
||||||
|
`nodejs_external_memory_bytes`, `nodejs_heap_space_size_*_bytes`
|
||||||
|
- `nodejs_gc_duration_seconds`
|
||||||
|
- `nodejs_version_info`
|
||||||
|
|
||||||
|
The exact default-metric set is determined by `prom-client` and the Node.js
|
||||||
|
version, not by Etherpad.
|
||||||
|
|
||||||
|
### Opt-in scaling-dive metrics (`scalingDiveMetrics`)
|
||||||
|
|
||||||
|
A second, more detailed instrument set was added for the scaling investigation
|
||||||
|
(PR #7756). It is gated behind the `scalingDiveMetrics` setting, which defaults
|
||||||
|
to `false`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"scalingDiveMetrics": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
When the flag is off, these metrics are never registered and their recording
|
||||||
|
helpers short-circuit to no-ops, so production deployments pay nothing for the
|
||||||
|
instrumentation. When enabled, the following are added to the
|
||||||
|
`/stats/prometheus` output:
|
||||||
|
|
||||||
|
| Metric | Type | Labels | Meaning |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `etherpad_changeset_apply_duration_seconds` | histogram | — | Time spent applying an incoming `USER_CHANGES` message on the server (apply path only; excludes fan-out to other clients). Buckets: 0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 5 seconds. |
|
||||||
|
| `etherpad_socket_emits_total` | counter | `type` | Number of socket.io broadcast emits, bucketed by message type. |
|
||||||
|
| `etherpad_pad_users` | gauge | `padId` | Active users connected to each pad, keyed by pad id. |
|
||||||
|
|
||||||
|
**Cardinality caution.** The scaling-dive metrics carry high-cardinality
|
||||||
|
labels:
|
||||||
|
|
||||||
|
- `etherpad_pad_users` adds one time series **per active pad** (`padId`
|
||||||
|
label). On instances with many pads this can produce a large number of
|
||||||
|
series; stale labels are reset on each scrape so drained pads drop out.
|
||||||
|
- `etherpad_socket_emits_total` uses the `type` label. To keep cardinality
|
||||||
|
bounded, only a fixed allowlist of known message types is reported; any
|
||||||
|
other (or missing) type is rolled into a single `other` bucket, so a
|
||||||
|
misbehaving plugin or API caller cannot explode the label space.
|
||||||
|
|
||||||
|
Enable `scalingDiveMetrics` for targeted load-testing or capacity
|
||||||
|
investigations, not as a permanent production default.
|
||||||
|
|
||||||
|
## Scraping with Prometheus
|
||||||
|
|
||||||
|
Add a scrape job pointing at the `/stats/prometheus` endpoint, for example:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
scrape_configs:
|
||||||
|
- job_name: etherpad
|
||||||
|
metrics_path: /stats/prometheus
|
||||||
|
static_configs:
|
||||||
|
- targets: ['localhost:9001']
|
||||||
|
```
|
||||||
|
|
||||||
|
Make sure `enableMetrics` is `true` (the default) so the endpoint exists. If
|
||||||
|
your instance is reachable from untrusted networks, restrict access to
|
||||||
|
`/stats` and `/stats/prometheus` at your reverse proxy, since they expose
|
||||||
|
operational details about the deployment.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue