A module that cannot set Betterbird up is of little use (the operator, 2026-10-12). The module now declares the community package absent and unpacks the pinned upstream release where that package had it, so the same profile opens; places the enterprise policy that turns Betterbird's updater off and force-installs Owl from the xpi the mesh pinned by its vendor's digest (never in a repository); and takes the operator's accounts from the trusted setting accounts, without any password or token: an account the profile already holds is adopted under its own keys, a new one is created under the module's mesh_ keys, an ambiguous one is refused with the remedy, and the account lists are only written into. betterbird_accounts says the profile's accounts by count and proposes the setting built from them for the operator's Approve. Every new build waits for the operator's push. The reminder test is visibly a test (MESH TEST band, event name) and its description says it opens a window on the operator's screen.
197 lines
9.6 KiB
Go
197 lines
9.6 KiB
Go
// The betterbird module's Go tools bundle (novox/hq ADR 0188, ADR 0193, ADR 0208): Betterbird, the
|
|
// mail and calendar client, in the operator's session, served by the node's runtime as the operator
|
|
// account. The module holds no seat, so every tool is its own.
|
|
//
|
|
// The operator's mail, calendars, accounts and credentials are never read: a tool reads processes,
|
|
// profiles.ini, and of the profile only prefs.js, user.js and extensions.json, and answers no
|
|
// preference that can hold a credential or names an account.
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"time"
|
|
|
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
|
)
|
|
|
|
func main() {
|
|
// The module's start runs this before Betterbird (novox/hq ADR 0316 §11, §12): the user.js placed and the
|
|
// declared accounts written into the account lists, while no Betterbird of this account runs.
|
|
if len(os.Args) == 2 && os.Args[1] == "prepare" {
|
|
if err := machine.Prepare(); err != nil {
|
|
fmt.Fprintln(os.Stderr, "betterbird: "+err.Error())
|
|
os.Exit(1)
|
|
}
|
|
return
|
|
}
|
|
if err := stdio.Serve("", tools()); err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
var machine = NewMachine()
|
|
|
|
func tools() []stdio.Tool {
|
|
return []stdio.Tool{
|
|
{
|
|
Name: "betterbird_status",
|
|
Description: "Betterbird: whether it runs (the main process's pid, since, and the scope it runs in; how " +
|
|
"many child processes); what is installed in /opt/betterbird and from where (the mesh's pinned release, " +
|
|
"or the community package before the switch), its version and build beside the newest published build; " +
|
|
"whether the enterprise policy is placed; Owl's version against the mesh's pin and whether it is active; " +
|
|
"where its output goes; the profile it uses and how that was found; the module's user.js (declared, " +
|
|
"placed, in effect); the declared accounts by count (in the profile's list, other accounts, not signed " +
|
|
"in yet), never an address, host or name; the add-ons in the profile; and the preferences that decide " +
|
|
"how its windows and notifications look. (r)",
|
|
Run: func(map[string]any) (any, error) { return machine.Status() },
|
|
},
|
|
{
|
|
Name: "betterbird_prefs",
|
|
Description: "Named preferences of Betterbird's profile: each one's value and where it comes from (user.js, " +
|
|
"which wins at the start; prefs.js; or Betterbird's default). A name ending in . or * is a prefix " +
|
|
"(at most 200 answered). A preference whose name can hold a credential, or that names an account, a " +
|
|
"server, a calendar or a person, is answered without its value. (r)",
|
|
Input: map[string]any{
|
|
"names": map[string]any{"type": "array", "items": map[string]any{"type": "string"},
|
|
"description": "preference names or prefixes, e.g. [\"calendar.alarms.\", \"extensions.activeThemeID\"]"},
|
|
},
|
|
Run: func(args map[string]any) (any, error) {
|
|
names, err := texts(args, "names")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return machine.Prefs(names)
|
|
},
|
|
},
|
|
{
|
|
Name: "betterbird_user_js",
|
|
Description: "The module's user.js — the preferences the module declares (setting prefs), then the declared " +
|
|
"accounts' preferences under the module's own keys (setting accounts) — against the profile's user.js: " +
|
|
"whether it is placed, and whether the running Betterbird started after it was. With apply, place it in " +
|
|
"the profile (only where the profile has no user.js or the module's own; an operator's file is never " +
|
|
"overwritten) and, when Betterbird is not running, add the declared accounts' keys to the profile's " +
|
|
"account lists. A declared account whose address the profile already holds under another key refuses " +
|
|
"the apply, before anything is written. Betterbird reads it at its next start: betterbird_restart. " +
|
|
"(r; a with apply)",
|
|
Input: map[string]any{
|
|
"apply": map[string]any{"type": "boolean", "description": "place the declared user.js in the profile (default false)"},
|
|
},
|
|
Run: func(args map[string]any) (any, error) {
|
|
apply, err := flag(args, "apply", false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return machine.userJS(apply)
|
|
},
|
|
},
|
|
{
|
|
Name: "betterbird_log",
|
|
Description: "The last lines Betterbird printed, from the journal of the unit the module starts it as " +
|
|
"(mesh-betterbird; or mesh-betterbird-reminder-test). With problems, only lines that say error, " +
|
|
"warning, assertion, crash or failure. Mail addresses and credentials are replaced by <hidden>. A " +
|
|
"Betterbird the module did not start prints elsewhere, and the answer says so. (r)",
|
|
Input: map[string]any{
|
|
"lines": map[string]any{"type": "integer", "description": "how many lines (default 100, at most 2000)"},
|
|
"problems": map[string]any{"type": "boolean", "description": "only problem lines (default false)"},
|
|
"unit": map[string]any{"type": "string", "enum": []string{restartAs, testUnit}, "description": "which start's journal (default " + restartAs + ")"},
|
|
},
|
|
Run: func(args map[string]any) (any, error) {
|
|
n, err := whole(args, "lines", 100, 1, 2000)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
problems, err := flag(args, "problems", false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
unit, err := text(args, "unit", false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return machine.Log(n, problems, unit)
|
|
},
|
|
},
|
|
{
|
|
Name: "betterbird_restart",
|
|
Description: "End Betterbird (asked to quit, forced after 8 s), place the module's user.js when the profile " +
|
|
"allows it, and start Betterbird again in the operator's desktop session under the account's service " +
|
|
"manager (unit mesh-betterbird), its output in the journal. Refused while a message is being written " +
|
|
"(a composer window is open), because the unsent message would be lost, unless discard_unsent is true — " +
|
|
"ask the operator first. Answers the pids ended and the new main process. (a)",
|
|
Input: map[string]any{
|
|
"discard_unsent": map[string]any{"type": "boolean", "description": "restart even with a composer open, losing what it holds (default false)"},
|
|
},
|
|
Run: func(args map[string]any) (any, error) {
|
|
discard, err := flag(args, "discard_unsent", false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return machine.Restart(discard)
|
|
},
|
|
},
|
|
{
|
|
Name: "betterbird_check",
|
|
Description: "Check what the module promises: Betterbird installed from the mesh's pinned release and not " +
|
|
"behind the newest published build; the enterprise policy placed; Owl at its pin and active; one " +
|
|
"Betterbird runs; its profile is found; the module's user.js on the machine, valid, placed and in " +
|
|
"effect; the declared accounts in the profile's list, none refused, each signed in once; its output " +
|
|
"read. Answers ok and each finding with what to do, never an address, host or name. (r)",
|
|
Run: func(map[string]any) (any, error) { return machine.Check() },
|
|
},
|
|
{
|
|
Name: "betterbird_accounts",
|
|
Description: "The accounts of the profile Betterbird uses, by count and kind: the IMAP and POP3 accounts a " +
|
|
"declaration can hold, the accounts Owl keeps (never declared), and the rest. Never an address, a host " +
|
|
"or a name, and never anything of the login store. With propose, it builds the module's setting " +
|
|
"accounts from them (every account but Owl's, without any password or token) and proposes it through " +
|
|
"the controller's settings verb for the operator's Approve on a channel that proves who answers " +
|
|
"(novox/hq ADR 0317, ADR 0277): nothing changes until then, and the operator reads the value whole at " +
|
|
"their desk. (r; a with propose)",
|
|
Input: map[string]any{
|
|
"propose": map[string]any{"type": "boolean", "description": "build the setting accounts from the profile and propose it to the operator (default false)"},
|
|
},
|
|
Run: func(args map[string]any) (any, error) {
|
|
propose, err := flag(args, "propose", false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return machine.ProfileAccounts(propose)
|
|
},
|
|
},
|
|
{
|
|
Name: "betterbird_reminder_test",
|
|
Description: "OPENS WINDOWS ON THE OPERATOR'S SCREEN: tell the operator before calling it. Shows a calendar " +
|
|
"reminder without touching the operator's calendar: a second Betterbird in a throw-away profile under " +
|
|
"the runtime directory (--no-remote, unit mesh-betterbird-reminder-test, at most 10 minutes), its main " +
|
|
"window marked MESH TEST in red, with the module's user.js (no accounts) and one local calendar file " +
|
|
"holding one event, \"MESH TEST reminder\", whose reminder is due after `after` seconds. carry copies " +
|
|
"named preferences of the operator's profile (a theme, a colour switch) into it, never a credential or " +
|
|
"an account. Then xorg_windows (class Calendar) and xorg_screenshot show the reminder window. stop ends " +
|
|
"it and removes the profile. (a)",
|
|
Input: map[string]any{
|
|
"after": map[string]any{"type": "integer", "description": "seconds until the reminder is due (default 40, 15 to 300)"},
|
|
"carry": map[string]any{"type": "array", "items": map[string]any{"type": "string"},
|
|
"description": "preference names to copy from the operator's profile, e.g. [\"extensions.activeThemeID\"]"},
|
|
"stop": map[string]any{"type": "boolean", "description": "end the test Betterbird and remove its profile (default false)"},
|
|
},
|
|
Run: func(args map[string]any) (any, error) {
|
|
after, err := whole(args, "after", 40, 15, 300)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
carry, err := texts(args, "carry")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
stop, err := flag(args, "stop", false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return machine.ReminderTest(time.Duration(after)*time.Second, carry, stop)
|
|
},
|
|
},
|
|
}
|
|
}
|