dstack 0.19.32__py3-none-any.whl → 0.19.33__py3-none-any.whl
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.
Potentially problematic release.
This version of dstack might be problematic. Click here for more details.
- dstack/_internal/cli/commands/offer.py +1 -1
- dstack/_internal/cli/services/configurators/run.py +1 -5
- dstack/_internal/core/backends/aws/compute.py +8 -5
- dstack/_internal/core/backends/azure/compute.py +9 -6
- dstack/_internal/core/backends/base/compute.py +40 -17
- dstack/_internal/core/backends/base/offers.py +5 -1
- dstack/_internal/core/backends/datacrunch/compute.py +9 -6
- dstack/_internal/core/backends/gcp/compute.py +137 -7
- dstack/_internal/core/backends/gcp/models.py +7 -0
- dstack/_internal/core/backends/gcp/resources.py +87 -5
- dstack/_internal/core/backends/hotaisle/compute.py +11 -1
- dstack/_internal/core/backends/kubernetes/compute.py +161 -83
- dstack/_internal/core/backends/kubernetes/models.py +4 -2
- dstack/_internal/core/backends/nebius/compute.py +9 -6
- dstack/_internal/core/backends/oci/compute.py +9 -6
- dstack/_internal/core/backends/runpod/compute.py +10 -6
- dstack/_internal/core/backends/vastai/compute.py +3 -1
- dstack/_internal/core/backends/vastai/configurator.py +0 -1
- dstack/_internal/core/models/fleets.py +1 -1
- dstack/_internal/core/models/profiles.py +1 -1
- dstack/_internal/core/models/runs.py +3 -2
- dstack/_internal/core/models/users.py +10 -0
- dstack/_internal/core/services/configs/__init__.py +1 -0
- dstack/_internal/server/background/tasks/process_instances.py +5 -1
- dstack/_internal/server/background/tasks/process_running_jobs.py +1 -0
- dstack/_internal/server/migrations/versions/ff1d94f65b08_user_ssh_key.py +34 -0
- dstack/_internal/server/models.py +3 -0
- dstack/_internal/server/routers/runs.py +5 -1
- dstack/_internal/server/routers/users.py +14 -2
- dstack/_internal/server/services/runs.py +9 -4
- dstack/_internal/server/services/users.py +35 -2
- dstack/_internal/server/statics/index.html +1 -1
- dstack/_internal/server/statics/main-720ce3a11140daa480cc.css +3 -0
- dstack/_internal/server/statics/{main-c51afa7f243e24d3e446.js → main-97c7e184573ca23f9fe4.js} +12218 -7625
- dstack/_internal/server/statics/{main-c51afa7f243e24d3e446.js.map → main-97c7e184573ca23f9fe4.js.map} +1 -1
- dstack/api/_public/__init__.py +9 -12
- dstack/api/_public/repos.py +0 -21
- dstack/api/_public/runs.py +64 -9
- dstack/api/server/_users.py +17 -2
- dstack/version.py +2 -2
- {dstack-0.19.32.dist-info → dstack-0.19.33.dist-info}/METADATA +2 -2
- {dstack-0.19.32.dist-info → dstack-0.19.33.dist-info}/RECORD +45 -44
- dstack/_internal/server/statics/main-56191fbfe77f49b251de.css +0 -3
- {dstack-0.19.32.dist-info → dstack-0.19.33.dist-info}/WHEEL +0 -0
- {dstack-0.19.32.dist-info → dstack-0.19.33.dist-info}/entry_points.txt +0 -0
- {dstack-0.19.32.dist-info → dstack-0.19.33.dist-info}/licenses/LICENSE.md +0 -0
|
@@ -17,7 +17,7 @@ from dstack._internal.server.schemas.runs import (
|
|
|
17
17
|
SubmitRunRequest,
|
|
18
18
|
)
|
|
19
19
|
from dstack._internal.server.security.permissions import Authenticated, ProjectMember
|
|
20
|
-
from dstack._internal.server.services import runs
|
|
20
|
+
from dstack._internal.server.services import runs, users
|
|
21
21
|
from dstack._internal.server.utils.routers import (
|
|
22
22
|
CustomORJSONResponse,
|
|
23
23
|
get_base_api_additional_responses,
|
|
@@ -111,6 +111,8 @@ async def get_plan(
|
|
|
111
111
|
This is an optional step before calling `/apply`.
|
|
112
112
|
"""
|
|
113
113
|
user, project = user_project
|
|
114
|
+
if not user.ssh_public_key and not body.run_spec.ssh_key_pub:
|
|
115
|
+
await users.refresh_ssh_key(session=session, user=user, username=user.name)
|
|
114
116
|
run_plan = await runs.get_plan(
|
|
115
117
|
session=session,
|
|
116
118
|
project=project,
|
|
@@ -137,6 +139,8 @@ async def apply_plan(
|
|
|
137
139
|
If the existing run is active and cannot be updated, it must be stopped first.
|
|
138
140
|
"""
|
|
139
141
|
user, project = user_project
|
|
142
|
+
if not user.ssh_public_key and not body.plan.run_spec.ssh_key_pub:
|
|
143
|
+
await users.refresh_ssh_key(session=session, user=user, username=user.name)
|
|
140
144
|
return CustomORJSONResponse(
|
|
141
145
|
await runs.apply_plan(
|
|
142
146
|
session=session,
|
|
@@ -36,11 +36,11 @@ async def list_users(
|
|
|
36
36
|
return CustomORJSONResponse(await users.list_users_for_user(session=session, user=user))
|
|
37
37
|
|
|
38
38
|
|
|
39
|
-
@router.post("/get_my_user", response_model=
|
|
39
|
+
@router.post("/get_my_user", response_model=UserWithCreds)
|
|
40
40
|
async def get_my_user(
|
|
41
41
|
user: UserModel = Depends(Authenticated()),
|
|
42
42
|
):
|
|
43
|
-
return CustomORJSONResponse(users.
|
|
43
|
+
return CustomORJSONResponse(users.user_model_to_user_with_creds(user))
|
|
44
44
|
|
|
45
45
|
|
|
46
46
|
@router.post("/get_user", response_model=UserWithCreds)
|
|
@@ -91,6 +91,18 @@ async def update_user(
|
|
|
91
91
|
return CustomORJSONResponse(users.user_model_to_user(res))
|
|
92
92
|
|
|
93
93
|
|
|
94
|
+
@router.post("/refresh_ssh_key", response_model=UserWithCreds)
|
|
95
|
+
async def refresh_ssh_key(
|
|
96
|
+
body: RefreshTokenRequest,
|
|
97
|
+
session: AsyncSession = Depends(get_session),
|
|
98
|
+
user: UserModel = Depends(Authenticated()),
|
|
99
|
+
):
|
|
100
|
+
res = await users.refresh_ssh_key(session=session, user=user, username=body.username)
|
|
101
|
+
if res is None:
|
|
102
|
+
raise ResourceNotExistsError()
|
|
103
|
+
return CustomORJSONResponse(users.user_model_to_user_with_creds(res))
|
|
104
|
+
|
|
105
|
+
|
|
94
106
|
@router.post("/refresh_token", response_model=UserWithCreds)
|
|
95
107
|
async def refresh_token(
|
|
96
108
|
body: RefreshTokenRequest,
|
|
@@ -317,7 +317,7 @@ async def get_plan(
|
|
|
317
317
|
spec=effective_run_spec,
|
|
318
318
|
)
|
|
319
319
|
effective_run_spec = RunSpec.parse_obj(effective_run_spec.dict())
|
|
320
|
-
_validate_run_spec_and_set_defaults(effective_run_spec)
|
|
320
|
+
_validate_run_spec_and_set_defaults(user, effective_run_spec)
|
|
321
321
|
|
|
322
322
|
profile = effective_run_spec.merged_profile
|
|
323
323
|
creation_policy = profile.creation_policy
|
|
@@ -422,7 +422,7 @@ async def apply_plan(
|
|
|
422
422
|
)
|
|
423
423
|
# Spec must be copied by parsing to calculate merged_profile
|
|
424
424
|
run_spec = RunSpec.parse_obj(run_spec.dict())
|
|
425
|
-
_validate_run_spec_and_set_defaults(run_spec)
|
|
425
|
+
_validate_run_spec_and_set_defaults(user, run_spec)
|
|
426
426
|
if run_spec.run_name is None:
|
|
427
427
|
return await submit_run(
|
|
428
428
|
session=session,
|
|
@@ -489,7 +489,7 @@ async def submit_run(
|
|
|
489
489
|
project: ProjectModel,
|
|
490
490
|
run_spec: RunSpec,
|
|
491
491
|
) -> Run:
|
|
492
|
-
_validate_run_spec_and_set_defaults(run_spec)
|
|
492
|
+
_validate_run_spec_and_set_defaults(user, run_spec)
|
|
493
493
|
repo = await _get_run_repo_or_error(
|
|
494
494
|
session=session,
|
|
495
495
|
project=project,
|
|
@@ -981,7 +981,7 @@ def _get_job_submission_cost(job_submission: JobSubmission) -> float:
|
|
|
981
981
|
return job_submission.job_provisioning_data.price * duration_hours
|
|
982
982
|
|
|
983
983
|
|
|
984
|
-
def _validate_run_spec_and_set_defaults(run_spec: RunSpec):
|
|
984
|
+
def _validate_run_spec_and_set_defaults(user: UserModel, run_spec: RunSpec):
|
|
985
985
|
# This function may set defaults for null run_spec values,
|
|
986
986
|
# although most defaults are resolved when building job_spec
|
|
987
987
|
# so that we can keep both the original user-supplied value (null in run_spec)
|
|
@@ -1031,6 +1031,11 @@ def _validate_run_spec_and_set_defaults(run_spec: RunSpec):
|
|
|
1031
1031
|
if run_spec.configuration.priority is None:
|
|
1032
1032
|
run_spec.configuration.priority = RUN_PRIORITY_DEFAULT
|
|
1033
1033
|
set_resources_defaults(run_spec.configuration.resources)
|
|
1034
|
+
if run_spec.ssh_key_pub is None:
|
|
1035
|
+
if user.ssh_public_key:
|
|
1036
|
+
run_spec.ssh_key_pub = user.ssh_public_key
|
|
1037
|
+
else:
|
|
1038
|
+
raise ServerClientError("ssh_key_pub must be set if the user has no ssh_public_key")
|
|
1034
1039
|
|
|
1035
1040
|
|
|
1036
1041
|
_UPDATABLE_SPEC_FIELDS = ["configuration_path", "configuration"]
|
|
@@ -12,6 +12,7 @@ from dstack._internal.core.errors import ResourceExistsError, ServerClientError
|
|
|
12
12
|
from dstack._internal.core.models.users import (
|
|
13
13
|
GlobalRole,
|
|
14
14
|
User,
|
|
15
|
+
UserHookConfig,
|
|
15
16
|
UserPermissions,
|
|
16
17
|
UserTokenCreds,
|
|
17
18
|
UserWithCreds,
|
|
@@ -19,6 +20,8 @@ from dstack._internal.core.models.users import (
|
|
|
19
20
|
from dstack._internal.server.models import DecryptedString, UserModel
|
|
20
21
|
from dstack._internal.server.services.permissions import get_default_permissions
|
|
21
22
|
from dstack._internal.server.utils.routers import error_forbidden
|
|
23
|
+
from dstack._internal.utils.common import run_async
|
|
24
|
+
from dstack._internal.utils.crypto import generate_rsa_key_pair_bytes
|
|
22
25
|
from dstack._internal.utils.logging import get_logger
|
|
23
26
|
|
|
24
27
|
logger = get_logger(__name__)
|
|
@@ -77,6 +80,7 @@ async def create_user(
|
|
|
77
80
|
email: Optional[str] = None,
|
|
78
81
|
active: bool = True,
|
|
79
82
|
token: Optional[str] = None,
|
|
83
|
+
config: Optional[UserHookConfig] = None,
|
|
80
84
|
) -> UserModel:
|
|
81
85
|
validate_username(username)
|
|
82
86
|
user_model = await get_user_model_by_name(session=session, username=username, ignore_case=True)
|
|
@@ -84,6 +88,7 @@ async def create_user(
|
|
|
84
88
|
raise ResourceExistsError()
|
|
85
89
|
if token is None:
|
|
86
90
|
token = str(uuid.uuid4())
|
|
91
|
+
private_bytes, public_bytes = await run_async(generate_rsa_key_pair_bytes, username)
|
|
87
92
|
user = UserModel(
|
|
88
93
|
id=uuid.uuid4(),
|
|
89
94
|
name=username,
|
|
@@ -92,11 +97,13 @@ async def create_user(
|
|
|
92
97
|
token_hash=get_token_hash(token),
|
|
93
98
|
email=email,
|
|
94
99
|
active=active,
|
|
100
|
+
ssh_private_key=private_bytes.decode(),
|
|
101
|
+
ssh_public_key=public_bytes.decode(),
|
|
95
102
|
)
|
|
96
103
|
session.add(user)
|
|
97
104
|
await session.commit()
|
|
98
105
|
for func in _CREATE_USER_HOOKS:
|
|
99
|
-
await func(session, user)
|
|
106
|
+
await func(session, user, config)
|
|
100
107
|
return user
|
|
101
108
|
|
|
102
109
|
|
|
@@ -120,6 +127,27 @@ async def update_user(
|
|
|
120
127
|
return await get_user_model_by_name_or_error(session=session, username=username)
|
|
121
128
|
|
|
122
129
|
|
|
130
|
+
async def refresh_ssh_key(
|
|
131
|
+
session: AsyncSession,
|
|
132
|
+
user: UserModel,
|
|
133
|
+
username: str,
|
|
134
|
+
) -> Optional[UserModel]:
|
|
135
|
+
logger.debug("Refreshing SSH key for user [code]%s[/code]", username)
|
|
136
|
+
if user.global_role != GlobalRole.ADMIN and user.name != username:
|
|
137
|
+
raise error_forbidden()
|
|
138
|
+
private_bytes, public_bytes = await run_async(generate_rsa_key_pair_bytes, username)
|
|
139
|
+
await session.execute(
|
|
140
|
+
update(UserModel)
|
|
141
|
+
.where(UserModel.name == username)
|
|
142
|
+
.values(
|
|
143
|
+
ssh_private_key=private_bytes.decode(),
|
|
144
|
+
ssh_public_key=public_bytes.decode(),
|
|
145
|
+
)
|
|
146
|
+
)
|
|
147
|
+
await session.commit()
|
|
148
|
+
return await get_user_model_by_name(session=session, username=username)
|
|
149
|
+
|
|
150
|
+
|
|
123
151
|
async def refresh_user_token(
|
|
124
152
|
session: AsyncSession,
|
|
125
153
|
user: UserModel,
|
|
@@ -199,6 +227,7 @@ def user_model_to_user(user_model: UserModel) -> User:
|
|
|
199
227
|
email=user_model.email,
|
|
200
228
|
active=user_model.active,
|
|
201
229
|
permissions=get_user_permissions(user_model),
|
|
230
|
+
ssh_public_key=user_model.ssh_public_key,
|
|
202
231
|
)
|
|
203
232
|
|
|
204
233
|
|
|
@@ -211,7 +240,9 @@ def user_model_to_user_with_creds(user_model: UserModel) -> UserWithCreds:
|
|
|
211
240
|
email=user_model.email,
|
|
212
241
|
active=user_model.active,
|
|
213
242
|
permissions=get_user_permissions(user_model),
|
|
243
|
+
ssh_public_key=user_model.ssh_public_key,
|
|
214
244
|
creds=UserTokenCreds(token=user_model.token.get_plaintext_or_error()),
|
|
245
|
+
ssh_private_key=user_model.ssh_private_key,
|
|
215
246
|
)
|
|
216
247
|
|
|
217
248
|
|
|
@@ -238,7 +269,9 @@ def is_valid_username(username: str) -> bool:
|
|
|
238
269
|
_CREATE_USER_HOOKS = []
|
|
239
270
|
|
|
240
271
|
|
|
241
|
-
def register_create_user_hook(
|
|
272
|
+
def register_create_user_hook(
|
|
273
|
+
func: Callable[[AsyncSession, UserModel, Optional[UserHookConfig]], Awaitable[None]],
|
|
274
|
+
):
|
|
242
275
|
_CREATE_USER_HOOKS.append(func)
|
|
243
276
|
|
|
244
277
|
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><title>dstack</title><meta name="description" content="Get GPUs at the best prices and availability from a wide range of providers. No cloud account of your own is required.
|
|
2
2
|
"/><link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel="stylesheet"><meta name="og:title" content="dstack"><meta name="og:type" content="article"><meta name="og:image" content="/splash_thumbnail.png"><meta name="og:description" content="Get GPUs at the best prices and availability from a wide range of providers. No cloud account of your own is required.
|
|
3
|
-
"><link rel="icon" type="image/x-icon" href="/assets/favicon.ico"><link rel="icon" type="image/png" sizes="16x16" href="/assets/favicon-16x16.png"><link rel="icon" type="image/png" sizes="32x32" href="/assets/favicon-32x32.png"><link rel="icon" type="image/png" sizes="48x48" href="/assets/favicon-48x48.png"><link rel="manifest" href="/assets/manifest.webmanifest"><meta name="mobile-web-app-capable" content="yes"><meta name="theme-color" content="#fff"><meta name="application-name" content="dstackai"><link rel="apple-touch-icon" sizes="57x57" href="/assets/apple-touch-icon-57x57.png"><link rel="apple-touch-icon" sizes="60x60" href="/assets/apple-touch-icon-60x60.png"><link rel="apple-touch-icon" sizes="72x72" href="/assets/apple-touch-icon-72x72.png"><link rel="apple-touch-icon" sizes="76x76" href="/assets/apple-touch-icon-76x76.png"><link rel="apple-touch-icon" sizes="114x114" href="/assets/apple-touch-icon-114x114.png"><link rel="apple-touch-icon" sizes="120x120" href="/assets/apple-touch-icon-120x120.png"><link rel="apple-touch-icon" sizes="144x144" href="/assets/apple-touch-icon-144x144.png"><link rel="apple-touch-icon" sizes="152x152" href="/assets/apple-touch-icon-152x152.png"><link rel="apple-touch-icon" sizes="167x167" href="/assets/apple-touch-icon-167x167.png"><link rel="apple-touch-icon" sizes="180x180" href="/assets/apple-touch-icon-180x180.png"><link rel="apple-touch-icon" sizes="1024x1024" href="/assets/apple-touch-icon-1024x1024.png"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"><meta name="apple-mobile-web-app-title" content="dstackai"><link rel="apple-touch-startup-image" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-640x1136.png"><link rel="apple-touch-startup-image" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-1136x640.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-750x1334.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-1334x750.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1125x2436.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2436x1125.png"><link rel="apple-touch-startup-image" media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1170x2532.png"><link rel="apple-touch-startup-image" media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2532x1170.png"><link rel="apple-touch-startup-image" media="(device-width: 393px) and (device-height: 852px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1179x2556.png"><link rel="apple-touch-startup-image" media="(device-width: 393px) and (device-height: 852px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2556x1179.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-828x1792.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-1792x828.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1242x2688.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2688x1242.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1242x2208.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2208x1242.png"><link rel="apple-touch-startup-image" media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1284x2778.png"><link rel="apple-touch-startup-image" media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2778x1284.png"><link rel="apple-touch-startup-image" media="(device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1290x2796.png"><link rel="apple-touch-startup-image" media="(device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2796x1290.png"><link rel="apple-touch-startup-image" media="(device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1488x2266.png"><link rel="apple-touch-startup-image" media="(device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2266x1488.png"><link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1536x2048.png"><link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2048x1536.png"><link rel="apple-touch-startup-image" media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1620x2160.png"><link rel="apple-touch-startup-image" media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2160x1620.png"><link rel="apple-touch-startup-image" media="(device-width: 820px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1640x2160.png"><link rel="apple-touch-startup-image" media="(device-width: 820px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2160x1640.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1668x2388.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2388x1668.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1668x2224.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2224x1668.png"><link rel="apple-touch-startup-image" media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-2048x2732.png"><link rel="apple-touch-startup-image" media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2732x2048.png"><meta name="msapplication-TileColor" content="#fff"><meta name="msapplication-TileImage" content="/assets/mstile-144x144.png"><meta name="msapplication-config" content="/assets/browserconfig.xml"><link rel="yandex-tableau-widget" href="/assets/yandex-browser-manifest.json"><script defer="defer" src="/main-c51afa7f243e24d3e446.js"></script><link href="/main-56191fbfe77f49b251de.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div class="b-page-header" id="header"></div><div id="root"></div><script async src="https://widget.kapa.ai/kapa-widget.bundle.js" data-website-id="11a9339d-20ce-4ddb-9ba3-1b6e29afe8eb" data-project-name="dstack" data-project-color="rgba(0, 0, 0, 0.87)" data-font-size-lg="0.78rem" data-button-hide="true" data-modal-image="/logo-notext.svg" data-modal-z-index="1100" data-modal-title="Ask me anything" data-font-family='metro-web, Metro, -apple-system, "system-ui", "Segoe UI", Roboto' data-project-logo="/assets/images/kapa.svg" data-modal-disclaimer="This is a custom LLM for dstack with access to Documentation, API references and GitHub issues. This feature is experimental - Give it a try!" data-user-analytics-fingerprint-enabled="true"></script></body></html>
|
|
3
|
+
"><link rel="icon" type="image/x-icon" href="/assets/favicon.ico"><link rel="icon" type="image/png" sizes="16x16" href="/assets/favicon-16x16.png"><link rel="icon" type="image/png" sizes="32x32" href="/assets/favicon-32x32.png"><link rel="icon" type="image/png" sizes="48x48" href="/assets/favicon-48x48.png"><link rel="manifest" href="/assets/manifest.webmanifest"><meta name="mobile-web-app-capable" content="yes"><meta name="theme-color" content="#fff"><meta name="application-name" content="dstackai"><link rel="apple-touch-icon" sizes="57x57" href="/assets/apple-touch-icon-57x57.png"><link rel="apple-touch-icon" sizes="60x60" href="/assets/apple-touch-icon-60x60.png"><link rel="apple-touch-icon" sizes="72x72" href="/assets/apple-touch-icon-72x72.png"><link rel="apple-touch-icon" sizes="76x76" href="/assets/apple-touch-icon-76x76.png"><link rel="apple-touch-icon" sizes="114x114" href="/assets/apple-touch-icon-114x114.png"><link rel="apple-touch-icon" sizes="120x120" href="/assets/apple-touch-icon-120x120.png"><link rel="apple-touch-icon" sizes="144x144" href="/assets/apple-touch-icon-144x144.png"><link rel="apple-touch-icon" sizes="152x152" href="/assets/apple-touch-icon-152x152.png"><link rel="apple-touch-icon" sizes="167x167" href="/assets/apple-touch-icon-167x167.png"><link rel="apple-touch-icon" sizes="180x180" href="/assets/apple-touch-icon-180x180.png"><link rel="apple-touch-icon" sizes="1024x1024" href="/assets/apple-touch-icon-1024x1024.png"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"><meta name="apple-mobile-web-app-title" content="dstackai"><link rel="apple-touch-startup-image" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-640x1136.png"><link rel="apple-touch-startup-image" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-1136x640.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-750x1334.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-1334x750.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1125x2436.png"><link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2436x1125.png"><link rel="apple-touch-startup-image" media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1170x2532.png"><link rel="apple-touch-startup-image" media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2532x1170.png"><link rel="apple-touch-startup-image" media="(device-width: 393px) and (device-height: 852px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1179x2556.png"><link rel="apple-touch-startup-image" media="(device-width: 393px) and (device-height: 852px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2556x1179.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-828x1792.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-1792x828.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1242x2688.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2688x1242.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1242x2208.png"><link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2208x1242.png"><link rel="apple-touch-startup-image" media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1284x2778.png"><link rel="apple-touch-startup-image" media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2778x1284.png"><link rel="apple-touch-startup-image" media="(device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1290x2796.png"><link rel="apple-touch-startup-image" media="(device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2796x1290.png"><link rel="apple-touch-startup-image" media="(device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1488x2266.png"><link rel="apple-touch-startup-image" media="(device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2266x1488.png"><link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1536x2048.png"><link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2048x1536.png"><link rel="apple-touch-startup-image" media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1620x2160.png"><link rel="apple-touch-startup-image" media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2160x1620.png"><link rel="apple-touch-startup-image" media="(device-width: 820px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1640x2160.png"><link rel="apple-touch-startup-image" media="(device-width: 820px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2160x1640.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1668x2388.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2388x1668.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-1668x2224.png"><link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2224x1668.png"><link rel="apple-touch-startup-image" media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/assets/apple-touch-startup-image-2048x2732.png"><link rel="apple-touch-startup-image" media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/assets/apple-touch-startup-image-2732x2048.png"><meta name="msapplication-TileColor" content="#fff"><meta name="msapplication-TileImage" content="/assets/mstile-144x144.png"><meta name="msapplication-config" content="/assets/browserconfig.xml"><link rel="yandex-tableau-widget" href="/assets/yandex-browser-manifest.json"><script defer="defer" src="/main-97c7e184573ca23f9fe4.js"></script><link href="/main-720ce3a11140daa480cc.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div class="b-page-header" id="header"></div><div id="root"></div><script async src="https://widget.kapa.ai/kapa-widget.bundle.js" data-website-id="11a9339d-20ce-4ddb-9ba3-1b6e29afe8eb" data-project-name="dstack" data-project-color="rgba(0, 0, 0, 0.87)" data-font-size-lg="0.78rem" data-button-hide="true" data-modal-image="/logo-notext.svg" data-modal-z-index="1100" data-modal-title="Ask me anything" data-font-family='metro-web, Metro, -apple-system, "system-ui", "Segoe UI", Roboto' data-project-logo="/assets/images/kapa.svg" data-modal-disclaimer="This is a custom LLM for dstack with access to Documentation, API references and GitHub issues. This feature is experimental - Give it a try!" data-user-analytics-fingerprint-enabled="true"></script></body></html>
|