@serve.zone/cli 33.10.0 → 33.12.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.
@@ -14,5 +14,12 @@ export declare class CliClient {
14
14
  * the cluster, the generation, the size, the SHA-256 and the audit receipt, never the bearer.
15
15
  */
16
16
  exportRelayAuthorization(requestArg: plugins.servezoneInterfaces.data.IClusterRelayAuthorizationExportRequest, outputPathArg: string): Promise<void>;
17
+ /**
18
+ * Stores the Corestore control token while it is still exactly at `expectedArg` and prints the
19
+ * state and the receipt the set produced, never the token. The token is the caller's to wipe.
20
+ */
21
+ setCorestoreControlToken(expectedArg: plugins.servezoneInterfaces.data.TCorestoreControlTokenState, tokenArg: Uint8Array): Promise<void>;
22
+ /** Prints whether Cloudly holds the Corestore control token, its revision and its receipts. */
23
+ getCorestoreControlTokenState(): Promise<void>;
17
24
  getGatewayStatus(): Promise<void>;
18
25
  }
@@ -1,6 +1,7 @@
1
1
  import * as plugins from './plugins.js';
2
2
  import { CloudlyApiClient } from '@serve.zone/api';
3
3
  import { exportRelayAuthorizationToFile } from './relayauthorization.js';
4
+ import { getCorestoreControlTokenState, setCorestoreControlToken } from './corestorecontroltoken.js';
4
5
  export class CliClient {
5
6
  constructor(cloudlyApiClientArg) {
6
7
  this.cloudlyApiClient = cloudlyApiClientArg;
@@ -28,9 +29,21 @@ export class CliClient {
28
29
  const output = await exportRelayAuthorizationToFile(this.cloudlyApiClient, requestArg, outputPathArg);
29
30
  console.log(JSON.stringify({ ok: true, command: 'relay export-authorization', ...output }));
30
31
  }
32
+ /**
33
+ * Stores the Corestore control token while it is still exactly at `expectedArg` and prints the
34
+ * state and the receipt the set produced, never the token. The token is the caller's to wipe.
35
+ */
36
+ async setCorestoreControlToken(expectedArg, tokenArg) {
37
+ const output = await setCorestoreControlToken(this.cloudlyApiClient, expectedArg, tokenArg);
38
+ console.log(JSON.stringify(output));
39
+ }
40
+ /** Prints whether Cloudly holds the Corestore control token, its revision and its receipts. */
41
+ async getCorestoreControlTokenState() {
42
+ console.log(JSON.stringify(await getCorestoreControlTokenState(this.cloudlyApiClient)));
43
+ }
31
44
  async getGatewayStatus() {
32
45
  const { state } = await this.cloudlyApiClient.settings.getExternalGatewayEnrollmentState();
33
46
  console.log(JSON.stringify(state, null, 2));
34
47
  }
35
48
  }
36
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5jbGljbGllbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90c19jbGljbGllbnQvY2xhc3Nlcy5jbGljbGllbnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxjQUFjLENBQUM7QUFDeEMsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFDbkQsT0FBTyxFQUFFLDhCQUE4QixFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFFekUsTUFBTSxPQUFPLFNBQVM7SUFHcEIsWUFBWSxtQkFBcUM7UUFDL0MsSUFBSSxDQUFDLGdCQUFnQixHQUFHLG1CQUFtQixDQUFDO0lBQzlDLENBQUM7SUFFTSxLQUFLLENBQUMsV0FBVztRQUN0QixNQUFNLFFBQVEsR0FBRyxNQUFNLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxPQUFPLENBQUMsV0FBVyxFQUFFLENBQUM7UUFDbkUsT0FBTyxDQUFDLEdBQUcsQ0FBQyxRQUFRLENBQUMsQ0FBQztJQUN4QixDQUFDO0lBRUQ7OztPQUdHO0lBQ0ksS0FBSyxDQUFDLGdCQUFnQixDQUMzQixTQUErRCxFQUMvRCxpQkFBNkI7UUFFN0IsTUFBTSxFQUFFLEtBQUssRUFBRSxHQUFHLE1BQU0sSUFBSSxDQUFDLGdCQUFnQixDQUFDLFFBQVEsQ0FBQyxrQ0FBa0MsQ0FBQztZQUN4RixNQUFNLEVBQUUsU0FBUztZQUNqQixjQUFjLEVBQUUsaUJBQWlCO1NBQ2xDLENBQUMsQ0FBQztRQUNILE9BQU8sQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxLQUFLLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDOUMsQ0FBQztJQUVEOzs7T0FHRztJQUNJLEtBQUssQ0FBQyx3QkFBd0IsQ0FDbkMsVUFBb0YsRUFDcEYsYUFBcUI7UUFFckIsTUFBTSxNQUFNLEdBQUcsTUFBTSw4QkFBOEIsQ0FDakQsSUFBSSxDQUFDLGdCQUFnQixFQUFFLFVBQVUsRUFBRSxhQUFhLENBQUMsQ0FBQztRQUNwRCxPQUFPLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsRUFBRSxFQUFFLEVBQUUsSUFBSSxFQUFFLE9BQU8sRUFBRSw0QkFBNEIsRUFBRSxHQUFHLE1BQU0sRUFBRSxDQUFDLENBQUMsQ0FBQztJQUM5RixDQUFDO0lBRU0sS0FBSyxDQUFDLGdCQUFnQjtRQUMzQixNQUFNLEVBQUUsS0FBSyxFQUFFLEdBQUcsTUFBTSxJQUFJLENBQUMsZ0JBQWdCLENBQUMsUUFBUSxDQUFDLGlDQUFpQyxFQUFFLENBQUM7UUFDM0YsT0FBTyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLEtBQUssRUFBRSxJQUFJLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUM5QyxDQUFDO0NBQ0YifQ==
49
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5jbGljbGllbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90c19jbGljbGllbnQvY2xhc3Nlcy5jbGljbGllbnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxjQUFjLENBQUM7QUFDeEMsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFDbkQsT0FBTyxFQUFFLDhCQUE4QixFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFDekUsT0FBTyxFQUFFLDZCQUE2QixFQUFFLHdCQUF3QixFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFFckcsTUFBTSxPQUFPLFNBQVM7SUFHcEIsWUFBWSxtQkFBcUM7UUFDL0MsSUFBSSxDQUFDLGdCQUFnQixHQUFHLG1CQUFtQixDQUFDO0lBQzlDLENBQUM7SUFFTSxLQUFLLENBQUMsV0FBVztRQUN0QixNQUFNLFFBQVEsR0FBRyxNQUFNLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxPQUFPLENBQUMsV0FBVyxFQUFFLENBQUM7UUFDbkUsT0FBTyxDQUFDLEdBQUcsQ0FBQyxRQUFRLENBQUMsQ0FBQztJQUN4QixDQUFDO0lBRUQ7OztPQUdHO0lBQ0ksS0FBSyxDQUFDLGdCQUFnQixDQUMzQixTQUErRCxFQUMvRCxpQkFBNkI7UUFFN0IsTUFBTSxFQUFFLEtBQUssRUFBRSxHQUFHLE1BQU0sSUFBSSxDQUFDLGdCQUFnQixDQUFDLFFBQVEsQ0FBQyxrQ0FBa0MsQ0FBQztZQUN4RixNQUFNLEVBQUUsU0FBUztZQUNqQixjQUFjLEVBQUUsaUJBQWlCO1NBQ2xDLENBQUMsQ0FBQztRQUNILE9BQU8sQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxLQUFLLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDOUMsQ0FBQztJQUVEOzs7T0FHRztJQUNJLEtBQUssQ0FBQyx3QkFBd0IsQ0FDbkMsVUFBb0YsRUFDcEYsYUFBcUI7UUFFckIsTUFBTSxNQUFNLEdBQUcsTUFBTSw4QkFBOEIsQ0FDakQsSUFBSSxDQUFDLGdCQUFnQixFQUFFLFVBQVUsRUFBRSxhQUFhLENBQUMsQ0FBQztRQUNwRCxPQUFPLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsRUFBRSxFQUFFLEVBQUUsSUFBSSxFQUFFLE9BQU8sRUFBRSw0QkFBNEIsRUFBRSxHQUFHLE1BQU0sRUFBRSxDQUFDLENBQUMsQ0FBQztJQUM5RixDQUFDO0lBRUQ7OztPQUdHO0lBQ0ksS0FBSyxDQUFDLHdCQUF3QixDQUNuQyxXQUF5RSxFQUN6RSxRQUFvQjtRQUVwQixNQUFNLE1BQU0sR0FBRyxNQUFNLHdCQUF3QixDQUFDLElBQUksQ0FBQyxnQkFBZ0IsRUFBRSxXQUFXLEVBQUUsUUFBUSxDQUFDLENBQUM7UUFDNUYsT0FBTyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUM7SUFDdEMsQ0FBQztJQUVELCtGQUErRjtJQUN4RixLQUFLLENBQUMsNkJBQTZCO1FBQ3hDLE9BQU8sQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxNQUFNLDZCQUE2QixDQUFDLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUMxRixDQUFDO0lBRU0sS0FBSyxDQUFDLGdCQUFnQjtRQUMzQixNQUFNLEVBQUUsS0FBSyxFQUFFLEdBQUcsTUFBTSxJQUFJLENBQUMsZ0JBQWdCLENBQUMsUUFBUSxDQUFDLGlDQUFpQyxFQUFFLENBQUM7UUFDM0YsT0FBTyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLEtBQUssRUFBRSxJQUFJLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUM5QyxDQUFDO0NBQ0YifQ==
@@ -14,6 +14,12 @@ export type TCliCommand =
14
14
  request: plugins.servezoneInterfaces.data.IClusterRelayAuthorizationExportRequest;
15
15
  /** A normalized absolute path in a private directory; the file must not exist yet. */
16
16
  outputPath: string;
17
+ } | {
18
+ kind: 'corestore-set-control-token';
19
+ /** Exactly the state the administrator last read; never a minimum. */
20
+ expected: plugins.servezoneInterfaces.data.TCorestoreControlTokenState;
21
+ } | {
22
+ kind: 'corestore-control-token-state';
17
23
  };
18
24
  export declare const cliUsage: string;
19
25
  /**
@@ -7,6 +7,9 @@ export const cliUsage = [
7
7
  ' The dcrouter admin token is read from stdin, never from arguments or the environment.',
8
8
  ' servezone relay export-authorization --cluster-id <id> --expected-generation <n> --output <absolute path>',
9
9
  ' Writes the relay bearer to a new 0600 file in a directory only you can write; never to stdout.',
10
+ ' servezone corestore set-control-token --stdin --expect absent|<revision>',
11
+ ' The Corestore control token is read from stdin, never from arguments or the environment.',
12
+ ' servezone corestore control-token-state',
10
13
  ].join('\n');
11
14
  /**
12
15
  * An invocation the CLI does not run. It never quotes what it was given, because an argument may
@@ -42,6 +45,8 @@ export const parseCliCommand = (argsArg) => {
42
45
  const [group, action, ...rest] = argsArg;
43
46
  if (group === 'relay')
44
47
  return parseRelayCommand(action, rest);
48
+ if (group === 'corestore')
49
+ return parseCorestoreCommand(action, rest);
45
50
  requireUsage(group === 'gateway');
46
51
  if (action === 'status') {
47
52
  requireUsage(rest.length === 0);
@@ -54,19 +59,49 @@ export const parseCliCommand = (argsArg) => {
54
59
  target: { gatewayUrl: options['--gateway-url'], gatewayClientId: options['--client-id'] },
55
60
  };
56
61
  };
62
+ /** A positive integer without leading zeros, as a generation or a revision is named. */
63
+ const parseCounter = (valueArg) => {
64
+ requireUsage(/^[1-9][0-9]{0,15}$/.test(valueArg) && Number.isSafeInteger(Number(valueArg)));
65
+ return Number(valueArg);
66
+ };
57
67
  /** `relay export-authorization`: the cluster, the exact generation and a normalized absolute output. */
