Commit Graph
1877 Commits
Author SHA1 Message Date
Linus Rath dcecd50b7f fix: keep up to 256 rendered branding images per kind, enough for many domains #1066 2026-11-01 01:03:33 +01:00
Linus Rath 38bc06f2d3 fix: answer 503 for plugin lists and marketplace installs while the shared store is down #1066
directory-keys-shared.test.ts: the store-down case now expects the store error instead of an 'invalid' signature, since a store outage no longer counts as a bad signature.
2026-11-01 01:03:13 +01:00
Linus Rath d78ce02f78 fix: refuse a plugin install that can't hold the registry lock and a bundle that doesn't match its entry #1066 2026-11-01 01:01:19 +01:00
Linus Rath 750f3114c1 fix: keep an old plugin's bundle hash when the shared store hiccups while it is fixed #1066 2026-11-01 00:58:33 +01:00
Linus Rath b39e3e27ac fix: cache a plugin signature only when it verifies for the cached bundle #1066 2026-11-01 00:57:21 +01:00
Linus Rath d9f4bdd9be fix: keep serving a plugin unsigned when its first signature can't be stored 2026-11-01 00:55:51 +01:00
Linus Rath 256855b294 fix: refuse to sign plugins on an instance whose signing key differs from the others #1066 2026-11-01 00:54:57 +01:00
Linus Rath 00e28e5f83 Merge branch 'feat/multi-instance' into feat/multi-instance-ext 2026-11-01 00:52:04 +01:00
Linus Rath 77a06a8aa0 fix: stop logging that the shared store is unreachable while its first connection is still being made 2026-11-01 00:39:01 +01:00
Linus Rath 26f0b72b2e feat: write plugins, themes, approvals and branding back to local files on export #1066 2026-11-01 00:30:41 +01:00
Linus Rath 355e57ca6c Merge branch 'feat/multi-instance' into feat/multi-instance-ext 2026-11-01 00:28:15 +01:00
Linus Rath ad7a8d713c feat: bring plugins, themes, approvals and branding along when moving to a shared store #1066 2026-11-01 00:27:17 +01:00
Linus Rath 3c34eda260 fix: cap the cache of Stalwart admin checks so it can't grow without bound 2026-11-01 00:25:35 +01:00
Linus Rath 5efccb65e0 fix: show a re-uploaded logo or icon at once instead of after a restart 2026-11-01 00:24:07 +01:00
Linus Rath fc7d5617c2 feat: serve uploaded branding from the shared store on every instance #1066 2026-11-01 00:20:24 +01:00
Linus Rath 3ed25e1d95 feat: import an existing install into the shared store on first start and export it back with SHARED_STORE_EXPORT #1066 2026-11-01 00:17:20 +01:00
Linus Rath 51e7999e3c feat: sign plugin bundles with one key on every instance #1066 2026-11-01 00:16:42 +01:00
Linus Rath 2078b0a13f Merge branch 'feat/multi-instance-auth' into feat/multi-instance 2026-11-01 00:13:17 +01:00
Linus Rath 5e4b6465d1 Merge branch 'feat/multi-instance' into feat/multi-instance-ext 2026-11-01 00:13:14 +01:00
Linus Rath e3936287fd feat: share plugin approvals and plugin settings between instances #1066 2026-11-01 00:13:09 +01:00
Linus Rath e75834314d fix: show plugin approval requests and decisions to every part of the server at once 2026-11-01 00:13:08 +01:00
Linus Rath 2944d2797f fix: keep every key when several of a plugin's settings are saved at once 2026-11-01 00:12:26 +01:00
Linus Rath 89f337c0ae Merge branch 'feat/multi-instance-admin' into feat/multi-instance 2026-11-01 00:12:24 +01:00
Linus Rath 8127640acf fix: write an office editor sign-out made during a store outage as soon as the store is back #1066 2026-11-01 00:10:09 +01:00
Linus Rath 40578f039d test: give each replica in the setup token tests a state dir of its own #1066 2026-11-01 00:09:49 +01:00
Linus Rath b880e453a5 fix: import the audit log in batches and finish an import that stopped halfway #1066 2026-11-01 00:08:39 +01:00
Linus Rath 5e73fcbcc1 feat: append lines in batches to the shared log and clear it #1066 2026-11-01 00:08:39 +01:00
Linus Rath 47281e5156 fix: answer 503 when an impersonation link's use can't be recorded and keep used links as expiring keys #1066
The marker-write test from d28ee361c now expects the 503 path (a rejected promise) instead of false: refusing with "Token already used" sent admins looking for a replay that never happened, and the jti is now remembered in memory only after it was recorded, so a retry works once the store or state dir is back.
2026-11-01 00:07:41 +01:00
Linus Rath 8b40cfca90 fix: update the plugin frame origins in the CSP right after an install, also on other instances #1066 2026-11-01 00:06:52 +01:00
Linus Rath ff08fe50fe fix: say when an admin sign-out or password change couldn't be stored instead of answering ok or wrong password #1066 2026-11-01 00:05:15 +01:00
Linus Rath df1f5cd6a4 fix: give a pair step-up attempt back only to the windows it was counted in #1066 2026-11-01 00:05:09 +01:00
Linus Rath 80bcfde5f3 Merge branch 'feat/multi-instance-data' into feat/multi-instance 2026-11-01 00:05:03 +01:00
Linus Rath bcb333d40c feat: share installed plugins and themes between instances #1066 2026-11-01 00:04:15 +01:00
Linus Rath 0ed5fb148c fix: never write telemetry defaults over a state file that can't be read #1066 2026-11-01 00:03:34 +01:00
Linus Rath 10abf7273c test: check that pairing, the policy Bearer check and the SSO hand-off answer 503 while the shared store is down #1066 2026-11-01 00:03:20 +01:00
Linus Rath 47ec029717 fix: give a hand-off redeem back when the store fails, and hand out a sign-in whose tombstone couldn't be written #1066 2026-11-01 00:03:19 +01:00
Linus Rath 740a892590 fix: answer 503 for admin and setup saves while the shared store is down #1066
Config, policy, branding, OAuth setup, password and setup wizard writes
used to answer 500 when the shared store couldn't be reached, and the
wizard's token check failed outside any handler. They answer 503 with
Retry-After now, through the helper that already turned a read-only
refusal into 409. The step-session-secret test's wizard mock gains
wizardSessionRefusal, which the setup routes now call.
2026-11-01 00:02:57 +01:00
Linus Rath 79e678716e fix: stop running the background jobs while the shared store has lost its data #1066 2026-11-01 00:02:35 +01:00
Linus Rath a87a9a5f6a fix: flush account archives to disk before they replace the old file again #1066 2026-10-31 23:59:43 +01:00
Linus Rath 46f47b6b8f fix: answer 409 instead of 500 for every admin save a read-only config refuses #1249 2026-10-31 23:59:00 +01:00
Linus Rath 37696f9cf7 fix: answer 503 on the update status routes while the shared store is down #1066 2026-10-31 23:58:21 +01:00
Linus Rath fedbd650bf test: type the pinned pairing store in the disposal tests 2026-10-31 23:57:27 +01:00
Linus Rath 6d22814e5f fix: move settings and plugin credentials that no longer decrypt aside instead of refusing every save #1066
The earlier fix answered 503 to every read and write of a settings file
or plugin vault that doesn't decrypt, so a rotated SESSION_SECRET left
the user with no way out: even disconnecting a plugin failed. Now such a
document reads as nothing stored, and the next write first moves it
aside, then saves as usual. The bytes are kept unchanged, so the data can
be restored with the old secret:

