From 45d5c2eaad7372871d2d22037314046cb2af1aa8 Mon Sep 17 00:00:00 2001 From: Emil Date: Sun, 2 Aug 2026 22:32:54 +0300 Subject: [PATCH] Update the add-worker command and expose the coordinator public URL in serve --- coordinator/cmd/coordinator/serve_cmd.go | 20 ++++++++++++------- .../transport/http/templates/add-worker.html | 10 +++++++--- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/coordinator/cmd/coordinator/serve_cmd.go b/coordinator/cmd/coordinator/serve_cmd.go index 5ab2466..fa58f6e 100644 --- a/coordinator/cmd/coordinator/serve_cmd.go +++ b/coordinator/cmd/coordinator/serve_cmd.go @@ -31,13 +31,14 @@ func runServe(args []string) error { flags.PrintDefaults() } var ( - dataDir = flags.String("data-dir", defaultDataDir(), "data directory (default: ~/.scimesh)") - addr = flags.String("addr", "127.0.0.1:8080", "listen address") - workers = flags.Int("workers", 1, "number of local worker agents to spawn") - open = flags.Bool("open", false, "open the UI in the browser") - docsDir = flags.String("docs-dir", "", "built MkDocs site directory to serve at /ui/docs/") - email = flags.String("admin-email", "admin@scimesh.local", "admin account email") - password = flags.String("admin-password", "", "admin password (generated on first run when empty)") + dataDir = flags.String("data-dir", defaultDataDir(), "data directory (default: ~/.scimesh)") + addr = flags.String("addr", "127.0.0.1:8080", "listen address") + workers = flags.Int("workers", 1, "number of local worker agents to spawn") + open = flags.Bool("open", false, "open the UI in the browser") + docsDir = flags.String("docs-dir", "", "built MkDocs site directory to serve at /ui/docs/") + email = flags.String("admin-email", "admin@scimesh.local", "admin account email") + password = flags.String("admin-password", "", "admin password (generated on first run when empty)") + publicURL = flags.String("public-url", "", "browser/worker-facing coordinator URL (default: http://)") ) if err := flags.Parse(args); err != nil { return err @@ -100,6 +101,10 @@ func runServe(args []string) error { defer stopAgents(agents) // 6. The coordinator server itself. + coordinatorPublicURL := *publicURL + if coordinatorPublicURL == "" { + coordinatorPublicURL = "http://" + *addr + } cfg := infra.Config{ Addr: *addr, DatabaseEngine: "sqlite", @@ -107,6 +112,7 @@ func runServe(args []string) error { Token: workerToken, JWTSecret: jwtSecret, UserserviceURL: "http://" + usersAddr, + PublicCoordinatorURL: coordinatorPublicURL, PublicUserserviceURL: "http://" + usersAddr, LogLevel: "info", StorageDir: filepath.Join(*dataDir, "artifacts"), diff --git a/coordinator/internal/transport/http/templates/add-worker.html b/coordinator/internal/transport/http/templates/add-worker.html index 486c098..9ec7be5 100644 --- a/coordinator/internal/transport/http/templates/add-worker.html +++ b/coordinator/internal/transport/http/templates/add-worker.html @@ -29,8 +29,12 @@

Set it up

  1. Create a key
    Use the form; copy the command it generates.
  2. -
  3. Paste it in a terminal
    The command clones the project, sets up a Python environment, installs the worker, and starts it. The machine then appears under My machines.
  4. +
  5. Paste it in a terminal
    The command installs the worker, points it at this coordinator, and starts it. The machine then appears under My machines.
+

Single-binary mode?

+

If the coordinator runs as coordinator serve, skip the key: + use the token in ~/.scimesh/worker.token on the coordinator + machine as WORKER_AUTH_TOKEN instead.

Will my results count?

Your worker is untrusted by default: its results are cross-checked and accepted once a second independent worker computes the same answer (quorum), or once an admin marks your account verified — then your workers are trusted and results count immediately.

similarity-search and other SDK workloads from the library run on volunteer workers.

@@ -43,8 +47,8 @@ const keysBox=document.querySelector('#keys'),cmdBox=document.querySelector('#command'),form=document.querySelector('#create'),nameInput=document.querySelector('#key-name'),createBtn=document.querySelector('#create-btn'),error=document.querySelector('#error'); const node=(tag,text,cls)=>{const n=document.createElement(tag);if(text!==undefined)n.textContent=text;if(cls)n.className=cls;return n}; const shq=s=>"'"+String(s).replace(/'/g,"'\\''")+"'"; - const buildCommand=(key,name)=>['git clone https://github.com/emil28092005/SciMesh.git','cd SciMesh','python -m venv .venv','source .venv/bin/activate','pip install -e .','','SCIMESH_COORDINATOR_URL='+coord+' \\','SCIMESH_USERSERVICE_URL='+(users||'')+' \\','SCIMESH_WORKER_KEY='+key+' \\','scimesh-worker --worker-name '+shq(name||'my-machine')].join('\n'); - const showCommand=(key,name)=>{cmdBox.replaceChildren();cmdBox.append(node('strong','Key created — copy it now, it is shown only once.'));const pre=node('pre',buildCommand(key,name));cmdBox.append(pre);const copy=node('button','Copy command','button secondary');copy.type='button';copy.addEventListener('click',()=>{navigator.clipboard&&navigator.clipboard.writeText(buildCommand(key,name)).then(()=>{copy.textContent='Copied ✓'},()=>{copy.textContent='Press Ctrl+C to copy'})});cmdBox.append(document.createElement('br'),copy);if(!users){cmdBox.append(node('p','Set SCIMESH_USERSERVICE_URL to a userservice URL your machine can reach.','warn'))}cmdBox.classList.remove('hidden')}; + const buildCommand=(key,name)=>['# install the worker binary (or download worker-agent from the release page)','curl -fsSL https://raw.githubusercontent.com/emil28092005/SciMesh/main/install.sh | bash -s worker','','export COORDINATOR_URL='+coord,'export USERSERVICE_URL='+(users||'')+' \\','export WORKER_KEY='+key,'export WORKER_NAME='+shq(name||'my-machine'),'export WORK_DIR=~/scimesh-worker','worker-agent'].join('\n'); + const showCommand=(key,name)=>{cmdBox.replaceChildren();cmdBox.append(node('strong','Key created — copy it now, it is shown only once.'));const pre=node('pre',buildCommand(key,name));cmdBox.append(pre);const copy=node('button','Copy command','button secondary');copy.type='button';copy.addEventListener('click',()=>{navigator.clipboard&&navigator.clipboard.writeText(buildCommand(key,name)).then(()=>{copy.textContent='Copied ✓'},()=>{copy.textContent='Press Ctrl+C to copy'})});cmdBox.append(document.createElement('br'),copy);if(!users){cmdBox.append(node('p','Set USERSERVICE_URL to a userservice URL your machine can reach, or skip it and run the worker with WORKER_AUTH_TOKEN instead of a key.','warn'))}cmdBox.classList.remove('hidden')}; const revoke=async id=>{const r=await fetch('/ui/api/worker-keys/'+encodeURIComponent(id)+'/revoke',{method:'POST'});if(r.status===204||r.ok){loadKeys()}else{error.textContent='Could not revoke the key.'}}; const renderKeys=keys=>{keysBox.replaceChildren();if(!keys.length){keysBox.append(node('div','No keys yet. Create one above to connect a machine.','empty'));return}for(const k of keys){const row=node('div',undefined,'key'),left=node('div');left.append(node('div',k.name||'unnamed','kn'),node('div',k.prefix+'…','kp'),node('div','Created '+new Date(k.created_at).toLocaleString()+(k.last_used_at?' · last used '+new Date(k.last_used_at).toLocaleString():' · never used'),'kd'));const btn=node('button','Revoke','revoke');btn.type='button';btn.addEventListener('click',()=>revoke(k.id));row.append(left,btn);keysBox.append(row)}}; const loadKeys=async()=>{try{const r=await fetch('/ui/api/worker-keys',{headers:{Accept:'application/json'}});if(!r.ok)throw Error();const data=await r.json();renderKeys(data.worker_keys||[])}catch(_){keysBox.replaceChildren(node('div','Could not load your keys.','empty'))}};