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.

Files changed (46) hide show
  1. dstack/_internal/cli/commands/offer.py +1 -1
  2. dstack/_internal/cli/services/configurators/run.py +1 -5
  3. dstack/_internal/core/backends/aws/compute.py +8 -5
  4. dstack/_internal/core/backends/azure/compute.py +9 -6
  5. dstack/_internal/core/backends/base/compute.py +40 -17
  6. dstack/_internal/core/backends/base/offers.py +5 -1
  7. dstack/_internal/core/backends/datacrunch/compute.py +9 -6
  8. dstack/_internal/core/backends/gcp/compute.py +137 -7
  9. dstack/_internal/core/backends/gcp/models.py +7 -0
  10. dstack/_internal/core/backends/gcp/resources.py +87 -5
  11. dstack/_internal/core/backends/hotaisle/compute.py +11 -1
  12. dstack/_internal/core/backends/kubernetes/compute.py +161 -83
  13. dstack/_internal/core/backends/kubernetes/models.py +4 -2
  14. dstack/_internal/core/backends/nebius/compute.py +9 -6
  15. dstack/_internal/core/backends/oci/compute.py +9 -6
  16. dstack/_internal/core/backends/runpod/compute.py +10 -6
  17. dstack/_internal/core/backends/vastai/compute.py +3 -1
  18. dstack/_internal/core/backends/vastai/configurator.py +0 -1
  19. dstack/_internal/core/models/fleets.py +1 -1
  20. dstack/_internal/core/models/profiles.py +1 -1
  21. dstack/_internal/core/models/runs.py +3 -2
  22. dstack/_internal/core/models/users.py +10 -0
  23. dstack/_internal/core/services/configs/__init__.py +1 -0
  24. dstack/_internal/server/background/tasks/process_instances.py +5 -1
  25. dstack/_internal/server/background/tasks/process_running_jobs.py +1 -0
  26. dstack/_internal/server/migrations/versions/ff1d94f65b08_user_ssh_key.py +34 -0
  27. dstack/_internal/server/models.py +3 -0
  28. dstack/_internal/server/routers/runs.py +5 -1
  29. dstack/_internal/server/routers/users.py +14 -2
  30. dstack/_internal/server/services/runs.py +9 -4
  31. dstack/_internal/server/services/users.py +35 -2
  32. dstack/_internal/server/statics/index.html +1 -1
  33. dstack/_internal/server/statics/main-720ce3a11140daa480cc.css +3 -0
  34. dstack/_internal/server/statics/{main-c51afa7f243e24d3e446.js → main-97c7e184573ca23f9fe4.js} +12218 -7625
  35. dstack/_internal/server/statics/{main-c51afa7f243e24d3e446.js.map → main-97c7e184573ca23f9fe4.js.map} +1 -1
  36. dstack/api/_public/__init__.py +9 -12
  37. dstack/api/_public/repos.py +0 -21
  38. dstack/api/_public/runs.py +64 -9
  39. dstack/api/server/_users.py +17 -2
  40. dstack/version.py +2 -2
  41. {dstack-0.19.32.dist-info → dstack-0.19.33.dist-info}/METADATA +2 -2
  42. {dstack-0.19.32.dist-info → dstack-0.19.33.dist-info}/RECORD +45 -44
  43. dstack/_internal/server/statics/main-56191fbfe77f49b251de.css +0 -3
  44. {dstack-0.19.32.dist-info → dstack-0.19.33.dist-info}/WHEEL +0 -0
  45. {dstack-0.19.32.dist-info → dstack-0.19.33.dist-info}/entry_points.txt +0 -0
  46. {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=User)
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.user_model_to_user(user))
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(func: Callable[[AsyncSession, UserModel], Awaitable[None]]):
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>