Small operational scripts and scheduled chores for Offworld Labs.
Where this runs. Everything in this repository currently runs on a VPS controlled by Jonny Spicer — it is not managed infrastructure, and there is no deployment pipeline. Scripts are checked out at
/opt/apps/opson that box and scheduled from root's crontab. If Jonny's VPS is unavailable, these jobs do not run and nothing will alert you. Treat anything here as best-effort convenience automation rather than something the team can rely on.
| Script | What it does | Schedule |
|---|---|---|
weekly-checkin/ |
Posts a fixed weekly check-in prompt to the "Offworld Labs" ClickUp chat channel | 09:00 Europe/London, Mondays |
check-dead-code.sh |
Dead-code gate (vulture) consumed by the Python repos as a pre-commit hook | On every commit / CI run in consumer repos |
ruff-shared.toml |
Canonical ruff configuration, enforced in consumer repos by the ruff-config hook |
On every commit / CI run in consumer repos |
sdrplay-mirror/ |
Uploads the SDRplay RSP API installer and SDRconnect into an R2 bucket owl-os's OS image build pulls them from, since sdrplay.com's own downloads became unreliable | Manual only — re-run by hand if SDRplay's file changes again |
This repository is also a pre-commit hook repository. Consumers reference it by pinned tag, so there is exactly one copy of each hook and vendored drift cannot happen:
repos:
- repo: https://github.com/offworldlabs/ops
rev: hooks-v1.0
hooks:
- id: dead-code # requires vulture==2.14 on PATH
- id: ruff-config
If a consumer's Python lives in a subdirectory, add args: [<dir>] to both
hooks — retina-server, whose backend is not at repo root, needs exactly that.
| Hook | Script | Requires |
|---|---|---|
dead-code |
check-dead-code.sh |
vulture==2.14 on PATH |
ruff-config |
check-ruff-config.py |
Python 3.11+ (tomllib) |
Hook versions are published as repo-level tags named hooks-v<MAJOR>.<MINOR>.
The dot is required, not cosmetic. pre-commit warns "appears to be a mutable
reference" for any rev containing neither a . nor pure hex
(clientlib.py, WarnMutableRev), so hooks-v1 would make every consumer
print a spurious warning on every run.
Tags are repo-level rather than per-hook because pre-commit pins the whole
repository at one rev — two hooks cannot be versioned independently from a
single repo. The older dead-code-v1.0 and dead-code-v1.1 tags remain valid
for consumers that have not moved.
A git tag is mutable by default: git push --force moves it, and consumers
that pinned it fetch whatever it points at now. CI runners are always
cache-cold, so a moved tag would reach every consumer on its next run with no
diff in any consuming repo to show for it. Nine repos pin hooks-v1.0.
The Immutable hook version tags ruleset closes that off at source. It targets
refs/tags/hooks-v* and refs/tags/dead-code-v*, and blocks force-pushes and
deletions with no bypass actors — not even admins, deliberately. Creating a
new tag is unaffected, so cutting hooks-v1.1 works exactly as before.
The trade is that a tag pushed to the wrong commit cannot be quietly corrected.
Cut a new tag instead; that is the honest fix anyway, since anyone who already
consumed the bad one would never see a correction. If a tag genuinely must be
removed, add it to the ruleset's exclude list, delete it, and take it back
out — which leaves an audit trail, unlike a force-push.
Consumers therefore do not need to pin by commit SHA. That was considered and
rejected: pre-commit autoupdate silently rewrites SHA pins back to tags, so
the protection would evaporate the first time anyone ran the documented bump
command. See ClickUp 86cb4jjbv for the full decision.
Edit it here, run both suites in tests/, merge, tag, then bump rev in the
consumers:
pre-commit autoupdate --repo https://github.com/offworldlabs/ops
Use --repo. A bare pre-commit autoupdate updates every repo in the
consumer's config. In the six consumers that also run ruff through pre-commit
(everything except node-infra, retina-gui and retina-node, which pip-install
ruff in CI instead), that silently bumps astral-sh/ruff-pre-commit as a side
effect of bumping the ops hook. Those repos' CI runs pre-commit run --all-files, so the new ruff takes effect in CI on the same commit — the exact
"an unpinned linter picks up new rules on release and turns a green branch red
without anything in this repo changing" outcome their neighbouring
# Pinned deliberately comments exist to prevent. Bump ruff on purpose, in its
own commit, or not at all.
The "Adding a script" conventions below are about scheduled chores on the VPS and do not apply to hooks — a hook takes no env config and nothing schedules it. Convention 4, fail loudly, very much does apply: the dead-code gate exits 127 when vulture is missing rather than reporting a scan that never happened.
This repository is public, so every script must follow these rules. They are what make it safe to keep adding to.
- No site-specific values in committed files. Workspace IDs, channel IDs,
hostnames, paths, and messages live in a
*.envconfig file that stays on the host. Commit a*.env.examplewith placeholders instead. - Secrets are referenced by path, never embedded. A script reads its
credential from a file outside the repository (mode
600), and that path is itself configuration. No token, key, or password is ever committed — not even in an example file. - Runtime output stays out of the repository. Logs, findings, caches, and scratch files are written outside the checkout (or to a gitignored directory). This is the rule most easily broken by accident, because once the checkout is the working directory it feels natural to write a log next to the script.
- Fail loudly. Cron on the host has no MTA, so unredirected stderr is
discarded. Scripts exit non-zero on failure and report to stderr, the system
log (
logger), and their own log file. - Config files are not secrets either. Keeping credentials in a separate file from the site config means an accidentally-shared config costs nothing.
A secret-scanning workflow runs on every push and pull request to enforce rule 2 mechanically.
your-script/
your-script.sh # reads config from an env file; no hardcoded values
your-script.env.example
README.md # what it does, how to configure, how it is scheduled
Then add it to the table above and wire up the schedule on the host.