@nosana/node 1.1.62-rc → 1.1.64-rc
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/.env +2 -3
- package/dist/.env.dev +1 -1
- package/dist/.env.prd +1 -1
- package/dist/package.json +3 -2
- package/dist/src/NodeManager/configs/NodeConfigs.d.ts +1 -0
- package/dist/src/NodeManager/configs/NodeConfigs.js +4 -1
- package/dist/src/NodeManager/configs/configs.js +2 -2
- package/dist/src/NodeManager/db/index.d.ts +23 -0
- package/dist/src/NodeManager/index.js +8 -3
- package/dist/src/NodeManager/node/api/ApiHandler.js +16 -3
- package/dist/src/NodeManager/node/api/middlewares/index.d.ts +0 -2
- package/dist/src/NodeManager/node/api/middlewares/index.js +0 -2
- package/dist/src/NodeManager/node/api/middlewares/rest/verifyJobOwnerSignatureMiddleware.js +1 -0
- package/dist/src/NodeManager/node/api/middlewares/ws/verifyWSJobOwnerSignatureMiddleware.js +1 -0
- package/dist/src/NodeManager/node/api/routes/get/node-info.js +9 -7
- package/dist/src/NodeManager/node/api/routes/post/index.d.ts +1 -0
- package/dist/src/NodeManager/node/api/routes/post/index.js +1 -0
- package/dist/src/NodeManager/node/api/routes/post/ssh-authorize.d.ts +17 -0
- package/dist/src/NodeManager/node/api/routes/post/ssh-authorize.js +53 -0
- package/dist/src/NodeManager/node/api/routes/wss/index.d.ts +2 -0
- package/dist/src/NodeManager/node/api/routes/wss/index.js +2 -0
- package/dist/src/NodeManager/node/api/routes/wss/log.js +2 -1
- package/dist/src/NodeManager/node/api/routes/wss/terminal/terminal.d.ts +21 -0
- package/dist/src/NodeManager/node/api/routes/wss/terminal/terminal.js +165 -0
- package/dist/src/NodeManager/node/api/routes/wss/terminal/terminalAuthorization.d.ts +18 -0
- package/dist/src/NodeManager/node/api/routes/wss/terminal/terminalAuthorization.js +61 -0
- package/dist/src/NodeManager/node/market/hostManager.d.ts +3 -3
- package/dist/src/NodeManager/node/resource/helpers/isAuthGatedResource.d.ts +9 -0
- package/dist/src/NodeManager/node/resource/helpers/isAuthGatedResource.js +11 -0
- package/dist/src/NodeManager/node/resource/image/imageManager.d.ts +14 -1
- package/dist/src/NodeManager/node/resource/image/imageManager.js +30 -4
- package/dist/src/NodeManager/node/resource/resourceManager.d.ts +1 -1
- package/dist/src/NodeManager/node/resource/resourceManager.js +21 -5
- package/dist/src/NodeManager/node/resource/volume/volumeManager.d.ts +31 -4
- package/dist/src/NodeManager/node/resource/volume/volumeManager.js +69 -12
- package/dist/src/NodeManager/provider/Provider.d.ts +29 -0
- package/dist/src/NodeManager/provider/Provider.js +143 -61
- package/dist/src/NodeManager/provider/SshAccessManager.d.ts +49 -0
- package/dist/src/NodeManager/provider/SshAccessManager.js +148 -0
- package/dist/src/NodeManager/provider/containerOrchestration/docker/createDockerRunOptions.d.ts +1 -1
- package/dist/src/NodeManager/provider/containerOrchestration/docker/createDockerRunOptions.js +6 -3
- package/dist/src/NodeManager/provider/containerOrchestration/docker/index.d.ts +5 -1
- package/dist/src/NodeManager/provider/containerOrchestration/docker/index.js +40 -2
- package/dist/src/NodeManager/provider/containerOrchestration/interface.d.ts +28 -1
- package/dist/src/NodeManager/provider/containerOrchestration/labels.d.ts +10 -0
- package/dist/src/NodeManager/provider/containerOrchestration/labels.js +12 -0
- package/dist/src/NodeManager/provider/containerOrchestration/podman/index.js +14 -9
- package/dist/src/NodeManager/provider/containerOrchestration/podman/utils/createPodmanRunOptions.d.ts +39 -0
- package/dist/src/NodeManager/provider/containerOrchestration/podman/utils/createPodmanRunOptions.js +16 -2
- package/dist/src/NodeManager/provider/sshSidecar.d.ts +66 -0
- package/dist/src/NodeManager/provider/sshSidecar.js +176 -0
- package/dist/src/NodeManager/provider/webTerminal.d.ts +24 -0
- package/dist/src/NodeManager/provider/webTerminal.js +98 -0
- package/dist/src/NodeManager/repository/NodeRepository.d.ts +5 -1
- package/dist/src/NodeManager/repository/NodeRepository.js +20 -0
- package/npm-shrinkwrap.json +28 -2
- package/package.json +3 -2
- package/dist/src/NodeManager/node/api/middlewares/ws/verifyWSMiddleware.d.ts +0 -3
- package/dist/src/NodeManager/node/api/middlewares/ws/verifyWSMiddleware.js +0 -20
- package/dist/src/NodeManager/node/api/middlewares/ws/verifyWSNodeOrJobOwnerSignatureMiddleware.d.ts +0 -3
- package/dist/src/NodeManager/node/api/middlewares/ws/verifyWSNodeOrJobOwnerSignatureMiddleware.js +0 -33
|
@@ -28,7 +28,7 @@ export declare class HostManager {
|
|
|
28
28
|
};
|
|
29
29
|
content: {
|
|
30
30
|
"application/json": {
|
|
31
|
-
status: "
|
|
31
|
+
status: "success" | "failed";
|
|
32
32
|
report?: {
|
|
33
33
|
marketAddress: string;
|
|
34
34
|
marketName?: string | undefined;
|
|
@@ -43,7 +43,7 @@ export declare class HostManager {
|
|
|
43
43
|
} | undefined;
|
|
44
44
|
};
|
|
45
45
|
"multipart/form-data": {
|
|
46
|
-
status: "
|
|
46
|
+
status: "success" | "failed";
|
|
47
47
|
report?: {
|
|
48
48
|
marketAddress: string;
|
|
49
49
|
marketName?: string | undefined;
|
|
@@ -58,7 +58,7 @@ export declare class HostManager {
|
|
|
58
58
|
} | undefined;
|
|
59
59
|
};
|
|
60
60
|
"text/plain": {
|
|
61
|
-
status: "
|
|
61
|
+
status: "success" | "failed";
|
|
62
62
|
report?: {
|
|
63
63
|
marketAddress: string;
|
|
64
64
|
marketName?: string | undefined;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Resource } from '@nosana/sdk/dist/types/resources.js';
|
|
2
|
+
/**
|
|
3
|
+
* Whether a resource is fetched from an auth-gated remote — S3 behind IAM
|
|
4
|
+
* credentials or a Hugging Face repo behind an access token. Such a resource is
|
|
5
|
+
* private to the credentials that fetched it, so its cached volume must be
|
|
6
|
+
* locked to the fetching project rather than shared: a later job must present
|
|
7
|
+
* its own credentials, not reuse a cache it was never entitled to.
|
|
8
|
+
*/
|
|
9
|
+
export declare function isAuthGatedResource(resource: Resource): boolean;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a resource is fetched from an auth-gated remote — S3 behind IAM
|
|
3
|
+
* credentials or a Hugging Face repo behind an access token. Such a resource is
|
|
4
|
+
* private to the credentials that fetched it, so its cached volume must be
|
|
5
|
+
* locked to the fetching project rather than shared: a later job must present
|
|
6
|
+
* its own credentials, not reuse a cache it was never entitled to.
|
|
7
|
+
*/
|
|
8
|
+
export function isAuthGatedResource(resource) {
|
|
9
|
+
return ((resource.type === 'S3' && !!resource.IAM) ||
|
|
10
|
+
(resource.type === 'HF' && !!resource.accessToken));
|
|
11
|
+
}
|
|
@@ -18,5 +18,18 @@ export declare class ImageManager {
|
|
|
18
18
|
pullMarketRequiredImages(required_images: RequiredImage[]): Promise<void>;
|
|
19
19
|
pruneImages(): Promise<void>;
|
|
20
20
|
resyncImagesDB(): Promise<void>;
|
|
21
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Whether a job for `project` must re-authenticate before it may use this
|
|
23
|
+
* image, rather than reuse the cached copy. True only for a private image the
|
|
24
|
+
* node last pulled for a *different* project: the daemon holds one copy per
|
|
25
|
+
* image reference, so the caller forces a pull, which re-verifies this
|
|
26
|
+
* project's credentials against the registry (reusing cached layers) and
|
|
27
|
+
* denies a job that cannot authenticate.
|
|
28
|
+
*
|
|
29
|
+
* The project that already owns the cached image, a public image, and a
|
|
30
|
+
* market-required image (shared across the market by design) all reuse the
|
|
31
|
+
* cache with no registry round-trip.
|
|
32
|
+
*/
|
|
33
|
+
requiresReauth(image: string, project?: string): boolean;
|
|
34
|
+
setImage(image: string, isPrivate?: boolean, project?: string): Promise<void>;
|
|
22
35
|
}
|
|
@@ -45,7 +45,7 @@ export class ImageManager {
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
async resyncImagesDB() {
|
|
48
|
-
for (const [image, { lastUsed, required
|
|
48
|
+
for (const [image, { lastUsed, required }] of Object.entries(this.repository.getImagesResources())) {
|
|
49
49
|
if (!(await this.containerOrchestration.hasImage(image))) {
|
|
50
50
|
this.repository.deleteImageResource(image);
|
|
51
51
|
continue;
|
|
@@ -54,19 +54,45 @@ export class ImageManager {
|
|
|
54
54
|
this.market_required_images.includes(image)) {
|
|
55
55
|
continue;
|
|
56
56
|
}
|
|
57
|
+
// A private image is kept cached like any other and ages out on the same
|
|
58
|
+
// unused rule: cross-project reuse is gated at pull time by `project`
|
|
59
|
+
// (see requiresReauth), so it no longer has to be dropped on sight.
|
|
57
60
|
const hoursSinceLastUsed = hoursSinceDate(new Date(lastUsed));
|
|
58
|
-
if (hoursSinceLastUsed > 24
|
|
61
|
+
if (hoursSinceLastUsed > 24) {
|
|
59
62
|
await this.containerOrchestration.deleteImage(image);
|
|
60
63
|
this.repository.deleteImageResource(image);
|
|
61
64
|
}
|
|
62
65
|
}
|
|
63
66
|
}
|
|
64
|
-
|
|
67
|
+
/**
|
|
68
|
+
* Whether a job for `project` must re-authenticate before it may use this
|
|
69
|
+
* image, rather than reuse the cached copy. True only for a private image the
|
|
70
|
+
* node last pulled for a *different* project: the daemon holds one copy per
|
|
71
|
+
* image reference, so the caller forces a pull, which re-verifies this
|
|
72
|
+
* project's credentials against the registry (reusing cached layers) and
|
|
73
|
+
* denies a job that cannot authenticate.
|
|
74
|
+
*
|
|
75
|
+
* The project that already owns the cached image, a public image, and a
|
|
76
|
+
* market-required image (shared across the market by design) all reuse the
|
|
77
|
+
* cache with no registry round-trip.
|
|
78
|
+
*/
|
|
79
|
+
requiresReauth(image, project) {
|
|
80
|
+
if (this.market_required_images.includes(image))
|
|
81
|
+
return false;
|
|
82
|
+
const entry = this.repository.getImageResource(image);
|
|
83
|
+
return !!entry?.isPrivate && entry.project !== project;
|
|
84
|
+
}
|
|
85
|
+
async setImage(image, isPrivate = false, project) {
|
|
65
86
|
const imageObj = this.repository.getImageResource(image);
|
|
66
87
|
this.repository.updateImageResource(image, {
|
|
67
88
|
lastUsed: new Date(),
|
|
68
89
|
usage: imageObj?.usage + 1 || 1,
|
|
69
|
-
|
|
90
|
+
// Never downgrade a known-private image to public — a later reuse by its
|
|
91
|
+
// owner arrives without credentials — and keep the owning project when
|
|
92
|
+
// this pull did not re-establish one. A different project claims the image
|
|
93
|
+
// only by passing its own project after a successful credentialed pull.
|
|
94
|
+
isPrivate: isPrivate || !!imageObj?.isPrivate,
|
|
95
|
+
project: project ?? imageObj?.project,
|
|
70
96
|
});
|
|
71
97
|
}
|
|
72
98
|
}
|
|
@@ -45,7 +45,7 @@ export declare class ResourceManager {
|
|
|
45
45
|
resyncResourcesDB(): Promise<void>;
|
|
46
46
|
fetchMarketRequiredResources(market: Market | string): Promise<void>;
|
|
47
47
|
prune(): Promise<void>;
|
|
48
|
-
getResourceVolumes(resources: Resource[], controller: AbortController, job?: JobContext): Promise<{
|
|
48
|
+
getResourceVolumes(resources: Resource[], controller: AbortController, job?: JobContext, project?: string): Promise<{
|
|
49
49
|
dest: string;
|
|
50
50
|
name: string;
|
|
51
51
|
readonly?: boolean;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { HostManager } from '../market/hostManager.js';
|
|
2
2
|
import { createResourceName } from './helpers/createResourceName.js';
|
|
3
|
+
import { isAuthGatedResource } from './helpers/isAuthGatedResource.js';
|
|
3
4
|
import { ImageManager } from './image/imageManager.js';
|
|
4
5
|
import { VolumeManager } from './volume/volumeManager.js';
|
|
5
6
|
/**
|
|
@@ -71,11 +72,26 @@ export class ResourceManager {
|
|
|
71
72
|
await this.images.pruneImages();
|
|
72
73
|
await this.volumes.pruneVolumes();
|
|
73
74
|
}
|
|
74
|
-
async getResourceVolumes(resources, controller, job) {
|
|
75
|
+
async getResourceVolumes(resources, controller, job, project) {
|
|
75
76
|
const volumes = [];
|
|
76
77
|
for (const resource of resources) {
|
|
77
|
-
|
|
78
|
-
|
|
78
|
+
const allowWrite = resource.type === 'S3' && !!resource.allowWrite;
|
|
79
|
+
// A writable mount can leave the job's bytes behind, and an auth-gated
|
|
80
|
+
// remote is private to the credentials that fetched it; either way the
|
|
81
|
+
// volume must be locked to this project rather than shared, so a different
|
|
82
|
+
// project pulls its own clean copy with its own credentials.
|
|
83
|
+
const lockToProject = allowWrite || isAuthGatedResource(resource);
|
|
84
|
+
// Locks the resource to this project (claiming the shared copy if one
|
|
85
|
+
// exists) so its volume is never reused across projects. Unlocked
|
|
86
|
+
// resources resolve to the shared key, untouched — unless this project
|
|
87
|
+
// already holds a claim, which is reused for every later job.
|
|
88
|
+
const resourceKey = this.volumes.resolveResourceKey(resource, project, lockToProject);
|
|
89
|
+
// A project-scoped key must keep carrying its project so a read-only
|
|
90
|
+
// reuse does not clear the claim; the shared key carries none. The key
|
|
91
|
+
// itself is the source of truth here, not this job's lock state.
|
|
92
|
+
const claimedForProject = resourceKey !== createResourceName(resource);
|
|
93
|
+
await this.volumes.createRemoteVolume(resource, controller, job, resourceKey, claimedForProject ? project : undefined);
|
|
94
|
+
if ((await this.volumes.hasVolume(resource, resourceKey)) === false) {
|
|
79
95
|
const error = new Error(`Missing required resource ${createResourceName(resource)}.`);
|
|
80
96
|
throw error;
|
|
81
97
|
}
|
|
@@ -85,8 +101,8 @@ export class ResourceManager {
|
|
|
85
101
|
? '/root/.ollama/models'
|
|
86
102
|
: resource.target
|
|
87
103
|
: resource.target,
|
|
88
|
-
name: await this.volumes.getVolume(resource),
|
|
89
|
-
readonly:
|
|
104
|
+
name: await this.volumes.getVolume(resource, resourceKey),
|
|
105
|
+
readonly: !allowWrite,
|
|
90
106
|
});
|
|
91
107
|
}
|
|
92
108
|
return volumes;
|
|
@@ -52,11 +52,38 @@ export declare class VolumeManager {
|
|
|
52
52
|
private progressBarReporter;
|
|
53
53
|
constructor(containerOrchestration: ContainerOrchestrationInterface, repository: NodeRepository);
|
|
54
54
|
pullMarketRequiredVolumes(remoteResources: RequiredResource[]): Promise<void>;
|
|
55
|
-
|
|
55
|
+
/**
|
|
56
|
+
* The DB key a job's resource must use, locking a resource to one project
|
|
57
|
+
* when it must not be shared across projects.
|
|
58
|
+
*
|
|
59
|
+
* Two things force a lock. A writable (`allowWrite`) mount can leave the job's
|
|
60
|
+
* own bytes in the cached volume, so a later job reusing it could be handed
|
|
61
|
+
* code an earlier project planted. An auth-gated resource is private to the
|
|
62
|
+
* credentials that fetched it, so a later job reusing the cache would read
|
|
63
|
+
* private data without presenting its own credentials. Either way the first
|
|
64
|
+
* such use claims the resource for its project — the volume is tagged and
|
|
65
|
+
* moved to a project-scoped key, which frees the shared key for the clean copy
|
|
66
|
+
* the next project downloads with its own credentials.
|
|
67
|
+
*
|
|
68
|
+
* Claiming renames the DB entry only; the Docker volume and its contents stay
|
|
69
|
+
* in place, so the claiming project inherits the already-downloaded data with
|
|
70
|
+
* no re-download. The next use of the shared key pays for a fresh pull.
|
|
71
|
+
*
|
|
72
|
+
* Once a project holds a claim, every later job of that project reuses it —
|
|
73
|
+
* read-only ones included, even public ones that would not have locked on
|
|
74
|
+
* their own. The claimed copy holds only what this project's own jobs put
|
|
75
|
+
* there, so reusing it leaks nothing across projects and spares the project a
|
|
76
|
+
* second download of a resource it already has.
|
|
77
|
+
*
|
|
78
|
+
* A resource with no claim for this project and nothing forcing one (a public,
|
|
79
|
+
* read-only mount) and market preloads stay on the shared key, reused freely.
|
|
80
|
+
*/
|
|
81
|
+
resolveResourceKey(resource: Resource, project?: string, lockToProject?: boolean): string;
|
|
82
|
+
createRemoteVolume(resource: RequiredResource, controller: AbortController, job?: JobContext, resourceKey?: string, project?: string): Promise<string>;
|
|
56
83
|
private runResourceManagerContainer;
|
|
57
|
-
setVolume(bucket: string, volume: string): Promise<void>;
|
|
58
|
-
hasVolume(resource: Resource): Promise<boolean>;
|
|
59
|
-
getVolume(resource: RequiredResource | Resource): Promise<string>;
|
|
84
|
+
setVolume(bucket: string, volume: string, project?: string): Promise<void>;
|
|
85
|
+
hasVolume(resource: Resource, resourceKey?: string): Promise<boolean>;
|
|
86
|
+
getVolume(resource: RequiredResource | Resource, resourceKey?: string): Promise<string>;
|
|
60
87
|
pruneVolumes(): Promise<void>;
|
|
61
88
|
resyncResourcesDB(): Promise<void>;
|
|
62
89
|
}
|
|
@@ -115,8 +115,63 @@ export class VolumeManager {
|
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
|
-
|
|
119
|
-
|
|
118
|
+
/**
|
|
119
|
+
* The DB key a job's resource must use, locking a resource to one project
|
|
120
|
+
* when it must not be shared across projects.
|
|
121
|
+
*
|
|
122
|
+
* Two things force a lock. A writable (`allowWrite`) mount can leave the job's
|
|
123
|
+
* own bytes in the cached volume, so a later job reusing it could be handed
|
|
124
|
+
* code an earlier project planted. An auth-gated resource is private to the
|
|
125
|
+
* credentials that fetched it, so a later job reusing the cache would read
|
|
126
|
+
* private data without presenting its own credentials. Either way the first
|
|
127
|
+
* such use claims the resource for its project — the volume is tagged and
|
|
128
|
+
* moved to a project-scoped key, which frees the shared key for the clean copy
|
|
129
|
+
* the next project downloads with its own credentials.
|
|
130
|
+
*
|
|
131
|
+
* Claiming renames the DB entry only; the Docker volume and its contents stay
|
|
132
|
+
* in place, so the claiming project inherits the already-downloaded data with
|
|
133
|
+
* no re-download. The next use of the shared key pays for a fresh pull.
|
|
134
|
+
*
|
|
135
|
+
* Once a project holds a claim, every later job of that project reuses it —
|
|
136
|
+
* read-only ones included, even public ones that would not have locked on
|
|
137
|
+
* their own. The claimed copy holds only what this project's own jobs put
|
|
138
|
+
* there, so reusing it leaks nothing across projects and spares the project a
|
|
139
|
+
* second download of a resource it already has.
|
|
140
|
+
*
|
|
141
|
+
* A resource with no claim for this project and nothing forcing one (a public,
|
|
142
|
+
* read-only mount) and market preloads stay on the shared key, reused freely.
|
|
143
|
+
*/
|
|
144
|
+
resolveResourceKey(resource, project, lockToProject = false) {
|
|
145
|
+
const sharedKey = createResourceName(resource);
|
|
146
|
+
if (!project) {
|
|
147
|
+
return sharedKey;
|
|
148
|
+
}
|
|
149
|
+
const claimedKey = `${sharedKey}::${project}`;
|
|
150
|
+
// Already claimed by this project on an earlier job — reuse it, writes and
|
|
151
|
+
// all, whatever this job's own lock state. This is what lets a later
|
|
152
|
+
// read-only job read back a copy an earlier writable job of the same
|
|
153
|
+
// project produced, rather than re-downloading it.
|
|
154
|
+
if (this.repository.getVolumeResource(claimedKey)) {
|
|
155
|
+
return claimedKey;
|
|
156
|
+
}
|
|
157
|
+
// No claim yet, and nothing forces one: an unlocked (public, read-only)
|
|
158
|
+
// resource stays on the shared key, reused freely across projects.
|
|
159
|
+
if (!lockToProject) {
|
|
160
|
+
return sharedKey;
|
|
161
|
+
}
|
|
162
|
+
// First locked use of this resource by this project. An unclaimed shared
|
|
163
|
+
// volume already holds it: claim it in place rather than re-downloading.
|
|
164
|
+
// Moving it to the project key frees the shared key for the next project's
|
|
165
|
+
// clean copy.
|
|
166
|
+
const shared = this.repository.getVolumeResource(sharedKey);
|
|
167
|
+
if (shared && !shared.project) {
|
|
168
|
+
this.repository.updateVolumeResource(claimedKey, { ...shared, project });
|
|
169
|
+
this.repository.deleteVolumeResource(sharedKey);
|
|
170
|
+
}
|
|
171
|
+
return claimedKey;
|
|
172
|
+
}
|
|
173
|
+
async createRemoteVolume(resource, controller, job, resourceKey, project) {
|
|
174
|
+
const resourceName = resourceKey ?? createResourceName(resource);
|
|
120
175
|
// Checked before anything is allocated. Left to the download, an empty repo
|
|
121
176
|
// becomes a request for `/api/models//tree/main` and comes back as a bare
|
|
122
177
|
// 404 with nothing in it naming the resource at fault.
|
|
@@ -141,6 +196,7 @@ export class VolumeManager {
|
|
|
141
196
|
lastUsed: new Date(),
|
|
142
197
|
usage: 0,
|
|
143
198
|
pending: true,
|
|
199
|
+
project,
|
|
144
200
|
});
|
|
145
201
|
}
|
|
146
202
|
switch (resource.type) {
|
|
@@ -161,7 +217,7 @@ export class VolumeManager {
|
|
|
161
217
|
await this.runResourceManagerContainer(volumeName, resourceName, args, controller, sync, job);
|
|
162
218
|
}
|
|
163
219
|
}
|
|
164
|
-
this.setVolume(resourceName, volumeName);
|
|
220
|
+
this.setVolume(resourceName, volumeName, project);
|
|
165
221
|
}
|
|
166
222
|
catch (err) {
|
|
167
223
|
throw new Error(err.message);
|
|
@@ -172,7 +228,7 @@ export class VolumeManager {
|
|
|
172
228
|
const { repo, revision, files: hfFiles, accessToken, } = resource;
|
|
173
229
|
const args = createHFArgs(volumeName, { repo, revision, files: hfFiles }, accessToken);
|
|
174
230
|
await this.runResourceManagerContainer(volumeName, resourceName, args, controller, sync, job);
|
|
175
|
-
this.setVolume(resourceName, volumeName);
|
|
231
|
+
this.setVolume(resourceName, volumeName, project);
|
|
176
232
|
}
|
|
177
233
|
catch (err) {
|
|
178
234
|
throw new Error(err.message);
|
|
@@ -183,7 +239,7 @@ export class VolumeManager {
|
|
|
183
239
|
const { model } = resource;
|
|
184
240
|
const args = createOllamaArgs(volumeName, model);
|
|
185
241
|
await this.runResourceManagerContainer(volumeName, resourceName, args, controller, sync, job);
|
|
186
|
-
this.setVolume(resourceName, volumeName);
|
|
242
|
+
this.setVolume(resourceName, volumeName, project);
|
|
187
243
|
}
|
|
188
244
|
catch (err) {
|
|
189
245
|
throw new Error(err.message);
|
|
@@ -284,7 +340,7 @@ export class VolumeManager {
|
|
|
284
340
|
this.progressBarReporter.stop(`${syncing ? 'Synced' : 'Downloaded'} resource ${name} completed`);
|
|
285
341
|
await this.containerOrchestration.stopAndDeleteContainer(container.id);
|
|
286
342
|
}
|
|
287
|
-
async setVolume(bucket, volume) {
|
|
343
|
+
async setVolume(bucket, volume, project) {
|
|
288
344
|
const volumeObj = this.repository.getVolumeResource(bucket);
|
|
289
345
|
this.repository.updateVolumeResource(bucket, {
|
|
290
346
|
volume,
|
|
@@ -293,22 +349,23 @@ export class VolumeManager {
|
|
|
293
349
|
usage: volumeObj?.usage + 1 || 1,
|
|
294
350
|
// Only a completed sync reaches here, which is what pending means.
|
|
295
351
|
pending: false,
|
|
352
|
+
project,
|
|
296
353
|
});
|
|
297
354
|
}
|
|
298
|
-
async hasVolume(resource) {
|
|
299
|
-
const
|
|
355
|
+
async hasVolume(resource, resourceKey) {
|
|
356
|
+
const key = resourceKey ?? createResourceName(resource);
|
|
357
|
+
const volume = this.repository.getVolumeResource(key)?.volume;
|
|
300
358
|
if (!volume) {
|
|
301
359
|
return false;
|
|
302
360
|
}
|
|
303
361
|
const dockerHasVolume = await this.containerOrchestration.hasVolume(volume);
|
|
304
362
|
if (!dockerHasVolume) {
|
|
305
|
-
this.repository.deleteVolumeResource(
|
|
363
|
+
this.repository.deleteVolumeResource(key);
|
|
306
364
|
}
|
|
307
365
|
return dockerHasVolume;
|
|
308
366
|
}
|
|
309
|
-
async getVolume(resource) {
|
|
310
|
-
return this.repository.getVolumeResource(createResourceName(resource))
|
|
311
|
-
?.volume;
|
|
367
|
+
async getVolume(resource, resourceKey) {
|
|
368
|
+
return this.repository.getVolumeResource(resourceKey ?? createResourceName(resource))?.volume;
|
|
312
369
|
}
|
|
313
370
|
async pruneVolumes() {
|
|
314
371
|
const cachedVolumes = await this.containerOrchestration.listVolumes();
|
|
@@ -4,16 +4,45 @@ import EventEmitter from 'events';
|
|
|
4
4
|
import { NodeRepository } from '../repository/NodeRepository.js';
|
|
5
5
|
import { ResourceManager } from '../node/resource/resourceManager.js';
|
|
6
6
|
import { ContainerOrchestrationInterface } from './containerOrchestration/interface.js';
|
|
7
|
+
import { type TerminalSession } from './webTerminal.js';
|
|
7
8
|
export declare class Provider {
|
|
8
9
|
containerOrchestration: ContainerOrchestrationInterface;
|
|
9
10
|
private repository;
|
|
10
11
|
private resourceManager;
|
|
11
12
|
private readonly frpcImage;
|
|
13
|
+
private readonly sshAccess;
|
|
12
14
|
private proxyStartupAbortController;
|
|
13
15
|
constructor(containerOrchestration: ContainerOrchestrationInterface, repository: NodeRepository, resourceManager: ResourceManager);
|
|
14
16
|
hasTrustedExecutionEnvCapability(): Promise<boolean>;
|
|
15
17
|
stopReverseProxyApi(address: string): Promise<boolean>;
|
|
16
18
|
setUpReverseProxyApi(address: string, port: string): Promise<boolean>;
|
|
19
|
+
generateApiFrpcProxies(address: string, port: string): any[];
|
|
20
|
+
/**
|
|
21
|
+
* Opens an interactive PTY exec session into a running job container for the
|
|
22
|
+
* wallet-authorized web terminal. The authorization check is performed by the
|
|
23
|
+
* caller (the terminal websocket route); this only enforces that the feature
|
|
24
|
+
* is enabled and that we can reach the runtime over a socket.
|
|
25
|
+
*/
|
|
26
|
+
createJobTerminalSession(jobId: string, options?: {
|
|
27
|
+
op?: string;
|
|
28
|
+
cols?: number;
|
|
29
|
+
rows?: number;
|
|
30
|
+
}): Promise<TerminalSession>;
|
|
31
|
+
authorizeSshKeyForJob(jobId: string, publicKey: string, expiresAt?: string): Promise<void>;
|
|
32
|
+
listSshKeysForJob(jobId: string): Promise<import("../db/index.js").SshKeyGrant[]>;
|
|
33
|
+
revokeSshKeyForJob(jobId: string, publicKey: string): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* SSH requires the runtime to be reached over a socket the sidecar can mount
|
|
36
|
+
* to exec into the workload. Every SSH entry point, including the web
|
|
37
|
+
* terminal, checks this and nothing else.
|
|
38
|
+
*/
|
|
39
|
+
private get sshEnabled();
|
|
40
|
+
private assertSshEnabled;
|
|
41
|
+
/** The SSH sidecars of a job, one per operation, created ones included. */
|
|
42
|
+
private getSshSidecars;
|
|
43
|
+
private hostSocketPath?;
|
|
44
|
+
/** The runtime socket as a host path, which is what a bind source must be. */
|
|
45
|
+
private resolveHostSocketPath;
|
|
17
46
|
taskManagerContainerRunOperation(flow: Flow, op: Operation<'container/run'>, controller: AbortController, emitter: EventEmitter): Promise<void>;
|
|
18
47
|
generateFrpcContainerConfig(name: string, networks: {
|
|
19
48
|
[p: string]: {};
|