terminay 4.2.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/dist/account.d.ts +31 -0
- package/dist/account.js +150 -0
- package/dist/account.js.map +1 -0
- package/dist/address.d.ts +14 -0
- package/dist/address.js +58 -0
- package/dist/address.js.map +1 -0
- package/dist/args.d.ts +37 -0
- package/dist/args.js +252 -0
- package/dist/args.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +92 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/install.d.ts +48 -0
- package/dist/commands/install.js +213 -0
- package/dist/commands/install.js.map +1 -0
- package/dist/commands/lifecycle.d.ts +23 -0
- package/dist/commands/lifecycle.js +66 -0
- package/dist/commands/lifecycle.js.map +1 -0
- package/dist/commands/pairing.d.ts +23 -0
- package/dist/commands/pairing.js +143 -0
- package/dist/commands/pairing.js.map +1 -0
- package/dist/commands/uninstall.d.ts +26 -0
- package/dist/commands/uninstall.js +70 -0
- package/dist/commands/uninstall.js.map +1 -0
- package/dist/commands/upgrade.d.ts +30 -0
- package/dist/commands/upgrade.js +175 -0
- package/dist/commands/upgrade.js.map +1 -0
- package/dist/context.d.ts +26 -0
- package/dist/context.js +37 -0
- package/dist/context.js.map +1 -0
- package/dist/download.d.ts +11 -0
- package/dist/download.js +95 -0
- package/dist/download.js.map +1 -0
- package/dist/health.d.ts +18 -0
- package/dist/health.js +61 -0
- package/dist/health.js.map +1 -0
- package/dist/http.d.ts +11 -0
- package/dist/http.js +88 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/install.d.ts +30 -0
- package/dist/install.js +100 -0
- package/dist/install.js.map +1 -0
- package/dist/layout.d.ts +52 -0
- package/dist/layout.js +96 -0
- package/dist/layout.js.map +1 -0
- package/dist/manifest.d.ts +46 -0
- package/dist/manifest.js +115 -0
- package/dist/manifest.js.map +1 -0
- package/dist/platform.d.ts +17 -0
- package/dist/platform.js +34 -0
- package/dist/platform.js.map +1 -0
- package/dist/prompt.d.ts +20 -0
- package/dist/prompt.js +47 -0
- package/dist/prompt.js.map +1 -0
- package/dist/resolve.d.ts +35 -0
- package/dist/resolve.js +194 -0
- package/dist/resolve.js.map +1 -0
- package/dist/socket.d.ts +55 -0
- package/dist/socket.js +98 -0
- package/dist/socket.js.map +1 -0
- package/dist/socketClient.d.ts +1 -0
- package/dist/socketClient.js +22 -0
- package/dist/socketClient.js.map +1 -0
- package/dist/source.d.ts +24 -0
- package/dist/source.js +152 -0
- package/dist/source.js.map +1 -0
- package/dist/systemd.d.ts +30 -0
- package/dist/systemd.js +86 -0
- package/dist/systemd.js.map +1 -0
- package/dist/unit.d.ts +37 -0
- package/dist/unit.js +74 -0
- package/dist/unit.js.map +1 -0
- package/dist/verify.d.ts +24 -0
- package/dist/verify.js +46 -0
- package/dist/verify.js.map +1 -0
- package/package.json +49 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"platform.js","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAErC;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,cAAc,GAAG,qBAAqB,CAAC;AAEpD,MAAM,uBAAuB,GAAsB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;AAQpE,MAAM,UAAU,aAAa;IAC5B,OAAO,MAAM,CAAC,MAAM,CAAC;QACpB,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,UAAU,EAAE,UAAU,CAAC,cAAc,CAAC;KACtC,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,OAAO,oBAAqB,SAAQ,KAAK;CAAG;AAElD,MAAM,UAAU,mBAAmB,CAAC,QAAmB,aAAa,EAAE;IACrE,IAAI,KAAK,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QAChC,MAAM,IAAI,oBAAoB,CAC7B,mEAAmE,KAAK,CAAC,QAAQ,8CAA8C,CAC/H,CAAC;IACH,CAAC;IACD,IAAI,CAAC,uBAAuB,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACnD,MAAM,IAAI,oBAAoB,CAC7B,0EAA0E,KAAK,CAAC,IAAI,GAAG,CACvF,CAAC;IACH,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC;QACvB,MAAM,IAAI,oBAAoB,CAC7B,4DAA4D,cAAc,0BAA0B,CACpG,CAAC;IACH,CAAC;AACF,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC/B,QAAmB,aAAa,EAAE;IAElC,mBAAmB,CAAC,KAAK,CAAC,CAAC;IAC3B,OAAO,KAAK,CAAC,IAAuB,CAAC;AACtC,CAAC"}
|
package/dist/prompt.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Readable, Writable } from 'node:stream';
|
|
2
|
+
/**
|
|
3
|
+
* The few questions the installer asks.
|
|
4
|
+
*
|
|
5
|
+
* A prompt appears only when standard input is a terminal and the answer was
|
|
6
|
+
* not given as a flag. Without a terminal the CLI requires the flag instead of
|
|
7
|
+
* choosing for the operator, because both questions it asks — install scope
|
|
8
|
+
* and the account terminals run as — decide what the daemon can reach.
|
|
9
|
+
*/
|
|
10
|
+
export interface PromptStreams {
|
|
11
|
+
readonly input: Readable & {
|
|
12
|
+
isTTY?: boolean;
|
|
13
|
+
};
|
|
14
|
+
readonly output: Writable;
|
|
15
|
+
}
|
|
16
|
+
export declare function defaultStreams(): PromptStreams;
|
|
17
|
+
export declare function isInteractive(streams?: PromptStreams): boolean;
|
|
18
|
+
/** Enter accepts the first choice, which is always the safer default. */
|
|
19
|
+
export declare function choose<T extends string>(question: string, choices: readonly (readonly [T, string])[], streams?: PromptStreams): Promise<T>;
|
|
20
|
+
export declare function confirm(question: string, streams?: PromptStreams, fallback?: boolean): Promise<boolean>;
|
package/dist/prompt.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { createInterface } from 'node:readline';
|
|
2
|
+
export function defaultStreams() {
|
|
3
|
+
return { input: process.stdin, output: process.stdout };
|
|
4
|
+
}
|
|
5
|
+
export function isInteractive(streams = defaultStreams()) {
|
|
6
|
+
return streams.input.isTTY === true;
|
|
7
|
+
}
|
|
8
|
+
async function ask(question, streams) {
|
|
9
|
+
const rl = createInterface({ input: streams.input, output: streams.output });
|
|
10
|
+
try {
|
|
11
|
+
return await new Promise((resolve) => rl.question(question, resolve));
|
|
12
|
+
}
|
|
13
|
+
finally {
|
|
14
|
+
rl.close();
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** Enter accepts the first choice, which is always the safer default. */
|
|
18
|
+
export async function choose(question, choices, streams = defaultStreams()) {
|
|
19
|
+
const [first] = choices;
|
|
20
|
+
if (first === undefined)
|
|
21
|
+
throw new Error('a choice needs at least one option');
|
|
22
|
+
const lines = choices.map(([value, label], index) => ` ${index + 1}) ${label}${index === 0 ? ' [default]' : ''} (${value})`);
|
|
23
|
+
for (;;) {
|
|
24
|
+
const answer = (await ask(`${question}\n${lines.join('\n')}\n> `, streams))
|
|
25
|
+
.trim()
|
|
26
|
+
.toLowerCase();
|
|
27
|
+
if (answer.length === 0)
|
|
28
|
+
return first[0];
|
|
29
|
+
const byNumber = choices[Number(answer) - 1];
|
|
30
|
+
if (/^\d+$/u.test(answer) && byNumber !== undefined)
|
|
31
|
+
return byNumber[0];
|
|
32
|
+
const byName = choices.find(([value]) => value === answer);
|
|
33
|
+
if (byName !== undefined)
|
|
34
|
+
return byName[0];
|
|
35
|
+
streams.output.write(`Please answer with a number from 1 to ${choices.length}, or a name.\n`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export async function confirm(question, streams = defaultStreams(), fallback = false) {
|
|
39
|
+
const suffix = fallback ? '[Y/n]' : '[y/N]';
|
|
40
|
+
const answer = (await ask(`${question} ${suffix} `, streams))
|
|
41
|
+
.trim()
|
|
42
|
+
.toLowerCase();
|
|
43
|
+
if (answer.length === 0)
|
|
44
|
+
return fallback;
|
|
45
|
+
return answer === 'y' || answer === 'yes';
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=prompt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt.js","sourceRoot":"","sources":["../src/prompt.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export declare const DEFAULT_REPOSITORY = "markwylde/terminay";
|
|
2
|
+
/**
|
|
3
|
+
* The rolling channel is tagged `main-latest`, not `main`: a release tag named
|
|
4
|
+
* for the default branch makes the ref ambiguous in every clone, so
|
|
5
|
+
* `git fetch main` resolves the tag instead of the branch.
|
|
6
|
+
*/
|
|
7
|
+
export declare const ROLLING_TAG = "main-latest";
|
|
8
|
+
export type ReleaseChannel = 'tag' | 'main' | 'source';
|
|
9
|
+
export type Architecture = 'x64' | 'arm64';
|
|
10
|
+
export interface ResolvedAssets {
|
|
11
|
+
readonly archive: string;
|
|
12
|
+
readonly sha256: string;
|
|
13
|
+
readonly signature: string;
|
|
14
|
+
}
|
|
15
|
+
export interface ResolvedRef {
|
|
16
|
+
readonly channel: ReleaseChannel;
|
|
17
|
+
/** Release version for `tag`, the channel name for `main`, the short commit for `source`. */
|
|
18
|
+
readonly version: string;
|
|
19
|
+
/** Known up front only for `source`; otherwise read from the signed manifest after install. */
|
|
20
|
+
readonly revision?: string;
|
|
21
|
+
readonly tag?: string;
|
|
22
|
+
readonly publishedAt?: string;
|
|
23
|
+
readonly assets?: ResolvedAssets;
|
|
24
|
+
/** Set for `source`: the reference to clone and build. */
|
|
25
|
+
readonly sourceRef?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface ResolveOptions {
|
|
28
|
+
readonly repository?: string;
|
|
29
|
+
readonly architecture: Architecture;
|
|
30
|
+
/** Overridden by tests to point at a local server. */
|
|
31
|
+
readonly apiBase?: string;
|
|
32
|
+
readonly webBase?: string;
|
|
33
|
+
}
|
|
34
|
+
export declare function archiveName(version: string, architecture: Architecture): string;
|
|
35
|
+
export declare function resolveRef(ref: string | undefined, options: ResolveOptions): Promise<ResolvedRef>;
|
package/dist/resolve.js
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
import { request, requestJson } from './http.js';
|
|
4
|
+
/**
|
|
5
|
+
* Turning an operator's reference into something installable.
|
|
6
|
+
*
|
|
7
|
+
* Four shapes are accepted, and the shape decides the channel the install
|
|
8
|
+
* follows afterwards. The channel matters more than the version: an upgrade
|
|
9
|
+
* with no reference re-resolves whatever channel the machine is already on,
|
|
10
|
+
* so a box tracking `main` never silently jumps onto the tag stream.
|
|
11
|
+
*/
|
|
12
|
+
const execFileAsync = promisify(execFile);
|
|
13
|
+
export const DEFAULT_REPOSITORY = 'markwylde/terminay';
|
|
14
|
+
/**
|
|
15
|
+
* The rolling channel is tagged `main-latest`, not `main`: a release tag named
|
|
16
|
+
* for the default branch makes the ref ambiguous in every clone, so
|
|
17
|
+
* `git fetch main` resolves the tag instead of the branch.
|
|
18
|
+
*/
|
|
19
|
+
export const ROLLING_TAG = 'main-latest';
|
|
20
|
+
/** Assets on the rolling release are named for the channel, not a version. */
|
|
21
|
+
const ROLLING_ASSET_VERSION = 'main';
|
|
22
|
+
const SEMVER_TAG = /^v\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/u;
|
|
23
|
+
const COMMIT_SHA = /^[0-9a-f]{7,40}$/u;
|
|
24
|
+
function apiBase(options) {
|
|
25
|
+
return options.apiBase ?? 'https://api.github.com';
|
|
26
|
+
}
|
|
27
|
+
function webBase(options) {
|
|
28
|
+
return options.webBase ?? 'https://github.com';
|
|
29
|
+
}
|
|
30
|
+
function repository(options) {
|
|
31
|
+
return options.repository ?? DEFAULT_REPOSITORY;
|
|
32
|
+
}
|
|
33
|
+
export function archiveName(version, architecture) {
|
|
34
|
+
return `terminay-server-${version}-linux-${architecture}.tar.gz`;
|
|
35
|
+
}
|
|
36
|
+
function assetUrls(options, tag, version) {
|
|
37
|
+
const base = `${webBase(options)}/${repository(options)}/releases/download/${encodeURIComponent(tag)}`;
|
|
38
|
+
const name = archiveName(version, options.architecture);
|
|
39
|
+
return Object.freeze({
|
|
40
|
+
archive: `${base}/${name}`,
|
|
41
|
+
sha256: `${base}/${name}.sha256`,
|
|
42
|
+
signature: `${base}/${name}.sig`,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
function readReleaseJson(document) {
|
|
46
|
+
if (typeof document !== 'object' || document === null)
|
|
47
|
+
return undefined;
|
|
48
|
+
const release = document;
|
|
49
|
+
if (typeof release.tag_name !== 'string')
|
|
50
|
+
return undefined;
|
|
51
|
+
const assets = Array.isArray(release.assets) ? release.assets : [];
|
|
52
|
+
return Object.freeze({
|
|
53
|
+
tagName: release.tag_name,
|
|
54
|
+
publishedAt: typeof release.published_at === 'string'
|
|
55
|
+
? release.published_at
|
|
56
|
+
: undefined,
|
|
57
|
+
assetNames: Object.freeze(assets
|
|
58
|
+
.map((asset) => typeof asset === 'object' && asset !== null
|
|
59
|
+
? asset.name
|
|
60
|
+
: undefined)
|
|
61
|
+
.filter((name) => typeof name === 'string')),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The release page's asset fragment is plain HTML and is not rate limited, so
|
|
66
|
+
* it answers when an anonymous `npx` install has exhausted the API budget.
|
|
67
|
+
*/
|
|
68
|
+
async function readReleaseHtml(options, tag) {
|
|
69
|
+
const url = `${webBase(options)}/${repository(options)}/releases/expanded_assets/${encodeURIComponent(tag)}`;
|
|
70
|
+
const response = await request(url, 'text/html');
|
|
71
|
+
if (response.status !== 200)
|
|
72
|
+
throw new Error(`release ${tag} was not found (GitHub returned ${response.status})`);
|
|
73
|
+
const names = new Set();
|
|
74
|
+
const pattern = /\/releases\/download\/[^"'/]+\/([^"'?#]+)/gu;
|
|
75
|
+
for (const match of response.body.matchAll(pattern)) {
|
|
76
|
+
const name = match[1];
|
|
77
|
+
if (name !== undefined)
|
|
78
|
+
names.add(decodeURIComponent(name));
|
|
79
|
+
}
|
|
80
|
+
return Object.freeze({
|
|
81
|
+
tagName: tag,
|
|
82
|
+
publishedAt: undefined,
|
|
83
|
+
assetNames: Object.freeze([...names]),
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
async function readRelease(options, path, tag) {
|
|
87
|
+
try {
|
|
88
|
+
const payload = readReleaseJson(await requestJson(`${apiBase(options)}/repos/${repository(options)}/releases/${path}`));
|
|
89
|
+
if (payload !== undefined)
|
|
90
|
+
return payload;
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// Falls through to the release page, which is the documented fallback
|
|
94
|
+
// for a rate-limited or unavailable API.
|
|
95
|
+
}
|
|
96
|
+
return readReleaseHtml(options, tag);
|
|
97
|
+
}
|
|
98
|
+
/** `latest` has no fixed tag, so the redirect on the web release page names it. */
|
|
99
|
+
async function readLatestTag(options) {
|
|
100
|
+
try {
|
|
101
|
+
const payload = readReleaseJson(await requestJson(`${apiBase(options)}/repos/${repository(options)}/releases/latest`));
|
|
102
|
+
if (payload !== undefined)
|
|
103
|
+
return payload.tagName;
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
// Falls through to the redirect below.
|
|
107
|
+
}
|
|
108
|
+
const response = await request(`${webBase(options)}/${repository(options)}/releases/latest`, 'text/html', false);
|
|
109
|
+
const location = response.headers.location;
|
|
110
|
+
const target = typeof location === 'string' ? location : response.body;
|
|
111
|
+
const match = /\/releases\/tag\/([^"'?#\s]+)/u.exec(target);
|
|
112
|
+
if (match?.[1] === undefined)
|
|
113
|
+
throw new Error('no tagged release could be resolved for this repository');
|
|
114
|
+
return decodeURIComponent(match[1]);
|
|
115
|
+
}
|
|
116
|
+
function requireArchitectureAsset(payload, options, version) {
|
|
117
|
+
const name = archiveName(version, options.architecture);
|
|
118
|
+
if (payload.assetNames.length > 0 && !payload.assetNames.includes(name)) {
|
|
119
|
+
throw new Error(`release ${payload.tagName} has no server archive for the ${options.architecture} architecture (expected ${name})`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async function resolveTag(options, tag) {
|
|
123
|
+
const payload = await readRelease(options, `tags/${encodeURIComponent(tag)}`, tag);
|
|
124
|
+
const version = payload.tagName.replace(/^v/u, '');
|
|
125
|
+
requireArchitectureAsset(payload, options, version);
|
|
126
|
+
return Object.freeze({
|
|
127
|
+
channel: 'tag',
|
|
128
|
+
version,
|
|
129
|
+
tag: payload.tagName,
|
|
130
|
+
...(payload.publishedAt === undefined
|
|
131
|
+
? {}
|
|
132
|
+
: { publishedAt: payload.publishedAt }),
|
|
133
|
+
assets: assetUrls(options, payload.tagName, version),
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
async function resolveRolling(options) {
|
|
137
|
+
const payload = await readRelease(options, `tags/${ROLLING_TAG}`, ROLLING_TAG);
|
|
138
|
+
requireArchitectureAsset(payload, options, ROLLING_ASSET_VERSION);
|
|
139
|
+
return Object.freeze({
|
|
140
|
+
channel: 'main',
|
|
141
|
+
version: ROLLING_ASSET_VERSION,
|
|
142
|
+
tag: ROLLING_TAG,
|
|
143
|
+
...(payload.publishedAt === undefined
|
|
144
|
+
? {}
|
|
145
|
+
: { publishedAt: payload.publishedAt }),
|
|
146
|
+
assets: assetUrls(options, ROLLING_TAG, ROLLING_ASSET_VERSION),
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
async function resolveSource(options, ref) {
|
|
150
|
+
const remote = `${webBase(options)}/${repository(options)}.git`;
|
|
151
|
+
if (COMMIT_SHA.test(ref)) {
|
|
152
|
+
return Object.freeze({
|
|
153
|
+
channel: 'source',
|
|
154
|
+
version: ref.slice(0, 12),
|
|
155
|
+
revision: ref,
|
|
156
|
+
sourceRef: ref,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
let stdout;
|
|
160
|
+
try {
|
|
161
|
+
({ stdout } = await execFileAsync('git', ['ls-remote', remote, ref], {
|
|
162
|
+
timeout: 60_000,
|
|
163
|
+
}));
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
throw new Error(`could not resolve "${ref}" as a release, a branch, or a commit: ${message(error)}`);
|
|
167
|
+
}
|
|
168
|
+
const line = stdout.split('\n').find((entry) => entry.trim().length > 0);
|
|
169
|
+
const revision = line?.split(/\s+/u)[0];
|
|
170
|
+
if (revision === undefined || !/^[0-9a-f]{40}$/u.test(revision)) {
|
|
171
|
+
throw new Error(`could not resolve "${ref}" as a release, a branch, or a commit`);
|
|
172
|
+
}
|
|
173
|
+
return Object.freeze({
|
|
174
|
+
channel: 'source',
|
|
175
|
+
version: revision.slice(0, 12),
|
|
176
|
+
revision,
|
|
177
|
+
sourceRef: ref,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
function message(error) {
|
|
181
|
+
return error instanceof Error ? error.message : String(error);
|
|
182
|
+
}
|
|
183
|
+
export async function resolveRef(ref, options) {
|
|
184
|
+
if (ref === undefined)
|
|
185
|
+
return resolveTag(options, await readLatestTag(options));
|
|
186
|
+
if (ref === 'latest')
|
|
187
|
+
return resolveTag(options, await readLatestTag(options));
|
|
188
|
+
if (SEMVER_TAG.test(ref))
|
|
189
|
+
return resolveTag(options, ref);
|
|
190
|
+
if (ref === 'main')
|
|
191
|
+
return resolveRolling(options);
|
|
192
|
+
return resolveSource(options, ref);
|
|
193
|
+
}
|
|
194
|
+
//# sourceMappingURL=resolve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve.js","sourceRoot":"","sources":["../src/resolve.ts"],"names":[],"mappings":"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"}
|
package/dist/socket.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export declare const APPROVAL_SOCKET_FILENAME = "approval.sock";
|
|
2
|
+
export type ApprovalRequest = Readonly<{
|
|
3
|
+
op: 'list';
|
|
4
|
+
}> | Readonly<{
|
|
5
|
+
op: 'approve';
|
|
6
|
+
approvalId: string;
|
|
7
|
+
}> | Readonly<{
|
|
8
|
+
op: 'deny';
|
|
9
|
+
approvalId: string;
|
|
10
|
+
}> | Readonly<{
|
|
11
|
+
op: 'pairing';
|
|
12
|
+
rotate?: boolean;
|
|
13
|
+
}>;
|
|
14
|
+
export interface PendingApproval {
|
|
15
|
+
readonly approvalId: string;
|
|
16
|
+
readonly deviceName: string;
|
|
17
|
+
readonly matchCode: string;
|
|
18
|
+
readonly expiresAt: number;
|
|
19
|
+
}
|
|
20
|
+
export interface PairingHandoff {
|
|
21
|
+
readonly mode: string;
|
|
22
|
+
readonly pairingUrl: string;
|
|
23
|
+
readonly pairingExpiresAt: string;
|
|
24
|
+
readonly serverId: string;
|
|
25
|
+
}
|
|
26
|
+
export type ApprovalResponse = Readonly<{
|
|
27
|
+
ok: true;
|
|
28
|
+
pending: readonly PendingApproval[];
|
|
29
|
+
}> | Readonly<{
|
|
30
|
+
ok: true;
|
|
31
|
+
approvalId: string;
|
|
32
|
+
outcome: 'approved' | 'denied';
|
|
33
|
+
deviceName: string;
|
|
34
|
+
}> | Readonly<{
|
|
35
|
+
ok: true;
|
|
36
|
+
exposure: readonly string[] | 'off';
|
|
37
|
+
handoffs: readonly PairingHandoff[];
|
|
38
|
+
}> | Readonly<{
|
|
39
|
+
ok: false;
|
|
40
|
+
error: string;
|
|
41
|
+
}>;
|
|
42
|
+
export declare function approvalSocketPath(dataRoot: string): string;
|
|
43
|
+
export declare class SocketError extends Error {
|
|
44
|
+
}
|
|
45
|
+
export declare function sendApprovalRequest(socketPath: string, request: ApprovalRequest): Promise<ApprovalResponse>;
|
|
46
|
+
/**
|
|
47
|
+
* The socket is owner-only, so root cannot simply open a socket owned by the
|
|
48
|
+
* run-as account without dropping to it first. Re-executing this one call
|
|
49
|
+
* under `sudo -u` keeps the server's ownership check meaningful instead of
|
|
50
|
+
* relaxing the socket's permissions to work around it.
|
|
51
|
+
*/
|
|
52
|
+
export declare function sendAsUser(socketPath: string, request: ApprovalRequest, runAs: string, currentUid?: number | undefined): Promise<ApprovalResponse>;
|
|
53
|
+
export declare function requireOk(response: ApprovalResponse): Extract<ApprovalResponse, {
|
|
54
|
+
ok: true;
|
|
55
|
+
}>;
|
package/dist/socket.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { createConnection } from 'node:net';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { promisify } from 'node:util';
|
|
5
|
+
/**
|
|
6
|
+
* The client side of the server's owner-only approval socket.
|
|
7
|
+
*
|
|
8
|
+
* The socket lives inside the data root, which is the trust boundary: it is
|
|
9
|
+
* mode 0600 and owned by the account the server runs as, so being able to open
|
|
10
|
+
* it is the authority. No token crosses it, and nothing it returns carries a
|
|
11
|
+
* host key or a device key.
|
|
12
|
+
*
|
|
13
|
+
* The CLI speaks this protocol itself rather than importing the server,
|
|
14
|
+
* because the published package must not pull a native addon along with it.
|
|
15
|
+
*/
|
|
16
|
+
const execFileAsync = promisify(execFile);
|
|
17
|
+
export const APPROVAL_SOCKET_FILENAME = 'approval.sock';
|
|
18
|
+
const MAX_FRAME_BYTES = 4 * 1024;
|
|
19
|
+
const REQUEST_TIMEOUT_MS = 10_000;
|
|
20
|
+
export function approvalSocketPath(dataRoot) {
|
|
21
|
+
return join(dataRoot, APPROVAL_SOCKET_FILENAME);
|
|
22
|
+
}
|
|
23
|
+
export class SocketError extends Error {
|
|
24
|
+
}
|
|
25
|
+
export function sendApprovalRequest(socketPath, request) {
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
let buffered = '';
|
|
28
|
+
let settled = false;
|
|
29
|
+
const socket = createConnection(socketPath);
|
|
30
|
+
const timer = setTimeout(() => {
|
|
31
|
+
if (settled)
|
|
32
|
+
return;
|
|
33
|
+
settled = true;
|
|
34
|
+
socket.destroy();
|
|
35
|
+
reject(new SocketError('the running server did not answer'));
|
|
36
|
+
}, REQUEST_TIMEOUT_MS);
|
|
37
|
+
timer.unref?.();
|
|
38
|
+
socket.setEncoding('utf8');
|
|
39
|
+
socket.on('connect', () => socket.write(`${JSON.stringify(request)}\n`));
|
|
40
|
+
socket.on('data', (chunk) => {
|
|
41
|
+
buffered += chunk;
|
|
42
|
+
if (buffered.length > MAX_FRAME_BYTES)
|
|
43
|
+
socket.destroy();
|
|
44
|
+
});
|
|
45
|
+
socket.on('error', () => {
|
|
46
|
+
if (settled)
|
|
47
|
+
return;
|
|
48
|
+
settled = true;
|
|
49
|
+
clearTimeout(timer);
|
|
50
|
+
reject(new SocketError('no running server accepts commands at this data root. Start it with `terminay daemon start`.'));
|
|
51
|
+
});
|
|
52
|
+
socket.on('close', () => {
|
|
53
|
+
if (settled)
|
|
54
|
+
return;
|
|
55
|
+
settled = true;
|
|
56
|
+
clearTimeout(timer);
|
|
57
|
+
try {
|
|
58
|
+
resolve(JSON.parse(buffered.trim()));
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
reject(new SocketError('the running server returned an unreadable response'));
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The socket is owner-only, so root cannot simply open a socket owned by the
|
|
68
|
+
* run-as account without dropping to it first. Re-executing this one call
|
|
69
|
+
* under `sudo -u` keeps the server's ownership check meaningful instead of
|
|
70
|
+
* relaxing the socket's permissions to work around it.
|
|
71
|
+
*/
|
|
72
|
+
export async function sendAsUser(socketPath, request, runAs, currentUid = process.getuid?.()) {
|
|
73
|
+
const needsDrop = currentUid === 0 && runAs !== 'root';
|
|
74
|
+
if (!needsDrop)
|
|
75
|
+
return sendApprovalRequest(socketPath, request);
|
|
76
|
+
const script = new URL('./socketClient.js', import.meta.url).pathname;
|
|
77
|
+
try {
|
|
78
|
+
const { stdout } = await execFileAsync('sudo', [
|
|
79
|
+
'-n',
|
|
80
|
+
'-u',
|
|
81
|
+
runAs,
|
|
82
|
+
process.execPath,
|
|
83
|
+
script,
|
|
84
|
+
socketPath,
|
|
85
|
+
JSON.stringify(request),
|
|
86
|
+
], { timeout: REQUEST_TIMEOUT_MS + 5_000, maxBuffer: MAX_FRAME_BYTES });
|
|
87
|
+
return JSON.parse(stdout.trim());
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
throw new SocketError(`could not reach the server's socket as ${runAs}: ${error instanceof Error ? error.message : String(error)}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
export function requireOk(response) {
|
|
94
|
+
if (response.ok !== true)
|
|
95
|
+
throw new SocketError(response.error);
|
|
96
|
+
return response;
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=socket.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"socket.js","sourceRoot":"","sources":["../src/socket.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { sendApprovalRequest } from './socket.js';
|
|
2
|
+
/**
|
|
3
|
+
* The one-shot helper `sendAsUser` re-executes under `sudo -u <run-as>`.
|
|
4
|
+
*
|
|
5
|
+
* It exists so a root operator can reach a socket that only the run-as account
|
|
6
|
+
* may open, without the CLI ever loosening the socket's permissions. It writes
|
|
7
|
+
* exactly the server's response to stdout and nothing else.
|
|
8
|
+
*/
|
|
9
|
+
const [socketPath, encodedRequest] = process.argv.slice(2);
|
|
10
|
+
if (socketPath === undefined || encodedRequest === undefined) {
|
|
11
|
+
process.stderr.write('usage: socketClient.js <socket-path> <json-request>\n');
|
|
12
|
+
process.exit(2);
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
const response = await sendApprovalRequest(socketPath, JSON.parse(encodedRequest));
|
|
16
|
+
process.stdout.write(`${JSON.stringify(response)}\n`);
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=socketClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"socketClient.js","sourceRoot":"","sources":["../src/socketClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAElD;;;;;;GAMG;AAEH,MAAM,CAAC,UAAU,EAAE,cAAc,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAE3D,IAAI,UAAU,KAAK,SAAS,IAAI,cAAc,KAAK,SAAS,EAAE,CAAC;IAC9D,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,uDAAuD,CAAC,CAAC;IAC9E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACjB,CAAC;AAED,IAAI,CAAC;IACJ,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CACzC,UAAU,EACV,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,CAC1B,CAAC;IACF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AACvD,CAAC;AAAC,OAAO,KAAK,EAAE,CAAC;IAChB,OAAO,CAAC,MAAM,CAAC,KAAK,CACnB,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAC7D,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACjB,CAAC"}
|
package/dist/source.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export declare const REQUIRED_TOOLS: readonly string[];
|
|
2
|
+
export declare class ToolchainError extends Error {
|
|
3
|
+
}
|
|
4
|
+
export declare class SourceBuildError extends Error {
|
|
5
|
+
}
|
|
6
|
+
export declare function preflightToolchain(tools?: readonly string[], env?: NodeJS.ProcessEnv): Promise<void>;
|
|
7
|
+
export interface SourceBuildOptions {
|
|
8
|
+
readonly ref: string;
|
|
9
|
+
readonly revision?: string;
|
|
10
|
+
readonly repository?: string;
|
|
11
|
+
readonly write?: (line: string) => void;
|
|
12
|
+
readonly workingDirectory?: string;
|
|
13
|
+
/** Where the finished archive is placed before the build tree is removed. */
|
|
14
|
+
readonly destination?: string;
|
|
15
|
+
readonly env?: Readonly<Record<string, string | undefined>>;
|
|
16
|
+
readonly architecture?: 'x64' | 'arm64';
|
|
17
|
+
readonly keepOnFailure?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface SourceBuildResult {
|
|
20
|
+
readonly archivePath: string;
|
|
21
|
+
readonly revision: string;
|
|
22
|
+
readonly buildDirectory: string;
|
|
23
|
+
}
|
|
24
|
+
export declare function buildFromSource(options: SourceBuildOptions): Promise<SourceBuildResult>;
|