Merge pull request 'to-be 56: the betterbird module owns Betterbird's whole configuration, shown without a credential (issue 586, ADR 0329)' (#336) from design/586-betterbird-owns-its-configuration into main
This commit was merged in pull request #336.
This commit is contained in:
@@ -2,10 +2,12 @@
|
|||||||
layer: to-be
|
layer: to-be
|
||||||
status: designed
|
status: designed
|
||||||
code: []
|
code: []
|
||||||
updated: 2026-10-12
|
updated: 2026-10-13
|
||||||
decisions:
|
decisions:
|
||||||
- "ADR 0316"
|
- "ADR 0316"
|
||||||
- "ADR 0317"
|
- "ADR 0317"
|
||||||
|
- "ADR 0326"
|
||||||
|
- "ADR 0329"
|
||||||
---
|
---
|
||||||
|
|
||||||
# 56 — The mail client, set up by the mesh
|
# 56 — The mail client, set up by the mesh
|
||||||
@@ -14,12 +16,17 @@ decisions:
|
|||||||
upstream release, ships the Owl add-on with it through Betterbird's enterprise policy, and declares the
|
upstream release, ships the Owl add-on with it through Betterbird's enterprise policy, and declares the
|
||||||
operator's mail accounts without their credentials. A password or token never passes through the mesh: each
|
operator's mail accounts without their credentials. A password or token never passes through the mesh: each
|
||||||
account signs in once, at a person's first use, and Betterbird keeps the result in its own login store.**
|
account signs in once, at a person's first use, and Betterbird keeps the result in its own login store.**
|
||||||
(ADR 0316, from issue 345.)
|
(ADR 0316, from issue 345.) **The module owns Betterbird's whole configuration as settings, mesh-wide with a
|
||||||
|
node's layer over each: the preferences, the accounts, the identities with their signatures, the CalDAV
|
||||||
|
calendars, the CardDAV address books, the filters and the tags; its tools show the operator's own values, never
|
||||||
|
a credential.** (ADR 0329, from issue 586.)
|
||||||
|
|
||||||
**Acceptance.** On a desktop node set up from nothing, the operator starts Betterbird from the launcher and
|
**Acceptance.** On a desktop node set up from nothing, the operator starts Betterbird from the launcher and
|
||||||
finds Owl installed and the declared accounts listed; each asks for its sign-in once, and the work account is
|
finds Owl installed and the declared accounts listed; each asks for its sign-in once, and the work account is
|
||||||
created in Owl's own setup. On a desktop node that ran the community package before, the same Betterbird opens
|
created in Owl's own setup. On a desktop node that ran the community package before, the same Betterbird opens
|
||||||
the same profile, with its mail and logins, after the switch.
|
the same profile, with its mail and logins, after the switch. Once the operator has approved the derived layers,
|
||||||
|
every desktop node shows the same signatures, calendars, address books, filters and tags, and `betterbird_drift`
|
||||||
|
on each says no difference.
|
||||||
|
|
||||||
The module's look-only part (status, preferences, a declared user.js, its log, a restart and a reminder test)
|
The module's look-only part (status, preferences, a declared user.js, its log, a restart and a reminder test)
|
||||||
is issue 345's and is not repeated here. This document adds what sets Betterbird up.
|
is issue 345's and is not repeated here. This document adds what sets Betterbird up.
|
||||||
@@ -31,11 +38,13 @@ is issue 345's and is not repeated here. This document adds what sets Betterbird
|
|||||||
| Betterbird, the upstream release archive, unpacked | the installation directory the community package uses | owned, whole |
|
| Betterbird, the upstream release archive, unpacked | the installation directory the community package uses | owned, whole |
|
||||||
| the enterprise policy, `policies.json` | the installation's `distribution` directory | owned, whole |
|
| the enterprise policy, `policies.json` | the installation's `distribution` directory | owned, whole |
|
||||||
| the Owl add-on, the vendor's xpi | the installation directory | owned, whole |
|
| the Owl add-on, the vendor's xpi | the installation directory | owned, whole |
|
||||||
| a defaults file naming the system's spelling dictionaries | the installation's `defaults/pref` | owned, whole |
|
| a defaults file naming the system's spelling dictionaries, and one naming the preferences kept at Betterbird's own default (no value of the operator's) | the installation's `defaults/pref` | owned, whole |
|
||||||
| a launcher entry and a start script | under the system's local prefix | owned, whole |
|
| a launcher entry and a start script | under the system's local prefix | owned, whole |
|
||||||
| the user.js, from `prefs` and `accounts` | the module's directory in the home, copied into the profile by its tools | owned, whole |
|
| the settings as the node-engine merges them, one mergeable file per group, each naming the keys it takes (ADR 0326) | the module's directory in the home | owned, whole |
|
||||||
| the account list in the profile's `prefs.js` | the profile | written into: the module's own keys only |
|
| the user.js, composed from the settings | the profile, placed by the tools | the module's, or never touched |
|
||||||
| the profile: mail, address books, calendars, logins | the profile | found |
|
| the lists in the profile's `prefs.js`: accounts, outgoing servers, an adopted account's identities, the calendars' order | the profile | written into: the module's own keys only, only while Betterbird is closed |
|
||||||
|
| each declared account's filter file | the account's directory in the profile | owned, whole, only while Betterbird is closed |
|
||||||
|
| the profile: mail, calendar events, contacts, logins | the profile | found |
|
||||||
|
|
||||||
The module creates no link: where the community package linked the command, the icons and the dictionaries,
|
The module creates no link: where the community package linked the command, the icons and the dictionaries,
|
||||||
the module places a start script, a launcher entry that names the installation directly, and the dictionaries'
|
the module places a start script, a launcher entry that names the installation directly, and the dictionaries'
|
||||||
@@ -159,14 +168,26 @@ the provider's own sign-in can issue. With no credential to carry, the rule that
|
|||||||
|
|
||||||
## 6. What the tools say
|
## 6. What the tools say
|
||||||
|
|
||||||
`betterbird_status` and `betterbird_check` add, never with an address, a host or a name:
|
**The tools show the operator's own configuration, never a credential** (ADR 0329 §1–§3, changing ADR 0316 §16
|
||||||
|
and ADR 0317 §3). An answer may hold an account's name, address, user name and its servers' hosts and ports; an
|
||||||
|
identity's name, address, reply-to, organisation and signature; a calendar's or an address book's name, address,
|
||||||
|
colour and options; a filter's conditions and actions; a tag's name and colour. These values appear only in a
|
||||||
|
tool's answer to its caller and, for a proposed value, in the controller's proposal record; never in an event, a
|
||||||
|
log line, a condition, a metric, the module's record per node, an issue, the knowledge base or this repository.
|
||||||
|
The operator channel's content rule does not apply to these answers, because it withholds the addresses they
|
||||||
|
show; in its place every answer passes the module's own credential check, which withholds a field whose name says
|
||||||
|
password, token, secret or cookie, and the password part of any address. The login store is read only to count,
|
||||||
|
by server, the sign-ins kept. The values reach whoever may call the module's tools, which is every login and
|
||||||
|
agent session on the operator's nodes; the ask itself still shows a proposed value in its shape.
|
||||||
|
|
||||||
- the installed version against the pin and the newest upstream release, and whether it came from the mesh or
|
| tool | answers |
|
||||||
from the community package;
|
|---|---|
|
||||||
- whether the policy file is placed;
|
| `betterbird_status`, `betterbird_check` | the installed version against the pin and the newest release; the policy placed; Owl's version and whether it is active; the declared accounts in the list and how many have not signed in yet; and, per kind, the declared entries that drift or are refused and the changes waiting for Betterbird to close |
|
||||||
- Owl's version against its pin, and whether it is active;
|
| `betterbird_accounts` | the accounts with their values and the declared account each is, Owl's marked by name and address; with `propose`, `accounts` derived and proposed, the value shown |
|
||||||
- how many declared accounts are in the profile's list, beside how many other accounts exist, and how many have
|
| `betterbird_identities`, `betterbird_calendars`, `betterbird_address_books`, `betterbird_filters`, `betterbird_tags` | the profile's entries beside the declared ones, how each resolved, an ICS calendar by its name only |
|
||||||
not signed in yet.
|
| `betterbird_derive` | the five kinds derived per layer, the fields another node holds differently, what was left out; with `propose`, the next layer proposed |
|
||||||
|
| `betterbird_drift` | per kind and declared entry: holds, differs (each field with both values), lacks, or refused; and the undeclared entries |
|
||||||
|
| `betterbird_config` | each managed preference through the locks, the setting `prefs` and Betterbird's defaults, with its drift |
|
||||||
|
|
||||||
## 7. What changes for the builder
|
## 7. What changes for the builder
|
||||||
|
|
||||||
@@ -178,12 +199,79 @@ Not decided here: a package repository of the mesh's own, which would also answe
|
|||||||
node runs from outside the official repositories. ADR 0316 weighed it, and rejected it as larger than this
|
node runs from outside the official repositories. ADR 0316 weighed it, and rejected it as larger than this
|
||||||
module needs.
|
module needs.
|
||||||
|
|
||||||
|
## 8. The rest of the configuration (ADR 0329)
|
||||||
|
|
||||||
|
**The preferences are one entry each.** The setting `prefs` maps a preference's name to its value, so a node's
|
||||||
|
layer overrides one preference, or takes one out with null, and the operator's setup lives in the mesh-wide
|
||||||
|
layer, not in the catalogue. The module ships only its policy's locks and the names it keeps at Betterbird's own
|
||||||
|
default (issue 586). `prefs` refuses a credential's preference, an account's (§4), and the four prefixes the
|
||||||
|
settings below own: `calendar.registry.`, `calendar.list.`, `ldap_2.servers.` and `mailnews.tags.`, so each value
|
||||||
|
has one owner.
|
||||||
|
|
||||||
|
**Five more settings**, set mesh-wide so every desktop node has the same setup, with a node's layer holding only a
|
||||||
|
difference that node is meant to have:
|
||||||
|
|
||||||
|
| setting | an entry | matched in the profile by |
|
||||||
|
|---|---|---|
|
||||||
|
| `identities` | an identity of a declared account: name, address, reply-to, organisation, the signature's text, whether it is HTML, whether it goes above the quote; the account's first identity takes its name and address from `accounts` | the account's default, or an identity of that account with the address |
|
||||||
|
| `calendars` | a CalDAV calendar: name, address, colour, read-only, reminders, user name, the identity that answers invitations | its address |
|
||||||
|
| `address-books` | a CardDAV address book: name, address, user name | its address |
|
||||||
|
| `filters` | per declared account, the ordered list of filters: name, on or off, when it runs, all or any condition, conditions, actions, a folder named by its path within the account | owned whole |
|
||||||
|
| `tags` | per tag key: name, colour, place | its key |
|
||||||
|
|
||||||
|
An identity, a calendar or an address book may carry, in a node's own layer only, the optional `key` of the
|
||||||
|
profile's entry to adopt there, as `accounts` does. The first four are **trusted**, because each names where a
|
||||||
|
password, a reply or forwarded mail goes; `tags` names no server and sends no mail, and is set through the
|
||||||
|
`settings` verb as well. A setting holds no line break, so a signature is a list of its lines; no setting holds a
|
||||||
|
password, a token or a cookie, and an address whose user part carries a password is refused.
|
||||||
|
|
||||||
|
**Adopted as accounts are.** At every apply each declared entry is matched as §4 matches an account: recorded,
|
||||||
|
the module's own key, a node's `key`, else one match adopted, none created under the module's key, several
|
||||||
|
refused with the remedy. Its declared fields go into the user.js under that key. An entry leaving the setting is
|
||||||
|
released if adopted; if created, its preferences are removed, never a calendar's events, an address book's
|
||||||
|
contacts or a cache. An entry no setting declares stays the person's. Owl's accounts and calendars, a calendar
|
||||||
|
subscribed by an ICS address (such an address is often itself the secret), local calendars and local address
|
||||||
|
books are never declared.
|
||||||
|
|
||||||
|
**Filters are owned whole per declared account.** For an account named in `filters`, the module writes its filter
|
||||||
|
file from the declared list, turning each folder's path into that folder's address on the node; a filter added in
|
||||||
|
Betterbird to that account is gone at the next apply, and drift shows it first. Before its first write on a node
|
||||||
|
the module keeps the file it replaces in its own directory in the home. When an account leaves `filters`, the
|
||||||
|
module stops writing its file, which stays the person's as last written, and says where the kept copy is. An
|
||||||
|
account never named keeps its file.
|
||||||
|
|
||||||
|
**What Betterbird rewrites is written only while it is closed:** the lists in `prefs.js` and each filter file.
|
||||||
|
An apply while it runs writes neither, and the module's check counts, per kind, the changes that wait; the next
|
||||||
|
start from the launcher writes them. The user.js is placed as before, since Betterbird reads it only at its start.
|
||||||
|
|
||||||
|
**Derived from the profiles, proposed one layer at a time.** `betterbird_derive` asks every desktop node that
|
||||||
|
holds Betterbird for what its profile holds. An entry found on every such node goes into the mesh-wide layer
|
||||||
|
with the value of the node the derive was called on, and the answer lists every field another node holds
|
||||||
|
differently, which then shows as drift on that node; an entry found on some nodes only goes into those nodes'
|
||||||
|
own layers. With `propose` it proposes the next layer for the operator's Approve: first the mesh-wide one, then
|
||||||
|
each node's that needs one, each only after the one before it was set or declined, against the layer as it then
|
||||||
|
stands and carrying that layer whole (ADR 0277 keeps one open ask per module and machine). It proposes nothing
|
||||||
|
while a desktop node does not answer, because a layer derived without it would put on every node what only some
|
||||||
|
hold. The first mesh-wide proposal also carries the operator's setup of `prefs`, which the catalogue held before.
|
||||||
|
|
||||||
## How it is checked
|
## How it is checked
|
||||||
|
|
||||||
- **The catalogue's tests:** both pins carry a URL, a version and a sha256; a Betterbird pin outside Owl's range
|
- **The catalogue's tests:** both pins carry a URL, a version and a sha256; a Betterbird pin outside Owl's range
|
||||||
is refused; the generated policy file names Owl as `force_installed` from the placed file with
|
is refused; the generated policy file names Owl as `force_installed` from the placed file with
|
||||||
`updates_disabled`, and sets `DisableAppUpdate`; `prefs` refuses the account prefixes; `accounts` refuses a
|
`updates_disabled`, and sets `DisableAppUpdate`; `prefs` refuses the account prefixes and the four of §8;
|
||||||
password-like field; a declared account's user.js holds only the module's own keys.
|
`accounts` refuses a password-like field; a declared account's user.js holds only the module's own keys; every
|
||||||
|
mergeable file names the keys it takes, `tags` alone untrusted; the module's defaults hold no value of the
|
||||||
|
operator's.
|
||||||
|
- **The controller's tests:** a key of a trusted file that names its keys is refused through the `settings` verb,
|
||||||
|
and a key of a file nothing trusts, `tags`, is set through it.
|
||||||
|
- **The configuration's tests, on profile fixtures:** each kind adopted, created, or refused when ambiguous, a
|
||||||
|
`key` settling it; leaving releases or removes and never touches events; a declared filter list, forwarding
|
||||||
|
included, written with this node's folder addresses, the replaced file kept, a file never named left byte for
|
||||||
|
byte, one taken out left as last written; with Betterbird running nothing of `prefs.js` or a filter file is
|
||||||
|
written and the waiting changes are counted; derived from two nodes, an entry both hold mesh-wide with the
|
||||||
|
calling node's value and the other's differing fields listed, one on one node in its own layer, Owl's, ICS
|
||||||
|
calendars and a passworded address left out, and one layer proposed at a time; drift names each differing field;
|
||||||
|
no answer holds a credential.
|
||||||
- **The module's tests:** writing into `prefs.js` adds and removes only the module's keys, keeps the others and
|
- **The module's tests:** writing into `prefs.js` adds and removes only the module's keys, keeps the others and
|
||||||
their order, and is refused while Betterbird runs; the count of declared accounts without a kept sign-in is
|
their order, and is refused while Betterbird runs; the count of declared accounts without a kept sign-in is
|
||||||
taken from the login store's server names, which are kept in the clear, without decrypting anything.
|
taken from the login store's server names, which are kept in the clear, without decrypting anything.
|
||||||
@@ -196,9 +284,14 @@ module needs.
|
|||||||
the mesh, the policy placed, Owl at its pin and active, the profile in use the same as before the switch, and
|
the mesh, the policy placed, Owl at its pin and active, the profile in use the same as before the switch, and
|
||||||
the declared accounts in the list with how many have not signed in yet; the package manager's list of foreign packages no longer names the
|
the declared accounts in the list with how many have not signed in yet; the package manager's list of foreign packages no longer names the
|
||||||
community package.
|
community package.
|
||||||
|
- **Live, after the operator's Approve of the derived layers:** on the node derived on, `betterbird_drift` shows no
|
||||||
|
drift for the mesh-wide entries; on the other desktop node it first shows each field where it differs, and,
|
||||||
|
after Betterbird is closed there and started again, none; `betterbird_check` on both says no drift and no
|
||||||
|
waiting change.
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
- ADR 0316, ADR 0317, ADR 0180, ADR 0182, ADR 0205, ADR 0208, ADR 0277.
|
- ADR 0316, ADR 0317, ADR 0326, ADR 0329, ADR 0180, ADR 0182, ADR 0205, ADR 0208, ADR 0277.
|
||||||
- Issue 345 (the reminder window, extended on 2026-10-12), issue 342, issue 339.
|
- Issue 345 (the reminder window, extended on 2026-10-12), issue 586 (the whole configuration), issue 342,
|
||||||
|
issue 339.
|
||||||
- Thunderbird's enterprise policy templates (ESR 153): `ExtensionSettings`, `DisableAppUpdate`, `Preferences`.
|
- Thunderbird's enterprise policy templates (ESR 153): `ExtensionSettings`, `DisableAppUpdate`, `Preferences`.
|
||||||
|
|||||||
Reference in New Issue
Block a user