@tenphi/akno 0.2.0 → 0.4.0
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 +6 -3
- package/dist/bin.js +1 -1
- package/dist/commands/doctor-cmd.d.ts.map +1 -1
- package/dist/commands/doctor-cmd.js +45 -5
- package/dist/commands/doctor-cmd.js.map +1 -1
- package/dist/commands/dream-cmd.d.ts +6 -0
- package/dist/commands/dream-cmd.d.ts.map +1 -1
- package/dist/commands/dream-cmd.js +30 -7
- package/dist/commands/dream-cmd.js.map +1 -1
- package/dist/commands/dream-notifications.d.ts +23 -4
- package/dist/commands/dream-notifications.d.ts.map +1 -1
- package/dist/commands/dream-notifications.js +42 -29
- package/dist/commands/dream-notifications.js.map +1 -1
- package/dist/commands/dream-schedule.d.ts +4 -0
- package/dist/commands/dream-schedule.d.ts.map +1 -1
- package/dist/commands/dream-schedule.js +34 -2
- package/dist/commands/dream-schedule.js.map +1 -1
- package/dist/commands/index-cmd.d.ts.map +1 -1
- package/dist/commands/index-cmd.js +3 -13
- package/dist/commands/index-cmd.js.map +1 -1
- package/dist/commands/init-cmd.d.ts +13 -3
- package/dist/commands/init-cmd.d.ts.map +1 -1
- package/dist/commands/init-cmd.js +3 -3
- package/dist/commands/init-cmd.js.map +1 -1
- package/dist/commands/mutate-cmd.js +3 -3
- package/dist/commands/redeploy-cmd.d.ts +5 -1
- package/dist/commands/redeploy-cmd.d.ts.map +1 -1
- package/dist/commands/redeploy-cmd.js +67 -32
- package/dist/commands/redeploy-cmd.js.map +1 -1
- package/dist/commands/serve-cmd.d.ts +5 -3
- package/dist/commands/serve-cmd.d.ts.map +1 -1
- package/dist/commands/serve-cmd.js +127 -30
- package/dist/commands/serve-cmd.js.map +1 -1
- package/dist/commands/service-systemd.d.ts +50 -0
- package/dist/commands/service-systemd.d.ts.map +1 -0
- package/dist/commands/service-systemd.js +221 -0
- package/dist/commands/service-systemd.js.map +1 -0
- package/dist/ops-handle.d.ts +3 -1
- package/dist/ops-handle.d.ts.map +1 -1
- package/dist/ops-handle.js +2 -0
- package/dist/ops-handle.js.map +1 -1
- package/dist/serve/http.d.ts +20 -11
- package/dist/serve/http.d.ts.map +1 -1
- package/dist/serve/http.js +135 -30
- package/dist/serve/http.js.map +1 -1
- package/dist/serve/socket.d.ts +2 -0
- package/dist/serve/socket.d.ts.map +1 -1
- package/dist/serve/socket.js +24 -8
- package/dist/serve/socket.js.map +1 -1
- package/package.json +6 -5
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import net from 'node:net';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { spawnSync } from 'node:child_process';
|
|
5
|
+
import { randomUUID } from 'node:crypto';
|
|
6
|
+
import { once } from 'node:events';
|
|
7
|
+
import { Hello, PROTOCOL_VERSION } from '@tenphi/akno-protocol';
|
|
8
|
+
function writePrivateFileAtomic(filePath, contents) {
|
|
9
|
+
const temporaryPath = path.join(path.dirname(filePath), `.${path.basename(filePath)}.${randomUUID()}.tmp`);
|
|
10
|
+
let descriptor;
|
|
11
|
+
let created = false;
|
|
12
|
+
try {
|
|
13
|
+
descriptor = fs.openSync(temporaryPath, 'wx', 0o600);
|
|
14
|
+
created = true;
|
|
15
|
+
fs.fchmodSync(descriptor, 0o600);
|
|
16
|
+
fs.writeFileSync(descriptor, contents);
|
|
17
|
+
const descriptorToClose = descriptor;
|
|
18
|
+
descriptor = undefined;
|
|
19
|
+
fs.closeSync(descriptorToClose);
|
|
20
|
+
fs.renameSync(temporaryPath, filePath);
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
if (descriptor !== undefined) {
|
|
24
|
+
try {
|
|
25
|
+
fs.closeSync(descriptor);
|
|
26
|
+
}
|
|
27
|
+
catch { }
|
|
28
|
+
}
|
|
29
|
+
if (created)
|
|
30
|
+
fs.rmSync(temporaryPath, { force: true });
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export function systemdPaths(label, configHome = process.env.XDG_CONFIG_HOME) {
|
|
35
|
+
const directory = path.join(configHome || path.join(process.env.HOME ?? '', '.config'), 'systemd', 'user');
|
|
36
|
+
return {
|
|
37
|
+
directory,
|
|
38
|
+
service: path.join(directory, `${label}.service`),
|
|
39
|
+
dreamService: path.join(directory, `${label}.dream.service`),
|
|
40
|
+
dreamTimer: path.join(directory, `${label}.dream.timer`),
|
|
41
|
+
healthService: path.join(directory, `${label}.dream-health.service`),
|
|
42
|
+
healthTimer: path.join(directory, `${label}.dream-health.timer`),
|
|
43
|
+
target: path.join(directory, `${label}.target.json`),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export function systemdUnit(definition, oneshot = false) {
|
|
47
|
+
const command = [definition.node, definition.script, ...definition.args]
|
|
48
|
+
.map((value) => systemdQuote(value))
|
|
49
|
+
.join(' ');
|
|
50
|
+
const environment = Object.entries(definition.environment ?? {})
|
|
51
|
+
.map(([name, value]) => `Environment=${systemdQuote(`${name}=${value}`, false)}\n`)
|
|
52
|
+
.join('');
|
|
53
|
+
return `[Unit]\nDescription=Akno ${definition.label}\n\n[Service]\nType=${oneshot ? 'oneshot' : 'simple'}\n${environment}ExecStart=${command}\n${oneshot ? '' : 'Restart=always\nRestartSec=1\n'}${oneshot ? '' : '\n[Install]\nWantedBy=default.target\n'}`;
|
|
54
|
+
}
|
|
55
|
+
export function systemdTimer(label, hour, minute) {
|
|
56
|
+
const time = `${String(hour).padStart(2, '0')}:${String(minute).padStart(2, '0')}:00`;
|
|
57
|
+
return `[Unit]\nDescription=Schedule ${label}\n\n[Timer]\nOnCalendar=*-*-* ${time}\nPersistent=true\nUnit=${label}.service\n\n[Install]\nWantedBy=timers.target\n`;
|
|
58
|
+
}
|
|
59
|
+
function systemdQuote(value, escapeDollar = true) {
|
|
60
|
+
if (/[^\t\x20-\x7e]/.test(value))
|
|
61
|
+
throw new Error('systemd arguments must not contain a control character');
|
|
62
|
+
// `%` is a systemd specifier. `$` triggers environment expansion only in command lines;
|
|
63
|
+
// Environment= values preserve it literally, so doubling there would change the selector.
|
|
64
|
+
let escaped = value.replaceAll('\\', '\\\\').replaceAll('"', '\\"').replaceAll('%', '%%');
|
|
65
|
+
if (escapeDollar)
|
|
66
|
+
escaped = escaped.replaceAll('$', () => '$$');
|
|
67
|
+
return `"${escaped}"`;
|
|
68
|
+
}
|
|
69
|
+
export function systemdInstallPlan(label, dream) {
|
|
70
|
+
const schedules = [`${label}.dream.timer`, `${label}.dream-health.timer`];
|
|
71
|
+
return [
|
|
72
|
+
...(!dream ? [['disable', '--now', ...schedules]] : []),
|
|
73
|
+
['daemon-reload'],
|
|
74
|
+
['enable', `${label}.service`],
|
|
75
|
+
['restart', `${label}.service`],
|
|
76
|
+
...(dream ? [['enable', '--now', ...schedules]] : []),
|
|
77
|
+
];
|
|
78
|
+
}
|
|
79
|
+
export async function installSystemdService(options) {
|
|
80
|
+
const paths = systemdPaths(options.label, options.configHome);
|
|
81
|
+
const systemctl = options.systemctl ?? defaultSystemctl;
|
|
82
|
+
fs.mkdirSync(paths.directory, { recursive: true });
|
|
83
|
+
writePrivateFileAtomic(paths.target, `${JSON.stringify(options.target, null, 2)}\n`);
|
|
84
|
+
const environment = options.target.configPath ? { AKNO_CONFIG: options.target.configPath } : undefined;
|
|
85
|
+
fs.writeFileSync(paths.service, systemdUnit({
|
|
86
|
+
label: options.label,
|
|
87
|
+
node: options.node,
|
|
88
|
+
script: options.script,
|
|
89
|
+
args: options.serviceArgs,
|
|
90
|
+
environment,
|
|
91
|
+
}));
|
|
92
|
+
if (options.dream) {
|
|
93
|
+
fs.writeFileSync(paths.dreamService, systemdUnit({
|
|
94
|
+
label: `${options.label}.dream`,
|
|
95
|
+
node: options.node,
|
|
96
|
+
script: options.script,
|
|
97
|
+
args: options.dreamArgs,
|
|
98
|
+
environment,
|
|
99
|
+
}, true));
|
|
100
|
+
fs.writeFileSync(paths.dreamTimer, systemdTimer(`${options.label}.dream`, options.dreamHour, 0));
|
|
101
|
+
const healthHour = (options.dreamHour + 2) % 24;
|
|
102
|
+
fs.writeFileSync(paths.healthService, systemdUnit({
|
|
103
|
+
label: `${options.label}.dream-health`,
|
|
104
|
+
node: options.node,
|
|
105
|
+
script: options.script,
|
|
106
|
+
args: options.healthArgs,
|
|
107
|
+
environment,
|
|
108
|
+
}, true));
|
|
109
|
+
fs.writeFileSync(paths.healthTimer, systemdTimer(`${options.label}.dream-health`, healthHour, 5));
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
disableUnits([
|
|
113
|
+
[paths.dreamTimer, `${options.label}.dream.timer`],
|
|
114
|
+
[paths.healthTimer, `${options.label}.dream-health.timer`],
|
|
115
|
+
], systemctl);
|
|
116
|
+
for (const target of [paths.dreamService, paths.dreamTimer, paths.healthService, paths.healthTimer]) {
|
|
117
|
+
fs.rmSync(target, { force: true });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const plan = systemdInstallPlan(options.label, options.dream).filter((args) => options.dream || args[0] !== 'disable');
|
|
121
|
+
const schedule = options.dream ? plan.pop() : undefined;
|
|
122
|
+
for (const args of plan) {
|
|
123
|
+
const result = systemctl(args);
|
|
124
|
+
if (result.status !== 0 && args[0] !== 'disable')
|
|
125
|
+
throw systemctlError(args, result);
|
|
126
|
+
}
|
|
127
|
+
const ready = await (options.waitForReady ?? waitForSocket)(options.socketPath, 30_000);
|
|
128
|
+
if (!ready)
|
|
129
|
+
throw new Error(`systemd restarted ${options.label}, but ${options.socketPath} is not ready`);
|
|
130
|
+
if (schedule) {
|
|
131
|
+
const result = systemctl(schedule);
|
|
132
|
+
if (result.status !== 0)
|
|
133
|
+
throw systemctlError(schedule, result);
|
|
134
|
+
}
|
|
135
|
+
return paths;
|
|
136
|
+
}
|
|
137
|
+
export function readSystemdInstallTarget(label, configHome) {
|
|
138
|
+
try {
|
|
139
|
+
return JSON.parse(fs.readFileSync(systemdPaths(label, configHome).target, 'utf8'));
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
export function uninstallSystemdService(label, configHome, systemctl = defaultSystemctl) {
|
|
146
|
+
const paths = systemdPaths(label, configHome);
|
|
147
|
+
disableUnits([
|
|
148
|
+
[paths.service, `${label}.service`],
|
|
149
|
+
[paths.dreamTimer, `${label}.dream.timer`],
|
|
150
|
+
[paths.healthTimer, `${label}.dream-health.timer`],
|
|
151
|
+
], systemctl);
|
|
152
|
+
for (const unitPath of Object.values(paths).filter((candidate) => candidate !== paths.directory)) {
|
|
153
|
+
fs.rmSync(unitPath, { force: true });
|
|
154
|
+
}
|
|
155
|
+
const reloaded = systemctl(['daemon-reload']);
|
|
156
|
+
if (reloaded.status !== 0)
|
|
157
|
+
throw systemctlError(['daemon-reload'], reloaded);
|
|
158
|
+
const failed = [`${label}.service`, `${label}.dream.service`, `${label}.dream-health.service`].filter((unit) => systemctl(['is-failed', '--quiet', unit]).status === 0);
|
|
159
|
+
if (failed.length > 0) {
|
|
160
|
+
const args = ['reset-failed', ...failed];
|
|
161
|
+
const result = systemctl(args);
|
|
162
|
+
if (result.status !== 0)
|
|
163
|
+
throw systemctlError(args, result);
|
|
164
|
+
}
|
|
165
|
+
return paths;
|
|
166
|
+
}
|
|
167
|
+
export function systemdUnitIsActive(unit, systemctl = defaultSystemctl) {
|
|
168
|
+
return systemctl(['is-active', '--quiet', unit]).status === 0;
|
|
169
|
+
}
|
|
170
|
+
function disableUnits(units, systemctl) {
|
|
171
|
+
const selected = units
|
|
172
|
+
.filter(([unitPath, unit]) => fs.existsSync(unitPath) || systemctl(['is-active', '--quiet', unit]).status === 0)
|
|
173
|
+
.map(([, unit]) => unit);
|
|
174
|
+
if (selected.length === 0)
|
|
175
|
+
return;
|
|
176
|
+
const args = ['disable', '--now', ...selected];
|
|
177
|
+
const result = systemctl(args);
|
|
178
|
+
if (result.status !== 0)
|
|
179
|
+
throw systemctlError(args, result);
|
|
180
|
+
}
|
|
181
|
+
function defaultSystemctl(args) {
|
|
182
|
+
return spawnSync('systemctl', ['--user', ...args], { encoding: 'utf8' });
|
|
183
|
+
}
|
|
184
|
+
function systemctlError(args, result) {
|
|
185
|
+
const detail = (result.stderr || result.error?.message || '').trim();
|
|
186
|
+
return new Error(`systemctl --user ${args.join(' ')} failed (exit ${result.status ?? 'unknown'})${detail ? `: ${detail}` : ''}`);
|
|
187
|
+
}
|
|
188
|
+
export function isCompatibleAknoHello(line) {
|
|
189
|
+
try {
|
|
190
|
+
const hello = Hello.safeParse(JSON.parse(line.trim()));
|
|
191
|
+
return hello.success && hello.data.protocol === PROTOCOL_VERSION;
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
export async function aknoSocketIsReady(socketPath) {
|
|
198
|
+
const socket = net.createConnection(socketPath);
|
|
199
|
+
socket.setEncoding('utf8');
|
|
200
|
+
try {
|
|
201
|
+
await once(socket, 'connect', { signal: AbortSignal.timeout(500) });
|
|
202
|
+
const [chunk] = (await once(socket, 'data', { signal: AbortSignal.timeout(500) }));
|
|
203
|
+
return isCompatibleAknoHello(chunk.split('\n')[0] ?? '');
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
finally {
|
|
209
|
+
socket.destroy();
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
async function waitForSocket(socketPath, timeoutMs) {
|
|
213
|
+
const deadline = Date.now() + timeoutMs;
|
|
214
|
+
while (Date.now() < deadline) {
|
|
215
|
+
if (await aknoSocketIsReady(socketPath))
|
|
216
|
+
return true;
|
|
217
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
218
|
+
}
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
//# sourceMappingURL=service-systemd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-systemd.js","sourceRoot":"","sources":["../../src/commands/service-systemd.ts"],"names":[],"mappings":"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"}
|
package/dist/ops-handle.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Akno, type MaintenanceStatus } from '@tenphi/akno-core';
|
|
2
|
-
import type { CommandName, AknoOps, OpInput, OpName, OpResult } from '@tenphi/akno-protocol';
|
|
2
|
+
import type { CommandName, AknoOps, Hello, OpInput, OpName, OpResult } from '@tenphi/akno-protocol';
|
|
3
3
|
export interface OpsHandle {
|
|
4
4
|
/**
|
|
5
5
|
* The named ops, plus the dynamic `call` both sides implement — a door that dispatches by
|
|
@@ -12,6 +12,8 @@ export interface OpsHandle {
|
|
|
12
12
|
/** Set when the ops run in-process, for commands that need more than the ops. */
|
|
13
13
|
akno: Akno | null;
|
|
14
14
|
via: 'socket' | 'in-process';
|
|
15
|
+
/** Service handshake, including door policy. Null for an in-process handle. */
|
|
16
|
+
hello: Hello | null;
|
|
15
17
|
close(): Promise<void>;
|
|
16
18
|
}
|
|
17
19
|
export interface MaintenanceWaitUpdate {
|
package/dist/ops-handle.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ops-handle.d.ts","sourceRoot":"","sources":["../src/ops-handle.ts"],"names":[],"mappings":"AAGA,OAAO,EAA+B,KAAK,IAAI,EAAE,KAAK,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACnG,OAAO,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"ops-handle.d.ts","sourceRoot":"","sources":["../src/ops-handle.ts"],"names":[],"mappings":"AAGA,OAAO,EAA+B,KAAK,IAAI,EAAE,KAAK,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACnG,OAAO,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAGpG,MAAM,WAAW,SAAS;IACxB;;;;OAIG;IACH,GAAG,EAAE,OAAO,GAAG;QAAE,IAAI,CAAC,CAAC,SAAS,MAAM,EAAE,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAA;KAAE,CAAC;IAC1F,iFAAiF;IACjF,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAClB,GAAG,EAAE,QAAQ,GAAG,YAAY,CAAC;IAC7B,+EAA+E;IAC/E,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,QAAQ,GAAG,YAAY,CAAC;IAC7B,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAAC;CAClC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,UAAU,CAC9B,MAAM,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,EAC7C,WAAW,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,EACrD,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAA;CAAO,GACnE,OAAO,CAAC,SAAS,CAAC,CA6CpB;AAmED;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAAC,CAAC,EACpC,OAAO,EAAE,WAAW,EACpB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,MAAM,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,EAC7C,WAAW,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,EACrD,SAAS,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,EACrC,OAAO,GAAE;IACP,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,iFAAiF;IACjF,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,qBAAqB,KAAK,IAAI,CAAC;IACjD,WAAW,CAAC,EAAE,MAAM,CAAC;CACjB,GACL,OAAO,CAAC,CAAC,CAAC,CA+CZ"}
|
package/dist/ops-handle.js
CHANGED
|
@@ -31,6 +31,7 @@ export async function resolveOps(values, openOptions, options = {}) {
|
|
|
31
31
|
ops: client,
|
|
32
32
|
akno: null,
|
|
33
33
|
via: 'socket',
|
|
34
|
+
hello: client.hello,
|
|
34
35
|
close: () => client.close(),
|
|
35
36
|
};
|
|
36
37
|
}
|
|
@@ -57,6 +58,7 @@ export async function resolveOps(values, openOptions, options = {}) {
|
|
|
57
58
|
ops: akno,
|
|
58
59
|
akno,
|
|
59
60
|
via: 'in-process',
|
|
61
|
+
hello: null,
|
|
60
62
|
close: () => akno.close(),
|
|
61
63
|
};
|
|
62
64
|
}
|
package/dist/ops-handle.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ops-handle.js","sourceRoot":"","sources":["../src/ops-handle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,IAAI,EAAqC,MAAM,mBAAmB,CAAC;AAEnG,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"ops-handle.js","sourceRoot":"","sources":["../src/ops-handle.ts"],"names":[],"mappings":"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"}
|
package/dist/serve/http.d.ts
CHANGED
|
@@ -1,19 +1,28 @@
|
|
|
1
1
|
import type { Akno } from '@tenphi/akno-core';
|
|
2
2
|
export interface HttpServer {
|
|
3
3
|
readonly address: string;
|
|
4
|
+
readonly loopback: boolean;
|
|
4
5
|
close(): Promise<void>;
|
|
5
6
|
}
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
*/
|
|
15
|
-
export declare function serveHttp(akno: Akno, address: string, options?: {
|
|
7
|
+
interface HttpIdentity {
|
|
8
|
+
name: string;
|
|
9
|
+
token: string;
|
|
10
|
+
actor: 'user' | 'agent' | 'akno';
|
|
11
|
+
allow: string[];
|
|
12
|
+
}
|
|
13
|
+
export interface HttpServerOptions {
|
|
14
|
+
/** Final door restriction; every public or authenticated policy is intersected with it. */
|
|
16
15
|
allow?: string[];
|
|
16
|
+
/** Unauthenticated loopback policy. Defaults to registry read operations. */
|
|
17
|
+
publicAllow?: string[];
|
|
18
|
+
identities?: HttpIdentity[];
|
|
17
19
|
log?: (message: string) => void;
|
|
18
|
-
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* HTTP is the network-facing door. Loopback callers get a read-only public policy;
|
|
23
|
+
* authenticated callers get the actor and operation set owned by their credential.
|
|
24
|
+
* A request header can never promote either one.
|
|
25
|
+
*/
|
|
26
|
+
export declare function serveHttp(akno: Akno, address: string, options?: HttpServerOptions): Promise<HttpServer>;
|
|
27
|
+
export {};
|
|
19
28
|
//# sourceMappingURL=http.d.ts.map
|
package/dist/serve/http.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/serve/http.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/serve/http.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAG9C,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,UAAU,YAAY;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;IACjC,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC,2FAA2F;IAC3F,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,6EAA6E;IAC7E,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,UAAU,CAAC,EAAE,YAAY,EAAE,CAAC;IAC5B,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC;AAQD;;;;GAIG;AACH,wBAAsB,SAAS,CAC7B,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,UAAU,CAAC,CA0DrB"}
|
package/dist/serve/http.js
CHANGED
|
@@ -1,23 +1,38 @@
|
|
|
1
|
+
import { createHash, timingSafeEqual } from 'node:crypto';
|
|
1
2
|
import http from 'node:http';
|
|
3
|
+
import { isIP } from 'node:net';
|
|
2
4
|
import { AknoError, OPS, PROTOCOL_VERSION, isOpName } from '@tenphi/akno-protocol';
|
|
3
5
|
import { AKNO_VERSION } from "../version.js";
|
|
4
6
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* that makes WAL concurrency safe.
|
|
9
|
-
*
|
|
10
|
-
* Binds loopback unless told otherwise, and says so loudly if not: this door has
|
|
11
|
-
* no authentication of its own and is meant to sit behind one.
|
|
7
|
+
* HTTP is the network-facing door. Loopback callers get a read-only public policy;
|
|
8
|
+
* authenticated callers get the actor and operation set owned by their credential.
|
|
9
|
+
* A request header can never promote either one.
|
|
12
10
|
*/
|
|
13
11
|
export async function serveHttp(akno, address, options = {}) {
|
|
14
12
|
const [host, portText] = splitAddress(address);
|
|
15
13
|
const port = Number(portText);
|
|
16
|
-
if (!Number.isInteger(port) || port
|
|
14
|
+
if (!/^\d+$/.test(portText) || !Number.isInteger(port) || port < 0 || port > 65_535) {
|
|
17
15
|
throw new AknoError('invalid', `not a valid host:port — ${address}`);
|
|
18
16
|
}
|
|
17
|
+
const identities = (options.identities ?? []).map((identity) => ({
|
|
18
|
+
...identity,
|
|
19
|
+
digest: tokenDigest(identity.token),
|
|
20
|
+
allow: allowedOps(identity.allow, options.allow),
|
|
21
|
+
}));
|
|
22
|
+
if (!isLoopback(host) && identities.length === 0) {
|
|
23
|
+
throw new AknoError('forbidden', `HTTP cannot bind ${host} without a configured bearer identity in server.http_access`, { reason: 'http_auth_required' });
|
|
24
|
+
}
|
|
25
|
+
ensureDistinctCredentials(identities);
|
|
26
|
+
const loopbackTarget = isLoopback(host);
|
|
27
|
+
const publicOps = loopbackTarget
|
|
28
|
+
? allowedOps(options.publicAllow ?? readOps(), options.allow).filter((name) => OPS[name].kind === 'read')
|
|
29
|
+
: [];
|
|
19
30
|
const server = http.createServer((request, response) => {
|
|
20
|
-
void route(request, response, akno,
|
|
31
|
+
void route(request, response, akno, {
|
|
32
|
+
publicOps,
|
|
33
|
+
identities,
|
|
34
|
+
log: options.log,
|
|
35
|
+
});
|
|
21
36
|
});
|
|
22
37
|
await new Promise((resolve, reject) => {
|
|
23
38
|
server.once('error', reject);
|
|
@@ -26,8 +41,19 @@ export async function serveHttp(akno, address, options = {}) {
|
|
|
26
41
|
resolve();
|
|
27
42
|
});
|
|
28
43
|
});
|
|
44
|
+
const bound = server.address();
|
|
45
|
+
const actualHost = typeof bound === 'object' && bound ? bound.address : host;
|
|
46
|
+
const actualPort = typeof bound === 'object' && bound ? bound.port : port;
|
|
47
|
+
const loopback = isLoopback(actualHost);
|
|
48
|
+
if (loopbackTarget && !loopback) {
|
|
49
|
+
await new Promise((resolve) => server.close(() => resolve()));
|
|
50
|
+
throw new AknoError('forbidden', `HTTP loopback name ${host} resolved to non-loopback ${actualHost}`, {
|
|
51
|
+
reason: 'http_auth_required',
|
|
52
|
+
});
|
|
53
|
+
}
|
|
29
54
|
return {
|
|
30
|
-
address:
|
|
55
|
+
address: formatAddress(actualHost, actualPort),
|
|
56
|
+
loopback,
|
|
31
57
|
async close() {
|
|
32
58
|
await new Promise((resolve) => server.close(() => resolve()));
|
|
33
59
|
},
|
|
@@ -35,22 +61,40 @@ export async function serveHttp(akno, address, options = {}) {
|
|
|
35
61
|
}
|
|
36
62
|
async function route(request, response, akno, options) {
|
|
37
63
|
const url = new URL(request.url ?? '/', 'http://localhost');
|
|
64
|
+
if (url.pathname === '/health') {
|
|
65
|
+
send(response, 200, { ok: true, writable: akno.writable });
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
let access;
|
|
69
|
+
try {
|
|
70
|
+
if (request.headers['x-akno-actor'] !== undefined) {
|
|
71
|
+
throw new AknoError('forbidden', 'HTTP callers cannot declare an actor', {
|
|
72
|
+
reason: 'server_owned_actor',
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
access = authenticate(request, options.publicOps, options.identities);
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
const denied = AknoError.from(error);
|
|
79
|
+
const unauthorized = denied.details?.reason === 'invalid_http_credential';
|
|
80
|
+
send(response, unauthorized ? 401 : statusFor(denied.code), {
|
|
81
|
+
ok: false,
|
|
82
|
+
error: denied.toJSON(),
|
|
83
|
+
});
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
38
86
|
if (url.pathname === '/hello') {
|
|
39
87
|
const hello = {
|
|
40
88
|
hello: 'akno',
|
|
41
89
|
protocol: PROTOCOL_VERSION,
|
|
42
90
|
version: AKNO_VERSION,
|
|
43
|
-
writable: akno.writable,
|
|
91
|
+
writable: akno.writable && access.ops.some((op) => OPS[op].kind === 'write'),
|
|
44
92
|
akno_path: akno.config.aknoPath,
|
|
45
|
-
ops:
|
|
93
|
+
ops: access.ops,
|
|
46
94
|
};
|
|
47
95
|
send(response, 200, hello);
|
|
48
96
|
return;
|
|
49
97
|
}
|
|
50
|
-
if (url.pathname === '/health') {
|
|
51
|
-
send(response, 200, { ok: true, writable: akno.writable });
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
98
|
const match = /^\/op\/([a-z_]+)$/.exec(url.pathname);
|
|
55
99
|
if (!match || request.method !== 'POST') {
|
|
56
100
|
send(response, 404, { ok: false, error: { code: 'not_found', message: 'POST /op/<name>' } });
|
|
@@ -60,26 +104,70 @@ async function route(request, response, akno, options) {
|
|
|
60
104
|
try {
|
|
61
105
|
if (!isOpName(op))
|
|
62
106
|
throw new AknoError('invalid', `unknown op: ${op}`);
|
|
63
|
-
if (
|
|
64
|
-
throw new AknoError('forbidden', `${op} is not allowed
|
|
107
|
+
if (!access.ops.includes(op)) {
|
|
108
|
+
throw new AknoError('forbidden', `${op} is not allowed for this HTTP identity`);
|
|
65
109
|
}
|
|
66
110
|
const input = await readJson(request);
|
|
67
|
-
// A header rather than a field in the body: `actor` is about the caller, not the op, and putting
|
|
68
|
-
// it in the JSON would collide the day an op wants a field of that name.
|
|
69
|
-
const declared = request.headers['x-akno-actor'];
|
|
70
|
-
const actor = declared === 'user' || declared === 'agent' || declared === 'akno' ? declared : undefined;
|
|
71
111
|
const started = performance.now();
|
|
72
|
-
const result = await akno.call(op, input,
|
|
73
|
-
options.log?.(`${op} ${(performance.now() - started).toFixed(1)}ms`);
|
|
112
|
+
const result = await akno.call(op, input, { actor: access.actor });
|
|
113
|
+
options.log?.(`${op} ${access.identity} ${(performance.now() - started).toFixed(1)}ms`);
|
|
74
114
|
send(response, 200, { ok: true, result });
|
|
75
115
|
}
|
|
76
|
-
catch (
|
|
77
|
-
const
|
|
78
|
-
options.log?.(`${op} ${
|
|
79
|
-
send(response, statusFor(
|
|
116
|
+
catch (error) {
|
|
117
|
+
const failed = AknoError.from(error);
|
|
118
|
+
options.log?.(`${op} ${access.identity} ${failed.code}: ${failed.message}`);
|
|
119
|
+
send(response, statusFor(failed.code), { ok: false, error: failed.toJSON() });
|
|
80
120
|
}
|
|
81
121
|
}
|
|
82
|
-
|
|
122
|
+
function authenticate(request, publicOps, identities) {
|
|
123
|
+
const authorization = request.headers.authorization;
|
|
124
|
+
if (authorization === undefined) {
|
|
125
|
+
if (publicOps.length === 0) {
|
|
126
|
+
throw new AknoError('forbidden', 'HTTP bearer credential required', {
|
|
127
|
+
reason: 'invalid_http_credential',
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
return { actor: 'agent', ops: publicOps, identity: 'public' };
|
|
131
|
+
}
|
|
132
|
+
const match = /^Bearer ([^\s]+)$/.exec(authorization);
|
|
133
|
+
const candidate = tokenDigest(match?.[1] ?? '');
|
|
134
|
+
let selected = null;
|
|
135
|
+
// Evaluate every fixed-size digest. Authentication time does not reveal which entry matched.
|
|
136
|
+
for (const identity of identities) {
|
|
137
|
+
if (timingSafeEqual(candidate, identity.digest))
|
|
138
|
+
selected = identity;
|
|
139
|
+
}
|
|
140
|
+
if (!match || !selected) {
|
|
141
|
+
throw new AknoError('forbidden', 'HTTP bearer credential rejected', {
|
|
142
|
+
reason: 'invalid_http_credential',
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
actor: selected.actor,
|
|
147
|
+
ops: selected.allow,
|
|
148
|
+
identity: selected.name,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
function tokenDigest(value) {
|
|
152
|
+
return createHash('sha256').update(value).digest();
|
|
153
|
+
}
|
|
154
|
+
function ensureDistinctCredentials(identities) {
|
|
155
|
+
for (let left = 0; left < identities.length; left++) {
|
|
156
|
+
for (let right = left + 1; right < identities.length; right++) {
|
|
157
|
+
if (timingSafeEqual(identities[left].digest, identities[right].digest)) {
|
|
158
|
+
throw new AknoError('invalid', 'server.http_access contains duplicate bearer credentials');
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function readOps() {
|
|
164
|
+
return Object.keys(OPS).filter((name) => OPS[name].kind === 'read');
|
|
165
|
+
}
|
|
166
|
+
function allowedOps(policy, door) {
|
|
167
|
+
const restriction = door ? new Set(door) : null;
|
|
168
|
+
return [...new Set(policy)].filter((name) => isOpName(name) && (!restriction || restriction.has(name)));
|
|
169
|
+
}
|
|
170
|
+
/** HTTP status codes chosen so a proxy or dashboard reads them correctly. */
|
|
83
171
|
function statusFor(code) {
|
|
84
172
|
switch (code) {
|
|
85
173
|
case 'invalid':
|
|
@@ -90,6 +178,7 @@ function statusFor(code) {
|
|
|
90
178
|
return 404;
|
|
91
179
|
case 'read_only':
|
|
92
180
|
case 'busy':
|
|
181
|
+
case 'conflict':
|
|
93
182
|
return 409;
|
|
94
183
|
case 'not_implemented':
|
|
95
184
|
return 501;
|
|
@@ -112,7 +201,6 @@ async function readJson(request) {
|
|
|
112
201
|
let bytes = 0;
|
|
113
202
|
for await (const chunk of request) {
|
|
114
203
|
bytes += chunk.length;
|
|
115
|
-
// A 4 MB cap: `remember` takes a transcript, not a corpus.
|
|
116
204
|
if (bytes > 4 * 1024 * 1024)
|
|
117
205
|
throw new AknoError('invalid', 'request body too large');
|
|
118
206
|
chunks.push(chunk);
|
|
@@ -127,9 +215,26 @@ async function readJson(request) {
|
|
|
127
215
|
}
|
|
128
216
|
}
|
|
129
217
|
function splitAddress(address) {
|
|
218
|
+
if (address.startsWith('[')) {
|
|
219
|
+
const bracket = address.indexOf(']');
|
|
220
|
+
if (bracket < 0 || address[bracket + 1] !== ':')
|
|
221
|
+
return [address, ''];
|
|
222
|
+
return [address.slice(1, bracket), address.slice(bracket + 2)];
|
|
223
|
+
}
|
|
130
224
|
const index = address.lastIndexOf(':');
|
|
131
225
|
if (index === -1)
|
|
132
226
|
return ['127.0.0.1', address];
|
|
133
227
|
return [address.slice(0, index) || '127.0.0.1', address.slice(index + 1)];
|
|
134
228
|
}
|
|
229
|
+
function isLoopback(host) {
|
|
230
|
+
const normalized = host.toLowerCase();
|
|
231
|
+
if (normalized === 'localhost' || normalized === '::1')
|
|
232
|
+
return true;
|
|
233
|
+
if (isIP(normalized) !== 4)
|
|
234
|
+
return false;
|
|
235
|
+
return normalized.split('.')[0] === '127';
|
|
236
|
+
}
|
|
237
|
+
function formatAddress(host, port) {
|
|
238
|
+
return isIP(host) === 6 ? `[${host}]:${port}` : `${host}:${port}`;
|
|
239
|
+
}
|
|
135
240
|
//# sourceMappingURL=http.js.map
|