gcb-host 0.0.0 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/bin/gcb.js +5 -0
  3. package/lib/audit.d.ts +25 -0
  4. package/lib/audit.d.ts.map +1 -0
  5. package/lib/audit.js +73 -0
  6. package/lib/audit.js.map +1 -0
  7. package/lib/broker.d.ts +61 -0
  8. package/lib/broker.d.ts.map +1 -0
  9. package/lib/broker.js +512 -0
  10. package/lib/broker.js.map +1 -0
  11. package/lib/commands/authorize.d.ts +38 -0
  12. package/lib/commands/authorize.d.ts.map +1 -0
  13. package/lib/commands/authorize.js +134 -0
  14. package/lib/commands/authorize.js.map +1 -0
  15. package/lib/commands/compose.d.ts +42 -0
  16. package/lib/commands/compose.d.ts.map +1 -0
  17. package/lib/commands/compose.js +208 -0
  18. package/lib/commands/compose.js.map +1 -0
  19. package/lib/commands/deployment.d.ts +49 -0
  20. package/lib/commands/deployment.d.ts.map +1 -0
  21. package/lib/commands/deployment.js +72 -0
  22. package/lib/commands/deployment.js.map +1 -0
  23. package/lib/commands/diagnose.d.ts +4 -0
  24. package/lib/commands/diagnose.d.ts.map +1 -0
  25. package/lib/commands/diagnose.js +213 -0
  26. package/lib/commands/diagnose.js.map +1 -0
  27. package/lib/commands/host-opener.d.ts +23 -0
  28. package/lib/commands/host-opener.d.ts.map +1 -0
  29. package/lib/commands/host-opener.js +244 -0
  30. package/lib/commands/host-opener.js.map +1 -0
  31. package/lib/commands/init.d.ts +153 -0
  32. package/lib/commands/init.d.ts.map +1 -0
  33. package/lib/commands/init.js +515 -0
  34. package/lib/commands/init.js.map +1 -0
  35. package/lib/commands/stage.d.ts +44 -0
  36. package/lib/commands/stage.d.ts.map +1 -0
  37. package/lib/commands/stage.js +165 -0
  38. package/lib/commands/stage.js.map +1 -0
  39. package/lib/config.d.ts +110 -0
  40. package/lib/config.d.ts.map +1 -0
  41. package/lib/config.js +408 -0
  42. package/lib/config.js.map +1 -0
  43. package/lib/daemon.d.ts +45 -0
  44. package/lib/daemon.d.ts.map +1 -0
  45. package/lib/daemon.js +123 -0
  46. package/lib/daemon.js.map +1 -0
  47. package/lib/device-flow.d.ts +80 -0
  48. package/lib/device-flow.d.ts.map +1 -0
  49. package/lib/device-flow.js +148 -0
  50. package/lib/device-flow.js.map +1 -0
  51. package/lib/gh.d.ts +37 -0
  52. package/lib/gh.d.ts.map +1 -0
  53. package/lib/gh.js +195 -0
  54. package/lib/gh.js.map +1 -0
  55. package/lib/http.d.ts +20 -0
  56. package/lib/http.d.ts.map +1 -0
  57. package/lib/http.js +133 -0
  58. package/lib/http.js.map +1 -0
  59. package/lib/index.d.ts +10 -0
  60. package/lib/index.d.ts.map +1 -0
  61. package/lib/index.js +52 -0
  62. package/lib/index.js.map +1 -0
  63. package/lib/policy.d.ts +60 -0
  64. package/lib/policy.d.ts.map +1 -0
  65. package/lib/policy.js +118 -0
  66. package/lib/policy.js.map +1 -0
  67. package/lib/providers/github-app.d.ts +56 -0
  68. package/lib/providers/github-app.d.ts.map +1 -0
  69. package/lib/providers/github-app.js +901 -0
  70. package/lib/providers/github-app.js.map +1 -0
  71. package/lib/providers/index.d.ts +29 -0
  72. package/lib/providers/index.d.ts.map +1 -0
  73. package/lib/providers/index.js +32 -0
  74. package/lib/providers/index.js.map +1 -0
  75. package/lib/providers/static.d.ts +18 -0
  76. package/lib/providers/static.d.ts.map +1 -0
  77. package/lib/providers/static.js +41 -0
  78. package/lib/providers/static.js.map +1 -0
  79. package/lib/proxy.d.ts +23 -0
  80. package/lib/proxy.d.ts.map +1 -0
  81. package/lib/proxy.js +57 -0
  82. package/lib/proxy.js.map +1 -0
  83. package/package.json +36 -2
  84. package/README.md +0 -1
