A polite, daemon-less batch distributor for opportunistic SSH targets.
Project description
yuj
yuj scatters work across idle lab desktops over SSH and pulls the results back. A self-healing watchdog on each host keeps the batch running through crashes and reboots, so you don't need root on the machines or a scheduler watching over them.
If you have a 'batch of jobs' that can be split into independent items, you can use yuj to achieve effective and fast parallelisation. Each item runs on one host on its own, with no communication between items and no shared memory.
yuj will not work if your job needs nodes to talk to each other mid-run (MPI, a shared address space).
Quickstart
# Python job
pipx install yuj
mkdir my-job && cd my-job
yuj init --template python # scaffold worker.py + config
$EDITOR fleet.csv # add hosts (username, IP, password or key)
yuj bootstrap # install Python (uv) on each host, no root
yuj deploy && yuj submit # push code, start watchdog + cron
yuj status --watch 30 # live dashboard
yuj pull --loop 60
yuj decommission lab-desk-1 --at "9am tomorrow"
# R job
pipx install yuj
mkdir my-r-job && cd my-r-job
yuj init --template r # scaffold worker.R + environment.yaml
$EDITOR fleet.csv
yuj bootstrap # install R via micromamba, no root or compiler
yuj deploy && yuj submit
yuj status --watch 30
yuj pull --loop 60
See the full quickstart and R users guide in the docs.
How it works
- provision (optional): if you only have an admin/sudo login,
yuj provisioncreates a dedicated worker user with key-only auth and writes aprovisioned-fleet.csvyou can hand to the rest of the commands. This is the one step that needs privilege. - deploy: rsync your code + data to
$HOMEon each host. Aborts up front if a configured path is missing locally. - submit: dry-run the work command on one host first (a canary), so a broken job aborts before it installs a watchdog everywhere. Then install a watchdog and a cron entry that restarts it every 15 min. The watchdog runs your work loop, detects stalls (no new output in N min), and relaunches.
- resume: the work loop skips any item whose output file already exists. This is the only reliable checkpoint when a worker crashes mid-write.
- pull: parallel rsync of outputs back to a central directory, tolerant of hosts being down.
- survive: supervision lives in cron on each host, so a rebooted host resumes within 15 min with no controller involved.
Caveats
- Passwords work but trigger fail2ban on repeated auth failures. Pass
key_pathinfleet.csvto avoid this. - A few failed auths can get your IP banned for hours.
yuj diagnoseclassifies what's wrong (net down / sshd down / banner drop / auth refused); it won't hammer a banned host. - Passwords are stored as plain text in fleet.csv. The
.gitignoreexcludes it by default, but double-check before pushing. yuj statusshows a warning when someone is at the console. Useyuj decommission --at "9am tomorrow"to hand machines back at a civilised time.
Development
uv sync --locked # create the venv and install dev deps
uv run pytest # tests
uv run ruff check . # lint
uv run ruff format . # format
uv run mypy src # strict type-check
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file yuj-0.1.0.tar.gz.
File metadata
- Download URL: yuj-0.1.0.tar.gz
- Upload date:
- Size: 205.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
baa2dcd500c0d24f4e8a2903a3121380f8c8d5a7f627703474dacab58f7899be
|
|
| MD5 |
851e2e7aac4a0689706d146d48742b03
|
|
| BLAKE2b-256 |
17d7a35989e50fd0775b7648ea5dc7301433bfcc30fcea602afa70aa7f112371
|
Provenance
The following attestation bundles were made for yuj-0.1.0.tar.gz:
Publisher:
release.yml on saketlab/yuj
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
yuj-0.1.0.tar.gz -
Subject digest:
baa2dcd500c0d24f4e8a2903a3121380f8c8d5a7f627703474dacab58f7899be - Sigstore transparency entry: 2189270460
- Sigstore integration time:
-
Permalink:
saketlab/yuj@104f2450b115a2b4a7dd946daa4deeb12dff27c4 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/saketlab
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@104f2450b115a2b4a7dd946daa4deeb12dff27c4 -
Trigger Event:
push
-
Statement type:
File details
Details for the file yuj-0.1.0-py3-none-any.whl.
File metadata
- Download URL: yuj-0.1.0-py3-none-any.whl
- Upload date:
- Size: 95.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
90a45e977ac67f969634d7d1b124fbaf4486af41cc7ca7d98f8f8acac20dea42
|
|
| MD5 |
0094cc642215317b6a121973fff202d6
|
|
| BLAKE2b-256 |
351b5ef91ebcab41aa88e9f946f074247bf90f5be9273509c37f7a0ccbbbb962
|
Provenance
The following attestation bundles were made for yuj-0.1.0-py3-none-any.whl:
Publisher:
release.yml on saketlab/yuj
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
yuj-0.1.0-py3-none-any.whl -
Subject digest:
90a45e977ac67f969634d7d1b124fbaf4486af41cc7ca7d98f8f8acac20dea42 - Sigstore transparency entry: 2189270528
- Sigstore integration time:
-
Permalink:
saketlab/yuj@104f2450b115a2b4a7dd946daa4deeb12dff27c4 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/saketlab
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@104f2450b115a2b4a7dd946daa4deeb12dff27c4 -
Trigger Event:
push
-
Statement type: