mesh/merge-gate pass: builds new: modules/betterbird, sent nowhere; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery superseded: a newer head of the same pull request
A restart ended Betterbird with whatever a composer held; now it refuses while a Msgcompose window is open unless discard_unsent says the operator agreed, and answers within the runtime's 30 s. A preference's value could carry an address, a user%40domain@host URI, a host or the home; those are masked, recent-folder, account and cloud-file preferences and every add-on preference but the theme's id are withheld, and a request for everything is refused. A log kept the token after Bearer or Basic. The reminder test's Betterbird counted as the operator's and left its profile behind when it ended on its own; it is now left out everywhere and its unit removes the profile when it stops.
158 lines
7.2 KiB
Go
158 lines
7.2 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() {
|
|
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), the installed package and version and whether it is from the AUR, where its " +
|
|
"output goes, the profile it uses and how that was found, the module's user.js (declared, placed, in " +
|
|
"effect), the add-ons the operator installed, and the preferences that decide how its windows and " +
|
|
"notifications look (reminder dialog, theme, renderer, system notifications). (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, from its setting prefs, written by " +
|
|
"the mesh to ~/.config/betterbird/user.js — 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). 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 10 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 is installed; one runs; its profile is found; the " +
|
|
"module's user.js is on the machine, valid, placed in the profile and in effect; its output is read " +
|
|
"(a pipe nobody reads loses what it prints). Answers ok and each finding with what to do. (r)",
|
|
Run: func(map[string]any) (any, error) { return machine.Check() },
|
|
},
|
|
{
|
|
Name: "betterbird_reminder_test",
|
|
Description: "Show 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), with the module's user.js and one local calendar file holding one event 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)
|
|
},
|
|
},
|
|
}
|
|
}
|