58
68
  const parseRelayCommand = (actionArg, restArg) => {
59
69
  requireUsage(actionArg === 'export-authorization');
60
70
  const options = parseOptions(restArg, ['--cluster-id', '--expected-generation', '--output']);
61
- const generation = options['--expected-generation'];
62
- requireUsage(/^[1-9][0-9]{0,15}$/.test(generation) && Number.isSafeInteger(Number(generation)));
71
+ const generation = parseCounter(options['--expected-generation']);
63
72
  const outputPath = options['--output'];
64
73
  requireUsage(plugins.path.isAbsolute(outputPath) && plugins.path.normalize(outputPath) === outputPath
65
74
  && !outputPath.endsWith(plugins.path.sep));
66
75
  return {
67
76
  kind: 'relay-export-authorization',
68
- request: { clusterId: options['--cluster-id'], expectedGeneration: Number(generation) },
77
+ request: { clusterId: options['--cluster-id'], expectedGeneration: generation },
69
78
  outputPath,
70
79
  };
71
80
  };
72
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29tbWFuZHMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90c19jbGljbGllbnQvY29tbWFuZHMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxjQUFjLENBQUM7QUFleEMsTUFBTSxDQUFDLE1BQU0sUUFBUSxHQUFHO0lBQ3RCLFFBQVE7SUFDUixhQUFhO0lBQ2IsNEJBQTRCO0lBQzVCLDBFQUEwRTtJQUMxRSwyRkFBMkY7SUFDM0YsNkdBQTZHO0lBQzdHLG9HQUFvRztDQUNyRyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQztBQUViOzs7R0FHRztBQUNILE1BQU0sT0FBTyxhQUFjLFNBQVEsS0FBSztJQUN0QztRQUNFLEtBQUssQ0FBQyxRQUFRLENBQUMsQ0FBQztRQUNoQixJQUFJLENBQUMsSUFBSSxHQUFHLGVBQWUsQ0FBQztJQUM5QixDQUFDO0NBQ0Y7QUFFRCxTQUFTLFlBQVksQ0FBQyxZQUFxQjtJQUN6QyxJQUFJLENBQUMsWUFBWTtRQUFFLE1BQU0sSUFBSSxhQUFhLEVBQUUsQ0FBQztBQUMvQyxDQUFDO0FBRUQsbUZBQW1GO0FBQ25GLE1BQU0sWUFBWSxHQUFHLENBQ25CLE9BQTBCLEVBQzFCLFFBQTBCLEVBQ0gsRUFBRTtJQUN6QixNQUFNLE9BQU8sR0FBRyxJQUFJLEdBQUcsRUFBa0IsQ0FBQztJQUMxQyxZQUFZLENBQUMsT0FBTyxDQUFDLE1BQU0sS0FBSyxRQUFRLENBQUMsTUFBTSxHQUFHLENBQUMsQ0FBQyxDQUFDO0lBQ3JELEtBQUssSUFBSSxLQUFLLEdBQUcsQ0FBQyxFQUFFLEtBQUssR0FBRyxPQUFPLENBQUMsTUFBTSxFQUFFLEtBQUssSUFBSSxDQUFDLEVBQUUsQ0FBQztRQUN2RCxNQUFNLElBQUksR0FBRyxPQUFPLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDNUIsTUFBTSxLQUFLLEdBQUcsT0FBTyxDQUFDLEtBQUssR0FBRyxDQUFDLENBQUMsQ0FBQztRQUNqQyxZ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
81
+ /**
82
+ * `corestore set-control-token --stdin --expect absent|<revision>` and
83
+ * `corestore control-token-state`.
84
+ *
85
+ * `--stdin` names where the token comes from, so no invocation reads it from anywhere else, and
86
+ * `--expect` is the state `control-token-state` answered: `absent`, or the stored revision.
87
+ */
88
+ const parseCorestoreCommand = (actionArg, restArg) => {
89
+ if (actionArg === 'control-token-state') {
90
+ requireUsage(restArg.length === 0);
91
+ return { kind: 'corestore-control-token-state' };
92
+ }
93
+ requireUsage(actionArg === 'set-control-token');
94
+ // `--stdin` is a flag in an option's place, either before or after `--expect <value>`.
95
+ requireUsage(restArg.length === 3);
96
+ const stdinIndex = restArg[0] === '--stdin' ? 0 : 2;
97
+ requireUsage(restArg[stdinIndex] === '--stdin');
98
+ const options = parseOptions(restArg.filter((_argArg, indexArg) => indexArg !== stdinIndex), ['--expect']);
99
+ const expected = options['--expect'];
100
+ return {
101
+ kind: 'corestore-set-control-token',
102
+ expected: expected === 'absent'
103
+ ? { state: 'absent' }
104
+ : { state: 'present', revision: parseCounter(expected) },
105
+ };
106
+ };
107
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29tbWFuZHMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90c19jbGljbGllbnQvY29tbWFuZHMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxjQUFjLENBQUM7QUFxQnhDLE1BQU0sQ0FBQyxNQUFNLFFBQVEsR0FBRztJQUN0QixRQUFRO0lBQ1IsYUFBYTtJQUNiLDRCQUE0QjtJQUM1QiwwRUFBMEU7SUFDMUUsMkZBQTJGO0lBQzNGLDZHQUE2RztJQUM3RyxvR0FBb0c7SUFDcEcsNEVBQTRFO0lBQzVFLDhGQUE4RjtJQUM5RiwyQ0FBMkM7Q0FDNUMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7QUFFYjs7O0dBR0c7QUFDSCxNQUFNLE9BQU8sYUFBYyxTQUFRLEtBQUs7SUFDdEM7UUFDRSxLQUFLLENBQUMsUUFBUSxDQUFDLENBQUM7UUFDaEIsSUFBSSxDQUFDLElBQUksR0FBRyxlQUFlLENBQUM7SUFDOUIsQ0FBQztDQUNGO0FBRUQsU0FBUyxZQUFZLENBQUMsWUFBcUI7SUFDekMsSUFBSSxDQUFDLFlBQVk7UUFBRSxNQUFNLElBQUksYUFBYSxFQUFFLENBQUM7QUFDL0MsQ0FBQztBQUVELG1GQUFtRjtBQUNuRixNQUFNLFlBQVksR0FBRyxDQUNuQixPQUEwQixFQUMxQixRQUEwQixFQUNILEVBQUU7SUFDekIsTUFBTSxPQUFPLEdBQUcsSUFBSSxHQUFHLEVBQWtCLENBQUM7SUFDMUMsWUFBWSxDQUFDLE9BQU8sQ0FBQyxNQUFNLEtBQUssUUFBUSxDQUFDLE1BQU0sR0FBRyxDQUFDLENBQUMsQ0FBQztJQUNyRCxLQUFLLElBQUksS0FBSyxHQUFHLENBQUMsRUFBRSxLQUFLLEdBQUcsT0FBTyxDQUFDLE1BQU0sRUFBRSxLQUFLLElBQUksQ0FBQyxFQUFFLENBQUM7UUFDdkQsTUFBTSxJQUFJLEdBQUcsT0FBTyxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQzVCLE1BQU0sS0FBSyxHQUFHLE9B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
@@ -0,0 +1,33 @@
1
+ import * as plugins from './plugins.js';
2
+ type TCorestoreControlTokenSetRefusal = plugins.servezoneInterfaces.data.TCorestoreControlTokenSetRefusal;
3
+ /** Cloudly did not store the token, for one of the contract's refusals, which it names. */
4
+ export declare class CorestoreControlTokenSetRefusedError extends Error {
5
+ readonly refusal: TCorestoreControlTokenSetRefusal;
6
+ constructor(refusal: TCorestoreControlTokenSetRefusal);
7
+ }
8
+ /**
9
+ * The contract refusal an error is, or `undefined` for every other error.
10
+ *
11
+ * Cloudly answers a refusal by its name and nothing else, so the name is the whole of the error's
12
+ * message; any other message, Cloudly's one denial text included, is not a refusal.
13
+ */
14
+ export declare const nameCorestoreControlTokenSetRefusal: (errorArg: unknown) => TCorestoreControlTokenSetRefusal | undefined;
15
+ /** What the CLI prints about a stored token: the state it produced and its receipt, never the token. */
16
+ export interface ICorestoreControlTokenSetOutput extends plugins.servezoneInterfaces.data.ICorestoreControlTokenSetResult {
17
+ ok: true;
18
+ command: 'corestore set-control-token';
19
+ }
20
+ /** What the CLI prints about the token's state: the state and its value-free receipts. */
21
+ export interface ICorestoreControlTokenStateOutput extends plugins.servezoneInterfaces.data.ICorestoreControlTokenStateRead {
22
+ ok: true;
23
+ command: 'corestore control-token-state';
24
+ }
25
+ /**
26
+ * Stores the token through the API client while it is still exactly at `expected`, answering only
27
+ * the state and the receipt. The token bytes are the caller's to wipe; the text the request needs
28
+ * is decoded from them here and never printed, logged or put into an error.
29
+ */
30
+ export declare const setCorestoreControlToken: (apiClientArg: plugins.servezoneApi.CloudlyApiClient, expectedArg: plugins.servezoneInterfaces.data.TCorestoreControlTokenState, tokenArg: Uint8Array) => Promise<ICorestoreControlTokenSetOutput>;
31
+ /** Whether Cloudly holds the token, at which revision, and its receipts, oldest first. */
32
+ export declare const getCorestoreControlTokenState: (apiClientArg: plugins.servezoneApi.CloudlyApiClient) => Promise<ICorestoreControlTokenStateOutput>;
33
+ export {};
@@ -0,0 +1,48 @@
1
+ import * as plugins from './plugins.js';
2
+ import { decodeCorestoreControlToken } from './tokeninput.js';
3
+ /** Cloudly did not store the token, for one of the contract's refusals, which it names. */
4
+ export class CorestoreControlTokenSetRefusedError extends Error {
5
+ constructor(refusal) {
6
+ super(`The Corestore control token was not stored: ${refusal}.`);
7
+ this.refusal = refusal;
8
+ this.name = 'CorestoreControlTokenSetRefusedError';
9
+ }
10
+ }
11
+ /**
12
+ * The contract refusal an error is, or `undefined` for every other error.
13
+ *
14
+ * Cloudly answers a refusal by its name and nothing else, so the name is the whole of the error's
15
+ * message; any other message, Cloudly's one denial text included, is not a refusal.
16
+ */
17
+ export const nameCorestoreControlTokenSetRefusal = (errorArg) => {
18
+ if (!(errorArg instanceof Error))
19
+ return undefined;
20
+ return plugins.servezoneInterfaces.data.corestoreControlTokenSetRefusals
21
+ .find((refusalArg) => refusalArg === errorArg.message);
22
+ };
23
+ /**
24
+ * Stores the token through the API client while it is still exactly at `expected`, answering only
25
+ * the state and the receipt. The token bytes are the caller's to wipe; the text the request needs
26
+ * is decoded from them here and never printed, logged or put into an error.
27
+ */
28
+ export const setCorestoreControlToken = async (apiClientArg, expectedArg, tokenArg) => {
29
+ try {
30
+ const { state, receipt } = await apiClientArg.platform.setCorestoreControlToken({
31
+ token: decodeCorestoreControlToken(tokenArg),
32
+ expected: expectedArg,
33
+ });
34
+ return { ok: true, command: 'corestore set-control-token', state, receipt };
35
+ }
36
+ catch (error) {
37
+ const refusal = nameCorestoreControlTokenSetRefusal(error);
38
+ if (refusal !== undefined)
39
+ throw new CorestoreControlTokenSetRefusedError(refusal);
40
+ throw error;
41
+ }
42
+ };
43
+ /** Whether Cloudly holds the token, at which revision, and its receipts, oldest first. */
44
+ export const getCorestoreControlTokenState = async (apiClientArg) => {
45
+ const { state, receipts } = await apiClientArg.platform.getCorestoreControlTokenState();
46
+ return { ok: true, command: 'corestore control-token-state', state, receipts };
47
+ };
48
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29yZXN0b3JlY29udHJvbHRva2VuLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHNfY2xpY2xpZW50L2NvcmVzdG9yZWNvbnRyb2x0b2tlbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGNBQWMsQ0FBQztBQUN4QyxPQUFPLEVBQUUsMkJBQTJCLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUk5RCwyRkFBMkY7QUFDM0YsTUFBTSxPQUFPLG9DQUFxQyxTQUFRLEtBQUs7SUFDN0QsWUFBNEIsT0FBeUM7UUFDbkUsS0FBSyxDQUFDLCtDQUErQyxPQUFPLEdBQUcsQ0FBQyxDQUFDO1FBRHZDLFlBQU8sR0FBUCxPQUFPLENBQWtDO1FBRW5FLElBQUksQ0FBQyxJQUFJLEdBQUcsc0NBQXNDLENBQUM7SUFDckQsQ0FBQztDQUNGO0FBRUQ7Ozs7O0dBS0c7QUFDSCxNQUFNLENBQUMsTUFBTSxtQ0FBbUMsR0FBRyxDQUNqRCxRQUFpQixFQUM2QixFQUFFO0lBQ2hELElBQUksQ0FBQyxDQUFDLFFBQVEsWUFBWSxLQUFLLENBQUM7UUFBRSxPQUFPLFNBQVMsQ0FBQztJQUNuRCxPQUFPLE9BQU8sQ0FBQyxtQkFBbUIsQ0FBQyxJQUFJLENBQUMsZ0NBQWdDO1NBQ3JFLElBQUksQ0FBQyxDQUFDLFVBQVUsRUFBRSxFQUFFLENBQUMsVUFBVSxLQUFLLFFBQVEsQ0FBQyxPQUFPLENBQUMsQ0FBQztBQUMzRCxDQUFDLENBQUM7QUFnQkY7Ozs7R0FJRztBQUNILE1BQU0sQ0FBQyxNQUFNLHdCQUF3QixHQUFHLEtBQUssRUFDM0MsWUFBbUQsRUFDbkQsV0FBeUUsRUFDekUsUUFBb0IsRUFDc0IsRUFBRTtJQUM1QyxJQUFJLENBQUM7UUFDSCxNQUFNLEVBQUUsS0FBSyxFQUFFLE9BQU8sRUFBRSxHQUFHLE1BQU0sWUFBWSxDQUFDLFFBQVEsQ0FBQyx3QkFBd0IsQ0FBQztZQUM5RSxLQUFLLEVBQUUsMkJBQTJCLENBQUMsUUFBUSxDQUFDO1lBQzVDLFFBQVEsRUFBRSxXQUFXO1NBQ3RCLENBQUMsQ0FBQztRQUNILE9BQU8sRUFBRSxFQUFFLEVBQUUsSUFBSSxFQUFFLE9BQU8sRUFBRSw2QkFBNkIsRUFBRSxLQUFLLEVBQUUsT0FBTyxFQUFFLENBQUM7SUFDOUUsQ0FBQztJQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7UUFDZixNQUFNLE9BQU8sR0FBRyxtQ0FBbUMsQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUMzRCxJQUFJLE9BQU8sS0FBSyxTQUFTO1lBQUUsTUFBTSxJQUFJLG9DQUFvQyxDQUFDLE9BQU8sQ0FBQyxDQUFDO1FBQ25GLE1BQU0sS0FBSyxDQUFDO0lBQ2QsQ0FBQztBQUNILENBQUMsQ0FBQztBQUVGLDBGQUEwRjtBQUMxRixNQUFNLENBQUMsTUFBTSw2QkFBNkIsR0FBRyxLQUFLLEVBQ2hELFlBQW1ELEVBQ1AsRUFBRTtJQUM5QyxNQUFNLEVBQUUsS0FBSyxFQUFFLFFBQVEsRUFBRSxHQUFHLE1BQU0sWUFBWSxDQUFDLFFBQVEsQ0FBQyw2QkFBNkIsRUFBRSxDQUFDO0lBQ3hGLE9BQU8sRUFBRSxFQUFFLEVBQUUsSUFBSSxFQUFFLE9BQU8sRUFBRSwrQkFBK0IsRUFBRSxLQUFLLEVBQUUsUUFBUSxFQUFFLENBQUM7QUFDakYsQ0FBQyxDQUFDIn0=
@@ -1,4 +1,5 @@
1
1
  export { CliUsageError, parseCliCommand, type TCliCommand } from './commands.js';
