@nodetool-ai/deploy 0.7.0-rc.23 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -0
- package/dist/.nodetool-build-stamp +1 -0
- package/dist/admin-client.d.ts +5 -39
- package/dist/admin-client.d.ts.map +1 -1
- package/dist/admin-client.js +9 -230
- package/dist/admin-client.js.map +1 -1
- package/dist/api-user-manager.d.ts.map +1 -1
- package/dist/api-user-manager.js +5 -2
- package/dist/api-user-manager.js.map +1 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +0 -3
- package/dist/auth.js.map +1 -1
- package/dist/compose.d.ts.map +1 -1
- package/dist/compose.js +5 -6
- package/dist/compose.js.map +1 -1
- package/dist/configure.d.ts +1 -43
- package/dist/configure.d.ts.map +1 -1
- package/dist/configure.js +2 -43
- package/dist/configure.js.map +1 -1
- package/dist/deployment-config.d.ts +28 -786
- package/dist/deployment-config.d.ts.map +1 -1
- package/dist/deployment-config.js +34 -218
- package/dist/deployment-config.js.map +1 -1
- package/dist/docker-run.d.ts +2 -1
- package/dist/docker-run.d.ts.map +1 -1
- package/dist/docker-run.js +6 -22
- package/dist/docker-run.js.map +1 -1
- package/dist/docker.d.ts +6 -21
- package/dist/docker.d.ts.map +1 -1
- package/dist/docker.js +6 -53
- package/dist/docker.js.map +1 -1
- package/dist/image-spec.d.ts +1 -1
- package/dist/index.d.ts +0 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -17
- package/dist/index.js.map +1 -1
- package/dist/manager.d.ts.map +1 -1
- package/dist/manager.js +0 -44
- package/dist/manager.js.map +1 -1
- package/dist/progress.d.ts.map +1 -1
- package/dist/progress.js +0 -1
- package/dist/progress.js.map +1 -1
- package/dist/remote-users.d.ts.map +1 -1
- package/dist/remote-users.js +1 -4
- package/dist/remote-users.js.map +1 -1
- package/dist/runpod-rest.d.ts +114 -0
- package/dist/runpod-rest.d.ts.map +1 -0
- package/dist/runpod-rest.js +150 -0
- package/dist/runpod-rest.js.map +1 -0
- package/dist/self-hosted.d.ts +17 -43
- package/dist/self-hosted.d.ts.map +1 -1
- package/dist/self-hosted.js +50 -124
- package/dist/self-hosted.js.map +1 -1
- package/dist/ssh.d.ts +9 -0
- package/dist/ssh.d.ts.map +1 -1
- package/dist/ssh.js +1 -5
- package/dist/ssh.js.map +1 -1
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +1 -11
- package/dist/state.js.map +1 -1
- package/dist/workflow-syncer.d.ts +3 -32
- package/dist/workflow-syncer.d.ts.map +1 -1
- package/dist/workflow-syncer.js +3 -162
- package/dist/workflow-syncer.js.map +1 -1
- package/package.json +29 -14
- package/dist/admin-operations.d.ts +0 -197
- package/dist/admin-operations.d.ts.map +0 -1
- package/dist/admin-operations.js +0 -433
- package/dist/admin-operations.js.map +0 -1
- package/dist/admin-routes.d.ts +0 -306
- package/dist/admin-routes.d.ts.map +0 -1
- package/dist/admin-routes.js +0 -383
- package/dist/admin-routes.js.map +0 -1
- package/dist/collection-routes.d.ts +0 -37
- package/dist/collection-routes.d.ts.map +0 -1
- package/dist/collection-routes.js +0 -43
- package/dist/collection-routes.js.map +0 -1
- package/dist/deploy-to-gcp.d.ts +0 -67
- package/dist/deploy-to-gcp.d.ts.map +0 -1
- package/dist/deploy-to-gcp.js +0 -268
- package/dist/deploy-to-gcp.js.map +0 -1
- package/dist/deploy-to-runpod.d.ts +0 -53
- package/dist/deploy-to-runpod.d.ts.map +0 -1
- package/dist/deploy-to-runpod.js +0 -207
- package/dist/deploy-to-runpod.js.map +0 -1
- package/dist/fly.d.ts +0 -40
- package/dist/fly.d.ts.map +0 -1
- package/dist/fly.js +0 -291
- package/dist/fly.js.map +0 -1
- package/dist/gcp.d.ts +0 -59
- package/dist/gcp.d.ts.map +0 -1
- package/dist/gcp.js +0 -339
- package/dist/gcp.js.map +0 -1
- package/dist/google-cloud-run-api.d.ts +0 -134
- package/dist/google-cloud-run-api.d.ts.map +0 -1
- package/dist/google-cloud-run-api.js +0 -532
- package/dist/google-cloud-run-api.js.map +0 -1
- package/dist/huggingface.d.ts +0 -69
- package/dist/huggingface.d.ts.map +0 -1
- package/dist/huggingface.js +0 -346
- package/dist/huggingface.js.map +0 -1
- package/dist/railway.d.ts +0 -65
- package/dist/railway.d.ts.map +0 -1
- package/dist/railway.js +0 -284
- package/dist/railway.js.map +0 -1
- package/dist/runpod-api.d.ts +0 -206
- package/dist/runpod-api.d.ts.map +0 -1
- package/dist/runpod-api.js +0 -703
- package/dist/runpod-api.js.map +0 -1
- package/dist/runpod.d.ts +0 -82
- package/dist/runpod.d.ts.map +0 -1
- package/dist/runpod.js +0 -188
- package/dist/runpod.js.map +0 -1
- package/dist/storage-routes.d.ts +0 -55
- package/dist/storage-routes.d.ts.map +0 -1
- package/dist/storage-routes.js +0 -160
- package/dist/storage-routes.js.map +0 -1
- package/dist/sync.d.ts +0 -38
- package/dist/sync.d.ts.map +0 -1
- package/dist/sync.js +0 -130
- package/dist/sync.js.map +0 -1
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RunPod Pod management over the REST API (https://rest.runpod.io/v1).
|
|
3
|
+
*
|
|
4
|
+
* The serverless helpers in `runpod-api.ts` create templates/endpoints (the
|
|
5
|
+
* job/queue model) and read the API key from `process.env`. This module manages
|
|
6
|
+
* **persistent Pods** — the right shape for a long-lived NodeTool WebSocket
|
|
7
|
+
* worker the server/CLI attaches to — and takes the API key **explicitly** so it
|
|
8
|
+
* can be sourced from the per-user secret store (`getSecret`), never the
|
|
9
|
+
* environment.
|
|
10
|
+
*
|
|
11
|
+
* REST surface used: POST /v1/pods, GET /v1/pods/{id}, POST /v1/pods/{id}/stop,
|
|
12
|
+
* DELETE /v1/pods/{id}, GET /v1/pods.
|
|
13
|
+
*/
|
|
14
|
+
/** Request body for POST /v1/pods (subset we use; see the REST reference). */
|
|
15
|
+
export interface RunpodPodSpec {
|
|
16
|
+
name: string;
|
|
17
|
+
imageName: string;
|
|
18
|
+
computeType?: "CPU" | "GPU";
|
|
19
|
+
vcpuCount?: number;
|
|
20
|
+
cpuFlavorIds?: string[];
|
|
21
|
+
cpuFlavorPriority?: "availability" | "custom";
|
|
22
|
+
gpuTypeIds?: string[];
|
|
23
|
+
gpuCount?: number;
|
|
24
|
+
ports?: string[];
|
|
25
|
+
env?: Record<string, string>;
|
|
26
|
+
containerDiskInGb?: number;
|
|
27
|
+
volumeInGb?: number;
|
|
28
|
+
volumeMountPath?: string;
|
|
29
|
+
networkVolumeId?: string;
|
|
30
|
+
dockerEntrypoint?: string[];
|
|
31
|
+
dockerStartCmd?: string[];
|
|
32
|
+
supportPublicIp?: boolean;
|
|
33
|
+
globalNetworking?: boolean;
|
|
34
|
+
dataCenterIds?: string[];
|
|
35
|
+
}
|
|
36
|
+
/** Pod as returned by GET /v1/pods/{id} (fields we read). */
|
|
37
|
+
export interface RunpodPod {
|
|
38
|
+
id: string;
|
|
39
|
+
name?: string;
|
|
40
|
+
desiredStatus?: "RUNNING" | "EXITED" | "TERMINATED" | string;
|
|
41
|
+
image?: string;
|
|
42
|
+
/** Public IPv4; empty/null while the Pod is initializing. */
|
|
43
|
+
publicIp?: string | null;
|
|
44
|
+
/** Internal→external port map, e.g. {"7777": 41021}. */
|
|
45
|
+
portMappings?: Record<string, number> | null;
|
|
46
|
+
ports?: string[];
|
|
47
|
+
[k: string]: unknown;
|
|
48
|
+
}
|
|
49
|
+
/** Create (and deploy) a Pod. POST /v1/pods. */
|
|
50
|
+
export declare function createPod(apiKey: string, spec: RunpodPodSpec): Promise<RunpodPod>;
|
|
51
|
+
/** Get a Pod by id. GET /v1/pods/{id}. */
|
|
52
|
+
export declare function getPod(apiKey: string, id: string): Promise<RunpodPod>;
|
|
53
|
+
/** List Pods. GET /v1/pods. */
|
|
54
|
+
export declare function listPods(apiKey: string): Promise<RunpodPod[]>;
|
|
55
|
+
/** Stop a Pod (releases compute, keeps the volume). POST /v1/pods/{id}/stop. */
|
|
56
|
+
export declare function stopPod(apiKey: string, id: string): Promise<void>;
|
|
57
|
+
/** Terminate a Pod (deletes it). DELETE /v1/pods/{id}. */
|
|
58
|
+
export declare function deletePod(apiKey: string, id: string): Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* Direct TCP WebSocket URL for an exposed `/tcp` port, or null while the Pod
|
|
61
|
+
* lacks a public IP / port mapping. Preferred for large frames (no HTTP proxy).
|
|
62
|
+
*/
|
|
63
|
+
export declare function podDirectWsUrl(pod: RunpodPod, internalPort: number): string | null;
|
|
64
|
+
/**
|
|
65
|
+
* RunPod HTTP-proxy WebSocket URL for an exposed `/http` port. TLS-terminated
|
|
66
|
+
* by RunPod; subject to the proxy's size/timeout limits (fine for small frames).
|
|
67
|
+
*/
|
|
68
|
+
export declare function podProxyWsUrl(podId: string, internalPort: number): string;
|
|
69
|
+
export interface DeployWorkerPodOptions {
|
|
70
|
+
/** Pod name. */
|
|
71
|
+
name: string;
|
|
72
|
+
/** Registry image RunPod pulls (must be reachable by RunPod). */
|
|
73
|
+
image: string;
|
|
74
|
+
/** Shared-secret passed as NODETOOL_WORKER_TOKEN (gates the ws handshake). */
|
|
75
|
+
workerToken?: string;
|
|
76
|
+
/** Internal port the worker serves on. Default 7777. */
|
|
77
|
+
internalPort?: number;
|
|
78
|
+
/** Expose via RunPod HTTP proxy ("http" → wss proxy) or direct TCP. */
|
|
79
|
+
exposure?: "http" | "tcp";
|
|
80
|
+
computeType?: "CPU" | "GPU";
|
|
81
|
+
vcpuCount?: number;
|
|
82
|
+
gpuTypeIds?: string[];
|
|
83
|
+
containerDiskInGb?: number;
|
|
84
|
+
/** Extra env merged into the container. */
|
|
85
|
+
env?: Record<string, string>;
|
|
86
|
+
/** Poll budget for the Pod to reach RUNNING + expose its endpoint. */
|
|
87
|
+
timeoutMs?: number;
|
|
88
|
+
}
|
|
89
|
+
export interface DeployedWorkerPod {
|
|
90
|
+
podId: string;
|
|
91
|
+
/** ws/wss URL to set as NODETOOL_WORKER_URL. */
|
|
92
|
+
wsUrl: string;
|
|
93
|
+
pod: RunpodPod;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Deploy a NodeTool worker image as a persistent RunPod Pod and wait until it is
|
|
97
|
+
* RUNNING with a resolvable WebSocket endpoint. Does NOT read process.env — pass
|
|
98
|
+
* `apiKey` (from the secret store) and `workerToken` explicitly.
|
|
99
|
+
*
|
|
100
|
+
* Reaching RUNNING + having an endpoint does not guarantee the worker process
|
|
101
|
+
* has finished loading nodes; the caller should connect with retry.
|
|
102
|
+
*/
|
|
103
|
+
export declare function deployWorkerPod(apiKey: string, opts: DeployWorkerPodOptions): Promise<DeployedWorkerPod>;
|
|
104
|
+
/** Poll GET /v1/pods/{id} until RUNNING with a resolvable endpoint. */
|
|
105
|
+
export declare function waitForPodEndpoint(apiKey: string, id: string, opts: {
|
|
106
|
+
internalPort: number;
|
|
107
|
+
exposure: "http" | "tcp";
|
|
108
|
+
timeoutMs?: number;
|
|
109
|
+
intervalMs?: number;
|
|
110
|
+
}): Promise<{
|
|
111
|
+
pod: RunpodPod;
|
|
112
|
+
wsUrl: string;
|
|
113
|
+
}>;
|
|
114
|
+
//# sourceMappingURL=runpod-rest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runpod-rest.d.ts","sourceRoot":"","sources":["../src/runpod-rest.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RunPod Pod management over the REST API (https://rest.runpod.io/v1).
|
|
3
|
+
*
|
|
4
|
+
* The serverless helpers in `runpod-api.ts` create templates/endpoints (the
|
|
5
|
+
* job/queue model) and read the API key from `process.env`. This module manages
|
|
6
|
+
* **persistent Pods** — the right shape for a long-lived NodeTool WebSocket
|
|
7
|
+
* worker the server/CLI attaches to — and takes the API key **explicitly** so it
|
|
8
|
+
* can be sourced from the per-user secret store (`getSecret`), never the
|
|
9
|
+
* environment.
|
|
10
|
+
*
|
|
11
|
+
* REST surface used: POST /v1/pods, GET /v1/pods/{id}, POST /v1/pods/{id}/stop,
|
|
12
|
+
* DELETE /v1/pods/{id}, GET /v1/pods.
|
|
13
|
+
*/
|
|
14
|
+
const RUNPOD_REST_BASE_URL = "https://rest.runpod.io/v1";
|
|
15
|
+
/**
|
|
16
|
+
* Call the RunPod REST API with an explicitly-supplied key.
|
|
17
|
+
*
|
|
18
|
+
* @param apiKey RunPod API key (from the per-user secret store).
|
|
19
|
+
* @param endpoint Path under /v1, e.g. "pods" or "pods/<id>".
|
|
20
|
+
*/
|
|
21
|
+
async function runpodRest(apiKey, endpoint, method = "GET", data) {
|
|
22
|
+
if (!apiKey)
|
|
23
|
+
throw new Error("RunPod API key is required");
|
|
24
|
+
const init = {
|
|
25
|
+
method,
|
|
26
|
+
headers: {
|
|
27
|
+
Authorization: `Bearer ${apiKey}`,
|
|
28
|
+
"Content-Type": "application/json",
|
|
29
|
+
Accept: "application/json"
|
|
30
|
+
},
|
|
31
|
+
signal: AbortSignal.timeout(60_000)
|
|
32
|
+
};
|
|
33
|
+
if (data && method === "POST")
|
|
34
|
+
init.body = JSON.stringify(data);
|
|
35
|
+
const response = await fetch(`${RUNPOD_REST_BASE_URL}/${endpoint}`, init);
|
|
36
|
+
if (!response.ok) {
|
|
37
|
+
const body = await response.text().catch(() => "");
|
|
38
|
+
throw new Error(`RunPod REST ${method} /${endpoint} failed (${response.status}): ${body}`);
|
|
39
|
+
}
|
|
40
|
+
if (method === "DELETE" && response.status === 204)
|
|
41
|
+
return {};
|
|
42
|
+
const text = await response.text();
|
|
43
|
+
return text ? JSON.parse(text) : {};
|
|
44
|
+
}
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Pod operations
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
/** Create (and deploy) a Pod. POST /v1/pods. */
|
|
49
|
+
export async function createPod(apiKey, spec) {
|
|
50
|
+
const pod = (await runpodRest(apiKey, "pods", "POST", spec));
|
|
51
|
+
if (!pod?.id)
|
|
52
|
+
throw new Error("RunPod createPod returned no pod id");
|
|
53
|
+
return pod;
|
|
54
|
+
}
|
|
55
|
+
/** Get a Pod by id. GET /v1/pods/{id}. */
|
|
56
|
+
export async function getPod(apiKey, id) {
|
|
57
|
+
return (await runpodRest(apiKey, `pods/${id}`));
|
|
58
|
+
}
|
|
59
|
+
/** List Pods. GET /v1/pods. */
|
|
60
|
+
export async function listPods(apiKey) {
|
|
61
|
+
const res = await runpodRest(apiKey, "pods");
|
|
62
|
+
if (Array.isArray(res))
|
|
63
|
+
return res;
|
|
64
|
+
const arr = res.pods;
|
|
65
|
+
return Array.isArray(arr) ? arr : [];
|
|
66
|
+
}
|
|
67
|
+
/** Stop a Pod (releases compute, keeps the volume). POST /v1/pods/{id}/stop. */
|
|
68
|
+
export async function stopPod(apiKey, id) {
|
|
69
|
+
await runpodRest(apiKey, `pods/${id}/stop`, "POST");
|
|
70
|
+
}
|
|
71
|
+
/** Terminate a Pod (deletes it). DELETE /v1/pods/{id}. */
|
|
72
|
+
export async function deletePod(apiKey, id) {
|
|
73
|
+
await runpodRest(apiKey, `pods/${id}`, "DELETE");
|
|
74
|
+
}
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
// Endpoint helpers
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
/**
|
|
79
|
+
* Direct TCP WebSocket URL for an exposed `/tcp` port, or null while the Pod
|
|
80
|
+
* lacks a public IP / port mapping. Preferred for large frames (no HTTP proxy).
|
|
81
|
+
*/
|
|
82
|
+
export function podDirectWsUrl(pod, internalPort) {
|
|
83
|
+
const ext = pod.portMappings?.[String(internalPort)];
|
|
84
|
+
if (!pod.publicIp || !ext)
|
|
85
|
+
return null;
|
|
86
|
+
return `ws://${pod.publicIp}:${ext}`;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* RunPod HTTP-proxy WebSocket URL for an exposed `/http` port. TLS-terminated
|
|
90
|
+
* by RunPod; subject to the proxy's size/timeout limits (fine for small frames).
|
|
91
|
+
*/
|
|
92
|
+
export function podProxyWsUrl(podId, internalPort) {
|
|
93
|
+
return `wss://${podId}-${internalPort}.proxy.runpod.net`;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Deploy a NodeTool worker image as a persistent RunPod Pod and wait until it is
|
|
97
|
+
* RUNNING with a resolvable WebSocket endpoint. Does NOT read process.env — pass
|
|
98
|
+
* `apiKey` (from the secret store) and `workerToken` explicitly.
|
|
99
|
+
*
|
|
100
|
+
* Reaching RUNNING + having an endpoint does not guarantee the worker process
|
|
101
|
+
* has finished loading nodes; the caller should connect with retry.
|
|
102
|
+
*/
|
|
103
|
+
export async function deployWorkerPod(apiKey, opts) {
|
|
104
|
+
const internalPort = opts.internalPort ?? 7777;
|
|
105
|
+
const exposure = opts.exposure ?? "http";
|
|
106
|
+
const env = { ...(opts.env ?? {}) };
|
|
107
|
+
if (opts.workerToken)
|
|
108
|
+
env.NODETOOL_WORKER_TOKEN = opts.workerToken;
|
|
109
|
+
const spec = {
|
|
110
|
+
name: opts.name,
|
|
111
|
+
imageName: opts.image,
|
|
112
|
+
computeType: opts.computeType ?? "CPU",
|
|
113
|
+
vcpuCount: opts.computeType === "GPU" ? undefined : (opts.vcpuCount ?? 4),
|
|
114
|
+
gpuTypeIds: opts.gpuTypeIds,
|
|
115
|
+
ports: [`${internalPort}/${exposure}`],
|
|
116
|
+
env,
|
|
117
|
+
containerDiskInGb: opts.containerDiskInGb ?? 20
|
|
118
|
+
};
|
|
119
|
+
const created = await createPod(apiKey, spec);
|
|
120
|
+
const { pod, wsUrl } = await waitForPodEndpoint(apiKey, created.id, {
|
|
121
|
+
internalPort,
|
|
122
|
+
exposure,
|
|
123
|
+
timeoutMs: opts.timeoutMs ?? 240_000
|
|
124
|
+
});
|
|
125
|
+
return { podId: created.id, wsUrl, pod };
|
|
126
|
+
}
|
|
127
|
+
/** Poll GET /v1/pods/{id} until RUNNING with a resolvable endpoint. */
|
|
128
|
+
export async function waitForPodEndpoint(apiKey, id, opts) {
|
|
129
|
+
const deadline = Date.now() + (opts.timeoutMs ?? 240_000);
|
|
130
|
+
const intervalMs = opts.intervalMs ?? 5_000;
|
|
131
|
+
let last;
|
|
132
|
+
while (Date.now() < deadline) {
|
|
133
|
+
const pod = await getPod(apiKey, id);
|
|
134
|
+
last = pod;
|
|
135
|
+
if (pod.desiredStatus === "TERMINATED" || pod.desiredStatus === "EXITED") {
|
|
136
|
+
throw new Error(`Pod ${id} reached terminal status ${pod.desiredStatus}`);
|
|
137
|
+
}
|
|
138
|
+
if (pod.desiredStatus === "RUNNING") {
|
|
139
|
+
if (opts.exposure === "http") {
|
|
140
|
+
return { pod, wsUrl: podProxyWsUrl(id, opts.internalPort) };
|
|
141
|
+
}
|
|
142
|
+
const direct = podDirectWsUrl(pod, opts.internalPort);
|
|
143
|
+
if (direct)
|
|
144
|
+
return { pod, wsUrl: direct };
|
|
145
|
+
}
|
|
146
|
+
await new Promise((r) => setTimeout(r, intervalMs));
|
|
147
|
+
}
|
|
148
|
+
throw new Error(`Pod ${id} did not become reachable within the timeout (last status: ${last?.desiredStatus ?? "unknown"})`);
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=runpod-rest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runpod-rest.js","sourceRoot":"","sources":["../src/runpod-rest.ts"],"names":[],"mappings":"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"}
|
package/dist/self-hosted.d.ts
CHANGED
|
@@ -8,8 +8,7 @@
|
|
|
8
8
|
* - Health monitoring
|
|
9
9
|
* - Localhost detection (skips SSH for localhost deployments)
|
|
10
10
|
*/
|
|
11
|
-
import {
|
|
12
|
-
import { DockerDeployment, SelfHostedDeployment } from "./deployment-config.js";
|
|
11
|
+
import { DockerDeployment } from "./deployment-config.js";
|
|
13
12
|
import { StateManager } from "./state.js";
|
|
14
13
|
/**
|
|
15
14
|
* Common interface for executors used in deployer code.
|
|
@@ -75,11 +74,8 @@ export declare class LocalExecutor {
|
|
|
75
74
|
*/
|
|
76
75
|
close(): void;
|
|
77
76
|
/**
|
|
78
|
-
* Execute a command locally
|
|
79
|
-
*
|
|
80
|
-
* Matches SSHConnection behavior by running in a shell.
|
|
81
|
-
* This allows shell features like pipes, redirects, &&/|| operators,
|
|
82
|
-
* and environment variable assignments.
|
|
77
|
+
* Execute a command locally in a shell, so pipes, redirects, &&/||, and
|
|
78
|
+
* environment-variable assignments work as they do over SSH.
|
|
83
79
|
*/
|
|
84
80
|
execute(command: string, check?: boolean, timeout?: number): Promise<[exitCode: number, stdout: string, stderr: string]>;
|
|
85
81
|
/**
|
|
@@ -88,55 +84,33 @@ export declare class LocalExecutor {
|
|
|
88
84
|
mkdir(dirPath: string, _mode?: number, parents?: boolean): Promise<void>;
|
|
89
85
|
}
|
|
90
86
|
/**
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
* Type parameter `T` is the concrete deployment configuration type
|
|
94
|
-
* (e.g. DockerDeployment).
|
|
87
|
+
* Deployer for Docker-based self-hosted deployments, over SSH or locally.
|
|
95
88
|
*/
|
|
96
|
-
export declare
|
|
89
|
+
export declare class DockerDeployer {
|
|
97
90
|
readonly deploymentName: string;
|
|
98
|
-
readonly deployment:
|
|
91
|
+
readonly deployment: DockerDeployment;
|
|
99
92
|
readonly stateManager: StateManager;
|
|
100
93
|
readonly isLocalhost: boolean;
|
|
101
|
-
constructor(deploymentName: string, deployment:
|
|
102
|
-
|
|
103
|
-
|
|
94
|
+
constructor(deploymentName: string, deployment: DockerDeployment, stateManager?: StateManager);
|
|
95
|
+
private log;
|
|
96
|
+
private getExecutor;
|
|
104
97
|
/**
|
|
105
98
|
* Use an executor within a callback, ensuring proper open/close lifecycle.
|
|
106
99
|
*/
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
protected containerName(): string;
|
|
100
|
+
private withExecutor;
|
|
101
|
+
private createDirectories;
|
|
102
|
+
private resolveLocalRuntimeCommand;
|
|
103
|
+
private runtimeCommandForShell;
|
|
104
|
+
private containerGenerator;
|
|
105
|
+
private containerName;
|
|
114
106
|
/**
|
|
115
107
|
* Return host port for direct app mode (matches DockerRunGenerator).
|
|
116
108
|
*/
|
|
117
|
-
|
|
118
|
-
protected abstract createSpecificDirectories(ssh: Executor, workspacePath: string): Promise<void>;
|
|
119
|
-
abstract plan(): Promise<DeployPlan>;
|
|
120
|
-
abstract apply(opts?: {
|
|
121
|
-
dryRun?: boolean;
|
|
122
|
-
}): Promise<DeployResult>;
|
|
123
|
-
abstract destroy(): Promise<DeployResult>;
|
|
124
|
-
abstract status(): Promise<DeployStatus>;
|
|
125
|
-
abstract logs(opts?: {
|
|
126
|
-
service?: string;
|
|
127
|
-
follow?: boolean;
|
|
128
|
-
tail?: number;
|
|
129
|
-
}): Promise<string>;
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* Deployer for Docker-based self-hosted deployments.
|
|
133
|
-
*/
|
|
134
|
-
export declare class DockerDeployer extends BaseSSHDeployer<DockerDeployment> {
|
|
135
|
-
protected createSpecificDirectories(ssh: Executor, workspacePath: string): Promise<void>;
|
|
109
|
+
private appHostPort;
|
|
136
110
|
plan(): Promise<DeployPlan>;
|
|
137
111
|
apply(opts?: {
|
|
138
112
|
dryRun?: boolean;
|
|
139
|
-
}): Promise<DeployResult>;
|
|
113
|
+
}): Promise<DeployResult | DeployPlan>;
|
|
140
114
|
private stopExistingContainer;
|
|
141
115
|
private stopExistingContainerWithShell;
|
|
142
116
|
private stopLocalPortConflictsWithShell;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"self-hosted.d.ts","sourceRoot":"","sources":["../src/self-hosted.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;
|
|
1
|
+
{"version":3,"file":"self-hosted.d.ts","sourceRoot":"","sources":["../src/self-hosted.ts"],"names":[],"mappings":"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"}
|
package/dist/self-hosted.js
CHANGED
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
* - Localhost detection (skips SSH for localhost deployments)
|
|
10
10
|
*/
|
|
11
11
|
import { execSync, execFileSync } from "child_process";
|
|
12
|
-
import * as dns from "dns";
|
|
13
12
|
import * as fs from "fs";
|
|
14
13
|
import * as os from "os";
|
|
15
14
|
import * as path from "path";
|
|
@@ -47,85 +46,53 @@ function expandUser(p) {
|
|
|
47
46
|
}
|
|
48
47
|
return p;
|
|
49
48
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
49
|
+
const LOCALHOST_NAMES = new Set(["localhost", "127.0.0.1", "::1", "0.0.0.0"]);
|
|
50
|
+
/** Every address a host resolves to; empty when neither tool answers. */
|
|
51
|
+
function resolveIps(target) {
|
|
52
|
+
try {
|
|
53
|
+
// getent (Linux) prints "127.0.0.1 localhost" — the IP is the first field.
|
|
54
|
+
return execFileSync("getent", ["hosts", target], {
|
|
55
|
+
encoding: "utf-8",
|
|
56
|
+
timeout: 5000
|
|
57
|
+
})
|
|
58
|
+
.trim()
|
|
59
|
+
.split("\n")
|
|
60
|
+
.map((line) => line.trim().split(/\s+/)[0])
|
|
61
|
+
.filter((ip) => ip.length > 0);
|
|
58
62
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
try {
|
|
62
|
-
// Synchronous DNS lookup via Node.js dns.resolve
|
|
63
|
-
const addresses = dns.resolve4.length
|
|
64
|
-
? (() => {
|
|
65
|
-
try {
|
|
66
|
-
// Use execFileSync with getent (Linux) as primary method
|
|
67
|
-
const result = execFileSync("getent", ["hosts", target], {
|
|
68
|
-
encoding: "utf-8",
|
|
69
|
-
timeout: 5000
|
|
70
|
-
}).trim();
|
|
71
|
-
const resolved = [];
|
|
72
|
-
for (const line of result.split("\n")) {
|
|
73
|
-
const ip = line.trim().split(/\s+/)[0];
|
|
74
|
-
if (ip)
|
|
75
|
-
resolved.push(ip);
|
|
76
|
-
}
|
|
77
|
-
return resolved;
|
|
78
|
-
}
|
|
79
|
-
catch {
|
|
80
|
-
// getent not available (macOS) — fall back to dscacheutil or just skip
|
|
81
|
-
try {
|
|
82
|
-
const result = execFileSync("dscacheutil", ["-q", "host", "-a", "name", target], { encoding: "utf-8", timeout: 5000 }).trim();
|
|
83
|
-
const resolved = [];
|
|
84
|
-
for (const line of result.split("\n")) {
|
|
85
|
-
const match = line.match(/^ip_address:\s+(.+)/);
|
|
86
|
-
if (match)
|
|
87
|
-
resolved.push(match[1].trim());
|
|
88
|
-
}
|
|
89
|
-
return resolved;
|
|
90
|
-
}
|
|
91
|
-
catch {
|
|
92
|
-
return [];
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
})()
|
|
96
|
-
: [];
|
|
97
|
-
for (const ip of addresses)
|
|
98
|
-
ips.add(ip);
|
|
99
|
-
}
|
|
100
|
-
catch {
|
|
101
|
-
// Unable to resolve
|
|
102
|
-
}
|
|
103
|
-
return ips;
|
|
63
|
+
catch {
|
|
64
|
+
// getent is Linux-only; macOS answers through dscacheutil instead.
|
|
104
65
|
}
|
|
105
|
-
let hostIps;
|
|
106
66
|
try {
|
|
107
|
-
|
|
67
|
+
return execFileSync("dscacheutil", ["-q", "host", "-a", "name", target], {
|
|
68
|
+
encoding: "utf-8",
|
|
69
|
+
timeout: 5000
|
|
70
|
+
})
|
|
71
|
+
.trim()
|
|
72
|
+
.split("\n")
|
|
73
|
+
.map((line) => line.match(/^ip_address:\s+(.+)/))
|
|
74
|
+
.filter((match) => match !== null)
|
|
75
|
+
.map((match) => match[1].trim());
|
|
108
76
|
}
|
|
109
77
|
catch {
|
|
110
|
-
return
|
|
78
|
+
return [];
|
|
111
79
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
catch {
|
|
121
|
-
continue;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
for (const ip of hostIps) {
|
|
125
|
-
if (localIps.has(ip))
|
|
126
|
-
return true;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Check if host resolves to the local machine.
|
|
83
|
+
*/
|
|
84
|
+
export function isLocalhost(host) {
|
|
85
|
+
if (LOCALHOST_NAMES.has(host.trim().toLowerCase())) {
|
|
86
|
+
return true;
|
|
127
87
|
}
|
|
128
|
-
|
|
88
|
+
const hostIps = resolveIps(host);
|
|
89
|
+
if (hostIps.length === 0)
|
|
90
|
+
return false;
|
|
91
|
+
const localIps = new Set([
|
|
92
|
+
...resolveIps("localhost"),
|
|
93
|
+
...resolveIps(os.hostname())
|
|
94
|
+
]);
|
|
95
|
+
return hostIps.some((ip) => localIps.has(ip));
|
|
129
96
|
}
|
|
130
97
|
// ---------------------------------------------------------------------------
|
|
131
98
|
// LocalExecutor
|
|
@@ -148,11 +115,8 @@ export class LocalExecutor {
|
|
|
148
115
|
// nothing to do
|
|
149
116
|
}
|
|
150
117
|
/**
|
|
151
|
-
* Execute a command locally
|
|
152
|
-
*
|
|
153
|
-
* Matches SSHConnection behavior by running in a shell.
|
|
154
|
-
* This allows shell features like pipes, redirects, &&/|| operators,
|
|
155
|
-
* and environment variable assignments.
|
|
118
|
+
* Execute a command locally in a shell, so pipes, redirects, &&/||, and
|
|
119
|
+
* environment-variable assignments work as they do over SSH.
|
|
156
120
|
*/
|
|
157
121
|
async execute(command, check = true, timeout) {
|
|
158
122
|
try {
|
|
@@ -194,15 +158,12 @@ export class LocalExecutor {
|
|
|
194
158
|
}
|
|
195
159
|
}
|
|
196
160
|
// ---------------------------------------------------------------------------
|
|
197
|
-
//
|
|
161
|
+
// DockerDeployer
|
|
198
162
|
// ---------------------------------------------------------------------------
|
|
199
163
|
/**
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
* Type parameter `T` is the concrete deployment configuration type
|
|
203
|
-
* (e.g. DockerDeployment).
|
|
164
|
+
* Deployer for Docker-based self-hosted deployments, over SSH or locally.
|
|
204
165
|
*/
|
|
205
|
-
export class
|
|
166
|
+
export class DockerDeployer {
|
|
206
167
|
deploymentName;
|
|
207
168
|
deployment;
|
|
208
169
|
stateManager;
|
|
@@ -274,24 +235,6 @@ export class BaseSSHDeployer {
|
|
|
274
235
|
}
|
|
275
236
|
}
|
|
276
237
|
}
|
|
277
|
-
// ---- Content upload ----------------------------------------------------
|
|
278
|
-
async uploadContent(ssh, content, remotePath) {
|
|
279
|
-
if (this.isLocalhost) {
|
|
280
|
-
const expanded = remotePath.startsWith("~")
|
|
281
|
-
? expandUser(remotePath)
|
|
282
|
-
: remotePath;
|
|
283
|
-
const dir = path.dirname(expanded);
|
|
284
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
285
|
-
fs.writeFileSync(expanded, content, "utf-8");
|
|
286
|
-
}
|
|
287
|
-
else {
|
|
288
|
-
// Remote upload via base64 to avoid shell escaping issues
|
|
289
|
-
const b64Content = Buffer.from(content, "utf-8").toString("base64");
|
|
290
|
-
const dirName = path.posix.dirname(remotePath);
|
|
291
|
-
await ssh.execute(`mkdir -p ${safeShellQuote(dirName)}`, true);
|
|
292
|
-
await ssh.execute(`echo ${b64Content} | base64 -d > ${safeShellQuote(remotePath)}`, true);
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
238
|
// ---- Directory creation ------------------------------------------------
|
|
296
239
|
async createDirectories(ssh, results) {
|
|
297
240
|
this.log(results, "Creating directories...");
|
|
@@ -301,8 +244,8 @@ export class BaseSSHDeployer {
|
|
|
301
244
|
await ssh.mkdir(`${workspacePath}/data`, 0o755, true);
|
|
302
245
|
await ssh.mkdir(`${workspacePath}/assets`, 0o755, true);
|
|
303
246
|
await ssh.mkdir(`${workspacePath}/temp`, 0o755, true);
|
|
304
|
-
|
|
305
|
-
await
|
|
247
|
+
await ssh.mkdir(`${workspacePath}/proxy`, 0o755, true);
|
|
248
|
+
await ssh.mkdir(`${workspacePath}/acme`, 0o755, true);
|
|
306
249
|
const hfCachePath = expandUser(this.deployment.paths.hf_cache);
|
|
307
250
|
await ssh.mkdir(hfCachePath, 0o755, true);
|
|
308
251
|
this.log(results, ` Created: ${hfCachePath}`);
|
|
@@ -372,18 +315,7 @@ export class BaseSSHDeployer {
|
|
|
372
315
|
return 8000;
|
|
373
316
|
return container?.port ?? 8000;
|
|
374
317
|
}
|
|
375
|
-
|
|
376
|
-
// ---------------------------------------------------------------------------
|
|
377
|
-
// DockerDeployer
|
|
378
|
-
// ---------------------------------------------------------------------------
|
|
379
|
-
/**
|
|
380
|
-
* Deployer for Docker-based self-hosted deployments.
|
|
381
|
-
*/
|
|
382
|
-
export class DockerDeployer extends BaseSSHDeployer {
|
|
383
|
-
async createSpecificDirectories(ssh, workspacePath) {
|
|
384
|
-
await ssh.mkdir(`${workspacePath}/proxy`, 0o755, true);
|
|
385
|
-
await ssh.mkdir(`${workspacePath}/acme`, 0o755, true);
|
|
386
|
-
}
|
|
318
|
+
// ---- Deployment operations ---------------------------------------------
|
|
387
319
|
async plan() {
|
|
388
320
|
const plan = {
|
|
389
321
|
deployment_name: this.deploymentName,
|
|
@@ -413,8 +345,9 @@ export class DockerDeployer extends BaseSSHDeployer {
|
|
|
413
345
|
return plan;
|
|
414
346
|
}
|
|
415
347
|
async apply(opts) {
|
|
348
|
+
// A dry run reports the plan itself rather than a deployment result.
|
|
416
349
|
if (opts?.dryRun) {
|
|
417
|
-
return
|
|
350
|
+
return await this.plan();
|
|
418
351
|
}
|
|
419
352
|
const results = {
|
|
420
353
|
deployment_name: this.deploymentName,
|
|
@@ -430,13 +363,9 @@ export class DockerDeployer extends BaseSSHDeployer {
|
|
|
430
363
|
await this.withExecutor(async (executor) => {
|
|
431
364
|
await this.createDirectories(executor, results);
|
|
432
365
|
await this.ensureImage(executor, results);
|
|
433
|
-
// Stop existing container if present
|
|
434
366
|
await this.stopExistingContainer(executor, results);
|
|
435
|
-
// Start container
|
|
436
367
|
const containerRunHash = await this.startContainer(executor, results);
|
|
437
|
-
// Check health
|
|
438
368
|
await this.checkHealth(executor, results);
|
|
439
|
-
// Update state with success
|
|
440
369
|
const containerName = this.containerName();
|
|
441
370
|
await this.stateManager.writeState(this.deploymentName, {
|
|
442
371
|
status: DeploymentStatus.RUNNING,
|
|
@@ -650,7 +579,6 @@ export class DockerDeployer extends BaseSSHDeployer {
|
|
|
650
579
|
await this.withExecutor(async (ssh) => {
|
|
651
580
|
const containerName = this.containerName();
|
|
652
581
|
const runtime = this.runtimeCommandForShell();
|
|
653
|
-
// Stop container
|
|
654
582
|
try {
|
|
655
583
|
await ssh.execute(`${runtime} stop ${safeShellQuote(containerName)}`, false, 30);
|
|
656
584
|
results.steps.push(`Container stopped: ${containerName}`);
|
|
@@ -660,7 +588,6 @@ export class DockerDeployer extends BaseSSHDeployer {
|
|
|
660
588
|
results.steps.push(`Warning: Failed to stop container: ${e.stderr}`);
|
|
661
589
|
}
|
|
662
590
|
}
|
|
663
|
-
// Remove container
|
|
664
591
|
try {
|
|
665
592
|
await ssh.execute(`${runtime} rm ${safeShellQuote(containerName)}`, false, 30);
|
|
666
593
|
results.steps.push(`Container removed: ${containerName}`);
|
|
@@ -671,7 +598,6 @@ export class DockerDeployer extends BaseSSHDeployer {
|
|
|
671
598
|
}
|
|
672
599
|
throw e;
|
|
673
600
|
}
|
|
674
|
-
// Update state
|
|
675
601
|
await this.stateManager.updateDeploymentStatus(this.deploymentName, DeploymentStatus.DESTROYED);
|
|
676
602
|
});
|
|
677
603
|
}
|