create-wpt-iot 0.1.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 (62) hide show
  1. package/README.md +71 -0
  2. package/dist/artifact.d.ts +13 -0
  3. package/dist/artifact.d.ts.map +1 -0
  4. package/dist/artifact.js +37 -0
  5. package/dist/artifact.js.map +1 -0
  6. package/dist/bin.d.ts +3 -0
  7. package/dist/bin.d.ts.map +1 -0
  8. package/dist/bin.js +5 -0
  9. package/dist/bin.js.map +1 -0
  10. package/dist/cli.d.ts +33 -0
  11. package/dist/cli.d.ts.map +1 -0
  12. package/dist/cli.js +175 -0
  13. package/dist/cli.js.map +1 -0
  14. package/dist/config-file.d.ts +12 -0
  15. package/dist/config-file.d.ts.map +1 -0
  16. package/dist/config-file.js +35 -0
  17. package/dist/config-file.js.map +1 -0
  18. package/dist/i18n.d.ts +7 -0
  19. package/dist/i18n.d.ts.map +1 -0
  20. package/dist/i18n.js +10 -0
  21. package/dist/i18n.js.map +1 -0
  22. package/dist/installer-manifest.d.ts +3 -0
  23. package/dist/installer-manifest.d.ts.map +1 -0
  24. package/dist/installer-manifest.js +7 -0
  25. package/dist/installer-manifest.js.map +1 -0
  26. package/dist/local.d.ts +6 -0
  27. package/dist/local.d.ts.map +1 -0
  28. package/dist/local.js +42 -0
  29. package/dist/local.js.map +1 -0
  30. package/dist/messages/en.d.ts +48 -0
  31. package/dist/messages/en.d.ts.map +1 -0
  32. package/dist/messages/en.js +48 -0
  33. package/dist/messages/en.js.map +1 -0
  34. package/dist/messages/it.d.ts +3 -0
  35. package/dist/messages/it.d.ts.map +1 -0
  36. package/dist/messages/it.js +48 -0
  37. package/dist/messages/it.js.map +1 -0
  38. package/dist/preflight.d.ts +5 -0
  39. package/dist/preflight.d.ts.map +1 -0
  40. package/dist/preflight.js +25 -0
  41. package/dist/preflight.js.map +1 -0
  42. package/dist/process.d.ts +39 -0
  43. package/dist/process.d.ts.map +1 -0
  44. package/dist/process.js +115 -0
  45. package/dist/process.js.map +1 -0
  46. package/dist/prompts.d.ts +36 -0
  47. package/dist/prompts.d.ts.map +1 -0
  48. package/dist/prompts.js +65 -0
  49. package/dist/prompts.js.map +1 -0
  50. package/dist/remote.d.ts +8 -0
  51. package/dist/remote.d.ts.map +1 -0
  52. package/dist/remote.js +211 -0
  53. package/dist/remote.js.map +1 -0
  54. package/dist/types.d.ts +24 -0
  55. package/dist/types.d.ts.map +1 -0
  56. package/dist/types.js +2 -0
  57. package/dist/types.js.map +1 -0
  58. package/dist/validation.d.ts +12 -0
  59. package/dist/validation.d.ts.map +1 -0
  60. package/dist/validation.js +69 -0
  61. package/dist/validation.js.map +1 -0
  62. package/package.json +44 -0