2
2
  export { RelayAuthorizationOutputError, exportRelayAuthorizationToFile, requirePrivateOutputDirectory, writeRelayAuthorization, type IRelayAuthorizationOutput, type TRelayAuthorizationOutputRefusal, } from './relayauthorization.js';
3
- export { BootstrapTokenInputError, readBootstrapTokenInput, type TBootstrapTokenInputRefusal, } from './tokeninput.js';
3
+ export { BootstrapTokenInputError, CorestoreControlTokenInputError, TokenInputError, readBootstrapTokenInput, readCorestoreControlTokenInput, type TBootstrapTokenInputRefusal, type TTokenInputRefusal, } from './tokeninput.js';
4
+ export { CorestoreControlTokenSetRefusedError, getCorestoreControlTokenState, nameCorestoreControlTokenSetRefusal, setCorestoreControlToken, type ICorestoreControlTokenSetOutput, type ICorestoreControlTokenStateOutput, } from './corestorecontroltoken.js';
4
5
  export declare const runCli: () => Promise<void>;
@@ -1,20 +1,28 @@
1
1
  import * as plugins from './plugins.js';
2
2
  import { CliClient } from './classes.cliclient.js';
3
3
  import { parseCliCommand } from './commands.js';
4
- import { readBootstrapTokenInput } from './tokeninput.js';
4
+ import { readBootstrapTokenInput, readCorestoreControlTokenInput } from './tokeninput.js';
5
5
  import { requirePrivateOutputDirectory } from './relayauthorization.js';
6
6
  export { CliUsageError, parseCliCommand } from './commands.js';
7
7
  export { RelayAuthorizationOutputError, exportRelayAuthorizationToFile, requirePrivateOutputDirectory, writeRelayAuthorization, } from './relayauthorization.js';
