diff --git a/build.sh b/build.sh index 286c9e1..bf9aa0f 100755 --- a/build.sh +++ b/build.sh @@ -1,8 +1,8 @@ #!/usr/bin/env bash -# Assemble dist/ — the CSP-clean static build served at learn.bztmon.com. +# Assemble dist/ - the CSP-clean static build served at learn.bztmon.com. # The site's face is the delivery arc (index.html); scripts stay SEPARATE files so # `script-src 'self'` holds with no 'unsafe-inline' exception. Assembly logic lives -# in pilot/assemble-arc.mjs (node — the build image carries no python). +# in pilot/assemble-arc.mjs (node - the build image carries no python). set -euo pipefail IFS=$'\n\t' trap 'printf "build failed at line %s\n" "$LINENO" >&2' ERR @@ -35,7 +35,7 @@ node pilot/assemble-arc.mjs "$dist" # No inline JS may survive into ANY served HTML, or the CSP silently kills the page. for page in "$dist"/*.html; do if grep -oiE ']*>' -- "$page" | grep -qivE 'src='; then - printf '%s contains an inline \n" - f"\n" + out) - -dest = ROOT / "pilot/arc.html" -dest.write_text(out) -heroes = len(re.findall(r"data:image/webp", out)) -print(f"built pilot/arc.html {len(out)/1024:.0f}KB ({heroes} heroes inlined)") diff --git a/pilot/assemble.py b/pilot/assemble.py deleted file mode 100644 index b494078..0000000 --- a/pilot/assemble.py +++ /dev/null @@ -1,98 +0,0 @@ -#!/usr/bin/env python3 -"""Derive the Course I slice manifest from measured geometry, then assemble the pilot. - -The cut lines below are NOT eyeballed. They were fitted from the hero's own pixels: -each slab's bottom edge tracked column-by-column through the void gaps, least-squares -lines through the left and right arms, tip at their intersection. Symmetry of the two -arms came out within 0-10% on every slab, which is the check that the fit is real. -Re-derive with tools/fit-geometry.py if the hero is ever regenerated. -""" -import base64, json, pathlib, re, sys - -ROOT = pathlib.Path(__file__).resolve().parent.parent -HERO = ROOT / "assets/dist/docker-layers.webp" - -# measured bottom silhouettes: left corner -> bottom tip -> right corner (percent of frame) -CUTS = { - "writable": dict(l=(27.75, 27.24), tip=(50.77, 40.73), r=(72.00, 26.87)), - "code": dict(l=(28.38, 41.34), tip=(49.76, 56.15), r=(71.50, 41.02)), - "deps": dict(l=(27.88, 56.36), tip=(49.48, 76.21), r=(71.88, 56.30)), - "base": dict(l=(28.12, 74.41), tip=(49.95, 94.20), r=(71.75, 74.40)), -} -EPS = 0.7 # regions grow UPWARD only — the slab in front always covers the overlap. - # Growing downward would drag a copy of the slab behind into the slice. -NEST = 0.75 # fraction of each gap closed when collapsed - -ORDER = ["base", "deps", "code", "writable"] # paint order: back -> front -LABEL = { - "base": ("Base image", "The OS layer everything else is stacked on. Pull it once, " - "share it across every image on the host."), - "deps": ("Dependencies", "Your packages and runtime. Change these and every layer " - "above has to be rebuilt."), - "code": ("Application code", "Your actual program — usually the smallest layer, and " - "the one that changes every single build."), - "writable": ("Writable layer", "Created fresh per container, thrown away when it dies. " - "Anything written here is not in the image."), -} - - -def V(c): return [list(c["l"]), list(c["tip"]), list(c["r"])] -def up(v, e): return [[p[0], round(p[1] - e, 2)] for p in v] - - -def band(top, bot): - """Region between two silhouettes; sides run to the frame edge, which is pure void.""" - p = [] - p += ([[0, top[0][1]]] + top + [[100, top[2][1]]]) if top else [[0, 0], [100, 0]] - p += ([[100, bot[2][1]]] + bot[::-1] + [[0, bot[0][1]]]) if bot else [[100, 100], [0, 100]] - return p - - -def manifest(): - tips = {k: CUTS[k]["tip"][1] for k in CUTS} - dy, acc = {}, 0.0 - for i, k in enumerate(ORDER): - if i: - acc += (tips[ORDER[i - 1]] - tips[k]) * NEST - dy[k] = round(acc, 2) - - regions = { - "base": band(up(V(CUTS["deps"]), EPS), None), - "deps": band(up(V(CUTS["code"]), EPS), V(CUTS["deps"])), - "code": band(up(V(CUTS["writable"]), EPS), V(CUTS["code"])), - "writable": band(None, V(CUTS["writable"])), - } - return { - "scene": "course-I-docker-layers", "eps": EPS, "nest": NEST, - "source": "assets/dist/docker-layers.webp", - "parts": [{"name": k, "title": LABEL[k][0], "blurb": LABEL[k][1], - "anchor": round(CUTS[k]["tip"][1] - 6, 2), - "points": regions[k], "dy": dy[k]} for k in ORDER], - } - - -def main(): - if not HERO.exists(): - sys.exit(f"missing hero: {HERO}") - man = manifest() - (ROOT / "pilot/manifest.json").write_text(json.dumps(man, indent=1)) - - hero = "data:image/webp;base64," + base64.b64encode(HERO.read_bytes()).decode() - out = (ROOT / "pilot/course-1.tpl.html").read_text() \ - .replace("__MANIFEST__", json.dumps(man, separators=(",", ":"))) \ - .replace("__HERO__", hero) - out = (f"\n" - f"\n" + out) - - dest = ROOT / "pilot/course-1.html" - dest.write_text(out) - - blocks = re.findall(r"", out) - print(f"built {dest.relative_to(ROOT)} {len(out)/1024:.0f}KB " - f"({len(blocks)} script blocks, hero {len(hero)/1024:.0f}KB inlined)") - for name, part in zip(ORDER, man["parts"]): - print(f" {name:<9} {len(part['points']):2d} pts collapse travel {part['dy']:5.2f}%") - - -if __name__ == "__main__": - main() diff --git a/pilot/course-1.tpl.html b/pilot/course-1.tpl.html deleted file mode 100644 index 27a45fd..0000000 --- a/pilot/course-1.tpl.html +++ /dev/null @@ -1,229 +0,0 @@ -
-

