@scalebun/cli 1.0.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/api/client.d.ts +8 -0
- package/lib/api/client.d.ts.map +1 -1
- package/lib/api/client.js +70 -2
- package/lib/api/client.js.map +1 -1
- package/lib/commands/ci-init.d.ts +0 -6
- package/lib/commands/ci-init.d.ts.map +1 -1
- package/lib/commands/ci-init.js +172 -28
- package/lib/commands/ci-init.js.map +1 -1
- package/lib/commands/doctor.d.ts +8 -0
- package/lib/commands/doctor.d.ts.map +1 -0
- package/lib/commands/doctor.js +252 -0
- package/lib/commands/doctor.js.map +1 -0
- package/lib/commands/login.d.ts +28 -4
- package/lib/commands/login.d.ts.map +1 -1
- package/lib/commands/login.js +132 -51
- package/lib/commands/login.js.map +1 -1
- package/lib/commands/ota-bundle.d.ts.map +1 -1
- package/lib/commands/ota-bundle.js +2 -0
- package/lib/commands/ota-bundle.js.map +1 -1
- package/lib/commands/ota-delete-bundle.d.ts +8 -0
- package/lib/commands/ota-delete-bundle.d.ts.map +1 -1
- package/lib/commands/ota-delete-bundle.js +60 -0
- package/lib/commands/ota-delete-bundle.js.map +1 -1
- package/lib/commands/ota-keys.d.ts +14 -0
- package/lib/commands/ota-keys.d.ts.map +1 -0
- package/lib/commands/ota-keys.js +200 -0
- package/lib/commands/ota-keys.js.map +1 -0
- package/lib/commands/ota-publish.d.ts +10 -0
- package/lib/commands/ota-publish.d.ts.map +1 -1
- package/lib/commands/ota-publish.js +251 -34
- package/lib/commands/ota-publish.js.map +1 -1
- package/lib/commands/ota-rollout.d.ts +3 -0
- package/lib/commands/ota-rollout.d.ts.map +1 -0
- package/lib/commands/ota-rollout.js +235 -0
- package/lib/commands/ota-rollout.js.map +1 -0
- package/lib/commands/quickstart.d.ts +4 -0
- package/lib/commands/quickstart.d.ts.map +1 -0
- package/lib/commands/quickstart.js +274 -0
- package/lib/commands/quickstart.js.map +1 -0
- package/lib/index.js +20 -1
- package/lib/index.js.map +1 -1
- package/lib/utils/bundle.d.ts +86 -0
- package/lib/utils/bundle.d.ts.map +1 -1
- package/lib/utils/bundle.js +244 -14
- package/lib/utils/bundle.js.map +1 -1
- package/lib/utils/credentials.d.ts +12 -1
- package/lib/utils/credentials.d.ts.map +1 -1
- package/lib/utils/credentials.js +40 -1
- package/lib/utils/credentials.js.map +1 -1
- package/lib/utils/transportSecurity.d.ts +36 -0
- package/lib/utils/transportSecurity.d.ts.map +1 -0
- package/lib/utils/transportSecurity.js +74 -0
- package/lib/utils/transportSecurity.js.map +1 -0
- package/lib/utils/ui.d.ts.map +1 -1
- package/lib/utils/ui.js +2 -0
- package/lib/utils/ui.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,8 +1,50 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.registerOtaDeleteBundleCommand = registerOtaDeleteBundleCommand;
|
|
37
|
+
const readline = __importStar(require("readline"));
|
|
4
38
|
const credentials_1 = require("../utils/credentials");
|
|
5
39
|
const client_1 = require("../api/client");
|
|
40
|
+
/**
|
|
41
|
+
* `scalebun ota delete-bundle` — permanent, irreversible artifact deletion.
|
|
42
|
+
*
|
|
43
|
+
* It asks first. This used to delete on sight: one mis-pasted --bundle-id and
|
|
44
|
+
* the artifact was gone, with devices possibly still running it. `ota rollback`
|
|
45
|
+
* already had this shape (prompt on a TTY, explicit --yes off-TTY); a command
|
|
46
|
+
* whose own description says "permanently" needs it at least as much.
|
|
47
|
+
*/
|
|
6
48
|
function registerOtaDeleteBundleCommand(ota) {
|
|
7
49
|
ota
|
|
8
50
|
.command('delete-bundle')
|
|
@@ -10,12 +52,30 @@ function registerOtaDeleteBundleCommand(ota) {
|
|
|
10
52
|
.description('Delete an OTA bundle permanently')
|
|
11
53
|
.requiredOption('--app-id <id>', 'Scalebun app ID')
|
|
12
54
|
.requiredOption('--bundle-id <id>', 'OTA bundle ID to delete')
|
|
55
|
+
.option('--yes', 'Skip the confirmation prompt (required in CI / non-interactive shells)')
|
|
13
56
|
.action(async (opts) => {
|
|
14
57
|
const creds = (0, credentials_1.readCredentials)();
|
|
15
58
|
if (!creds) {
|
|
16
59
|
console.error('❌ Not logged in. Run `scalebun login` first.');
|
|
17
60
|
process.exit(1);
|
|
18
61
|
}
|
|
62
|
+
if (!opts.yes) {
|
|
63
|
+
if (!process.stdin.isTTY) {
|
|
64
|
+
console.error('❌ Refusing to delete without confirmation in a non-interactive shell.\n' +
|
|
65
|
+
' Re-run with --yes if this is intentional.');
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
console.log(`\n⚠️ This permanently deletes bundle ${opts.bundleId} from app ${opts.appId}.\n` +
|
|
69
|
+
' Devices still running it keep their local copy, but it can never be\n' +
|
|
70
|
+
' re-served, and any release pointing at it becomes undeliverable.\n');
|
|
71
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
72
|
+
const answer = await new Promise((resolve) => rl.question(' Type the bundle id to confirm: ', resolve));
|
|
73
|
+
rl.close();
|
|
74
|
+
if (answer.trim() !== opts.bundleId) {
|
|
75
|
+
console.log('Aborted — nothing was deleted.');
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
19
79
|
const client = new client_1.ApiClient(creds);
|
|
20
80
|
try {
|
|
21
81
|
console.log(`🗑️ Deleting bundle ${opts.bundleId} from App ${opts.appId}…`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ota-delete-bundle.js","sourceRoot":"","sources":["../../src/commands/ota-delete-bundle.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ota-delete-bundle.js","sourceRoot":"","sources":["../../src/commands/ota-delete-bundle.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAaA,wEAiDC;AA9DD,mDAAqC;AAErC,sDAAuD;AACvD,0CAA0C;AAE1C;;;;;;;GAOG;AACH,SAAgB,8BAA8B,CAAC,GAAY;IACzD,GAAG;SACA,OAAO,CAAC,eAAe,CAAC;SACxB,KAAK,CAAC,QAAQ,CAAC;SACf,WAAW,CAAC,kCAAkC,CAAC;SAC/C,cAAc,CAAC,eAAe,EAAE,iBAAiB,CAAC;SAClD,cAAc,CAAC,kBAAkB,EAAE,yBAAyB,CAAC;SAC7D,MAAM,CAAC,OAAO,EAAE,wEAAwE,CAAC;SACzF,MAAM,CAAC,KAAK,EAAE,IAAwD,EAAE,EAAE;QACzE,MAAM,KAAK,GAAG,IAAA,6BAAe,GAAE,CAAC;QAChC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,CAAC,KAAK,CAAC,8CAA8C,CAAC,CAAC;YAC9D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;YACd,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;gBACzB,OAAO,CAAC,KAAK,CACX,yEAAyE;oBACvE,8CAA8C,CACjD,CAAC;gBACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAClB,CAAC;YACD,OAAO,CAAC,GAAG,CACT,yCAAyC,IAAI,CAAC,QAAQ,aAAa,IAAI,CAAC,KAAK,KAAK;gBAChF,0EAA0E;gBAC1E,uEAAuE,CAC1E,CAAC;YACF,MAAM,EAAE,GAAG,QAAQ,CAAC,eAAe,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;YACtF,MAAM,MAAM,GAAG,MAAM,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,EAAE,CACnD,EAAE,CAAC,QAAQ,CAAC,oCAAoC,EAAE,OAAO,CAAC,CAC3D,CAAC;YACF,EAAE,CAAC,KAAK,EAAE,CAAC;YACX,IAAI,MAAM,CAAC,IAAI,EAAE,KAAK,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACpC,OAAO,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAC;gBAC9C,OAAO;YACT,CAAC;QACH,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,kBAAS,CAAC,KAAK,CAAC,CAAC;QACpC,IAAI,CAAC;YACH,OAAO,CAAC,GAAG,CAAC,uBAAuB,IAAI,CAAC,QAAQ,aAAa,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC;YAC5E,MAAM,MAAM,CAAC,MAAM,CAAM,cAAc,IAAI,CAAC,KAAK,gBAAgB,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;YAClF,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,CAAC,QAAQ,wBAAwB,CAAC,CAAC;QACjE,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAClB,OAAO,CAAC,KAAK,CAAC,8BAA8B,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;YAC3D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import * as crypto from 'crypto';
|
|
3
|
+
/** Hex-encoded raw 32-byte ed25519 public key from a Node KeyObject. */
|
|
4
|
+
export declare function rawPublicKeyHex(publicKey: crypto.KeyObject): string;
|
|
5
|
+
export declare function registerOtaKeysCommand(ota: Command): void;
|
|
6
|
+
/**
|
|
7
|
+
* Sign a bundle hash with the locally-held private key.
|
|
8
|
+
*
|
|
9
|
+
* Returns null when no key is present — publishing without signing stays valid
|
|
10
|
+
* for apps that have not adopted it, and `ota publish` reports which happened
|
|
11
|
+
* rather than failing.
|
|
12
|
+
*/
|
|
13
|
+
export declare function signBundleHash(sha256Hex: string, keyDir?: string): string | null;
|
|
14
|
+
//# sourceMappingURL=ota-keys.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ota-keys.d.ts","sourceRoot":"","sources":["../../src/commands/ota-keys.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AA+BjC,wEAAwE;AACxE,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,CAAC,SAAS,GAAG,MAAM,CAInE;AAED,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CA0JzD;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,MAAM,GACd,MAAM,GAAG,IAAI,CAcf"}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.rawPublicKeyHex = rawPublicKeyHex;
|
|
37
|
+
exports.registerOtaKeysCommand = registerOtaKeysCommand;
|
|
38
|
+
exports.signBundleHash = signBundleHash;
|
|
39
|
+
const crypto = __importStar(require("crypto"));
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const credentials_1 = require("../utils/credentials");
|
|
43
|
+
const client_1 = require("../api/client");
|
|
44
|
+
/**
|
|
45
|
+
* Bundle signing keys (OTA-03).
|
|
46
|
+
*
|
|
47
|
+
* The security model, which is the reason this command exists at all: the
|
|
48
|
+
* private key is generated on the developer's machine and NEVER leaves it. Only
|
|
49
|
+
* the public half is uploaded. A server that cannot sign cannot forge a bundle,
|
|
50
|
+
* so a compromise of ScaleBun's infrastructure still cannot push code to a
|
|
51
|
+
* customer's users.
|
|
52
|
+
*
|
|
53
|
+
* `scalebun ota generate-key-pair` was referenced in the product documentation
|
|
54
|
+
* before it existed — the docs described verification the SDK never performed
|
|
55
|
+
* and a command the CLI never registered. This implements it.
|
|
56
|
+
*/
|
|
57
|
+
const KEY_DIR = '.scalebun';
|
|
58
|
+
const PRIVATE_KEY_FILE = 'ota-signing-key.pem';
|
|
59
|
+
/** Hex-encoded raw 32-byte ed25519 public key from a Node KeyObject. */
|
|
60
|
+
function rawPublicKeyHex(publicKey) {
|
|
61
|
+
// SPKI for ed25519 is a fixed 12-byte header followed by the 32-byte key.
|
|
62
|
+
const spki = publicKey.export({ type: 'spki', format: 'der' });
|
|
63
|
+
return spki.subarray(spki.length - 32).toString('hex');
|
|
64
|
+
}
|
|
65
|
+
function registerOtaKeysCommand(ota) {
|
|
66
|
+
ota
|
|
67
|
+
.command('generate-key-pair')
|
|
68
|
+
.description('Generate an ed25519 signing keypair locally and register the public half')
|
|
69
|
+
.option('--app-id <id>', 'Register the public key against this app')
|
|
70
|
+
.option('--name <name>', 'Label for the key in the dashboard')
|
|
71
|
+
.option('--out <dir>', `Directory for the private key (default: ./${KEY_DIR})`)
|
|
72
|
+
.option('--force', 'Overwrite an existing private key')
|
|
73
|
+
.action(async (opts) => {
|
|
74
|
+
const outDir = path.resolve(opts.out ?? KEY_DIR);
|
|
75
|
+
const privatePath = path.join(outDir, PRIVATE_KEY_FILE);
|
|
76
|
+
if (fs.existsSync(privatePath) && !opts.force) {
|
|
77
|
+
console.error(`❌ A signing key already exists at ${privatePath}.\n` +
|
|
78
|
+
` Overwriting it means every bundle signed with the old key can no longer be\n` +
|
|
79
|
+
` verified by apps carrying the old public key. Pass --force if that is intended.`);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
const { publicKey, privateKey } = crypto.generateKeyPairSync('ed25519');
|
|
83
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
84
|
+
fs.writeFileSync(privatePath, privateKey.export({ type: 'pkcs8', format: 'pem' }),
|
|
85
|
+
// Owner read/write only. Irrelevant on Windows, correct everywhere else.
|
|
86
|
+
{ mode: 0o600 });
|
|
87
|
+
const publicHex = rawPublicKeyHex(publicKey);
|
|
88
|
+
console.log(`\n🔑 ed25519 signing keypair generated.\n`);
|
|
89
|
+
console.log(` Private key : ${privatePath}`);
|
|
90
|
+
console.log(` NEVER commit this. Add ${KEY_DIR}/ to .gitignore.`);
|
|
91
|
+
console.log(` Public key : ${publicHex}\n`);
|
|
92
|
+
if (opts.appId) {
|
|
93
|
+
const creds = (0, credentials_1.readCredentials)();
|
|
94
|
+
if (!creds) {
|
|
95
|
+
console.error('❌ Not logged in. Run `scalebun login` first.');
|
|
96
|
+
console.error(' The keypair was still written; register it later with:');
|
|
97
|
+
console.error(` scalebun ota register-key --app-id <id> --public-key ${publicHex}`);
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
const client = new client_1.ApiClient(creds);
|
|
101
|
+
try {
|
|
102
|
+
await client.post(`/saas/apps/${opts.appId}/ota/signing-keys`, {
|
|
103
|
+
publicKey: publicHex,
|
|
104
|
+
name: opts.name ?? `CLI ${new Date().toISOString().slice(0, 10)}`,
|
|
105
|
+
});
|
|
106
|
+
console.log('✅ Public key registered with ScaleBun.\n');
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
console.error(`❌ Failed to register public key: ${err.message}`);
|
|
110
|
+
console.error(` Retry with: scalebun ota register-key --app-id ${opts.appId} --public-key ${publicHex}\n`);
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
console.log('ℹ️ Pass --app-id to register the public key automatically.\n');
|
|
116
|
+
}
|
|
117
|
+
console.log('Next: embed the PUBLIC key in your app and enforce it at init:\n');
|
|
118
|
+
console.log(' ScaleBun.init({');
|
|
119
|
+
console.log(' // …');
|
|
120
|
+
console.log(' ota: {');
|
|
121
|
+
console.log(` publicSigningKey: '${publicHex}',`);
|
|
122
|
+
console.log(' verifySignature: myEd25519Verifier,');
|
|
123
|
+
console.log(' },');
|
|
124
|
+
console.log(' })\n');
|
|
125
|
+
});
|
|
126
|
+
ota
|
|
127
|
+
.command('register-key')
|
|
128
|
+
.description('Register an existing ed25519 public key with an app')
|
|
129
|
+
.requiredOption('--app-id <id>', 'Scalebun app ID')
|
|
130
|
+
.requiredOption('--public-key <hex>', 'ed25519 public key (hex or base64)')
|
|
131
|
+
.option('--name <name>', 'Label for the key in the dashboard')
|
|
132
|
+
.action(async (opts) => {
|
|
133
|
+
const creds = (0, credentials_1.readCredentials)();
|
|
134
|
+
if (!creds) {
|
|
135
|
+
console.error('❌ Not logged in. Run `scalebun login` first.');
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
const client = new client_1.ApiClient(creds);
|
|
139
|
+
try {
|
|
140
|
+
await client.post(`/saas/apps/${opts.appId}/ota/signing-keys`, {
|
|
141
|
+
publicKey: opts.publicKey,
|
|
142
|
+
name: opts.name ?? 'CLI',
|
|
143
|
+
});
|
|
144
|
+
console.log('✅ Public key registered.\n');
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
console.error(`❌ Failed to register public key: ${err.message}`);
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
ota
|
|
152
|
+
.command('keys')
|
|
153
|
+
.description('List signing keys registered for an app')
|
|
154
|
+
.requiredOption('--app-id <id>', 'Scalebun app ID')
|
|
155
|
+
.action(async (opts) => {
|
|
156
|
+
const creds = (0, credentials_1.readCredentials)();
|
|
157
|
+
if (!creds) {
|
|
158
|
+
console.error('❌ Not logged in. Run `scalebun login` first.');
|
|
159
|
+
process.exit(1);
|
|
160
|
+
}
|
|
161
|
+
const client = new client_1.ApiClient(creds);
|
|
162
|
+
try {
|
|
163
|
+
const keys = await client.get(`/saas/apps/${opts.appId}/ota/signing-keys`);
|
|
164
|
+
console.log(`\n🔑 Signing keys for app ${opts.appId}:\n`);
|
|
165
|
+
if (!keys?.length) {
|
|
166
|
+
console.log(' (none — run `scalebun ota generate-key-pair --app-id <id>`)\n');
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
for (const k of keys) {
|
|
170
|
+
const state = k.revokedAt ? 'REVOKED' : 'active';
|
|
171
|
+
console.log(` • ${(k.name ?? 'unnamed').padEnd(20)} ${state.padEnd(8)} ${k.publicKey.slice(0, 16)}…`);
|
|
172
|
+
}
|
|
173
|
+
console.log('');
|
|
174
|
+
}
|
|
175
|
+
catch (err) {
|
|
176
|
+
console.error(`❌ Failed to list keys: ${err.message}`);
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Sign a bundle hash with the locally-held private key.
|
|
183
|
+
*
|
|
184
|
+
* Returns null when no key is present — publishing without signing stays valid
|
|
185
|
+
* for apps that have not adopted it, and `ota publish` reports which happened
|
|
186
|
+
* rather than failing.
|
|
187
|
+
*/
|
|
188
|
+
function signBundleHash(sha256Hex, keyDir) {
|
|
189
|
+
const privatePath = path.join(path.resolve(keyDir ?? KEY_DIR), PRIVATE_KEY_FILE);
|
|
190
|
+
if (!fs.existsSync(privatePath))
|
|
191
|
+
return null;
|
|
192
|
+
const privateKey = crypto.createPrivateKey(fs.readFileSync(privatePath));
|
|
193
|
+
// The signature covers the bundle's SHA-256, which the device already has from
|
|
194
|
+
// the check response and re-computes from the downloaded bytes. Signing the
|
|
195
|
+
// hash rather than the bundle keeps the CLI from re-reading 20 MB, and gives
|
|
196
|
+
// the device one value to verify that transitively covers the whole artifact.
|
|
197
|
+
const signature = crypto.sign(null, Buffer.from(sha256Hex, 'hex'), privateKey);
|
|
198
|
+
return signature.toString('hex');
|
|
199
|
+
}
|
|
200
|
+
//# sourceMappingURL=ota-keys.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ota-keys.js","sourceRoot":"","sources":["../../src/commands/ota-keys.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCA,0CAIC;AAED,wDA0JC;AASD,wCAiBC;AA1ND,+CAAiC;AACjC,uCAAyB;AACzB,2CAA6B;AAC7B,sDAAuD;AACvD,0CAA0C;AAE1C;;;;;;;;;;;;GAYG;AAEH,MAAM,OAAO,GAAG,WAAW,CAAC;AAC5B,MAAM,gBAAgB,GAAG,qBAAqB,CAAC;AAU/C,wEAAwE;AACxE,SAAgB,eAAe,CAAC,SAA2B;IACzD,0EAA0E;IAC1E,MAAM,IAAI,GAAG,SAAS,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,CAAW,CAAC;IACzE,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACzD,CAAC;AAED,SAAgB,sBAAsB,CAAC,GAAY;IACjD,GAAG;SACA,OAAO,CAAC,mBAAmB,CAAC;SAC5B,WAAW,CACV,0EAA0E,CAC3E;SACA,MAAM,CAAC,eAAe,EAAE,0CAA0C,CAAC;SACnE,MAAM,CAAC,eAAe,EAAE,oCAAoC,CAAC;SAC7D,MAAM,CACL,aAAa,EACb,6CAA6C,OAAO,GAAG,CACxD;SACA,MAAM,CAAC,SAAS,EAAE,mCAAmC,CAAC;SACtD,MAAM,CACL,KAAK,EAAE,IAKN,EAAE,EAAE;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,CAAC;QACjD,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;QAExD,IAAI,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;YAC9C,OAAO,CAAC,KAAK,CACX,qCAAqC,WAAW,KAAK;gBACnD,iFAAiF;gBACjF,oFAAoF,CACvF,CAAC;YACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,GAAG,MAAM,CAAC,mBAAmB,CAAC,SAAS,CAAC,CAAC;QAExE,EAAE,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1C,EAAE,CAAC,aAAa,CACd,WAAW,EACX,UAAU,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAW;QAC7D,yEAAyE;QACzE,EAAE,IAAI,EAAE,KAAK,EAAE,CAChB,CAAC;QAEF,MAAM,SAAS,GAAG,eAAe,CAAC,SAAS,CAAC,CAAC;QAE7C,OAAO,CAAC,GAAG,CAAC,2CAA2C,CAAC,CAAC;QACzD,OAAO,CAAC,GAAG,CAAC,mBAAmB,WAAW,EAAE,CAAC,CAAC;QAC9C,OAAO,CAAC,GAAG,CAAC,0CAA0C,OAAO,kBAAkB,CAAC,CAAC;QACjF,OAAO,CAAC,GAAG,CAAC,mBAAmB,SAAS,IAAI,CAAC,CAAC;QAE9C,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACf,MAAM,KAAK,GAAG,IAAA,6BAAe,GAAE,CAAC;YAChC,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,OAAO,CAAC,KAAK,CAAC,8CAA8C,CAAC,CAAC;gBAC9D,OAAO,CAAC,KAAK,CACX,2DAA2D,CAC5D,CAAC;gBACF,OAAO,CAAC,KAAK,CACX,2DAA2D,SAAS,EAAE,CACvE,CAAC;gBACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAClB,CAAC;YAED,MAAM,MAAM,GAAG,IAAI,kBAAS,CAAC,KAAK,CAAC,CAAC;YACpC,IAAI,CAAC;gBACH,MAAM,MAAM,CAAC,IAAI,CAAC,cAAc,IAAI,CAAC,KAAK,mBAAmB,EAAE;oBAC7D,SAAS,EAAE,SAAS;oBACpB,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE;iBAClE,CAAC,CAAC;gBACH,OAAO,CAAC,GAAG,CAAC,0CAA0C,CAAC,CAAC;YAC1D,CAAC;YAAC,OAAO,GAAQ,EAAE,CAAC;gBAClB,OAAO,CAAC,KAAK,CAAC,oCAAoC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;gBACjE,OAAO,CAAC,KAAK,CACX,qDAAqD,IAAI,CAAC,KAAK,iBAAiB,SAAS,IAAI,CAC9F,CAAC;gBACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAClB,CAAC;QACH,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CACT,+DAA+D,CAChE,CAAC;QACJ,CAAC;QAED,OAAO,CAAC,GAAG,CAAC,kEAAkE,CAAC,CAAC;QAChF,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;QACjC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QACxB,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QAC1B,OAAO,CAAC,GAAG,CAAC,4BAA4B,SAAS,IAAI,CAAC,CAAC;QACvD,OAAO,CAAC,GAAG,CAAC,2CAA2C,CAAC,CAAC;QACzD,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACtB,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACxB,CAAC,CACF,CAAC;IAEJ,GAAG;SACA,OAAO,CAAC,cAAc,CAAC;SACvB,WAAW,CAAC,qDAAqD,CAAC;SAClE,cAAc,CAAC,eAAe,EAAE,iBAAiB,CAAC;SAClD,cAAc,CAAC,oBAAoB,EAAE,oCAAoC,CAAC;SAC1E,MAAM,CAAC,eAAe,EAAE,oCAAoC,CAAC;SAC7D,MAAM,CACL,KAAK,EAAE,IAAyD,EAAE,EAAE;QAClE,MAAM,KAAK,GAAG,IAAA,6BAAe,GAAE,CAAC;QAChC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,CAAC,KAAK,CAAC,8CAA8C,CAAC,CAAC;YAC9D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,kBAAS,CAAC,KAAK,CAAC,CAAC;QACpC,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,IAAI,CAAC,cAAc,IAAI,CAAC,KAAK,mBAAmB,EAAE;gBAC7D,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,KAAK;aACzB,CAAC,CAAC;YACH,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC;QAC5C,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAClB,OAAO,CAAC,KAAK,CAAC,oCAAoC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;YACjE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC,CACF,CAAC;IAEJ,GAAG;SACA,OAAO,CAAC,MAAM,CAAC;SACf,WAAW,CAAC,yCAAyC,CAAC;SACtD,cAAc,CAAC,eAAe,EAAE,iBAAiB,CAAC;SAClD,MAAM,CAAC,KAAK,EAAE,IAAuB,EAAE,EAAE;QACxC,MAAM,KAAK,GAAG,IAAA,6BAAe,GAAE,CAAC;QAChC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,CAAC,KAAK,CAAC,8CAA8C,CAAC,CAAC;YAC9D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,kBAAS,CAAC,KAAK,CAAC,CAAC;QACpC,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,GAAG,CAC3B,cAAc,IAAI,CAAC,KAAK,mBAAmB,CAC5C,CAAC;YACF,OAAO,CAAC,GAAG,CAAC,6BAA6B,IAAI,CAAC,KAAK,KAAK,CAAC,CAAC;YAC1D,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC;gBAClB,OAAO,CAAC,GAAG,CACT,iEAAiE,CAClE,CAAC;gBACF,OAAO;YACT,CAAC;YACD,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;gBACrB,MAAM,KAAK,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC;gBACjD,OAAO,CAAC,GAAG,CACT,OAAO,CAAC,CAAC,CAAC,IAAI,IAAI,SAAS,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAC1F,CAAC;YACJ,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAClB,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAClB,OAAO,CAAC,KAAK,CAAC,0BAA0B,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;YACvD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,cAAc,CAC5B,SAAiB,EACjB,MAAe;IAEf,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAC3B,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,EAC/B,gBAAgB,CACjB,CAAC;IACF,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC;QAAE,OAAO,IAAI,CAAC;IAE7C,MAAM,UAAU,GAAG,MAAM,CAAC,gBAAgB,CAAC,EAAE,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC,CAAC;IACzE,+EAA+E;IAC/E,4EAA4E;IAC5E,6EAA6E;IAC7E,8EAA8E;IAC9E,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,EAAE,UAAU,CAAC,CAAC;IAC/E,OAAO,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACnC,CAAC"}
|
|
@@ -12,5 +12,15 @@ export type InstallMode = 'IMMEDIATE' | 'ON_NEXT_RESTART' | 'ON_NEXT_RESUME';
|
|
|
12
12
|
* 6. POST /saas/apps/:appId/ota/bundles/:id/promote → configure installMode & rollout
|
|
13
13
|
* 7. Print success
|
|
14
14
|
*/
|
|
15
|
+
/**
|
|
16
|
+
* Parse `--rollout`, refusing anything that isn't 0-100.
|
|
17
|
+
*
|
|
18
|
+
* This was `parseInt(x, 10) || 100`, which turned BOTH `--rollout 0` and any
|
|
19
|
+
* typo into a 100% rollout — the opposite of what the operator asked for. A
|
|
20
|
+
* dark release (0%) is a documented, legitimate way to promote a bundle without
|
|
21
|
+
* exposing it, so silently inverting it exposes every production device to an
|
|
22
|
+
* untested bundle. Exits rather than guessing.
|
|
23
|
+
*/
|
|
24
|
+
export declare function parseRolloutPercent(raw: string | undefined): number;
|
|
15
25
|
export declare function registerOtaPublishCommand(ota: Command): void;
|
|
16
26
|
//# sourceMappingURL=ota-publish.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ota-publish.d.ts","sourceRoot":"","sources":["../../src/commands/ota-publish.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ota-publish.d.ts","sourceRoot":"","sources":["../../src/commands/ota-publish.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA6BpC,MAAM,MAAM,WAAW,GAAG,WAAW,GAAG,iBAAiB,GAAG,gBAAgB,CAAC;AAE7E;;;;;;;;;;;GAWG;AACH;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAWnE;AAED,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAuc5D"}
|