@hakimedes/dsh-easyremote 0.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/LICENSE +21 -0
- package/README.md +17 -0
- package/dist/android.js +50 -0
- package/dist/android.js.map +1 -0
- package/dist/autostart.js +104 -0
- package/dist/autostart.js.map +1 -0
- package/dist/cli-views.js +32 -0
- package/dist/cli-views.js.map +1 -0
- package/dist/cli.js +584 -0
- package/dist/cli.js.map +1 -0
- package/dist/cloudflared.js +54 -0
- package/dist/cloudflared.js.map +1 -0
- package/dist/command-router.js +12 -0
- package/dist/command-router.js.map +1 -0
- package/dist/connector-install.js +42 -0
- package/dist/connector-install.js.map +1 -0
- package/dist/controller.js +206 -0
- package/dist/controller.js.map +1 -0
- package/dist/doctor.js +46 -0
- package/dist/doctor.js.map +1 -0
- package/dist/domain.js +33 -0
- package/dist/domain.js.map +1 -0
- package/dist/download.js +59 -0
- package/dist/download.js.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/install-state.js +48 -0
- package/dist/install-state.js.map +1 -0
- package/dist/local-runtime.js +115 -0
- package/dist/local-runtime.js.map +1 -0
- package/dist/maintenance.js +58 -0
- package/dist/maintenance.js.map +1 -0
- package/dist/named-tunnel.js +49 -0
- package/dist/named-tunnel.js.map +1 -0
- package/dist/runtime.js +110 -0
- package/dist/runtime.js.map +1 -0
- package/dist/setup-progress.js +23 -0
- package/dist/setup-progress.js.map +1 -0
- package/dist/supervisor.js +81 -0
- package/dist/supervisor.js.map +1 -0
- package/dist/wizard.js +251 -0
- package/dist/wizard.js.map +1 -0
- package/package.json +56 -0
- package/runtime/connector/README.md +52 -0
- package/runtime/connector/cordis.patch.yml +3 -0
- package/runtime/connector/dsh.plugin.json +16 -0
- package/runtime/connector/lib/client.js +262 -0
- package/runtime/connector/lib/command-cache.d.ts +7 -0
- package/runtime/connector/lib/command-cache.js +37 -0
- package/runtime/connector/lib/command-cache.js.map +1 -0
- package/runtime/connector/lib/connector-config.d.ts +20 -0
- package/runtime/connector/lib/connector-config.js +74 -0
- package/runtime/connector/lib/connector-config.js.map +1 -0
- package/runtime/connector/lib/dsh-api.d.ts +38 -0
- package/runtime/connector/lib/dsh-api.js +76 -0
- package/runtime/connector/lib/dsh-api.js.map +1 -0
- package/runtime/connector/lib/index.d.ts +4 -0
- package/runtime/connector/lib/index.js +913 -0
- package/runtime/connector/lib/index.js.map +1 -0
- package/runtime/connector/lib/protocol.d.ts +15 -0
- package/runtime/connector/lib/protocol.js +96 -0
- package/runtime/connector/lib/protocol.js.map +1 -0
- package/runtime/connector/package.json +44 -0
- package/runtime/hub/database.js +239 -0
- package/runtime/hub/database.js.map +1 -0
- package/runtime/hub/index.js +1804 -0
- package/runtime/hub/index.js.map +1 -0
- package/runtime/hub/schema.js +111 -0
- package/runtime/hub/schema.js.map +1 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { createServer } from 'node:net';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
import { createWriteStream, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { artifactFor, CLOUDFLARED_VERSION } from './cloudflared.js';
|
|
7
|
+
import { installCloudflaredArtifact } from './download.js';
|
|
8
|
+
export async function findAvailablePort(preferred = 8787, probe = isLoopbackPortFree) {
|
|
9
|
+
for (let offset = 0; offset < 100; offset += 1) {
|
|
10
|
+
const port = preferred + offset;
|
|
11
|
+
if (port > 65_535)
|
|
12
|
+
break;
|
|
13
|
+
if (await probe(port))
|
|
14
|
+
return port;
|
|
15
|
+
}
|
|
16
|
+
throw new Error(`No free loopback port found from ${preferred}`);
|
|
17
|
+
}
|
|
18
|
+
export async function ensureCloudflaredRuntime(paths, options = {}) {
|
|
19
|
+
const platform = options.platform ?? process.platform;
|
|
20
|
+
const arch = options.arch ?? process.arch;
|
|
21
|
+
const artifact = artifactFor(platform, arch);
|
|
22
|
+
const expected = {
|
|
23
|
+
version: CLOUDFLARED_VERSION,
|
|
24
|
+
platform,
|
|
25
|
+
arch,
|
|
26
|
+
artifact: artifact.name,
|
|
27
|
+
sha256: artifact.sha256,
|
|
28
|
+
};
|
|
29
|
+
if (inspectCloudflaredRuntime(paths, { platform, arch }).ok)
|
|
30
|
+
return;
|
|
31
|
+
mkdirSync(paths.cloudflaredDir, { recursive: true, mode: 0o700 });
|
|
32
|
+
await (options.installer ?? installCloudflaredArtifact)({ artifact, destination: paths.cloudflaredExecutable });
|
|
33
|
+
writeFileSync(paths.cloudflaredManifest, `${JSON.stringify({
|
|
34
|
+
...expected,
|
|
35
|
+
executableSha256: fileSha256(paths.cloudflaredExecutable),
|
|
36
|
+
}, null, 2)}\n`, { mode: 0o600 });
|
|
37
|
+
}
|
|
38
|
+
export function inspectCloudflaredRuntime(paths, options = {}) {
|
|
39
|
+
if (!existsSync(paths.cloudflaredExecutable) || !existsSync(paths.cloudflaredManifest)) {
|
|
40
|
+
return { ok: false, detail: 'cloudflared is not installed' };
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const platform = options.platform ?? process.platform;
|
|
44
|
+
const arch = options.arch ?? process.arch;
|
|
45
|
+
const artifact = artifactFor(platform, arch);
|
|
46
|
+
const manifest = JSON.parse(readFileSync(paths.cloudflaredManifest, 'utf8'));
|
|
47
|
+
const metadataMatches = manifest.version === CLOUDFLARED_VERSION
|
|
48
|
+
&& manifest.platform === platform
|
|
49
|
+
&& manifest.arch === arch
|
|
50
|
+
&& manifest.artifact === artifact.name
|
|
51
|
+
&& manifest.sha256 === artifact.sha256;
|
|
52
|
+
const executableHash = fileSha256(paths.cloudflaredExecutable);
|
|
53
|
+
const hashMatches = typeof manifest.executableSha256 === 'string'
|
|
54
|
+
&& manifest.executableSha256 === executableHash;
|
|
55
|
+
return {
|
|
56
|
+
ok: metadataMatches && hashMatches,
|
|
57
|
+
detail: metadataMatches && hashMatches
|
|
58
|
+
? `verified cloudflared ${CLOUDFLARED_VERSION}`
|
|
59
|
+
: 'cloudflared metadata or executable checksum mismatch',
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
return { ok: false, detail: error instanceof Error ? error.message : String(error) };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function fileSha256(path) {
|
|
67
|
+
return createHash('sha256').update(readFileSync(path)).digest('hex');
|
|
68
|
+
}
|
|
69
|
+
export function spawnLoggedProcess(paths, launch, role, mirrorOutput = true) {
|
|
70
|
+
mkdirSync(paths.logsDir, { recursive: true, mode: 0o700 });
|
|
71
|
+
const log = createWriteStream(join(paths.logsDir, `${role}.log`), { flags: 'a', mode: 0o600 });
|
|
72
|
+
const child = spawn(launch.command, launch.args, {
|
|
73
|
+
env: launch.env ?? process.env,
|
|
74
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
75
|
+
windowsHide: true,
|
|
76
|
+
});
|
|
77
|
+
child.stdout?.pipe(log, { end: false });
|
|
78
|
+
child.stderr?.pipe(log, { end: false });
|
|
79
|
+
if (mirrorOutput) {
|
|
80
|
+
child.stdout?.pipe(process.stdout, { end: false });
|
|
81
|
+
child.stderr?.pipe(process.stderr, { end: false });
|
|
82
|
+
}
|
|
83
|
+
child.once('close', () => log.end());
|
|
84
|
+
return child;
|
|
85
|
+
}
|
|
86
|
+
export function runProcessLaunch(launch, interactive = false) {
|
|
87
|
+
return new Promise((resolve, reject) => {
|
|
88
|
+
const child = spawn(launch.command, launch.args, {
|
|
89
|
+
env: launch.env ?? process.env,
|
|
90
|
+
stdio: interactive ? 'inherit' : ['ignore', 'pipe', 'pipe'],
|
|
91
|
+
windowsHide: true,
|
|
92
|
+
});
|
|
93
|
+
const errors = [];
|
|
94
|
+
if (!interactive)
|
|
95
|
+
child.stderr?.on('data', (chunk) => errors.push(chunk));
|
|
96
|
+
child.once('error', reject);
|
|
97
|
+
child.once('close', (code) => {
|
|
98
|
+
if (code === 0)
|
|
99
|
+
resolve();
|
|
100
|
+
else
|
|
101
|
+
reject(new Error(`Command failed (${code ?? 'signal'}): ${Buffer.concat(errors).toString('utf8').trim()}`));
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
function isLoopbackPortFree(port) {
|
|
106
|
+
return new Promise((resolve) => {
|
|
107
|
+
const server = createServer();
|
|
108
|
+
server.unref();
|
|
109
|
+
server.once('error', () => resolve(false));
|
|
110
|
+
server.listen({ host: '127.0.0.1', port, exclusive: true }, () => {
|
|
111
|
+
server.close(() => resolve(true));
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=local-runtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-runtime.js","sourceRoot":"","sources":["../src/local-runtime.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { backup, DatabaseSync } from 'node:sqlite';
|
|
2
|
+
import { chmodSync, copyFileSync, existsSync, mkdirSync, renameSync, writeFileSync, } from 'node:fs';
|
|
3
|
+
import { basename, join } from 'node:path';
|
|
4
|
+
const SAFE_METADATA = [
|
|
5
|
+
'installState',
|
|
6
|
+
'connectorConfig',
|
|
7
|
+
'publicEntry',
|
|
8
|
+
'setupProgress',
|
|
9
|
+
'namedConfig',
|
|
10
|
+
'cloudflaredManifest',
|
|
11
|
+
];
|
|
12
|
+
export async function createBackup(paths, destination) {
|
|
13
|
+
if (!existsSync(paths.database))
|
|
14
|
+
throw new Error('Hub database does not exist');
|
|
15
|
+
const backupDirectory = destination ?? join(paths.backupsDir, new Date().toISOString().replaceAll(':', '-'));
|
|
16
|
+
mkdirSync(backupDirectory, { recursive: true, mode: 0o700 });
|
|
17
|
+
const database = new DatabaseSync(paths.database, { readOnly: true });
|
|
18
|
+
try {
|
|
19
|
+
await backup(database, join(backupDirectory, 'hub.sqlite'));
|
|
20
|
+
}
|
|
21
|
+
finally {
|
|
22
|
+
database.close();
|
|
23
|
+
}
|
|
24
|
+
for (const key of SAFE_METADATA) {
|
|
25
|
+
const source = paths[key];
|
|
26
|
+
if (existsSync(source))
|
|
27
|
+
copyFileSecure(source, join(backupDirectory, basename(source)));
|
|
28
|
+
}
|
|
29
|
+
writeFileSync(join(backupDirectory, 'backup.json'), `${JSON.stringify({
|
|
30
|
+
schemaVersion: 1,
|
|
31
|
+
createdAt: new Date().toISOString(),
|
|
32
|
+
includesSecrets: false,
|
|
33
|
+
}, null, 2)}\n`, { mode: 0o600 });
|
|
34
|
+
return backupDirectory;
|
|
35
|
+
}
|
|
36
|
+
export async function restoreBackup(paths, source, options) {
|
|
37
|
+
if (options.isRunning())
|
|
38
|
+
throw new Error('Stop DSH EasyRemote before restoring a backup');
|
|
39
|
+
const sourcePaths = [join(source, 'hub.sqlite'), join(source, 'data', 'hub.sqlite')];
|
|
40
|
+
const sourceDatabase = sourcePaths.find(existsSync);
|
|
41
|
+
if (!sourceDatabase)
|
|
42
|
+
throw new Error('Backup does not contain hub.sqlite');
|
|
43
|
+
mkdirSync(paths.dataDir, { recursive: true, mode: 0o700 });
|
|
44
|
+
const temporaryDatabase = `${paths.database}.restore-${process.pid}`;
|
|
45
|
+
copyFileSecure(sourceDatabase, temporaryDatabase);
|
|
46
|
+
renameSync(temporaryDatabase, paths.database);
|
|
47
|
+
if (process.platform !== 'win32')
|
|
48
|
+
chmodSync(paths.database, 0o600);
|
|
49
|
+
const installState = join(source, 'install.json');
|
|
50
|
+
if (existsSync(installState))
|
|
51
|
+
copyFileSecure(installState, paths.installState);
|
|
52
|
+
}
|
|
53
|
+
function copyFileSecure(source, destination) {
|
|
54
|
+
copyFileSync(source, destination);
|
|
55
|
+
if (process.platform !== 'win32')
|
|
56
|
+
chmodSync(destination, 0o600);
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=maintenance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"maintenance.js","sourceRoot":"","sources":["../src/maintenance.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
export function buildLoginLaunch(executable) {
|
|
3
|
+
return { command: executable, args: ['tunnel', 'login'] };
|
|
4
|
+
}
|
|
5
|
+
export function buildCreateTunnelLaunch(executable, originCertificate, credentialsFile, tunnelName) {
|
|
6
|
+
return {
|
|
7
|
+
command: executable,
|
|
8
|
+
args: [
|
|
9
|
+
'tunnel', '--origincert', originCertificate, '--credentials-file', credentialsFile,
|
|
10
|
+
'create', tunnelName,
|
|
11
|
+
],
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export function buildDnsRouteLaunch(executable, originCertificate, tunnelId, hostname) {
|
|
15
|
+
return {
|
|
16
|
+
command: executable,
|
|
17
|
+
args: ['tunnel', '--origincert', originCertificate, 'route', 'dns', tunnelId, hostname],
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export function readTunnelCredentials(credentialsFile) {
|
|
21
|
+
let value;
|
|
22
|
+
try {
|
|
23
|
+
value = JSON.parse(readFileSync(credentialsFile, 'utf8'));
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
throw new Error(`Tunnel credentials are not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
27
|
+
}
|
|
28
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)
|
|
29
|
+
|| typeof value.TunnelID !== 'string') {
|
|
30
|
+
throw new Error('Tunnel credentials do not contain TunnelID');
|
|
31
|
+
}
|
|
32
|
+
return { tunnelId: value.TunnelID, credentialsFile };
|
|
33
|
+
}
|
|
34
|
+
export async function provisionNamedTunnel(options) {
|
|
35
|
+
const exists = options.exists ?? existsSync;
|
|
36
|
+
if (!exists(options.originCertificate)) {
|
|
37
|
+
await options.run(buildLoginLaunch(options.executable), true);
|
|
38
|
+
if (!existsSync(options.originCertificate)) {
|
|
39
|
+
throw new Error('Cloudflare authorization completed without creating cert.pem');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (!exists(options.credentialsFile)) {
|
|
43
|
+
await options.run(buildCreateTunnelLaunch(options.executable, options.originCertificate, options.credentialsFile, options.tunnelName));
|
|
44
|
+
}
|
|
45
|
+
const credentials = readTunnelCredentials(options.credentialsFile);
|
|
46
|
+
await options.run(buildDnsRouteLaunch(options.executable, options.originCertificate, credentials.tunnelId, options.hostname));
|
|
47
|
+
return credentials;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=named-tunnel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"named-tunnel.js","sourceRoot":"","sources":["../src/named-tunnel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAInD,MAAM,UAAU,gBAAgB,CAAC,UAAkB;IACjD,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,EAAE,CAAC;AAC5D,CAAC;AAED,MAAM,UAAU,uBAAuB,CACrC,UAAkB,EAClB,iBAAyB,EACzB,eAAuB,EACvB,UAAkB;IAElB,OAAO;QACL,OAAO,EAAE,UAAU;QACnB,IAAI,EAAE;YACJ,QAAQ,EAAE,cAAc,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,eAAe;YAClF,QAAQ,EAAE,UAAU;SACrB;KACF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,mBAAmB,CACjC,UAAkB,EAClB,iBAAyB,EACzB,QAAgB,EAChB,QAAgB;IAEhB,OAAO;QACL,OAAO,EAAE,UAAU;QACnB,IAAI,EAAE,CAAC,QAAQ,EAAE,cAAc,EAAE,iBAAiB,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,CAAC;KACxF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,eAAuB;IAC3D,IAAI,KAAc,CAAC;IACnB,IAAI,CAAC;QACH,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC,CAAC;IAC5D,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,0CAA0C,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACtH,CAAC;IACD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;WAC1D,OAAQ,KAAiC,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QACrE,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;IAChE,CAAC;IACD,OAAO,EAAE,QAAQ,EAAG,KAA8B,CAAC,QAAQ,EAAE,eAAe,EAAE,CAAC;AACjF,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,OAQ1C;IACC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,UAAU,CAAC;IAC5C,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,iBAAiB,CAAC,EAAE,CAAC;QACvC,MAAM,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,IAAI,CAAC,CAAC;QAC9D,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,iBAAiB,CAAC,EAAE,CAAC;YAC3C,MAAM,IAAI,KAAK,CAAC,8DAA8D,CAAC,CAAC;QAClF,CAAC;IACH,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC,EAAE,CAAC;QACrC,MAAM,OAAO,CAAC,GAAG,CAAC,uBAAuB,CACvC,OAAO,CAAC,UAAU,EAClB,OAAO,CAAC,iBAAiB,EACzB,OAAO,CAAC,eAAe,EACvB,OAAO,CAAC,UAAU,CACnB,CAAC,CAAC;IACL,CAAC;IACD,MAAM,WAAW,GAAG,qBAAqB,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;IACnE,MAAM,OAAO,CAAC,GAAG,CAAC,mBAAmB,CACnC,OAAO,CAAC,UAAU,EAClB,OAAO,CAAC,iBAAiB,EACzB,WAAW,CAAC,QAAQ,EACpB,OAAO,CAAC,QAAQ,CACjB,CAAC,CAAC;IACH,OAAO,WAAW,CAAC;AACrB,CAAC"}
|
package/dist/runtime.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { chmodSync, mkdirSync, renameSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
export function createRuntimePaths(root) {
|
|
5
|
+
const cloudflaredDir = join(root, 'cloudflared');
|
|
6
|
+
const dataDir = join(root, 'data');
|
|
7
|
+
const secretsDir = join(root, 'secrets');
|
|
8
|
+
const runDir = join(root, 'run');
|
|
9
|
+
return {
|
|
10
|
+
root,
|
|
11
|
+
installState: join(root, 'install.json'),
|
|
12
|
+
setupProgress: join(root, 'setup.json'),
|
|
13
|
+
connectorConfig: join(root, 'connector.json'),
|
|
14
|
+
publicEntry: join(root, 'public-origin.json'),
|
|
15
|
+
dataDir,
|
|
16
|
+
database: join(dataDir, 'hub.sqlite'),
|
|
17
|
+
secretsDir,
|
|
18
|
+
jwtSecret: join(secretsDir, 'jwt-secret'),
|
|
19
|
+
cloudflaredDir,
|
|
20
|
+
cloudflaredExecutable: join(cloudflaredDir, process.platform === 'win32' ? 'cloudflared.exe' : 'cloudflared'),
|
|
21
|
+
cloudflaredManifest: join(cloudflaredDir, 'install.json'),
|
|
22
|
+
quickConfig: join(cloudflaredDir, 'quick.yml'),
|
|
23
|
+
namedConfig: join(cloudflaredDir, 'named.yml'),
|
|
24
|
+
logsDir: join(root, 'logs'),
|
|
25
|
+
runDir,
|
|
26
|
+
hubPid: join(runDir, 'hub.pid'),
|
|
27
|
+
tunnelPid: join(runDir, 'tunnel.pid'),
|
|
28
|
+
backupsDir: join(root, 'backups'),
|
|
29
|
+
packagesDir: join(root, 'packages'),
|
|
30
|
+
serviceDir: join(root, 'service'),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export function buildHubLaunch(paths, port, jwtSecret, hubScript) {
|
|
34
|
+
return {
|
|
35
|
+
command: process.execPath,
|
|
36
|
+
args: [hubScript],
|
|
37
|
+
env: {
|
|
38
|
+
...process.env,
|
|
39
|
+
HOST: '127.0.0.1',
|
|
40
|
+
PORT: String(port),
|
|
41
|
+
DATABASE_PATH: paths.database,
|
|
42
|
+
HUB_ENTRY_FILE: paths.publicEntry,
|
|
43
|
+
HUB_ENTRY: `http://127.0.0.1:${port}`,
|
|
44
|
+
JWT_SECRET: jwtSecret,
|
|
45
|
+
NODE_ENV: 'production',
|
|
46
|
+
DSH_EASYREMOTE_VERSION: '0.2.0',
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export function buildQuickTunnelLaunch(paths, hubPort, executable) {
|
|
51
|
+
return {
|
|
52
|
+
command: executable,
|
|
53
|
+
args: [
|
|
54
|
+
'tunnel', '--config', paths.quickConfig, '--no-autoupdate',
|
|
55
|
+
'--url', `http://127.0.0.1:${hubPort}`, '--loglevel', 'info',
|
|
56
|
+
],
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
export function buildNamedTunnelLaunch(paths, tunnelId, executable) {
|
|
60
|
+
return {
|
|
61
|
+
command: executable,
|
|
62
|
+
args: ['tunnel', '--config', paths.namedConfig, '--no-autoupdate', 'run', tunnelId],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export function writeConnectorConfig(paths, hubUrl, nodeName, defaultCwd) {
|
|
66
|
+
const parsed = new URL(hubUrl);
|
|
67
|
+
if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:')
|
|
68
|
+
throw new Error('Connector Hub URL must use HTTP or HTTPS');
|
|
69
|
+
writeSecureJson(paths.connectorConfig, {
|
|
70
|
+
schemaVersion: 1,
|
|
71
|
+
hubUrl: parsed.origin,
|
|
72
|
+
...(nodeName?.trim() ? { nodeName: nodeName.trim() } : {}),
|
|
73
|
+
...(defaultCwd?.trim() ? { defaultCwd: defaultCwd.trim() } : {}),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
export function writePublicEntry(paths, publicOrigin) {
|
|
77
|
+
const parsed = new URL(publicOrigin);
|
|
78
|
+
if (!['http:', 'https:'].includes(parsed.protocol))
|
|
79
|
+
throw new Error('Public origin must use HTTP or HTTPS');
|
|
80
|
+
writeSecureJson(paths.publicEntry, { publicOrigin: parsed.origin });
|
|
81
|
+
}
|
|
82
|
+
export function writeNamedTunnelConfig(paths, options) {
|
|
83
|
+
const yaml = [
|
|
84
|
+
`tunnel: ${JSON.stringify(options.tunnelId)}`,
|
|
85
|
+
`credentials-file: ${JSON.stringify(options.credentialsFile)}`,
|
|
86
|
+
'ingress:',
|
|
87
|
+
` - hostname: ${JSON.stringify(options.hostname)}`,
|
|
88
|
+
` service: http://127.0.0.1:${options.hubPort}`,
|
|
89
|
+
' - service: http_status:404',
|
|
90
|
+
'',
|
|
91
|
+
].join('\n');
|
|
92
|
+
writeSecureFile(paths.namedConfig, yaml);
|
|
93
|
+
}
|
|
94
|
+
export function writeQuickTunnelConfig(paths) {
|
|
95
|
+
writeSecureFile(paths.quickConfig, '{}\n');
|
|
96
|
+
}
|
|
97
|
+
function writeSecureJson(path, value) {
|
|
98
|
+
writeSecureFile(path, `${JSON.stringify(value, null, 2)}\n`);
|
|
99
|
+
}
|
|
100
|
+
function writeSecureFile(path, value) {
|
|
101
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
102
|
+
const temporaryPath = `${path}.${randomUUID()}.tmp`;
|
|
103
|
+
writeFileSync(temporaryPath, value, { mode: 0o600 });
|
|
104
|
+
if (process.platform !== 'win32')
|
|
105
|
+
chmodSync(temporaryPath, 0o600);
|
|
106
|
+
renameSync(temporaryPath, path);
|
|
107
|
+
if (process.platform !== 'win32')
|
|
108
|
+
chmodSync(path, 0o600);
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=runtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { dirname } from 'node:path';
|
|
4
|
+
export function loadSetupProgress(path) {
|
|
5
|
+
if (!existsSync(path))
|
|
6
|
+
return null;
|
|
7
|
+
const value = JSON.parse(readFileSync(path, 'utf8'));
|
|
8
|
+
if (value.schemaVersion !== 1 || typeof value.phase !== 'string' || typeof value.updatedAt !== 'number') {
|
|
9
|
+
throw new Error('Invalid setup progress');
|
|
10
|
+
}
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
export function saveSetupProgress(path, progress) {
|
|
14
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
15
|
+
const temporaryPath = `${path}.${randomUUID()}.tmp`;
|
|
16
|
+
writeFileSync(temporaryPath, `${JSON.stringify(progress, null, 2)}\n`, { mode: 0o600 });
|
|
17
|
+
if (process.platform !== 'win32')
|
|
18
|
+
chmodSync(temporaryPath, 0o600);
|
|
19
|
+
renameSync(temporaryPath, path);
|
|
20
|
+
if (process.platform !== 'win32')
|
|
21
|
+
chmodSync(path, 0o600);
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=setup-progress.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup-progress.js","sourceRoot":"","sources":["../src/setup-progress.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACpG,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAwBpC,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACnC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAkB,CAAC;IACtE,IAAI,KAAK,CAAC,aAAa,KAAK,CAAC,IAAI,OAAO,KAAK,CAAC,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,CAAC,SAAS,KAAK,QAAQ,EAAE,CAAC;QACxG,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;IAC5C,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,IAAY,EAAE,QAAuB;IACrE,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC3D,MAAM,aAAa,GAAG,GAAG,IAAI,IAAI,UAAU,EAAE,MAAM,CAAC;IACpD,aAAa,CAAC,aAAa,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACxF,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO;QAAE,SAAS,CAAC,aAAa,EAAE,KAAK,CAAC,CAAC;IAClE,UAAU,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;IAChC,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO;QAAE,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AAC3D,CAAC"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { parseQuickTunnelOrigin } from './cloudflared.js';
|
|
2
|
+
export async function waitForHubMeta(origin, options = {}) {
|
|
3
|
+
const fetcher = options.fetcher ?? fetch;
|
|
4
|
+
const timeoutMs = options.timeoutMs ?? 15_000;
|
|
5
|
+
const retryMs = options.retryMs ?? 150;
|
|
6
|
+
const deadline = Date.now() + timeoutMs;
|
|
7
|
+
let lastError;
|
|
8
|
+
while (Date.now() <= deadline) {
|
|
9
|
+
try {
|
|
10
|
+
const response = await fetcher(`${origin}/v1/meta`);
|
|
11
|
+
if (!response.ok)
|
|
12
|
+
throw new Error(`Hub returned HTTP ${response.status}`);
|
|
13
|
+
const value = await response.json();
|
|
14
|
+
if (!value || typeof value.hubId !== 'string' || typeof value.publicOrigin !== 'string') {
|
|
15
|
+
throw new Error('Hub returned invalid metadata');
|
|
16
|
+
}
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
lastError = error;
|
|
21
|
+
await new Promise((resolve) => setTimeout(resolve, retryMs));
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
throw new Error(`Local Hub did not become ready: ${lastError instanceof Error ? lastError.message : String(lastError)}`);
|
|
25
|
+
}
|
|
26
|
+
export function waitForQuickOrigin(child, timeoutMs = 30_000) {
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
let settled = false;
|
|
29
|
+
const streams = [child.stdout, child.stderr].filter((stream) => Boolean(stream));
|
|
30
|
+
const cleanup = () => {
|
|
31
|
+
clearTimeout(timer);
|
|
32
|
+
for (const stream of streams)
|
|
33
|
+
stream.off('data', onData);
|
|
34
|
+
};
|
|
35
|
+
const finish = (error, value) => {
|
|
36
|
+
if (settled)
|
|
37
|
+
return;
|
|
38
|
+
settled = true;
|
|
39
|
+
cleanup();
|
|
40
|
+
if (error)
|
|
41
|
+
reject(error);
|
|
42
|
+
else
|
|
43
|
+
resolve(value);
|
|
44
|
+
};
|
|
45
|
+
const onData = (chunk) => {
|
|
46
|
+
const origin = parseQuickTunnelOrigin(String(chunk));
|
|
47
|
+
if (origin)
|
|
48
|
+
finish(null, origin);
|
|
49
|
+
};
|
|
50
|
+
const timer = setTimeout(() => finish(new Error('Timed out waiting for Quick Tunnel URL')), timeoutMs);
|
|
51
|
+
for (const stream of streams)
|
|
52
|
+
stream.on('data', onData);
|
|
53
|
+
child.once?.('error', (error) => finish(error));
|
|
54
|
+
child.once?.('close', (code) => {
|
|
55
|
+
if (!settled)
|
|
56
|
+
finish(new Error(`cloudflared exited before publishing a URL (${code ?? 'signal'})`));
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
export function stopManagedChild(child, timeoutMs = 5_000) {
|
|
61
|
+
if (child.exitCode != null || child.signalCode != null)
|
|
62
|
+
return Promise.resolve();
|
|
63
|
+
return new Promise((resolve) => {
|
|
64
|
+
let done = false;
|
|
65
|
+
const finish = () => {
|
|
66
|
+
if (done)
|
|
67
|
+
return;
|
|
68
|
+
done = true;
|
|
69
|
+
clearTimeout(timer);
|
|
70
|
+
resolve();
|
|
71
|
+
};
|
|
72
|
+
child.once('close', finish);
|
|
73
|
+
child.kill('SIGTERM');
|
|
74
|
+
const timer = setTimeout(() => {
|
|
75
|
+
if (child.exitCode == null && child.signalCode == null)
|
|
76
|
+
child.kill('SIGKILL');
|
|
77
|
+
finish();
|
|
78
|
+
}, timeoutMs);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=supervisor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"supervisor.js","sourceRoot":"","sources":["../src/supervisor.ts"],"names":[],"mappings":"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"}
|