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.
Files changed (79) hide show
  1. package/dist/account.d.ts +31 -0
  2. package/dist/account.js +150 -0
  3. package/dist/account.js.map +1 -0
  4. package/dist/address.d.ts +14 -0
  5. package/dist/address.js +58 -0
  6. package/dist/address.js.map +1 -0
  7. package/dist/args.d.ts +37 -0
  8. package/dist/args.js +252 -0
  9. package/dist/args.js.map +1 -0
  10. package/dist/cli.d.ts +2 -0
  11. package/dist/cli.js +92 -0
  12. package/dist/cli.js.map +1 -0
  13. package/dist/commands/install.d.ts +48 -0
  14. package/dist/commands/install.js +213 -0
  15. package/dist/commands/install.js.map +1 -0
  16. package/dist/commands/lifecycle.d.ts +23 -0
  17. package/dist/commands/lifecycle.js +66 -0
  18. package/dist/commands/lifecycle.js.map +1 -0
  19. package/dist/commands/pairing.d.ts +23 -0
  20. package/dist/commands/pairing.js +143 -0
  21. package/dist/commands/pairing.js.map +1 -0
  22. package/dist/commands/uninstall.d.ts +26 -0
  23. package/dist/commands/uninstall.js +70 -0
  24. package/dist/commands/uninstall.js.map +1 -0
  25. package/dist/commands/upgrade.d.ts +30 -0
  26. package/dist/commands/upgrade.js +175 -0
  27. package/dist/commands/upgrade.js.map +1 -0
  28. package/dist/context.d.ts +26 -0
  29. package/dist/context.js +37 -0
  30. package/dist/context.js.map +1 -0
  31. package/dist/download.d.ts +11 -0
  32. package/dist/download.js +95 -0
  33. package/dist/download.js.map +1 -0
  34. package/dist/health.d.ts +18 -0
  35. package/dist/health.js +61 -0
  36. package/dist/health.js.map +1 -0
  37. package/dist/http.d.ts +11 -0
  38. package/dist/http.js +88 -0
  39. package/dist/http.js.map +1 -0
  40. package/dist/index.d.ts +12 -0
  41. package/dist/index.js +11 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/install.d.ts +30 -0
  44. package/dist/install.js +100 -0
  45. package/dist/install.js.map +1 -0
  46. package/dist/layout.d.ts +52 -0
  47. package/dist/layout.js +96 -0
  48. package/dist/layout.js.map +1 -0
  49. package/dist/manifest.d.ts +46 -0
  50. package/dist/manifest.js +115 -0
  51. package/dist/manifest.js.map +1 -0
  52. package/dist/platform.d.ts +17 -0
  53. package/dist/platform.js +34 -0
  54. package/dist/platform.js.map +1 -0
  55. package/dist/prompt.d.ts +20 -0
  56. package/dist/prompt.js +47 -0
  57. package/dist/prompt.js.map +1 -0
  58. package/dist/resolve.d.ts +35 -0
  59. package/dist/resolve.js +194 -0
  60. package/dist/resolve.js.map +1 -0
  61. package/dist/socket.d.ts +55 -0
  62. package/dist/socket.js +98 -0
  63. package/dist/socket.js.map +1 -0
  64. package/dist/socketClient.d.ts +1 -0
  65. package/dist/socketClient.js +22 -0
  66. package/dist/socketClient.js.map +1 -0
  67. package/dist/source.d.ts +24 -0
  68. package/dist/source.js +152 -0
  69. package/dist/source.js.map +1 -0
  70. package/dist/systemd.d.ts +30 -0
  71. package/dist/systemd.js +86 -0
  72. package/dist/systemd.js.map +1 -0
  73. package/dist/unit.d.ts +37 -0
  74. package/dist/unit.js +74 -0
  75. package/dist/unit.js.map +1 -0
  76. package/dist/verify.d.ts +24 -0
  77. package/dist/verify.js +46 -0
  78. package/dist/verify.js.map +1 -0
  79. package/package.json +49 -0
