flowmesh-cli-stack 0.1.8rc1__tar.gz → 0.1.10__tar.gz

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.
Files changed (21) hide show
  1. {flowmesh_cli_stack-0.1.8rc1/src/flowmesh_cli_stack.egg-info → flowmesh_cli_stack-0.1.10}/PKG-INFO +3 -3
  2. {flowmesh_cli_stack-0.1.8rc1 → flowmesh_cli_stack-0.1.10}/pyproject.toml +3 -3
  3. {flowmesh_cli_stack-0.1.8rc1 → flowmesh_cli_stack-0.1.10}/src/flowmesh_cli_stack/assets/.env.example +28 -2
  4. {flowmesh_cli_stack-0.1.8rc1 → flowmesh_cli_stack-0.1.10}/src/flowmesh_cli_stack/env_schema.py +106 -2
  5. {flowmesh_cli_stack-0.1.8rc1 → flowmesh_cli_stack-0.1.10}/src/flowmesh_cli_stack/worker.py +21 -21
  6. {flowmesh_cli_stack-0.1.8rc1 → flowmesh_cli_stack-0.1.10/src/flowmesh_cli_stack.egg-info}/PKG-INFO +3 -3
  7. flowmesh_cli_stack-0.1.10/src/flowmesh_cli_stack.egg-info/requires.txt +5 -0
  8. flowmesh_cli_stack-0.1.8rc1/src/flowmesh_cli_stack.egg-info/requires.txt +0 -5
  9. {flowmesh_cli_stack-0.1.8rc1 → flowmesh_cli_stack-0.1.10}/LICENSE +0 -0
  10. {flowmesh_cli_stack-0.1.8rc1 → flowmesh_cli_stack-0.1.10}/README.md +0 -0
  11. {flowmesh_cli_stack-0.1.8rc1 → flowmesh_cli_stack-0.1.10}/setup.cfg +0 -0
  12. {flowmesh_cli_stack-0.1.8rc1 → flowmesh_cli_stack-0.1.10}/src/flowmesh_cli_stack/__init__.py +0 -0
  13. {flowmesh_cli_stack-0.1.8rc1 → flowmesh_cli_stack-0.1.10}/src/flowmesh_cli_stack/assets/compose.yml +0 -0
  14. {flowmesh_cli_stack-0.1.8rc1 → flowmesh_cli_stack-0.1.10}/src/flowmesh_cli_stack/assets/docker-bake.hcl +0 -0
  15. {flowmesh_cli_stack-0.1.8rc1 → flowmesh_cli_stack-0.1.10}/src/flowmesh_cli_stack/bundle.py +0 -0
  16. {flowmesh_cli_stack-0.1.8rc1 → flowmesh_cli_stack-0.1.10}/src/flowmesh_cli_stack/image.py +0 -0
  17. {flowmesh_cli_stack-0.1.8rc1 → flowmesh_cli_stack-0.1.10}/src/flowmesh_cli_stack/stack.py +0 -0
  18. {flowmesh_cli_stack-0.1.8rc1 → flowmesh_cli_stack-0.1.10}/src/flowmesh_cli_stack/utils.py +0 -0
  19. {flowmesh_cli_stack-0.1.8rc1 → flowmesh_cli_stack-0.1.10}/src/flowmesh_cli_stack.egg-info/SOURCES.txt +0 -0
  20. {flowmesh_cli_stack-0.1.8rc1 → flowmesh_cli_stack-0.1.10}/src/flowmesh_cli_stack.egg-info/dependency_links.txt +0 -0
  21. {flowmesh_cli_stack-0.1.8rc1 → flowmesh_cli_stack-0.1.10}/src/flowmesh_cli_stack.egg-info/top_level.txt +0 -0
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flowmesh-cli-stack
3
- Version: 0.1.8rc1
3
+ Version: 0.1.10
4
4
  Summary: FlowMesh CLI stack commands
5
5
  License-Expression: Apache-2.0