The Exploded Cluster · Course I

-

An image is not a box.
It is a stack of frozen diffs.

-

Scroll, and the thing you keep calling "a container image" comes apart in your hands. - Four layers. Each one only stores what changed from the layer under it.

- -
- -
-

Docker image layers, exploded

- -
-
-
- An exploded isometric view of a container image: four stacked slabs floating apart —
-                  a heavy metal base, a circuit-etched dependency layer, a magenta-traced code layer,
-                  and a thin frosted-glass writable layer on top. -
- -
    -
    -
    -
    - -
    -

    - Field note. The order of your Dockerfile is a caching decision, not a style choice. - Put COPY . . above your dependency install and you have told the builder to - throw away every cached layer every time you change one line of code. -

    -
    - - - - diff --git a/pilot/manifest.json b/pilot/manifest.json deleted file mode 100644 index 8ad2bdf..0000000 --- a/pilot/manifest.json +++ /dev/null @@ -1,180 +0,0 @@ -{ - "scene": "course-I-docker-layers", - "eps": 0.7, - "nest": 0.75, - "source": "assets/dist/docker-layers.webp", - "parts": [ - { - "name": "base", - "title": "Base image", - "blurb": "The OS layer everything else is stacked on. Pull it once, share it across every image on the host.", - "anchor": 88.2, - "points": [ - [ - 0, - 55.66 - ], - [ - 27.88, - 55.66 - ], - [ - 49.48, - 75.51 - ], - [ - 71.88, - 55.6 - ], - [ - 100, - 55.6 - ], - [ - 100, - 100 - ], - [ - 0, - 100 - ] - ], - "dy": 0.0 - }, - { - "name": "deps", - "title": "Dependencies", - "blurb": "Your packages and runtime. Change these and every layer above has to be rebuilt.", - "anchor": 70.21, - "points": [ - [ - 0, - 40.64 - ], - [ - 28.38, - 40.64 - ], - [ - 49.76, - 55.45 - ], - [ - 71.5, - 40.32 - ], - [ - 100, - 40.32 - ], - [ - 100, - 56.3 - ], - [ - 71.88, - 56.3 - ], - [ - 49.48, - 76.21 - ], - [ - 27.88, - 56.36 - ], - [ - 0, - 56.36 - ] - ], - "dy": 13.49 - }, - { - "name": "code", - "title": "Application code", - "blurb": "Your actual program \u2014 usually the smallest layer, and the one that changes every single build.", - "anchor": 50.15, - "points": [ - [ - 0, - 26.54 - ], - [ - 27.75, - 26.54 - ], - [ - 50.77, - 40.03 - ], - [ - 72.0, - 26.17 - ], - [ - 100, - 26.17 - ], - [ - 100, - 41.02 - ], - [ - 71.5, - 41.02 - ], - [ - 49.76, - 56.15 - ], - [ - 28.38, - 41.34 - ], - [ - 0, - 41.34 - ] - ], - "dy": 28.54 - }, - { - "name": "writable", - "title": "Writable layer", - "blurb": "Created fresh per container, thrown away when it dies. Anything written here is not in the image.", - "anchor": 34.73, - "points": [ - [ - 0, - 0 - ], - [ - 100, - 0 - ], - [ - 100, - 26.87 - ], - [ - 72.0, - 26.87 - ], - [ - 50.77, - 40.73 - ], - [ - 27.75, - 27.24 - ], - [ - 0, - 27.24 - ] - ], - "dy": 40.1 - } - ] -} \ No newline at end of file