@@ -0,0 +1,134 @@
1
+ /**
2
+ * `authorize` — get a user token for this app, once, from whoever runs it.
3
+ *
4
+ * Run on the host, beside the configuration. It prints a code, waits for that person to type it at
5
+ * GitHub on whatever device has a browser, and stores the refresh token next to the configuration and
6
+ * its key — outside every mount, never in the repository. From then on the broker renews the token by
7
+ * itself and nothing here has to be run again.
8
+ *
9
+ * The token is what makes a pull request authored by a person rather than by the app. Only a person
10
+ * can be the author of one that reviewers will not skip, and only the device flow reaches a person
11
+ * whose host has no browser and no callback server.
12
+ *
13
+ * Exit codes: 0 stored, 1 refused.
14
+ */
15
+ import { dirname, join, resolve as resolvePath } from 'node:path';
16
+ import { USER_TOKEN_FILE, userTokenPath, loadConfig, resolveConfigPath } from '../config.js';
17
+ import { collectUserToken, startDeviceFlow } from '../device-flow.js';
18
+ import { fetcherFor, parseProxy } from '../http.js';
19
+ import { fail, insideMountedPath, isInsideContainer, parseArgs, say, writeSecretFile } from 'gcb-core';
20
+ /** Usage text for `authorize`. */
21
+ export const AUTHORIZE_USAGE = `Usage: gcb authorize [options]
22
+
23
+ Get a user token for this app, by authorizing it once from a browser somewhere else. Run it on the host,
24
+ beside the configuration. It stores a refresh token and prints where; nothing else needs running after
25
+ that, because the broker renews the token itself.
26
+
27
+ --config <path> The configuration (default: $GIT_BROKER_CONFIG, else ./broker.config.yml)
28
+ --host <host> The host block to authorize for (default: github.com)
29
+ --to <path> Where to write the refresh token (default: beside the configuration)
30
+ --proxy <url> Use this proxy for this command
31
+ --no-proxy Reach GitHub directly for this command
32
+ -h, --help Show this help
33
+
34
+ The app needs its client id configured (the App ID will not do), and "Enable Device Flow" selected in
35
+ its settings. GitHub returns a refresh token only while "Expire user authorization tokens" is selected,
36
+ which is its default and what this wants.
37
+ `;
38
+ /**
39
+ * Where this command writes the refresh token.
40
+ *
41
+ * Beside the configuration, because the configuration is the path this process was *given*: the paths
42
+ * inside it belong to whichever process runs the broker, and for a sidecar those are container paths
43
+ * that do not exist out here. In the usual sidecar layout the configuration and the key sit in one
44
+ * directory, so this lands in the same file the broker reads; where they differ, the difference is
45
+ * printed rather than discovered later.
46
+ *
47
+ * @param configPath - the resolved configuration path.
48
+ * @returns the path to write the refresh token to.
49
+ */
50
+ export function refreshTokenPath(configPath) {
51
+ return join(dirname(resolvePath(configPath)), USER_TOKEN_FILE);
52
+ }
53
+ /**
54
+ * Where the broker will look for it, which is beside the key — from the key's own point of view.
55
+ *
56
+ * @param privateKeyPath - the path as the configuration records it.
57
+ * @returns the path the broker reads, or `undefined` when the key is not a file.
58
+ */
59
+ export function brokerReadsPath(privateKeyPath) {
60
+ return privateKeyPath === undefined ? undefined : userTokenPath(privateKeyPath);
61
+ }
62
+ /**
63
+ * Run the `authorize` command.
64
+ *
65
+ * @param argv - arguments after the command name.
66
+ * @returns the process exit code: 0 stored, 1 refused.
67
+ */
68
+ import { assertProxyEnabled } from '../proxy.js';
69
+ export async function runAuthorize(argv) {
70
+ const args = parseArgs(argv, { booleans: ['help', 'no-proxy'] });
71
+ if (args.has('no-proxy') && args.value('proxy') !== undefined)
72
+ fail('--proxy and --no-proxy are mutually exclusive');
73
+ if (args.has('proxy') && args.value('proxy') === undefined)
74
+ fail('--proxy needs a value');
75
+ const proxy = args.has('no-proxy') ? null : args.value('proxy');
76
+ if (proxy !== undefined && parseProxy(proxy) === false)
77
+ fail('--proxy must be an http(s) URL');
78
+ if (args.has('help') || args.has('h')) {
79
+ say(AUTHORIZE_USAGE);
80
+ return 0;
81
+ }
82
+ if (proxy === undefined)
83
+ assertProxyEnabled();
84
+ const configPath = resolveConfigPath(args.value('config'));
85
+ const host = args.value('host') ?? 'github.com';
86
+ const config = loadConfig(configPath);
87
+ const block = config.hosts[host];
88
+ if (block === undefined)
89
+ fail(`no host ${host} in ${configPath}`);
90
+ if (block.provider !== 'github-app')
91
+ fail(`${host} is not a github-app host`);
92
+ if (block.clientId === undefined) {
93
+ fail(`${host} has no clientId — a device flow needs the app's client id, and the App ID will not do`);
94
+ }
95
+ const fetcher = fetcherFor(proxy);
96
+ const code = await startDeviceFlow(block.clientId, fetcher);
97
+ say(`Open ${code.verificationUri} and enter: ${code.userCode}`);
98
+ say(`Waiting up to ${Math.round(code.expiresIn / 60)} minutes for that to be authorized…`);
99
+ const token = await collectUserToken(block.clientId, code, fetcher, (ms) => new Promise((done) => setTimeout(done, ms)));
100
+ if (token.refreshToken === undefined) {
101
+ fail('GitHub returned no refresh token, so the token would expire in eight hours with no way to renew.\n' +
102
+ ' Select "Expire user authorization tokens" in the app settings and authorize again.');
103
+ }
104
+ const target = args.value('to') ?? refreshTokenPath(configPath);
105
+ // The same two checks init and stage apply, for the same reason: this is a credential of a person, and
106
+ // the container that pushes must not be able to read it. It is written outside every mount or not at all.
107
+ if (isInsideContainer()) {
108
+ fail('run this on the host, not inside the container: the token must not land anywhere the container can read');
109
+ }
110
+ const mounted = insideMountedPath(target);
111
+ if (mounted !== null) {
112
+ fail(`${target} is inside ${mounted}, which is mounted into the container; the refresh token must live\n` +
113
+ ' outside every mount — the deployment directory beside the configuration is the usual place');
114
+ }
115
+ writeSecretFile(target, `${token.refreshToken}\n`);
116
+ say(`stored the refresh token in ${target} (mode 600)`);
117
+ const readByBroker = brokerReadsPath(block.privateKeyPath);
118
+ if (readByBroker === undefined) {
119
+ // An inline privateKeyPem deployment: the broker has no directory to derive a path from, so it never
120
+ // looks for this file. Saying otherwise would be a promise the broker does not keep.
121
+ say('note: this host block keeps its key inline, so the broker has nowhere to look for this file:');
122
+ say(' pull requests will stay app-authored until the key is a file (privateKeyPath)');
123
+ }
124
+ else if (resolvePath(readByBroker) !== resolvePath(target)) {
125
+ say(`note: the broker will look in ${readByBroker} — make sure that is this file, and that it is`);
126
+ say(' writable, because GitHub rotates the token on every exchange');
127
+ }
128
+ else {
129
+ say('which is where the broker looks for it — keep it writable, since the token is rotated on every use');
130
+ }
131
+ say('the broker will use it for pull requests authored by that person; nothing else needs running');
132
+ return 0;
133
+ }
134
+ //# sourceMappingURL=authorize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authorize.js","sourceRoot":"","sources":["../../src/commands/authorize.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ /** Everything `compose` needs, resolved. */
2
+ export interface ComposeInput {
3
+ /** Host directory holding broker.config.yml, app.pem and log/. */
4
+ readonly dir: string;
5
+ /** Host configuration file; defaults to `${dir}/broker.config.yml`. */
6
+ readonly configPath?: string;
7
+ /** Host directory the pushing container already mounts, where the socket will appear. */
8
+ readonly socketDir: string;
9
+ /** `uid:gid` the sidecar runs as; must match the container that needs the socket. */
10
+ readonly user: string;
11
+ /** Base image. */
12
+ readonly image: string;
13
+ /** npm spec to run, e.g. `gcb@0.0.0`. */
14
+ readonly packageSpec: string;
15
+ /** Compose service and container name. */
16
+ readonly name: string;
17
+ /**
18
+ * Host directory holding the broker's code, as `stage` exports it. When given, the sidecar runs
19
+ * that code with the image's Node instead of fetching the package at start: no registry access
20
+ * at boot, and the commit that runs is the one you reviewed.
21
+ */
22
+ readonly code?: string;
23
+ }
24
+ /** Where a code-mounted sidecar expects the broker. */
25
+ export declare const CODE_DIR = "/opt/gcb";
26
+ /**
27
+ * Render the compose file.
28
+ *
29
+ * @param input - the resolved options.
30
+ * @returns the YAML, newline-terminated.
31
+ */
32
+ export declare function renderCompose(input: ComposeInput): string;
33
+ /** Usage text for `compose`. */
34
+ export declare const COMPOSE_USAGE = "Usage: gcb compose [options]\n\nPrint a Docker sidecar definition for the broker (to stdout). Pair it with a config written by\n`init --mode sidecar`, whose paths this file fixes.\n\n --dir <path> Host directory holding broker.config.yml, app.pem and log/ (default: the config's directory)\n --config <path> The sidecar's config, checked before anything is printed\n (default: $GIT_BROKER_CONFIG, else ./broker.config.yml)\n --socket-dir <path> Host directory the pushing container already mounts\n --code <path> Run code mounted from this host directory instead of fetching the package\n at start (as `stage` exports it); nothing is fetched or built at boot\n --user <uid:gid> UID the sidecar runs as (default: this user)\n --image <name> Base image (default: node:24-slim)\n --package <spec> npm spec to run (default: gcb-host@<version>)\n --name <name> Service and container name (default: gcb)\n -h, --help Show this help\n";
35
+ /**
36
+ * Run the `compose` command.
37
+ *
38
+ * @param argv - arguments after the command name.
39
+ * @returns the process exit code.
40
+ */
41
+ export declare function runCompose(argv: readonly string[]): number;
42
+ //# sourceMappingURL=compose.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../../src/commands/compose.ts"],"names":[],"mappings":"AAmBA,4CAA4C;AAC5C,MAAM,WAAW,YAAY;IAC3B,kEAAkE;IAClE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,uEAAuE;IACvE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,yFAAyF;IACzF,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,qFAAqF;IACrF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,kBAAkB;IAClB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,yCAAyC;IACzC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,0CAA0C;IAC1C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,uDAAuD;AACvD,eAAO,MAAM,QAAQ,aAAa,CAAC;AAEnC;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAgHzD;AAED,gCAAgC;AAChC,eAAO,MAAM,aAAa,kgCAgBzB,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CA0D1D"}
@@ -0,0 +1,208 @@
1
+ /**
2
+ * `compose` — print a Docker sidecar definition for the broker.
3
+ *
4
+ * The broker runs in its own container with the private key mounted read-only, no published
5
+ * ports, no capabilities and a read-only root filesystem; the only surface it shares with
6
+ * anything else is the socket directory. The container fetches the published package, so there
7
+ * is no checkout to copy and nothing to build on the host.
8
+ *
9
+ * The mount targets are constants from `deployment.ts`, shared with `init --mode sidecar`, so
10
+ * the paths recorded in `broker.config.yml` and the paths this file mounts cannot drift apart.
11
+ */
12
+ import fs from 'node:fs';
13
+ import path from 'node:path';
14
+ import { inferMode, loadConfig, resolveConfigPath, deploymentDir, SIDECAR } from '../config.js';
15
+ import { PACKAGE_VERSION } from 'gcb-core';
16
+ import { fail, parseArgs, say, warn } from 'gcb-core';
17
+ import { USER_TOKEN_FILE } from '../config.js';
18
+ /** Where a code-mounted sidecar expects the broker. */
19
+ export const CODE_DIR = '/opt/gcb';
20
+ /**
21
+ * Render the compose file.
22
+ *
23
+ * @param input - the resolved options.
24
+ * @returns the YAML, newline-terminated.
25
+ */
26
+ export function renderCompose(input) {
27
+ const socketDir = input.socketDir.replace(/\/+$/, '');
28
+ const configPath = input.configPath ?? `${input.dir}/broker.config.yml`;
29
+ const fromCode = input.code !== undefined;
30
+ const origin = fromCode
31
+ ? `# It runs the code mounted read-only from ${input.code}, which is whatever \`stage\` put there —
32
+ # see the STAGED.json beside it for the commit. stage does not install dependencies or build:
33
+ # run \`corepack yarn install\` in that directory first, then the source entry point runs here.`
34
+ : `# The image fetches the published package at start:
35
+ #
36
+ # npx --yes --package '${input.packageSpec}' gcb daemon
37
+ #
38
+ # To avoid depending on the registry at boot, either render this file with --code <directory> to
39
+ # run staged code from a mount, or point --image at an image of your own that already has the
40
+ # package installed, and change \`command\` to the binary in it:
41
+ # ["gcb", "daemon", "--config", "${SIDECAR.configPath}"]`;
42
+ // The egress lines belong to both renders: a --code deployment is a sidecar too, and without them it
43
+ // would carry no proxy variables at all, so the daemon would go direct and hang rather than refuse.
44
+ const egress = `
45
+ # Egress. The socket needs no proxy; the broker's calls to api.github.com and github.com do.
46
+ - http_proxy=\${http_proxy:-\${HTTP_PROXY:-}}
47
+ - https_proxy=\${https_proxy:-\${HTTPS_PROXY:-}}
48
+ - no_proxy=\${no_proxy:-\${NO_PROXY:-}}
49
+ # The daemon refuses to start with a proxy variable and without this one.
50
+ - NODE_USE_ENV_PROXY=1
51
+ # Runtime override: a URL pins a proxy, an empty value goes direct, unset keeps the above.
52
+ - GCB_PROXY=\${GCB_PROXY-default}`;
53
+ const environment = fromCode
54
+ ? `
55
+ environment:${egress}`
56
+ : `
57
+ environment:
58
+ # npx needs somewhere writable for its cache; /tmp is a tmpfs above.
59
+ - npm_config_cache=/tmp/npm-cache${egress}`;
60
+ // A bind mount for a path that does not exist becomes a directory, and the broker would then read a
61
+ // directory instead of a token — for every action, not only the personal ones. Emit it once `authorize`
62
+ // has written the file, and say what to run until then.
63
+ const userRefreshMount = fs.existsSync(`${input.dir}/${USER_TOKEN_FILE}`)
64
+ ? `\n # The authorized person's refresh token: the only secret mounted writable, because GitHub\n # rotates it on every exchange.\n - ${input.dir}/${USER_TOKEN_FILE}:${SIDECAR.userTokenPath}:rw`
65
+ : '\n # No user.refresh yet: run `gcb authorize` and render this again. A mount' +
66
+ '\n # for a path that does not exist would become a directory, and the broker would read that.';
67
+ const start = fromCode
68
+ ? `exec node --conditions=development ${CODE_DIR}/packages/host/bin/gcb.js daemon --config ${SIDECAR.configPath}`
69
+ : `exec npx --yes --package '${input.packageSpec}' gcb daemon --config ${SIDECAR.configPath}`;
70
+ const command = ` - sh
71
+ - -c
72
+ - |
73
+ case "\$\${GCB_PROXY-default}" in
74
+ default) ;;
75
+ "") unset http_proxy https_proxy all_proxy no_proxy HTTP_PROXY HTTPS_PROXY ALL_PROXY NO_PROXY NODE_USE_ENV_PROXY ;;
76
+ *) export http_proxy="\$\$GCB_PROXY" https_proxy="\$\$GCB_PROXY" no_proxy=localhost,127.0.0.1 NODE_USE_ENV_PROXY=1 ;;
77
+ esac
78
+ ${start}`;
79
+ const codeMount = fromCode
80
+ ? `
81
+ # The code, read-only, from the commit you reviewed and staged.
82
+ - ${input.code}:${CODE_DIR}:ro`
83
+ : '';
84
+ return `# Docker sidecar for gcb-host@${PACKAGE_VERSION}
85
+ #
86
+ # The broker holds the only long-lived secret, so it runs isolated: the key is mounted
87
+ # read-only, there are no published ports, no capabilities, and the root filesystem is
88
+ # read-only. The socket directory is the only thing shared with the container that pushes.
89
+ #
90
+ # docker compose -f <this file> up -d
91
+ #
92
+ ${origin}
93
+ #
94
+ # IMPORTANT: this file fixes where the sidecar sees things, so broker.config.yml must have been
95
+ # written for it. Create it with:
96
+ #
97
+ # gcb init --mode sidecar --cert <key.pem> --allow <owner/repo> \\
98
+ # --client-id <id> --app-id <id> --dir ${input.dir}
99
+ #
100
+ # which records exactly these paths (both commands read them from one definition):
101
+ #
102
+ # socketPath ${SIDECAR.socketPath}
103
+ # privateKeyPath ${SIDECAR.keyPath}
104
+ # auditPath ${SIDECAR.auditPath}
105
+ #
106
+ # A config written with --mode host records host paths and the sidecar will not start.
107
+ services:
108
+ ${input.name}:
109
+ image: ${input.image}
110
+ container_name: ${input.name}
111
+ restart: unless-stopped
112
+ user: "${input.user}"
113
+ read_only: true
114
+ tmpfs:
115
+ - /tmp
116
+ cap_drop:
117
+ - ALL
118
+ security_opt:
119
+ - no-new-privileges:true${environment}
120
+ command:
121
+ ${command}
122
+ volumes:${codeMount}
123
+ - ${configPath}:${SIDECAR.configPath}:ro
124
+ - ${input.dir}/app.pem:${SIDECAR.keyPath}:ro
125
+ ${userRefreshMount}
126
+ - ${input.dir}/log:${SIDECAR.auditDir}
127
+ - ${socketDir}:${SIDECAR.socketDir}
128
+ # The socket appears at ${socketDir}${SIDECAR.socketPath.slice(SIDECAR.socketDir.length)} on the host. Point the pushing
129
+ # environment at the same socket with:
130
+ # gcb setup --socket <that path as the pusher sees it>
131
+ `;
132
+ }
133
+ /** Usage text for `compose`. */
134
+ export const COMPOSE_USAGE = `Usage: gcb compose [options]
135
+
136
+ Print a Docker sidecar definition for the broker (to stdout). Pair it with a config written by
137
+ \`init --mode sidecar\`, whose paths this file fixes.
138
+
139
+ --dir <path> Host directory holding broker.config.yml, app.pem and log/ (default: the config's directory)
140
+ --config <path> The sidecar's config, checked before anything is printed
141
+ (default: $GIT_BROKER_CONFIG, else ./broker.config.yml)
142
+ --socket-dir <path> Host directory the pushing container already mounts
143
+ --code <path> Run code mounted from this host directory instead of fetching the package
144
+ at start (as \`stage\` exports it); nothing is fetched or built at boot
145
+ --user <uid:gid> UID the sidecar runs as (default: this user)
146
+ --image <name> Base image (default: node:24-slim)
147
+ --package <spec> npm spec to run (default: gcb-host@<version>)
148
+ --name <name> Service and container name (default: gcb)
149
+ -h, --help Show this help
150
+ `;
151
+ /**
152
+ * Run the `compose` command.
153
+ *
154
+ * @param argv - arguments after the command name.
155
+ * @returns the process exit code.
156
+ */
157
+ export function runCompose(argv) {
158
+ const args = parseArgs(argv, { booleans: ['help'] });
159
+ if (args.has('help') || args.has('h')) {
160
+ say(COMPOSE_USAGE);
161
+ return 0;
162
+ }
163
+ const explicitConfig = args.value('config');
164
+ const dirArg = args.value('dir');
165
+ const configPath = path.resolve(explicitConfig !== undefined
166
+ ? resolveConfigPath(explicitConfig)
167
+ : dirArg !== undefined
168
+ ? path.join(dirArg, 'broker.config.yml')
169
+ : resolveConfigPath());
170
+ const dir = path.resolve(dirArg ?? deploymentDir(configPath));
171
+ const socketDir = args.value('socket-dir');
172
+ if (!socketDir) {
173
+ fail('--socket-dir is required (a host directory the pushing container already mounts)');
174
+ }
175
+ // Check before emitting. A config written for the other deployment produces a sidecar that
176
+ // starts and then cannot find its own private key, which is a confusing way to find out.
177
+ if (fs.existsSync(configPath)) {
178
+ const mode = inferMode(loadConfig(configPath));
179
+ if (mode !== 'sidecar') {
180
+ fail(`${configPath} was written with --mode host; re-run ` +
181
+ '`gcb init --mode sidecar ...` before using compose');
182
+ }
183
+ }
184
+ else {
185
+ warn(`no configuration at ${configPath} yet; create it with \`init --mode sidecar\` first`);
186
+ }
187
+ // POSIX only, and guaranteed to exist by the refusal above: the file this prints binds host paths
188
+ // and a socket directory, neither of which works from a Windows host. Asserting it here rather
189
+ // than defaulting to a made-up uid — a wrong `user:` line is a silent misconfiguration.
190
+ const uid = process.getuid?.();
191
+ const gid = process.getgid?.();
192
+ if (uid === undefined || gid === undefined) {
193
+ fail('compose needs a POSIX uid and gid');
194
+ }
195
+ const user = args.value('user') ?? `${uid}:${gid}`;
196
+ say(renderCompose({
197
+ dir,
198
+ configPath,
199
+ socketDir,
200
+ user,
201
+ image: args.value('image') ?? 'node:24-slim',
202
+ packageSpec: args.value('package') ?? `gcb-host@${PACKAGE_VERSION}`,
203
+ name: args.value('name') ?? 'gcb',
204
+ code: args.value('code'),
205
+ }));
206
+ return 0;
207
+ }
208
+ //# sourceMappingURL=compose.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compose.js","sourceRoot":"","sources":["../../src/commands/compose.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ import type { DeploymentMode } from '../config.ts';
2
+ /** The modes, for validation and help text. */
3
+ export declare const DEPLOYMENT_MODES: readonly DeploymentMode[];
4
+ /** Paths recorded in the configuration, as the broker process will resolve them. */
5
+ export interface BrokerPaths {
6
+ readonly socketPath: string;
7
+ readonly privateKeyPath: string;
8
+ readonly auditPath: string;
9
+ }
10
+ /** Files `init` writes on the host. The same in both modes; only the recorded paths differ. */
11
+ export interface HostArtifacts {
12
+ readonly keyPath: string;
13
+ /** Where authorize leaves the refresh token, beside the key. */
14
+ readonly userTokenPath: string;
15
+ readonly configPath: string;
16
+ readonly auditDir: string;
17
+ }
18
+ /** The socket path a host-process deployment uses unless told otherwise. */
19
+ export declare const HOST_SOCKET_PATH_DEFAULT = "/run/git-cred-broker/broker.sock";
20
+ /**
21
+ * The host-side files a deployment needs, regardless of mode.
22
+ *
23
+ * @param dir - the host directory holding the deployment.
24
+ * @returns the paths on the host.
25
+ */
26
+ export declare function hostArtifacts(dir: string): HostArtifacts;
27
+ /**
28
+ * The paths to record in the configuration for one deployment.
29
+ *
30
+ * @param mode - which deployment the broker will run as.
31
+ * @param dir - the host directory holding the deployment.
32
+ * @param hostSocketPath - the socket path a host process should use; ignored in sidecar mode.
33
+ * @returns the paths as the broker will resolve them.
34
+ */
35
+ export declare function brokerPaths(mode: DeploymentMode, dir: string, hostSocketPath?: string): BrokerPaths;
36
+ /**
37
+ * Reject an override that cannot apply in sidecar mode.
38
+ *
39
+ * The compose file fixes these locations, so silently ignoring a different value would produce a
40
+ * broker that starts and then cannot find its own key. Failing loudly is the only kind option.
41
+ *
42
+ * @param flag - the flag name, for the message.
43
+ * @param given - the value the user supplied, if any.
44
+ * @param fixed - the value the deployment requires.
45
+ * @param mode - the chosen deployment.
46
+ * @throws {Error} when sidecar mode was chosen and a conflicting value was supplied.
47
+ */
48
+ export declare function requireFixedInSidecar(flag: string, given: string | undefined, fixed: string, mode: DeploymentMode): void;
49
+ //# sourceMappingURL=deployment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deployment.d.ts","sourceRoot":"","sources":["../../src/commands/deployment.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEnD,+CAA+C;AAC/C,eAAO,MAAM,gBAAgB,EAAE,SAAS,cAAc,EAAwB,CAAC;AAE/E,oFAAoF;AACpF,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,+FAA+F;AAC/F,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,gEAAgE;IAChE,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,4EAA4E;AAC5E,eAAO,MAAM,wBAAwB,qCAAqC,CAAC;AAE3E;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,CAOxD;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,cAAc,EACpB,GAAG,EAAE,MAAM,EACX,cAAc,GAAE,MAAiC,GAChD,WAAW,CAab;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,cAAc,GACnB,IAAI,CAKN"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Where each deployment expects its files, from the **broker's** point of view.
3
+ *
4
+ * This module exists because of one easily-missed fact: the paths inside `broker.config.yml` are
5
+ * resolved by whichever process reads the file. A broker running as a host process sees the
6
+ * host's filesystem, and a broker running as a sidecar sees only what its `volumes:` mount.
7
+ * The same file therefore means two different things, and one config cannot serve both.
8
+ *
9
+ * Both `init --mode` and `compose` derive from here, so the two halves cannot drift apart —
10
+ * which they did once: `init` recorded host paths while `compose` mounted the files at container
11
+ * paths, producing a sidecar that could not read its own key.
12
+ */
13
+ import path from 'node:path';
14
+ import { USER_TOKEN_FILE, SIDECAR } from '../config.js';
15
+ /** The modes, for validation and help text. */
16
+ export const DEPLOYMENT_MODES = ['host', 'sidecar'];
17
+ /** The socket path a host-process deployment uses unless told otherwise. */
18
+ export const HOST_SOCKET_PATH_DEFAULT = '/run/git-cred-broker/broker.sock';
19
+ /**
20
+ * The host-side files a deployment needs, regardless of mode.
21
+ *
22
+ * @param dir - the host directory holding the deployment.
23
+ * @returns the paths on the host.
24
+ */
25
+ export function hostArtifacts(dir) {
26
+ return {
27
+ keyPath: path.join(dir, 'app.pem'),
28
+ userTokenPath: path.join(dir, USER_TOKEN_FILE),
29
+ configPath: path.join(dir, 'broker.config.yml'),
30
+ auditDir: path.join(dir, 'log'),
31
+ };
32
+ }
33
+ /**
34
+ * The paths to record in the configuration for one deployment.
35
+ *
36
+ * @param mode - which deployment the broker will run as.
37
+ * @param dir - the host directory holding the deployment.
38
+ * @param hostSocketPath - the socket path a host process should use; ignored in sidecar mode.
39
+ * @returns the paths as the broker will resolve them.
40
+ */
41
+ export function brokerPaths(mode, dir, hostSocketPath = HOST_SOCKET_PATH_DEFAULT) {
42
+ if (mode === 'sidecar') {
43
+ return {
44
+ socketPath: SIDECAR.socketPath,
45
+ privateKeyPath: SIDECAR.keyPath,
46
+ auditPath: SIDECAR.auditPath,
47
+ };
48
+ }
49
+ return {
50
+ socketPath: hostSocketPath,
51
+ privateKeyPath: path.join(dir, 'app.pem'),
52
+ auditPath: path.join(dir, 'log', 'audit.jsonl'),
53
+ };
54
+ }
55
+ /**
56
+ * Reject an override that cannot apply in sidecar mode.
57
+ *
58
+ * The compose file fixes these locations, so silently ignoring a different value would produce a
59
+ * broker that starts and then cannot find its own key. Failing loudly is the only kind option.
60
+ *
61
+ * @param flag - the flag name, for the message.
62
+ * @param given - the value the user supplied, if any.
63
+ * @param fixed - the value the deployment requires.
64
+ * @param mode - the chosen deployment.
65
+ * @throws {Error} when sidecar mode was chosen and a conflicting value was supplied.
66
+ */
67
+ export function requireFixedInSidecar(flag, given, fixed, mode) {
68
+ if (mode !== 'sidecar' || given === undefined || given === fixed)
69
+ return;
70
+ throw new Error(`${flag} must be ${fixed} with --mode sidecar, because the compose file mounts it there; got ${given}`);
71
+ }
72
+ //# sourceMappingURL=deployment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deployment.js","sourceRoot":"","sources":["../../src/commands/deployment.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAGxD,+CAA+C;AAC/C,MAAM,CAAC,MAAM,gBAAgB,GAA8B,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AAkB/E,4EAA4E;AAC5E,MAAM,CAAC,MAAM,wBAAwB,GAAG,kCAAkC,CAAC;AAE3E;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,GAAW;IACvC,OAAO;QACL,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC;QAClC,aAAa,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,eAAe,CAAC;QAC9C,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,mBAAmB,CAAC;QAC/C,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC;KAChC,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,WAAW,CACzB,IAAoB,EACpB,GAAW,EACX,cAAc,GAAW,wBAAwB;IAEjD,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,OAAO;YACL,UAAU,EAAE,OAAO,CAAC,UAAU;YAC9B,cAAc,EAAE,OAAO,CAAC,OAAO;YAC/B,SAAS,EAAE,OAAO,CAAC,SAAS;SAC7B,CAAC;IACJ,CAAC;IACD,OAAO;QACL,UAAU,EAAE,cAAc;QAC1B,cAAc,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC;QACzC,SAAS,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,aAAa,CAAC;KAChD,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,qBAAqB,CACnC,IAAY,EACZ,KAAyB,EACzB,KAAa,EACb,IAAoB;IAEpB,IAAI,IAAI,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,KAAK;QAAE,OAAO;IACzE,MAAM,IAAI,KAAK,CACb,GAAG,IAAI,YAAY,KAAK,uEAAuE,KAAK,EAAE,CACvG,CAAC;AACJ,CAAC"}
@@ -0,0 +1,4 @@
1
+ /** Usage text for `diagnose`. */
2
+ export declare const DIAGNOSE_USAGE = "Usage: gcb diagnose [options]\n\nAsk GitHub what this app can actually see: identity, permissions, installations and the\nrepositories each installation can reach. Never prints a key or a token.\n\n --config <path> The broker configuration to inspect (default: $GIT_BROKER_CONFIG, else ./broker.config.yml)\n --host <host> Which host block to inspect (default: the first github-app one)\n --proxy <url> Use this proxy for this command\n --no-proxy Reach GitHub directly for this command\n -h, --help Show this help\n";
3
+ export declare function runDiagnose(argv: readonly string[]): Promise<number>;
4
+ //# sourceMappingURL=diagnose.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diagnose.d.ts","sourceRoot":"","sources":["../../src/commands/diagnose.ts"],"names":[],"mappings":"AAqCA,iCAAiC;AACjC,eAAO,MAAM,cAAc,iiBAU1B,CAAC;AAUF,wBAAsB,WAAW,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAwL1E"}