install radicale and register officer-caldav on an existing server's next boot

Calendar and contacts became core in 9c309c5a. A FRESH install gets radicale
from machine-setup and officer-caldav from CORE_PROCESSES; an existing one gets
neither from a git pull, because a pull changes nothing outside the repository.
So without this, pulling that commit leaves /calendar mounted, rendering, and
talking to nothing — the silent half-working failure this codebase keeps
getting bitten by, and the same shape as the plugin permission collision fixed
in ee321f49.

Best-effort and AFTER serve(), never blocking: a held apt lock, an unreachable
mirror or a machine without passwordless sudo must not stop Officer starting.
Uses the existing os-deps machinery rather than a second spelling of "install a
package" — including `sudo -n`, which fails fast instead of hanging on a prompt
no one can answer under PM2. When it cannot install, it logs the exact command
to run by hand.

addCoreProcess is new and deliberately narrow. The platform normally never
writes a core entry — it does not know the core list, and duplicating
CORE_PROCESSES here would be a second copy to drift. The exception is exactly
this: a commit that MOVES something into the core set, which an existing
install has no other way to learn about.

Verified on this machine rather than assumed: radicale was absent, the guard
installed it (3.1.8), and a second run reported `present` and wrote nothing.
The install path is not unit-tested on purpose — a test that really ran
apt-get would modify the machine running it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-19 04:28:17 +00:00
co-authored by Claude Opus 5
parent 9c309c5ab6
commit d6f093532d
5 changed files with 150 additions and 1 deletions
+17
View File
@@ -129,6 +129,23 @@ export function addPluginToEcosystem(plugin: DiscoveredPlugin): boolean {
return true;
}
/**
* Add a CORE process the running platform knows it needs, when the file predates it.
*
* Normally core entries come from `officer-setup`'s CORE_PROCESSES and the platform never writes one —
* it does not know the core list and duplicating it here would be a second copy to drift. This is the
* exception, and it is narrow on purpose: an existing install that pulls a commit which MOVED something
* into the core set has no other way to learn about it, because `git pull` changes nothing outside the
* repository. Caller supplies the whole entry; nothing here has an opinion about what belongs.
*
* Idempotent. Replaces an entry of the same name so a changed script path is picked up.
*/
export function addCoreProcess(entry: Pm2App): void {
const apps = readEcosystemApps().filter((a) => a.name !== entry.name);
apps.push(entry);
writeEcosystemApps(apps);
}
/** Drop the plugin's entry. Safe to call when it was never there. */
export function removePluginFromEcosystem(appName: string): void {
const name = pluginProcessName(appName);
+1 -1
View File
@@ -97,7 +97,7 @@ export type DependencyReport = {
* Homebrew is the exception and needs no escalation: it refuses to run as root outright, which is also
* why machine-setup never escalates on macOS.
*/
function sudoAvailable(): boolean {
export function sudoAvailable(): boolean {
if (!Bun.which('sudo')) return false;
try {
return Bun.spawnSync(['sudo', '-n', 'true'], { stdout: 'ignore', stderr: 'ignore' }).exitCode === 0;