package/dist/source.js ADDED
@@ -0,0 +1,152 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { cp, mkdtemp, readdir, rm } from 'node:fs/promises';
3
+ import { tmpdir } from 'node:os';
4
+ import { basename, join } from 'node:path';
5
+ import { promisify } from 'node:util';
6
+ import { downloadAsset } from './download.js';
7
+ import { hostArchitecture } from './platform.js';
8
+ import { DEFAULT_REPOSITORY } from './resolve.js';
9
+ /**
10
+ * Building a server archive on the target when the reference is a branch or a
11
+ * commit rather than a release.
12
+ *
13
+ * There is no publisher for such a build, so there is no signature to check —
14
+ * the operator asked for this exact commit and the machine built it locally,
15
+ * which is a different trust story from a downloaded artifact rather than a
16
+ * weaker one. The manifest is still verified when it is installed, so a build
17
+ * that produced a broken tree is caught before it is activated.
18
+ *
19
+ * The toolchain is checked before anything is downloaded, because finding out
20
+ * a compiler is missing after a shallow clone and a Node download wastes the
21
+ * operator's time and bandwidth.
22
+ */
23
+ const execFileAsync = promisify(execFile);
24
+ export const REQUIRED_TOOLS = [
25
+ 'git',
26
+ 'python3',
27
+ 'make',
28
+ 'c++',
29
+ ];
30
+ const BUILD_TIMEOUT_MS = 45 * 60 * 1000;
31
+ export class ToolchainError extends Error {
32
+ }
33
+ export class SourceBuildError extends Error {
34
+ }
35
+ async function which(tool, env) {
36
+ try {
37
+ await execFileAsync('command', ['-v', tool], {
38
+ timeout: 30_000,
39
+ shell: '/bin/sh',
40
+ ...(env === undefined ? {} : { env }),
41
+ });
42
+ return true;
43
+ }
44
+ catch {
45
+ return false;
46
+ }
47
+ }
48
+ export async function preflightToolchain(tools = REQUIRED_TOOLS, env) {
49
+ const missing = [];
50
+ for (const tool of tools) {
51
+ if (!(await which(tool, env)))
52
+ missing.push(tool);
53
+ }
54
+ if (missing.length > 0) {
55
+ throw new ToolchainError(`building from source needs ${missing.join(', ')}, which ${missing.length === 1 ? 'is' : 'are'} not installed. Install ${missing.length === 1 ? 'it' : 'them'} (on Debian: apt install build-essential python3 git) or install a released version instead.`);
56
+ }
57
+ }
58
+ async function run(command, args, cwd, env) {
59
+ const { stdout } = await execFileAsync(command, [...args], {
60
+ cwd,
61
+ timeout: BUILD_TIMEOUT_MS,
62
+ maxBuffer: 64 * 1024 * 1024,
63
+ ...(env === undefined ? {} : { env }),
64
+ });
65
+ return stdout;
66
+ }
67
+ export async function buildFromSource(options) {
68
+ const write = options.write ?? (() => undefined);
69
+ const env = options.env;
70
+ const architecture = options.architecture ?? hostArchitecture();
71
+ const target = `linux-${architecture}`;
72
+ // Before any download: a missing compiler must not cost a clone.
73
+ await preflightToolchain(REQUIRED_TOOLS, env);
74
+ const parent = options.workingDirectory ?? tmpdir();
75
+ const build = await mkdtemp(join(parent, 'terminay-source-build-'));
76
+ let succeeded = false;
77
+ try {
78
+ const remote = `https://github.com/${options.repository ?? DEFAULT_REPOSITORY}.git`;
79
+ write(`Cloning ${options.ref} …`);
80
+ const checkout = join(build, 'source');
81
+ // A commit cannot be cloned by branch, so it is fetched and checked out.
82
+ if (options.revision !== undefined && /^[0-9a-f]{40}$/u.test(options.ref)) {
83
+ await run('git', ['init', checkout], build, env);
84
+ await run('git', ['remote', 'add', 'origin', remote], checkout, env);
85
+ await run('git', ['fetch', '--depth', '1', 'origin', options.ref], checkout, env);
86
+ await run('git', ['checkout', '--detach', 'FETCH_HEAD'], checkout, env);
87
+ }
88
+ else {
89
+ await run('git', ['clone', '--depth', '1', '--branch', options.ref, remote, checkout], build, env);
90
+ }
91
+ const revision = (await run('git', ['rev-parse', 'HEAD'], checkout, env)).trim();
92
+ write('Installing dependencies …');
93
+ await run('npm', ['ci'], checkout, env);
94
+ write('Compiling …');
95
+ await run('npm', ['run', 'build:application-graph'], checkout, env);
96
+ await run('npm', ['run', 'build:server-postcompile'], checkout, env);
97
+ await run('node', ['scripts/stage-selected-secure-werift-runtime.mjs'], checkout, env);
98
+ write('Fetching the pinned Node runtime …');
99
+ const nodeArchiveUrl = (await run('node', [
100
+ '-e',
101
+ 'import("./scripts/pty-runtime-platforms.mjs").then((m) => process.stdout.write(m.getPtyRuntimePlatform(process.argv[1]).nodeArchive))',
102
+ target,
103
+ ], checkout, env)).trim();
104
+ // The builder re-verifies this archive's pinned digest, so a wrong or
105
+ // tampered download fails there rather than being baked in.
106
+ const nodeArchive = await downloadAsset(nodeArchiveUrl, build, 'node-runtime.tar.xz');
107
+ write('Building the standalone archive …');
108
+ const output = join(build, 'artifact');
109
+ const built = await run('node', [
110
+ 'scripts/build-standalone-server-artifact.mjs',
111
+ '--target',
112
+ target,
113
+ '--channel',
114
+ 'source',
115
+ '--revision',
116
+ revision,
117
+ '--node-archive',
118
+ nodeArchive.path,
119
+ '--runtime-modules',
120
+ 'node_modules',
121
+ '--webrtc-runtime',
122
+ 'build/webrtc-runtime',
123
+ '--output-dir',
124
+ output,
125
+ ], checkout, env);
126
+ const archivePath = JSON.parse(built).archivePath;
127
+ const produced = await readdir(output).catch(() => []);
128
+ if (typeof archivePath !== 'string' || produced.length === 0) {
129
+ throw new SourceBuildError('the source build produced no archive');
130
+ }
131
+ // Moved out of the build tree before that tree is removed, so the
132
+ // caller has something to install from.
133
+ const kept = join(options.destination ?? parent, basename(archivePath));
134
+ await rm(kept, { force: true });
135
+ await cp(archivePath, kept);
136
+ succeeded = true;
137
+ return Object.freeze({
138
+ archivePath: kept,
139
+ revision,
140
+ buildDirectory: build,
141
+ });
142
+ }
143
+ catch (error) {
144
+ throw new SourceBuildError(`building ${options.ref} from source failed: ${error instanceof Error ? error.message : String(error)}. The build directory was kept at ${build}.`);
145
+ }
146
+ finally {
147
+ // Kept on failure so the operator can read the compiler's output.
148
+ if (succeeded && options.keepOnFailure !== true)
149
+ await rm(build, { recursive: true, force: true }).catch(() => undefined);
150
+ }
151
+ }
152
+ //# sourceMappingURL=source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source.js","sourceRoot":"","sources":["../src/source.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import type { InstallScope } from './args.js';
2
+ export interface SystemdOptions {
3
+ readonly scope: InstallScope;
4
+ readonly env?: Readonly<Record<string, string | undefined>>;
5
+ }
6
+ export interface CommandResult {
7
+ readonly code: number;
8
+ readonly stdout: string;
9
+ readonly stderr: string;
10
+ }
11
+ export declare function createSystemd(options: SystemdOptions): {
12
+ daemonReload: () => Promise<CommandResult>;
13
+ enableNow: () => Promise<CommandResult>;
14
+ enable: () => Promise<CommandResult>;
15
+ disable: () => Promise<CommandResult>;
16
+ start: () => Promise<CommandResult>;
17
+ stop: () => Promise<CommandResult>;
18
+ resetFailed: () => Promise<CommandResult>;
19
+ isActive(): Promise<boolean>;
20
+ isEnabled(): Promise<boolean>;
21
+ state(): Promise<string>;
22
+ /** The last few journal lines, for a failure the operator has to read. */
23
+ journal(lines?: number): Promise<string>;
24
+ /**
25
+ * A user unit stops when its owner logs out unless lingering is on, so a
26
+ * user-scope server would die the moment the operator disconnected.
27
+ */
28
+ enableLinger(user: string): Promise<CommandResult>;
29
+ };
30
+ export type Systemd = ReturnType<typeof createSystemd>;
@@ -0,0 +1,86 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+ import { UNIT_NAME } from './layout.js';
4
+ /**
5
+ * A thin wrapper over `systemctl`, `loginctl`, and `journalctl`.
6
+ *
7
+ * Nothing here reimplements systemd or talks to its bus: the CLI shells out to
8
+ * the tools already on the box. That is also what makes the rest of the CLI
9
+ * testable off a systemd host, because a stub earlier on PATH answers instead.
10
+ */
11
+ const execFileAsync = promisify(execFile);
12
+ const TIMEOUT_MS = 120_000;
13
+ function scopeArguments(scope) {
14
+ return scope === 'user' ? ['--user'] : [];
15
+ }
16
+ async function run(command, args, options) {
17
+ try {
18
+ const { stdout, stderr } = await execFileAsync(command, [...args], {
19
+ timeout: TIMEOUT_MS,
20
+ maxBuffer: 4 * 1024 * 1024,
21
+ ...(options.env === undefined
22
+ ? {}
23
+ : { env: options.env }),
24
+ });
25
+ return Object.freeze({ code: 0, stdout, stderr });
26
+ }
27
+ catch (error) {
28
+ const failure = error;
29
+ // `systemctl is-active` exits non-zero to answer the question, so the
30
+ // exit status is returned rather than thrown for every caller to handle.
31
+ return Object.freeze({
32
+ code: typeof failure.code === 'number' ? failure.code : 1,
33
+ stdout: failure.stdout ?? '',
34
+ stderr: failure.stderr ?? failure.message ?? '',
35
+ });
36
+ }
37
+ }
38
+ export function createSystemd(options) {
39
+ const systemctl = (args) => run('systemctl', [...scopeArguments(options.scope), ...args], options);
40
+ const expect = async (args) => {
41
+ const result = await systemctl(args);
42
+ if (result.code !== 0) {
43
+ throw new Error(`systemctl ${args.join(' ')} failed: ${(result.stderr || result.stdout).trim()}`);
44
+ }
45
+ return result;
46
+ };
47
+ return {
48
+ daemonReload: () => expect(['daemon-reload']),
49
+ enableNow: () => expect(['enable', '--now', UNIT_NAME]),
50
+ enable: () => expect(['enable', UNIT_NAME]),
51
+ disable: () => systemctl(['disable', UNIT_NAME]),
52
+ start: () => expect(['start', UNIT_NAME]),
53
+ stop: () => expect(['stop', UNIT_NAME]),
54
+ resetFailed: () => systemctl(['reset-failed', UNIT_NAME]),
55
+ async isActive() {
56
+ return (await systemctl(['is-active', UNIT_NAME])).code === 0;
57
+ },
58
+ async isEnabled() {
59
+ return (await systemctl(['is-enabled', UNIT_NAME])).code === 0;
60
+ },
61
+ async state() {
62
+ const result = await systemctl(['is-active', UNIT_NAME]);
63
+ return (result.stdout || result.stderr).trim() || 'unknown';
64
+ },
65
+ /** The last few journal lines, for a failure the operator has to read. */
66
+ async journal(lines = 20) {
67
+ const result = await run('journalctl', [
68
+ ...scopeArguments(options.scope),
69
+ '-u',
70
+ UNIT_NAME,
71
+ '--no-pager',
72
+ '-n',
73
+ String(lines),
74
+ ], options);
75
+ return (result.stdout || result.stderr).trim();
76
+ },
77
+ /**
78
+ * A user unit stops when its owner logs out unless lingering is on, so a
79
+ * user-scope server would die the moment the operator disconnected.
80
+ */
81
+ async enableLinger(user) {
82
+ return run('loginctl', ['enable-linger', user], options);
83
+ },
84
+ };
85
+ }
86
+ //# sourceMappingURL=systemd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"systemd.js","sourceRoot":"","sources":["../src/systemd.ts"],"names":[],"mappings":"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"}
package/dist/unit.d.ts ADDED
@@ -0,0 +1,37 @@
1
+ import type { InstallLayout } from './layout.js';
2
+ /**
3
+ * The unit and environment file the installer writes.
4
+ *
5
+ * Both are generated from these templates on every install and upgrade, with
6
+ * one exception: the server id is written once and never rewritten, because it
7
+ * is the identity paired devices know the machine by. Rewriting it on upgrade
8
+ * would silently unpair every device.
9
+ *
10
+ * Neither file ever carries a vault passphrase, a device key, or pairing
11
+ * material. Those live in the data root, which is the trust boundary; a unit
12
+ * file is world-readable on most systems and an environment file is visible to
13
+ * anything that can read the service's configuration.
14
+ */
15
+ export interface ServiceConfiguration {
16
+ readonly serverId: string;
17
+ readonly dataRoot: string;
18
+ readonly projectRoot: string;
19
+ readonly port: number;
20
+ readonly healthPort: number;
21
+ readonly expose: string;
22
+ readonly hostedDomain: string;
23
+ readonly directOrigin?: string;
24
+ readonly uiBundle: string;
25
+ }
26
+ export declare function renderEnvironmentFile(configuration: ServiceConfiguration): string;
27
+ /**
28
+ * Parse an existing environment file so a reinstall can keep the values it is
29
+ * not being asked to change — the server id above all.
30
+ */
31
+ export declare function parseEnvironmentFile(contents: string): Readonly<Record<string, string>>;
32
+ export interface UnitConfiguration {
33
+ readonly layout: InstallLayout;
34
+ readonly runAs: string;
35
+ readonly workingDirectory: string;
36
+ }
37
+ export declare function renderUnit(configuration: UnitConfiguration): string;
package/dist/unit.js ADDED
@@ -0,0 +1,74 @@
1
+ export function renderEnvironmentFile(configuration) {
2
+ const lines = [
3
+ '# Written by `terminay daemon install`. Edit and restart the service to change it.',
4
+ '# No passphrase, device key, or pairing token belongs in this file.',
5
+ `TERMINAY_SERVER_ID=${configuration.serverId}`,
6
+ `TERMINAY_DATA_ROOT=${configuration.dataRoot}`,
7
+ `TERMINAY_PROJECT_ROOT=${configuration.projectRoot}`,
8
+ 'TERMINAY_HTTP_HOST=0.0.0.0',
9
+ `TERMINAY_HTTP_PORT=${configuration.port}`,
10
+ // Health and readiness stay on loopback: they are an operator surface,
11
+ // not part of what the server exposes to devices.
12
+ 'TERMINAY_HEALTH_HOST=127.0.0.1',
13
+ `TERMINAY_HEALTH_PORT=${configuration.healthPort}`,
14
+ `TERMINAY_EXPOSE=${configuration.expose}`,
15
+ `TERMINAY_HOSTED_DOMAIN=${configuration.hostedDomain}`,
16
+ ...(configuration.directOrigin === undefined
17
+ ? []
18
+ : [`TERMINAY_DIRECT_ORIGIN=${configuration.directOrigin}`]),
19
+ 'TERMINAY_AGENT_INTEGRATION=enabled',
20
+ 'TERMINAY_AI_PROVIDERS=disabled',
21
+ 'TERMINAY_LOG_SINK=journal',
22
+ `TERMINAY_UI_BUNDLE=${configuration.uiBundle}`,
23
+ ];
24
+ return `${lines.join('\n')}\n`;
25
+ }
26
+ /**
27
+ * Parse an existing environment file so a reinstall can keep the values it is
28
+ * not being asked to change — the server id above all.
29
+ */
30
+ export function parseEnvironmentFile(contents) {
31
+ const values = {};
32
+ for (const line of contents.split('\n')) {
33
+ const trimmed = line.trim();
34
+ if (trimmed.length === 0 || trimmed.startsWith('#'))
35
+ continue;
36
+ const equals = trimmed.indexOf('=');
37
+ if (equals <= 0)
38
+ continue;
39
+ values[trimmed.slice(0, equals)] = trimmed.slice(equals + 1);
40
+ }
41
+ return Object.freeze(values);
42
+ }
43
+ export function renderUnit(configuration) {
44
+ const { layout } = configuration;
45
+ // System scope names the account explicitly; a user unit already runs as
46
+ // its owner, and User=/Group= are rejected there.
47
+ const account = layout.scope === 'system'
48
+ ? [`User=${configuration.runAs}`, `Group=${configuration.runAs}`]
49
+ : [];
50
+ return `[Unit]
51
+ Description=Terminay Server
52
+ Documentation=https://terminay.com/docs/installation
53
+ After=network-online.target
54
+ Wants=network-online.target
55
+
56
+ [Service]
57
+ Type=simple
58
+ ${account.join('\n')}${account.length > 0 ? '\n' : ''}WorkingDirectory=${configuration.workingDirectory}
59
+ EnvironmentFile=${layout.environmentFile}
60
+ ExecStart=${layout.currentLink}/bin/terminay-server
61
+ Restart=on-failure
62
+ RestartSec=5s
63
+ KillSignal=SIGTERM
64
+ TimeoutStopSec=15s
65
+ NoNewPrivileges=true
66
+ PrivateTmp=true
67
+ StandardOutput=journal
68
+ StandardError=journal
69
+
70
+ [Install]
71
+ WantedBy=${layout.scope === 'system' ? 'multi-user.target' : 'default.target'}
72
+ `;
73
+ }
74
+ //# sourceMappingURL=unit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unit.js","sourceRoot":"","sources":["../src/unit.ts"],"names":[],"mappings":"AA4BA,MAAM,UAAU,qBAAqB,CACpC,aAAmC;IAEnC,MAAM,KAAK,GAAG;QACb,oFAAoF;QACpF,qEAAqE;QACrE,sBAAsB,aAAa,CAAC,QAAQ,EAAE;QAC9C,sBAAsB,aAAa,CAAC,QAAQ,EAAE;QAC9C,yBAAyB,aAAa,CAAC,WAAW,EAAE;QACpD,4BAA4B;QAC5B,sBAAsB,aAAa,CAAC,IAAI,EAAE;QAC1C,uEAAuE;QACvE,kDAAkD;QAClD,gCAAgC;QAChC,wBAAwB,aAAa,CAAC,UAAU,EAAE;QAClD,mBAAmB,aAAa,CAAC,MAAM,EAAE;QACzC,0BAA0B,aAAa,CAAC,YAAY,EAAE;QACtD,GAAG,CAAC,aAAa,CAAC,YAAY,KAAK,SAAS;YAC3C,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC,CAAC,0BAA0B,aAAa,CAAC,YAAY,EAAE,CAAC,CAAC;QAC5D,oCAAoC;QACpC,gCAAgC;QAChC,2BAA2B;QAC3B,sBAAsB,aAAa,CAAC,QAAQ,EAAE;KAC9C,CAAC;IACF,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;AAChC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CACnC,QAAgB;IAEhB,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACzC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QAC9D,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACpC,IAAI,MAAM,IAAI,CAAC;YAAE,SAAS;QAC1B,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC9D,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;AAC9B,CAAC;AAQD,MAAM,UAAU,UAAU,CAAC,aAAgC;IAC1D,MAAM,EAAE,MAAM,EAAE,GAAG,aAAa,CAAC;IACjC,yEAAyE;IACzE,kDAAkD;IAClD,MAAM,OAAO,GACZ,MAAM,CAAC,KAAK,KAAK,QAAQ;QACxB,CAAC,CAAC,CAAC,QAAQ,aAAa,CAAC,KAAK,EAAE,EAAE,SAAS,aAAa,CAAC,KAAK,EAAE,CAAC;QACjE,CAAC,CAAC,EAAE,CAAC;IACP,OAAO;;;;;;;;EAQN,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,oBAAoB,aAAa,CAAC,gBAAgB;kBACrF,MAAM,CAAC,eAAe;YAC5B,MAAM,CAAC,WAAW;;;;;;;;;;;WAWnB,MAAM,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,gBAAgB;CAC5E,CAAC;AACF,CAAC"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The boundary this crosses is "release pipeline → operator's machine", and
3
+ * the embedded key is the only thing that makes the download trustworthy. So
4
+ * there is no skip flag and no environment override: if verification cannot
5
+ * pass, the install does not happen. A release that cannot be signed is a
6
+ * release to block, not a check to relax.
7
+ */
8
+ export declare const RELEASE_PUBLIC_KEY_PEM = "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAYC52lwOqFefLXCpv9GcplfMj8/+QDjA1Rh95vEZlLcg=\n-----END PUBLIC KEY-----\n";
9
+ export declare class VerificationError extends Error {
10
+ }
11
+ /** The sidecar is `<sha256> <filename>`, as `release-checksum.mjs` writes it. */
12
+ export declare function parseChecksumSidecar(contents: string): string;
13
+ export declare function releasePublicKey(pem?: string): import("crypto").KeyObject;
14
+ export interface VerifyArchiveInput {
15
+ readonly archivePath: string;
16
+ readonly sidecar: string;
17
+ readonly signature: Buffer;
18
+ /** Present when the archive was already hashed while downloading. */
19
+ readonly digest?: string;
20
+ readonly publicKeyPem?: string;
21
+ }
22
+ export declare function verifyArchive(input: VerifyArchiveInput): Promise<{
23
+ readonly sha256: string;
24
+ }>;
package/dist/verify.js ADDED
@@ -0,0 +1,46 @@
1
+ import { createPublicKey, verify } from 'node:crypto';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { hashFile } from './download.js';
4
+ /**
5
+ * The boundary this crosses is "release pipeline → operator's machine", and
6
+ * the embedded key is the only thing that makes the download trustworthy. So
7
+ * there is no skip flag and no environment override: if verification cannot
8
+ * pass, the install does not happen. A release that cannot be signed is a
9
+ * release to block, not a check to relax.
10
+ */
11
+ export const RELEASE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
12
+ MCowBQYDK2VwAyEAYC52lwOqFefLXCpv9GcplfMj8/+QDjA1Rh95vEZlLcg=
13
+ -----END PUBLIC KEY-----
14
+ `;
15
+ export class VerificationError extends Error {
16
+ }
17
+ /** The sidecar is `<sha256> <filename>`, as `release-checksum.mjs` writes it. */
18
+ export function parseChecksumSidecar(contents) {
19
+ const match = /^([0-9a-f]{64})\s/u.exec(contents.trim());
20
+ if (match?.[1] === undefined)
21
+ throw new VerificationError('the published checksum sidecar is not readable');
22
+ return match[1];
23
+ }
24
+ export function releasePublicKey(pem = RELEASE_PUBLIC_KEY_PEM) {
25
+ const key = createPublicKey(pem);
26
+ if (key.asymmetricKeyType !== 'ed25519')
27
+ throw new VerificationError('the embedded release key is not an Ed25519 key');
28
+ return key;
29
+ }
30
+ export async function verifyArchive(input) {
31
+ const expected = parseChecksumSidecar(input.sidecar);
32
+ const actual = input.digest ?? (await hashFile(input.archivePath));
33
+ if (actual !== expected) {
34
+ throw new VerificationError(`the downloaded archive does not match its published checksum (expected ${expected}, got ${actual})`);
35
+ }
36
+ // Signing the archive bytes, not the sidecar, is what stops an attacker who
37
+ // can replace both the asset and its checksum. Ed25519 signs a whole
38
+ // message, so the archive is read in full here, exactly as the release
39
+ // pipeline reads it when signing.
40
+ const payload = await readFile(input.archivePath);
41
+ if (!verify(null, payload, releasePublicKey(input.publicKeyPem), input.signature)) {
42
+ throw new VerificationError('the downloaded archive is not signed by the Terminay release key');
43
+ }
44
+ return Object.freeze({ sha256: actual });
45
+ }
46
+ //# sourceMappingURL=verify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify.js","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC;;;;;;GAMG;AAEH,MAAM,CAAC,MAAM,sBAAsB,GAAG;;;CAGrC,CAAC;AAEF,MAAM,OAAO,iBAAkB,SAAQ,KAAK;CAAG;AAE/C,kFAAkF;AAClF,MAAM,UAAU,oBAAoB,CAAC,QAAgB;IACpD,MAAM,KAAK,GAAG,oBAAoB,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IACzD,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,SAAS;QAC3B,MAAM,IAAI,iBAAiB,CAC1B,gDAAgD,CAChD,CAAC;IACH,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,MAAc,sBAAsB;IACpE,MAAM,GAAG,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;IACjC,IAAI,GAAG,CAAC,iBAAiB,KAAK,SAAS;QACtC,MAAM,IAAI,iBAAiB,CAC1B,gDAAgD,CAChD,CAAC;IACH,OAAO,GAAG,CAAC;AACZ,CAAC;AAWD,MAAM,CAAC,KAAK,UAAU,aAAa,CAClC,KAAyB;IAEzB,MAAM,QAAQ,GAAG,oBAAoB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACrD,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,IAAI,CAAC,MAAM,QAAQ,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC;IACnE,IAAI,MAAM,KAAK,QAAQ,EAAE,CAAC;QACzB,MAAM,IAAI,iBAAiB,CAC1B,0EAA0E,QAAQ,SAAS,MAAM,GAAG,CACpG,CAAC;IACH,CAAC;IACD,4EAA4E;IAC5E,qEAAqE;IACrE,uEAAuE;IACvE,kCAAkC;IAClC,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IAClD,IACC,CAAC,MAAM,CACN,IAAI,EACJ,OAAO,EACP,gBAAgB,CAAC,KAAK,CAAC,YAAY,CAAC,EACpC,KAAK,CAAC,SAAS,CACf,EACA,CAAC;QACF,MAAM,IAAI,iBAAiB,CAC1B,kEAAkE,CAClE,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;AAC1C,CAAC"}
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "terminay",
3
+ "version": "4.2.0",
4
+ "type": "module",
5
+ "description": "Installs and controls a headless Terminay Server on Linux.",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/markwylde/terminay.git",
10
+ "directory": "apps/terminay-cli"
11
+ },
12
+ "homepage": "https://terminay.com",
13
+ "keywords": [
14
+ "terminay",
15
+ "terminal",
16
+ "daemon",
17
+ "systemd",
18
+ "cli"
19
+ ],
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/index.js",
24
+ "default": "./dist/index.js"
25
+ }
26
+ },
27
+ "files": [
28
+ "dist"
29
+ ],
30
+ "bin": {
31
+ "terminay": "dist/cli.js"
32
+ },
33
+ "deterministicBuild": true,
34
+ "dependencies": {
35
+ "qrcode": "1.5.4"
36
+ },
37
+ "engines": {
38
+ "node": ">=20"
39
+ },
40
+ "scripts": {
41
+ "build": "tsc -p tsconfig.json",
42
+ "typecheck": "tsc -p tsconfig.json --noEmit",
43
+ "test": "npm run build && node --test test/*.test.mjs",
44
+ "test:ci": "node --test test/*.test.mjs"
45
+ },
46
+ "devDependencies": {
47
+ "selfsigned": "^5.5.0"
48
+ }
49
+ }