package/dist/remote.js ADDED
@@ -0,0 +1,211 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { chmod, copyFile, mkdtemp, rm, writeFile } from 'node:fs/promises';
3
+ import { isIP } from 'node:net';
4
+ import { tmpdir } from 'node:os';
5
+ import { join } from 'node:path';
6
+ import { assertSufficientDiskSpace, normalizeArchitecture } from './preflight.js';
7
+ import { validateHost, validatePort, validateUsername } from './validation.js';
8
+ const REMOTE_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
9
+ const REMOTE_PROBE = `set -eu
10
+ exec 2>&1
11
+ INSTALL_DIR="$(printf '%s' "$1" | base64 --decode)"
12
+ case "$INSTALL_DIR" in
13
+ /*) ;;
14
+ *) echo 'invalid install directory' >&2; exit 1 ;;
15
+ esac
16
+ [ "$INSTALL_DIR" != "/" ] || { echo 'invalid install directory' >&2; exit 1; }
17
+ [ "$(uname -s)" = "Linux" ] || { echo 'Linux target required' >&2; exit 1; }
18
+ for REQUIRED_COMMAND in apt-get systemctl sudo curl; do
19
+ command -v "$REQUIRED_COMMAND" >/dev/null 2>&1 || {
20
+ echo "missing command: $REQUIRED_COMMAND" >&2
21
+ exit 1
22
+ }
23
+ done
24
+ ARCHITECTURE="$(uname -m)"
25
+ case "$ARCHITECTURE" in
26
+ aarch64|arm64|x86_64|amd64) ;;
27
+ *) echo "unsupported architecture: $ARCHITECTURE" >&2; exit 1 ;;
28
+ esac
29
+ EXISTING_INSTALL=false
30
+ [ -f "$INSTALL_DIR/docker-compose.yml" ] && EXISTING_INSTALL=true
31
+ DETECTED_SERIAL=""
32
+ [ ! -r /etc/wpt/serial ] || DETECTED_SERIAL="$(tr -d '\\n\\r \\t' < /etc/wpt/serial)"
33
+ DISK_AVAILABLE_KB="$(df -Pk / | awk 'NR == 2 { print $4 }')"
34
+ case "$DISK_AVAILABLE_KB" in
35
+ ''|*[!0-9]*) echo 'unable to determine disk availability' >&2; exit 1 ;;
36
+ esac
37
+ for URL in https://ghcr.io https://raw.githubusercontent.com https://get.docker.com; do
38
+ curl -fsS -o /dev/null -m 10 "$URL"
39
+ done
40
+ printf 'architecture=%s\\n' "$ARCHITECTURE"
41
+ printf 'existing_install=%s\\n' "$EXISTING_INSTALL"
42
+ printf 'detected_serial_base64=%s\\n' "$(printf '%s' "$DETECTED_SERIAL" | base64 | tr -d '\\n')"
43
+ printf 'disk_available_kb=%s\\n' "$DISK_AVAILABLE_KB"
44
+ `;
45
+ const STALE_CLEANUP = `set -eu
46
+ exec 2>&1
47
+ find /tmp -xdev -maxdepth 1 -type f -user "$(id -un)" -mtime +0 \\
48
+ \\( -name 'create-wpt-iot-????????-????-????-????-????????????-installer.sh' \\
49
+ -o -name 'create-wpt-iot-????????-????-????-????-????????????-install.conf' \\
50
+ -o -name 'create-wpt-iot-????????-????-????-????-????????????-run.sh' \\) \\
51
+ -delete
52
+ `;
53
+ const REMOTE_INSTALL = `#!/bin/sh
54
+ set -eu
55
+ exec 2>&1
56
+ INSTALLER="$1"
57
+ CONFIG="$2"
58
+ RUNNER="$0"
59
+ cleanup() { rm -f -- "$CONFIG" "$INSTALLER" "$RUNNER"; }
60
+ trap cleanup EXIT
61
+ trap 'exit 129' HUP
62
+ trap 'exit 130' INT
63
+ trap 'exit 143' TERM
64
+ chmod 700 "$INSTALLER"
65
+ chmod 600 "$CONFIG"
66
+ sudo bash "$INSTALLER" --config "$CONFIG"
67
+ `;
68
+ const CURRENT_CLEANUP = `set -eu
69
+ exec 2>&1
70
+ rm -f -- "$1" "$2" "$3"
71
+ `;
72
+ function remoteScriptCommand(script, args = []) {
73
+ const encodedScript = Buffer.from(script, 'utf8').toString('base64');
74
+ const suffix = args.length === 0 ? '' : ` -- ${args.join(' ')}`;
75
+ return `printf %s ${encodedScript} | base64 --decode | sh -s${suffix}`;
76
+ }
77
+ async function stageRemoteFiles(artifact, config, remoteId, tempRoot) {
78
+ const directory = await mkdtemp(join(tempRoot, 'create-wpt-iot-upload-'));
79
+ const installerPath = join(directory, `create-wpt-iot-${remoteId}-installer.sh`);
80
+ const configPath = join(directory, `create-wpt-iot-${remoteId}-install.conf`);
81
+ const runnerPath = join(directory, `create-wpt-iot-${remoteId}-run.sh`);
82
+ try {
83
+ await chmod(directory, 0o700);
84
+ await copyFile(artifact.path, installerPath);
85
+ await copyFile(config.path, configPath);
86
+ await writeFile(runnerPath, REMOTE_INSTALL, { mode: 0o700, flag: 'wx' });
87
+ await chmod(installerPath, 0o700);
88
+ await chmod(configPath, 0o600);
89
+ await chmod(runnerPath, 0o700);
90
+ }
91
+ catch (error) {
92
+ await rm(directory, { recursive: true, force: true });
93
+ throw error;
94
+ }
95
+ return {
96
+ path: directory,
97
+ installerPath,
98
+ configPath,
99
+ runnerPath,
100
+ cleanup: async () => rm(directory, { recursive: true, force: true }),
101
+ };
102
+ }
103
+ export function sshDestination(target) {
104
+ const username = validateUsername(target.username);
105
+ const host = validateHost(target.host);
106
+ return `${username}@${isIP(host) === 6 ? `[${host}]` : host}`;
107
+ }
108
+ function parseProbeOutput(output) {
109
+ const values = new Map();
110
+ for (const line of output.split(/\r?\n/)) {
111
+ const separator = line.indexOf('=');
112
+ if (separator <= 0)
113
+ continue;
114
+ values.set(line.slice(0, separator), line.slice(separator + 1));
115
+ }
116
+ const rawArchitecture = values.get('architecture');
117
+ const rawExisting = values.get('existing_install');
118
+ const rawSerial = values.get('detected_serial_base64');
119
+ const rawDisk = values.get('disk_available_kb');
120
+ if (rawArchitecture === undefined
121
+ || !['true', 'false'].includes(rawExisting ?? '')
122
+ || rawSerial === undefined
123
+ || rawDisk === undefined
124
+ || !/^\d+$/.test(rawDisk)) {
125
+ throw new Error('invalidRemotePreflightOutput');
126
+ }
127
+ assertSufficientDiskSpace(rawDisk);
128
+ if (rawSerial !== '' && !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(rawSerial)) {
129
+ throw new Error('invalidRemotePreflightOutput');
130
+ }
131
+ return {
132
+ architecture: normalizeArchitecture(rawArchitecture),
133
+ existingInstall: rawExisting === 'true',
134
+ detectedSerial: Buffer.from(rawSerial, 'base64').toString('utf8').trim(),
135
+ };
136
+ }
137
+ export async function preflightRemote(runner, target, installDir, platform = process.platform) {
138
+ await runner.run('ssh', ['-V']);
139
+ if (platform === 'win32') {
140
+ await runner.run('where.exe', ['scp.exe']);
141
+ }
142
+ else if (platform === 'linux') {
143
+ await runner.run('sh', [
144
+ '-c',
145
+ 'command -v "$1" >/dev/null 2>&1',
146
+ 'create-wpt-iot',
147
+ 'scp',
148
+ ]);
149
+ }
150
+ else {
151
+ throw new Error('unsupportedRemoteClientPlatform');
152
+ }
153
+ const result = await runner.run('ssh', [
154
+ '-p',
155
+ String(validatePort(String(target.port))),
156
+ sshDestination(target),
157
+ remoteScriptCommand(REMOTE_PROBE, [Buffer.from(installDir, 'utf8').toString('base64')]),
158
+ ], { terminal: true });
159
+ return parseProbeOutput(result.stdout);
160
+ }
161
+ export async function installRemote(runner, target, artifact, config, settings, remoteId = randomUUID(), warn = (message) => process.stderr.write(`${message}\n`), tempRoot = tmpdir()) {
162
+ if (!REMOTE_ID_PATTERN.test(remoteId))
163
+ throw new Error('invalidRemoteId');
164
+ const destination = sshDestination(target);
165
+ const port = String(validatePort(String(target.port)));
166
+ const installerPath = `/tmp/create-wpt-iot-${remoteId}-installer.sh`;
167
+ const configPath = `/tmp/create-wpt-iot-${remoteId}-install.conf`;
168
+ const runnerPath = `/tmp/create-wpt-iot-${remoteId}-run.sh`;
169
+ const redactions = [settings.adminPassword];
170
+ await runner.run('ssh', ['-p', port, destination, remoteScriptCommand(STALE_CLEANUP)], { terminal: true, redactions });
171
+ const staged = await stageRemoteFiles(artifact, config, remoteId, tempRoot);
172
+ let operationFailed = false;
173
+ let operationError;
174
+ try {
175
+ await runner.run('scp', [
176
+ '-P',
177
+ port,
178
+ staged.installerPath,
179
+ staged.configPath,
180
+ staged.runnerPath,
181
+ `${destination}:/tmp/`,
182
+ ], { terminal: true, redactions });
183
+ await runner.run('ssh', ['-tt', '-p', port, destination, 'sh', runnerPath, installerPath, configPath], { terminal: true, redactions });
184
+ }
185
+ catch (error) {
186
+ operationFailed = true;
187
+ operationError = error;
188
+ try {
189
+ await runner.run('ssh', [
190
+ '-p',
191
+ port,
192
+ destination,
193
+ remoteScriptCommand(CURRENT_CLEANUP, [installerPath, configPath, runnerPath]),
194
+ ], { terminal: true, redactions });
195
+ }
196
+ catch {
197
+ warn('remoteCleanupFailed');
198
+ }
199
+ }
200
+ try {
201
+ await staged.cleanup();
202
+ }
203
+ catch (cleanupError) {
204
+ warn('localStagingCleanupFailed');
205
+ if (!operationFailed)
206
+ throw cleanupError;
207
+ }
208
+ if (operationFailed)
209
+ throw operationError;
210
+ }
211
+ //# sourceMappingURL=remote.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote.js","sourceRoot":"","sources":["../src/remote.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ export type InstallMode = 'local' | 'remote';
2
+ export interface RemoteTarget {
3
+ host: string;
4
+ port: number;
5
+ username: string;
6
+ }
7
+ export interface InstallSettings {
8
+ installDir: string;
9
+ deviceSerial: string;
10
+ enableAutoUpdate: boolean;
11
+ adminPassword?: string;
12
+ }
13
+ export interface PreflightResult {
14
+ architecture: 'arm64' | 'amd64';
15
+ existingInstall: boolean;
16
+ detectedSerial: string;
17
+ }
18
+ export interface InstallRequest {
19
+ mode: InstallMode;
20
+ remote?: RemoteTarget;
21
+ settings: InstallSettings;
22
+ preflight: PreflightResult;
23
+ }
24
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,QAAQ,CAAC;AAE7C,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,eAAe;IAC9B,YAAY,EAAE,OAAO,GAAG,OAAO,CAAC;IAChC,eAAe,EAAE,OAAO,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,WAAW,CAAC;IAClB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,QAAQ,EAAE,eAAe,CAAC;IAC1B,SAAS,EAAE,eAAe,CAAC;CAC5B"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,12 @@
1
+ export declare class ValidationError extends Error {
2
+ readonly code: string;
3
+ constructor(code: string);
4
+ }
5
+ export declare function validateHost(raw: string): string;
6
+ export declare function validatePort(raw: string): number;
7
+ export declare function validateUsername(raw: string): string;
8
+ export declare function validateInstallDir(raw: string): string;
9
+ export declare function validateDeviceSerial(raw: string): string;
10
+ export declare function validateAdminPassword(raw: string): string;
11
+ export declare function confirmAdminPassword(password: string, confirmation: string): string;
12
+ //# sourceMappingURL=validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAGA,qBAAa,eAAgB,SAAQ,KAAK;aACZ,IAAI,EAAE,MAAM;gBAAZ,IAAI,EAAE,MAAM;CAIzC;AAMD,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAMhD;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAQhD;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAIpD;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAatD;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAOxD;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAGzD;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAGnF"}
@@ -0,0 +1,69 @@
1
+ import { isIP } from 'node:net';
2
+ import { posix } from 'node:path';
3
+ export class ValidationError extends Error {
4
+ code;
5
+ constructor(code) {
6
+ super(code);
7
+ this.code = code;
8
+ this.name = 'ValidationError';
9
+ }
10
+ }
11
+ const hostnamePattern = /^(?=.{1,253}$)(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)(?:\.(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?))*$/;
12
+ const usernamePattern = /^[a-z_][a-z0-9_-]{0,31}$/;
13
+ const serialPattern = /^[a-z0-9](?:[a-z0-9-]{0,30}[a-z0-9])?$/;
14
+ export function validateHost(raw) {
15
+ const value = raw.trim();
16
+ if (!isIP(value) && !hostnamePattern.test(value)) {
17
+ throw new ValidationError('invalidHost');
18
+ }
19
+ return value;
20
+ }
21
+ export function validatePort(raw) {
22
+ const trimmed = raw.trim();
23
+ if (!/^\d+$/.test(trimmed))
24
+ throw new ValidationError('invalidPort');
25
+ const value = Number(trimmed);
26
+ if (!Number.isInteger(value) || value < 1 || value > 65535) {
27
+ throw new ValidationError('invalidPort');
28
+ }
29
+ return value;
30
+ }
31
+ export function validateUsername(raw) {
32
+ const value = raw.trim();
33
+ if (!usernamePattern.test(value))
34
+ throw new ValidationError('invalidUsername');
35
+ return value;
36
+ }
37
+ export function validateInstallDir(raw) {
38
+ if ([...raw].some((character) => {
39
+ const code = character.codePointAt(0) ?? 0;
40
+ return code <= 31 || code === 127;
41
+ })) {
42
+ throw new ValidationError('invalidInstallDir');
43
+ }
44
+ const normalized = posix.normalize(raw.trim());
45
+ const value = normalized.length > 1 ? normalized.replace(/\/$/, '') : normalized;
46
+ if (!value.startsWith('/') || value === '/') {
47
+ throw new ValidationError('invalidInstallDir');
48
+ }
49
+ return value;
50
+ }
51
+ export function validateDeviceSerial(raw) {
52
+ const normalized = raw.trim().toLowerCase();
53
+ const value = normalized.startsWith('wpt-') ? normalized.slice(4) : normalized;
54
+ if (!serialPattern.test(value) || value.length > 32) {
55
+ throw new ValidationError('invalidSerial');
56
+ }
57
+ return value;
58
+ }
59
+ export function validateAdminPassword(raw) {
60
+ if ([...raw].length < 12)
61
+ throw new ValidationError('weakPassword');
62
+ return raw;
63
+ }
64
+ export function confirmAdminPassword(password, confirmation) {
65
+ if (password !== confirmation)
66
+ throw new ValidationError('passwordMismatch');
67
+ return password;
68
+ }
69
+ //# sourceMappingURL=validation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.js","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "create-wpt-iot",
3
+ "version": "0.1.0",
4
+ "description": "Interactive local and remote installer for WPT IoT edge devices",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/chetto1983/wpt-iot.git",
8
+ "directory": "packages/create-wpt-iot"
9
+ },
10
+ "type": "module",
11
+ "bin": {
12
+ "create-wpt-iot": "dist/bin.js"
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "README.md"
17
+ ],
18
+ "engines": {
19
+ "node": ">=22.13.0"
20
+ },
21
+ "scripts": {
22
+ "build": "node scripts/clean-dist.mjs && tsc && node scripts/stamp-installer.mjs && node scripts/verify-installer-manifest.mjs",
23
+ "lint": "eslint src",
24
+ "test": "vitest run",
25
+ "test:watch": "vitest",
26
+ "verify:manifest": "node scripts/verify-installer-manifest.mjs",
27
+ "verify:manifest:remote": "node scripts/verify-installer-manifest.mjs --remote",
28
+ "prepack": "pnpm run build"
29
+ },
30
+ "dependencies": {
31
+ "@inquirer/prompts": "^8.7.0",
32
+ "execa": "^10.0.1"
33
+ },
34
+ "devDependencies": {
35
+ "@types/node": "^22.0.0",
36
+ "typescript": "^5.8.0",
37
+ "vitest": "^3.2.4"
38
+ },
39
+ "license": "UNLICENSED",
40
+ "publishConfig": {
41
+ "access": "public",
42
+ "provenance": true
43
+ }
44
+ }