feat: self-service worker enrollment bound to a user account
Let a signed-in user turn their own machine into a worker without the shared token. The coordinator already binds a JWT-authenticated registration to owner_id as untrusted; this adds the missing pieces. userservice: long-lived worker keys (scimesh_wk_live_*, hash-at-rest) with create/list/revoke and a public /worker-tokens/exchange that trades a key for a short-lived JWT carrying the owner current role/verified. python worker: SCIMESH_WORKER_KEY + SCIMESH_USERSERVICE_URL; a token provider exchanges the key and refreshes the JWT proactively and on 401, so a long-running worker survives token expiry. Static bearer token path is unchanged. coordinator UI: an "add your machine" page that mints a key and shows a ready-to-run command, proxying key management to the userservice; the dashboard gains an owner-scoped "my machines" section. docs: how to run a worker from your account, plus the untrusted/quorum/ verified trust model.
This commit is contained in:
+26
-4
@@ -7,6 +7,7 @@ import logging
|
||||
from pathlib import Path
|
||||
|
||||
from .artifacts import HttpArtifactClient
|
||||
from .auth import provider_from_config
|
||||
from .config import WorkerConfig
|
||||
from .coordinator import HttpCoordinatorClient
|
||||
from .daemon import WorkerDaemon
|
||||
@@ -22,14 +23,23 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
"SCIMESH_WORKER_NAME, SCIMESH_CPU_COUNT, SCIMESH_MEMORY_MB, "
|
||||
"SCIMESH_POLL_INTERVAL, SCIMESH_REQUEST_TIMEOUT, "
|
||||
"SCIMESH_HEARTBEAT_INTERVAL, SCIMESH_CLEANUP_AFTER_SECONDS, "
|
||||
"SCIMESH_MAX_TASKS, and SCIMESH_BEARER_TOKEN. "
|
||||
"SCIMESH_WORKER_ID is a legacy/test override."
|
||||
"SCIMESH_MAX_TASKS, SCIMESH_BEARER_TOKEN, SCIMESH_WORKER_KEY, and "
|
||||
"SCIMESH_USERSERVICE_URL. SCIMESH_WORKER_ID is a legacy/test override."
|
||||
),
|
||||
)
|
||||
parser.add_argument("--coordinator-url")
|
||||
parser.add_argument("--worker-id")
|
||||
parser.add_argument("--work-dir")
|
||||
parser.add_argument("--worker-name")
|
||||
parser.add_argument(
|
||||
"--worker-key",
|
||||
help="Long-lived worker key from the web UI; the worker exchanges it for "
|
||||
"short-lived tokens, binding it to your account. Requires --userservice-url.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--userservice-url",
|
||||
help="Base URL of the userservice that issues tokens for --worker-key",
|
||||
)
|
||||
parser.add_argument("--cpu-count", type=int)
|
||||
parser.add_argument("--memory-mb", type=int)
|
||||
parser.add_argument("--poll-interval", type=float)
|
||||
@@ -68,11 +78,23 @@ def main(argv: list[str] | None = None) -> int:
|
||||
except (TypeError, ValueError) as error:
|
||||
parser.error(str(error))
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
|
||||
client = HttpCoordinatorClient(config.coordinator_url, config.request_timeout, config.bearer_token)
|
||||
# One shared token strategy backs both clients: a worker key (exchanged and
|
||||
# refreshed) or a static bearer token, decided by what the config carries.
|
||||
tokens = provider_from_config(
|
||||
worker_key=config.worker_key,
|
||||
userservice_url=config.userservice_url,
|
||||
bearer_token=config.bearer_token,
|
||||
request_timeout=config.request_timeout,
|
||||
)
|
||||
client = HttpCoordinatorClient(
|
||||
config.coordinator_url, config.request_timeout, token_provider=tokens
|
||||
)
|
||||
completed_without_interruption = WorkerDaemon(
|
||||
config,
|
||||
client,
|
||||
HttpArtifactClient(config.coordinator_url, config.request_timeout, config.bearer_token),
|
||||
HttpArtifactClient(
|
||||
config.coordinator_url, config.request_timeout, token_provider=tokens
|
||||
),
|
||||
SciMeshRunner(),
|
||||
).run_forever()
|
||||
return 0 if completed_without_interruption else 130
|
||||
|
||||
Reference in New Issue
Block a user