6
6
  Requires-Python: >=3.12
7
7
  Description-Content-Type: text/markdown
8
8
  License-File: LICENSE
9
- Requires-Dist: flowmesh-cli==0.1.8rc1
10
- Requires-Dist: flowmesh-sdk-stack==0.1.8rc1
9
+ Requires-Dist: flowmesh-cli==0.1.10
10
+ Requires-Dist: flowmesh-sdk-stack==0.1.10
11
11
  Requires-Dist: docker>=7.1.0
12
12
  Requires-Dist: packaging>=24
13
13
  Requires-Dist: pyyaml>=6.0.2
@@ -4,15 +4,15 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "flowmesh-cli-stack"
7
- version = "0.1.8rc1"
7
+ version = "0.1.10"
8
8
  description = "FlowMesh CLI stack commands"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
11
11
  license = "Apache-2.0"
12
12
  license-files = ["LICENSE"]
13
13
  dependencies = [
14
- "flowmesh-cli==0.1.8rc1",
15
- "flowmesh-sdk-stack==0.1.8rc1",
14
+ "flowmesh-cli==0.1.10",
15
+ "flowmesh-sdk-stack==0.1.10",
16
16
  "docker>=7.1.0",
17
17
  "packaging>=24",
18
18
  "pyyaml>=6.0.2",
@@ -43,6 +43,10 @@ SUPERVISOR_GRPC_KEEPALIVE_PERMIT_WITHOUT_CALLS=true
43
43
  SUPERVISOR_GRPC_MIN_RECV_PING_INTERVAL_MS=60000
44
44
  SUPERVISOR_GRPC_KEEPALIVE_TIME_MS=300000
45
45
  SUPERVISOR_GRPC_KEEPALIVE_TIMEOUT_MS=10000
46
+ # Shared secret admitting `external` workers (empty disables them).
47
+ EXTERNAL_WORKER_TOKEN=
48
+ # File holding the secret; wins over EXTERNAL_WORKER_TOKEN.
49
+ EXTERNAL_WORKER_TOKEN_FILE=
46
50
 
47
51
  # ==== Redis Connectivity ====
48
52
  REDIS_CONTROL_URL=redis://localhost:6379/0
@@ -85,7 +89,7 @@ SERVER_PORT_FORWARD_PORT_END=32100
85
89
 
86
90
  # ==== SSH Task Support ====
87
91
  ENABLE_SERVER_SSH_PROXY=true
88
- ENABLE_SERVER_SSH_CONNECTION_AUDIT=true
92
+ ENABLE_SERVER_SSH_CONNECTION_REGISTRY=true
89
93
 
90
94
  # ==== Serve Task Support ====
91
95
  ENABLE_SERVER_SERVE_PROXY=true
@@ -105,7 +109,19 @@ SSH_MAX_PIDS=
105
109
  # Whether to apply requested GPU limits to SSH tasks.
106
110
  # If false, SSH tasks are allocated all available GPUs
107
111
  # regardless of their resource requests.
108
- ENABLE_SSH_GPU_LIMIT=false
112
+ ENABLE_SSH_GPU_LIMIT=true
113
+ # Whether a non-root worker may serve SSH sessions,
114
+ # which run as the worker and can read its credentials.
115
+ ENABLE_UNISOLATED_SSH_SESSION=false
116
+ # Sandbox an SSH session runs in. 'docker' creates a
117
+ # sibling container; 'process' runs sshd inside the
118
+ # worker. 'auto' picks docker, falling back to process
119
+ # on a root worker.
120
+ SSH_SESSION_BACKEND=auto
121
+ # Address a direct-mode session is advertised at.
122
+ # Defaults to the worker's tailnet address, else its
123
+ # FQDN.
124
+ SSH_DIRECT_HOST=
109
125
 
110
126
  # ==== General Settings ====
111
127
  TZ=Asia/Singapore
@@ -128,6 +144,8 @@ ENABLE_WORKER_WATCHDOG=true
128
144
  WORKER_DEATH_CHECK_INTERVAL=30
129
145
  WORKER_DEATH_GRACE_SEC=60
130
146
  WORKER_REHYDRATION_GRACE_SEC=120
147
+ ENABLE_WORKER_REAPER=true
148
+ WORKER_REAP_GRACE_SEC=900
131
149
 
132
150
  # ==== Server Heartbeat ====
133
151
  SERVER_HEARTBEAT_INTERVAL=30
@@ -163,6 +181,14 @@ SERVER_CUDA_PROBE_IMAGE=nvidia/cuda:12.9.1-base-ubuntu24.04
163
181
  # Optional Docker runtime name for GPU containers.
164
182
  DOCKER_GPU_RUNTIME=nvidia
165
183
  CUDA_VISIBLE_DEVICES=all
184
+ # Report a GPU as unavailable while a process outside FlowMesh uses it.
185
+ WORKER_FOREIGN_GPU_GATE=true
186
+ # Foreign GPU-memory threshold in MiB.
187
+ WORKER_FOREIGN_GPU_MEM_MIB=1024
188
+ # Consecutive occupancy readings required before a device changes availability.
189
+ WORKER_FOREIGN_GPU_CONSECUTIVE=2
190
+ # Delay occupancy checks after a worker task completes.
191
+ WORKER_FOREIGN_GPU_GRACE_SEC=90
166
192
  WORKER_UPLOAD_RESULTS=false
167
193
  WORKER_EXECUTOR_IDLE_CLEANUP_SEC=60
168
194
  MODEL_CLEANUP_AFTER_UPLOAD=0
@@ -1,6 +1,7 @@
1
1
  """Stack env schema."""
2
2
 
3
3
  from flowmesh.models.nodes import NodeRole
4
+ from flowmesh_stack.env import parse_bool
4
5
  from flowmesh_stack.env_schema import (
5
6
  EnvSchema,
6
7
  EnvSection,
@@ -10,6 +11,24 @@ from flowmesh_stack.env_schema import (
10
11
  require_if_true,
11
12
  )
12
13
 
14
+
15
+ def _warn_reaper_without_watchdog(
16
+ env: dict[str, str], errors: list[str], warnings: list[str]
17
+ ) -> None:
18
+ """Warn when the stale-worker reaper is enabled while the watchdog is off.
19
+
20
+ The reaper runs on the watchdog thread, so it never fires without it. An
21
+ unset or unparsable ENABLE_WORKER_WATCHDOG keeps its own default and is
22
+ left alone, so only an explicit false value warns.
23
+ """
24
+ if parse_bool(env.get("ENABLE_WORKER_REAPER", "")) and (
25
+ parse_bool(env.get("ENABLE_WORKER_WATCHDOG", "")) is False
26
+ ):
27
+ warnings.append(
28
+ "ENABLE_WORKER_REAPER has no effect while ENABLE_WORKER_WATCHDOG is false"
29
+ )
30
+
31
+
13
32
  STACK_ENV_SCHEMA = EnvSchema(
14
33
  name="stack",
15
34
  header=[
@@ -147,6 +166,17 @@ STACK_ENV_SCHEMA = EnvSchema(
147
166
  var_type=EnvVarType.INT,
148
167
  min_value=0,
149
168
  ),
169
+ EnvVar(
170
+ "EXTERNAL_WORKER_TOKEN",
171
+ description="Shared secret admitting `external` workers "
172
+ "(empty disables them).",
173
+ ),
174
+ EnvVar(
175
+ "EXTERNAL_WORKER_TOKEN_FILE",
176
+ description="File holding the secret; wins over "
177
+ "EXTERNAL_WORKER_TOKEN.",
178
+ var_type=EnvVarType.FILE_PATH,
179
+ ),
150
180
  ],
151
181
  ),
152
182
  EnvSection(
@@ -292,7 +322,7 @@ STACK_ENV_SCHEMA = EnvSchema(
292
322
  vars=[
293
323
  EnvVar("ENABLE_SERVER_SSH_PROXY", "true", var_type=EnvVarType.BOOL),
294
324
  EnvVar(
295
- "ENABLE_SERVER_SSH_CONNECTION_AUDIT",
325
+ "ENABLE_SERVER_SSH_CONNECTION_REGISTRY",
296
326
  "true",
297
327
  var_type=EnvVarType.BOOL,
298
328
  ),
@@ -325,7 +355,7 @@ STACK_ENV_SCHEMA = EnvSchema(
325
355
  EnvVar("SSH_MAX_PIDS", var_type=EnvVarType.INT, min_value=1),
326
356
  EnvVar(
327
357
  "ENABLE_SSH_GPU_LIMIT",
328
- "false",
358
+ "true",
329
359
  var_type=EnvVarType.BOOL,
330
360
  description=[
331
361
  "Whether to apply requested GPU limits to SSH tasks.",
@@ -333,6 +363,35 @@ STACK_ENV_SCHEMA = EnvSchema(
333
363
  "regardless of their resource requests.",
334
364
  ],
335
365
  ),
366
+ EnvVar(
367
+ "ENABLE_UNISOLATED_SSH_SESSION",
368
+ "false",
369
+ var_type=EnvVarType.BOOL,
370
+ description=[
371
+ "Whether a non-root worker may serve SSH sessions,",
372
+ "which run as the worker and can read its credentials.",
373
+ ],
374
+ ),
375
+ EnvVar(
376
+ "SSH_SESSION_BACKEND",
377
+ "auto",
378
+ var_type=EnvVarType.ENUM,
379
+ choices={"auto", "docker", "process"},
380
+ description=[
381
+ "Sandbox an SSH session runs in. 'docker' creates a",
382
+ "sibling container; 'process' runs sshd inside the",
383
+ "worker. 'auto' picks docker, falling back to process",
384
+ "on a root worker.",
385
+ ],
386
+ ),
387
+ EnvVar(
388
+ "SSH_DIRECT_HOST",
389
+ description=[
390
+ "Address a direct-mode session is advertised at.",
391
+ "Defaults to the worker's tailnet address, else its",
392
+ "FQDN.",
393
+ ],
394
+ ),
336
395
  ],
337
396
  ),
338
397
  EnvSection(
@@ -427,6 +486,17 @@ STACK_ENV_SCHEMA = EnvSchema(
427
486
  var_type=EnvVarType.INT,
428
487
  min_value=0,
429
488
  ),
489
+ EnvVar(
490
+ "ENABLE_WORKER_REAPER",
491
+ "true",
492
+ var_type=EnvVarType.BOOL,
493
+ ),
494
+ EnvVar(
495
+ "WORKER_REAP_GRACE_SEC",
496
+ "900",
497
+ var_type=EnvVarType.INT,
498
+ min_value=0,
499
+ ),
430
500
  ],
431
501
  ),
432
502
  EnvSection(
@@ -523,6 +593,39 @@ STACK_ENV_SCHEMA = EnvSchema(
523
593
  EnvVar(
524
594
  "CUDA_VISIBLE_DEVICES", "all", var_type=EnvVarType.CSV_INTS_OR_ALL
525
595
  ),
596
+ EnvVar(
597
+ "WORKER_FOREIGN_GPU_GATE",
598
+ "true",
599
+ description=[
600
+ "Report a GPU as unavailable while a process outside "
601
+ "FlowMesh uses it.",
602
+ ],
603
+ var_type=EnvVarType.BOOL,
604
+ ),
605
+ EnvVar(
606
+ "WORKER_FOREIGN_GPU_MEM_MIB",
607
+ "1024",
608
+ description="Foreign GPU-memory threshold in MiB.",
609
+ var_type=EnvVarType.INT,
610
+ min_value=1,
611
+ ),
612
+ EnvVar(
613
+ "WORKER_FOREIGN_GPU_CONSECUTIVE",
614
+ "2",
615
+ description=[
616
+ "Consecutive occupancy readings required before a device "
617
+ "changes availability.",
618
+ ],
619
+ var_type=EnvVarType.INT,
620
+ min_value=1,
621
+ ),
622
+ EnvVar(
623
+ "WORKER_FOREIGN_GPU_GRACE_SEC",
624
+ "90",
625
+ description="Delay occupancy checks after a worker task completes.",
626
+ var_type=EnvVarType.FLOAT,
627
+ min_value=0,
628
+ ),
526
629
  EnvVar("WORKER_UPLOAD_RESULTS", "false", var_type=EnvVarType.BOOL),
527
630
  EnvVar(
528
631
  "WORKER_EXECUTOR_IDLE_CLEANUP_SEC",
@@ -693,6 +796,7 @@ STACK_ENV_SCHEMA = EnvSchema(
693
796
  ],
694
797
  errors,
695
798
  ),
799
+ _warn_reaper_without_watchdog,
696
800
  ],
697
801
  )
698
802
 
@@ -80,11 +80,11 @@ def worker_up(
80
80
  "Repeat --config-raw to provide multiple configs."
81
81
  ),
82
82
  ),
83
- name_template: str | None = typer.Option(
83
+ alias_template: str | None = typer.Option(
84
84
  None,
85
- "--name-template",
85
+ "--alias-template",
86
86
  help=(
87
- "Worker name template for cpu/gpu presets. Placeholders: "
87
+ "Worker alias template for cpu/gpu presets. Placeholders: "
88
88
  "{slug}, {kind}, {idx}, {gpu}. "
89
89
  "Default: '{slug}_worker_{kind}_{idx|gpu}'."
90
90
  ),
@@ -113,21 +113,21 @@ def worker_up(
113
113
  targets=targets,
114
114
  config_paths=config,
115
115
  config_raw=config_raw,
116
- name_template=name_template,
116
+ alias_template=alias_template,
117
117
  slug=slug,
118
118
  )
119
119
  except FlowMeshError as exc:
120
120
  logging.error(str(exc))
121
121
  raise typer.Exit(code=1)
122
122
  for label, worker_info in created:
123
- worker_name = worker_info.get("name", "<unknown>")
124
- logging.success(f"Created {label} '{worker_name}':")
123
+ worker_alias = worker_info.get("alias", "<unknown>")
124
+ logging.success(f"Created {label} '{worker_alias}':")
125
125
  logging.log(json.dumps(worker_info, indent=2))
126
126
 
127
127
 
128
128
  @app.command("start")
129
129
  def worker_start(
130
- names: list[str] = typer.Argument(..., help="Worker name(s) or 'all'"),
130
+ aliases: list[str] = typer.Argument(..., help="Worker alias(es) or 'all'"),
131
131
  env_file: Path = typer.Option(
132
132
  DEFAULT_ENV_FILE, "--env-file", help="Env file to load defaults"
133
133
  ),
@@ -139,20 +139,20 @@ def worker_start(
139
139
  """Start a stopped worker container."""
140
140
  client = stack_node_client(env_file, base_url, token or None)
141
141
  try:
142
- started = operate_workers(client, names, operation="start")
142
+ started = operate_workers(client, aliases, operation="start")
143
143
  except FlowMeshError as exc:
144
144
  logging.error(str(exc))
145
145
  raise typer.Exit(code=1)
146
146
  if not started:
147
147
  logging.warning("No workers found.")
148
148
  return
149
- for name in started:
150
- logging.success(f"Started worker {name}")
149
+ for alias in started:
150
+ logging.success(f"Started worker {alias}")
151
151
 
152
152
 
153
153
  @app.command("stop")
154
154
  def worker_stop(
155
- names: list[str] = typer.Argument(..., help="Worker name(s) or 'all'"),
155
+ aliases: list[str] = typer.Argument(..., help="Worker alias(es) or 'all'"),
156
156
  env_file: Path = typer.Option(
157
157
  DEFAULT_ENV_FILE, "--env-file", help="Env file to load defaults"
158
158
  ),
@@ -164,20 +164,20 @@ def worker_stop(
164
164
  """Stop a running worker container without removing it."""
165
165
  client = stack_node_client(env_file, base_url, token or None)
166
166
  try:
167
- stopped = operate_workers(client, names, operation="stop")
167
+ stopped = operate_workers(client, aliases, operation="stop")
168
168
  except FlowMeshError as exc:
169
169
  logging.error(str(exc))
170
170
  raise typer.Exit(code=1)
171
171
  if not stopped:
172
172
  logging.warning("No workers found.")
173
173
  return
174
- for name in stopped:
175
- logging.success(f"Stopped worker {name}")
174
+ for alias in stopped:
175
+ logging.success(f"Stopped worker {alias}")
176
176
 
177
177
 
178
178
  @app.command("down")
179
179
  def worker_down(
180
- names: list[str] = typer.Argument(..., help="Worker name(s) or 'all'"),
180
+ aliases: list[str] = typer.Argument(..., help="Worker alias(es) or 'all'"),
181
181
  env_file: Path = typer.Option(
182
182
  DEFAULT_ENV_FILE, "--env-file", help="Env file to load defaults"
183
183
  ),
@@ -188,9 +188,9 @@ def worker_down(
188
188
  ) -> None:
189
189
  """Destroy a worker or all workers, removing containers and associated resources."""
190
190
  client = stack_node_client(env_file, base_url, token or None)
191
- if "all" in names:
192
- if len(names) != 1:
193
- logging.error("Use either 'all' or worker names, not both.")
191
+ if "all" in aliases:
192
+ if len(aliases) != 1:
193
+ logging.error("Use either 'all' or worker aliases, not both.")
194
194
  raise typer.Exit(code=1)
195
195
  try:
196
196
  logging.info("Destroying all workers...")
@@ -202,15 +202,15 @@ def worker_down(
202
202
  return
203
203
 
204
204
  try:
205
- destroyed = operate_workers(client, names, operation="destroy")
205
+ destroyed = operate_workers(client, aliases, operation="destroy")
206
206
  except FlowMeshError as exc:
207
207
  logging.error(str(exc))
208
208
  raise typer.Exit(code=1)
209
209
  if not destroyed:
210
210
  logging.warning("No workers found.")
211
211
  return
212
- for name in destroyed:
213
- logging.success(f"Destroyed worker {name}")
212
+ for alias in destroyed:
213
+ logging.success(f"Destroyed worker {alias}")
214
214
 
215
215
 
216
216
  @app.command("pull")
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flowmesh-cli-stack
3
- Version: 0.1.8rc1
3
+ Version: 0.1.10
4
4
  Summary: FlowMesh CLI stack commands
5
5
  License-Expression: Apache-2.0
6
6
  Requires-Python: >=3.12
7
7
  Description-Content-Type: text/markdown
8
8
  License-File: LICENSE
9
- Requires-Dist: flowmesh-cli==0.1.8rc1
10
- Requires-Dist: flowmesh-sdk-stack==0.1.8rc1
9
+ Requires-Dist: flowmesh-cli==0.1.10
10
+ Requires-Dist: flowmesh-sdk-stack==0.1.10
11
11
  Requires-Dist: docker>=7.1.0
12
12
  Requires-Dist: packaging>=24
13
13
  Requires-Dist: pyyaml>=6.0.2
@@ -0,0 +1,5 @@
1
+ flowmesh-cli==0.1.10
2
+ flowmesh-sdk-stack==0.1.10
3
+ docker>=7.1.0
4
+ packaging>=24
5
+ pyyaml>=6.0.2
@@ -1,5 +0,0 @@
1
- flowmesh-cli==0.1.8rc1
2
- flowmesh-sdk-stack==0.1.8rc1
3
- docker>=7.1.0
4
- packaging>=24
5
- pyyaml>=6.0.2