@geekmidas/cli 10.0.0-alpha.66 → 10.0.0-alpha.68
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +110 -178
- package/dist/{HostingerProvider-CY_VpVud.mjs → HostingerProvider-D85ayzTi.mjs} +2 -2
- package/dist/{HostingerProvider-CY_VpVud.mjs.map → HostingerProvider-D85ayzTi.mjs.map} +1 -1
- package/dist/{HostingerProvider-BznqRC2S.cjs → HostingerProvider-dxkwynSs.cjs} +2 -2
- package/dist/{HostingerProvider-BznqRC2S.cjs.map → HostingerProvider-dxkwynSs.cjs.map} +1 -1
- package/dist/{LegacyStateStore-BFcTT2QL.cjs → LegacyStateStore-BZiO9-Lw.cjs} +2 -2
- package/dist/{LegacyStateStore-BFcTT2QL.cjs.map → LegacyStateStore-BZiO9-Lw.cjs.map} +1 -1
- package/dist/{LegacyStateStore-CFheFo62.mjs → LegacyStateStore-C39UKwmf.mjs} +2 -2
- package/dist/{LegacyStateStore-CFheFo62.mjs.map → LegacyStateStore-C39UKwmf.mjs.map} +1 -1
- package/dist/{LocalStateStore-BVUexSvu.cjs → LocalStateStore-DHVsZLyF.cjs} +2 -2
- package/dist/{LocalStateStore-BVUexSvu.cjs.map → LocalStateStore-DHVsZLyF.cjs.map} +1 -1
- package/dist/{LocalStateStore-Bm3x9oOg.mjs → LocalStateStore-Dk3Wbvzk.mjs} +2 -2
- package/dist/{LocalStateStore-Bm3x9oOg.mjs.map → LocalStateStore-Dk3Wbvzk.mjs.map} +1 -1
- package/dist/{S3StateStore-CoXBhILd.cjs → S3StateStore--4tYVMAL.cjs} +2 -2
- package/dist/{S3StateStore-CoXBhILd.cjs.map → S3StateStore--4tYVMAL.cjs.map} +1 -1
- package/dist/{S3StateStore-BFHuctWM.mjs → S3StateStore-B_ZF9eHt.mjs} +2 -2
- package/dist/{S3StateStore-BFHuctWM.mjs.map → S3StateStore-B_ZF9eHt.mjs.map} +1 -1
- package/dist/{SSMStateStore-BqCA9TP2.cjs → SSMStateStore-ACZ8hRvZ.cjs} +2 -2
- package/dist/{SSMStateStore-BqCA9TP2.cjs.map → SSMStateStore-ACZ8hRvZ.cjs.map} +1 -1
- package/dist/{SSMStateStore-C1kvHScY.mjs → SSMStateStore-DjPuKqrm.mjs} +2 -2
- package/dist/{SSMStateStore-C1kvHScY.mjs.map → SSMStateStore-DjPuKqrm.mjs.map} +1 -1
- package/dist/{bundler-Dvaiud0h.mjs → bundler-C-w7aGoZ.mjs} +20 -5
- package/dist/bundler-C-w7aGoZ.mjs.map +1 -0
- package/dist/{bundler-orRqUJNZ.cjs → bundler-C1pnNrR0.cjs} +20 -5
- package/dist/bundler-C1pnNrR0.cjs.map +1 -0
- package/dist/{cli-D1JkkOwg.cjs → cli-BLyD4c0O.cjs} +14 -5
- package/dist/cli-BLyD4c0O.cjs.map +1 -0
- package/dist/{cli-CfHm0AO7.mjs → cli-Ce4pLL5O.mjs} +14 -5
- package/dist/cli-Ce4pLL5O.mjs.map +1 -0
- package/dist/{compose-CNQL8BnO.mjs → compose-DFxuYhn6.mjs} +291 -152
- package/dist/compose-DFxuYhn6.mjs.map +1 -0
- package/dist/{compose-jrLy7KGO.cjs → compose-DvsgumY2.cjs} +298 -159
- package/dist/compose-DvsgumY2.cjs.map +1 -0
- package/dist/{config-4XFxX1al.d.cts → config-BnA0kPTY.d.cts} +3 -3
- package/dist/{config-4XFxX1al.d.cts.map → config-BnA0kPTY.d.cts.map} +1 -1
- package/dist/{config-UtKzhpFb.d.mts → config-CxEp_g7j.d.mts} +3 -3
- package/dist/{config-UtKzhpFb.d.mts.map → config-CxEp_g7j.d.mts.map} +1 -1
- package/dist/config.d.cts +3 -3
- package/dist/config.d.mts +3 -3
- package/dist/{credentials-Bzbg23n8.cjs → credentials-BVs7mrtw.cjs} +239 -3
- package/dist/credentials-BVs7mrtw.cjs.map +1 -0
- package/dist/{credentials-Dq1MnWQf.mjs → credentials-pAtB70k3.mjs} +223 -5
- package/dist/credentials-pAtB70k3.mjs.map +1 -0
- package/dist/deploy/public.cjs +21 -17
- package/dist/deploy/public.cjs.map +1 -1
- package/dist/deploy/public.d.cts +15 -4
- package/dist/deploy/public.d.cts.map +1 -1
- package/dist/deploy/public.d.mts +15 -4
- package/dist/deploy/public.d.mts.map +1 -1
- package/dist/deploy/public.mjs +10 -10
- package/dist/{deploy-B7aJi_Ah.cjs → deploy-C08GONYW.cjs} +14 -10
- package/dist/deploy-C08GONYW.cjs.map +1 -0
- package/dist/{deploy-DWn7YfIc.mjs → deploy-D-EVOWvu.mjs} +11 -7
- package/dist/deploy-D-EVOWvu.mjs.map +1 -0
- package/dist/{dokploy-B4LcYG1E.cjs → dokploy-CZ6nfSV9.cjs} +2 -2
- package/dist/{dokploy-B4LcYG1E.cjs.map → dokploy-CZ6nfSV9.cjs.map} +1 -1
- package/dist/{dokploy-DalwLML5.mjs → dokploy-DU-XJsoT.mjs} +2 -2
- package/dist/{dokploy-DalwLML5.mjs.map → dokploy-DU-XJsoT.mjs.map} +1 -1
- package/dist/{encryption-3KlGS1sl.cjs → encryption-CCcBSLZs.cjs} +2 -2
- package/dist/{encryption-3KlGS1sl.cjs.map → encryption-CCcBSLZs.cjs.map} +1 -1
- package/dist/{encryption-CPfENwWO.mjs → encryption-DSiLau1-.mjs} +2 -2
- package/dist/{encryption-CPfENwWO.mjs.map → encryption-DSiLau1-.mjs.map} +1 -1
- package/dist/{engine-DR6KurWX.cjs → engine-BM9Dy_DT.cjs} +141 -209
- package/dist/engine-BM9Dy_DT.cjs.map +1 -0
- package/dist/{engine-CunvOG3s.mjs → engine-llWnMZH5.mjs} +137 -205
- package/dist/engine-llWnMZH5.mjs.map +1 -0
- package/dist/{github-BJ5gh9bw.cjs → github-ZoMam0OH.cjs} +2 -2
- package/dist/{github-BJ5gh9bw.cjs.map → github-ZoMam0OH.cjs.map} +1 -1
- package/dist/{github-MXA9v7Ge.mjs → github-yy6WpHIl.mjs} +2 -2
- package/dist/{github-MXA9v7Ge.mjs.map → github-yy6WpHIl.mjs.map} +1 -1
- package/dist/{index-DD1FfUHh.d.mts → index-CkX89m6S.d.mts} +2 -2
- package/dist/{index-CyApaHVe.d.cts.map → index-CkX89m6S.d.mts.map} +1 -1
- package/dist/{index-CyApaHVe.d.cts → index-EvZBkycu.d.cts} +2 -2
- package/dist/{index-DD1FfUHh.d.mts.map → index-EvZBkycu.d.cts.map} +1 -1
- package/dist/index.cjs +59 -70
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +55 -66
- package/dist/index.mjs.map +1 -1
- package/dist/{migrate-BJBcm4sG.mjs → migrate-BWPuwiXk.mjs} +2 -2
- package/dist/{migrate-BJBcm4sG.mjs.map → migrate-BWPuwiXk.mjs.map} +1 -1
- package/dist/{migrate-CF4J4GQS.cjs → migrate-tOwxQBTI.cjs} +2 -2
- package/dist/{migrate-CF4J4GQS.cjs.map → migrate-tOwxQBTI.cjs.map} +1 -1
- package/dist/openapi.d.cts +1 -1
- package/dist/openapi.d.mts +1 -1
- package/dist/reconcile/public.d.cts +1 -1
- package/dist/reconcile/public.d.mts +1 -1
- package/dist/{resolve-CVmch0wI.cjs → resolve-BymzGtuT.cjs} +4 -4
- package/dist/{resolve-CVmch0wI.cjs.map → resolve-BymzGtuT.cjs.map} +1 -1
- package/dist/{resolve-Db0tNlrW.mjs → resolve-pIvXlZ1h.mjs} +4 -4
- package/dist/{resolve-Db0tNlrW.mjs.map → resolve-pIvXlZ1h.mjs.map} +1 -1
- package/dist/{rollback-DvAo4OKS.mjs → rollback-ZlsbQOvj.mjs} +5 -6
- package/dist/{rollback-DvAo4OKS.mjs.map → rollback-ZlsbQOvj.mjs.map} +1 -1
- package/dist/{rollback-DxJK84hy.cjs → rollback-zdfi6Reg.cjs} +6 -7
- package/dist/{rollback-DxJK84hy.cjs.map → rollback-zdfi6Reg.cjs.map} +1 -1
- package/dist/run-BJLIerMM.mjs +133 -0
- package/dist/run-BJLIerMM.mjs.map +1 -0
- package/dist/run-fVZ-rLnc.cjs +174 -0
- package/dist/run-fVZ-rLnc.cjs.map +1 -0
- package/dist/{runtime-BRqFOvPM.d.cts → runtime-B3jNgeP-.d.mts} +2 -2
- package/dist/{runtime-BRqFOvPM.d.cts.map → runtime-B3jNgeP-.d.mts.map} +1 -1
- package/dist/{runtime-CEk5n4VH.d.mts → runtime-DWThEP-r.d.cts} +2 -2
- package/dist/{runtime-CEk5n4VH.d.mts.map → runtime-DWThEP-r.d.cts.map} +1 -1
- package/dist/{sniffer-BAQhLEsI.mjs → sniffer-BdFeLSmA.mjs} +8 -60
- package/dist/sniffer-BdFeLSmA.mjs.map +1 -0
- package/dist/{sniffer-5xmrkh2a.cjs → sniffer-D66HIvi9.cjs} +7 -77
- package/dist/sniffer-D66HIvi9.cjs.map +1 -0
- package/dist/{sst-B2HbrDR3.mjs → sst-DRi3H5TB.mjs} +3 -3
- package/dist/{sst-B2HbrDR3.mjs.map → sst-DRi3H5TB.mjs.map} +1 -1
- package/dist/{sst-DcfsVH6x.cjs → sst-hBrKouBy.cjs} +5 -5
- package/dist/{sst-DcfsVH6x.cjs.map → sst-hBrKouBy.cjs.map} +1 -1
- package/dist/{state-uuYqnRVO.mjs → state-Bn5JDMFT.mjs} +1717 -1334
- package/dist/state-Bn5JDMFT.mjs.map +1 -0
- package/dist/{state-Y6ATakMX.cjs → state-CHZqCDRC.cjs} +2863 -2354
- package/dist/state-CHZqCDRC.cjs.map +1 -0
- package/dist/target/public.cjs +1 -1
- package/dist/target/public.d.cts +2 -2
- package/dist/target/public.d.mts +2 -2
- package/dist/target/public.mjs +1 -1
- package/dist/{terminal-DUhcZ1ND.mjs → terminal-C3cv-OQu.mjs} +2 -2
- package/dist/{terminal-DUhcZ1ND.mjs.map → terminal-C3cv-OQu.mjs.map} +1 -1
- package/dist/{terminal-CHy_MdjG.cjs → terminal-DjzgLu6m.cjs} +2 -2
- package/dist/{terminal-CHy_MdjG.cjs.map → terminal-DjzgLu6m.cjs.map} +1 -1
- package/dist/{types-CSw2w69a.d.mts → types-CZdCxUCK.d.cts} +64 -2
- package/dist/types-CZdCxUCK.d.cts.map +1 -0
- package/dist/{types-D5WIxRDP.d.cts → types-Da0E3Qv7.d.mts} +63 -3
- package/dist/types-Da0E3Qv7.d.mts.map +1 -0
- package/dist/workspace/index.d.cts +2 -2
- package/dist/workspace/index.d.mts +2 -2
- package/package.json +13 -13
- package/dist/bundler-Dvaiud0h.mjs.map +0 -1
- package/dist/bundler-orRqUJNZ.cjs.map +0 -1
- package/dist/cli-CfHm0AO7.mjs.map +0 -1
- package/dist/cli-D1JkkOwg.cjs.map +0 -1
- package/dist/compose-CNQL8BnO.mjs.map +0 -1
- package/dist/compose-jrLy7KGO.cjs.map +0 -1
- package/dist/credentials-Bzbg23n8.cjs.map +0 -1
- package/dist/credentials-Dq1MnWQf.mjs.map +0 -1
- package/dist/deploy-B7aJi_Ah.cjs.map +0 -1
- package/dist/deploy-DWn7YfIc.mjs.map +0 -1
- package/dist/engine-CunvOG3s.mjs.map +0 -1
- package/dist/engine-DR6KurWX.cjs.map +0 -1
- package/dist/local-BdH4XN0E.cjs +0 -409
- package/dist/local-BdH4XN0E.cjs.map +0 -1
- package/dist/local-CWR4Y_kW.mjs +0 -350
- package/dist/local-CWR4Y_kW.mjs.map +0 -1
- package/dist/sniffer-5xmrkh2a.cjs.map +0 -1
- package/dist/sniffer-BAQhLEsI.mjs.map +0 -1
- package/dist/state-Y6ATakMX.cjs.map +0 -1
- package/dist/state-uuYqnRVO.mjs.map +0 -1
- package/dist/types-CSw2w69a.d.mts.map +0 -1
- package/dist/types-D5WIxRDP.d.cts.map +0 -1
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
const
|
|
1
|
+
const require_run = require("./run-fVZ-rLnc.cjs");
|
|
2
|
+
const require_sandbox = require("./sandbox-CL--ZDui.cjs");
|
|
2
3
|
let node_path = require("node:path");
|
|
3
4
|
let node_fs = require("node:fs");
|
|
4
5
|
let node_fs_promises = require("node:fs/promises");
|
|
6
|
+
let node_os = require("node:os");
|
|
7
|
+
let node_child_process = require("node:child_process");
|
|
5
8
|
//#region src/auth/credentials.ts
|
|
6
9
|
/**
|
|
7
10
|
* Get the path to the credentials directory
|
|
8
11
|
*/
|
|
9
12
|
function getCredentialsDir(options) {
|
|
10
13
|
if (options?.home) return options.home;
|
|
11
|
-
return options?.root ? (0, node_path.join)(options.root, ".gkm") :
|
|
14
|
+
return options?.root ? (0, node_path.join)(options.root, ".gkm") : require_run.gkmHome();
|
|
12
15
|
}
|
|
13
16
|
/**
|
|
14
17
|
* Get the path to the credentials file
|
|
@@ -124,6 +127,221 @@ async function getHostingerToken(options) {
|
|
|
124
127
|
return (await readCredentials(options)).hostinger?.token ?? null;
|
|
125
128
|
}
|
|
126
129
|
//#endregion
|
|
130
|
+
//#region src/sandbox/env.ts
|
|
131
|
+
/**
|
|
132
|
+
* What of the host's environment a local sandbox passes on.
|
|
133
|
+
*
|
|
134
|
+
* An allowlist, not a denylist: a credential's variable has any name its
|
|
135
|
+
* owner chose (`DOKPLOY_TOKEN`, `AWS_SECRET_ACCESS_KEY`, `MY_VAULT_TOKEN`), so
|
|
136
|
+
* the only safe default is to name what a program needs to *run* and pass
|
|
137
|
+
* nothing else.
|
|
138
|
+
*/
|
|
139
|
+
/**
|
|
140
|
+
* Variables a command needs to find programs, write temp files, print in the
|
|
141
|
+
* user's locale and reach the network through the host's proxy — and nothing
|
|
142
|
+
* that authenticates as anyone.
|
|
143
|
+
*
|
|
144
|
+
* Deliberately left out:
|
|
145
|
+
* - `NODE_OPTIONS`: it can `--require` or `--import` code into every node
|
|
146
|
+
* process, and CI often holds `--import tsx` there. A step that needs a
|
|
147
|
+
* loader passes its own.
|
|
148
|
+
* - `NODE_AUTH_TOKEN`, `NPM_TOKEN`, `npm_config_*`: registry tokens
|
|
149
|
+
* (`actions/setup-node` writes `NODE_AUTH_TOKEN`), which is why there is no
|
|
150
|
+
* `NODE_*` wildcard.
|
|
151
|
+
* - `AWS_*`, `DOCKER_*`, `DOKPLOY_*`, `GITHUB_TOKEN`, `TURBO_TOKEN`, `GKM_*`:
|
|
152
|
+
* credentials, or where the deploy keeps them.
|
|
153
|
+
* - `SHELL`, `EDITOR` and the rest of a login session: nothing here starts a
|
|
154
|
+
* shell.
|
|
155
|
+
*
|
|
156
|
+
* Proxies are passed: behind one, nothing installs or builds without it. A
|
|
157
|
+
* proxy URL with a password in it reaches the command too — a host that keeps
|
|
158
|
+
* one there and builds code it does not trust should pass a sandbox of its
|
|
159
|
+
* own.
|
|
160
|
+
*/
|
|
161
|
+
const SANDBOX_ENV_ALLOWLIST = [
|
|
162
|
+
"PATH",
|
|
163
|
+
"HOME",
|
|
164
|
+
"USER",
|
|
165
|
+
"LOGNAME",
|
|
166
|
+
"TMPDIR",
|
|
167
|
+
"TMP",
|
|
168
|
+
"TEMP",
|
|
169
|
+
"LANG",
|
|
170
|
+
"LANGUAGE",
|
|
171
|
+
"TZ",
|
|
172
|
+
/^LC_[A-Z]+$/,
|
|
173
|
+
"TERM",
|
|
174
|
+
"COLORTERM",
|
|
175
|
+
"NO_COLOR",
|
|
176
|
+
"FORCE_COLOR",
|
|
177
|
+
"CI",
|
|
178
|
+
"NODE_ENV",
|
|
179
|
+
"NODE_EXTRA_CA_CERTS",
|
|
180
|
+
"COREPACK_HOME",
|
|
181
|
+
"PNPM_HOME",
|
|
182
|
+
"XDG_CACHE_HOME",
|
|
183
|
+
"XDG_DATA_HOME",
|
|
184
|
+
"HTTP_PROXY",
|
|
185
|
+
"HTTPS_PROXY",
|
|
186
|
+
"NO_PROXY",
|
|
187
|
+
"http_proxy",
|
|
188
|
+
"https_proxy",
|
|
189
|
+
"no_proxy",
|
|
190
|
+
"SystemRoot",
|
|
191
|
+
"SYSTEMROOT",
|
|
192
|
+
"windir",
|
|
193
|
+
"WINDIR",
|
|
194
|
+
"ComSpec",
|
|
195
|
+
"COMSPEC",
|
|
196
|
+
"PATHEXT",
|
|
197
|
+
"USERPROFILE",
|
|
198
|
+
"APPDATA",
|
|
199
|
+
"LOCALAPPDATA"
|
|
200
|
+
];
|
|
201
|
+
function allowed(name) {
|
|
202
|
+
return SANDBOX_ENV_ALLOWLIST.some((entry) => typeof entry === "string" ? entry === name : entry.test(name));
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* The allowlisted part of `source`, plus each of `extra` the host chose to
|
|
206
|
+
* pass on by name.
|
|
207
|
+
*/
|
|
208
|
+
function allowlistedEnv(source = process.env, extra = []) {
|
|
209
|
+
const env = {};
|
|
210
|
+
for (const [name, value] of Object.entries(source)) {
|
|
211
|
+
if (value === void 0) continue;
|
|
212
|
+
if (allowed(name) || extra.includes(name)) env[name] = value;
|
|
213
|
+
}
|
|
214
|
+
return env;
|
|
215
|
+
}
|
|
216
|
+
//#endregion
|
|
217
|
+
//#region src/sandbox/local.ts
|
|
218
|
+
/** How long a timed-out command has to exit on SIGTERM before SIGKILL. */
|
|
219
|
+
const KILL_GRACE_MS = 5e3;
|
|
220
|
+
/** Where `output` sends a child's streams. */
|
|
221
|
+
const STDIO = {
|
|
222
|
+
capture: [
|
|
223
|
+
"ignore",
|
|
224
|
+
"pipe",
|
|
225
|
+
"pipe"
|
|
226
|
+
],
|
|
227
|
+
inherit: "inherit",
|
|
228
|
+
stderr: [
|
|
229
|
+
"ignore",
|
|
230
|
+
2,
|
|
231
|
+
2
|
|
232
|
+
],
|
|
233
|
+
ignore: "ignore"
|
|
234
|
+
};
|
|
235
|
+
/**
|
|
236
|
+
* A child process on this machine, as this user, with an allowlisted
|
|
237
|
+
* environment.
|
|
238
|
+
*
|
|
239
|
+
* What it withholds from the command: the host's environment beyond
|
|
240
|
+
* {@link SANDBOX_ENV_ALLOWLIST}, a shell, a working directory outside the
|
|
241
|
+
* project, and any time past the timeout. What it does not: the host's
|
|
242
|
+
* filesystem, which the command can read and write as the user running the
|
|
243
|
+
* deploy. So it is not `isolating`, and it is the default for projects the
|
|
244
|
+
* host already trusts — the developer's own, a team's own CI. Code nobody
|
|
245
|
+
* vetted belongs in a sandbox that is.
|
|
246
|
+
*
|
|
247
|
+
* A timed-out command is sent SIGTERM, then SIGKILL; processes it started
|
|
248
|
+
* itself are its to stop.
|
|
249
|
+
*/
|
|
250
|
+
var LocalSandbox = class {
|
|
251
|
+
root;
|
|
252
|
+
isolating = false;
|
|
253
|
+
env;
|
|
254
|
+
constructor(options) {
|
|
255
|
+
this.root = (0, node_path.resolve)(options.root);
|
|
256
|
+
this.env = {
|
|
257
|
+
...allowlistedEnv(options.hostEnv ?? process.env, options.passEnv),
|
|
258
|
+
...options.home ? {
|
|
259
|
+
HOME: options.home,
|
|
260
|
+
USERPROFILE: options.home
|
|
261
|
+
} : {}
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
async exec(command, args, options) {
|
|
265
|
+
const cwd = require_sandbox.confineCwd(this.root, options.cwd);
|
|
266
|
+
require_sandbox.assertSecretNames(options.secrets);
|
|
267
|
+
options.signal?.throwIfAborted();
|
|
268
|
+
const secretsDir = await mountSecrets(options.secrets);
|
|
269
|
+
try {
|
|
270
|
+
return await spawnConfined(command, args, {
|
|
271
|
+
...options,
|
|
272
|
+
cwd,
|
|
273
|
+
env: secretsDir ? {
|
|
274
|
+
...options.env,
|
|
275
|
+
[require_sandbox.SECRETS_DIR_ENV]: secretsDir
|
|
276
|
+
} : options.env
|
|
277
|
+
});
|
|
278
|
+
} finally {
|
|
279
|
+
if (secretsDir) await (0, node_fs_promises.rm)(secretsDir, {
|
|
280
|
+
recursive: true,
|
|
281
|
+
force: true
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
/**
|
|
287
|
+
* Each secret as a 0600 file in a fresh 0700 directory outside the project —
|
|
288
|
+
* where nothing the build writes, packs or commits will pick it up.
|
|
289
|
+
*/
|
|
290
|
+
async function mountSecrets(secrets) {
|
|
291
|
+
if (!secrets || Object.keys(secrets).length === 0) return void 0;
|
|
292
|
+
const dir = await (0, node_fs_promises.mkdtemp)((0, node_path.join)((0, node_os.tmpdir)(), "gkm-secrets-"));
|
|
293
|
+
for (const [name, value] of Object.entries(secrets)) await (0, node_fs_promises.writeFile)((0, node_path.join)(dir, name), value, { mode: 384 });
|
|
294
|
+
return dir;
|
|
295
|
+
}
|
|
296
|
+
function spawnConfined(command, args, options) {
|
|
297
|
+
const { output = "capture", signal, timeoutMs } = options;
|
|
298
|
+
return new Promise((resolvePromise, reject) => {
|
|
299
|
+
const child = (0, node_child_process.spawn)(command, [...args], {
|
|
300
|
+
cwd: options.cwd,
|
|
301
|
+
env: { ...options.env },
|
|
302
|
+
stdio: STDIO[output],
|
|
303
|
+
shell: false,
|
|
304
|
+
...signal ? { signal } : {}
|
|
305
|
+
});
|
|
306
|
+
let stdout = "";
|
|
307
|
+
let stderr = "";
|
|
308
|
+
child.stdout?.on("data", (chunk) => {
|
|
309
|
+
stdout += chunk.toString();
|
|
310
|
+
});
|
|
311
|
+
child.stderr?.on("data", (chunk) => {
|
|
312
|
+
stderr += chunk.toString();
|
|
313
|
+
});
|
|
314
|
+
let timedOut = false;
|
|
315
|
+
let killer;
|
|
316
|
+
const timer = setTimeout(() => {
|
|
317
|
+
timedOut = true;
|
|
318
|
+
child.kill("SIGTERM");
|
|
319
|
+
killer = setTimeout(() => child.kill("SIGKILL"), KILL_GRACE_MS);
|
|
320
|
+
killer.unref?.();
|
|
321
|
+
}, timeoutMs);
|
|
322
|
+
timer.unref?.();
|
|
323
|
+
const settle = () => {
|
|
324
|
+
clearTimeout(timer);
|
|
325
|
+
if (killer) clearTimeout(killer);
|
|
326
|
+
};
|
|
327
|
+
child.on("error", (error) => {
|
|
328
|
+
settle();
|
|
329
|
+
reject(signal?.aborted ? signal.reason : error);
|
|
330
|
+
});
|
|
331
|
+
child.on("close", (code, killedBy) => {
|
|
332
|
+
settle();
|
|
333
|
+
if (signal?.aborted) reject(signal.reason);
|
|
334
|
+
else if (timedOut) reject(new require_run.CommandTimedOut(command, args, timeoutMs));
|
|
335
|
+
else resolvePromise({
|
|
336
|
+
exitCode: code,
|
|
337
|
+
signal: killedBy ?? null,
|
|
338
|
+
stdout,
|
|
339
|
+
stderr
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
//#endregion
|
|
127
345
|
//#region src/deploy/credentials.ts
|
|
128
346
|
/**
|
|
129
347
|
* How a deploy gets the credentials it acts with.
|
|
@@ -228,12 +446,30 @@ function storedCredentials(options = {}) {
|
|
|
228
446
|
} };
|
|
229
447
|
}
|
|
230
448
|
//#endregion
|
|
449
|
+
Object.defineProperty(exports, "LocalSandbox", {
|
|
450
|
+
enumerable: true,
|
|
451
|
+
get: function() {
|
|
452
|
+
return LocalSandbox;
|
|
453
|
+
}
|
|
454
|
+
});
|
|
231
455
|
Object.defineProperty(exports, "MissingCredential", {
|
|
232
456
|
enumerable: true,
|
|
233
457
|
get: function() {
|
|
234
458
|
return MissingCredential;
|
|
235
459
|
}
|
|
236
460
|
});
|
|
461
|
+
Object.defineProperty(exports, "SANDBOX_ENV_ALLOWLIST", {
|
|
462
|
+
enumerable: true,
|
|
463
|
+
get: function() {
|
|
464
|
+
return SANDBOX_ENV_ALLOWLIST;
|
|
465
|
+
}
|
|
466
|
+
});
|
|
467
|
+
Object.defineProperty(exports, "allowlistedEnv", {
|
|
468
|
+
enumerable: true,
|
|
469
|
+
get: function() {
|
|
470
|
+
return allowlistedEnv;
|
|
471
|
+
}
|
|
472
|
+
});
|
|
237
473
|
Object.defineProperty(exports, "chainCredentials", {
|
|
238
474
|
enumerable: true,
|
|
239
475
|
get: function() {
|
|
@@ -295,4 +531,4 @@ Object.defineProperty(exports, "storedCredentials", {
|
|
|
295
531
|
}
|
|
296
532
|
});
|
|
297
533
|
|
|
298
|
-
//# sourceMappingURL=credentials-
|
|
534
|
+
//# sourceMappingURL=credentials-BVs7mrtw.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials-BVs7mrtw.cjs","names":["join","gkmHome","existsSync","readFile","writeFile","resolve","confineCwd","SECRETS_DIR_ENV","rm","mkdtemp","join","tmpdir","writeFile","spawn","CommandTimedOut"],"sources":["../src/auth/credentials.ts","../src/sandbox/env.ts","../src/sandbox/local.ts","../src/deploy/credentials.ts"],"sourcesContent":["import { existsSync, mkdirSync } from 'node:fs';\nimport { readFile, unlink, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { gkmHome } from '../home';\n\n/**\n * Stored credentials for various services\n */\nexport interface StoredCredentials {\n\tdokploy?: {\n\t\t/** API token */\n\t\ttoken: string;\n\t\t/** Dokploy endpoint URL */\n\t\tendpoint: string;\n\t\t/** Registry ID in Dokploy (for Docker image pulls) */\n\t\tregistryId?: string;\n\t\t/** When the credentials were stored */\n\t\tstoredAt: string;\n\t};\n\thostinger?: {\n\t\t/** API token from hpanel.hostinger.com/profile/api */\n\t\ttoken: string;\n\t\t/** When the credentials were stored */\n\t\tstoredAt: string;\n\t};\n}\n\n/**\n * Options for credential operations\n */\nexport interface CredentialOptions {\n\t/**\n\t * A directory whose `.gkm` holds the credentials. Default: the CLI's home\n\t * (`GKM_HOME`, else `~/.gkm`).\n\t */\n\troot?: string;\n\t/** The CLI's home itself, holding `credentials.json`. Wins over `root`. */\n\thome?: string;\n}\n\n/**\n * Get the path to the credentials directory\n */\nexport function getCredentialsDir(options?: CredentialOptions): string {\n\tif (options?.home) return options.home;\n\treturn options?.root ? join(options.root, '.gkm') : gkmHome();\n}\n\n/**\n * Get the path to the credentials file\n */\nexport function getCredentialsPath(options?: CredentialOptions): string {\n\treturn join(getCredentialsDir(options), 'credentials.json');\n}\n\n/**\n * Ensure the credentials directory exists\n */\nfunction ensureCredentialsDir(options?: CredentialOptions): void {\n\tconst dir = getCredentialsDir(options);\n\tif (!existsSync(dir)) {\n\t\tmkdirSync(dir, { recursive: true, mode: 0o700 });\n\t}\n}\n\n/**\n * Read stored credentials from disk\n */\nexport async function readCredentials(\n\toptions?: CredentialOptions,\n): Promise<StoredCredentials> {\n\tconst path = getCredentialsPath(options);\n\n\tif (!existsSync(path)) {\n\t\treturn {};\n\t}\n\n\ttry {\n\t\tconst content = await readFile(path, 'utf-8');\n\t\treturn JSON.parse(content) as StoredCredentials;\n\t} catch {\n\t\treturn {};\n\t}\n}\n\n/**\n * Write credentials to disk\n */\nexport async function writeCredentials(\n\tcredentials: StoredCredentials,\n\toptions?: CredentialOptions,\n): Promise<void> {\n\tensureCredentialsDir(options);\n\tconst path = getCredentialsPath(options);\n\n\tawait writeFile(path, JSON.stringify(credentials, null, 2), {\n\t\tmode: 0o600, // Owner read/write only\n\t});\n}\n\n/**\n * Store Dokploy credentials\n */\nexport async function storeDokployCredentials(\n\ttoken: string,\n\tendpoint: string,\n\toptions?: CredentialOptions,\n): Promise<void> {\n\tconst credentials = await readCredentials(options);\n\n\tcredentials.dokploy = {\n\t\ttoken,\n\t\tendpoint,\n\t\tstoredAt: new Date().toISOString(),\n\t};\n\n\tawait writeCredentials(credentials, options);\n}\n\n/**\n * Get stored Dokploy credentials\n */\nexport async function getDokployCredentials(\n\toptions?: CredentialOptions,\n): Promise<{\n\ttoken: string;\n\tendpoint: string;\n\tregistryId?: string;\n} | null> {\n\tconst credentials = await readCredentials(options);\n\n\t// The environment first, and each half independently.\n\t//\n\t// `getDokployToken` has always read `DOKPLOY_API_TOKEN` while this — the\n\t// function the deploy path actually calls — read only the file. So a CI job\n\t// given the token as a secret had a token and nowhere to send it, and the\n\t// only way through was committing a credentials file or running an\n\t// interactive login on the runner.\n\t//\n\t// Read separately rather than as a pair because they arrive separately: a\n\t// token is a secret and belongs in the environment, while an endpoint is\n\t// configuration and usually belongs in `deploy.dokploy.endpoint`. Requiring\n\t// both from the same place is what made either one useless alone.\n\tconst token = process.env.DOKPLOY_API_TOKEN ?? credentials.dokploy?.token;\n\tconst endpoint =\n\t\tprocess.env.DOKPLOY_ENDPOINT ?? credentials.dokploy?.endpoint;\n\n\tif (!token || !endpoint) return null;\n\n\treturn {\n\t\ttoken,\n\t\tendpoint: endpoint.replace(/\\/$/, ''),\n\t\t...(credentials.dokploy?.registryId\n\t\t\t? { registryId: credentials.dokploy.registryId }\n\t\t\t: {}),\n\t};\n}\n\n/**\n * Remove Dokploy credentials\n */\nexport async function removeDokployCredentials(\n\toptions?: CredentialOptions,\n): Promise<boolean> {\n\tconst credentials = await readCredentials(options);\n\n\tif (!credentials.dokploy) {\n\t\treturn false;\n\t}\n\n\tdelete credentials.dokploy;\n\tawait writeCredentials(credentials, options);\n\treturn true;\n}\n\n/**\n * Remove all stored credentials\n */\nexport async function removeAllCredentials(\n\toptions?: CredentialOptions,\n): Promise<void> {\n\tconst path = getCredentialsPath(options);\n\n\tif (existsSync(path)) {\n\t\tawait unlink(path);\n\t}\n}\n\n/**\n * Get Dokploy API token, checking stored credentials first, then environment\n */\nexport async function getDokployToken(\n\toptions?: CredentialOptions,\n): Promise<string | null> {\n\t// First check environment variable (takes precedence)\n\tconst envToken = process.env.DOKPLOY_API_TOKEN;\n\tif (envToken) {\n\t\treturn envToken;\n\t}\n\n\t// Then check stored credentials\n\tconst stored = await getDokployCredentials(options);\n\tif (stored) {\n\t\treturn stored.token;\n\t}\n\n\treturn null;\n}\n\n/**\n * Get Dokploy endpoint from stored credentials\n */\nexport async function getDokployEndpoint(\n\toptions?: CredentialOptions,\n): Promise<string | null> {\n\tconst stored = await getDokployCredentials(options);\n\treturn stored?.endpoint ?? null;\n}\n\n/**\n * Store Dokploy registry ID\n */\nexport async function storeDokployRegistryId(\n\tregistryId: string,\n\toptions?: CredentialOptions,\n): Promise<void> {\n\tconst credentials = await readCredentials(options);\n\n\tif (!credentials.dokploy) {\n\t\tthrow new Error(\n\t\t\t'Dokploy credentials not found. Run \"gkm login --service dokploy\" first.',\n\t\t);\n\t}\n\n\tcredentials.dokploy.registryId = registryId;\n\tawait writeCredentials(credentials, options);\n}\n\n/**\n * Get Dokploy registry ID from stored credentials\n */\nexport async function getDokployRegistryId(\n\toptions?: CredentialOptions,\n): Promise<string | undefined> {\n\tconst stored = await getDokployCredentials(options);\n\treturn stored?.registryId ?? undefined;\n}\n\n// ============================================\n// Hostinger credentials\n// ============================================\n\n/**\n * Store Hostinger API token\n *\n * @param token - API token from hpanel.hostinger.com/profile/api\n */\nexport async function storeHostingerToken(\n\ttoken: string,\n\toptions?: CredentialOptions,\n): Promise<void> {\n\tconst credentials = await readCredentials(options);\n\n\tcredentials.hostinger = {\n\t\ttoken,\n\t\tstoredAt: new Date().toISOString(),\n\t};\n\n\tawait writeCredentials(credentials, options);\n}\n\n/**\n * Get stored Hostinger API token\n *\n * Checks environment variable first (HOSTINGER_API_TOKEN),\n * then falls back to stored credentials.\n */\nexport async function getHostingerToken(\n\toptions?: CredentialOptions,\n): Promise<string | null> {\n\t// First check environment variable (takes precedence)\n\tconst envToken = process.env.HOSTINGER_API_TOKEN;\n\tif (envToken) {\n\t\treturn envToken;\n\t}\n\n\t// Then check stored credentials\n\tconst credentials = await readCredentials(options);\n\treturn credentials.hostinger?.token ?? null;\n}\n\n/**\n * Remove Hostinger credentials\n */\nexport async function removeHostingerCredentials(\n\toptions?: CredentialOptions,\n): Promise<boolean> {\n\tconst credentials = await readCredentials(options);\n\n\tif (!credentials.hostinger) {\n\t\treturn false;\n\t}\n\n\tdelete credentials.hostinger;\n\tawait writeCredentials(credentials, options);\n\treturn true;\n}\n","/**\n * What of the host's environment a local sandbox passes on.\n *\n * An allowlist, not a denylist: a credential's variable has any name its\n * owner chose (`DOKPLOY_TOKEN`, `AWS_SECRET_ACCESS_KEY`, `MY_VAULT_TOKEN`), so\n * the only safe default is to name what a program needs to *run* and pass\n * nothing else.\n */\n\n/**\n * Variables a command needs to find programs, write temp files, print in the\n * user's locale and reach the network through the host's proxy — and nothing\n * that authenticates as anyone.\n *\n * Deliberately left out:\n * - `NODE_OPTIONS`: it can `--require` or `--import` code into every node\n * process, and CI often holds `--import tsx` there. A step that needs a\n * loader passes its own.\n * - `NODE_AUTH_TOKEN`, `NPM_TOKEN`, `npm_config_*`: registry tokens\n * (`actions/setup-node` writes `NODE_AUTH_TOKEN`), which is why there is no\n * `NODE_*` wildcard.\n * - `AWS_*`, `DOCKER_*`, `DOKPLOY_*`, `GITHUB_TOKEN`, `TURBO_TOKEN`, `GKM_*`:\n * credentials, or where the deploy keeps them.\n * - `SHELL`, `EDITOR` and the rest of a login session: nothing here starts a\n * shell.\n *\n * Proxies are passed: behind one, nothing installs or builds without it. A\n * proxy URL with a password in it reaches the command too — a host that keeps\n * one there and builds code it does not trust should pass a sandbox of its\n * own.\n */\nexport const SANDBOX_ENV_ALLOWLIST: readonly (string | RegExp)[] = [\n\t// Finding programs and a home to cache in.\n\t'PATH',\n\t'HOME',\n\t'USER',\n\t'LOGNAME',\n\t'TMPDIR',\n\t'TMP',\n\t'TEMP',\n\t// Locale and terminal: output a person reads should look the same.\n\t'LANG',\n\t'LANGUAGE',\n\t'TZ',\n\t/^LC_[A-Z]+$/,\n\t'TERM',\n\t'COLORTERM',\n\t'NO_COLOR',\n\t'FORCE_COLOR',\n\t'CI',\n\t// Node: the mode a build runs in, and the CAs a corporate network adds.\n\t'NODE_ENV',\n\t'NODE_EXTRA_CA_CERTS',\n\t// Package managers' own homes, so a build reuses the host's store.\n\t'COREPACK_HOME',\n\t'PNPM_HOME',\n\t'XDG_CACHE_HOME',\n\t'XDG_DATA_HOME',\n\t// The network, through the host's proxy.\n\t'HTTP_PROXY',\n\t'HTTPS_PROXY',\n\t'NO_PROXY',\n\t'http_proxy',\n\t'https_proxy',\n\t'no_proxy',\n\t// Windows: without these, nothing starts.\n\t'SystemRoot',\n\t'SYSTEMROOT',\n\t'windir',\n\t'WINDIR',\n\t'ComSpec',\n\t'COMSPEC',\n\t'PATHEXT',\n\t'USERPROFILE',\n\t'APPDATA',\n\t'LOCALAPPDATA',\n];\n\nfunction allowed(name: string): boolean {\n\treturn SANDBOX_ENV_ALLOWLIST.some((entry) =>\n\t\ttypeof entry === 'string' ? entry === name : entry.test(name),\n\t);\n}\n\n/**\n * The allowlisted part of `source`, plus each of `extra` the host chose to\n * pass on by name.\n */\nexport function allowlistedEnv(\n\tsource: NodeJS.ProcessEnv = process.env,\n\textra: readonly string[] = [],\n): Record<string, string> {\n\tconst env: Record<string, string> = {};\n\tfor (const [name, value] of Object.entries(source)) {\n\t\tif (value === undefined) continue;\n\t\tif (allowed(name) || extra.includes(name)) env[name] = value;\n\t}\n\treturn env;\n}\n","import { type SpawnOptions, spawn } from 'node:child_process';\nimport { mkdtemp, rm, writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join, resolve } from 'node:path';\nimport { CommandTimedOut } from '../run';\nimport { allowlistedEnv } from './env';\nimport {\n\tassertSecretNames,\n\tconfineCwd,\n\ttype Sandbox,\n\ttype SandboxExecOptions,\n\ttype SandboxOutput,\n\ttype SandboxResult,\n\tSECRETS_DIR_ENV,\n} from './sandbox';\n\n/** How long a timed-out command has to exit on SIGTERM before SIGKILL. */\nconst KILL_GRACE_MS = 5_000;\n\nexport interface LocalSandboxOptions {\n\t/** The project. Every command runs at or below it. */\n\troot: string;\n\t/**\n\t * `HOME` for every command. Defaults to the host's, because a build\n\t * reuses the caches kept there — the pnpm store, corepack's package\n\t * managers, turbo's cache — and a cold one re-downloads all of them.\n\t * Point it at an empty directory to keep `~/.aws`, `~/.npmrc` and\n\t * `~/.docker` out of reach of an SDK's default lookup — though, sharing\n\t * the host's filesystem, a command that goes looking still finds them.\n\t */\n\thome?: string;\n\t/**\n\t * Host variables to pass on beyond the allowlist, by name — a remote\n\t * cache's `TURBO_TOKEN`, for a build the host trusts with it.\n\t */\n\tpassEnv?: readonly string[];\n\t/** Where the allowlisted variables are read from. Defaults to `process.env`. */\n\thostEnv?: NodeJS.ProcessEnv;\n}\n\n/** Where `output` sends a child's streams. */\nconst STDIO: Record<SandboxOutput, SpawnOptions['stdio']> = {\n\tcapture: ['ignore', 'pipe', 'pipe'],\n\tinherit: 'inherit',\n\tstderr: ['ignore', 2, 2],\n\tignore: 'ignore',\n};\n\n/**\n * A child process on this machine, as this user, with an allowlisted\n * environment.\n *\n * What it withholds from the command: the host's environment beyond\n * {@link SANDBOX_ENV_ALLOWLIST}, a shell, a working directory outside the\n * project, and any time past the timeout. What it does not: the host's\n * filesystem, which the command can read and write as the user running the\n * deploy. So it is not `isolating`, and it is the default for projects the\n * host already trusts — the developer's own, a team's own CI. Code nobody\n * vetted belongs in a sandbox that is.\n *\n * A timed-out command is sent SIGTERM, then SIGKILL; processes it started\n * itself are its to stop.\n */\nexport class LocalSandbox implements Sandbox {\n\treadonly root: string;\n\treadonly isolating = false;\n\treadonly env: Readonly<Record<string, string>>;\n\n\tconstructor(options: LocalSandboxOptions) {\n\t\tthis.root = resolve(options.root);\n\t\tthis.env = {\n\t\t\t...allowlistedEnv(options.hostEnv ?? process.env, options.passEnv),\n\t\t\t...(options.home\n\t\t\t\t? { HOME: options.home, USERPROFILE: options.home }\n\t\t\t\t: {}),\n\t\t};\n\t}\n\n\tasync exec(\n\t\tcommand: string,\n\t\targs: readonly string[],\n\t\toptions: SandboxExecOptions,\n\t): Promise<SandboxResult> {\n\t\tconst cwd = confineCwd(this.root, options.cwd);\n\t\tassertSecretNames(options.secrets);\n\t\toptions.signal?.throwIfAborted();\n\n\t\tconst secretsDir = await mountSecrets(options.secrets);\n\t\ttry {\n\t\t\treturn await spawnConfined(command, args, {\n\t\t\t\t...options,\n\t\t\t\tcwd,\n\t\t\t\tenv: secretsDir\n\t\t\t\t\t? { ...options.env, [SECRETS_DIR_ENV]: secretsDir }\n\t\t\t\t\t: options.env,\n\t\t\t});\n\t\t} finally {\n\t\t\tif (secretsDir) await rm(secretsDir, { recursive: true, force: true });\n\t\t}\n\t}\n}\n\n/**\n * Each secret as a 0600 file in a fresh 0700 directory outside the project —\n * where nothing the build writes, packs or commits will pick it up.\n */\nasync function mountSecrets(\n\tsecrets: Readonly<Record<string, string>> | undefined,\n): Promise<string | undefined> {\n\tif (!secrets || Object.keys(secrets).length === 0) return undefined;\n\n\tconst dir = await mkdtemp(join(tmpdir(), 'gkm-secrets-'));\n\tfor (const [name, value] of Object.entries(secrets)) {\n\t\tawait writeFile(join(dir, name), value, { mode: 0o600 });\n\t}\n\treturn dir;\n}\n\nfunction spawnConfined(\n\tcommand: string,\n\targs: readonly string[],\n\toptions: SandboxExecOptions,\n): Promise<SandboxResult> {\n\tconst { output = 'capture', signal, timeoutMs } = options;\n\n\treturn new Promise<SandboxResult>((resolvePromise, reject) => {\n\t\tconst child = spawn(command, [...args], {\n\t\t\tcwd: options.cwd,\n\t\t\t// The whole environment, as given. `spawn` reads `process.env` only\n\t\t\t// when this is left out.\n\t\t\tenv: { ...options.env },\n\t\t\tstdio: STDIO[output],\n\t\t\tshell: false,\n\t\t\t...(signal ? { signal } : {}),\n\t\t});\n\n\t\tlet stdout = '';\n\t\tlet stderr = '';\n\t\tchild.stdout?.on('data', (chunk: Buffer) => {\n\t\t\tstdout += chunk.toString();\n\t\t});\n\t\tchild.stderr?.on('data', (chunk: Buffer) => {\n\t\t\tstderr += chunk.toString();\n\t\t});\n\n\t\tlet timedOut = false;\n\t\tlet killer: NodeJS.Timeout | undefined;\n\t\tconst timer = setTimeout(() => {\n\t\t\ttimedOut = true;\n\t\t\tchild.kill('SIGTERM');\n\t\t\t// A command that ignores SIGTERM would otherwise outlive its timeout.\n\t\t\tkiller = setTimeout(() => child.kill('SIGKILL'), KILL_GRACE_MS);\n\t\t\tkiller.unref?.();\n\t\t}, timeoutMs);\n\t\ttimer.unref?.();\n\n\t\tconst settle = () => {\n\t\t\tclearTimeout(timer);\n\t\t\tif (killer) clearTimeout(killer);\n\t\t};\n\n\t\tchild.on('error', (error) => {\n\t\t\tsettle();\n\t\t\t// Node reports an abort as its own `AbortError`; the caller asked\n\t\t\t// with a reason, and that is what it gets back.\n\t\t\treject(signal?.aborted ? signal.reason : error);\n\t\t});\n\n\t\tchild.on('close', (code: number | null, killedBy: string | null) => {\n\t\t\tsettle();\n\t\t\tif (signal?.aborted) reject(signal.reason);\n\t\t\telse if (timedOut) reject(new CommandTimedOut(command, args, timeoutMs));\n\t\t\telse\n\t\t\t\tresolvePromise({\n\t\t\t\t\texitCode: code,\n\t\t\t\t\tsignal: killedBy ?? null,\n\t\t\t\t\tstdout,\n\t\t\t\t\tstderr,\n\t\t\t\t});\n\t\t});\n\t});\n}\n","/**\n * How a deploy gets the credentials it acts with.\n *\n * `deploy()` never prompts and never reads a terminal: it asks a\n * `CredentialProvider` and, when the provider has nothing, stops with\n * `MissingCredential` naming what it needed and how to supply it. Whether to\n * ask a person, read a vault or fail is the caller's decision — `gkm deploy`\n * prompts at a terminal and stores what was typed; a CI job sets the\n * environment; a host passes its own provider.\n */\n\nimport { readCredentials } from '../auth/credentials';\n\n/**\n * What each kind of credential is asked for with, and what it is.\n *\n * A target that needs a kind the CLI does not know declares it by augmenting\n * this interface, and lists it in its `credentials`:\n *\n * ```ts\n * declare module '@geekmidas/cli/target' {\n * interface CredentialKinds {\n * fly: { request: { org: string }; value: { token: string } };\n * }\n * }\n * ```\n */\nexport interface CredentialKinds {\n\t/** The Dokploy API a stage deploys through. */\n\tdokploy: {\n\t\t/** `deploy.dokploy.endpoint`, when the config names one. */\n\t\trequest: { endpoint?: string };\n\t\tvalue: { endpoint: string; token: string };\n\t};\n\t/**\n\t * A container registry login, for Dokploy to pull with. Asked for only when\n\t * Dokploy has no registry for `deploy.registry` and one has to be\n\t * created.\n\t */\n\tregistry: {\n\t\t/** `deploy.registry`. */\n\t\trequest: { url: string };\n\t\tvalue: { username: string; password: string };\n\t};\n\t/**\n\t * The AWS account a stage deploys to, for the SST target's `sst deploy`.\n\t * Handed to that one command's environment and to nothing else.\n\t */\n\taws: {\n\t\t/** The stage, for a provider that keeps one account per stage. */\n\t\trequest: { stage: string };\n\t\tvalue: AwsCredential;\n\t};\n}\n\n/**\n * Who to act as on AWS: a named profile, or keys.\n *\n * Never both. Given both, every AWS SDK takes the keys and ignores the\n * profile, so `--profile prod` with staging's keys still exported would deploy\n * production's stack into staging's account — the reason `secretsStoreFor`\n * resolves a named profile on its own, and why this cannot express the mix.\n */\nexport type AwsCredential =\n\t| {\n\t\t\t/** A profile in `~/.aws/config` — keys, SSO, an assumed role. */\n\t\t\tprofile: string;\n\t\t\tregion?: string;\n\t }\n\t| {\n\t\t\taccessKeyId: string;\n\t\t\tsecretAccessKey: string;\n\t\t\t/** Present for temporary keys: an assumed role, GitHub's OIDC. */\n\t\t\tsessionToken?: string;\n\t\t\tregion?: string;\n\t };\n\nexport type CredentialKind = keyof CredentialKinds;\n\n/** What a deploy asks a provider for. */\nexport type CredentialRequest<K extends CredentialKind = CredentialKind> = {\n\t[P in K]: { kind: P } & CredentialKinds[P]['request'];\n}[K];\n\n/** What a provider answers with. */\nexport type Credential<K extends CredentialKind> = CredentialKinds[K]['value'];\n\nexport interface CredentialProvider {\n\t/**\n\t * The credential `request` asks for, or `undefined` when this provider has\n\t * none — the deploy then raises {@link MissingCredential}.\n\t */\n\tget<K extends CredentialKind>(\n\t\trequest: CredentialRequest<K>,\n\t\toptions?: { signal?: AbortSignal },\n\t): Promise<Credential<K> | undefined>;\n}\n\n/**\n * How to supply each kind the CLI knows, for the error that says it is\n * missing. A target's own kinds are its to explain.\n */\nconst HOW_TO_PROVIDE: Partial<Record<CredentialKind, string>> = {\n\tdokploy:\n\t\t\"Set DOKPLOY_API_TOKEN (and DOKPLOY_ENDPOINT, or deploy.dokploy.endpoint in gkm.config.ts), run `gkm login --provider dokploy`, or pass them through the deploy's CredentialProvider.\",\n\tregistry:\n\t\t'Add the registry in Dokploy (Settings → Docker Registry) and set deploy.dokploy.registryId, set DOCKER_REGISTRY_USERNAME and DOCKER_REGISTRY_PASSWORD, or run `gkm deploy` at a terminal to be asked for them.',\n\taws: \"Set AWS_PROFILE to the stage account's profile, or AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY (and AWS_SESSION_TOKEN) — in CI, the keys aws-actions/configure-aws-credentials exports — or pass them through the deploy's CredentialProvider.\",\n};\n\n/** A deploy needed a credential, and its provider had none. */\nexport class MissingCredential extends Error {\n\tconstructor(\n\t\treadonly kind: CredentialKind,\n\t\t/** What it was for: the Dokploy endpoint or the registry URL. */\n\t\treadonly target: string | undefined,\n\t\t/** How to supply it. */\n\t\treadonly howToProvide: string = HOW_TO_PROVIDE[kind] ??\n\t\t\t\"Pass it through the deploy's CredentialProvider.\",\n\t) {\n\t\tconst what =\n\t\t\tkind === 'dokploy'\n\t\t\t\t? `No Dokploy credentials${target ? ` for ${target}` : ''}.`\n\t\t\t\t: kind === 'registry'\n\t\t\t\t\t? `Dokploy has no registry for ${target ?? 'the configured registry'}, and there are no credentials to create one with.`\n\t\t\t\t\t: kind === 'aws'\n\t\t\t\t\t\t? `No AWS credentials${target ? ` for the \"${target}\" stage` : ''}.`\n\t\t\t\t\t\t: `No \"${String(kind)}\" credentials${target ? ` for ${target}` : ''}.`;\n\t\tsuper(`${what} ${howToProvide}`);\n\t\tthis.name = 'MissingCredential';\n\t}\n}\n\n/** Whatever was asked for, from the first provider that has it. */\nexport function chainCredentials(\n\t...providers: CredentialProvider[]\n): CredentialProvider {\n\treturn {\n\t\tasync get(request, options) {\n\t\t\tfor (const provider of providers) {\n\t\t\t\tconst found = await provider.get(request, options);\n\t\t\t\tif (found) return found;\n\t\t\t}\n\t\t\treturn undefined;\n\t\t},\n\t};\n}\n\nexport interface StoredCredentialsOptions {\n\t/** Where to read the variables from. Defaults to `process.env`. */\n\tenv?: NodeJS.ProcessEnv;\n\t/**\n\t * The CLI's home, whose `credentials.json` holds stored logins. Defaults\n\t * to `GKM_HOME`, else `~/.gkm`.\n\t */\n\thome?: string;\n}\n\n/**\n * Credentials from the environment, then from what `gkm login` stored. What\n * `deploy()` uses when it is handed no provider.\n *\n * - Dokploy: `DOKPLOY_API_TOKEN` / `DOKPLOY_ENDPOINT`, each falling back to\n * the stored login, and the endpoint finally to `deploy.dokploy.endpoint`.\n * Each half is read on its own: a token is a secret and belongs in the\n * environment, an endpoint is configuration and belongs in the config.\n * - Registry: `DOCKER_REGISTRY_USERNAME` / `DOCKER_REGISTRY_PASSWORD`.\n * - AWS: `AWS_PROFILE` when it is set, and then nothing else; otherwise\n * `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` / `AWS_SESSION_TOKEN`. The\n * profile winning is `secretsStoreFor`'s rule: a profile is a choice of\n * account someone made, exported keys may be left over from another.\n * Read here, by name, and handed only to the command that deploys — the\n * sandbox every other step runs in passes no `AWS_*` on.\n */\nexport function storedCredentials(\n\toptions: StoredCredentialsOptions = {},\n): CredentialProvider {\n\tconst env = options.env ?? process.env;\n\n\treturn {\n\t\tasync get(asked) {\n\t\t\t// The union, so a switch on `kind` narrows it: `K` itself never is.\n\t\t\tconst request = asked as CredentialRequest;\n\t\t\tswitch (request.kind) {\n\t\t\t\tcase 'dokploy': {\n\t\t\t\t\tconst stored = (\n\t\t\t\t\t\tawait readCredentials(\n\t\t\t\t\t\t\toptions.home ? { home: options.home } : undefined,\n\t\t\t\t\t\t)\n\t\t\t\t\t).dokploy;\n\t\t\t\t\tconst token = env.DOKPLOY_API_TOKEN ?? stored?.token;\n\t\t\t\t\tconst endpoint =\n\t\t\t\t\t\tenv.DOKPLOY_ENDPOINT ?? stored?.endpoint ?? request.endpoint;\n\t\t\t\t\tif (!token || !endpoint) return undefined;\n\t\t\t\t\treturn { endpoint: endpoint.replace(/\\/$/, ''), token } as never;\n\t\t\t\t}\n\t\t\t\tcase 'registry': {\n\t\t\t\t\tconst username = env.DOCKER_REGISTRY_USERNAME;\n\t\t\t\t\tconst password = env.DOCKER_REGISTRY_PASSWORD;\n\t\t\t\t\tif (!username || !password) return undefined;\n\t\t\t\t\treturn { username, password } as never;\n\t\t\t\t}\n\t\t\t\tcase 'aws': {\n\t\t\t\t\tconst region = env.AWS_REGION ?? env.AWS_DEFAULT_REGION;\n\t\t\t\t\tconst where = region ? { region } : {};\n\t\t\t\t\tif (env.AWS_PROFILE) {\n\t\t\t\t\t\treturn { profile: env.AWS_PROFILE, ...where } as never;\n\t\t\t\t\t}\n\t\t\t\t\tconst accessKeyId = env.AWS_ACCESS_KEY_ID;\n\t\t\t\t\tconst secretAccessKey = env.AWS_SECRET_ACCESS_KEY;\n\t\t\t\t\tif (!accessKeyId || !secretAccessKey) return undefined;\n\t\t\t\t\treturn {\n\t\t\t\t\t\taccessKeyId,\n\t\t\t\t\t\tsecretAccessKey,\n\t\t\t\t\t\t...(env.AWS_SESSION_TOKEN\n\t\t\t\t\t\t\t? { sessionToken: env.AWS_SESSION_TOKEN }\n\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t\t...where,\n\t\t\t\t\t} as never;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// A kind a target declared: nothing stored here can answer it.\n\t\t\treturn undefined;\n\t\t},\n\t};\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { n as CommandTimedOut, s as gkmHome } from "./run-BJLIerMM.mjs";
|
|
2
|
+
import { a as assertSecretNames, o as confineCwd, t as SECRETS_DIR_ENV } from "./sandbox-Dm247NGx.mjs";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
3
4
|
import { existsSync, mkdirSync } from "node:fs";
|
|
4
|
-
import { readFile, writeFile } from "node:fs/promises";
|
|
5
|
+
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
|
+
import { spawn } from "node:child_process";
|
|
5
8
|
//#region src/auth/credentials.ts
|
|
6
9
|
/**
|
|
7
10
|
* Get the path to the credentials directory
|
|
@@ -124,6 +127,221 @@ async function getHostingerToken(options) {
|
|
|
124
127
|
return (await readCredentials(options)).hostinger?.token ?? null;
|
|
125
128
|
}
|
|
126
129
|
//#endregion
|
|
130
|
+
//#region src/sandbox/env.ts
|
|
131
|
+
/**
|
|
132
|
+
* What of the host's environment a local sandbox passes on.
|
|
133
|
+
*
|
|
134
|
+
* An allowlist, not a denylist: a credential's variable has any name its
|
|
135
|
+
* owner chose (`DOKPLOY_TOKEN`, `AWS_SECRET_ACCESS_KEY`, `MY_VAULT_TOKEN`), so
|
|
136
|
+
* the only safe default is to name what a program needs to *run* and pass
|
|
137
|
+
* nothing else.
|
|
138
|
+
*/
|
|
139
|
+
/**
|
|
140
|
+
* Variables a command needs to find programs, write temp files, print in the
|
|
141
|
+
* user's locale and reach the network through the host's proxy — and nothing
|
|
142
|
+
* that authenticates as anyone.
|
|
143
|
+
*
|
|
144
|
+
* Deliberately left out:
|
|
145
|
+
* - `NODE_OPTIONS`: it can `--require` or `--import` code into every node
|
|
146
|
+
* process, and CI often holds `--import tsx` there. A step that needs a
|
|
147
|
+
* loader passes its own.
|
|
148
|
+
* - `NODE_AUTH_TOKEN`, `NPM_TOKEN`, `npm_config_*`: registry tokens
|
|
149
|
+
* (`actions/setup-node` writes `NODE_AUTH_TOKEN`), which is why there is no
|
|
150
|
+
* `NODE_*` wildcard.
|
|
151
|
+
* - `AWS_*`, `DOCKER_*`, `DOKPLOY_*`, `GITHUB_TOKEN`, `TURBO_TOKEN`, `GKM_*`:
|
|
152
|
+
* credentials, or where the deploy keeps them.
|
|
153
|
+
* - `SHELL`, `EDITOR` and the rest of a login session: nothing here starts a
|
|
154
|
+
* shell.
|
|
155
|
+
*
|
|
156
|
+
* Proxies are passed: behind one, nothing installs or builds without it. A
|
|
157
|
+
* proxy URL with a password in it reaches the command too — a host that keeps
|
|
158
|
+
* one there and builds code it does not trust should pass a sandbox of its
|
|
159
|
+
* own.
|
|
160
|
+
*/
|
|
161
|
+
const SANDBOX_ENV_ALLOWLIST = [
|
|
162
|
+
"PATH",
|
|
163
|
+
"HOME",
|
|
164
|
+
"USER",
|
|
165
|
+
"LOGNAME",
|
|
166
|
+
"TMPDIR",
|
|
167
|
+
"TMP",
|
|
168
|
+
"TEMP",
|
|
169
|
+
"LANG",
|
|
170
|
+
"LANGUAGE",
|
|
171
|
+
"TZ",
|
|
172
|
+
/^LC_[A-Z]+$/,
|
|
173
|
+
"TERM",
|
|
174
|
+
"COLORTERM",
|
|
175
|
+
"NO_COLOR",
|
|
176
|
+
"FORCE_COLOR",
|
|
177
|
+
"CI",
|
|
178
|
+
"NODE_ENV",
|
|
179
|
+
"NODE_EXTRA_CA_CERTS",
|
|
180
|
+
"COREPACK_HOME",
|
|
181
|
+
"PNPM_HOME",
|
|
182
|
+
"XDG_CACHE_HOME",
|
|
183
|
+
"XDG_DATA_HOME",
|
|
184
|
+
"HTTP_PROXY",
|
|
185
|
+
"HTTPS_PROXY",
|
|
186
|
+
"NO_PROXY",
|
|
187
|
+
"http_proxy",
|
|
188
|
+
"https_proxy",
|
|
189
|
+
"no_proxy",
|
|
190
|
+
"SystemRoot",
|
|
191
|
+
"SYSTEMROOT",
|
|
192
|
+
"windir",
|
|
193
|
+
"WINDIR",
|
|
194
|
+
"ComSpec",
|
|
195
|
+
"COMSPEC",
|
|
196
|
+
"PATHEXT",
|
|
197
|
+
"USERPROFILE",
|
|
198
|
+
"APPDATA",
|
|
199
|
+
"LOCALAPPDATA"
|
|
200
|
+
];
|
|
201
|
+
function allowed(name) {
|
|
202
|
+
return SANDBOX_ENV_ALLOWLIST.some((entry) => typeof entry === "string" ? entry === name : entry.test(name));
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* The allowlisted part of `source`, plus each of `extra` the host chose to
|
|
206
|
+
* pass on by name.
|
|
207
|
+
*/
|
|
208
|
+
function allowlistedEnv(source = process.env, extra = []) {
|
|
209
|
+
const env = {};
|
|
210
|
+
for (const [name, value] of Object.entries(source)) {
|
|
211
|
+
if (value === void 0) continue;
|
|
212
|
+
if (allowed(name) || extra.includes(name)) env[name] = value;
|
|
213
|
+
}
|
|
214
|
+
return env;
|
|
215
|
+
}
|
|
216
|
+
//#endregion
|
|
217
|
+
//#region src/sandbox/local.ts
|
|
218
|
+
/** How long a timed-out command has to exit on SIGTERM before SIGKILL. */
|
|
219
|
+
const KILL_GRACE_MS = 5e3;
|
|
220
|
+
/** Where `output` sends a child's streams. */
|
|
221
|
+
const STDIO = {
|
|
222
|
+
capture: [
|
|
223
|
+
"ignore",
|
|
224
|
+
"pipe",
|
|
225
|
+
"pipe"
|
|
226
|
+
],
|
|
227
|
+
inherit: "inherit",
|
|
228
|
+
stderr: [
|
|
229
|
+
"ignore",
|
|
230
|
+
2,
|
|
231
|
+
2
|
|
232
|
+
],
|
|
233
|
+
ignore: "ignore"
|
|
234
|
+
};
|
|
235
|
+
/**
|
|
236
|
+
* A child process on this machine, as this user, with an allowlisted
|
|
237
|
+
* environment.
|
|
238
|
+
*
|
|
239
|
+
* What it withholds from the command: the host's environment beyond
|
|
240
|
+
* {@link SANDBOX_ENV_ALLOWLIST}, a shell, a working directory outside the
|
|
241
|
+
* project, and any time past the timeout. What it does not: the host's
|
|
242
|
+
* filesystem, which the command can read and write as the user running the
|
|
243
|
+
* deploy. So it is not `isolating`, and it is the default for projects the
|
|
244
|
+
* host already trusts — the developer's own, a team's own CI. Code nobody
|
|
245
|
+
* vetted belongs in a sandbox that is.
|
|
246
|
+
*
|
|
247
|
+
* A timed-out command is sent SIGTERM, then SIGKILL; processes it started
|
|
248
|
+
* itself are its to stop.
|
|
249
|
+
*/
|
|
250
|
+
var LocalSandbox = class {
|
|
251
|
+
root;
|
|
252
|
+
isolating = false;
|
|
253
|
+
env;
|
|
254
|
+
constructor(options) {
|
|
255
|
+
this.root = resolve(options.root);
|
|
256
|
+
this.env = {
|
|
257
|
+
...allowlistedEnv(options.hostEnv ?? process.env, options.passEnv),
|
|
258
|
+
...options.home ? {
|
|
259
|
+
HOME: options.home,
|
|
260
|
+
USERPROFILE: options.home
|
|
261
|
+
} : {}
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
async exec(command, args, options) {
|
|
265
|
+
const cwd = confineCwd(this.root, options.cwd);
|
|
266
|
+
assertSecretNames(options.secrets);
|
|
267
|
+
options.signal?.throwIfAborted();
|
|
268
|
+
const secretsDir = await mountSecrets(options.secrets);
|
|
269
|
+
try {
|
|
270
|
+
return await spawnConfined(command, args, {
|
|
271
|
+
...options,
|
|
272
|
+
cwd,
|
|
273
|
+
env: secretsDir ? {
|
|
274
|
+
...options.env,
|
|
275
|
+
[SECRETS_DIR_ENV]: secretsDir
|
|
276
|
+
} : options.env
|
|
277
|
+
});
|
|
278
|
+
} finally {
|
|
279
|
+
if (secretsDir) await rm(secretsDir, {
|
|
280
|
+
recursive: true,
|
|
281
|
+
force: true
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
/**
|
|
287
|
+
* Each secret as a 0600 file in a fresh 0700 directory outside the project —
|
|
288
|
+
* where nothing the build writes, packs or commits will pick it up.
|
|
289
|
+
*/
|
|
290
|
+
async function mountSecrets(secrets) {
|
|
291
|
+
if (!secrets || Object.keys(secrets).length === 0) return void 0;
|
|
292
|
+
const dir = await mkdtemp(join(tmpdir(), "gkm-secrets-"));
|
|
293
|
+
for (const [name, value] of Object.entries(secrets)) await writeFile(join(dir, name), value, { mode: 384 });
|
|
294
|
+
return dir;
|
|
295
|
+
}
|
|
296
|
+
function spawnConfined(command, args, options) {
|
|
297
|
+
const { output = "capture", signal, timeoutMs } = options;
|
|
298
|
+
return new Promise((resolvePromise, reject) => {
|
|
299
|
+
const child = spawn(command, [...args], {
|
|
300
|
+
cwd: options.cwd,
|
|
301
|
+
env: { ...options.env },
|
|
302
|
+
stdio: STDIO[output],
|
|
303
|
+
shell: false,
|
|
304
|
+
...signal ? { signal } : {}
|
|
305
|
+
});
|
|
306
|
+
let stdout = "";
|
|
307
|
+
let stderr = "";
|
|
308
|
+
child.stdout?.on("data", (chunk) => {
|
|
309
|
+
stdout += chunk.toString();
|
|
310
|
+
});
|
|
311
|
+
child.stderr?.on("data", (chunk) => {
|
|
312
|
+
stderr += chunk.toString();
|
|
313
|
+
});
|
|
314
|
+
let timedOut = false;
|
|
315
|
+
let killer;
|
|
316
|
+
const timer = setTimeout(() => {
|
|
317
|
+
timedOut = true;
|
|
318
|
+
child.kill("SIGTERM");
|
|
319
|
+
killer = setTimeout(() => child.kill("SIGKILL"), KILL_GRACE_MS);
|
|
320
|
+
killer.unref?.();
|
|
321
|
+
}, timeoutMs);
|
|
322
|
+
timer.unref?.();
|
|
323
|
+
const settle = () => {
|
|
324
|
+
clearTimeout(timer);
|
|
325
|
+
if (killer) clearTimeout(killer);
|
|
326
|
+
};
|
|
327
|
+
child.on("error", (error) => {
|
|
328
|
+
settle();
|
|
329
|
+
reject(signal?.aborted ? signal.reason : error);
|
|
330
|
+
});
|
|
331
|
+
child.on("close", (code, killedBy) => {
|
|
332
|
+
settle();
|
|
333
|
+
if (signal?.aborted) reject(signal.reason);
|
|
334
|
+
else if (timedOut) reject(new CommandTimedOut(command, args, timeoutMs));
|
|
335
|
+
else resolvePromise({
|
|
336
|
+
exitCode: code,
|
|
337
|
+
signal: killedBy ?? null,
|
|
338
|
+
stdout,
|
|
339
|
+
stderr
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
//#endregion
|
|
127
345
|
//#region src/deploy/credentials.ts
|
|
128
346
|
/**
|
|
129
347
|
* How a deploy gets the credentials it acts with.
|
|
@@ -228,6 +446,6 @@ function storedCredentials(options = {}) {
|
|
|
228
446
|
} };
|
|
229
447
|
}
|
|
230
448
|
//#endregion
|
|
231
|
-
export {
|
|
449
|
+
export { SANDBOX_ENV_ALLOWLIST as a, getDokployCredentials as c, getHostingerToken as d, removeDokployCredentials as f, LocalSandbox as i, getDokployRegistryId as l, storeHostingerToken as m, chainCredentials as n, allowlistedEnv as o, storeDokployCredentials as p, storedCredentials as r, getCredentialsPath as s, MissingCredential as t, getDokployToken as u };
|
|
232
450
|
|
|
233
|
-
//# sourceMappingURL=credentials-
|
|
451
|
+
//# sourceMappingURL=credentials-pAtB70k3.mjs.map
|