@launchsecure/launch-kit 0.0.44 → 0.0.45
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/dist/deck-client/assets/{_baseUniq-C6w7kg8x.js → _baseUniq-9540Lrb7.js} +1 -1
- package/dist/deck-client/assets/{arc-Cx9pT3Nn.js → arc-2FFU5_0l.js} +1 -1
- package/dist/deck-client/assets/{architectureDiagram-Q4EWVU46-BITSj3vA.js → architectureDiagram-Q4EWVU46-CV2e3n5Z.js} +1 -1
- package/dist/deck-client/assets/{blockDiagram-DXYQGD6D-BehOFuwh.js → blockDiagram-DXYQGD6D-B9JFwjAL.js} +1 -1
- package/dist/deck-client/assets/{c4Diagram-AHTNJAMY-BZTYM4na.js → c4Diagram-AHTNJAMY-C0Gwco04.js} +1 -1
- package/dist/deck-client/assets/channel-DUo1BfyB.js +1 -0
- package/dist/deck-client/assets/{chunk-4BX2VUAB-CCUx5CTd.js → chunk-4BX2VUAB-pnGex62D.js} +1 -1
- package/dist/deck-client/assets/{chunk-4TB4RGXK-UDZXXga6.js → chunk-4TB4RGXK-BRfs5enT.js} +1 -1
- package/dist/deck-client/assets/{chunk-55IACEB6-CfcU6PIW.js → chunk-55IACEB6-DV2sc7BN.js} +1 -1
- package/dist/deck-client/assets/{chunk-EDXVE4YY-BK6F5Fof.js → chunk-EDXVE4YY-BH8QD8Jn.js} +1 -1
- package/dist/deck-client/assets/{chunk-FMBD7UC4-C-2idlFB.js → chunk-FMBD7UC4-BVuRSGoP.js} +1 -1
- package/dist/deck-client/assets/{chunk-OYMX7WX6-D6hBkYLP.js → chunk-OYMX7WX6-DBBVRR48.js} +1 -1
- package/dist/deck-client/assets/{chunk-QZHKN3VN-DixNpysA.js → chunk-QZHKN3VN-DNLfqlpV.js} +1 -1
- package/dist/deck-client/assets/{chunk-YZCP3GAM-Cd3pNBtQ.js → chunk-YZCP3GAM-w7-OIPaD.js} +1 -1
- package/dist/deck-client/assets/classDiagram-6PBFFD2Q-sHUWMvyj.js +1 -0
- package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-sHUWMvyj.js +1 -0
- package/dist/deck-client/assets/clone-pfbkP49m.js +1 -0
- package/dist/deck-client/assets/{cose-bilkent-S5V4N54A-OF3JWdEt.js → cose-bilkent-S5V4N54A-D8kmXu30.js} +1 -1
- package/dist/deck-client/assets/{dagre-KV5264BT-Bqu-qcv4.js → dagre-KV5264BT--b1FD3_Y.js} +1 -1
- package/dist/deck-client/assets/{diagram-5BDNPKRD--0eHmUBS.js → diagram-5BDNPKRD-DKPVzUtl.js} +1 -1
- package/dist/deck-client/assets/{diagram-G4DWMVQ6-nss6oL20.js → diagram-G4DWMVQ6-DYSdoCus.js} +1 -1
- package/dist/deck-client/assets/{diagram-MMDJMWI5-D_gSGnLR.js → diagram-MMDJMWI5-DmqAI88z.js} +1 -1
- package/dist/deck-client/assets/{diagram-TYMM5635-BIt-P6Pk.js → diagram-TYMM5635-Dbt6BCnF.js} +1 -1
- package/dist/deck-client/assets/{erDiagram-SMLLAGMA-Bi-E4KQm.js → erDiagram-SMLLAGMA-C7Kfi12r.js} +1 -1
- package/dist/deck-client/assets/{flowDiagram-DWJPFMVM-DMJCvLMA.js → flowDiagram-DWJPFMVM-h1nKPzIv.js} +1 -1
- package/dist/deck-client/assets/{ganttDiagram-T4ZO3ILL-C3xgEoPD.js → ganttDiagram-T4ZO3ILL-40rX1Tln.js} +1 -1
- package/dist/deck-client/assets/{gitGraphDiagram-UUTBAWPF-CD0BEGAW.js → gitGraphDiagram-UUTBAWPF-CtvJWtdg.js} +1 -1
- package/dist/deck-client/assets/{graph-Dtsd9Jwe.js → graph-ByiwozwM.js} +1 -1
- package/dist/deck-client/assets/{index-TFX8vtTG.js → index-CGG2xGJc.js} +1 -1
- package/dist/deck-client/assets/{infoDiagram-42DDH7IO-7IcQYqe_.js → infoDiagram-42DDH7IO-CrGXAAvG.js} +1 -1
- package/dist/deck-client/assets/{ishikawaDiagram-UXIWVN3A-DsCEbx3u.js → ishikawaDiagram-UXIWVN3A-csBJinUG.js} +1 -1
- package/dist/deck-client/assets/{journeyDiagram-VCZTEJTY-1mP2JwCk.js → journeyDiagram-VCZTEJTY-B2dZRDOR.js} +1 -1
- package/dist/deck-client/assets/{kanban-definition-6JOO6SKY-vT0Xrqh9.js → kanban-definition-6JOO6SKY-Dyznt2QN.js} +1 -1
- package/dist/deck-client/assets/{layout-Cw4rS2pn.js → layout-tvNTD61-.js} +1 -1
- package/dist/deck-client/assets/{linear-CzOjL-Ih.js → linear-BTcLgDqZ.js} +1 -1
- package/dist/deck-client/assets/{mermaid.core-DYi3A-qK.js → mermaid.core-CZoq2C4e.js} +4 -4
- package/dist/deck-client/assets/{min-DstloRoL.js → min-J7Zsly_t.js} +1 -1
- package/dist/deck-client/assets/{mindmap-definition-QFDTVHPH-D-cCX2d2.js → mindmap-definition-QFDTVHPH-Ba-GyprJ.js} +1 -1
- package/dist/deck-client/assets/{pieDiagram-DEJITSTG-BqW2NTmy.js → pieDiagram-DEJITSTG-BMLzzK64.js} +1 -1
- package/dist/deck-client/assets/{quadrantDiagram-34T5L4WZ-DbJoWA8f.js → quadrantDiagram-34T5L4WZ-Dh_Ut0Yp.js} +1 -1
- package/dist/deck-client/assets/{requirementDiagram-MS252O5E-DQrUiz_d.js → requirementDiagram-MS252O5E-Dkva9qg0.js} +1 -1
- package/dist/deck-client/assets/{sankeyDiagram-XADWPNL6-kB7PZc3g.js → sankeyDiagram-XADWPNL6-3lcCBbua.js} +1 -1
- package/dist/deck-client/assets/{sequenceDiagram-FGHM5R23-CpyVu1TN.js → sequenceDiagram-FGHM5R23-MH0H8qIE.js} +1 -1
- package/dist/deck-client/assets/{stateDiagram-FHFEXIEX-CjqQcnty.js → stateDiagram-FHFEXIEX-ByLZbwGe.js} +1 -1
- package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-C87YsgaN.js +1 -0
- package/dist/deck-client/assets/{timeline-definition-GMOUNBTQ-B2PAO9bk.js → timeline-definition-GMOUNBTQ-COT8qN-f.js} +1 -1
- package/dist/deck-client/assets/{vennDiagram-DHZGUBPP-C0G3ItCr.js → vennDiagram-DHZGUBPP-YuKnKVX_.js} +1 -1
- package/dist/deck-client/assets/wardley-RL74JXVD-C4YfwXTm.js +162 -0
- package/dist/deck-client/assets/{wardleyDiagram-NUSXRM2D-B-qtbNZe.js → wardleyDiagram-NUSXRM2D-BuLtRVnC.js} +1 -1
- package/dist/deck-client/assets/{xychartDiagram-5P7HB3ND-41kcBoBE.js → xychartDiagram-5P7HB3ND-CcIc1c6H.js} +1 -1
- package/dist/deck-client/index.html +1 -1
- package/dist/server/init-entry.js +40 -11
- package/dist/server/radar-docker-init-entry.js +34 -11
- package/dist/server/rover-entry.js +515 -130
- package/package.json +1 -1
- package/scaffolds/ls-marketplace/plugins/kit/skills/ship/SKILL.md +24 -18
- package/dist/deck-client/assets/channel-Cw2WDt9a.js +0 -1
- package/dist/deck-client/assets/classDiagram-6PBFFD2Q-JLUXVCUr.js +0 -1
- package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-JLUXVCUr.js +0 -1
- package/dist/deck-client/assets/clone-H0XCnSb6.js +0 -1
- package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-tfMSn8xx.js +0 -1
- package/dist/deck-client/assets/wardley-RL74JXVD-B0TVaOmp.js +0 -162
|
@@ -33,12 +33,201 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
33
33
|
mod
|
|
34
34
|
));
|
|
35
35
|
|
|
36
|
+
// src/server/cf-access.ts
|
|
37
|
+
async function cf(opts) {
|
|
38
|
+
const res = await fetch(`${CF_API_BASE}${opts.path}`, {
|
|
39
|
+
method: opts.method,
|
|
40
|
+
headers: {
|
|
41
|
+
Authorization: `Bearer ${opts.apiToken}`,
|
|
42
|
+
"Content-Type": "application/json",
|
|
43
|
+
Accept: "application/json",
|
|
44
|
+
"User-Agent": "launch-kit/cf-access"
|
|
45
|
+
},
|
|
46
|
+
body: opts.body !== void 0 ? JSON.stringify(opts.body) : void 0,
|
|
47
|
+
signal: AbortSignal.timeout(15e3)
|
|
48
|
+
});
|
|
49
|
+
const text = await res.text();
|
|
50
|
+
try {
|
|
51
|
+
return text ? JSON.parse(text) : { success: false };
|
|
52
|
+
} catch {
|
|
53
|
+
throw new Error(`[cf-access] ${opts.method} ${opts.path} \u2192 ${res.status}, non-JSON: ${text.slice(0, 200)}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function fail(env, what) {
|
|
57
|
+
throw new Error(`[cf-access] ${what} failed: ${JSON.stringify(env.errors)}`);
|
|
58
|
+
}
|
|
59
|
+
function loadState(path) {
|
|
60
|
+
if (!(0, import_node_fs.existsSync)(path)) return null;
|
|
61
|
+
try {
|
|
62
|
+
const parsed = JSON.parse((0, import_node_fs.readFileSync)(path, "utf8"));
|
|
63
|
+
return typeof parsed?.accountId === "string" ? parsed : null;
|
|
64
|
+
} catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function saveState(path, state) {
|
|
69
|
+
const dir = (0, import_node_path.dirname)(path);
|
|
70
|
+
if (!(0, import_node_fs.existsSync)(dir)) (0, import_node_fs.mkdirSync)(dir, { recursive: true });
|
|
71
|
+
(0, import_node_fs.writeFileSync)(path, JSON.stringify(state, null, 2));
|
|
72
|
+
}
|
|
73
|
+
async function getAccessAuthDomain(apiToken, accountId) {
|
|
74
|
+
const res = await cf({
|
|
75
|
+
apiToken,
|
|
76
|
+
method: "GET",
|
|
77
|
+
path: `/accounts/${accountId}/access/organizations`
|
|
78
|
+
});
|
|
79
|
+
if (!res.success || !res.result?.auth_domain) {
|
|
80
|
+
fail(res, "GET access/organizations (is Zero Trust enabled on this account?)");
|
|
81
|
+
}
|
|
82
|
+
return res.result.auth_domain;
|
|
83
|
+
}
|
|
84
|
+
async function ensureAccessIdp(input) {
|
|
85
|
+
const config = {
|
|
86
|
+
client_id: input.clientId,
|
|
87
|
+
client_secret: input.clientSecret,
|
|
88
|
+
auth_url: `${input.issuer}/api/oidc/authorize`,
|
|
89
|
+
token_url: `${input.issuer}/api/oidc/token`,
|
|
90
|
+
certs_url: `${input.issuer}/.well-known/jwks.json`,
|
|
91
|
+
scopes: ["openid", "email", "profile"],
|
|
92
|
+
claims: ["org", "project_access", "roles", "email"],
|
|
93
|
+
email_claim_name: "email",
|
|
94
|
+
pkce_enabled: true
|
|
95
|
+
};
|
|
96
|
+
const body = { name: IDP_NAME, type: "oidc", config };
|
|
97
|
+
let idpId = input.knownIdpId;
|
|
98
|
+
if (!idpId) {
|
|
99
|
+
const list3 = await cf({
|
|
100
|
+
apiToken: input.apiToken,
|
|
101
|
+
method: "GET",
|
|
102
|
+
path: `/accounts/${input.accountId}/access/identity_providers`
|
|
103
|
+
});
|
|
104
|
+
if (!list3.success) fail(list3, "list identity_providers");
|
|
105
|
+
idpId = (list3.result ?? []).find((p) => p.name === IDP_NAME)?.id ?? null;
|
|
106
|
+
}
|
|
107
|
+
if (idpId) {
|
|
108
|
+
const upd = await cf({
|
|
109
|
+
apiToken: input.apiToken,
|
|
110
|
+
method: "PUT",
|
|
111
|
+
path: `/accounts/${input.accountId}/access/identity_providers/${idpId}`,
|
|
112
|
+
body
|
|
113
|
+
});
|
|
114
|
+
if (!upd.success || !upd.result) fail(upd, "update identity_provider");
|
|
115
|
+
return upd.result.id;
|
|
116
|
+
}
|
|
117
|
+
const created = await cf({
|
|
118
|
+
apiToken: input.apiToken,
|
|
119
|
+
method: "POST",
|
|
120
|
+
path: `/accounts/${input.accountId}/access/identity_providers`,
|
|
121
|
+
body
|
|
122
|
+
});
|
|
123
|
+
if (!created.success || !created.result) fail(created, "create identity_provider");
|
|
124
|
+
return created.result.id;
|
|
125
|
+
}
|
|
126
|
+
async function ensureAccessApp(input) {
|
|
127
|
+
const { service } = input;
|
|
128
|
+
const appDomain = service.path ? `${service.hostname}${service.path}` : service.hostname;
|
|
129
|
+
const policy = service.bypass ? {
|
|
130
|
+
name: "launch-kit-public-bypass",
|
|
131
|
+
decision: "bypass",
|
|
132
|
+
include: [{ everyone: {} }]
|
|
133
|
+
} : {
|
|
134
|
+
name: "launch-kit-org-allow",
|
|
135
|
+
decision: "allow",
|
|
136
|
+
include: [
|
|
137
|
+
{
|
|
138
|
+
oidc: {
|
|
139
|
+
identity_provider_id: input.idpId,
|
|
140
|
+
claim_name: "org",
|
|
141
|
+
claim_value: input.organizationId
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
]
|
|
145
|
+
};
|
|
146
|
+
const body = service.bypass ? {
|
|
147
|
+
name: `launch-kit ${appDomain} (public)`,
|
|
148
|
+
domain: appDomain,
|
|
149
|
+
type: "self_hosted",
|
|
150
|
+
policies: [policy]
|
|
151
|
+
} : {
|
|
152
|
+
name: `launch-kit ${appDomain}`,
|
|
153
|
+
domain: appDomain,
|
|
154
|
+
type: "self_hosted",
|
|
155
|
+
// Bot terminal = RCE surface → short session. Read portals = a workday.
|
|
156
|
+
session_duration: service.strict ? "30m" : "24h",
|
|
157
|
+
allowed_idps: [input.idpId],
|
|
158
|
+
auto_redirect_to_identity: true,
|
|
159
|
+
policies: [policy]
|
|
160
|
+
};
|
|
161
|
+
const list3 = await cf({
|
|
162
|
+
apiToken: input.apiToken,
|
|
163
|
+
method: "GET",
|
|
164
|
+
path: `/accounts/${input.accountId}/access/apps`
|
|
165
|
+
});
|
|
166
|
+
if (!list3.success) fail(list3, "list access apps");
|
|
167
|
+
const existing = (list3.result ?? []).find((a) => a.domain === appDomain);
|
|
168
|
+
if (existing) {
|
|
169
|
+
const upd = await cf({
|
|
170
|
+
apiToken: input.apiToken,
|
|
171
|
+
method: "PUT",
|
|
172
|
+
path: `/accounts/${input.accountId}/access/apps/${existing.id}`,
|
|
173
|
+
body
|
|
174
|
+
});
|
|
175
|
+
if (!upd.success || !upd.result) fail(upd, `update access app ${appDomain}`);
|
|
176
|
+
return upd.result.id;
|
|
177
|
+
}
|
|
178
|
+
const created = await cf({
|
|
179
|
+
apiToken: input.apiToken,
|
|
180
|
+
method: "POST",
|
|
181
|
+
path: `/accounts/${input.accountId}/access/apps`,
|
|
182
|
+
body
|
|
183
|
+
});
|
|
184
|
+
if (!created.success || !created.result) fail(created, `create access app ${appDomain}`);
|
|
185
|
+
return created.result.id;
|
|
186
|
+
}
|
|
187
|
+
async function provisionAccess(input) {
|
|
188
|
+
const authDomain = await getAccessAuthDomain(input.apiToken, input.accountId);
|
|
189
|
+
const callbackUrl = `https://${authDomain}/cdn-cgi/access/callback`;
|
|
190
|
+
const { clientId, clientSecret, organizationId } = await input.registerClient([callbackUrl]);
|
|
191
|
+
const prior = loadState(input.stateFile);
|
|
192
|
+
const idpId = await ensureAccessIdp({
|
|
193
|
+
apiToken: input.apiToken,
|
|
194
|
+
accountId: input.accountId,
|
|
195
|
+
issuer: input.issuer,
|
|
196
|
+
clientId,
|
|
197
|
+
clientSecret,
|
|
198
|
+
knownIdpId: prior?.idpId ?? null
|
|
199
|
+
});
|
|
200
|
+
saveState(input.stateFile, { idpId, accountId: input.accountId });
|
|
201
|
+
const appIds = {};
|
|
202
|
+
for (const service of input.services) {
|
|
203
|
+
const appDomain = service.path ? `${service.hostname}${service.path}` : service.hostname;
|
|
204
|
+
appIds[appDomain] = await ensureAccessApp({
|
|
205
|
+
apiToken: input.apiToken,
|
|
206
|
+
accountId: input.accountId,
|
|
207
|
+
idpId,
|
|
208
|
+
organizationId,
|
|
209
|
+
service
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
return { idpId, authDomain, appIds };
|
|
213
|
+
}
|
|
214
|
+
var import_node_fs, import_node_path, CF_API_BASE, IDP_NAME;
|
|
215
|
+
var init_cf_access = __esm({
|
|
216
|
+
"src/server/cf-access.ts"() {
|
|
217
|
+
"use strict";
|
|
218
|
+
import_node_fs = require("node:fs");
|
|
219
|
+
import_node_path = require("node:path");
|
|
220
|
+
CF_API_BASE = "https://api.cloudflare.com/client/v4";
|
|
221
|
+
IDP_NAME = "launch-kit-oidc";
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
|
|
36
225
|
// src/server/cf-ingress.ts
|
|
37
226
|
function serviceLabel(s) {
|
|
38
227
|
return s.label ?? s.name;
|
|
39
228
|
}
|
|
40
|
-
async function
|
|
41
|
-
const res = await fetch(`${
|
|
229
|
+
async function cf2(opts) {
|
|
230
|
+
const res = await fetch(`${CF_API_BASE2}${opts.path}`, {
|
|
42
231
|
method: opts.method,
|
|
43
232
|
headers: {
|
|
44
233
|
Authorization: `Bearer ${opts.apiToken}`,
|
|
@@ -63,7 +252,7 @@ function isNotFound(env) {
|
|
|
63
252
|
}
|
|
64
253
|
async function findTunnelByName(input) {
|
|
65
254
|
const q = new URLSearchParams({ name: input.tunnelName, is_deleted: "false" }).toString();
|
|
66
|
-
const res = await
|
|
255
|
+
const res = await cf2({
|
|
67
256
|
apiToken: input.apiToken,
|
|
68
257
|
method: "GET",
|
|
69
258
|
path: `/accounts/${input.accountId}/cfd_tunnel?${q}`
|
|
@@ -72,10 +261,10 @@ async function findTunnelByName(input) {
|
|
|
72
261
|
const live = res.result.find((t) => t.name === input.tunnelName && !t.deleted_at);
|
|
73
262
|
return live?.id ?? null;
|
|
74
263
|
}
|
|
75
|
-
function
|
|
76
|
-
if (!(0,
|
|
264
|
+
function loadState2(path) {
|
|
265
|
+
if (!(0, import_node_fs2.existsSync)(path)) return null;
|
|
77
266
|
try {
|
|
78
|
-
const parsed = JSON.parse((0,
|
|
267
|
+
const parsed = JSON.parse((0, import_node_fs2.readFileSync)(path, "utf8"));
|
|
79
268
|
if (typeof parsed?.tunnelId === "string" && typeof parsed?.accountId === "string") {
|
|
80
269
|
return parsed;
|
|
81
270
|
}
|
|
@@ -84,14 +273,14 @@ function loadState(path) {
|
|
|
84
273
|
return null;
|
|
85
274
|
}
|
|
86
275
|
}
|
|
87
|
-
function
|
|
88
|
-
const dir = (0,
|
|
89
|
-
if (!(0,
|
|
90
|
-
(0,
|
|
276
|
+
function saveState2(path, state) {
|
|
277
|
+
const dir = (0, import_node_path2.dirname)(path);
|
|
278
|
+
if (!(0, import_node_fs2.existsSync)(dir)) (0, import_node_fs2.mkdirSync)(dir, { recursive: true });
|
|
279
|
+
(0, import_node_fs2.writeFileSync)(path, JSON.stringify(state, null, 2));
|
|
91
280
|
}
|
|
92
281
|
async function ensureTunnel(input, knownTunnelId) {
|
|
93
282
|
if (knownTunnelId) {
|
|
94
|
-
const got = await
|
|
283
|
+
const got = await cf2({
|
|
95
284
|
apiToken: input.apiToken,
|
|
96
285
|
method: "GET",
|
|
97
286
|
path: `/accounts/${input.accountId}/cfd_tunnel/${knownTunnelId}`
|
|
@@ -108,7 +297,7 @@ async function ensureTunnel(input, knownTunnelId) {
|
|
|
108
297
|
console.log(`[cf] adopted existing tunnel "${input.tunnelName}" (${existing}) \u2014 local state was missing`);
|
|
109
298
|
return existing;
|
|
110
299
|
}
|
|
111
|
-
const created = await
|
|
300
|
+
const created = await cf2({
|
|
112
301
|
apiToken: input.apiToken,
|
|
113
302
|
method: "POST",
|
|
114
303
|
path: `/accounts/${input.accountId}/cfd_tunnel`,
|
|
@@ -125,7 +314,7 @@ async function ensureTunnel(input, knownTunnelId) {
|
|
|
125
314
|
throw new Error(`[cf] tunnel create failed: ${JSON.stringify(created.errors)}`);
|
|
126
315
|
}
|
|
127
316
|
async function fetchConnectorToken(input, tunnelId) {
|
|
128
|
-
const res = await
|
|
317
|
+
const res = await cf2({
|
|
129
318
|
apiToken: input.apiToken,
|
|
130
319
|
method: "GET",
|
|
131
320
|
path: `/accounts/${input.accountId}/cfd_tunnel/${tunnelId}/token`
|
|
@@ -141,7 +330,7 @@ async function setIngressConfig(input, tunnelId) {
|
|
|
141
330
|
service: `http://127.0.0.1:${s.port}`
|
|
142
331
|
}));
|
|
143
332
|
ingress.push({ service: "http_status:404" });
|
|
144
|
-
const res = await
|
|
333
|
+
const res = await cf2({
|
|
145
334
|
apiToken: input.apiToken,
|
|
146
335
|
method: "PUT",
|
|
147
336
|
path: `/accounts/${input.accountId}/cfd_tunnel/${tunnelId}/configurations`,
|
|
@@ -154,7 +343,7 @@ async function setIngressConfig(input, tunnelId) {
|
|
|
154
343
|
async function ensureDnsRecord(input, tunnelId, service) {
|
|
155
344
|
const fqdn = `${serviceLabel(service)}.${input.zone.name}`;
|
|
156
345
|
const target = `${tunnelId}.cfargotunnel.com`;
|
|
157
|
-
const existing = await
|
|
346
|
+
const existing = await cf2({
|
|
158
347
|
apiToken: input.apiToken,
|
|
159
348
|
method: "GET",
|
|
160
349
|
path: `/zones/${input.zone.id}/dns_records?name=${encodeURIComponent(fqdn)}&type=CNAME`
|
|
@@ -162,7 +351,7 @@ async function ensureDnsRecord(input, tunnelId, service) {
|
|
|
162
351
|
if (existing.success && Array.isArray(existing.result) && existing.result.length > 0) {
|
|
163
352
|
const rec = existing.result[0];
|
|
164
353
|
if (rec.content === target) return;
|
|
165
|
-
const upd = await
|
|
354
|
+
const upd = await cf2({
|
|
166
355
|
apiToken: input.apiToken,
|
|
167
356
|
method: "PUT",
|
|
168
357
|
path: `/zones/${input.zone.id}/dns_records/${rec.id}`,
|
|
@@ -173,7 +362,7 @@ async function ensureDnsRecord(input, tunnelId, service) {
|
|
|
173
362
|
}
|
|
174
363
|
return;
|
|
175
364
|
}
|
|
176
|
-
const created = await
|
|
365
|
+
const created = await cf2({
|
|
177
366
|
apiToken: input.apiToken,
|
|
178
367
|
method: "POST",
|
|
179
368
|
path: `/zones/${input.zone.id}/dns_records`,
|
|
@@ -184,9 +373,9 @@ async function ensureDnsRecord(input, tunnelId, service) {
|
|
|
184
373
|
throw new Error(`[cf] DNS record create for ${fqdn} failed: ${JSON.stringify(created.errors)}`);
|
|
185
374
|
}
|
|
186
375
|
async function provisionIngress(input) {
|
|
187
|
-
const prior =
|
|
376
|
+
const prior = loadState2(input.stateFile);
|
|
188
377
|
const tunnelId = await ensureTunnel(input, prior?.tunnelId ?? null);
|
|
189
|
-
|
|
378
|
+
saveState2(input.stateFile, {
|
|
190
379
|
tunnelId,
|
|
191
380
|
accountId: input.accountId,
|
|
192
381
|
tunnelName: input.tunnelName,
|
|
@@ -199,13 +388,13 @@ async function provisionIngress(input) {
|
|
|
199
388
|
for (const s of input.services) hostnames[s.name] = `${serviceLabel(s)}.${input.zone.name}`;
|
|
200
389
|
return { tunnelId, connectorToken, hostnames };
|
|
201
390
|
}
|
|
202
|
-
var
|
|
391
|
+
var import_node_fs2, import_node_path2, CF_API_BASE2, CF_ERR_DNS_RECORD_EXISTS;
|
|
203
392
|
var init_cf_ingress = __esm({
|
|
204
393
|
"src/server/cf-ingress.ts"() {
|
|
205
394
|
"use strict";
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
395
|
+
import_node_fs2 = require("node:fs");
|
|
396
|
+
import_node_path2 = require("node:path");
|
|
397
|
+
CF_API_BASE2 = "https://api.cloudflare.com/client/v4";
|
|
209
398
|
CF_ERR_DNS_RECORD_EXISTS = 81053;
|
|
210
399
|
}
|
|
211
400
|
});
|
|
@@ -516,16 +705,16 @@ __export(state_exports, {
|
|
|
516
705
|
saveRoverState: () => saveRoverState
|
|
517
706
|
});
|
|
518
707
|
function stateDir() {
|
|
519
|
-
return (0,
|
|
708
|
+
return (0, import_node_path3.join)((0, import_node_os.homedir)(), LAUNCHSECURE_DIR, "rover");
|
|
520
709
|
}
|
|
521
710
|
function statePath(roverId) {
|
|
522
|
-
return (0,
|
|
711
|
+
return (0, import_node_path3.join)(stateDir(), `${roverId}.json`);
|
|
523
712
|
}
|
|
524
713
|
function loadRoverState(roverId) {
|
|
525
714
|
const p = statePath(roverId);
|
|
526
|
-
if (!(0,
|
|
715
|
+
if (!(0, import_node_fs3.existsSync)(p)) return null;
|
|
527
716
|
try {
|
|
528
|
-
const data = JSON.parse((0,
|
|
717
|
+
const data = JSON.parse((0, import_node_fs3.readFileSync)(p, "utf-8"));
|
|
529
718
|
if (!data.roverId || !data.secret || !data.tunnelUrl) return null;
|
|
530
719
|
return data;
|
|
531
720
|
} catch {
|
|
@@ -533,21 +722,21 @@ function loadRoverState(roverId) {
|
|
|
533
722
|
}
|
|
534
723
|
}
|
|
535
724
|
function saveRoverState(state) {
|
|
536
|
-
(0,
|
|
725
|
+
(0, import_node_fs3.mkdirSync)(stateDir(), { recursive: true });
|
|
537
726
|
const final = statePath(state.roverId);
|
|
538
727
|
const tmp = `${final}.tmp`;
|
|
539
|
-
(0,
|
|
540
|
-
(0,
|
|
728
|
+
(0, import_node_fs3.writeFileSync)(tmp, JSON.stringify(state, null, 2) + "\n", { mode: 384 });
|
|
729
|
+
(0, import_node_fs3.renameSync)(tmp, final);
|
|
541
730
|
}
|
|
542
731
|
function clearRoverState(roverId) {
|
|
543
732
|
try {
|
|
544
|
-
(0,
|
|
733
|
+
(0, import_node_fs3.unlinkSync)(statePath(roverId));
|
|
545
734
|
} catch {
|
|
546
735
|
}
|
|
547
736
|
}
|
|
548
737
|
function listRoverIds() {
|
|
549
738
|
const dir = stateDir();
|
|
550
|
-
if (!(0,
|
|
739
|
+
if (!(0, import_node_fs3.existsSync)(dir)) return [];
|
|
551
740
|
try {
|
|
552
741
|
const fs = require("node:fs");
|
|
553
742
|
return fs.readdirSync(dir).filter((f) => f.endsWith(".json") && !f.includes(".tunnel.")).map((f) => f.slice(0, -5));
|
|
@@ -555,13 +744,13 @@ function listRoverIds() {
|
|
|
555
744
|
return [];
|
|
556
745
|
}
|
|
557
746
|
}
|
|
558
|
-
var
|
|
747
|
+
var import_node_fs3, import_node_os, import_node_path3;
|
|
559
748
|
var init_state = __esm({
|
|
560
749
|
"src/server/rover/state.ts"() {
|
|
561
750
|
"use strict";
|
|
562
|
-
|
|
751
|
+
import_node_fs3 = require("node:fs");
|
|
563
752
|
import_node_os = require("node:os");
|
|
564
|
-
|
|
753
|
+
import_node_path3 = require("node:path");
|
|
565
754
|
init_launch_kit_paths();
|
|
566
755
|
}
|
|
567
756
|
});
|
|
@@ -610,7 +799,7 @@ var require_package = __commonJS({
|
|
|
610
799
|
"package.json"(exports2, module2) {
|
|
611
800
|
module2.exports = {
|
|
612
801
|
name: "@launchsecure/launch-kit",
|
|
613
|
-
version: "0.0.
|
|
802
|
+
version: "0.0.45",
|
|
614
803
|
description: "LaunchSecure toolkit \u2014 launch-sequencer (pipeline runner + terminal bridge), launch-radar (feedback webhook receiver), launch-chart (project graph MCP), launch-deck (visual playground MCP), launch-kit-beacon (feedback Web Component), launch-recall (file-watcher backup). launch-pod is the container image these run inside.",
|
|
615
804
|
license: "MIT",
|
|
616
805
|
author: "LaunchSecure - AutomateWithUs",
|
|
@@ -881,7 +1070,7 @@ function renderDashboard(state, pods) {
|
|
|
881
1070
|
</head><body>
|
|
882
1071
|
<div class="topbar">
|
|
883
1072
|
<div class="brand">
|
|
884
|
-
<span class="logobox">${roverMark(
|
|
1073
|
+
<span class="logobox">${roverMark(28, "hsl(259 28% 12%)", "full")}</span>
|
|
885
1074
|
<span class="wordmark">LaunchRover</span>
|
|
886
1075
|
</div>
|
|
887
1076
|
<div class="context">
|
|
@@ -1005,14 +1194,14 @@ function coerceEntry(raw, index) {
|
|
|
1005
1194
|
if (r.args !== void 0 && (!Array.isArray(r.args) || r.args.some((a) => typeof a !== "string"))) {
|
|
1006
1195
|
throw new Error(`[launch-kit-services] entry #${index}: args must be a string[]`);
|
|
1007
1196
|
}
|
|
1008
|
-
const
|
|
1009
|
-
return {
|
|
1197
|
+
const spec = {
|
|
1010
1198
|
name: r.name,
|
|
1011
1199
|
port: r.port,
|
|
1012
1200
|
bin: r.bin,
|
|
1013
|
-
args: r.args ?? []
|
|
1014
|
-
skipSpawn
|
|
1201
|
+
args: r.args ?? []
|
|
1015
1202
|
};
|
|
1203
|
+
if (r.skipSpawn === true || r.bin === "") spec.skipSpawn = true;
|
|
1204
|
+
return spec;
|
|
1016
1205
|
}
|
|
1017
1206
|
function validate(services) {
|
|
1018
1207
|
if (services.length === 0) {
|
|
@@ -1057,11 +1246,11 @@ function resolveServices(opts = {}) {
|
|
|
1057
1246
|
}
|
|
1058
1247
|
return validate(parsed.map(coerceEntry));
|
|
1059
1248
|
}
|
|
1060
|
-
const filePath = (0,
|
|
1061
|
-
if ((0,
|
|
1249
|
+
const filePath = (0, import_node_path4.join)(cwd, ".launchpod", "services.json");
|
|
1250
|
+
if ((0, import_node_fs4.existsSync)(filePath)) {
|
|
1062
1251
|
let parsed;
|
|
1063
1252
|
try {
|
|
1064
|
-
parsed = JSON.parse((0,
|
|
1253
|
+
parsed = JSON.parse((0, import_node_fs4.readFileSync)(filePath, "utf8"));
|
|
1065
1254
|
} catch (err) {
|
|
1066
1255
|
throw new Error(`[launch-kit-services] ${filePath} is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
1067
1256
|
}
|
|
@@ -1072,12 +1261,12 @@ function resolveServices(opts = {}) {
|
|
|
1072
1261
|
}
|
|
1073
1262
|
return validate(defaultServices());
|
|
1074
1263
|
}
|
|
1075
|
-
var
|
|
1264
|
+
var import_node_fs4, import_node_path4, SHORTHANDS, DNS_NAME_RE, SHORTHAND_NAMES;
|
|
1076
1265
|
var init_launch_kit_services = __esm({
|
|
1077
1266
|
"src/server/launch-kit-services.ts"() {
|
|
1078
1267
|
"use strict";
|
|
1079
|
-
|
|
1080
|
-
|
|
1268
|
+
import_node_fs4 = require("node:fs");
|
|
1269
|
+
import_node_path4 = require("node:path");
|
|
1081
1270
|
SHORTHANDS = {
|
|
1082
1271
|
radar: { port: 3517, bin: "launch-radar", args: [] },
|
|
1083
1272
|
sequencer: { port: 3517, bin: "launch-sequencer", args: [] },
|
|
@@ -1097,12 +1286,23 @@ var init_launch_kit_services = __esm({
|
|
|
1097
1286
|
function slugLabel(projectSlug) {
|
|
1098
1287
|
return projectSlug.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
1099
1288
|
}
|
|
1100
|
-
function
|
|
1101
|
-
|
|
1289
|
+
function roverToken(roverId) {
|
|
1290
|
+
let h = 2166136261;
|
|
1291
|
+
for (let i = 0; i < roverId.length; i++) {
|
|
1292
|
+
h ^= roverId.charCodeAt(i);
|
|
1293
|
+
h = Math.imul(h, 16777619);
|
|
1294
|
+
}
|
|
1295
|
+
return ((h >>> 0) % 2176782336).toString(36).padStart(6, "0");
|
|
1296
|
+
}
|
|
1297
|
+
function serviceLabel2(projectSlug, serviceName, roverId) {
|
|
1298
|
+
return `${serviceName}-${slugLabel(projectSlug)}-${roverToken(roverId)}`;
|
|
1102
1299
|
}
|
|
1103
|
-
function
|
|
1104
|
-
|
|
1105
|
-
|
|
1300
|
+
function serviceHostname(projectSlug, serviceName, roverId, zone) {
|
|
1301
|
+
return `${serviceLabel2(projectSlug, serviceName, roverId)}.${zone}`;
|
|
1302
|
+
}
|
|
1303
|
+
function serviceUrl(projectSlug, serviceName, roverId, zone) {
|
|
1304
|
+
if (!zone || !roverId) return null;
|
|
1305
|
+
return `https://${serviceHostname(projectSlug, serviceName, roverId, zone)}`;
|
|
1106
1306
|
}
|
|
1107
1307
|
var init_hostnames = __esm({
|
|
1108
1308
|
"src/server/rover/runtime/hostnames.ts"() {
|
|
@@ -1124,8 +1324,12 @@ function validateUpRequest(state, body) {
|
|
|
1124
1324
|
if (typeof body.projectSlug !== "string" || !DNS_SLUG_RE.test(body.projectSlug)) {
|
|
1125
1325
|
throw new PodError("projectSlug must be a DNS-safe string (a-z, 0-9, -)");
|
|
1126
1326
|
}
|
|
1127
|
-
|
|
1128
|
-
|
|
1327
|
+
const hasBlob = typeof body.claudeCredentialsB64 === "string" && body.claudeCredentialsB64.length >= 16;
|
|
1328
|
+
const hasToken = typeof body.claudeOauthToken === "string" && body.claudeOauthToken.length >= 16;
|
|
1329
|
+
if (!hasBlob && !hasToken) {
|
|
1330
|
+
throw new PodError(
|
|
1331
|
+
"one of claudeCredentialsB64 (base64-encoded Claude credentials JSON) or claudeOauthToken (CLAUDE_CODE_OAUTH_TOKEN from `claude setup-token`) is required"
|
|
1332
|
+
);
|
|
1129
1333
|
}
|
|
1130
1334
|
}
|
|
1131
1335
|
var DNS_SLUG_RE, PodError;
|
|
@@ -1183,12 +1387,17 @@ async function create(state, body) {
|
|
|
1183
1387
|
if (body.podId) runArgs.push("--label", `launch-rover.podId=${body.podId}`);
|
|
1184
1388
|
if (body.name) runArgs.push("--label", `launch-rover.name=${body.name}`);
|
|
1185
1389
|
const envVars = {
|
|
1186
|
-
CLAUDE_CREDENTIALS_B64: body.claudeCredentialsB64,
|
|
1187
1390
|
LS_PAT: state.installPat,
|
|
1188
1391
|
LS_ORG_SLUG: state.orgSlug,
|
|
1189
1392
|
LS_PROJECT_SLUG: body.projectSlug,
|
|
1393
|
+
// Folded into each service's DNS label by the pod entry so two rovers
|
|
1394
|
+
// running the same project on the same zone don't collide — see
|
|
1395
|
+
// runtime/hostnames.ts. Also read back in servicesForContainer below.
|
|
1396
|
+
LS_ROVER_ID: state.roverId,
|
|
1190
1397
|
LS_SERVER_URL: rewriteLocalhostForContainer(state.serverUrl)
|
|
1191
1398
|
};
|
|
1399
|
+
if (body.claudeCredentialsB64) envVars.CLAUDE_CREDENTIALS_B64 = body.claudeCredentialsB64;
|
|
1400
|
+
if (body.claudeOauthToken) envVars.CLAUDE_CODE_OAUTH_TOKEN = body.claudeOauthToken;
|
|
1192
1401
|
if (body.services && Array.isArray(body.services)) {
|
|
1193
1402
|
envVars.LAUNCHKIT_SERVICES = JSON.stringify(body.services);
|
|
1194
1403
|
}
|
|
@@ -1205,7 +1414,7 @@ async function create(state, body) {
|
|
|
1205
1414
|
const containerId = (await dockerRun(runArgs)).stdout.trim();
|
|
1206
1415
|
return { podId: body.podId ?? null, projectSlug: body.projectSlug, containerId, image, createdAt, runtime: "docker" };
|
|
1207
1416
|
} finally {
|
|
1208
|
-
if (dockerConfigDir) (0,
|
|
1417
|
+
if (dockerConfigDir) (0, import_node_fs5.rmSync)(dockerConfigDir, { recursive: true, force: true });
|
|
1209
1418
|
}
|
|
1210
1419
|
}
|
|
1211
1420
|
async function stop(_state, body) {
|
|
@@ -1287,6 +1496,7 @@ async function servicesForContainer(ref, projectSlug) {
|
|
|
1287
1496
|
if (eq > 0) env[kv.slice(0, eq)] = kv.slice(eq + 1);
|
|
1288
1497
|
}
|
|
1289
1498
|
const zone = env.LAUNCHKIT_CF_BASE_DOMAIN || null;
|
|
1499
|
+
const roverId = env.LS_ROVER_ID || null;
|
|
1290
1500
|
let specs;
|
|
1291
1501
|
try {
|
|
1292
1502
|
specs = resolveServices({
|
|
@@ -1295,7 +1505,7 @@ async function servicesForContainer(ref, projectSlug) {
|
|
|
1295
1505
|
} catch {
|
|
1296
1506
|
return void 0;
|
|
1297
1507
|
}
|
|
1298
|
-
return specs.filter((s) => !s.skipSpawn).map((s) => ({ name: s.name, port: s.port, url: serviceUrl(projectSlug, s.name, zone) }));
|
|
1508
|
+
return specs.filter((s) => !s.skipSpawn).map((s) => ({ name: s.name, port: s.port, url: serviceUrl(projectSlug, s.name, roverId, zone) }));
|
|
1299
1509
|
}
|
|
1300
1510
|
async function purgeAll(state) {
|
|
1301
1511
|
const { pods } = await list(state);
|
|
@@ -1385,10 +1595,10 @@ function setupDockerConfig() {
|
|
|
1385
1595
|
);
|
|
1386
1596
|
return null;
|
|
1387
1597
|
}
|
|
1388
|
-
const dir = (0,
|
|
1598
|
+
const dir = (0, import_node_fs5.mkdtempSync)((0, import_node_path5.join)((0, import_node_os2.tmpdir)(), "launch-rover-docker-"));
|
|
1389
1599
|
const auth = Buffer.from(`${GHCR_USERNAME}:${token}`, "utf-8").toString("base64");
|
|
1390
1600
|
const config = { auths: { [GHCR_REGISTRY]: { auth } } };
|
|
1391
|
-
(0,
|
|
1601
|
+
(0, import_node_fs5.writeFileSync)((0, import_node_path5.join)(dir, "config.json"), JSON.stringify(config), { mode: 384 });
|
|
1392
1602
|
return dir;
|
|
1393
1603
|
}
|
|
1394
1604
|
function parsePsRow(row) {
|
|
@@ -1419,14 +1629,14 @@ function parseLabels(raw) {
|
|
|
1419
1629
|
}
|
|
1420
1630
|
return out;
|
|
1421
1631
|
}
|
|
1422
|
-
var import_node_child_process2,
|
|
1632
|
+
var import_node_child_process2, import_node_fs5, import_node_os2, import_node_path5, import_node_util, execFileAsync, DEFAULT_IMAGE, GHCR_REGISTRY, GHCR_USERNAME, MANAGED_LABEL, POD_NAME_PREFIX, VOLUME_PREFIX, dockerRuntime;
|
|
1423
1633
|
var init_docker = __esm({
|
|
1424
1634
|
"src/server/rover/runtime/docker.ts"() {
|
|
1425
1635
|
"use strict";
|
|
1426
1636
|
import_node_child_process2 = require("node:child_process");
|
|
1427
|
-
|
|
1637
|
+
import_node_fs5 = require("node:fs");
|
|
1428
1638
|
import_node_os2 = require("node:os");
|
|
1429
|
-
|
|
1639
|
+
import_node_path5 = require("node:path");
|
|
1430
1640
|
import_node_util = require("node:util");
|
|
1431
1641
|
init_launch_kit_services();
|
|
1432
1642
|
init_hostnames();
|
|
@@ -1474,16 +1684,16 @@ var init_ports = __esm({
|
|
|
1474
1684
|
|
|
1475
1685
|
// src/server/rover/runtime/registry.ts
|
|
1476
1686
|
function podsRoot() {
|
|
1477
|
-
return (0,
|
|
1687
|
+
return (0, import_node_path6.join)((0, import_node_os3.homedir)(), LAUNCHSECURE_DIR, "pods");
|
|
1478
1688
|
}
|
|
1479
1689
|
function registryPath() {
|
|
1480
|
-
return (0,
|
|
1690
|
+
return (0, import_node_path6.join)(podsRoot(), "registry.json");
|
|
1481
1691
|
}
|
|
1482
1692
|
function load() {
|
|
1483
1693
|
const p = registryPath();
|
|
1484
|
-
if (!(0,
|
|
1694
|
+
if (!(0, import_node_fs6.existsSync)(p)) return { pods: [] };
|
|
1485
1695
|
try {
|
|
1486
|
-
const doc = JSON.parse((0,
|
|
1696
|
+
const doc = JSON.parse((0, import_node_fs6.readFileSync)(p, "utf-8"));
|
|
1487
1697
|
if (!doc || !Array.isArray(doc.pods)) return { pods: [] };
|
|
1488
1698
|
return doc;
|
|
1489
1699
|
} catch {
|
|
@@ -1492,11 +1702,11 @@ function load() {
|
|
|
1492
1702
|
}
|
|
1493
1703
|
function save(doc) {
|
|
1494
1704
|
const dir = podsRoot();
|
|
1495
|
-
(0,
|
|
1705
|
+
(0, import_node_fs6.mkdirSync)(dir, { recursive: true });
|
|
1496
1706
|
const final = registryPath();
|
|
1497
1707
|
const tmp = `${final}.tmp`;
|
|
1498
|
-
(0,
|
|
1499
|
-
(0,
|
|
1708
|
+
(0, import_node_fs6.writeFileSync)(tmp, JSON.stringify(doc, null, 2) + "\n", { mode: 384 });
|
|
1709
|
+
(0, import_node_fs6.renameSync)(tmp, final);
|
|
1500
1710
|
}
|
|
1501
1711
|
function recordIsAlive(rec) {
|
|
1502
1712
|
try {
|
|
@@ -1538,13 +1748,13 @@ function removeBySlug(projectSlug) {
|
|
|
1538
1748
|
const next = doc.pods.filter((p) => p.projectSlug !== projectSlug);
|
|
1539
1749
|
if (next.length !== doc.pods.length) save({ pods: next });
|
|
1540
1750
|
}
|
|
1541
|
-
var
|
|
1751
|
+
var import_node_fs6, import_node_os3, import_node_path6;
|
|
1542
1752
|
var init_registry = __esm({
|
|
1543
1753
|
"src/server/rover/runtime/registry.ts"() {
|
|
1544
1754
|
"use strict";
|
|
1545
|
-
|
|
1755
|
+
import_node_fs6 = require("node:fs");
|
|
1546
1756
|
import_node_os3 = require("node:os");
|
|
1547
|
-
|
|
1757
|
+
import_node_path6 = require("node:path");
|
|
1548
1758
|
init_launch_kit_paths();
|
|
1549
1759
|
init_ports();
|
|
1550
1760
|
}
|
|
@@ -1555,27 +1765,29 @@ function isDeniedEnvKey(k) {
|
|
|
1555
1765
|
return DENIED_ENV.has(k) || /^(LD_|DYLD_)/.test(k);
|
|
1556
1766
|
}
|
|
1557
1767
|
function buildPodEnv(state, spec, root, services) {
|
|
1558
|
-
const home = (0,
|
|
1559
|
-
const workspace = (0,
|
|
1768
|
+
const home = (0, import_node_path7.join)(root, "home");
|
|
1769
|
+
const workspace = (0, import_node_path7.join)(root, "workspace");
|
|
1560
1770
|
const env = {};
|
|
1561
1771
|
for (const k of BASE_ENV_PASSTHROUGH) {
|
|
1562
1772
|
const v = process.env[k];
|
|
1563
1773
|
if (typeof v === "string") env[k] = v;
|
|
1564
1774
|
}
|
|
1565
1775
|
if (spec.claudeCredentialsB64) env.CLAUDE_CREDENTIALS_B64 = spec.claudeCredentialsB64;
|
|
1776
|
+
if (spec.claudeOauthToken) env.CLAUDE_CODE_OAUTH_TOKEN = spec.claudeOauthToken;
|
|
1566
1777
|
env.LS_PAT = state.installPat;
|
|
1567
1778
|
env.LS_ORG_SLUG = state.orgSlug;
|
|
1568
1779
|
env.LS_PROJECT_SLUG = spec.projectSlug;
|
|
1780
|
+
env.LS_ROVER_ID = state.roverId;
|
|
1569
1781
|
env.LS_SERVER_URL = state.serverUrl;
|
|
1570
1782
|
env.LAUNCHKIT_SERVICES = JSON.stringify(servicesToEntries(services));
|
|
1571
1783
|
if (spec.cfBaseDomain) env.LAUNCHKIT_CF_BASE_DOMAIN = spec.cfBaseDomain;
|
|
1572
1784
|
env.HOME = home;
|
|
1573
1785
|
env.LAUNCHPOD_WORKSPACE = workspace;
|
|
1574
|
-
env.XDG_CONFIG_HOME = (0,
|
|
1575
|
-
env.XDG_DATA_HOME = (0,
|
|
1576
|
-
env.XDG_CACHE_HOME = (0,
|
|
1577
|
-
env.GIT_CONFIG_GLOBAL = (0,
|
|
1578
|
-
env.GH_CONFIG_DIR = (0,
|
|
1786
|
+
env.XDG_CONFIG_HOME = (0, import_node_path7.join)(home, ".config");
|
|
1787
|
+
env.XDG_DATA_HOME = (0, import_node_path7.join)(home, ".local", "share");
|
|
1788
|
+
env.XDG_CACHE_HOME = (0, import_node_path7.join)(home, ".cache");
|
|
1789
|
+
env.GIT_CONFIG_GLOBAL = (0, import_node_path7.join)(home, ".gitconfig");
|
|
1790
|
+
env.GH_CONFIG_DIR = (0, import_node_path7.join)(home, ".config", "gh");
|
|
1579
1791
|
env.LAUNCHPOD_RUNTIME = "process";
|
|
1580
1792
|
env.LAUNCHPOD_NO_PARK = "1";
|
|
1581
1793
|
if (spec.env && typeof spec.env === "object") {
|
|
@@ -1594,18 +1806,18 @@ function servicesToEntries(services) {
|
|
|
1594
1806
|
return services.map((s) => ({ name: s.name, port: s.port, bin: s.bin, args: s.args, skipSpawn: s.skipSpawn }));
|
|
1595
1807
|
}
|
|
1596
1808
|
function ensurePodDirs(root) {
|
|
1597
|
-
const workspace = (0,
|
|
1598
|
-
const home = (0,
|
|
1599
|
-
(0,
|
|
1600
|
-
(0,
|
|
1601
|
-
(0,
|
|
1602
|
-
(0,
|
|
1603
|
-
(0,
|
|
1604
|
-
(0,
|
|
1809
|
+
const workspace = (0, import_node_path7.join)(root, "workspace");
|
|
1810
|
+
const home = (0, import_node_path7.join)(root, "home");
|
|
1811
|
+
(0, import_node_fs7.mkdirSync)(root, { recursive: true, mode: 448 });
|
|
1812
|
+
(0, import_node_fs7.mkdirSync)(workspace, { recursive: true, mode: 448 });
|
|
1813
|
+
(0, import_node_fs7.mkdirSync)((0, import_node_path7.join)(home, ".claude"), { recursive: true, mode: 448 });
|
|
1814
|
+
(0, import_node_fs7.mkdirSync)((0, import_node_path7.join)(home, ".config", "gh"), { recursive: true, mode: 448 });
|
|
1815
|
+
(0, import_node_fs7.mkdirSync)((0, import_node_path7.join)(home, ".local", "share"), { recursive: true, mode: 448 });
|
|
1816
|
+
(0, import_node_fs7.mkdirSync)((0, import_node_path7.join)(home, ".cache"), { recursive: true, mode: 448 });
|
|
1605
1817
|
return { workspace, home };
|
|
1606
1818
|
}
|
|
1607
1819
|
function spawnPod(workspace, env, logFile, projectSlug) {
|
|
1608
|
-
const logFd = (0,
|
|
1820
|
+
const logFd = (0, import_node_fs7.openSync)(logFile, "a", 384);
|
|
1609
1821
|
let child;
|
|
1610
1822
|
try {
|
|
1611
1823
|
child = (0, import_node_child_process3.spawn)(POD_ENTRY_BIN, [POD_ENTRY_ARG], {
|
|
@@ -1616,7 +1828,7 @@ function spawnPod(workspace, env, logFile, projectSlug) {
|
|
|
1616
1828
|
stdio: ["ignore", logFd, logFd]
|
|
1617
1829
|
});
|
|
1618
1830
|
} finally {
|
|
1619
|
-
(0,
|
|
1831
|
+
(0, import_node_fs7.closeSync)(logFd);
|
|
1620
1832
|
}
|
|
1621
1833
|
if (typeof child.pid !== "number") {
|
|
1622
1834
|
throw new PodError(`failed to spawn pod process for "${projectSlug}"`, 500);
|
|
@@ -1624,12 +1836,12 @@ function spawnPod(workspace, env, logFile, projectSlug) {
|
|
|
1624
1836
|
child.unref();
|
|
1625
1837
|
return child.pid;
|
|
1626
1838
|
}
|
|
1627
|
-
function reconstructUrls(projectSlug, services, cfBaseDomain, localOnly) {
|
|
1839
|
+
function reconstructUrls(projectSlug, services, roverId, cfBaseDomain, localOnly) {
|
|
1628
1840
|
const urls = {};
|
|
1629
1841
|
if (localOnly || !cfBaseDomain) return urls;
|
|
1630
1842
|
for (const s of services) {
|
|
1631
1843
|
if (s.skipSpawn) continue;
|
|
1632
|
-
const u = serviceUrl(projectSlug, s.name, cfBaseDomain);
|
|
1844
|
+
const u = serviceUrl(projectSlug, s.name, roverId, cfBaseDomain);
|
|
1633
1845
|
if (u) urls[s.name] = u;
|
|
1634
1846
|
}
|
|
1635
1847
|
return urls;
|
|
@@ -1641,7 +1853,7 @@ async function create2(state, body) {
|
|
|
1641
1853
|
await signalGroupDown(prior, "SIGTERM");
|
|
1642
1854
|
}
|
|
1643
1855
|
const slot = assignSlot(body.projectSlug);
|
|
1644
|
-
const root = (0,
|
|
1856
|
+
const root = (0, import_node_path7.join)(podsRoot(), body.projectSlug);
|
|
1645
1857
|
const { workspace } = ensurePodDirs(root);
|
|
1646
1858
|
let baseServices;
|
|
1647
1859
|
try {
|
|
@@ -1654,12 +1866,12 @@ async function create2(state, body) {
|
|
|
1654
1866
|
const services = assignPortsForSlot(baseServices, slot);
|
|
1655
1867
|
const env = buildPodEnv(state, body, root, services);
|
|
1656
1868
|
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1657
|
-
const logFile = (0,
|
|
1869
|
+
const logFile = (0, import_node_path7.join)(root, "pod.log");
|
|
1658
1870
|
const pid = spawnPod(workspace, env, logFile, body.projectSlug);
|
|
1659
1871
|
const ports = {};
|
|
1660
1872
|
for (const s of services) if (!s.skipSpawn) ports[s.name] = s.port;
|
|
1661
1873
|
const localOnly = body.env?.LAUNCHKIT_LOCAL_ONLY === "1";
|
|
1662
|
-
const urls = reconstructUrls(body.projectSlug, services, body.cfBaseDomain, localOnly);
|
|
1874
|
+
const urls = reconstructUrls(body.projectSlug, services, state.roverId, body.cfBaseDomain, localOnly);
|
|
1663
1875
|
const record = {
|
|
1664
1876
|
projectSlug: body.projectSlug,
|
|
1665
1877
|
podId: body.podId ?? null,
|
|
@@ -1670,9 +1882,13 @@ async function create2(state, body) {
|
|
|
1670
1882
|
ports,
|
|
1671
1883
|
urls,
|
|
1672
1884
|
// Kept so `start` can respawn the same service set + ingress without the
|
|
1673
|
-
// original up request (creds come from the pod's own on-disk store).
|
|
1885
|
+
// original up request (blob creds come from the pod's own on-disk store).
|
|
1674
1886
|
services: baseServices.map((s) => s.name),
|
|
1675
1887
|
cfBaseDomain: body.cfBaseDomain,
|
|
1888
|
+
// Token mode writes nothing to disk, so the long-lived token is kept here to
|
|
1889
|
+
// re-inject on respawn. Undefined (omitted) in blob mode. registry.json is
|
|
1890
|
+
// 0o600 — same protection blob mode gives the on-disk credentials file.
|
|
1891
|
+
claudeOauthToken: body.claudeOauthToken,
|
|
1676
1892
|
stopped: false,
|
|
1677
1893
|
root,
|
|
1678
1894
|
logFile,
|
|
@@ -1709,7 +1925,7 @@ async function tear2(_state, body) {
|
|
|
1709
1925
|
let volumesRemoved = false;
|
|
1710
1926
|
if (body.purge) {
|
|
1711
1927
|
try {
|
|
1712
|
-
(0,
|
|
1928
|
+
(0, import_node_fs7.rmSync)(rec.root, { recursive: true, force: true });
|
|
1713
1929
|
volumesRemoved = true;
|
|
1714
1930
|
} catch (err) {
|
|
1715
1931
|
console.warn(`[rover] purge of process pod folder "${rec.root}" failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -1735,7 +1951,12 @@ async function start2(state, body) {
|
|
|
1735
1951
|
}
|
|
1736
1952
|
const services = assignPortsForSlot(baseServices, rec.slot);
|
|
1737
1953
|
const { workspace } = ensurePodDirs(rec.root);
|
|
1738
|
-
const env = buildPodEnv(
|
|
1954
|
+
const env = buildPodEnv(
|
|
1955
|
+
state,
|
|
1956
|
+
{ projectSlug: rec.projectSlug, cfBaseDomain: rec.cfBaseDomain, claudeOauthToken: rec.claudeOauthToken },
|
|
1957
|
+
rec.root,
|
|
1958
|
+
services
|
|
1959
|
+
);
|
|
1739
1960
|
const pid = spawnPod(workspace, env, rec.logFile, rec.projectSlug);
|
|
1740
1961
|
upsert({ ...rec, pid, pgid: pid, stopped: false });
|
|
1741
1962
|
console.log(`[rover] process pod "${body.projectSlug}" resumed \u2014 pid ${pid}, slot ${rec.slot}`);
|
|
@@ -1801,13 +2022,13 @@ async function signalGroupDown(rec, signal) {
|
|
|
1801
2022
|
function sleep2(ms) {
|
|
1802
2023
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1803
2024
|
}
|
|
1804
|
-
var import_node_child_process3,
|
|
2025
|
+
var import_node_child_process3, import_node_fs7, import_node_path7, POD_ENTRY_BIN, POD_ENTRY_ARG, DENIED_ENV, BASE_ENV_PASSTHROUGH, processRuntime;
|
|
1805
2026
|
var init_process = __esm({
|
|
1806
2027
|
"src/server/rover/runtime/process.ts"() {
|
|
1807
2028
|
"use strict";
|
|
1808
2029
|
import_node_child_process3 = require("node:child_process");
|
|
1809
|
-
|
|
1810
|
-
|
|
2030
|
+
import_node_fs7 = require("node:fs");
|
|
2031
|
+
import_node_path7 = require("node:path");
|
|
1811
2032
|
init_launch_kit_services();
|
|
1812
2033
|
init_hostnames();
|
|
1813
2034
|
init_ports();
|
|
@@ -1902,25 +2123,25 @@ __export(daemon_exports, {
|
|
|
1902
2123
|
startDaemon: () => startDaemon
|
|
1903
2124
|
});
|
|
1904
2125
|
function lockPath() {
|
|
1905
|
-
return (0,
|
|
2126
|
+
return (0, import_node_path8.join)((0, import_node_os4.homedir)(), LAUNCHSECURE_DIR, ROVER_LOCK_FILENAME);
|
|
1906
2127
|
}
|
|
1907
2128
|
function readRoverLock() {
|
|
1908
2129
|
const p = lockPath();
|
|
1909
|
-
if (!(0,
|
|
2130
|
+
if (!(0, import_node_fs8.existsSync)(p)) return null;
|
|
1910
2131
|
try {
|
|
1911
|
-
return JSON.parse((0,
|
|
2132
|
+
return JSON.parse((0, import_node_fs8.readFileSync)(p, "utf-8"));
|
|
1912
2133
|
} catch {
|
|
1913
2134
|
return null;
|
|
1914
2135
|
}
|
|
1915
2136
|
}
|
|
1916
2137
|
function writeLock(lock) {
|
|
1917
|
-
const dir = (0,
|
|
1918
|
-
(0,
|
|
1919
|
-
(0,
|
|
2138
|
+
const dir = (0, import_node_path8.join)((0, import_node_os4.homedir)(), LAUNCHSECURE_DIR);
|
|
2139
|
+
(0, import_node_fs8.mkdirSync)(dir, { recursive: true });
|
|
2140
|
+
(0, import_node_fs8.writeFileSync)(lockPath(), JSON.stringify(lock, null, 2) + "\n", "utf-8");
|
|
1920
2141
|
}
|
|
1921
2142
|
function clearLock() {
|
|
1922
2143
|
try {
|
|
1923
|
-
(0,
|
|
2144
|
+
(0, import_node_fs8.unlinkSync)(lockPath());
|
|
1924
2145
|
} catch {
|
|
1925
2146
|
}
|
|
1926
2147
|
}
|
|
@@ -1990,6 +2211,9 @@ async function handleRequest(req, res, state) {
|
|
|
1990
2211
|
if (method === "GET" && path === "/health") {
|
|
1991
2212
|
return json(res, 200, { ok: true, roverId: state.roverId, orgSlug: state.orgSlug });
|
|
1992
2213
|
}
|
|
2214
|
+
if (method === "GET" && path === "/self/status") {
|
|
2215
|
+
return json(res, 200, { roverId: state.roverId, kitVersion: KIT_VERSION, podImage: POD_IMAGE });
|
|
2216
|
+
}
|
|
1993
2217
|
if (method === "GET" && (path === "/" || path === "/pods" || path === "/ui")) {
|
|
1994
2218
|
try {
|
|
1995
2219
|
const { pods } = await podsList(state);
|
|
@@ -2040,6 +2264,47 @@ async function handleRequest(req, res, state) {
|
|
|
2040
2264
|
return json(res, 500, { error: err instanceof Error ? err.message : "internal_error" });
|
|
2041
2265
|
}
|
|
2042
2266
|
}
|
|
2267
|
+
if (method === "POST" && path === "/self/update") {
|
|
2268
|
+
const rawBody = await readBody(req);
|
|
2269
|
+
const sigHeader = req.headers["x-ls-signature"];
|
|
2270
|
+
const sigStr = Array.isArray(sigHeader) ? sigHeader[0] : sigHeader;
|
|
2271
|
+
const failure = verifySignature(state.secret, rawBody, sigStr);
|
|
2272
|
+
if (failure) {
|
|
2273
|
+
console.warn(`[rover] POST /self/update \u2192 401 (${failure})`);
|
|
2274
|
+
return json(res, 401, { error: "invalid_signature" });
|
|
2275
|
+
}
|
|
2276
|
+
let targetVersion;
|
|
2277
|
+
if (rawBody.length > 0) {
|
|
2278
|
+
try {
|
|
2279
|
+
targetVersion = JSON.parse(rawBody).targetVersion;
|
|
2280
|
+
} catch {
|
|
2281
|
+
return json(res, 400, { error: "malformed_json" });
|
|
2282
|
+
}
|
|
2283
|
+
}
|
|
2284
|
+
if (!targetVersion || !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(targetVersion)) {
|
|
2285
|
+
return json(res, 400, { error: "invalid_target_version", detail: "expected a concrete semver, e.g. 0.0.45" });
|
|
2286
|
+
}
|
|
2287
|
+
if (targetVersion === KIT_VERSION) {
|
|
2288
|
+
return json(res, 200, { ok: true, updated: false, reason: "already_on_target", kitVersion: KIT_VERSION });
|
|
2289
|
+
}
|
|
2290
|
+
json(res, 200, { ok: true, updating: true, from: KIT_VERSION, to: targetVersion });
|
|
2291
|
+
const argv0 = process.argv[1] ?? "launch-rover";
|
|
2292
|
+
const child = (0, import_node_child_process4.spawn)(
|
|
2293
|
+
process.execPath,
|
|
2294
|
+
[argv0, "rover-update", `--rover=${state.roverId}`, `--version=${targetVersion}`, `--pod-image=${POD_IMAGE}`],
|
|
2295
|
+
{ detached: true, stdio: "ignore", env: process.env }
|
|
2296
|
+
);
|
|
2297
|
+
child.unref();
|
|
2298
|
+
console.log(`[rover] /self/update \u2014 spawned updater (pid ${child.pid}) ${KIT_VERSION} \u2192 ${targetVersion}; exiting for re-exec`);
|
|
2299
|
+
setTimeout(() => {
|
|
2300
|
+
try {
|
|
2301
|
+
process.kill(process.pid, "SIGTERM");
|
|
2302
|
+
} catch {
|
|
2303
|
+
process.exit(0);
|
|
2304
|
+
}
|
|
2305
|
+
}, 250).unref();
|
|
2306
|
+
return;
|
|
2307
|
+
}
|
|
2043
2308
|
if (method === "POST" && path === "/shutdown") {
|
|
2044
2309
|
const rawBody = await readBody(req);
|
|
2045
2310
|
const sigHeader = req.headers["x-ls-signature"];
|
|
@@ -2087,7 +2352,7 @@ function readBody(req) {
|
|
|
2087
2352
|
});
|
|
2088
2353
|
}
|
|
2089
2354
|
async function sendHeartbeat(state) {
|
|
2090
|
-
const payload = JSON.stringify({ roverId: state.roverId });
|
|
2355
|
+
const payload = JSON.stringify({ roverId: state.roverId, kitVersion: KIT_VERSION, podImage: POD_IMAGE });
|
|
2091
2356
|
const { header } = buildSignatureHeader(state.secret, payload);
|
|
2092
2357
|
const url = new URL("/api/rover/heartbeat", state.serverUrl);
|
|
2093
2358
|
await postJson(url, payload, { "X-LS-Signature": header });
|
|
@@ -2126,21 +2391,24 @@ function postJson(url, body, extra) {
|
|
|
2126
2391
|
req.end();
|
|
2127
2392
|
});
|
|
2128
2393
|
}
|
|
2129
|
-
var
|
|
2394
|
+
var import_node_child_process4, import_node_fs8, import_node_http2, import_node_http3, import_node_https2, import_node_os4, import_node_path8, KIT_VERSION, POD_IMAGE, ROVER_LOCK_FILENAME;
|
|
2130
2395
|
var init_daemon = __esm({
|
|
2131
2396
|
"src/server/rover/daemon.ts"() {
|
|
2132
2397
|
"use strict";
|
|
2133
|
-
|
|
2398
|
+
import_node_child_process4 = require("node:child_process");
|
|
2399
|
+
import_node_fs8 = require("node:fs");
|
|
2134
2400
|
import_node_http2 = require("node:http");
|
|
2135
2401
|
import_node_http3 = require("node:http");
|
|
2136
2402
|
import_node_https2 = require("node:https");
|
|
2137
2403
|
import_node_os4 = require("node:os");
|
|
2138
|
-
|
|
2404
|
+
import_node_path8 = require("node:path");
|
|
2139
2405
|
init_launch_kit_paths();
|
|
2140
2406
|
init_dashboard();
|
|
2141
2407
|
init_hmac();
|
|
2142
2408
|
init_pods();
|
|
2143
2409
|
init_state();
|
|
2410
|
+
KIT_VERSION = require_package().version;
|
|
2411
|
+
POD_IMAGE = process.env.LAUNCHKIT_POD_IMAGE ?? "ghcr.io/launchsecure/launch-pod:latest";
|
|
2144
2412
|
ROVER_LOCK_FILENAME = "launch-rover.lock";
|
|
2145
2413
|
}
|
|
2146
2414
|
});
|
|
@@ -20903,11 +21171,11 @@ function createTunnel(opts) {
|
|
|
20903
21171
|
const exhaustive = opts.provider;
|
|
20904
21172
|
throw new Error(`[tunnel] unknown provider: ${String(exhaustive)}`);
|
|
20905
21173
|
}
|
|
20906
|
-
var
|
|
21174
|
+
var import_node_fs9, import_node_events, import_promises3, import_undici, import_cloudflared, dnsResolver, dnsCache, dnsResilientDispatcher, BACKOFF_MIN_MS, BACKOFF_MAX_MS, SELFTEST_INTERVAL_MS, SELFTEST_TIMEOUT_MS, NAMED_FATAL_PATTERNS, CloudflaredTunnel;
|
|
20907
21175
|
var init_tunnel = __esm({
|
|
20908
21176
|
"src/server/tunnel/index.ts"() {
|
|
20909
21177
|
"use strict";
|
|
20910
|
-
|
|
21178
|
+
import_node_fs9 = require("node:fs");
|
|
20911
21179
|
import_node_events = require("node:events");
|
|
20912
21180
|
import_promises3 = require("node:dns/promises");
|
|
20913
21181
|
init_source();
|
|
@@ -20955,7 +21223,7 @@ var init_tunnel = __esm({
|
|
|
20955
21223
|
}
|
|
20956
21224
|
async start() {
|
|
20957
21225
|
if (this.tunnel) return;
|
|
20958
|
-
if (!(0,
|
|
21226
|
+
if (!(0, import_node_fs9.existsSync)(import_cloudflared.bin)) {
|
|
20959
21227
|
console.log("[tunnel] downloading cloudflared binary (one-time setup)\u2026");
|
|
20960
21228
|
try {
|
|
20961
21229
|
await (0, import_cloudflared.install)(import_cloudflared.bin);
|
|
@@ -21222,9 +21490,9 @@ async function runSetup(argv) {
|
|
|
21222
21490
|
}
|
|
21223
21491
|
const zone = await resolveZone(lsConfig.cfApiToken, lsConfig.cfDomainName);
|
|
21224
21492
|
console.log(`[rover] resolved CF zone: ${zone.name} (account ${zone.accountId})`);
|
|
21225
|
-
const stateDir2 = (0,
|
|
21226
|
-
(0,
|
|
21227
|
-
const tunnelStateFile = (0,
|
|
21493
|
+
const stateDir2 = (0, import_node_path9.join)((0, import_node_os6.homedir)(), LAUNCHSECURE_DIR, "rover");
|
|
21494
|
+
(0, import_node_fs10.mkdirSync)(stateDir2, { recursive: true });
|
|
21495
|
+
const tunnelStateFile = (0, import_node_path9.join)(stateDir2, `${roverId}.tunnel.json`);
|
|
21228
21496
|
const idSuffix = roverId.replace(/[^a-z0-9]/gi, "").toLowerCase().slice(-8);
|
|
21229
21497
|
const subdomain = `rover-${args.orgSlug.replace(/[^a-z0-9-]/gi, "-").toLowerCase().slice(0, 30)}-${idSuffix}`;
|
|
21230
21498
|
const tunnelName = `launch-rover-${roverId}`;
|
|
@@ -21257,7 +21525,7 @@ async function runSetup(argv) {
|
|
|
21257
21525
|
platform: (0, import_node_os6.platform)(),
|
|
21258
21526
|
hostname: args.name ?? (0, import_node_os6.hostname)(),
|
|
21259
21527
|
dockerVersion,
|
|
21260
|
-
kitVersion:
|
|
21528
|
+
kitVersion: KIT_VERSION2,
|
|
21261
21529
|
// The actual pod backend this host runs — lets LS show the reported
|
|
21262
21530
|
// runtime and flag drift from the configured `defaultRuntime`.
|
|
21263
21531
|
runtime: args.runtime
|
|
@@ -21267,6 +21535,14 @@ async function runSetup(argv) {
|
|
|
21267
21535
|
console.log(
|
|
21268
21536
|
`[rover] LS state: ${outcome.approvalStatus}${outcome.reused ? " (reused existing secret)" : " (new secret minted)"}`
|
|
21269
21537
|
);
|
|
21538
|
+
await gateDashboard({
|
|
21539
|
+
apiToken: lsConfig.cfApiToken,
|
|
21540
|
+
accountId: zone.accountId,
|
|
21541
|
+
issuer: args.serverUrl,
|
|
21542
|
+
hostname,
|
|
21543
|
+
pat: args.pat,
|
|
21544
|
+
stateFile: (0, import_node_path9.join)(stateDir2, `${roverId}.access.json`)
|
|
21545
|
+
});
|
|
21270
21546
|
await launchDaemon(args, ingress, outcome.state);
|
|
21271
21547
|
console.log("");
|
|
21272
21548
|
if (outcome.approvalStatus === "pending_approval") {
|
|
@@ -21322,7 +21598,7 @@ async function launchDaemon(args, ingress, state) {
|
|
|
21322
21598
|
return;
|
|
21323
21599
|
}
|
|
21324
21600
|
const argv0 = process.argv[1] ?? "launch-rover";
|
|
21325
|
-
const child = (0,
|
|
21601
|
+
const child = (0, import_node_child_process5.spawn)(process.execPath, [argv0, "serve", `--rover=${state.roverId}`], {
|
|
21326
21602
|
detached: true,
|
|
21327
21603
|
stdio: "ignore",
|
|
21328
21604
|
env: {
|
|
@@ -21334,6 +21610,52 @@ async function launchDaemon(args, ingress, state) {
|
|
|
21334
21610
|
child.unref();
|
|
21335
21611
|
console.log(`[rover] daemon spawned (pid ${child.pid}, detached)`);
|
|
21336
21612
|
}
|
|
21613
|
+
async function gateDashboard(input) {
|
|
21614
|
+
const services = [
|
|
21615
|
+
// Host-level gate — the read-only dashboard. 24h session (not RCE-bearing,
|
|
21616
|
+
// unlike the bot terminal which the pod entrypoint marks strict).
|
|
21617
|
+
{ hostname: input.hostname, strict: false },
|
|
21618
|
+
// Public carve-outs for the machine routes.
|
|
21619
|
+
...ACCESS_BYPASS_PATHS.map((path) => ({ hostname: input.hostname, path, bypass: true }))
|
|
21620
|
+
];
|
|
21621
|
+
console.log(`[rover] gating ${input.hostname} behind CF Access (IdP: ${input.issuer})`);
|
|
21622
|
+
try {
|
|
21623
|
+
const result = await provisionAccess({
|
|
21624
|
+
apiToken: input.apiToken,
|
|
21625
|
+
accountId: input.accountId,
|
|
21626
|
+
issuer: input.issuer,
|
|
21627
|
+
services,
|
|
21628
|
+
stateFile: input.stateFile,
|
|
21629
|
+
registerClient: (redirectUris) => registerOidcClient(input.issuer, input.pat, redirectUris)
|
|
21630
|
+
});
|
|
21631
|
+
console.log(`[rover] CF Access gate live \u2014 IdP ${result.idpId}, auth domain ${result.authDomain}`);
|
|
21632
|
+
} catch (err) {
|
|
21633
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
21634
|
+
throw new Error(
|
|
21635
|
+
`CF Access gating failed: ${msg}
|
|
21636
|
+
The dashboard would be publicly reachable without it, so setup is aborting before the tunnel goes live.
|
|
21637
|
+
Most common cause: the rover's Cloudflare token lacks Access scopes (Access: Apps and Policies + Account: Access Organizations, both Edit).
|
|
21638
|
+
Re-mint the token from LS \u2192 Organization \u2192 Integrations \u2192 Cloudflare (the "Create token" link pre-selects them), update the rover, and re-run setup.`
|
|
21639
|
+
);
|
|
21640
|
+
}
|
|
21641
|
+
}
|
|
21642
|
+
async function registerOidcClient(serverUrl, pat, redirectUris) {
|
|
21643
|
+
const res = await fetch(new URL("/api/rover/oidc-client", serverUrl), {
|
|
21644
|
+
method: "POST",
|
|
21645
|
+
headers: {
|
|
21646
|
+
Authorization: `Bearer ${pat}`,
|
|
21647
|
+
"Content-Type": "application/json",
|
|
21648
|
+
Accept: "application/json"
|
|
21649
|
+
},
|
|
21650
|
+
body: JSON.stringify({ redirectUris }),
|
|
21651
|
+
signal: AbortSignal.timeout(15e3)
|
|
21652
|
+
});
|
|
21653
|
+
const body = await res.json().catch(() => null);
|
|
21654
|
+
if (!res.ok || !body?.success || !body.data) {
|
|
21655
|
+
throw new Error(`OIDC client provisioning failed (HTTP ${res.status}): ${body?.error ?? "unexpected response"}`);
|
|
21656
|
+
}
|
|
21657
|
+
return body.data;
|
|
21658
|
+
}
|
|
21337
21659
|
function runSetupReset(roverId) {
|
|
21338
21660
|
clearRoverState(roverId);
|
|
21339
21661
|
}
|
|
@@ -21345,24 +21667,26 @@ function isPidAlive(pid) {
|
|
|
21345
21667
|
return false;
|
|
21346
21668
|
}
|
|
21347
21669
|
}
|
|
21348
|
-
var import_node_os6,
|
|
21670
|
+
var import_node_os6, import_node_child_process5, import_node_path9, import_node_fs10, KIT_VERSION2, DEFAULT_SERVER, DEFAULT_DAEMON_PORT, MIN_NODE_MAJOR, ACCESS_BYPASS_PATHS;
|
|
21349
21671
|
var init_setup = __esm({
|
|
21350
21672
|
"src/server/rover/setup.ts"() {
|
|
21351
21673
|
"use strict";
|
|
21352
21674
|
import_node_os6 = require("node:os");
|
|
21353
|
-
|
|
21354
|
-
|
|
21355
|
-
|
|
21675
|
+
import_node_child_process5 = require("node:child_process");
|
|
21676
|
+
import_node_path9 = require("node:path");
|
|
21677
|
+
import_node_fs10 = require("node:fs");
|
|
21678
|
+
init_cf_access();
|
|
21356
21679
|
init_cf_ingress();
|
|
21357
21680
|
init_launch_kit_paths();
|
|
21358
21681
|
init_docker_install();
|
|
21359
21682
|
init_mcp_client();
|
|
21360
21683
|
init_registration();
|
|
21361
21684
|
init_state();
|
|
21362
|
-
|
|
21685
|
+
KIT_VERSION2 = require_package().version;
|
|
21363
21686
|
DEFAULT_SERVER = "https://launchsecure-v2.vercel.app";
|
|
21364
21687
|
DEFAULT_DAEMON_PORT = 52749;
|
|
21365
21688
|
MIN_NODE_MAJOR = 18;
|
|
21689
|
+
ACCESS_BYPASS_PATHS = ["/health", "/pods/up", "/pods/down", "/pods/start", "/pods/list", "/shutdown"];
|
|
21366
21690
|
}
|
|
21367
21691
|
});
|
|
21368
21692
|
|
|
@@ -21427,15 +21751,15 @@ async function runTeardown(argv) {
|
|
|
21427
21751
|
}
|
|
21428
21752
|
}
|
|
21429
21753
|
clearRoverState(parsed.roverId);
|
|
21430
|
-
const tunnelStateFile = (0,
|
|
21754
|
+
const tunnelStateFile = (0, import_node_path10.join)(
|
|
21431
21755
|
(0, import_node_os7.homedir)(),
|
|
21432
21756
|
LAUNCHSECURE_DIR,
|
|
21433
21757
|
"rover",
|
|
21434
21758
|
`${parsed.roverId}.tunnel.json`
|
|
21435
21759
|
);
|
|
21436
|
-
if ((0,
|
|
21760
|
+
if ((0, import_node_fs11.existsSync)(tunnelStateFile)) {
|
|
21437
21761
|
try {
|
|
21438
|
-
(0,
|
|
21762
|
+
(0, import_node_fs11.unlinkSync)(tunnelStateFile);
|
|
21439
21763
|
} catch {
|
|
21440
21764
|
}
|
|
21441
21765
|
}
|
|
@@ -21486,19 +21810,75 @@ function isPidAlive2(pid) {
|
|
|
21486
21810
|
function sleep3(ms) {
|
|
21487
21811
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
21488
21812
|
}
|
|
21489
|
-
var
|
|
21813
|
+
var import_node_fs11, import_node_os7, import_node_path10;
|
|
21490
21814
|
var init_teardown = __esm({
|
|
21491
21815
|
"src/server/rover/teardown.ts"() {
|
|
21492
21816
|
"use strict";
|
|
21493
|
-
|
|
21817
|
+
import_node_fs11 = require("node:fs");
|
|
21494
21818
|
import_node_os7 = require("node:os");
|
|
21495
|
-
|
|
21819
|
+
import_node_path10 = require("node:path");
|
|
21496
21820
|
init_launch_kit_paths();
|
|
21497
21821
|
init_daemon();
|
|
21498
21822
|
init_state();
|
|
21499
21823
|
}
|
|
21500
21824
|
});
|
|
21501
21825
|
|
|
21826
|
+
// src/server/rover/update.ts
|
|
21827
|
+
var update_exports = {};
|
|
21828
|
+
__export(update_exports, {
|
|
21829
|
+
runUpdate: () => runUpdate
|
|
21830
|
+
});
|
|
21831
|
+
function parseArgs3(argv) {
|
|
21832
|
+
const get = (k) => argv.find((a) => a.startsWith(`--${k}=`))?.slice(`--${k}=`.length);
|
|
21833
|
+
const roverId = get("rover");
|
|
21834
|
+
const version = get("version");
|
|
21835
|
+
if (!roverId) throw new Error("rover-update requires --rover=<id>");
|
|
21836
|
+
if (!version || !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(version)) {
|
|
21837
|
+
throw new Error(`rover-update requires --version=<concrete semver> (got "${version ?? ""}")`);
|
|
21838
|
+
}
|
|
21839
|
+
return { roverId, version, podImage: get("pod-image") };
|
|
21840
|
+
}
|
|
21841
|
+
function log(msg) {
|
|
21842
|
+
process.stdout.write(`[rover-update] ${msg}
|
|
21843
|
+
`);
|
|
21844
|
+
}
|
|
21845
|
+
async function runUpdate(argv) {
|
|
21846
|
+
const args = parseArgs3(argv);
|
|
21847
|
+
log(`updating ${KIT_PACKAGE} \u2192 ${args.version} for rover ${args.roverId}`);
|
|
21848
|
+
const install2 = (0, import_node_child_process6.spawnSync)("npm", ["install", "-g", `${KIT_PACKAGE}@${args.version}`], {
|
|
21849
|
+
stdio: "inherit",
|
|
21850
|
+
env: process.env
|
|
21851
|
+
});
|
|
21852
|
+
if (install2.status !== 0) {
|
|
21853
|
+
log(`\u2717 npm install failed (status ${install2.status ?? "unknown"}); re-launching the existing version`);
|
|
21854
|
+
} else {
|
|
21855
|
+
log(`\u2713 installed ${KIT_PACKAGE}@${args.version}`);
|
|
21856
|
+
}
|
|
21857
|
+
if (args.podImage) {
|
|
21858
|
+
const pull = (0, import_node_child_process6.spawnSync)("docker", ["pull", args.podImage], { stdio: "inherit", env: process.env });
|
|
21859
|
+
if (pull.status === 0) {
|
|
21860
|
+
log(`\u2713 pulled ${args.podImage}`);
|
|
21861
|
+
} else {
|
|
21862
|
+
log(`\u26A0 docker pull skipped/failed (status ${pull.status ?? "unknown"}) \u2014 pod will pull on next up`);
|
|
21863
|
+
}
|
|
21864
|
+
}
|
|
21865
|
+
const child = (0, import_node_child_process6.spawn)("launch-rover", ["serve", `--rover=${args.roverId}`], {
|
|
21866
|
+
detached: true,
|
|
21867
|
+
stdio: "ignore",
|
|
21868
|
+
env: process.env
|
|
21869
|
+
});
|
|
21870
|
+
child.unref();
|
|
21871
|
+
log(`re-launched daemon (pid ${child.pid}); updater exiting`);
|
|
21872
|
+
}
|
|
21873
|
+
var import_node_child_process6, KIT_PACKAGE;
|
|
21874
|
+
var init_update = __esm({
|
|
21875
|
+
"src/server/rover/update.ts"() {
|
|
21876
|
+
"use strict";
|
|
21877
|
+
import_node_child_process6 = require("node:child_process");
|
|
21878
|
+
KIT_PACKAGE = "@launchsecure/launch-kit";
|
|
21879
|
+
}
|
|
21880
|
+
});
|
|
21881
|
+
|
|
21502
21882
|
// src/server/rover-entry.ts
|
|
21503
21883
|
function logStderr(msg) {
|
|
21504
21884
|
process.stderr.write(`[launch-rover] ${msg}
|
|
@@ -21561,6 +21941,11 @@ async function main() {
|
|
|
21561
21941
|
runStatus2(argv.slice(1));
|
|
21562
21942
|
return;
|
|
21563
21943
|
}
|
|
21944
|
+
case "rover-update": {
|
|
21945
|
+
const { runUpdate: runUpdate2 } = await Promise.resolve().then(() => (init_update(), update_exports));
|
|
21946
|
+
await runUpdate2(argv.slice(1));
|
|
21947
|
+
return;
|
|
21948
|
+
}
|
|
21564
21949
|
case "teardown": {
|
|
21565
21950
|
const { runTeardown: runTeardown2 } = await Promise.resolve().then(() => (init_teardown(), teardown_exports));
|
|
21566
21951
|
await runTeardown2(argv.slice(1));
|