Bash
There are 5 .sh
files in a 217,266-line repository. That is the
finding. Shell is not written here — it is generated from typed Python, and the pattern
is original enough to be the most interesting infrastructure convention in the estate.
1. Where shell actually lives
| Location | What |
|---|---|
sg_compute/platforms/ec2/user_data/ | 15 Section__* classes that render shell fragments. Nearly all shell in the estate lives here |
docker/ | Dockerfiles and their RUN lines |
.github/workflows/ | Inline run: blocks |
scripts/ | A handful of .sh files and Python CLI shims |
2. The Section__* pattern
sg_compute/platforms/ec2/user_data/Section__Shutdown.pyverbatim and entire · 04__bash-and-generated-shell.md# ═══════════════════════════════════════════════════════════════════════════════
# Ephemeral EC2 — Section__Shutdown
# Schedules auto-termination via systemd-run. Paired with
# InstanceInitiatedShutdownBehavior=terminate in EC2__Launch__Helper so that
# halt causes termination, not stop.
# max_hours=0 emits a no-op comment (caller omits this section instead).
# ═══════════════════════════════════════════════════════════════════════════════
from osbot_utils.type_safe.Type_Safe import Type_Safe
TEMPLATE = '''
# ── Auto-terminate after {max_hours}h ({seconds}s) ───────────────────────────
systemd-run --on-active={seconds}s /sbin/shutdown -h now
echo "[ephemeral-ec2] auto-terminate timer set: {max_hours}h ({seconds}s) from now"
'''
class Section__Shutdown(Type_Safe):
def render(self, max_hours: float = 1.0) -> str:
seconds = max(1, int(round(max_hours * 3600)))
return TEMPLATE.format(max_hours=max_hours, seconds=seconds)Apache-2.0 — quoted from the estate's own source, not covered by this site's CC BY 4.0. Counted 2026-08-24.
The anatomy, and every part of it is a convention
- A
═══banner carrying the coupling, not just the purpose — paired withInstanceInitiatedShutdownBehavior=terminateinEC2__Launch__Helper. The shell and the API call that makes it correct are documented together, in the place where forgetting one would break the other. This is the single best idea on this page. - A module-level
TEMPLATE— a triple-quoted string, so the shell is readable as shell. - The generated shell gets its own banner, using the
──box-drawing character rather than Python's═══. Shell inside Python is marked as a different language by its comment style. Section__X(Type_Safe)with a singlerender()returningstr.- Computation in Python, not in shell. The arithmetic, the clamping and the rounding all happen where they can be tested; the emitted shell contains a literal.
- An
echobreadcrumb with a bracketed prefix, so the boot log is greppable. - The edge case documented in the banner —
max_hours=0— with the caller's correct behaviour named.
The fifteen sections
Base · Docker · Sidecar · Nginx ·
Env__File · GPU_Verify · NVIDIA_Container_Toolkit ·
Ollama · VLLM · SGit_Venv ·
Claude_Code__Firstboot · Claude_Launch · Agent_Tools ·
Node · Shutdown
A node's user-data is composed by rendering the sections it needs and concatenating them.
Composition is a list, not a template with conditionals.
Which is why there is no {% if gpu %} anywhere, and why a spec that does not need
CUDA simply omits NVIDIA_Container_Toolkit. A conditional inside a template is a
branch nobody tests; an omitted list element is a list you can print.
3. The trade
What it buys
- The parameters are typed and tested. A section has unit tests; a bash script interpolated with
seddoes not. - No quoting hell. Values are substituted by
str.formatin Python, not by shell expansion inside a heredoc inside a YAML string. - Composition without conditionals. Include a section or do not.
- The shell is diffable as a unit. A change to shutdown behaviour is one small file, not a hunk inside a 400-line boot script.
- The coupling is documented next to the code, in the banner.
What it costs
- You cannot run it directly. No
bash -non a template, noshellcheck, no executing a section in isolation without rendering it first. - Two languages in one file, with
{}meaningstr.formatin a language where{}also means brace expansion. A literal{must be escaped as{{. - The rendered output is not committed, so a reviewer reads the template and never the script.
Both mitigations are cheap and neither exists yet. Render every section in a test and
pipe it through shellcheck; and commit a rendered golden file per section so diffs
show the actual shell. The first is the highest-value single change available anywhere in the
enforcement backlog. The check that would do it →
4. Hand-written shell — where it survives
5 files, and set -e discipline
is inconsistent: 2 of
5 have it, in three different forms —
set -euo pipefail, set -eu, set -u. No rule covers it.
The recommendation this site publishes is set -euo pipefail everywhere,
since it is already the strictest form in use and the other two are strictly weaker. That is a
recommendation, not an observed convention, and it is labelled as one on
the rules page.
For RUN lines in Dockerfiles and run: blocks in workflows, the
conventions visible in the repository are: one logical step per RUN,
&& chaining with \ continuations, cleanup in the same layer,
and — from the Playwright Dockerfile — build-time assertions rather than trust, with a
comment naming the production incident that motivated each. That last one is the same instinct as
rules 14 and 15: a check that cites the case that made it necessary.
5. This site runs the rule it recommends
The shell in this repository's own release pipeline uses set -euo pipefail, and
the workflow carries a comment explaining the one place it bites: piping git log
into an early-exiting reader makes git die of SIGPIPE, and under
pipefail that status propagates out of the command substitution and kills the job
after the variable has already been assigned — so it looks like the next line failed.
The job it lives in →