@securecoms/agent-bridge 0.30.0 → 0.30.2
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/README.md +77 -6
- package/dist/bin.d.ts +101 -0
- package/dist/bin.js +1102 -161
- package/dist/bridge-agent.d.ts +60 -0
- package/dist/config.d.ts +13 -0
- package/dist/errors.d.ts +1 -1
- package/dist/index.js +335 -27
- package/dist/preflight-upgrade.d.cts +0 -2
- package/dist/preflight.cjs +51 -49
- package/dist/preflight.d.cts +14 -0
- package/dist/service/credential-arg.d.ts +23 -0
- package/dist/service/install-prep.d.ts +21 -1
- package/dist/service/install.d.ts +7 -0
- package/dist/service/instance-lock.d.ts +88 -0
- package/dist/service/launchd.d.ts +1 -1
- package/dist/service/resolve-node.d.ts +1 -0
- package/dist/service/status.d.ts +3 -5
- package/dist/service/systemd.d.ts +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/ws-server.d.ts +4 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -35,13 +35,9 @@ The server still sees ciphertext only. The trust boundary is the machine running
|
|
|
35
35
|
- **Node.js 24 LTS or newer.** The daemon requires >= 24 (node:sqlite stable API).
|
|
36
36
|
- A valid SecureComs agent credential (`agent.json`, `chmod 600`)
|
|
37
37
|
|
|
38
|
-
You don't have to check your Node version yourself. The installer
|
|
38
|
+
You don't have to check your Node version yourself. The installer checks it before anything else runs. On an older Node it says so plainly, and on macOS it offers to install Node 24 from nodejs.org after asking you. That step needs your computer's administrator password. It checks the download's published SHA-256 checksum before running it.
|
|
39
39
|
|
|
40
|
-
To install Node 24
|
|
41
|
-
|
|
42
|
-
1. Official installer (recommended): download the Node 24 LTS installer from [nodejs.org](https://nodejs.org) and run it.
|
|
43
|
-
2. Homebrew (macOS): `brew install node`
|
|
44
|
-
3. nvm: `nvm install 24 && nvm use 24`
|
|
40
|
+
To install Node 24 yourself, download the Node 24 LTS installer from [nodejs.org](https://nodejs.org) and run it. Then run the install command again.
|
|
45
41
|
|
|
46
42
|
Always re-run the install command after upgrading Node — the background service pins the Node path it was installed with.
|
|
47
43
|
|
|
@@ -935,6 +931,81 @@ copied after the owner's next typed message.
|
|
|
935
931
|
|
|
936
932
|
---
|
|
937
933
|
|
|
934
|
+
## Release notes: 0.30.2
|
|
935
|
+
|
|
936
|
+
<!-- [COPY, approved by Eric 2026-10-15] -->
|
|
937
|
+
|
|
938
|
+
0.30.2 fixes the guided Node upgrade, makes reinstall safe while the agent is
|
|
939
|
+
running, and prints plain messages when the agent cannot start.
|
|
940
|
+
|
|
941
|
+
- **The Node upgrade works again on macOS.** When this computer has an older
|
|
942
|
+
Node, install offers to install Node 24 from nodejs.org. Before 0.30.2 this
|
|
943
|
+
step could not find the installer and stopped without saying why. It now
|
|
944
|
+
finds it, checks its published SHA-256 checksum, and asks first:
|
|
945
|
+
`SecureComs needs Node 24. Install it now from nodejs.org? This needs your
|
|
946
|
+
computer's administrator password. [y/N]`. If the step fails after you say
|
|
947
|
+
yes, it prints: `Couldn't install Node 24 from nodejs.org.`
|
|
948
|
+
- **Homebrew is no longer used.** The guided upgrade always uses the
|
|
949
|
+
nodejs.org installer.
|
|
950
|
+
- **Reinstall while the agent runs.** Install now waits up to 10 seconds for
|
|
951
|
+
the running Connector to stop before it starts the new one. Before, the new
|
|
952
|
+
copy could start too early, find the old copy still running, and stay
|
|
953
|
+
stopped. If the old copy does not stop in time, install starts nothing and
|
|
954
|
+
tells you to run install again.
|
|
955
|
+
- **Plain messages when the agent cannot start.** A missing, unreadable or
|
|
956
|
+
invalid key file, or a bad setting in it, now each print one short line
|
|
957
|
+
that says what to do. File paths are printed only in a terminal, not in the
|
|
958
|
+
service log.
|
|
959
|
+
- **No key file found.** If the copied install command finds no key file, it
|
|
960
|
+
prints: `No key file was found in your Downloads folder. Download the key
|
|
961
|
+
file again from SecureComs, or add --credential <path>.` An empty
|
|
962
|
+
`--credential` no longer falls back to the `SECURECOMS_CREDENTIAL`
|
|
963
|
+
environment variable.
|
|
964
|
+
- **Node 24 or newer.** The package now declares `node >= 24`, which the
|
|
965
|
+
Connector already required to run.
|
|
966
|
+
|
|
967
|
+
You do not need to change anything after the upgrade. Run the install command
|
|
968
|
+
again only if you want the new install behavior now.
|
|
969
|
+
|
|
970
|
+
---
|
|
971
|
+
|
|
972
|
+
## Release notes: 0.30.1
|
|
973
|
+
|
|
974
|
+
<!-- [COPY, approved by Eric 2026-10-14] -->
|
|
975
|
+
|
|
976
|
+
0.30.1 makes sure only one copy of an agent runs, and makes install say
|
|
977
|
+
clearly when the agent did not start.
|
|
978
|
+
|
|
979
|
+
- **Reinstall once after you upgrade.** Run the install command again for
|
|
980
|
+
each agent. It writes a new service file. The old file restarts the
|
|
981
|
+
Connector every time it stops, so a refused second copy would keep
|
|
982
|
+
restarting and print the "already running" message every 10 seconds.
|
|
983
|
+
- **One copy per agent on a computer.** A second copy of the same agent
|
|
984
|
+
stops before it connects and prints: `This agent is already running on this
|
|
985
|
+
computer (process <pid>). Stop that copy first, or run: agent-bridge status`
|
|
986
|
+
- **Port already in use.** If another program holds the Connector's port, the
|
|
987
|
+
agent does not start and prints: `Port <n> is already in use by another
|
|
988
|
+
program on this computer, so the agent did not start.`
|
|
989
|
+
- **Install checks that the agent really started.** Install now waits up to
|
|
990
|
+
75 seconds for the Connector to answer its health check. If it does not
|
|
991
|
+
answer, install fails and names the service log.
|
|
992
|
+
- **Install refuses a second install of the same agent.** If the agent is
|
|
993
|
+
already installed in another folder or with a different key, install stops
|
|
994
|
+
before it changes anything and prints: `This agent is already installed in
|
|
995
|
+
<dir> with a different key. Uninstall it first: agent-bridge uninstall
|
|
996
|
+
--label <label>`. A downloaded copy such as `margo (1).json` installs as the
|
|
997
|
+
same agent, not as a new one.
|
|
998
|
+
- **The service restarts only after a failure.** On macOS the new service
|
|
999
|
+
file restarts the Connector only when it exits with an error. On Linux the
|
|
1000
|
+
unit uses `Restart=on-failure`.
|
|
1001
|
+
- **Prepared for one active connection per agent.** When the server reports
|
|
1002
|
+
that the agent connected from another place, this copy stops all network
|
|
1003
|
+
traffic and prints: `This agent connected from another place, so this copy
|
|
1004
|
+
stopped. Stop the other copy, then restart this one.` It stays stopped and
|
|
1005
|
+
does not restart itself. The server side of this check is not turned on yet.
|
|
1006
|
+
|
|
1007
|
+
---
|
|
1008
|
+
|
|
938
1009
|
## Release notes: 0.30.0
|
|
939
1010
|
|
|
940
1011
|
<!-- [COPY, approved by Eric 2026-10-12] -->
|
package/dist/bin.d.ts
CHANGED
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* SecureComs Agent Bridge — HTTP daemon entry point.
|
|
4
|
+
* Long-running diagnostics go to stderr through the content-free service
|
|
5
|
+
* logger. The local bearer token is persisted to the `bridge-token` file (see
|
|
6
|
+
* tokenFile below); startup emits only a fixed token-loaded event, never the
|
|
7
|
+
* token, its fingerprint, or its path. This prevents the former
|
|
8
|
+
* `BRIDGE_TOKEN=<value>` leak into journald/launchd logs.
|
|
9
|
+
*/
|
|
10
|
+
import { type BridgeConfig, type ConfigErrorReason } from "./config.js";
|
|
2
11
|
import { BridgeAgent } from "./bridge-agent.js";
|
|
3
12
|
import { createHttpServer } from "./http-server.js";
|
|
4
13
|
import { attachAgentWebSocket } from "./ws-server.js";
|
|
14
|
+
import { createServer as netCreateServer } from "node:net";
|
|
15
|
+
import { type DaemonHealth } from "./service/install-prep.js";
|
|
5
16
|
import { type ResolveNodeDeps } from "./service/resolve-node.js";
|
|
6
17
|
import { type Harness } from "./service/wire.js";
|
|
18
|
+
import { type ServiceLog } from "./service-log.js";
|
|
19
|
+
import { type LockResult } from "./service/instance-lock.js";
|
|
7
20
|
export declare function nodeMajorOk(version: string): boolean;
|
|
8
21
|
/**
|
|
9
22
|
* A redacted bearer-token hint for explicit foreground install output: a short
|
|
@@ -14,7 +27,10 @@ export declare function tokenFingerprint(token: string): string;
|
|
|
14
27
|
export declare function parseVerb(argv: string[]): "install" | "uninstall" | "status" | "wire" | "unwire" | "models" | "pin" | null;
|
|
15
28
|
export declare function slugFromCredentialPath(credentialPath: string): string;
|
|
16
29
|
export declare function chooseInstallExecPath(runningExecPath: string, override: string | undefined, deps: ResolveNodeDeps): string;
|
|
30
|
+
export declare function realGetHealth(port: number): DaemonHealth | null;
|
|
17
31
|
export declare function realCopyConnector(srcDir: string, dstDir: string, harness: Harness): void;
|
|
32
|
+
/** The stderr line (with newline) a failed service verb prints. */
|
|
33
|
+
export declare function formatServiceError(verb: string, e: unknown): string;
|
|
18
34
|
/**
|
|
19
35
|
* Markdown memory 7a, N-L5: start the agent, THEN create the HTTP server and
|
|
20
36
|
* attach /v1/ws. start() applies the runtime config (and any clear issued
|
|
@@ -35,3 +51,88 @@ export declare function startThenServe(agent: BridgeAgent, opts: {
|
|
|
35
51
|
closeAllSseStreams: () => void;
|
|
36
52
|
agentWs: ReturnType<typeof attachAgentWebSocket>;
|
|
37
53
|
}>;
|
|
54
|
+
/** B1 `held` poll (spec §4.1.2, audit r2 M-3): the holder may be this same
|
|
55
|
+
* service's previous process, still inside shutdown()'s 8 s drain window. */
|
|
56
|
+
export declare const HELD_POLL_TOTAL_MS = 10000;
|
|
57
|
+
export declare const HELD_POLL_INTERVAL_MS = 500;
|
|
58
|
+
/** Extra acquires after the poll when "held" carries no holder pid (a takeover
|
|
59
|
+
* race). M1 names a real pid or is not printed. */
|
|
60
|
+
export declare const HELD_NO_PID_REACQUIRES = 3;
|
|
61
|
+
/** shutdown()'s hard-exit fallback for a hung agent.close() drain (ADR-0008 H1). */
|
|
62
|
+
export declare const SHUTDOWN_HARD_EXIT_MS = 8000;
|
|
63
|
+
/** The lock surface runMode uses (InstanceLock in production). */
|
|
64
|
+
export interface RunModeLock {
|
|
65
|
+
acquire(): {
|
|
66
|
+
result: LockResult;
|
|
67
|
+
holderPid?: number;
|
|
68
|
+
};
|
|
69
|
+
release(): void;
|
|
70
|
+
lastError?: unknown;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Everything run mode touches outside the process, injected so the start
|
|
74
|
+
* order, the refusal exits and the release timing are testable
|
|
75
|
+
* (bin-run-mode.test.ts). `exit` never returns in production; runMode
|
|
76
|
+
* returns right after calling it so a recording test double also stops.
|
|
77
|
+
*/
|
|
78
|
+
export interface RunModeDeps {
|
|
79
|
+
credentialPath: string;
|
|
80
|
+
storePath?: string;
|
|
81
|
+
argv: string[];
|
|
82
|
+
env: NodeJS.ProcessEnv;
|
|
83
|
+
home: string;
|
|
84
|
+
newLock(path: string): RunModeLock;
|
|
85
|
+
newAgent(cfg: BridgeConfig): BridgeAgent;
|
|
86
|
+
/** The B2 pre-bind's socket layer (node:net in production). */
|
|
87
|
+
net: {
|
|
88
|
+
createServer: typeof netCreateServer;
|
|
89
|
+
};
|
|
90
|
+
serve: typeof startThenServe;
|
|
91
|
+
now(): number;
|
|
92
|
+
sleep(ms: number): Promise<void>;
|
|
93
|
+
/** Arms the shutdown hard-exit fallback; returns its cancel. */
|
|
94
|
+
setHardExitTimer(cb: () => void, ms: number): () => void;
|
|
95
|
+
onSignal(signal: "SIGINT" | "SIGTERM", handler: () => void): void;
|
|
96
|
+
/** Synchronous `process.once("exit")` registration. */
|
|
97
|
+
onExit(handler: () => void): void;
|
|
98
|
+
exit(code: number): void;
|
|
99
|
+
/** One line of approved user-facing copy (spec §8). */
|
|
100
|
+
print(line: string): void;
|
|
101
|
+
/**
|
|
102
|
+
* Whether stderr is a terminal. Under launchd and systemd stderr IS the
|
|
103
|
+
* service log, so a printed line may carry a path only when this is true
|
|
104
|
+
* (spec 2026-10-15 §5.3 path rule).
|
|
105
|
+
*/
|
|
106
|
+
isTTY: boolean;
|
|
107
|
+
log: ServiceLog;
|
|
108
|
+
}
|
|
109
|
+
export type StartupConfigReason = ConfigErrorReason | "identity_id_invalid" | "bind_refused" | "unknown";
|
|
110
|
+
/**
|
|
111
|
+
* The one human line for a run-mode config refusal (spec 2026-10-15 §5.3,
|
|
112
|
+
* E1-E7 approved; E5 with source=env is the BT-5 draft). The path is shown
|
|
113
|
+
* only on a TTY; `field` is one of the six fixed REQUIRED names. `unknown`
|
|
114
|
+
* has no line. Never built from an error message.
|
|
115
|
+
*/
|
|
116
|
+
export declare function configErrorLine(reason: StartupConfigReason, o: {
|
|
117
|
+
path: string;
|
|
118
|
+
isTTY: boolean;
|
|
119
|
+
field?: string;
|
|
120
|
+
source?: "file" | "env";
|
|
121
|
+
}): string | undefined;
|
|
122
|
+
/**
|
|
123
|
+
* The daemon's run mode (spec §4.1.1). Order, each step gated on the last:
|
|
124
|
+
* 1. loadConfig (+ daemon args, bind check);
|
|
125
|
+
* 2. canonical-UUID check of identity_id (it becomes a path);
|
|
126
|
+
* 3. B1 per-identity lock at ~/.securecoms/locks/<identity_id>.lock;
|
|
127
|
+
* 4. resolveToken (it can WRITE the token file, so only after the lock);
|
|
128
|
+
* 5. B2 pre-bind of daemon.port: a bare net server that destroys every
|
|
129
|
+
* connection, so a taken port refuses before the store opens or any
|
|
130
|
+
* call to SecureComs is made;
|
|
131
|
+
* 6. new BridgeAgent (opens the SQLite MLS store);
|
|
132
|
+
* 7. startThenServe, then hand the pre-bound handle to the HTTP server.
|
|
133
|
+
* The lock is released only after agent.close() settles (audit H-2): in the
|
|
134
|
+
* close().finally, in the hard-exit fallback, and in a synchronous exit
|
|
135
|
+
* backstop. Never at shutdown() entry: a second copy must not open the MLS
|
|
136
|
+
* store while this one is still draining it.
|
|
137
|
+
*/
|
|
138
|
+
export declare function runMode(deps: RunModeDeps): Promise<void>;
|