@pellux/goodvibes-daemon 1.28.19 → 1.28.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +119 -63
- package/README.md +24 -19
- package/bin/launcher-support.js +5 -5
- package/package.json +6 -4
- package/scripts/postinstall.js +8 -8
- package/src/cli/command-catalog.ts +22 -22
- package/src/cli/completion.ts +4 -4
- package/src/cli/help.ts +5 -5
- package/src/cli/index.ts +3 -3
- package/src/cli/parser.ts +2 -2
- package/src/cli/surface-catalog.ts +1 -1
- package/src/cli/types.ts +2 -2
- package/src/cluster/daemon-ws-call.ts +5 -5
- package/src/cluster/raw-reply-route.ts +5 -5
- package/src/config/checkpoint-settings.ts +7 -7
- package/src/config/config-key-guard.ts +22 -0
- package/src/config/run-daemon-config-migration.ts +3 -3
- package/src/config/secret-config.ts +7 -7
- package/src/config/surface.ts +3 -3
- package/src/core/pairing-banner.ts +5 -5
- package/src/daemon/cli.ts +45 -43
- package/src/daemon/config-command.ts +15 -15
- package/src/daemon/handlers/context.ts +1 -1
- package/src/daemon/handlers/contracts.ts +19 -4
- package/src/daemon/handlers/credentials.ts +1 -1
- package/src/daemon/handlers/drafts/draft-store.ts +3 -3
- package/src/daemon/handlers/drafts/register.ts +4 -4
- package/src/daemon/handlers/inbox/aggregator.ts +8 -8
- package/src/daemon/handlers/inbox/cursor-store.ts +10 -10
- package/src/daemon/handlers/inbox/index.ts +7 -7
- package/src/daemon/handlers/inbox/mapping.ts +2 -2
- package/src/daemon/handlers/inbox/poller.ts +5 -5
- package/src/daemon/handlers/inbox/provider-adapter.ts +8 -8
- package/src/daemon/handlers/inbox/providers/discord.ts +6 -6
- package/src/daemon/handlers/inbox/providers/email.ts +3 -3
- package/src/daemon/handlers/inbox/providers/imap-client.ts +1 -1
- package/src/daemon/handlers/inbox/providers/slack.ts +4 -4
- package/src/daemon/handlers/index.ts +18 -8
- package/src/daemon/handlers/payments/address-store.ts +54 -0
- package/src/daemon/handlers/payments/budget-store.ts +356 -0
- package/src/daemon/handlers/payments/card-store.ts +486 -0
- package/src/daemon/handlers/payments/checkout-handlers.ts +526 -0
- package/src/daemon/handlers/payments/index.ts +38 -0
- package/src/daemon/handlers/payments/merchant-judge.ts +57 -0
- package/src/daemon/handlers/payments/notifier.ts +112 -0
- package/src/daemon/handlers/payments/purchase-ledger.ts +108 -0
- package/src/daemon/handlers/payments/register.ts +518 -0
- package/src/daemon/handlers/register.ts +3 -3
- package/src/daemon/handlers/remote/backends/cloud-terminal.ts +9 -1
- package/src/daemon/handlers/remote/backends/process-runner.ts +1 -1
- package/src/daemon/handlers/remote/backends/ssh.ts +9 -1
- package/src/daemon/handlers/remote/backends/types.ts +2 -2
- package/src/daemon/handlers/remote/dispatcher.ts +3 -3
- package/src/daemon/handlers/remote/index.ts +1 -1
- package/src/daemon/handlers/remote/peer-registry.ts +62 -13
- package/src/daemon/handlers/routing/inbox-bridge.ts +5 -5
- package/src/daemon/handlers/routing/index.ts +1 -1
- package/src/daemon/handlers/routing/route-store.ts +1 -1
- package/src/daemon/handlers/routing/routing-resolver.ts +3 -3
- package/src/daemon/handlers/sqlite-store.ts +9 -9
- package/src/daemon/handlers/triage/index.ts +1 -1
- package/src/daemon/handlers/triage/integration.ts +3 -3
- package/src/daemon/handlers/triage/pipeline.ts +2 -2
- package/src/daemon/handlers/triage/scorer.ts +2 -2
- package/src/daemon/handlers/triage/tagger/discord.ts +3 -3
- package/src/daemon/handlers/triage/tagger/imap.ts +7 -7
- package/src/daemon/handlers/triage/tagger/index.ts +1 -1
- package/src/daemon/handlers/triage/tagger/shared.ts +3 -3
- package/src/daemon/handlers/triage/tagger/slack.ts +1 -1
- package/src/daemon/handlers/triage/types.ts +2 -2
- package/src/daemon/lifecycle.ts +5 -5
- package/src/daemon/local-daemon-state.ts +7 -7
- package/src/daemon/pair-command.ts +14 -14
- package/src/daemon/provision-wake-model.ts +5 -5
- package/src/daemon/send/channels.ts +7 -7
- package/src/daemon/send/command.ts +11 -11
- package/src/daemon/send/composition.ts +5 -5
- package/src/daemon/send/failure-text.ts +6 -6
- package/src/daemon/send/inert-text.ts +18 -18
- package/src/daemon/send/stdin.ts +3 -3
- package/src/daemon/service-commands.ts +32 -32
- package/src/daemon/sessions-command.ts +7 -7
- package/src/daemon/status-command.ts +22 -22
- package/src/daemon/webui-command.ts +14 -14
- package/src/runtime/boot-tasks.ts +1 -1
- package/src/runtime/browser-checkout-seam-holder.ts +55 -0
- package/src/runtime/cluster-composition.ts +9 -9
- package/src/runtime/cluster-group-composition.ts +7 -7
- package/src/runtime/conversation-rewind-port.ts +8 -8
- package/src/runtime/credential-composition.ts +2 -2
- package/src/runtime/daemon-handler-composition.ts +61 -4
- package/src/runtime/device-posture-composition.ts +10 -10
- package/src/runtime/disposal-wiring.ts +8 -8
- package/src/runtime/fleet-needs-input-push.ts +4 -4
- package/src/runtime/fleet-services.ts +1 -1
- package/src/runtime/hosted-session-composition.ts +13 -13
- package/src/runtime/index.ts +1 -1
- package/src/runtime/knowledge-services.ts +2 -2
- package/src/runtime/legacy-daemon-migration.ts +43 -43
- package/src/runtime/legacy-daemon-reconcile.ts +30 -30
- package/src/runtime/mail-composition.ts +6 -6
- package/src/runtime/notification-dispatch.ts +7 -7
- package/src/runtime/payments-composition.ts +187 -0
- package/src/runtime/plugin-composition.ts +7 -7
- package/src/runtime/runtime-services-types.ts +9 -9
- package/src/runtime/services.ts +41 -32
- package/src/runtime/trigger-services.ts +1 -1
- package/src/runtime/trust/checkpoint-eligibility.ts +5 -5
- package/src/runtime/trust/trust-gated-approvals.ts +9 -9
- package/src/runtime/update-check.ts +4 -4
- package/src/runtime/workspace-checkpointing.ts +6 -6
- package/src/testing/daemon-fixture.ts +11 -11
- package/src/testing/hosted-session-failures.ts +4 -4
- package/src/version.ts +2 -2
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
* CLI (`src/daemon/service-commands.ts`, the `migrate-service` subcommand)
|
|
4
4
|
* and by this daemon's own boot-time reconcile (`legacy-daemon-reconcile.ts`).
|
|
5
5
|
* The terminal app ships an independent implementation of the same
|
|
6
|
-
* detect/migrate contract for its own onboarding guided UX
|
|
6
|
+
* detect/migrate contract for its own onboarding guided UX, not shared
|
|
7
7
|
* code, but the two are meant to agree on what "legacy" means and how a
|
|
8
8
|
* migration is carried out.
|
|
9
9
|
*
|
|
10
10
|
* NAMING, load-bearing: this module's identifiers say "legacy" because the
|
|
11
11
|
* engine migrates AWAY from the `goodvibes-daemon.service` unit name toward
|
|
12
|
-
* the runtime-managed unit
|
|
12
|
+
* the runtime-managed unit, that name was used by an older install script/
|
|
13
13
|
* release (scripts/install.sh now creates `goodvibes.service` and treats
|
|
14
14
|
* `goodvibes-daemon.service` as the retired name it migrates existing hosts
|
|
15
15
|
* away from; see migrate_legacy_installer_unit there). An already-installed
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* never labels it legacy or implies it should be removed unless the user is
|
|
19
19
|
* explicitly migrating.
|
|
20
20
|
*
|
|
21
|
-
* This lives under `src/runtime
|
|
21
|
+
* This lives under `src/runtime/`, not `src/daemon/`, because both
|
|
22
22
|
* `src/daemon/service-commands.ts` (the CLI subcommand) and
|
|
23
23
|
* `src/runtime/legacy-daemon-reconcile.ts` (the boot-time reconcile) need it,
|
|
24
24
|
* and `src/runtime/**` is this repository's shared layer both can import.
|
|
@@ -37,11 +37,11 @@
|
|
|
37
37
|
* - ADOPT-OR-WARN, NEVER KILL. If the legacy unit file is simply absent but
|
|
38
38
|
* something is already listening on the configured host:port (this dev
|
|
39
39
|
* host's real case: a manually `nohup`'d daemon with no unit at all),
|
|
40
|
-
* that is an unidentified process, not a managed unit
|
|
40
|
+
* that is an unidentified process, not a managed unit, nothing to stop
|
|
41
41
|
* or disable, and this module never attempts to kill it.
|
|
42
42
|
* - Every action (legacy stop/disable, unit-file removal, daemon-reload)
|
|
43
43
|
* goes through the injectable `actionRunner`/`legacyUnitFileRemove` seams
|
|
44
|
-
* tests use
|
|
44
|
+
* tests use, no code path here bypasses them, so the migration is
|
|
45
45
|
* exercised deterministically via fakes and never touches a real running
|
|
46
46
|
* service in tests.
|
|
47
47
|
*/
|
|
@@ -56,7 +56,7 @@ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
|
56
56
|
import { runDaemonConfigMigration } from '../config/run-daemon-config-migration.ts';
|
|
57
57
|
import { GOODVIBES_DAEMON_SURFACE_ROOT } from '../config/surface.ts';
|
|
58
58
|
|
|
59
|
-
/** Structurally derived from `PlatformServiceManager`'s own constructor
|
|
59
|
+
/** Structurally derived from `PlatformServiceManager`'s own constructor, the
|
|
60
60
|
* SDK's public `platform/daemon` entry point only re-exports the class and
|
|
61
61
|
* `ManagedServiceStatus`, not the options/definition/action-runner interfaces
|
|
62
62
|
* by name, so we pull their shapes off the class itself rather than reaching
|
|
@@ -66,7 +66,7 @@ type ManagedServiceDefinition = NonNullable<ManagedServiceManagerOptions['defini
|
|
|
66
66
|
export type ManagedServiceActionRunner = NonNullable<ManagedServiceManagerOptions['actionRunner']>;
|
|
67
67
|
type ManagedServiceActionResult = ReturnType<ManagedServiceActionRunner>;
|
|
68
68
|
|
|
69
|
-
// The one unit name/description this tool manages
|
|
69
|
+
// The one unit name/description this tool manages, used by the daemon CLI
|
|
70
70
|
// (`goodvibes-daemon install-service|uninstall-service|service-status|migrate-service`).
|
|
71
71
|
// The terminal app's own onboarding UX builds the exact same service
|
|
72
72
|
// definition independently, so a migration triggered from either surface
|
|
@@ -80,7 +80,7 @@ export const MANAGED_SERVICE_DESCRIPTION = 'GoodVibes daemon (shared session bro
|
|
|
80
80
|
|
|
81
81
|
/**
|
|
82
82
|
* Follow-up: resolve the unit name the SDK's `PlatformServiceManager`
|
|
83
|
-
* would actually manage, from config alone
|
|
83
|
+
* would actually manage, from config alone, for callers that need the
|
|
84
84
|
* honest display name BEFORE any manager/status exists (the onboarding
|
|
85
85
|
* wizard's detection banner resolves this at snapshot-collection time and
|
|
86
86
|
* carries it on `OnboardingLegacyDaemonSnapshot.trackedServiceName`).
|
|
@@ -101,17 +101,17 @@ export function resolveConfiguredServiceName(config: { get(key: string): unknown
|
|
|
101
101
|
export interface BuildManagedDaemonServiceManagerParams {
|
|
102
102
|
readonly binaryPath: string;
|
|
103
103
|
/**
|
|
104
|
-
* The GoodVibes tree home
|
|
104
|
+
* The GoodVibes tree home, GOODVIBES_HOME-overridable, used to root the
|
|
105
105
|
* ConfigManager and the daemon's own `--daemon-home` state directory. NEVER
|
|
106
106
|
* used for unit-file path resolution: see `unitHomeDir` for that. A unit
|
|
107
107
|
* path search rooted here would look for `~/.config/systemd/user/` under
|
|
108
108
|
* whatever GOODVIBES_HOME points at instead of the real login home systemd
|
|
109
|
-
* actually reads
|
|
109
|
+
* actually reads, the same class of bug the boot-time reconcile in
|
|
110
110
|
* `src/daemon/cli.ts` already guards against with the identical split.
|
|
111
111
|
*/
|
|
112
112
|
readonly homeDir: string;
|
|
113
113
|
/**
|
|
114
|
-
* The LOGIN user's home
|
|
114
|
+
* The LOGIN user's home, where `~/.config/systemd/user/` (or the launchd/
|
|
115
115
|
* Windows equivalent) actually lives, regardless of any GOODVIBES_HOME/
|
|
116
116
|
* GOODVIBES_DAEMON_HOME override in effect. Threaded through to
|
|
117
117
|
* `PlatformServiceManager`'s own `homeDirectory` option, which resolves the
|
|
@@ -122,7 +122,7 @@ export interface BuildManagedDaemonServiceManagerParams {
|
|
|
122
122
|
readonly unitHomeDir: string;
|
|
123
123
|
readonly host: string;
|
|
124
124
|
readonly port: number;
|
|
125
|
-
/** Defaults to `homeDir
|
|
125
|
+
/** Defaults to `homeDir`, overridable so tests can scope both to one tempdir. */
|
|
126
126
|
readonly workingDirectory?: string | undefined;
|
|
127
127
|
/** Injected in tests; a real `ConfigManager` rooted at `homeDir` otherwise. */
|
|
128
128
|
readonly configManager?: ConfigManager | undefined;
|
|
@@ -131,12 +131,12 @@ export interface BuildManagedDaemonServiceManagerParams {
|
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
/**
|
|
134
|
-
* Build the ONE `PlatformServiceManager` this tool manages
|
|
134
|
+
* Build the ONE `PlatformServiceManager` this tool manages, the single
|
|
135
135
|
* source of truth, in this repository, for the unit's definition
|
|
136
136
|
* (`ExecStart` command/args, name, description). Both
|
|
137
137
|
* `src/daemon/service-commands.ts` (the CLI) and this daemon's own
|
|
138
138
|
* boot-time reconcile call this so every path in this repository installs
|
|
139
|
-
* the identical unit
|
|
139
|
+
* the identical unit, no risk of consumers drifting apart.
|
|
140
140
|
*/
|
|
141
141
|
export function buildManagedDaemonServiceManager(params: BuildManagedDaemonServiceManagerParams): PlatformServiceManager {
|
|
142
142
|
const workingDirectory = params.workingDirectory ?? params.homeDir;
|
|
@@ -152,7 +152,7 @@ export function buildManagedDaemonServiceManager(params: BuildManagedDaemonServi
|
|
|
152
152
|
// already configured for hostMode=network / a non-default port) keeps its
|
|
153
153
|
// endpoint without a unit rewrite. Baking endpoint values here is what
|
|
154
154
|
// silently re-pinned custom-configured hosts back to the values current at
|
|
155
|
-
// install time. scripts/install.sh writes the same shape
|
|
155
|
+
// install time. scripts/install.sh writes the same shape, the two paths
|
|
156
156
|
// must produce the identical running daemon (see the installer parity test).
|
|
157
157
|
// `params.host`/`params.port` remain inputs because the migration engine
|
|
158
158
|
// still needs them for its read-only port-liveness probe.
|
|
@@ -161,15 +161,15 @@ export function buildManagedDaemonServiceManager(params: BuildManagedDaemonServi
|
|
|
161
161
|
description: MANAGED_SERVICE_DESCRIPTION,
|
|
162
162
|
workingDirectory,
|
|
163
163
|
command: params.binaryPath,
|
|
164
|
-
// `--daemon-home` names the daemon's own STATE directory
|
|
165
|
-
// operator-tokens.json, auth-users.json and daemon-settings.json
|
|
164
|
+
// `--daemon-home` names the daemon's own STATE directory, the one holding
|
|
165
|
+
// operator-tokens.json, auth-users.json and daemon-settings.json, which is
|
|
166
166
|
// `<home>/.goodvibes/daemon`. This baked the USER HOME, so a serviced
|
|
167
167
|
// daemon filed its identity a level above where every reader in this
|
|
168
168
|
// repository looks: the SDK's platform/config goodvibes-home resolves the flag AS the state
|
|
169
169
|
// directory, cli/service-posture.ts already writes the state directory into
|
|
170
170
|
// GOODVIBES_DAEMON_HOME for the unit it installs, and runtime/bootstrap.ts
|
|
171
171
|
// reads the companion token from the state directory. On a normal machine
|
|
172
|
-
// the mismatch is invisible from the outside
|
|
172
|
+
// the mismatch is invisible from the outside, the daemon simply mints a
|
|
173
173
|
// second operator-tokens.json in the home directory and the client keeps
|
|
174
174
|
// reading the empty one under .goodvibes/daemon.
|
|
175
175
|
args: ['--daemon-home', join(params.homeDir, '.goodvibes', 'daemon')],
|
|
@@ -179,7 +179,7 @@ export function buildManagedDaemonServiceManager(params: BuildManagedDaemonServi
|
|
|
179
179
|
return new PlatformServiceManager(configManager, {
|
|
180
180
|
workingDirectory,
|
|
181
181
|
// Unit paths resolve from the LOGIN home, never the (possibly
|
|
182
|
-
// GOODVIBES_HOME-relocated) tree home above
|
|
182
|
+
// GOODVIBES_HOME-relocated) tree home above, see `unitHomeDir`'s doc.
|
|
183
183
|
homeDirectory: params.unitHomeDir,
|
|
184
184
|
definitionOverride: definition,
|
|
185
185
|
defaultServiceName: MANAGED_SERVICE_NAME,
|
|
@@ -250,7 +250,7 @@ export function legacyUnitPath(homeDir: string): string {
|
|
|
250
250
|
|
|
251
251
|
export interface DetectLegacyUnitInput {
|
|
252
252
|
/**
|
|
253
|
-
* The LOGIN user's home
|
|
253
|
+
* The LOGIN user's home, where the legacy unit file would actually live
|
|
254
254
|
* (`~/.config/systemd/user/goodvibes-daemon.service`), never the
|
|
255
255
|
* GOODVIBES_HOME-overridable tree home. See
|
|
256
256
|
* `BuildManagedDaemonServiceManagerParams.unitHomeDir` for the identical
|
|
@@ -266,7 +266,7 @@ export interface DetectLegacyUnitInput {
|
|
|
266
266
|
/**
|
|
267
267
|
* Read-only detection: does a legacy `goodvibes-daemon.service` unit file
|
|
268
268
|
* exist, and if so, is it currently active? Never stops, disables, or
|
|
269
|
-
* modifies anything
|
|
269
|
+
* modifies anything, a file-existence check plus a read-only
|
|
270
270
|
* `systemctl --user is-active` query through the injected actionRunner.
|
|
271
271
|
*/
|
|
272
272
|
export function detectLegacyUnit(input: DetectLegacyUnitInput): LegacyUnitInfo {
|
|
@@ -283,8 +283,8 @@ export function detectLegacyUnit(input: DetectLegacyUnitInput): LegacyUnitInfo {
|
|
|
283
283
|
/**
|
|
284
284
|
* The unit name `PlatformServiceManager` is ACTUALLY about to mutate can
|
|
285
285
|
* differ from `MANAGED_SERVICE_NAME` / `definitionOverride.name`. The SDK's
|
|
286
|
-
* internal `resolveServiceName()
|
|
287
|
-
* `status()` alike to compute the unit file PATH
|
|
286
|
+
* internal `resolveServiceName()`, used by `install()`, `uninstall()`, and
|
|
287
|
+
* `status()` alike to compute the unit file PATH, resolves from the
|
|
288
288
|
* `service.serviceName` CONFIG key first, falling back to the
|
|
289
289
|
* `defaultServiceName` this module passes only when that key is unset. It
|
|
290
290
|
* never consults `definitionOverride.name` for the path. So if a host's
|
|
@@ -313,11 +313,11 @@ export function resolveManagedUnitName(status: ManagedServiceStatus): string {
|
|
|
313
313
|
return basename(status.path).replace(/\.(service|plist)$/, '');
|
|
314
314
|
}
|
|
315
315
|
|
|
316
|
-
/** Honest one-line disclosure of the install-script unit's presence/state plus a manual migration hint
|
|
316
|
+
/** Honest one-line disclosure of the install-script unit's presence/state plus a manual migration hint, never auto-acted-on. */
|
|
317
317
|
export function legacyUnitNote(legacy: LegacyUnitInfo, trackedServiceName: string): string {
|
|
318
318
|
const stateWord = legacy.active ? 'installed and RUNNING' : 'installed (not currently active)';
|
|
319
319
|
return (
|
|
320
|
-
`note: a separate service named ${LEGACY_SERVICE_UNIT_NAME}.service is ${stateWord} at ${legacy.path}
|
|
320
|
+
`note: a separate service named ${LEGACY_SERVICE_UNIT_NAME}.service is ${stateWord} at ${legacy.path}; ` +
|
|
321
321
|
`that unit name was used by an older install script/release, while this tool manages ` +
|
|
322
322
|
`${trackedServiceName}.service and will not touch the other unit automatically. Keep whichever one you use; running ` +
|
|
323
323
|
`both would start two daemons competing for the same port. To retire the install-script unit in favor of this ` +
|
|
@@ -329,7 +329,7 @@ export function legacyUnitNote(legacy: LegacyUnitInfo, trackedServiceName: strin
|
|
|
329
329
|
* Hard ceiling on every systemctl invocation made through a DEFAULT action
|
|
330
330
|
* runner in this module. The reconcile below runs on the daemon's own startup
|
|
331
331
|
* path, and `spawnSync` without a timeout blocks the single JS event loop for
|
|
332
|
-
* as long as the child runs
|
|
332
|
+
* as long as the child runs, a wedged user D-Bus (a real incident class on
|
|
333
333
|
* this host) would freeze an already-listening daemon indefinitely. A timed-out
|
|
334
334
|
* call reports `status: null`, which every status check in this module treats
|
|
335
335
|
* as failure, so a wedge degrades to an honest refusal instead of a hang.
|
|
@@ -353,7 +353,7 @@ export function parseMainPid(result: { status?: number | null; stdout?: string |
|
|
|
353
353
|
* Read-only, best-effort TCP connect probe used ONLY by the legacy-absent
|
|
354
354
|
* branch to tell "nothing is listening on this port" apart from "an
|
|
355
355
|
* unmanaged process (e.g. a manual `nohup`) already owns it." Never used to
|
|
356
|
-
* identify or act on that process
|
|
356
|
+
* identify or act on that process, a positive result only produces a
|
|
357
357
|
* warning, never a kill. Tests always inject a fake `portProbe`; this default
|
|
358
358
|
* is never exercised against a real host in this repo's test suite.
|
|
359
359
|
*/
|
|
@@ -376,11 +376,11 @@ export function defaultPortProbe(host: string, port: number, timeoutMs = 750): P
|
|
|
376
376
|
export interface RunLegacyDaemonMigrationParams {
|
|
377
377
|
readonly host: string;
|
|
378
378
|
readonly port: number;
|
|
379
|
-
/** The unit name this tool manages (e.g. 'goodvibes')
|
|
379
|
+
/** The unit name this tool manages (e.g. 'goodvibes'), distinct from LEGACY_SERVICE_UNIT_NAME. */
|
|
380
380
|
readonly trackedServiceName: string;
|
|
381
381
|
/**
|
|
382
382
|
* Explicit consent to actually execute the migration. Without it, the
|
|
383
|
-
* result is a printed plan only
|
|
383
|
+
* result is a printed plan only, never auto-migrate.
|
|
384
384
|
*/
|
|
385
385
|
readonly confirmMigration?: boolean | undefined;
|
|
386
386
|
/** Injectable port-liveness check for the legacy-absent branch. Defaults to `defaultPortProbe`. */
|
|
@@ -396,7 +396,7 @@ export interface RunLegacyDaemonMigrationParams {
|
|
|
396
396
|
* legacy unit. Called immediately before the two mutation calls
|
|
397
397
|
* (`manager.install()`, and `manager.uninstall()` on the failed-health
|
|
398
398
|
* rollback path) that would otherwise write to or remove that path. This is
|
|
399
|
-
* an internal invariant check, not a normal user-facing error path
|
|
399
|
+
* an internal invariant check, not a normal user-facing error path, the
|
|
400
400
|
* pre-flight collision check in `runLegacyDaemonMigration` already returns
|
|
401
401
|
* before either call site is reached whenever this would trip, so tripping
|
|
402
402
|
* here means that earlier check regressed, not that the user did anything
|
|
@@ -406,7 +406,7 @@ function assertUnitIsNotLegacy(status: ManagedServiceStatus, legacy: LegacyUnitI
|
|
|
406
406
|
if (status.path === legacy.path || resolveManagedUnitName(status) === LEGACY_SERVICE_UNIT_NAME) {
|
|
407
407
|
throw new Error(
|
|
408
408
|
`refusing to ${action}: the resolved managed unit (${resolveManagedUnitName(status)} at ${status.path}) is the ` +
|
|
409
|
-
`install-script ${LEGACY_SERVICE_UNIT_NAME}.service unit
|
|
409
|
+
`install-script ${LEGACY_SERVICE_UNIT_NAME}.service unit; this should already have been caught by the pre-flight ` +
|
|
410
410
|
'collision check in runLegacyDaemonMigration',
|
|
411
411
|
);
|
|
412
412
|
}
|
|
@@ -431,7 +431,7 @@ export async function runLegacyDaemonMigration(
|
|
|
431
431
|
): Promise<LegacyDaemonMigrationResult> {
|
|
432
432
|
const { trackedServiceName } = params;
|
|
433
433
|
// Computed once, up front, and reused for every branch below (this is the
|
|
434
|
-
// exact same single call each branch made individually before
|
|
434
|
+
// exact same single call each branch made individually before, see the
|
|
435
435
|
// fix note on `resolveManagedUnitName` for why the name/path it reports
|
|
436
436
|
// can differ from `trackedServiceName`).
|
|
437
437
|
const currentStatus = manager.status();
|
|
@@ -448,9 +448,9 @@ export async function runLegacyDaemonMigration(
|
|
|
448
448
|
`migrate-service: no install-script ${LEGACY_SERVICE_UNIT_NAME}.service unit was found, but something is already ` +
|
|
449
449
|
`listening on ${params.host}:${params.port}.`,
|
|
450
450
|
"That looks like a process this tool doesn't manage (for example, a manually-started `nohup` daemon) rather " +
|
|
451
|
-
'than a systemd unit
|
|
451
|
+
'than a systemd unit; there is nothing here to stop or disable, and this tool will not attempt to kill an ' +
|
|
452
452
|
'unrecognized process.',
|
|
453
|
-
'Stop that process yourself, then re-run migrate-service or install-service once the port is free
|
|
453
|
+
'Stop that process yourself, then re-run migrate-service or install-service once the port is free; or, if ' +
|
|
454
454
|
"it's already the daemon you want running, leave it alone: a client surface can still reach it at this " +
|
|
455
455
|
"host:port directly, with no service unit required for this tool to manage.",
|
|
456
456
|
],
|
|
@@ -462,7 +462,7 @@ export async function runLegacyDaemonMigration(
|
|
|
462
462
|
exitCode: 0,
|
|
463
463
|
lines: [
|
|
464
464
|
`migrate-service: no install-script ${LEGACY_SERVICE_UNIT_NAME}.service unit was found and ${params.host}:${params.port} ` +
|
|
465
|
-
'is free
|
|
465
|
+
'is free; there is nothing to migrate.',
|
|
466
466
|
`Run install-service to set up the managed ${resolvedUnitName}.service directly.`,
|
|
467
467
|
],
|
|
468
468
|
status: currentStatus,
|
|
@@ -476,7 +476,7 @@ export async function runLegacyDaemonMigration(
|
|
|
476
476
|
lines: [
|
|
477
477
|
`migrate-service: this host's detected service platform is '${currentStatus.platform}', not systemd, but a ` +
|
|
478
478
|
`unit file with the install-script name exists at ${legacy.path}.`,
|
|
479
|
-
'That unit is systemd-specific and this tool only knows how to migrate a systemd unit today
|
|
479
|
+
'That unit is systemd-specific and this tool only knows how to migrate a systemd unit today; ' +
|
|
480
480
|
'nothing was changed.',
|
|
481
481
|
],
|
|
482
482
|
status: currentStatus,
|
|
@@ -486,7 +486,7 @@ export async function runLegacyDaemonMigration(
|
|
|
486
486
|
// Before any mutation, confirm the unit PlatformServiceManager is
|
|
487
487
|
// actually about to install/uninstall isn't the legacy unit itself. This
|
|
488
488
|
// happens when the host's `service.serviceName` config key is set to the
|
|
489
|
-
// legacy unit's own name
|
|
489
|
+
// legacy unit's own name, see `resolveManagedUnitName`'s doc comment for
|
|
490
490
|
// why the SDK resolves mutation paths from that config key rather than
|
|
491
491
|
// from the definition this engine passes. Without this check, `install()`
|
|
492
492
|
// below would overwrite the legacy unit file, a failed-health rollback
|
|
@@ -516,12 +516,12 @@ export async function runLegacyDaemonMigration(
|
|
|
516
516
|
exitCode: 0,
|
|
517
517
|
lines: [
|
|
518
518
|
legacyUnitNote(legacy, resolvedUnitName),
|
|
519
|
-
'migrate-service (dry run
|
|
519
|
+
'migrate-service (dry run, re-run with confirmation to execute): this would',
|
|
520
520
|
` 1. install and start the new ${resolvedUnitName}.service unit`,
|
|
521
521
|
' 2. verify it comes up healthy (a fresh, honest systemd is-active check)',
|
|
522
522
|
` 3. only if that succeeds, stop, disable, and remove the install-script ${LEGACY_SERVICE_UNIT_NAME}.service unit ` +
|
|
523
523
|
'and run `systemctl --user daemon-reload`',
|
|
524
|
-
'Nothing has been changed. Nothing is migrated automatically
|
|
524
|
+
'Nothing has been changed. Nothing is migrated automatically; re-run with explicit confirmation ' +
|
|
525
525
|
"(the CLI's -y/--yes flag) to execute this plan.",
|
|
526
526
|
],
|
|
527
527
|
status: currentStatus,
|
|
@@ -531,7 +531,7 @@ export async function runLegacyDaemonMigration(
|
|
|
531
531
|
// Consented: new-up-then-old-down. The legacy unit is not touched until the
|
|
532
532
|
// new unit is verified healthy.
|
|
533
533
|
// Belt-and-braces: the collision check above already returns before
|
|
534
|
-
// reaching here whenever the resolved unit is the legacy one
|
|
534
|
+
// reaching here whenever the resolved unit is the legacy one, this
|
|
535
535
|
// re-asserts the same invariant right at the mutation site so a future
|
|
536
536
|
// change to the check above can never silently reopen the hole.
|
|
537
537
|
assertUnitIsNotLegacy(currentStatus, legacy, 'install the new unit');
|
|
@@ -554,7 +554,7 @@ export async function runLegacyDaemonMigration(
|
|
|
554
554
|
assertUnitIsNotLegacy(installed, legacy, 'roll back (uninstall) the new unit');
|
|
555
555
|
const rollback = manager.uninstall();
|
|
556
556
|
const rollbackNote = rollback.actionError
|
|
557
|
-
? `rolling back the new unit ALSO hit an error (${rollback.actionError})
|
|
557
|
+
? `rolling back the new unit ALSO hit an error (${rollback.actionError}); remove ${installed.path} by hand.`
|
|
558
558
|
: 'the newly-written unit has been rolled back (removed).';
|
|
559
559
|
return {
|
|
560
560
|
ok: false,
|
|
@@ -569,7 +569,7 @@ export async function runLegacyDaemonMigration(
|
|
|
569
569
|
};
|
|
570
570
|
}
|
|
571
571
|
|
|
572
|
-
// New unit verified healthy
|
|
572
|
+
// New unit verified healthy, now, and only now, retire the legacy unit.
|
|
573
573
|
const run: ManagedServiceActionRunner = params.actionRunner ?? defaultActionRunner(SYSTEMCTL_TIMEOUT_MS);
|
|
574
574
|
const stopResult = run('systemctl', ['--user', 'stop', `${LEGACY_SERVICE_UNIT_NAME}.service`]);
|
|
575
575
|
const disableResult = run('systemctl', ['--user', 'disable', `${LEGACY_SERVICE_UNIT_NAME}.service`]);
|
|
@@ -596,7 +596,7 @@ export async function runLegacyDaemonMigration(
|
|
|
596
596
|
);
|
|
597
597
|
}
|
|
598
598
|
if (removeError) {
|
|
599
|
-
lines.push(`note: could not remove the install-script unit file at ${legacy.path}: ${removeError}
|
|
599
|
+
lines.push(`note: could not remove the install-script unit file at ${legacy.path}: ${removeError}; remove it by hand.`);
|
|
600
600
|
} else {
|
|
601
601
|
lines.push(`the install-script ${LEGACY_SERVICE_UNIT_NAME}.service unit has been stopped, disabled, and removed.`);
|
|
602
602
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The unattended startup reconcile for the redundant install-script
|
|
3
|
-
* `goodvibes-daemon.service` unit
|
|
3
|
+
* `goodvibes-daemon.service` unit, split out of `legacy-daemon-migration.ts`
|
|
4
4
|
* (which owns the shared unit definition, detection, and the CONSENTED
|
|
5
5
|
* `migrate-service` engine) so each module stays within the architecture
|
|
6
6
|
* gate's file-size cap. Same layer (`src/runtime/`), same injectable-seam
|
|
@@ -24,8 +24,8 @@ type ManagedServiceActionResult = ReturnType<ManagedServiceActionRunner>;
|
|
|
24
24
|
/**
|
|
25
25
|
* The exact marker string scripts/install.sh writes into every unit it
|
|
26
26
|
* creates (as a `# managed by goodvibes install.sh` comment). The reconcile
|
|
27
|
-
* check below keys on it to tell an installer-created legacy unit
|
|
28
|
-
* auto-retire
|
|
27
|
+
* check below keys on it to tell an installer-created legacy unit, safe to
|
|
28
|
+
* auto-retire, apart from a hand-written one that must only ever be reported.
|
|
29
29
|
* Kept in lockstep with `INSTALLER_MARKER` in scripts/install.sh.
|
|
30
30
|
*/
|
|
31
31
|
export const INSTALLER_UNIT_MARKER = 'managed by goodvibes install.sh';
|
|
@@ -35,7 +35,7 @@ export const INSTALLER_UNIT_MARKER = 'managed by goodvibes install.sh';
|
|
|
35
35
|
/**
|
|
36
36
|
* CUMULATIVE ceiling on the reconcile's whole boot-path pass. Per-call
|
|
37
37
|
* timeouts alone still allow ~5 sequential slow-but-completing calls to stack
|
|
38
|
-
* up (a degraded
|
|
38
|
+
* up (a degraded, not wedged, user bus), blocking the already-listening
|
|
39
39
|
* daemon's event loop for tens of seconds. One overall deadline covers the
|
|
40
40
|
* pass: once it is exceeded, every remaining call is skipped (reported as
|
|
41
41
|
* `status: null`, which the guards treat as unknown → refusal) and the result
|
|
@@ -48,7 +48,7 @@ export const RECONCILE_DEADLINE_MS = 8_000;
|
|
|
48
48
|
/**
|
|
49
49
|
* Wrap a runner with the reconcile's cumulative deadline: calls after the
|
|
50
50
|
* deadline are skipped outright; calls near it get only the remaining budget
|
|
51
|
-
* as their per-call timeout (default runner only
|
|
51
|
+
* as their per-call timeout (default runner only, injected runners are test
|
|
52
52
|
* fakes that answer instantly).
|
|
53
53
|
*/
|
|
54
54
|
function makeDeadlineBoundRunner(
|
|
@@ -62,7 +62,7 @@ function makeDeadlineBoundRunner(
|
|
|
62
62
|
const remaining = deadlineMs - (Date.now() - startedAt);
|
|
63
63
|
if (remaining <= 0) {
|
|
64
64
|
hit = true;
|
|
65
|
-
return { status: null, stdout: '', stderr: 'reconcile time budget exceeded
|
|
65
|
+
return { status: null, stdout: '', stderr: 'reconcile time budget exceeded; call skipped' } as ManagedServiceActionResult;
|
|
66
66
|
}
|
|
67
67
|
if (injected) return injected(command, args);
|
|
68
68
|
return defaultActionRunner(Math.min(perCallTimeoutMs, remaining))(command, args);
|
|
@@ -167,7 +167,7 @@ function defaultReadOwnCgroup(): string {
|
|
|
167
167
|
* This runs on daemon startup (no user invocation needed) and, guard-railed:
|
|
168
168
|
* - Only acts when the canonical unit is CONFIRMED serving: `is-active`
|
|
169
169
|
* reports active AND its MainPID resolves to a live process. `is-active`
|
|
170
|
-
* alone is not proof
|
|
170
|
+
* alone is not proof, a Type=simple unit reports active from fork onward,
|
|
171
171
|
* including the pre-bind window of a daemon that is about to crash-loop.
|
|
172
172
|
* - NEVER acts from inside the legacy unit itself: if this process IS the
|
|
173
173
|
* legacy unit's MainPID, or /proc/self/cgroup names the legacy unit,
|
|
@@ -175,20 +175,20 @@ function defaultReadOwnCgroup(): string {
|
|
|
175
175
|
* code (mid-boot, from inside a blocking spawnSync). Refuses instead.
|
|
176
176
|
* - NEVER stops a RUNNING legacy daemon: if the legacy unit's MainPID is a
|
|
177
177
|
* live process, it may be the daemon serving the endpoint clients are
|
|
178
|
-
* configured to reach (the wrong-port two-daemon state)
|
|
178
|
+
* configured to reach (the wrong-port two-daemon state), the unattended
|
|
179
179
|
* path only retires enabled-but-NOT-running legacy units; a running one
|
|
180
180
|
* gets a notice pointing at the consented migrate-service.
|
|
181
181
|
* - Requires the CONFIGURED endpoint (the one clients resolve from
|
|
182
|
-
* settings.json, no runtime overrides) to be answering before retiring
|
|
182
|
+
* settings.json, no runtime overrides) to be answering before retiring,
|
|
183
183
|
* a canonical daemon alive on some other port proves nothing about the
|
|
184
184
|
* endpoint clients use.
|
|
185
185
|
* - AUTO-RETIRES only an installer-MARKER-managed legacy unit, and only
|
|
186
186
|
* removes its unit file AFTER `disable --now` reports success. A NONZERO
|
|
187
187
|
* exit leaves everything in place and says so; a TIMED-OUT disable
|
|
188
|
-
* (status null) has an UNKNOWN outcome
|
|
188
|
+
* (status null) has an UNKNOWN outcome, the enablement state is
|
|
189
189
|
* re-inspected before anything is printed, and the receipt states what
|
|
190
190
|
* was actually confirmed, never a blanket claim.
|
|
191
|
-
* - A hand-written legacy unit (no marker) is never touched
|
|
191
|
+
* - A hand-written legacy unit (no marker) is never touched, a one-line
|
|
192
192
|
* actionable notice. An UNREADABLE unit file is reported as unreadable,
|
|
193
193
|
* never misdiagnosed as hand-written.
|
|
194
194
|
* - The whole pass shares ONE cumulative time budget (RECONCILE_DEADLINE_MS)
|
|
@@ -214,11 +214,11 @@ export async function reconcileRedundantLegacyUnit(
|
|
|
214
214
|
const legacyUnit = `${LEGACY_SERVICE_UNIT_NAME}.service`;
|
|
215
215
|
const deadlineNote = (): string[] =>
|
|
216
216
|
deadlineHit()
|
|
217
|
-
? ['note: the reconcile hit its overall time budget
|
|
217
|
+
? ['note: the reconcile hit its overall time budget: remaining checks were skipped; it will re-run at the next daemon start.']
|
|
218
218
|
: [];
|
|
219
219
|
|
|
220
220
|
// Guard 1: the canonical unit must report active. (A timed-out, skipped, or
|
|
221
|
-
// failed probe
|
|
221
|
+
// failed probe, e.g. a wedged user bus, lands here too and refuses.)
|
|
222
222
|
const probe = run(systemctl, ['--user', 'is-active', canonicalUnit]);
|
|
223
223
|
const canonicalActive = (probe.status ?? 1) === 0 && (probe.stdout ?? '').trim() === 'active';
|
|
224
224
|
if (!canonicalActive) {
|
|
@@ -226,7 +226,7 @@ export async function reconcileRedundantLegacyUnit(
|
|
|
226
226
|
action: 'noop',
|
|
227
227
|
reason: 'canonical-not-active',
|
|
228
228
|
lines: [
|
|
229
|
-
`legacy-unit reconcile: a ${legacyUnit} unit file exists at ${path} but ${canonicalUnit} is not confirmably active
|
|
229
|
+
`legacy-unit reconcile: a ${legacyUnit} unit file exists at ${path} but ${canonicalUnit} is not confirmably active; ` +
|
|
230
230
|
'left untouched (it may be the only daemon).',
|
|
231
231
|
...deadlineNote(),
|
|
232
232
|
],
|
|
@@ -243,7 +243,7 @@ export async function reconcileRedundantLegacyUnit(
|
|
|
243
243
|
action: 'noop',
|
|
244
244
|
reason: 'canonical-mainpid-not-alive',
|
|
245
245
|
lines: [
|
|
246
|
-
`legacy-unit reconcile: ${canonicalUnit} reports active but its MainPID could not be confirmed alive
|
|
246
|
+
`legacy-unit reconcile: ${canonicalUnit} reports active but its MainPID could not be confirmed alive; ` +
|
|
247
247
|
`left the ${legacyUnit} unit untouched.`,
|
|
248
248
|
...deadlineNote(),
|
|
249
249
|
],
|
|
@@ -256,7 +256,7 @@ export async function reconcileRedundantLegacyUnit(
|
|
|
256
256
|
//
|
|
257
257
|
// The legacy MainPID probe is TRI-STATE, exactly like guards 1-2: a reply
|
|
258
258
|
// whose status is non-zero or null (bus flap, timeout, skipped-by-deadline)
|
|
259
|
-
// does NOT mean "MainPID=0, unit affirmatively stopped"
|
|
259
|
+
// does NOT mean "MainPID=0, unit affirmatively stopped", reading it that
|
|
260
260
|
// way let one transient systemctl failure on exactly this call authorize
|
|
261
261
|
// `disable --now` against a LIVE legacy daemon (and guard 5 cannot catch
|
|
262
262
|
// it: in the wrong-port state the legacy daemon itself answers the
|
|
@@ -271,7 +271,7 @@ export async function reconcileRedundantLegacyUnit(
|
|
|
271
271
|
action: 'noop',
|
|
272
272
|
reason: 'self-supervised-by-legacy',
|
|
273
273
|
lines: [
|
|
274
|
-
`legacy-unit reconcile: this daemon appears to be running UNDER ${legacyUnit} itself
|
|
274
|
+
`legacy-unit reconcile: this daemon appears to be running UNDER ${legacyUnit} itself; refusing to disable ` +
|
|
275
275
|
`the unit supervising the current process. Migrate from the canonical side instead: goodvibes-daemon migrate-service`,
|
|
276
276
|
],
|
|
277
277
|
};
|
|
@@ -282,14 +282,14 @@ export async function reconcileRedundantLegacyUnit(
|
|
|
282
282
|
reason: 'legacy-state-unknown',
|
|
283
283
|
lines: [
|
|
284
284
|
`legacy-unit reconcile: could not determine whether ${legacyUnit} has a running daemon (the MainPID query ` +
|
|
285
|
-
'failed or timed out)
|
|
285
|
+
'failed or timed out), refusing to act on a guess; it will be re-checked at the next daemon start.',
|
|
286
286
|
...deadlineNote(),
|
|
287
287
|
],
|
|
288
288
|
};
|
|
289
289
|
}
|
|
290
290
|
|
|
291
291
|
// Guard 4: never stop a RUNNING legacy daemon from the unattended path. A
|
|
292
|
-
// live legacy MainPID means a second daemon is actually serving something
|
|
292
|
+
// live legacy MainPID means a second daemon is actually serving something,
|
|
293
293
|
// possibly the endpoint clients resolve from settings.json (the wrong-port
|
|
294
294
|
// two-daemon state). Retiring an enabled-but-idle unit needs no --now kill;
|
|
295
295
|
// stopping a serving one needs consent. (A KNOWN reply of MainPID=0 is the
|
|
@@ -299,7 +299,7 @@ export async function reconcileRedundantLegacyUnit(
|
|
|
299
299
|
action: 'noop',
|
|
300
300
|
reason: 'legacy-running',
|
|
301
301
|
lines: [
|
|
302
|
-
`legacy-unit reconcile: ${legacyUnit} has a live main process (pid ${legacyPid})
|
|
302
|
+
`legacy-unit reconcile: ${legacyUnit} has a live main process (pid ${legacyPid}); a second daemon is ` +
|
|
303
303
|
'actually running, and it may be the one serving the endpoint your clients are configured to reach. ' +
|
|
304
304
|
'Refusing to stop it unattended; migrate deliberately with: goodvibes-daemon migrate-service',
|
|
305
305
|
],
|
|
@@ -308,7 +308,7 @@ export async function reconcileRedundantLegacyUnit(
|
|
|
308
308
|
|
|
309
309
|
// Guard 5: the endpoint clients resolve from settings.json must be answered
|
|
310
310
|
// before anything is retired. The canonical daemon being alive proves only
|
|
311
|
-
// that A daemon runs
|
|
311
|
+
// that A daemon runs, not that the configured endpoint is served (its unit
|
|
312
312
|
// may pin different launch args).
|
|
313
313
|
if (input.configuredEndpoint) {
|
|
314
314
|
const endpointProbe = input.endpointProbe ?? defaultPortProbe;
|
|
@@ -319,7 +319,7 @@ export async function reconcileRedundantLegacyUnit(
|
|
|
319
319
|
reason: 'configured-endpoint-unserved',
|
|
320
320
|
lines: [
|
|
321
321
|
`legacy-unit reconcile: nothing is answering on the CONFIGURED endpoint ` +
|
|
322
|
-
`${input.configuredEndpoint.host}:${input.configuredEndpoint.port}
|
|
322
|
+
`${input.configuredEndpoint.host}:${input.configuredEndpoint.port}; the canonical daemon is alive but not ` +
|
|
323
323
|
`provably serving what clients resolve from settings. Left the ${legacyUnit} unit untouched; check the ` +
|
|
324
324
|
"canonical unit's launch arguments against the controlPlane settings.",
|
|
325
325
|
],
|
|
@@ -345,7 +345,7 @@ export async function reconcileRedundantLegacyUnit(
|
|
|
345
345
|
reason: 'marker-unreadable',
|
|
346
346
|
lines: [
|
|
347
347
|
`note: ${canonicalUnit} is active and a separate ${legacyUnit} exists at ${path}, but its unit file could not ` +
|
|
348
|
-
`be read (${readError})
|
|
348
|
+
`be read (${readError}). Left untouched. Inspect it yourself; if it is redundant, retire it with: ` +
|
|
349
349
|
`systemctl --user disable --now ${legacyUnit} && rm ${path} && systemctl --user daemon-reload`,
|
|
350
350
|
],
|
|
351
351
|
};
|
|
@@ -365,14 +365,14 @@ export async function reconcileRedundantLegacyUnit(
|
|
|
365
365
|
}
|
|
366
366
|
|
|
367
367
|
// Installer-marker-managed and redundant: disable first, and only remove the
|
|
368
|
-
// unit file if the disable actually succeeded
|
|
368
|
+
// unit file if the disable actually succeeded, otherwise the enablement
|
|
369
369
|
// symlink dangles at a deleted file and this tool can never repair it (the
|
|
370
370
|
// next pass no-ops at the file-exists check).
|
|
371
371
|
const disableResult = run(systemctl, ['--user', 'disable', '--now', legacyUnit]);
|
|
372
372
|
const disableStatus = disableResult.status ?? 1;
|
|
373
373
|
if (disableStatus !== 0) {
|
|
374
374
|
if (disableResult.status === null) {
|
|
375
|
-
// TIMED OUT: the outcome is UNKNOWN, not failed
|
|
375
|
+
// TIMED OUT: the outcome is UNKNOWN, not failed, `disable --now`
|
|
376
376
|
// removes the enablement symlinks synchronously BEFORE waiting on the
|
|
377
377
|
// stop job, so the disable may well have taken effect even though the
|
|
378
378
|
// client was killed. Re-inspect instead of printing a blanket denial.
|
|
@@ -393,11 +393,11 @@ export async function reconcileRedundantLegacyUnit(
|
|
|
393
393
|
run(systemctl, ['--user', 'daemon-reload']);
|
|
394
394
|
const lines = [
|
|
395
395
|
`reconciled: the disable command timed out waiting on the stop job, but re-inspection confirms the ` +
|
|
396
|
-
`installer-managed ${legacyUnit} is no longer enabled${removeErrorAfterTimeout ? '' :
|
|
396
|
+
`installer-managed ${legacyUnit} is no longer enabled${removeErrorAfterTimeout ? '' : `, its unit file was removed (${path})`}. ` +
|
|
397
397
|
'Its stop may still be completing.',
|
|
398
398
|
];
|
|
399
399
|
if (removeErrorAfterTimeout) {
|
|
400
|
-
lines.push(`note: could not remove ${path}: ${removeErrorAfterTimeout}
|
|
400
|
+
lines.push(`note: could not remove ${path}: ${removeErrorAfterTimeout}; remove it by hand.`);
|
|
401
401
|
}
|
|
402
402
|
lines.push(...deadlineNote());
|
|
403
403
|
return { action: 'removed', reason: 'retired', lines };
|
|
@@ -406,7 +406,7 @@ export async function reconcileRedundantLegacyUnit(
|
|
|
406
406
|
action: 'failed',
|
|
407
407
|
reason: 'disable-timeout',
|
|
408
408
|
lines: [
|
|
409
|
-
`legacy-unit reconcile: the disable of ${legacyUnit} timed out and its outcome is UNKNOWN
|
|
409
|
+
`legacy-unit reconcile: the disable of ${legacyUnit} timed out and its outcome is UNKNOWN; the enablement ` +
|
|
410
410
|
`state could not be re-confirmed. This tool removed nothing; the unit file at ${path} was left in place ` +
|
|
411
411
|
'and will be re-checked at the next daemon start.',
|
|
412
412
|
`Verify it yourself: systemctl --user is-enabled ${legacyUnit}`,
|
|
@@ -420,7 +420,7 @@ export async function reconcileRedundantLegacyUnit(
|
|
|
420
420
|
reason: 'disable-failed',
|
|
421
421
|
lines: [
|
|
422
422
|
`legacy-unit reconcile: the disable of the redundant installer-managed ${legacyUnit} reported failure ` +
|
|
423
|
-
`(${detail})
|
|
423
|
+
`(${detail}); this tool removed nothing; the unit file at ${path} was left in place.`,
|
|
424
424
|
`Verify its state and retire it yourself: systemctl --user is-enabled ${legacyUnit} ; ` +
|
|
425
425
|
`systemctl --user disable --now ${legacyUnit} && rm ${path} && systemctl --user daemon-reload`,
|
|
426
426
|
],
|
|
@@ -441,7 +441,7 @@ export async function reconcileRedundantLegacyUnit(
|
|
|
441
441
|
`was disabled${removeError ? '' : ` and removed (${path})`}.`,
|
|
442
442
|
];
|
|
443
443
|
if (removeError) {
|
|
444
|
-
lines.push(`note: could not remove ${path}: ${removeError}
|
|
444
|
+
lines.push(`note: could not remove ${path}: ${removeError}; remove it by hand.`);
|
|
445
445
|
}
|
|
446
446
|
lines.push(...deadlineNote());
|
|
447
447
|
return { action: 'removed', reason: 'retired', lines };
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* The deps that let the platform register this daemon's `email.*` verbs.
|
|
5
5
|
*
|
|
6
6
|
* `calendar.*` and `email.*` are served by the platform now, not by handlers in
|
|
7
|
-
* this repository
|
|
7
|
+
* this repository, those were deleted when the platform gained an
|
|
8
8
|
* implementation the daemon could call. What is left for a product to supply is
|
|
9
9
|
* the wiring, and it is not optional: without `homeDirectory` the calendar
|
|
10
10
|
* composition returns null, without these deps the mail one does, and either way
|
|
@@ -27,7 +27,7 @@ import { nodeEmailTransport } from '@pellux/goodvibes-sdk/platform/email/node';
|
|
|
27
27
|
|
|
28
28
|
/** The narrow slices this composition needs; the real managers satisfy them. */
|
|
29
29
|
interface MailCompositionInput {
|
|
30
|
-
readonly configManager: { get(key:
|
|
30
|
+
readonly configManager: { get(key: string): unknown };
|
|
31
31
|
readonly secretsManager: { get(key: string): Promise<string | null> };
|
|
32
32
|
}
|
|
33
33
|
|
|
@@ -36,8 +36,8 @@ interface MailCompositionInput {
|
|
|
36
36
|
* `registerGatewayVerbGroups`.
|
|
37
37
|
*
|
|
38
38
|
* The settings come from the daemon's own `surfaces.email.*` keys through
|
|
39
|
-
* `withSurfaceEmailConfig`, so the keys an operator has already set
|
|
40
|
-
* the settings modal now shows
|
|
39
|
+
* `withSurfaceEmailConfig`, so the keys an operator has already set, and that
|
|
40
|
+
* the settings modal now shows, keep working unchanged, and a not-configured
|
|
41
41
|
* answer names the keys THIS operator actually has rather than the ones the
|
|
42
42
|
* service validates internally.
|
|
43
43
|
*/
|
|
@@ -45,7 +45,7 @@ export function composeMailDeps(input: MailCompositionInput): {
|
|
|
45
45
|
readonly emailServiceDeps: EmailServiceDeps;
|
|
46
46
|
readonly describeEmailConfigProblem: () => Promise<SurfaceEmailConfigProblem | null>;
|
|
47
47
|
} {
|
|
48
|
-
const getConfig = (key: string): unknown => input.configManager.get(key
|
|
48
|
+
const getConfig = (key: string): unknown => input.configManager.get(key);
|
|
49
49
|
const emailServiceDeps = withSurfaceEmailConfig({
|
|
50
50
|
getConfig,
|
|
51
51
|
secretsManager: input.secretsManager,
|
|
@@ -56,7 +56,7 @@ export function composeMailDeps(input: MailCompositionInput): {
|
|
|
56
56
|
// raises display confidence only, and commandAuthority is the literal
|
|
57
57
|
// 'none'.
|
|
58
58
|
describeSenderClaim: describeSenderClaimNeutrally,
|
|
59
|
-
}
|
|
59
|
+
});
|
|
60
60
|
return {
|
|
61
61
|
emailServiceDeps,
|
|
62
62
|
describeEmailConfigProblem: () =>
|