8
- export { BootstrapTokenInputError, readBootstrapTokenInput, } from './tokeninput.js';
8
+ export { BootstrapTokenInputError, CorestoreControlTokenInputError, TokenInputError, readBootstrapTokenInput, readCorestoreControlTokenInput, } from './tokeninput.js';
9
+ export { CorestoreControlTokenSetRefusedError, getCorestoreControlTokenState, nameCorestoreControlTokenSetRefusal, setCorestoreControlToken, } from './corestorecontroltoken.js';
10
+ /** Reads the token the command needs from stdin, before anything connects. */
11
+ const readInvocation = async (commandArg) => {
12
+ if (commandArg.kind === 'gateway-bootstrap') {
13
+ return { ...commandArg, bootstrapToken: await readBootstrapTokenInput(process.stdin) };
14
+ }
15
+ if (commandArg.kind === 'corestore-set-control-token') {
16
+ return { ...commandArg, controlToken: await readCorestoreControlTokenInput(process.stdin) };
17
+ }
18
+ return commandArg;
19
+ };
9
20
  export const runCli = async () => {
10
21
  // The command and the token are settled before anything connects, so a wrong invocation or a
11
22
  // token that could never be one fails without reaching Cloudly.
12
23
  let command;
13
24
  try {
14
- const parsed = parseCliCommand(process.argv.slice(2));
15
- command = parsed.kind === 'gateway-bootstrap'
16
- ? { ...parsed, bootstrapToken: await readBootstrapTokenInput(process.stdin) }
17
- : parsed;
25
+ command = await readInvocation(parseCliCommand(process.argv.slice(2)));
18
26
  // An output the bearer could not be written to fetches no bearer and records no export.
19
27
  if (command.kind === 'relay-export-authorization') {
20
28
  await requirePrivateOutputDirectory(command.outputPath);
@@ -40,6 +48,12 @@ export const runCli = async () => {
40
48
  else if (command.kind === 'relay-export-authorization') {
41
49
  await cliClient.exportRelayAuthorization(command.request, command.outputPath);
42
50
  }
51
+ else if (command.kind === 'corestore-set-control-token') {
52
+ await cliClient.setCorestoreControlToken(command.expected, command.controlToken);
53
+ }
54
+ else if (command.kind === 'corestore-control-token-state') {
55
+ await cliClient.getCorestoreControlTokenState();
56
+ }
43
57
  else {
44
58
  await cliClient.getGatewayStatus();
45
59
  }
@@ -56,6 +70,8 @@ export const runCli = async () => {
56
70
  finally {
57
71
  if (command.kind === 'gateway-bootstrap')
58
72
  command.bootstrapToken.fill(0);
73
+ if (command.kind === 'corestore-set-control-token')
74
+ command.controlToken.fill(0);
59
75
  }
60
76
  };
61
77
  /** The API client, started and signed in the way every command of this CLI signs in. */
@@ -91,4 +107,4 @@ const connect = async () => {
91
107
  }
92
108
  return apiClient;
93
109
  };
94
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90c19jbGljbGllbnQvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxjQUFjLENBQUM7QUFDeEMsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBQ25ELE9BQU8sRUFBRSxlQUFlLEVBQW9CLE1BQU0sZUFBZSxDQUFDO0FBQ2xFLE9BQU8sRUFBRSx1QkFBdUIsRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBQzFELE9BQU8sRUFBRSw2QkFBNkIsRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBRXhFLE9BQU8sRUFBRSxhQUFhLEVBQUUsZUFBZSxFQUFvQixNQUFNLGVBQWUsQ0FBQztBQUNqRixPQUFPLEVBQ0wsNkJBQTZCLEVBQzdCLDhCQUE4QixFQUM5Qiw2QkFBNkIsRUFDN0IsdUJBQXVCLEdBR3hCLE1BQU0seUJBQXlCLENBQUM7QUFDakMsT0FBTyxFQUNMLHdCQUF3QixFQUN4Qix1QkFBdUIsR0FFeEIsTUFBTSxpQkFBaUIsQ0FBQztBQU96QixNQUFNLENBQUMsTUFBTSxNQUFNLEdBQUcsS0FBSyxJQUFJLEVBQUU7SUFDL0IsNkZBQTZGO0lBQzdGLGdFQUFnRTtJQUNoRSxJQUFJLE9BQXVCLENBQUM7SUFDNUIsSUFBSSxDQUFDO1FBQ0gsTUFBTSxNQUFNLEdBQUcsZUFBZSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFDdEQsT0FBTyxHQUFHLE1BQU0sQ0FBQyxJQUFJLEtBQUssbUJBQW1CO1lBQzNDLENBQUMsQ0FBQyxFQUFFLEdBQUcsTUFBTSxFQUFFLGNBQWMsRUFBRSxNQUFNLHVCQUF1QixDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsRUFBRTtZQUM3RSxDQUFDLENBQUMsTUFBTSxDQUFDO1FBQ1gsd0ZBQXdGO1FBQ3hGLElBQUksT0FBTyxDQUFDLElBQUks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
110
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90c19jbGljbGllbnQvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxjQUFjLENBQUM7QUFDeEMsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBQ25ELE9BQU8sRUFBRSxlQUFlLEVBQW9CLE1BQU0sZUFBZSxDQUFDO0FBQ2xFLE9BQU8sRUFBRSx1QkFBdUIsRUFBRSw4QkFBOEIsRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBQzFGLE9BQU8sRUFBRSw2QkFBNkIsRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBRXhFLE9BQU8sRUFBRSxhQUFhLEVBQUUsZUFBZSxFQUFvQixNQUFNLGVBQWUsQ0FBQztBQUNqRixPQUFPLEVBQ0wsNkJBQTZCLEVBQzdCLDhCQUE4QixFQUM5Qiw2QkFBNkIsRUFDN0IsdUJBQXVCLEdBR3hCLE1BQU0seUJBQXlCLENBQUM7QUFDakMsT0FBTyxFQUNMLHdCQUF3QixFQUN4QiwrQkFBK0IsRUFDL0IsZUFBZSxFQUNmLHVCQUF1QixFQUN2Qiw4QkFBOEIsR0FHL0IsTUFBTSxpQkFBaUIsQ0FBQztBQUN6QixPQUFPLEVBQ0wsb0NBQW9DLEVBQ3BDLDZCQUE2QixFQUM3QixtQ0FBbUMsRUFDbkMsd0JBQXdCLEdBR3pCLE1BQU0sNEJBQTRCLENBQUM7QUFXcEMsOEVBQThFO0FBQzlFLE1BQU0sY0FBYyxHQUFHLEtBQUssRUFBRSxVQUF1QixFQUEyQixFQUFFO0lBQ2hGLElBQUksVUFBVSxDQUFDLElBQUksS0FBSyxtQkFBbUIsRUFBRSxDQUFDO1FBQzVDLE9BQU8sRUFBRSxHQUFHLFVBQVUsRUFBRSxjQUFjLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQztJQUN6RixDQUFDO0lBQ0QsSUFBSSxVQUFVLENBQUMsSUFBSSxLQUFLLDZCQUE2QixFQUFFLENB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
@@ -4,7 +4,8 @@ import * as path from 'node:path';
4
4
  export { crypto, fs, path, };
5
5
  import * as servezoneApi from '@serve.zone/api';
6
6
  import * as servezoneInterfaces from '@serve.zone/interfaces';
7
- export { servezoneApi, servezoneInterfaces };
7
+ import * as servezoneInterfacesRuntime from '@serve.zone/interfaces/runtime';
8
+ export { servezoneApi, servezoneInterfaces, servezoneInterfacesRuntime, };
8
9
  import * as projectinfo from '@push.rocks/projectinfo';
9
10
  import * as qenv from '@push.rocks/qenv';
10
11
  import * as smartfs from '@push.rocks/smartfs';
@@ -6,10 +6,11 @@ export { crypto, fs, path, };
6
6
  // @serve.zone scope
7
7
  import * as servezoneApi from '@serve.zone/api';
8
8
  import * as servezoneInterfaces from '@serve.zone/interfaces';
9
- export { servezoneApi, servezoneInterfaces };
9
+ import * as servezoneInterfacesRuntime from '@serve.zone/interfaces/runtime';
10
+ export { servezoneApi, servezoneInterfaces, servezoneInterfacesRuntime, };
10
11
  // @push.rocks scope
11
12
  import * as projectinfo from '@push.rocks/projectinfo';
12
13
  import * as qenv from '@push.rocks/qenv';
13
14
  import * as smartfs from '@push.rocks/smartfs';
14
15
  export { projectinfo, qenv, smartfs, };
15
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGx1Z2lucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzX2NsaWNsaWVudC9wbHVnaW5zLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLG9CQUFvQjtBQUNwQixPQUFPLEtBQUssTUFBTSxNQUFNLGFBQWEsQ0FBQztBQUN0QyxPQUFPLEtBQUssRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBQ3ZDLE9BQU8sS0FBSyxJQUFJLE1BQU0sV0FBVyxDQUFDO0FBRWxDLE9BQU8sRUFDTCxNQUFNLEVBQ04sRUFBRSxFQUNGLElBQUksR0FDTCxDQUFBO0FBRUQsb0JBQW9CO0FBQ3BCLE9BQU8sS0FBSyxZQUFZLE1BQU0saUJBQWlCLENBQUM7QUFDaEQsT0FBTyxLQUFLLG1CQUFtQixNQUFNLHdCQUF3QixDQUFDO0FBRTlELE9BQU8sRUFDTCxZQUFZLEVBQ1osbUJBQW1CLEVBQ3BCLENBQUE7QUFFRCxvQkFBb0I7QUFDcEIsT0FBTyxLQUFLLFdBQVcsTUFBTSx5QkFBeUIsQ0FBQztBQUN2RCxPQUFPLEtBQUssSUFBSSxNQUFNLGtCQUFrQixDQUFDO0FBQ3pDLE9BQU8sS0FBSyxPQUFPLE1BQU0scUJBQXFCLENBQUM7QUFFL0MsT0FBTyxFQUNMLFdBQVcsRUFDWCxJQUFJLEVBQ0osT0FBTyxHQUNSLENBQUEifQ==
16
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGx1Z2lucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzX2NsaWNsaWVudC9wbHVnaW5zLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLG9CQUFvQjtBQUNwQixPQUFPLEtBQUssTUFBTSxNQUFNLGFBQWEsQ0FBQztBQUN0QyxPQUFPLEtBQUssRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBQ3ZDLE9BQU8sS0FBSyxJQUFJLE1BQU0sV0FBVyxDQUFDO0FBRWxDLE9BQU8sRUFDTCxNQUFNLEVBQ04sRUFBRSxFQUNGLElBQUksR0FDTCxDQUFBO0FBRUQsb0JBQW9CO0FBQ3BCLE9BQU8sS0FBSyxZQUFZLE1BQU0saUJBQWlCLENBQUM7QUFDaEQsT0FBTyxLQUFLLG1CQUFtQixNQUFNLHdCQUF3QixDQUFDO0FBQzlELE9BQU8sS0FBSywwQkFBMEIsTUFBTSxnQ0FBZ0MsQ0FBQztBQUU3RSxPQUFPLEVBQ0wsWUFBWSxFQUNaLG1CQUFtQixFQUNuQiwwQkFBMEIsR0FDM0IsQ0FBQTtBQUVELG9CQUFvQjtBQUNwQixPQUFPLEtBQUssV0FBVyxNQUFNLHlCQUF5QixDQUFDO0FBQ3ZELE9BQU8sS0FBSyxJQUFJLE1BQU0sa0JBQWtCLENBQUM7QUFDekMsT0FBTyxLQUFLLE9BQU8sTUFBTSxxQkFBcUIsQ0FBQztBQUUvQyxPQUFPLEVBQ0wsV0FBVyxFQUNYLElBQUksRUFDSixPQUFPLEdBQ1IsQ0FBQSJ9
@@ -1,25 +1,42 @@
1
- /** Why the admin token piped in was not taken, named without quoting any of it. */
2
- export type TBootstrapTokenInputRefusal =
1
+ /** Why a token piped in was not taken, named without quoting any of it. */
2
+ export type TTokenInputRefusal =
3
3
  /** stdin is a terminal, which would echo a token typed into it; pipe the token in instead. */
4
4
  'token-input-terminal'
5
5
  /** stdin carried more bytes than a token and one line break. */
6
6
  | 'token-input-oversized'
7
- /** What stdin carried is not a token: empty, or a byte outside visible ASCII, a space included. */
7
+ /** What stdin carried is not a token by the token's own rule: empty, or a byte the rule refuses. */
8
8
  | 'token-input-invalid';
9
- export declare class BootstrapTokenInputError extends Error {
10
- readonly refusal: TBootstrapTokenInputRefusal;
11
- constructor(refusal: TBootstrapTokenInputRefusal);
9
+ /** Why the dcrouter admin token piped into `gateway bootstrap` was not taken. */
10
+ export type TBootstrapTokenInputRefusal = TTokenInputRefusal;
11
+ /** A refused token input: which token, and why, never a byte of what was read. */
12
+ export declare class TokenInputError extends Error {
13
+ readonly refusal: TTokenInputRefusal;
14
+ constructor(refusal: TTokenInputRefusal, subjectArg: string);
12
15
  }
13
- /** What the token is read from: the command's stdin, or a stream a test hands in. */
14
- export type TBootstrapTokenInput = AsyncIterable<Uint8Array> & {
16
+ export declare class BootstrapTokenInputError extends TokenInputError {
17
+ constructor(refusalArg: TTokenInputRefusal);
18
+ }
19
+ export declare class CorestoreControlTokenInputError extends TokenInputError {
20
+ constructor(refusalArg: TTokenInputRefusal);
21
+ }
22
+ /** What a token is read from: the command's stdin, or a stream a test hands in. */
23
+ export type TTokenInput = AsyncIterable<Uint8Array> & {
15
24
  isTTY?: boolean;
16
25
  };
26
+ export type TBootstrapTokenInput = TTokenInput;
27
+ /**
28
+ * The dcrouter admin token piped into `gateway bootstrap`: at most the bootstrap contract's
29
+ * `maximumTokenBytes` of visible ASCII.
30
+ */
31
+ export declare const readBootstrapTokenInput: (inputArg: TTokenInput) => Promise<Uint8Array>;
32
+ /**
33
+ * The token as text, exactly as its bytes say: invalid UTF-8 throws, and a leading byte order mark
34
+ * is kept, so the contract judges and Cloudly receives every character that was piped in.
35
+ */
36
+ export declare const decodeCorestoreControlToken: (tokenArg: Uint8Array) => string;
17
37
  /**
18
- * The dcrouter admin token piped into `gateway bootstrap`, as bytes the caller wipes.
19
- *
20
- * stdin is the only source: an argument would sit in the process list and the shell history, and
21
- * the environment leaks into child processes. One trailing line break, `\n` or `\r\n`, is what
22
- * `echo` or a file adds and is not part of the token; everything else must pass the contract's
23
- * token rule. Every byte read is wiped before this answers, whatever it answers.
38
+ * The Corestore control token piped into `corestore set-control-token`: what the contract's
39
+ * `isCorestoreControlToken` accepts, so at most `corestoreCredentialRuntimeLimits
40
+ * .maximumControlTokenBytes` of UTF-8 without whitespace.
24
41
  */
25
- export declare const readBootstrapTokenInput: (inputArg: TBootstrapTokenInput) => Promise<Uint8Array>;
42
+ export declare const readCorestoreControlTokenInput: (inputArg: TTokenInput) => Promise<Uint8Array>;
@@ -1,26 +1,37 @@
1
1
  import * as plugins from './plugins.js';
2
- export class BootstrapTokenInputError extends Error {
3
- constructor(refusal) {
4
- super(`The dcrouter admin token on stdin was refused: ${refusal}.`);
2
+ /** A refused token input: which token, and why, never a byte of what was read. */
3
+ export class TokenInputError extends Error {
4
+ constructor(refusal, subjectArg) {
5
+ super(`${subjectArg} on stdin was refused: ${refusal}.`);
5
6
  this.refusal = refusal;
7
+ this.name = 'TokenInputError';
8
+ }
9
+ }
10
+ export class BootstrapTokenInputError extends TokenInputError {
11
+ constructor(refusalArg) {
12
+ super(refusalArg, 'The dcrouter admin token');
6
13
  this.name = 'BootstrapTokenInputError';
7
14
  }
8
15
  }
9
- function refuseUnless(conditionArg, refusalArg) {
10
- if (!conditionArg)
11
- throw new BootstrapTokenInputError(refusalArg);
16
+ export class CorestoreControlTokenInputError extends TokenInputError {
17
+ constructor(refusalArg) {
18
+ super(refusalArg, 'The Corestore control token');
19
+ this.name = 'CorestoreControlTokenInputError';
20
+ }
12
21
  }
13
22
  /**
14
- * The dcrouter admin token piped into `gateway bootstrap`, as bytes the caller wipes.
23
+ * A token piped into a command, as bytes the caller wipes.
15
24
  *
16
25
  * stdin is the only source: an argument would sit in the process list and the shell history, and
17
26
  * the environment leaks into child processes. One trailing line break, `\n` or `\r\n`, is what
18
- * `echo` or a file adds and is not part of the token; everything else must pass the contract's
19
- * token rule. Every byte read is wiped before this answers, whatever it answers.
27
+ * `echo` or a file adds and is not part of the token; everything else must pass the token's rule.
28
+ * Reading stops as soon as stdin carries more than the rule's bound and one line break. Every byte
29
+ * read is wiped before this answers, whatever it answers.
20
30
  */
21
- export const readBootstrapTokenInput = async (inputArg) => {
22
- refuseUnless(inputArg.isTTY !== true, 'token-input-terminal');
23
- const maximumBytes = plugins.servezoneInterfaces.data.externalGatewayBootstrapContract.maximumTokenBytes + 2;
31
+ const readTokenInput = async (inputArg, ruleArg) => {
32
+ if (inputArg.isTTY === true)
33
+ throw ruleArg.refuse('token-input-terminal');
34
+ const maximumBytes = ruleArg.maximumTokenBytes + 2;
24
35
  const chunks = [];
25
36
  let input;
26
37
  try {
@@ -28,7 +39,8 @@ export const readBootstrapTokenInput = async (inputArg) => {
28
39
  for await (const chunk of inputArg) {
29
40
  chunks.push(chunk);
30
41
  byteLength += chunk.byteLength;
31
- refuseUnless(byteLength <= maximumBytes, 'token-input-oversized');
42
+ if (byteLength > maximumBytes)
43
+ throw ruleArg.refuse('token-input-oversized');
32
44
  }
33
45
  input = Buffer.concat(chunks, byteLength);
34
46
  let end = input.byteLength;
@@ -38,7 +50,8 @@ export const readBootstrapTokenInput = async (inputArg) => {
38
50
  end -= 1;
39
51
  }
40
52
  const token = input.subarray(0, end);
41
- refuseUnless(plugins.servezoneInterfaces.data.isExternalGatewayBootstrapToken(token), 'token-input-invalid');
53
+ if (!ruleArg.isToken(token))
54
+ throw ruleArg.refuse('token-input-invalid');
42
55
  // A copy, because the input and every view of it are wiped below.
43
56
  return new Uint8Array(token);
44
57
  }
@@ -48,4 +61,37 @@ export const readBootstrapTokenInput = async (inputArg) => {
48
61
  chunk.fill(0);
49
62
  }
50
63
  };
51
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidG9rZW5pbnB1dC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzX2NsaWNsaWVudC90b2tlbmlucHV0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBV3hDLE1BQU0sT0FBTyx3QkFBeUIsU0FBUSxLQUFLO0lBQ2pELFlBQTRCLE9BQW9DO1FBQzlELEtBQUssQ0FBQyxrREFBa0QsT0FBTyxHQUFHLENBQUMsQ0FBQztRQUQxQyxZQUFPLEdBQVAsT0FBTyxDQUE2QjtRQUU5RCxJQUFJLENBQUMsSUFBSSxHQUFHLDBCQUEwQixDQUFDO0lBQ3pDLENBQUM7Q0FDRjtBQUVELFNBQVMsWUFBWSxDQUFDLFlBQXFCLEVBQUUsVUFBdUM7SUFDbEYsSUFBSSxDQUFDLFlBQVk7UUFBRSxNQUFNLElBQUksd0JBQXdCLENBQUMsVUFBVSxDQUFDLENBQUM7QUFDcEUsQ0FBQztBQUtEOzs7Ozs7O0dBT0c7QUFDSCxNQUFNLENBQUMsTUFBTSx1QkFBdUIsR0FBRyxLQUFLLEVBQUUsUUFBOEIsRUFBdUIsRUFBRTtJQUNuRyxZQUFZLENBQUMsUUFBUSxDQUFDLEtBQUssS0FBSyxJQUFJLEVBQUUsc0JBQXNCLENBQUMsQ0FBQztJQUM5RCxNQUFNLFlBQVksR0FBRyxPQUFPLENBQUMsbUJBQW1CLENBQUMsSUFBSSxDQUFDLGdDQUFnQyxDQUFDLGlCQUFpQixHQUFHLENBQUMsQ0FBQztJQUM3RyxNQUFNLE1BQU0sR0FBaUIsRUFBRSxDQUFDO0lBQ2hDLElBQUksS0FBNkIsQ0FBQztJQUNsQyxJQUFJLENBQUM7UUFDSCxJQUFJLFVBQVUsR0FBRyxDQUFDLENBQUM7UUFDbkIsSUFBSSxLQUFLLEVBQUUsTUFBTSxLQUFLLElBQUksUUFBUSxFQUFFLENBQUM7WUFDbkMsTUFBTSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQztZQUNuQixVQUFVLElBQUksS0FBSyxDQUFDLFVBQVUsQ0FBQztZQUMvQixZQUFZLENBQUMsVUFBVSxJQUFJLFlBQVksRUFBRSx1QkFBdUIsQ0FBQyxDQUFDO1FBQ3BFLENBQUM7UUFDRCxLQUFLLEdBQUcsTUFBTSxDQUFDLE1BQU0sQ0FBQyxNQUFNLEVBQUUsVUFBVSxDQUFDLENBQUM7UUFDMUMsSUFBSSxHQUFHLEdBQUcsS0FBSyxDQUFDLFVBQVUsQ0FBQztRQUMzQixJQUFJLEdBQUcsR0FBRyxDQUFDLElBQUksS0FBSyxDQUFDLEdBQUcsR0FBRyxDQUFDLENBQUMsS0FBSyxJQUFJLEVBQUUsQ0FBQztZQUN2QyxHQUFHLElBQUksQ0FBQyxDQUFDO1lBQ1QsSUFBSSxHQUFHLEdBQUcsQ0FBQyxJQUFJLEtBQUssQ0FBQyxHQUFHLEdBQUcsQ0FBQyxDQUFDLEtBQUssSUFBSTtnQkFBRSxHQUFHLElBQUksQ0FBQyxDQUFDO1FBQ25ELENBQUM7UUFDRCxNQUFNLEtBQUssR0FBRyxLQUFLLENBQUMsUUFBUSxDQUFDLENBQUMsRUFBRSxHQUFHLENBQUMsQ0FBQztRQUNyQyxZQUFZLENBQUMsT0FBTyxDQUFDLG1CQUFtQixDQUFDLElBQUksQ0FBQywrQkFBK0IsQ0FBQyxLQUFLLENBQUMsRUFBRSxxQkFBcUIsQ0FBQyxDQUFDO1FBQzdHLGtFQUFrRTtRQUNsRSxPQUFPLElBQUksVUFBVSxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQy9CLENBQUM7WUFBUyxDQUFDO1FBQ1QsS0FBSyxFQUFFLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUNmLEtBQUssTUFBTSxLQUFLLElBQUksTUFBTTtZQUFFLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDNUMsQ0FBQztBQUNILENBQUMsQ0FBQyJ9
64
+ /**
65
+ * The dcrouter admin token piped into `gateway bootstrap`: at most the bootstrap contract's
66
+ * `maximumTokenBytes` of visible ASCII.
67
+ */
68
+ export const readBootstrapTokenInput = async (inputArg) => await readTokenInput(inputArg, {
69
+ maximumTokenBytes: plugins.servezoneInterfaces.data.externalGatewayBootstrapContract.maximumTokenBytes,
70
+ isToken: (tokenArg) => plugins.servezoneInterfaces.data.isExternalGatewayBootstrapToken(tokenArg),
71
+ refuse: (refusalArg) => new BootstrapTokenInputError(refusalArg),
72
+ });
73
+ /**
74
+ * The token as text, exactly as its bytes say: invalid UTF-8 throws, and a leading byte order mark
75
+ * is kept, so the contract judges and Cloudly receives every character that was piped in.
76
+ */
77
+ export const decodeCorestoreControlToken = (tokenArg) => new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(tokenArg);
78
+ /**
79
+ * The Corestore control token piped into `corestore set-control-token`: what the contract's
80
+ * `isCorestoreControlToken` accepts, so at most `corestoreCredentialRuntimeLimits
81
+ * .maximumControlTokenBytes` of UTF-8 without whitespace.
82
+ */
83
+ export const readCorestoreControlTokenInput = async (inputArg) => await readTokenInput(inputArg, {
84
+ maximumTokenBytes: plugins.servezoneInterfacesRuntime.corestoreCredentialRuntimeLimits.maximumControlTokenBytes,
85
+ isToken: (tokenArg) => {
86
+ let token;
87
+ try {
88
+ token = decodeCorestoreControlToken(tokenArg);
89
+ }
90
+ catch {
91
+ return false;
92
+ }
93
+ return plugins.servezoneInterfacesRuntime.isCorestoreControlToken(token);
94
+ },
95
+ refuse: (refusalArg) => new CorestoreControlTokenInputError(refusalArg),
96
+ });
97
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidG9rZW5pbnB1dC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzX2NsaWNsaWVudC90b2tlbmlucHV0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBY3hDLGtGQUFrRjtBQUNsRixNQUFNLE9BQU8sZUFBZ0IsU0FBUSxLQUFLO0lBQ3hDLFlBQTRCLE9BQTJCLEVBQUUsVUFBa0I7UUFDekUsS0FBSyxDQUFDLEdBQUcsVUFBVSwwQkFBMEIsT0FBTyxHQUFHLENBQUMsQ0FBQztRQUQvQixZQUFPLEdBQVAsT0FBTyxDQUFvQjtRQUVyRCxJQUFJLENBQUMsSUFBSSxHQUFHLGlCQUFpQixDQUFDO0lBQ2hDLENBQUM7Q0FDRjtBQUVELE1BQU0sT0FBTyx3QkFBeUIsU0FBUSxlQUFlO0lBQzNELFlBQVksVUFBOEI7UUFDeEMsS0FBSyxDQUFDLFVBQVUsRUFBRSwwQkFBMEIsQ0FBQyxDQUFDO1FBQzlDLElBQUksQ0FBQyxJQUFJLEdBQUcsMEJBQTBCLENBQUM7SUFDekMsQ0FBQztDQUNGO0FBRUQsTUFBTSxPQUFPLCtCQUFnQyxTQUFRLGVBQWU7SUFDbEUsWUFBWSxVQUE4QjtRQUN4QyxLQUFLLENBQUMsVUFBVSxFQUFFLDZCQUE2QixDQUFDLENBQUM7UUFDakQsSUFBSSxDQUFDLElBQUksR0FBRyxpQ0FBaUMsQ0FBQztJQUNoRCxDQUFDO0NBQ0Y7QUFlRDs7Ozs7Ozs7R0FRRztBQUNILE1BQU0sY0FBYyxHQUFHLEtBQUssRUFBRSxRQUFxQixFQUFFLE9BQXdCLEVBQXVCLEVBQUU7SUFDcEcsSUFBSSxRQUFRLENBQUMsS0FBSyxLQUFLLElBQUk7UUFBRSxNQUFNLE9BQU8sQ0FBQyxNQUFNLENBQUMsc0JBQXNCLENBQUMsQ0FBQztJQUMxRSxNQUFNLFlBQVksR0FBRyxPQUFPLENBQUMsaUJBQWlCLEdBQUcsQ0FBQyxDQUFDO0lBQ25ELE1BQU0sTUFBTSxHQUFpQixFQUFFLENBQUM7SUFD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
package/package.json CHANGED
@@ -38,7 +38,7 @@
38
38
  "security"
39
39
  ],
40
40
  "name": "@serve.zone/cli",
41
- "version": "33.10.0",
41
+ "version": "33.12.0",
42
42
  "type": "module",
43
43
  "description": "A comprehensive tool for managing containerized applications across multiple cloud providers using Docker Swarmkit, featuring web, CLI, and API interfaces.",
44
44
  "main": "./dist_ts_cliclient/index.js",
@@ -50,8 +50,8 @@
50
50
  }
51
51
  },
52
52
  "dependencies": {
53
- "@serve.zone/api": "32.4.0",
54
- "@serve.zone/interfaces": "32.41.0",
53
+ "@serve.zone/api": "32.5.0",
54
+ "@serve.zone/interfaces": "32.43.0",
55
55
  "@push.rocks/projectinfo": "^5.1.0",
56
56
  "@push.rocks/qenv": "^8.1.0",
57
57
  "@push.rocks/smartfs": "^1.8.0",
package/readme.md CHANGED
@@ -17,6 +17,7 @@ This submodule is intentionally small in the current codebase:
17
17
  - Without arguments, calls `CliClient.getClusters()` and prints the result.
18
18
  - Runs `servezone gateway bootstrap` and `servezone gateway status` for Cloudly's dcrouter gateway enrollment.
19
19
  - Runs `servezone relay export-authorization` to write a cluster relay's bearer to a private file.
20
+ - Runs `servezone corestore set-control-token` and `servezone corestore control-token-state` for the Corestore control token the cluster relays use.
20
21
 
21
22
  It is not currently a full command tree for services, secrets, deployments, logs, profiles, or shell completion. Those flows should be implemented against `@serve.zone/api` before documenting them as CLI commands.
22
23
 
@@ -91,6 +92,26 @@ servezone relay export-authorization --cluster-id <clusterId> --expected-generat
91
92
  - The bearer is fetched with `@serve.zone/api` (`cluster.exportClusterRelayAuthorization`, which checks the answer against the request) and written with `@push.rocks/smartfs` as a new file, mode `0600` whatever the umask, exclusive and atomic: the whole bearer or no file, never over an existing entry and never through a symbolic link at the path.
92
93
  - It is never written to stdout. stdout carries one JSON line of metadata: `clusterId`, `generation`, `outputPath`, `byteLength`, `sha256` (what `sha256sum` prints for the file) and `receiptId`, the audit receipt Cloudly recorded. A refusal is printed to stderr with exit code 1 and never carries the bearer.
93
94
 
95
+ ## Corestore Control Token
96
+
97
+ Every cluster relay calls its nodes' Corestore control API with one control token, which Cloudly stores for both Corestore provider configs. These commands set and read it over the API, without any access to Cloudly's process. Both sign in exactly as the default action does; the set needs an administrator who signed in within the last five minutes.
98
+
99
+ ```sh
100
+ CLOUDLY_URL=https://cloudly.example.com \
101
+ CLOUDLY_USERNAME=admin CLOUDLY_PASSWORD=change-me \
102
+ servezone corestore control-token-state
103
+
104
+ # Pipe the token in, naming the state the read answered:
105
+ servezone corestore set-control-token --stdin --expect absent < control-token.txt
106
+ servezone corestore set-control-token --stdin --expect 3 < control-token.txt
107
+ ```
108
+
109
+ - `control-token-state` prints one JSON line: `{"ok":true,"command":"corestore control-token-state","state":…,"receipts":[…]}`. `state` is `{"state":"absent"}` or `{"state":"present","revision":<n>}`; `receipts` are the value-free audit receipts (`id`, `revision`, `via`, `actorId`, `setAt`), oldest first.
110
+ - `set-control-token --stdin --expect absent|<revision>` stores the token only while it is still exactly at the state `--expect` names: `absent`, or the revision `control-token-state` answered (a positive integer without leading zeros). It is never a minimum, so a set that committed in between is refused rather than overwritten.
111
+ - The token is read from stdin and from nowhere else: `--stdin` names that, and an argument, `--token`, or any other extra argument is refused as usage without echoing it; the environment is never read for it. stdin must not be a terminal. One trailing line break (`\n` or `\r\n`) is stripped; what remains must pass the contract's `isCorestoreControlToken` (32 to 4096 bytes of UTF-8 without whitespace). A refused stdin is named (`token-input-terminal`, `token-input-oversized`, `token-input-invalid`) before anything connects, and every byte read is wiped.
112
+ - The token is sent once with `@serve.zone/api` (`platform.setCorestoreControlToken`, past every TypedRequest hook), and the answer is checked against the request. stdout carries one JSON line with the state the set produced and its receipt, never the token: `{"ok":true,"command":"corestore set-control-token","state":{"state":"present","revision":<n>},"receipt":{…}}`.
113
+ - A refusal is printed to stderr with exit code 1 as `The Corestore control token was not stored: <refusal>.`, naming one of the contract's `corestoreControlTokenSetRefusals`: `token-present` (`--expect absent`, and a token is stored), `token-absent` (a revision was expected, and none is stored), `token-revision-mismatch` (the stored token is at another revision), `token-invalid` or `provider-unavailable` (a Corestore provider config is missing or disabled). Every other failure, an unauthorized or stale administrator included, is Cloudly's one denial text, `Corestore control token request denied.`, with exit code 1.
114
+
94
115
  ## Programmatic Use
95
116
 
96
117
  The published submodule exports the `runCli()` entry point. For automation, most callers should use `@serve.zone/api` directly; the internal `CliClient` currently wraps `CloudlyApiClient` only to run the default cluster-list action:
@@ -116,9 +137,10 @@ await cli.getClusters();
116
137
  | Path | Purpose |
117
138
  | --- | --- |
118
139
  | `index.ts` | Runtime entry point for the published CLI. |
119
- | `classes.cliclient.ts` | Minimal client wrapper: `getClusters()`, `bootstrapGateway()`, `getGatewayStatus()` and `exportRelayAuthorization()`. |
140
+ | `classes.cliclient.ts` | Minimal client wrapper: `getClusters()`, `bootstrapGateway()`, `getGatewayStatus()`, `exportRelayAuthorization()`, `setCorestoreControlToken()` and `getCorestoreControlTokenState()`. |
120
141
  | `commands.ts` | Parses the argument vector into one command, refusing anything else as usage. |
121
- | `tokeninput.ts` | Reads the dcrouter admin token from stdin. |
142
+ | `tokeninput.ts` | Reads the dcrouter admin token and the Corestore control token from stdin, each bounded and checked by its contract. |
143
+ | `corestorecontroltoken.ts` | Sets and reads the Corestore control token through the API client and names the contract's refusals. |
122
144
  | `relayauthorization.ts` | Checks the private output directory and writes a cluster relay's bearer as a new 0600 file. |
123
145
  | `plugins.ts` | Centralized imports for the submodule. |
124
146
  | `tspublish.json` | Published package name, dependencies, `servezone` bin metadata, and `useBase`, which takes the registries from the release's npm target. |
@@ -1,6 +1,7 @@
1
1
  import * as plugins from './plugins.js';
2
2
  import { CloudlyApiClient } from '@serve.zone/api';
3
3
  import { exportRelayAuthorizationToFile } from './relayauthorization.js';
4
+ import { getCorestoreControlTokenState, setCorestoreControlToken } from './corestorecontroltoken.js';
4
5
 
5
6
  export class CliClient {
6
7
  public cloudlyApiClient: CloudlyApiClient;
@@ -42,6 +43,23 @@ export class CliClient {
42
43
  console.log(JSON.stringify({ ok: true, command: 'relay export-authorization', ...output }));
43
44
  }
44
45
 
46
+ /**
47
+ * Stores the Corestore control token while it is still exactly at `expectedArg` and prints the
48
+ * state and the receipt the set produced, never the token. The token is the caller's to wipe.
49
+ */
50
+ public async setCorestoreControlToken(
51
+ expectedArg: plugins.servezoneInterfaces.data.TCorestoreControlTokenState,
52
+ tokenArg: Uint8Array,
53
+ ) {
54
+ const output = await setCorestoreControlToken(this.cloudlyApiClient, expectedArg, tokenArg);
55
+ console.log(JSON.stringify(output));
56
+ }
57
+
58
+ /** Prints whether Cloudly holds the Corestore control token, its revision and its receipts. */
59
+ public async getCorestoreControlTokenState() {
60
+ console.log(JSON.stringify(await getCorestoreControlTokenState(this.cloudlyApiClient)));
61
+ }
62
+
45
63
  public async getGatewayStatus() {
46
64
  const { state } = await this.cloudlyApiClient.settings.getExternalGatewayEnrollmentState();
47
65
  console.log(JSON.stringify(state, null, 2));
@@ -11,7 +11,13 @@ export type TCliCommand =
11
11
  request: plugins.servezoneInterfaces.data.IClusterRelayAuthorizationExportRequest;
12
12
  /** A normalized absolute path in a private directory; the file must not exist yet. */
13
13
  outputPath: string;
14
- };
14
+ }
15
+ | {
16
+ kind: 'corestore-set-control-token';
17
+ /** Exactly the state the administrator last read; never a minimum. */
18
+ expected: plugins.servezoneInterfaces.data.TCorestoreControlTokenState;
19
+ }
20
+ | { kind: 'corestore-control-token-state' };
15
21
 
16
22
  export const cliUsage = [
17
23
  'Usage:',
@@ -21,6 +27,9 @@ export const cliUsage = [
21
27
  ' The dcrouter admin token is read from stdin, never from arguments or the environment.',
22
28
  ' servezone relay export-authorization --cluster-id <id> --expected-generation <n> --output <absolute path>',
23
29
  ' Writes the relay bearer to a new 0600 file in a directory only you can write; never to stdout.',
30
+ ' servezone corestore set-control-token --stdin --expect absent|<revision>',
31
+ ' The Corestore control token is read from stdin, never from arguments or the environment.',
32
+ ' servezone corestore control-token-state',
24
33
  ].join('\n');
25
34
 
26
35
  /**
@@ -60,6 +69,7 @@ export const parseCliCommand = (argsArg: readonly string[]): TCliCommand => {
60
69
  if (argsArg.length === 0) return { kind: 'clusters' };
61
70
  const [group, action, ...rest] = argsArg;
62
71
  if (group === 'relay') return parseRelayCommand(action, rest);
72
+ if (group === 'corestore') return parseCorestoreCommand(action, rest);
63
73
  requireUsage(group === 'gateway');
64
74
  if (action === 'status') {
65
75
  requireUsage(rest.length === 0);
@@ -73,18 +83,50 @@ export const parseCliCommand = (argsArg: readonly string[]): TCliCommand => {
73
83
  };
74
84
  };
75
85
 
86
+ /** A positive integer without leading zeros, as a generation or a revision is named. */
87
+ const parseCounter = (valueArg: string): number => {
88
+ requireUsage(/^[1-9][0-9]{0,15}$/.test(valueArg) && Number.isSafeInteger(Number(valueArg)));
89
+ return Number(valueArg);
90
+ };
91
+
76
92
  /** `relay export-authorization`: the cluster, the exact generation and a normalized absolute output. */
77
93
  const parseRelayCommand = (actionArg: string | undefined, restArg: readonly string[]): TCliCommand => {
78
94
  requireUsage(actionArg === 'export-authorization');
79
95
  const options = parseOptions(restArg, ['--cluster-id', '--expected-generation', '--output']);
80
- const generation = options['--expected-generation'];
81
- requireUsage(/^[1-9][0-9]{0,15}$/.test(generation) && Number.isSafeInteger(Number(generation)));
96
+ const generation = parseCounter(options['--expected-generation']);
82
97
  const outputPath = options['--output'];
83
98
  requireUsage(plugins.path.isAbsolute(outputPath) && plugins.path.normalize(outputPath) === outputPath
84
99
  && !outputPath.endsWith(plugins.path.sep));
85
100
  return {
86
101
  kind: 'relay-export-authorization',
87
- request: { clusterId: options['--cluster-id'], expectedGeneration: Number(generation) },
102
+ request: { clusterId: options['--cluster-id'], expectedGeneration: generation },
88
103
  outputPath,
89
104
  };
90
105
  };
106
+
107
+ /**
108
+ * `corestore set-control-token --stdin --expect absent|<revision>` and
109
+ * `corestore control-token-state`.
110
+ *
111
+ * `--stdin` names where the token comes from, so no invocation reads it from anywhere else, and
112
+ * `--expect` is the state `control-token-state` answered: `absent`, or the stored revision.
113
+ */
114
+ const parseCorestoreCommand = (actionArg: string | undefined, restArg: readonly string[]): TCliCommand => {
115
+ if (actionArg === 'control-token-state') {
116
+ requireUsage(restArg.length === 0);
117
+ return { kind: 'corestore-control-token-state' };
118
+ }
119
+ requireUsage(actionArg === 'set-control-token');
120
+ // `--stdin` is a flag in an option's place, either before or after `--expect <value>`.
121
+ requireUsage(restArg.length === 3);
122
+ const stdinIndex = restArg[0] === '--stdin' ? 0 : 2;
123
+ requireUsage(restArg[stdinIndex] === '--stdin');
124
+ const options = parseOptions(restArg.filter((_argArg, indexArg) => indexArg !== stdinIndex), ['--expect']);
125
+ const expected = options['--expect'];
126
+ return {
127
+ kind: 'corestore-set-control-token',
128
+ expected: expected === 'absent'
129
+ ? { state: 'absent' }
130
+ : { state: 'present', revision: parseCounter(expected) },
131
+ };
132
+ };
@@ -0,0 +1,71 @@
1
+ import * as plugins from './plugins.js';
2
+ import { decodeCorestoreControlToken } from './tokeninput.js';
3
+
4
+ type TCorestoreControlTokenSetRefusal = plugins.servezoneInterfaces.data.TCorestoreControlTokenSetRefusal;
5
+
6
+ /** Cloudly did not store the token, for one of the contract's refusals, which it names. */
7
+ export class CorestoreControlTokenSetRefusedError extends Error {
8
+ constructor(public readonly refusal: TCorestoreControlTokenSetRefusal) {
9
+ super(`The Corestore control token was not stored: ${refusal}.`);
10
+ this.name = 'CorestoreControlTokenSetRefusedError';
11
+ }
12
+ }
13
+
14
+ /**
15
+ * The contract refusal an error is, or `undefined` for every other error.
16
+ *
17
+ * Cloudly answers a refusal by its name and nothing else, so the name is the whole of the error's
18
+ * message; any other message, Cloudly's one denial text included, is not a refusal.
19
+ */
20
+ export const nameCorestoreControlTokenSetRefusal = (
21
+ errorArg: unknown,
22
+ ): TCorestoreControlTokenSetRefusal | undefined => {
23
+ if (!(errorArg instanceof Error)) return undefined;
24
+ return plugins.servezoneInterfaces.data.corestoreControlTokenSetRefusals
25
+ .find((refusalArg) => refusalArg === errorArg.message);
26
+ };
27
+
28
+ /** What the CLI prints about a stored token: the state it produced and its receipt, never the token. */
29
+ export interface ICorestoreControlTokenSetOutput
30
+ extends plugins.servezoneInterfaces.data.ICorestoreControlTokenSetResult {
31
+ ok: true;
32
+ command: 'corestore set-control-token';
33
+ }
34
+
35
+ /** What the CLI prints about the token's state: the state and its value-free receipts. */
36
+ export interface ICorestoreControlTokenStateOutput
37
+ extends plugins.servezoneInterfaces.data.ICorestoreControlTokenStateRead {
38
+ ok: true;
39
+ command: 'corestore control-token-state';
40
+ }
41
+
42
+ /**
43
+ * Stores the token through the API client while it is still exactly at `expected`, answering only
44
+ * the state and the receipt. The token bytes are the caller's to wipe; the text the request needs
45
+ * is decoded from them here and never printed, logged or put into an error.
46
+ */
47
+ export const setCorestoreControlToken = async (
48
+ apiClientArg: plugins.servezoneApi.CloudlyApiClient,
49
+ expectedArg: plugins.servezoneInterfaces.data.TCorestoreControlTokenState,
50
+ tokenArg: Uint8Array,
51
+ ): Promise<ICorestoreControlTokenSetOutput> => {
52
+ try {
53
+ const { state, receipt } = await apiClientArg.platform.setCorestoreControlToken({
54
+ token: decodeCorestoreControlToken(tokenArg),
55
+ expected: expectedArg,
56
+ });
57
+ return { ok: true, command: 'corestore set-control-token', state, receipt };
58
+ } catch (error) {
59
+ const refusal = nameCorestoreControlTokenSetRefusal(error);
60
+ if (refusal !== undefined) throw new CorestoreControlTokenSetRefusedError(refusal);
61
+ throw error;
62
+ }
63
+ };
64
+
65
+ /** Whether Cloudly holds the token, at which revision, and its receipts, oldest first. */
66
+ export const getCorestoreControlTokenState = async (
67
+ apiClientArg: plugins.servezoneApi.CloudlyApiClient,
68
+ ): Promise<ICorestoreControlTokenStateOutput> => {
69
+ const { state, receipts } = await apiClientArg.platform.getCorestoreControlTokenState();
70
+ return { ok: true, command: 'corestore control-token-state', state, receipts };
71
+ };
@@ -1,7 +1,7 @@
1
1
  import * as plugins from './plugins.js';
2
2
  import { CliClient } from './classes.cliclient.js';
3
3
  import { parseCliCommand, type TCliCommand } from './commands.js';
4
- import { readBootstrapTokenInput } from './tokeninput.js';
4
+ import { readBootstrapTokenInput, readCorestoreControlTokenInput } from './tokeninput.js';
5
5
  import { requirePrivateOutputDirectory } from './relayauthorization.js';
6
6
 
7
7
  export { CliUsageError, parseCliCommand, type TCliCommand } from './commands.js';
@@ -15,24 +15,48 @@ export {
15
15
  } from './relayauthorization.js';
16
16
  export {
17
17
  BootstrapTokenInputError,
18
+ CorestoreControlTokenInputError,
19
+ TokenInputError,
18
20
  readBootstrapTokenInput,
21
+ readCorestoreControlTokenInput,
19
22
  type TBootstrapTokenInputRefusal,
23
+ type TTokenInputRefusal,
20
24
  } from './tokeninput.js';
25
+ export {
26
+ CorestoreControlTokenSetRefusedError,
27
+ getCorestoreControlTokenState,
28
+ nameCorestoreControlTokenSetRefusal,
29
+ setCorestoreControlToken,
30
+ type ICorestoreControlTokenSetOutput,
31
+ type ICorestoreControlTokenStateOutput,
32
+ } from './corestorecontroltoken.js';
21
33
 
22
- /** A parsed command with everything it reads locally; a bootstrap carries the token from stdin. */
34
+ /**
35
+ * A parsed command with everything it reads locally: a bootstrap carries the admin token from
36
+ * stdin, a control token set the Corestore control token.
37
+ */
23
38
  type TCliInvocation =
24
- | Exclude<TCliCommand, { kind: 'gateway-bootstrap' }>
25
- | (Extract<TCliCommand, { kind: 'gateway-bootstrap' }> & { bootstrapToken: Uint8Array });
39
+ | Exclude<TCliCommand, { kind: 'gateway-bootstrap' | 'corestore-set-control-token' }>
40
+ | (Extract<TCliCommand, { kind: 'gateway-bootstrap' }> & { bootstrapToken: Uint8Array })
41
+ | (Extract<TCliCommand, { kind: 'corestore-set-control-token' }> & { controlToken: Uint8Array });
42
+
43
+ /** Reads the token the command needs from stdin, before anything connects. */
44
+ const readInvocation = async (commandArg: TCliCommand): Promise<TCliInvocation> => {
45
+ if (commandArg.kind === 'gateway-bootstrap') {
46
+ return { ...commandArg, bootstrapToken: await readBootstrapTokenInput(process.stdin) };
47
+ }
48
+ if (commandArg.kind === 'corestore-set-control-token') {
49
+ return { ...commandArg, controlToken: await readCorestoreControlTokenInput(process.stdin) };
50
+ }
51
+ return commandArg;
52
+ };
26
53
 
27
54
  export const runCli = async () => {
28
55
  // The command and the token are settled before anything connects, so a wrong invocation or a
29
56
  // token that could never be one fails without reaching Cloudly.
30
57
  let command: TCliInvocation;
31
58
  try {
32
- const parsed = parseCliCommand(process.argv.slice(2));
33
- command = parsed.kind === 'gateway-bootstrap'
34
- ? { ...parsed, bootstrapToken: await readBootstrapTokenInput(process.stdin) }
35
- : parsed;
59
+ command = await readInvocation(parseCliCommand(process.argv.slice(2)));
36
60
  // An output the bearer could not be written to fetches no bearer and records no export.
37
61
  if (command.kind === 'relay-export-authorization') {
38
62
  await requirePrivateOutputDirectory(command.outputPath);
@@ -56,6 +80,10 @@ export const runCli = async () => {
56
80
  await cliClient.bootstrapGateway(command.target, command.bootstrapToken);
57
81
  } else if (command.kind === 'relay-export-authorization') {
58
82
  await cliClient.exportRelayAuthorization(command.request, command.outputPath);
83
+ } else if (command.kind === 'corestore-set-control-token') {
84
+ await cliClient.setCorestoreControlToken(command.expected, command.controlToken);
85
+ } else if (command.kind === 'corestore-control-token-state') {
86
+ await cliClient.getCorestoreControlTokenState();
59
87
  } else {
60
88
  await cliClient.getGatewayStatus();
61
89
  }
@@ -68,6 +96,7 @@ export const runCli = async () => {
68
96
  }
69
97
  } finally {
70
98
  if (command.kind === 'gateway-bootstrap') command.bootstrapToken.fill(0);
99
+ if (command.kind === 'corestore-set-control-token') command.controlToken.fill(0);
71
100
  }
72
101
  };
73
102
 
@@ -12,10 +12,12 @@ export {
12
12
  // @serve.zone scope
13
13
  import * as servezoneApi from '@serve.zone/api';
14
14
  import * as servezoneInterfaces from '@serve.zone/interfaces';
15
+ import * as servezoneInterfacesRuntime from '@serve.zone/interfaces/runtime';
15
16
 
16
17
  export {
17
18
  servezoneApi,
18
- servezoneInterfaces
19
+ servezoneInterfaces,
20
+ servezoneInterfacesRuntime,
19
21
  }
20
22
 
21
23
  // @push.rocks scope
@@ -17,6 +17,7 @@ This submodule is intentionally small in the current codebase:
17
17
  - Without arguments, calls `CliClient.getClusters()` and prints the result.
18
18
  - Runs `servezone gateway bootstrap` and `servezone gateway status` for Cloudly's dcrouter gateway enrollment.
19
19
  - Runs `servezone relay export-authorization` to write a cluster relay's bearer to a private file.
20
+ - Runs `servezone corestore set-control-token` and `servezone corestore control-token-state` for the Corestore control token the cluster relays use.
20
21
 
21
22
  It is not currently a full command tree for services, secrets, deployments, logs, profiles, or shell completion. Those flows should be implemented against `@serve.zone/api` before documenting them as CLI commands.
22
23
 
@@ -91,6 +92,26 @@ servezone relay export-authorization --cluster-id <clusterId> --expected-generat
91
92
  - The bearer is fetched with `@serve.zone/api` (`cluster.exportClusterRelayAuthorization`, which checks the answer against the request) and written with `@push.rocks/smartfs` as a new file, mode `0600` whatever the umask, exclusive and atomic: the whole bearer or no file, never over an existing entry and never through a symbolic link at the path.
92
93
  - It is never written to stdout. stdout carries one JSON line of metadata: `clusterId`, `generation`, `outputPath`, `byteLength`, `sha256` (what `sha256sum` prints for the file) and `receiptId`, the audit receipt Cloudly recorded. A refusal is printed to stderr with exit code 1 and never carries the bearer.
93
94
 
95
+ ## Corestore Control Token
96
+
97
+ Every cluster relay calls its nodes' Corestore control API with one control token, which Cloudly stores for both Corestore provider configs. These commands set and read it over the API, without any access to Cloudly's process. Both sign in exactly as the default action does; the set needs an administrator who signed in within the last five minutes.
98
+
99
+ ```sh
100
+ CLOUDLY_URL=https://cloudly.example.com \
101
+ CLOUDLY_USERNAME=admin CLOUDLY_PASSWORD=change-me \
102
+ servezone corestore control-token-state
103
+
104
+ # Pipe the token in, naming the state the read answered:
105
+ servezone corestore set-control-token --stdin --expect absent < control-token.txt
106
+ servezone corestore set-control-token --stdin --expect 3 < control-token.txt
107
+ ```
108
+
109
+ - `control-token-state` prints one JSON line: `{"ok":true,"command":"corestore control-token-state","state":…,"receipts":[…]}`. `state` is `{"state":"absent"}` or `{"state":"present","revision":<n>}`; `receipts` are the value-free audit receipts (`id`, `revision`, `via`, `actorId`, `setAt`), oldest first.
110
+ - `set-control-token --stdin --expect absent|<revision>` stores the token only while it is still exactly at the state `--expect` names: `absent`, or the revision `control-token-state` answered (a positive integer without leading zeros). It is never a minimum, so a set that committed in between is refused rather than overwritten.
111
+ - The token is read from stdin and from nowhere else: `--stdin` names that, and an argument, `--token`, or any other extra argument is refused as usage without echoing it; the environment is never read for it. stdin must not be a terminal. One trailing line break (`\n` or `\r\n`) is stripped; what remains must pass the contract's `isCorestoreControlToken` (32 to 4096 bytes of UTF-8 without whitespace). A refused stdin is named (`token-input-terminal`, `token-input-oversized`, `token-input-invalid`) before anything connects, and every byte read is wiped.
112
+ - The token is sent once with `@serve.zone/api` (`platform.setCorestoreControlToken`, past every TypedRequest hook), and the answer is checked against the request. stdout carries one JSON line with the state the set produced and its receipt, never the token: `{"ok":true,"command":"corestore set-control-token","state":{"state":"present","revision":<n>},"receipt":{…}}`.
113
+ - A refusal is printed to stderr with exit code 1 as `The Corestore control token was not stored: <refusal>.`, naming one of the contract's `corestoreControlTokenSetRefusals`: `token-present` (`--expect absent`, and a token is stored), `token-absent` (a revision was expected, and none is stored), `token-revision-mismatch` (the stored token is at another revision), `token-invalid` or `provider-unavailable` (a Corestore provider config is missing or disabled). Every other failure, an unauthorized or stale administrator included, is Cloudly's one denial text, `Corestore control token request denied.`, with exit code 1.
114
+
94
115
  ## Programmatic Use
95
116
 
96
117
  The published submodule exports the `runCli()` entry point. For automation, most callers should use `@serve.zone/api` directly; the internal `CliClient` currently wraps `CloudlyApiClient` only to run the default cluster-list action:
@@ -116,9 +137,10 @@ await cli.getClusters();
116
137
  | Path | Purpose |
117
138
  | --- | --- |
118
139
  | `index.ts` | Runtime entry point for the published CLI. |
119
- | `classes.cliclient.ts` | Minimal client wrapper: `getClusters()`, `bootstrapGateway()`, `getGatewayStatus()` and `exportRelayAuthorization()`. |
140
+ | `classes.cliclient.ts` | Minimal client wrapper: `getClusters()`, `bootstrapGateway()`, `getGatewayStatus()`, `exportRelayAuthorization()`, `setCorestoreControlToken()` and `getCorestoreControlTokenState()`. |
120
141
  | `commands.ts` | Parses the argument vector into one command, refusing anything else as usage. |
121
- | `tokeninput.ts` | Reads the dcrouter admin token from stdin. |
142
+ | `tokeninput.ts` | Reads the dcrouter admin token and the Corestore control token from stdin, each bounded and checked by its contract. |
143
+ | `corestorecontroltoken.ts` | Sets and reads the Corestore control token through the API client and names the contract's refusals. |
122
144
  | `relayauthorization.ts` | Checks the private output directory and writes a cluster relay's bearer as a new 0600 file. |
123
145
  | `plugins.ts` | Centralized imports for the submodule. |
124
146
  | `tspublish.json` | Published package name, dependencies, `servezone` bin metadata, and `useBase`, which takes the registries from the release's npm target. |
@@ -1,39 +1,64 @@
1
1
  import * as plugins from './plugins.js';
2
2
 
3
- /** Why the admin token piped in was not taken, named without quoting any of it. */
4
- export type TBootstrapTokenInputRefusal =
3
+ /** Why a token piped in was not taken, named without quoting any of it. */
4
+ export type TTokenInputRefusal =
5
5
  /** stdin is a terminal, which would echo a token typed into it; pipe the token in instead. */
6
6
  | 'token-input-terminal'
7
7
  /** stdin carried more bytes than a token and one line break. */
8
8
  | 'token-input-oversized'
9
- /** What stdin carried is not a token: empty, or a byte outside visible ASCII, a space included. */
9
+ /** What stdin carried is not a token by the token's own rule: empty, or a byte the rule refuses. */
10
10
  | 'token-input-invalid';
11
11
 
12
- export class BootstrapTokenInputError extends Error {
13
- constructor(public readonly refusal: TBootstrapTokenInputRefusal) {
14
- super(`The dcrouter admin token on stdin was refused: ${refusal}.`);
12
+ /** Why the dcrouter admin token piped into `gateway bootstrap` was not taken. */
13
+ export type TBootstrapTokenInputRefusal = TTokenInputRefusal;
14
+
15
+ /** A refused token input: which token, and why, never a byte of what was read. */
16
+ export class TokenInputError extends Error {
17
+ constructor(public readonly refusal: TTokenInputRefusal, subjectArg: string) {
18
+ super(`${subjectArg} on stdin was refused: ${refusal}.`);
19
+ this.name = 'TokenInputError';
20
+ }
21
+ }
22
+
23
+ export class BootstrapTokenInputError extends TokenInputError {
24
+ constructor(refusalArg: TTokenInputRefusal) {
25
+ super(refusalArg, 'The dcrouter admin token');
15
26
  this.name = 'BootstrapTokenInputError';
16
27
  }
17
28
  }
18
29
 
19
- function refuseUnless(conditionArg: unknown, refusalArg: TBootstrapTokenInputRefusal): asserts conditionArg {
20
- if (!conditionArg) throw new BootstrapTokenInputError(refusalArg);
30
+ export class CorestoreControlTokenInputError extends TokenInputError {
31
+ constructor(refusalArg: TTokenInputRefusal) {
32
+ super(refusalArg, 'The Corestore control token');
33
+ this.name = 'CorestoreControlTokenInputError';
34
+ }
21
35
  }
22
36
 
23
- /** What the token is read from: the command's stdin, or a stream a test hands in. */
24
- export type TBootstrapTokenInput = AsyncIterable<Uint8Array> & { isTTY?: boolean };
37
+ /** What a token is read from: the command's stdin, or a stream a test hands in. */
38
+ export type TTokenInput = AsyncIterable<Uint8Array> & { isTTY?: boolean };
39
+ export type TBootstrapTokenInput = TTokenInput;
40
+
41
+ /** One token's rule: its contract's bound and check, and the error its refusals are named with. */
42
+ interface ITokenInputRule {
43
+ /** The most bytes the token itself may have; one line break more is read and stripped. */
44
+ maximumTokenBytes: number;
45
+ /** The contract's check of the token's bytes, line break stripped. */
46
+ isToken: (tokenArg: Uint8Array) => boolean;
47
+ refuse: (refusalArg: TTokenInputRefusal) => TokenInputError;
48
+ }
25
49
 
26
50
  /**
27
- * The dcrouter admin token piped into `gateway bootstrap`, as bytes the caller wipes.
51
+ * A token piped into a command, as bytes the caller wipes.
28
52
  *
29
53
  * stdin is the only source: an argument would sit in the process list and the shell history, and
30
54
  * the environment leaks into child processes. One trailing line break, `\n` or `\r\n`, is what
31
- * `echo` or a file adds and is not part of the token; everything else must pass the contract's
32
- * token rule. Every byte read is wiped before this answers, whatever it answers.
55
+ * `echo` or a file adds and is not part of the token; everything else must pass the token's rule.
56
+ * Reading stops as soon as stdin carries more than the rule's bound and one line break. Every byte
57
+ * read is wiped before this answers, whatever it answers.
33
58
  */
34
- export const readBootstrapTokenInput = async (inputArg: TBootstrapTokenInput): Promise<Uint8Array> => {
35
- refuseUnless(inputArg.isTTY !== true, 'token-input-terminal');
36
- const maximumBytes = plugins.servezoneInterfaces.data.externalGatewayBootstrapContract.maximumTokenBytes + 2;
59
+ const readTokenInput = async (inputArg: TTokenInput, ruleArg: ITokenInputRule): Promise<Uint8Array> => {
60
+ if (inputArg.isTTY === true) throw ruleArg.refuse('token-input-terminal');
61
+ const maximumBytes = ruleArg.maximumTokenBytes + 2;
37
62
  const chunks: Uint8Array[] = [];
38
63
  let input: Uint8Array | undefined;
39
64
  try {
@@ -41,7 +66,7 @@ export const readBootstrapTokenInput = async (inputArg: TBootstrapTokenInput): P
41
66
  for await (const chunk of inputArg) {
42
67
  chunks.push(chunk);
43
68
  byteLength += chunk.byteLength;
44
- refuseUnless(byteLength <= maximumBytes, 'token-input-oversized');
69
+ if (byteLength > maximumBytes) throw ruleArg.refuse('token-input-oversized');
45
70
  }
46
71
  input = Buffer.concat(chunks, byteLength);
47
72
  let end = input.byteLength;
@@ -50,7 +75,7 @@ export const readBootstrapTokenInput = async (inputArg: TBootstrapTokenInput): P
50
75
  if (end > 0 && input[end - 1] === 0x0d) end -= 1;
51
76
  }
52
77
  const token = input.subarray(0, end);
53
- refuseUnless(plugins.servezoneInterfaces.data.isExternalGatewayBootstrapToken(token), 'token-input-invalid');
78
+ if (!ruleArg.isToken(token)) throw ruleArg.refuse('token-input-invalid');
54
79
  // A copy, because the input and every view of it are wiped below.
55
80
  return new Uint8Array(token);
56
81
  } finally {
@@ -58,3 +83,41 @@ export const readBootstrapTokenInput = async (inputArg: TBootstrapTokenInput): P
58
83
  for (const chunk of chunks) chunk.fill(0);
59
84
  }
60
85
  };
86
+
87
+ /**
88
+ * The dcrouter admin token piped into `gateway bootstrap`: at most the bootstrap contract's
89
+ * `maximumTokenBytes` of visible ASCII.
90
+ */
91
+ export const readBootstrapTokenInput = async (inputArg: TTokenInput): Promise<Uint8Array> =>
92
+ await readTokenInput(inputArg, {
93
+ maximumTokenBytes: plugins.servezoneInterfaces.data.externalGatewayBootstrapContract.maximumTokenBytes,
94
+ isToken: (tokenArg) => plugins.servezoneInterfaces.data.isExternalGatewayBootstrapToken(tokenArg),
95
+ refuse: (refusalArg) => new BootstrapTokenInputError(refusalArg),
96
+ });
97
+
98
+ /**
99
+ * The token as text, exactly as its bytes say: invalid UTF-8 throws, and a leading byte order mark
100
+ * is kept, so the contract judges and Cloudly receives every character that was piped in.
101
+ */
102
+ export const decodeCorestoreControlToken = (tokenArg: Uint8Array): string =>
103
+ new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(tokenArg);
104
+
105
+ /**
106
+ * The Corestore control token piped into `corestore set-control-token`: what the contract's
107
+ * `isCorestoreControlToken` accepts, so at most `corestoreCredentialRuntimeLimits
108
+ * .maximumControlTokenBytes` of UTF-8 without whitespace.
109
+ */
110
+ export const readCorestoreControlTokenInput = async (inputArg: TTokenInput): Promise<Uint8Array> =>
111
+ await readTokenInput(inputArg, {
112
+ maximumTokenBytes: plugins.servezoneInterfacesRuntime.corestoreCredentialRuntimeLimits.maximumControlTokenBytes,
113
+ isToken: (tokenArg) => {
114
+ let token: string;
115
+ try {
116
+ token = decodeCorestoreControlToken(tokenArg);
117
+ } catch {
118
+ return false;
119
+ }
120
+ return plugins.servezoneInterfacesRuntime.isCorestoreControlToken(token);
121
+ },
122
+ refuse: (refusalArg) => new CorestoreControlTokenInputError(refusalArg),
123
+ });