- local mode renames the file to `<file>.unreadable-<time>` next to it
- shared mode copies the document to `<id>.unreadable-<time>`, then
  deletes the original only if it is still the same document

A settings file too short to hold anything reads as nothing stored and
is written over, as before 585e10041. The 503 is now only for a shared
store that can't be reached or a file that can't be read at all.

Credentials the admin set up for a plugin no longer read the user's
vault, so they keep working when it can't be read, and the credential
list still reports their status.
2026-10-31 23:57:08 +01:00
Linus Rath 933d26f0fe fix: delete an abandoned paired app password even when nobody uses pairing for a while, and finish a sign-out that hit a store outage #1066 2026-10-31 23:56:52 +01:00
Linus Rath 68d64f5018 fix: pair the policy with its own version and keep config reloads and saves from undoing each other #1066
Fixes from the review of the admin part:
- GET /api/admin/policy reads the policy and its version together; an
  identity check waiting on the store let another replica's change pair
  the old policy with the new ETag, and a save with it then won.
- A policy save answers with the stored policy and its ETag, and the
  policy, organisation, plugins and themes pages continue from that
  instead of reading it back from an instance that may not have it yet.
- A stored policy that doesn't parse keeps its version, so a save can
  replace it instead of answering 409 forever.
- A reload of another replica's change throws when this replica's writes
  kept landing while it read, so the change poller tries again; a write
  whose reload raced it reads the document again instead of applying its
  older result.
- Dropping a deleted plugin from the policy rereads policy.json first in
  local mode, as every other write does.
- New updateAdminConfig(key, change) builds a value on the stored one.

The policy route's test mocks gain getPolicyWithVersion.
2026-10-31 23:55:17 +01:00
Linus Rath af661a06c8 fix: key refused stored passwords by a secret-keyed hash and never hold a JMAP call for the shared store #1066 2026-10-31 23:53:11 +01:00
Linus Rath 25516ed347 refactor: recognise a shared-store outage in the auth routes with the store's own check 2026-10-31 23:53:11 +01:00
Linus Rath 2130716d83 fix: keep the day's heartbeat when its payload can't be built and never report zero accounts while the store is down #1066 2026-10-31 23:51:19 +01:00
Linus Rath 63f4af27f5 fix: send one heartbeat when the scheduler lead moves after a stall or a crash #1066 2026-10-31 23:50:07 +01:00
Linus Rath 01acacc27b fix: answer 503 instead of 500 when the shared store is full or too busy to take a write #1066 2026-10-31 23:48:40 +01:00