This commit is contained in:
2026-08-20 11:24:42 -03:00
parent a65c92257d
commit 83b6cbebe3
64 changed files with 7688 additions and 0 deletions

View File

@@ -0,0 +1,47 @@
digraph rig_install {
rankdir=LR
bgcolor="#0a0e17"
fontname="Helvetica"
node [fontname="Helvetica" fontsize=11 style=filled color="#1e2a4a" fontcolor="#e8eaf0" shape=box]
edge [fontname="Helvetica" fontsize=9 fontcolor="#8892a8" color="#4a5568"]
label="Installation — the only host prerequisite is Docker"
labelloc=t
fontsize=16
fontcolor="#0066ff"
subgraph cluster_host {
label="Your machine"
style=dashed
color="#1e2a4a"
fontcolor="#8892a8"
docker [label="Docker\n(the one prerequisite)" fillcolor="#1a1a3a" fontcolor="#0066ff" shape=octagon]
bin [label="~/.local/bin\nkind · kubectl · tilt\njq" fillcolor="#121829" shape=cylinder]
}
subgraph cluster_wizard {
label="Installer container (transient)"
style=dashed
color="#1e2a4a"
fontcolor="#8892a8"
wizard [label="wizard\ncurl · jq · python · graphviz" fillcolor="#121829"]
detect [label="detect host\nWSL · memory · inotify · docker" fillcolor="#121829"]
fetch [label="fetch + verify\nSHA256, pinned versions" fillcolor="#121829"]
}
upstream [label="upstream\nreleases / corporate mirror" fillcolor="#1a3a1a" fontcolor="#00c853" shape=octagon]
report [label="report what it\nCANNOT do" fillcolor="#3a1a1a" fontcolor="#ffc107"]
docker -> wizard [label="docker run"]
wizard -> detect
detect -> fetch
fetch -> upstream [label="pinned + checksummed" color="#00c853"]
fetch -> bin [label="install"]
detect -> report [style=dashed label="sudo / Windows-side steps" color="#ffc107"]
// The container is gone after this; nothing depends on it at run time.
wizard -> gone [style=dotted label="exits"]
gone [label="(container discarded)" fillcolor="#0a0e17" fontcolor="#4a5568" color="#1e2a4a" style="filled,dashed"]
}

View File

@@ -0,0 +1,128 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 14.1.2 (0)
-->
<!-- Title: rig_install Pages: 1 -->
<svg width="1145pt" height="287pt"
viewBox="0.00 0.00 1145.00 287.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 283.29)">
<title>rig_install</title>
<polygon fill="#0a0e17" stroke="none" points="-4,4 -4,-283.29 1141.06,-283.29 1141.06,4 -4,4"/>
<text xml:space="preserve" text-anchor="middle" x="568.53" y="-260.09" font-family="Helvetica,sans-Serif" font-size="16.00" fill="#0066ff">Installation — the only host prerequisite is Docker</text>
<g id="clust1" class="cluster">
<title>cluster_host</title>
<polygon fill="#0a0e17" stroke="#1e2a4a" stroke-dasharray="5,2" points="898.49,-8 898.49,-190 1123.82,-190 1123.82,-8 898.49,-8"/>
<text xml:space="preserve" text-anchor="middle" x="1011.15" y="-170.8" font-family="Helvetica,sans-Serif" font-size="16.00" fill="#8892a8">Your machine</text>
</g>
<g id="clust2" class="cluster">
<title>cluster_wizard</title>
<polygon fill="#0a0e17" stroke="#1e2a4a" stroke-dasharray="5,2" points="8,-95 8,-175 745.5,-175 745.5,-95 8,-95"/>
<text xml:space="preserve" text-anchor="middle" x="376.75" y="-155.8" font-family="Helvetica,sans-Serif" font-size="16.00" fill="#8892a8">Installer container (transient)</text>
</g>
<!-- docker -->
<g id="node1" class="node">
<title>docker</title>
<polygon fill="#1a1a3a" stroke="#1e2a4a" points="1115.82,-31.9 1115.82,-54.1 1054.51,-69.79 967.8,-69.79 906.49,-54.1 906.49,-31.9 967.8,-16.21 1054.51,-16.21 1115.82,-31.9"/>
<text xml:space="preserve" text-anchor="middle" x="1011.15" y="-46.05" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#0066ff">Docker</text>
<text xml:space="preserve" text-anchor="middle" x="1011.15" y="-32.55" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#0066ff">(the one prerequisite)</text>
</g>
<!-- wizard -->
<g id="node3" class="node">
<title>wizard</title>
<polygon fill="#121829" stroke="#1e2a4a" points="181.25,-139 16,-139 16,-103 181.25,-103 181.25,-139"/>
<text xml:space="preserve" text-anchor="middle" x="98.62" y="-124.05" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#e8eaf0">wizard</text>
<text xml:space="preserve" text-anchor="middle" x="98.62" y="-110.55" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#e8eaf0">curl · jq · python · graphviz</text>
</g>
<!-- docker&#45;&gt;wizard -->
<g id="edge1" class="edge">
<title>docker&#45;&gt;wizard</title>
<path fill="none" stroke="#4a5568" d="M906.12,-45.7C757.47,-50.51 475.98,-63.12 238.25,-94 223.38,-95.93 207.7,-98.48 192.45,-101.24"/>
<polygon fill="#4a5568" stroke="#4a5568" points="192.13,-97.74 182.93,-103.01 193.4,-104.63 192.13,-97.74"/>
<text xml:space="preserve" text-anchor="middle" x="506.62" y="-74.39" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#8892a8">docker run</text>
</g>
<!-- bin -->
<g id="node2" class="node">
<title>bin</title>
<path fill="#121829" stroke="#1e2a4a" d="M1069.03,-148.28C1069.03,-151.63 1043.09,-154.34 1011.15,-154.34 979.22,-154.34 953.28,-151.63 953.28,-148.28 953.28,-148.28 953.28,-93.72 953.28,-93.72 953.28,-90.37 979.22,-87.66 1011.15,-87.66 1043.09,-87.66 1069.03,-90.37 1069.03,-93.72 1069.03,-93.72 1069.03,-148.28 1069.03,-148.28"/>
<path fill="none" stroke="#1e2a4a" d="M1069.03,-148.28C1069.03,-144.94 1043.09,-142.22 1011.15,-142.22 979.22,-142.22 953.28,-144.94 953.28,-148.28"/>
<text xml:space="preserve" text-anchor="middle" x="1011.15" y="-130.8" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#e8eaf0">~/.local/bin</text>
<text xml:space="preserve" text-anchor="middle" x="1011.15" y="-117.3" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#e8eaf0">kind · kubectl · tilt</text>
<text xml:space="preserve" text-anchor="middle" x="1011.15" y="-103.8" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#e8eaf0">jq</text>
</g>
<!-- detect -->
<g id="node4" class="node">
<title>detect</title>
<polygon fill="#121829" stroke="#1e2a4a" points="429,-139 238.25,-139 238.25,-103 429,-103 429,-139"/>
<text xml:space="preserve" text-anchor="middle" x="333.62" y="-124.05" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#e8eaf0">detect host</text>
<text xml:space="preserve" text-anchor="middle" x="333.62" y="-110.55" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#e8eaf0">WSL · memory · inotify · docker</text>
</g>
<!-- wizard&#45;&gt;detect -->
<g id="edge2" class="edge">
<title>wizard&#45;&gt;detect</title>
<path fill="none" stroke="#4a5568" d="M181.44,-121C195.97,-121 211.28,-121 226.37,-121"/>
<polygon fill="#4a5568" stroke="#4a5568" points="226.33,-124.5 236.33,-121 226.33,-117.5 226.33,-124.5"/>
</g>
<!-- gone -->
<g id="node8" class="node">
<title>gone</title>
<polygon fill="#0a0e17" stroke="#1e2a4a" stroke-dasharray="5,2" points="400.5,-47 266.75,-47 266.75,-11 400.5,-11 400.5,-47"/>
<text xml:space="preserve" text-anchor="middle" x="333.62" y="-25.3" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#4a5568">(container discarded)</text>
</g>
<!-- wizard&#45;&gt;gone -->
<g id="edge7" class="edge">
<title>wizard&#45;&gt;gone</title>
<path fill="none" stroke="#4a5568" stroke-dasharray="1,5" d="M119.29,-102.62C138.26,-86 168.54,-62.29 199.25,-49.75 216.76,-42.6 236.49,-37.9 255.29,-34.81"/>
<polygon fill="#4a5568" stroke="#4a5568" points="255.67,-38.29 265.04,-33.35 254.64,-31.37 255.67,-38.29"/>
<text xml:space="preserve" text-anchor="middle" x="209.75" y="-52.45" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#8892a8">exits</text>
</g>
<!-- fetch -->
<g id="node5" class="node">
<title>fetch</title>
<polygon fill="#121829" stroke="#1e2a4a" points="737.5,-139 584.25,-139 584.25,-103 737.5,-103 737.5,-139"/>
<text xml:space="preserve" text-anchor="middle" x="660.88" y="-124.05" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#e8eaf0">fetch + verify</text>
<text xml:space="preserve" text-anchor="middle" x="660.88" y="-110.55" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#e8eaf0">SHA256, pinned versions</text>
</g>
<!-- detect&#45;&gt;fetch -->
<g id="edge3" class="edge">
<title>detect&#45;&gt;fetch</title>
<path fill="none" stroke="#4a5568" d="M429.14,-121C474.43,-121 528.32,-121 572.63,-121"/>
<polygon fill="#4a5568" stroke="#4a5568" points="572.46,-124.5 582.46,-121 572.46,-117.5 572.46,-124.5"/>
</g>
<!-- report -->
<g id="node7" class="node">
<title>report</title>
<polygon fill="#3a1a1a" stroke="#1e2a4a" points="706.75,-219 615,-219 615,-183 706.75,-183 706.75,-219"/>
<text xml:space="preserve" text-anchor="middle" x="660.88" y="-204.05" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#ffc107">report what it</text>
<text xml:space="preserve" text-anchor="middle" x="660.88" y="-190.55" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#ffc107">CANNOT do</text>
</g>
<!-- detect&#45;&gt;report -->
<g id="edge6" class="edge">
<title>detect&#45;&gt;report</title>
<path fill="none" stroke="#ffc107" stroke-dasharray="5,2" d="M409.65,-139.45C468.85,-154.02 550.13,-174.01 603.78,-187.2"/>
<polygon fill="#ffc107" stroke="#ffc107" points="602.77,-190.56 613.32,-189.55 604.45,-183.76 602.77,-190.56"/>
<text xml:space="preserve" text-anchor="middle" x="506.62" y="-180.06" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#8892a8">sudo / Windows&#45;side steps</text>
</g>
<!-- fetch&#45;&gt;bin -->
<g id="edge5" class="edge">
<title>fetch&#45;&gt;bin</title>
<path fill="none" stroke="#4a5568" d="M737.88,-121C798.58,-121 882.9,-121 941.56,-121"/>
<polygon fill="#4a5568" stroke="#4a5568" points="941.43,-124.5 951.43,-121 941.43,-117.5 941.43,-124.5"/>
<text xml:space="preserve" text-anchor="middle" x="811.38" y="-123.7" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#8892a8">install</text>
</g>
<!-- upstream -->
<g id="node6" class="node">
<title>upstream</title>
<polygon fill="#1a3a1a" stroke="#1e2a4a" points="1137.06,-213.9 1137.06,-236.1 1063.3,-251.79 959,-251.79 885.25,-236.1 885.25,-213.9 959,-198.21 1063.3,-198.21 1137.06,-213.9"/>
<text xml:space="preserve" text-anchor="middle" x="1011.15" y="-228.05" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#00c853">upstream</text>
<text xml:space="preserve" text-anchor="middle" x="1011.15" y="-214.55" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#00c853">releases / corporate mirror</text>
</g>
<!-- fetch&#45;&gt;upstream -->
<g id="edge4" class="edge">
<title>fetch&#45;&gt;upstream</title>
<path fill="none" stroke="#00c853" d="M714.8,-139.5C759.85,-154.95 826.43,-177.11 885.25,-194 894.79,-196.74 904.78,-199.46 914.78,-202.09"/>
<polygon fill="#00c853" stroke="#00c853" points="913.59,-205.4 924.15,-204.52 915.35,-198.62 913.59,-205.4"/>
<text xml:space="preserve" text-anchor="middle" x="811.38" y="-191.36" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#8892a8">pinned + checksummed</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

@@ -0,0 +1,54 @@
digraph rig_environment {
rankdir=TB
bgcolor="#0a0e17"
fontname="Helvetica"
node [fontname="Helvetica" fontsize=11 style=filled color="#1e2a4a" fontcolor="#e8eaf0" shape=box]
edge [fontname="Helvetica" fontsize=9 fontcolor="#8892a8" color="#4a5568"]
label="One environment per directory — copies never collide"
labelloc=t
fontsize=16
fontcolor="#0066ff"
dirname [label="directory name\ne.g. acmebank/" fillcolor="#1f6feb" fontcolor="#ffffff" shape=octagon]
subgraph cluster_derived {
label="Everything below is derived from it"
style=dashed
color="#1e2a4a"
fontcolor="#8892a8"
cname [label="cluster name\nacmebank" fillcolor="#121829"]
ctx [label="kubectl context\nkind-acmebank" fillcolor="#121829"]
img [label="image tag\nacmebank-wizard" fillcolor="#121829"]
ports [label="port block\n2130021309" fillcolor="#121829"]
reg [label="registry container\nacmebank-registry" fillcolor="#121829"]
}
subgraph cluster_config {
label="Configuration — weakest first, later wins"
style=dashed
color="#1e2a4a"
fontcolor="#8892a8"
versions [label="versions.env\npinned toolchain" fillcolor="#121829"]
profile [label="env.d/<profile>.env\nnodes · CNI · audit · addons" fillcolor="#121829"]
localenv [label="ctrl/.env\nsecrets, overrides" fillcolor="#121829"]
shell [label="the environment\nPROFILE=client make …" fillcolor="#1a3a1a" fontcolor="#00c853"]
}
dirname -> cname
dirname -> ctx
dirname -> img
dirname -> ports
dirname -> reg
versions -> profile [label="overridden by"]
profile -> localenv [label="overridden by"]
localenv -> shell [label="overridden by" color="#00c853"]
cluster [label="kind cluster" fillcolor="#1a1a3a" fontcolor="#0066ff" shape=octagon]
cname -> cluster
ports -> cluster
shell -> cluster [style=dashed]
}

View File

@@ -0,0 +1,169 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 14.1.2 (0)
-->
<!-- Title: rig_environment Pages: 1 -->
<svg width="971pt" height="481pt"
viewBox="0.00 0.00 971.00 481.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 476.83)">
<title>rig_environment</title>
<polygon fill="#0a0e17" stroke="none" points="-4,4 -4,-476.83 967,-476.83 967,4 -4,4"/>
<text xml:space="preserve" text-anchor="middle" x="481.5" y="-453.63" font-family="Helvetica,sans-Serif" font-size="16.00" fill="#0066ff">One environment per directory — copies never collide</text>
<g id="clust1" class="cluster">
<title>cluster_derived</title>
<polygon fill="#0a0e17" stroke="#1e2a4a" stroke-dasharray="5,2" points="8,-65 8,-144.5 605,-144.5 605,-65 8,-65"/>
<text xml:space="preserve" text-anchor="middle" x="306.5" y="-125.3" font-family="Helvetica,sans-Serif" font-size="16.00" fill="#8892a8">Everything below is derived from it</text>
</g>
<g id="clust2" class="cluster">
<title>cluster_config</title>
<polygon fill="#0a0e17" stroke="#1e2a4a" stroke-dasharray="5,2" points="613,-65 613,-437.33 955,-437.33 955,-65 613,-65"/>
<text xml:space="preserve" text-anchor="middle" x="784" y="-418.13" font-family="Helvetica,sans-Serif" font-size="16.00" fill="#8892a8">Configuration — weakest first, later wins</text>
</g>
<!-- dirname -->
<g id="node1" class="node">
<title>dirname</title>
<polygon fill="#1f6feb" stroke="#1e2a4a" points="375.11,-197.44 375.11,-219.63 329.94,-235.33 266.06,-235.33 220.89,-219.63 220.89,-197.44 266.06,-181.75 329.94,-181.75 375.11,-197.44"/>
<text xml:space="preserve" text-anchor="middle" x="298" y="-211.59" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#ffffff">directory name</text>
<text xml:space="preserve" text-anchor="middle" x="298" y="-198.09" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#ffffff">e.g. acmebank/</text>
</g>
<!-- cname -->
<g id="node2" class="node">
<title>cname</title>
<polygon fill="#121829" stroke="#1e2a4a" points="104,-109 16,-109 16,-73 104,-73 104,-109"/>
<text xml:space="preserve" text-anchor="middle" x="60" y="-94.05" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#e8eaf0">cluster name</text>
<text xml:space="preserve" text-anchor="middle" x="60" y="-80.55" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#e8eaf0">acmebank</text>
</g>
<!-- dirname&#45;&gt;cname -->
<g id="edge1" class="edge">
<title>dirname&#45;&gt;cname</title>
<path fill="none" stroke="#4a5568" d="M232.76,-193.05C195.81,-183.01 149.78,-167.28 113,-144.5 101.37,-137.29 90.31,-127.09 81.33,-117.6"/>
<polygon fill="#4a5568" stroke="#4a5568" points="84.04,-115.37 74.73,-110.3 78.85,-120.07 84.04,-115.37"/>
</g>
<!-- ctx -->
<g id="node3" class="node">
<title>ctx</title>
<polygon fill="#121829" stroke="#1e2a4a" points="223.75,-109 122.25,-109 122.25,-73 223.75,-73 223.75,-109"/>
<text xml:space="preserve" text-anchor="middle" x="173" y="-94.05" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#e8eaf0">kubectl context</text>
<text xml:space="preserve" text-anchor="middle" x="173" y="-80.55" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#e8eaf0">kind&#45;acmebank</text>
</g>
<!-- dirname&#45;&gt;ctx -->
<g id="edge2" class="edge">
<title>dirname&#45;&gt;ctx</title>
<path fill="none" stroke="#4a5568" d="M269.64,-181.32C248.71,-161.98 220.42,-135.83 199.86,-116.83"/>
<polygon fill="#4a5568" stroke="#4a5568" points="202.4,-114.41 192.68,-110.19 197.65,-119.55 202.4,-114.41"/>
</g>
<!-- img -->
<g id="node4" class="node">
<title>img</title>
<polygon fill="#121829" stroke="#1e2a4a" points="354,-109 242,-109 242,-73 354,-73 354,-109"/>
<text xml:space="preserve" text-anchor="middle" x="298" y="-94.05" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#e8eaf0">image tag</text>
<text xml:space="preserve" text-anchor="middle" x="298" y="-80.55" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#e8eaf0">acmebank&#45;wizard</text>
</g>
<!-- dirname&#45;&gt;img -->
<g id="edge3" class="edge">
<title>dirname&#45;&gt;img</title>
<path fill="none" stroke="#4a5568" d="M298,-181.32C298,-163.19 298,-139.07 298,-120.47"/>
<polygon fill="#4a5568" stroke="#4a5568" points="301.5,-120.67 298,-110.67 294.5,-120.67 301.5,-120.67"/>
</g>
<!-- ports -->
<g id="node5" class="node">
<title>ports</title>
<polygon fill="#121829" stroke="#1e2a4a" points="460.38,-109 371.62,-109 371.62,-73 460.38,-73 460.38,-109"/>
<text xml:space="preserve" text-anchor="middle" x="416" y="-94.05" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#e8eaf0">port block</text>
<text xml:space="preserve" text-anchor="middle" x="416" y="-80.55" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#e8eaf0">2130021309</text>
</g>
<!-- dirname&#45;&gt;ports -->
<g id="edge4" class="edge">
<title>dirname&#45;&gt;ports</title>
<path fill="none" stroke="#4a5568" d="M324.94,-181.53C336.66,-170.19 350.54,-156.71 363,-144.5 372.11,-135.57 382.06,-125.74 390.85,-117.02"/>
<polygon fill="#4a5568" stroke="#4a5568" points="393.15,-119.67 397.79,-110.14 388.22,-114.7 393.15,-119.67"/>
</g>
<!-- reg -->
<g id="node6" class="node">
<title>reg</title>
<polygon fill="#121829" stroke="#1e2a4a" points="597.38,-109 478.62,-109 478.62,-73 597.38,-73 597.38,-109"/>
<text xml:space="preserve" text-anchor="middle" x="538" y="-94.05" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#e8eaf0">registry container</text>
<text xml:space="preserve" text-anchor="middle" x="538" y="-80.55" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#e8eaf0">acmebank&#45;registry</text>
</g>
<!-- dirname&#45;&gt;reg -->
<g id="edge5" class="edge">
<title>dirname&#45;&gt;reg</title>
<path fill="none" stroke="#4a5568" d="M356.87,-190.63C390.74,-179.74 433.49,-163.98 469,-144.5 483.3,-136.65 497.86,-126.04 509.9,-116.41"/>
<polygon fill="#4a5568" stroke="#4a5568" points="511.88,-119.31 517.39,-110.26 507.44,-113.9 511.88,-119.31"/>
</g>
<!-- cluster -->
<g id="node11" class="node">
<title>cluster</title>
<polygon fill="#1a1a3a" stroke="#1e2a4a" points="469.81,-10.54 469.81,-25.46 438.29,-36 393.71,-36 362.19,-25.46 362.19,-10.54 393.71,0 438.29,0 469.81,-10.54"/>
<text xml:space="preserve" text-anchor="middle" x="416" y="-14.3" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#0066ff">kind cluster</text>
</g>
<!-- cname&#45;&gt;cluster -->
<g id="edge9" class="edge">
<title>cname&#45;&gt;cluster</title>
<path fill="none" stroke="#4a5568" d="M93.06,-72.62C99.54,-69.72 106.38,-67.02 113,-65 192.78,-40.7 288.45,-28.91 350.65,-23.42"/>
<polygon fill="#4a5568" stroke="#4a5568" points="350.69,-26.93 360.36,-22.6 350.1,-19.96 350.69,-26.93"/>
</g>
<!-- ports&#45;&gt;cluster -->
<g id="edge10" class="edge">
<title>ports&#45;&gt;cluster</title>
<path fill="none" stroke="#4a5568" d="M416,-72.81C416,-65.23 416,-56.1 416,-47.54"/>
<polygon fill="#4a5568" stroke="#4a5568" points="419.5,-47.54 416,-37.54 412.5,-47.54 419.5,-47.54"/>
</g>
<!-- versions -->
<g id="node7" class="node">
<title>versions</title>
<polygon fill="#121829" stroke="#1e2a4a" points="759.38,-401.83 652.62,-401.83 652.62,-365.83 759.38,-365.83 759.38,-401.83"/>
<text xml:space="preserve" text-anchor="middle" x="706" y="-386.88" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#e8eaf0">versions.env</text>
<text xml:space="preserve" text-anchor="middle" x="706" y="-373.38" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#e8eaf0">pinned toolchain</text>
</g>
<!-- profile -->
<g id="node8" class="node">
<title>profile</title>
<polygon fill="#121829" stroke="#1e2a4a" points="790.5,-318.58 621.5,-318.58 621.5,-282.58 790.5,-282.58 790.5,-318.58"/>
<text xml:space="preserve" text-anchor="middle" x="706" y="-303.63" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#e8eaf0">env.d/&lt;profile&gt;.env</text>
<text xml:space="preserve" text-anchor="middle" x="706" y="-290.13" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#e8eaf0">nodes · CNI · audit · addons</text>
</g>
<!-- versions&#45;&gt;profile -->
<g id="edge6" class="edge">
<title>versions&#45;&gt;profile</title>
<path fill="none" stroke="#4a5568" d="M706,-365.59C706,-355.32 706,-342.03 706,-330.21"/>
<polygon fill="#4a5568" stroke="#4a5568" points="709.5,-330.58 706,-320.58 702.5,-330.58 709.5,-330.58"/>
<text xml:space="preserve" text-anchor="middle" x="737.5" y="-339.28" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#8892a8">overridden by</text>
</g>
<!-- localenv -->
<g id="node9" class="node">
<title>localenv</title>
<polygon fill="#121829" stroke="#1e2a4a" points="761.88,-226.54 646.12,-226.54 646.12,-190.54 761.88,-190.54 761.88,-226.54"/>
<text xml:space="preserve" text-anchor="middle" x="704" y="-211.59" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#e8eaf0">ctrl/.env</text>
<text xml:space="preserve" text-anchor="middle" x="704" y="-198.09" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#e8eaf0">secrets, overrides</text>
</g>
<!-- profile&#45;&gt;localenv -->
<g id="edge7" class="edge">
<title>profile&#45;&gt;localenv</title>
<path fill="none" stroke="#4a5568" d="M705.61,-282.22C705.34,-269.76 704.96,-252.69 704.64,-238.23"/>
<polygon fill="#4a5568" stroke="#4a5568" points="708.14,-238.28 704.42,-228.36 701.14,-238.43 708.14,-238.28"/>
<text xml:space="preserve" text-anchor="middle" x="736.68" y="-256.03" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#8892a8">overridden by</text>
</g>
<!-- shell -->
<g id="node10" class="node">
<title>shell</title>
<polygon fill="#1a3a1a" stroke="#1e2a4a" points="772.38,-109 623.62,-109 623.62,-73 772.38,-73 772.38,-109"/>
<text xml:space="preserve" text-anchor="middle" x="698" y="-94.05" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#00c853">the environment</text>
<text xml:space="preserve" text-anchor="middle" x="698" y="-80.55" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#00c853">PROFILE=client make …</text>
</g>
<!-- localenv&#45;&gt;shell -->
<g id="edge8" class="edge">
<title>localenv&#45;&gt;shell</title>
<path fill="none" stroke="#00c853" d="M703.11,-190.49C702.16,-172.16 700.63,-142.72 699.49,-120.79"/>
<polygon fill="#00c853" stroke="#00c853" points="703,-120.81 698.99,-111.01 696.01,-121.18 703,-120.81"/>
<text xml:space="preserve" text-anchor="middle" x="733.21" y="-155.2" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#8892a8">overridden by</text>
</g>
<!-- shell&#45;&gt;cluster -->
<g id="edge11" class="edge">
<title>shell&#45;&gt;cluster</title>
<path fill="none" stroke="#4a5568" stroke-dasharray="5,2" d="M637.29,-72.6C627.83,-69.99 618.17,-67.38 609,-65 562.72,-52.99 509.89,-40.48 471.22,-31.54"/>
<polygon fill="#4a5568" stroke="#4a5568" points="472.2,-28.18 461.67,-29.35 470.63,-35 472.2,-28.18"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,56 @@
// TODO: PLACEHOLDER — replace with the real estate topology.
//
// This is where the extracted platform diagrams land. The shape below is
// illustrative only: it shows how a mocked dependency, a real service and a
// remote system are meant to sit together, not what the system actually is.
//
// The intended end state is that this file stops being hand-written and is
// generated from the running cluster, so the diagram becomes a report of what
// exists rather than a drawing of what was once intended.
digraph estate {
rankdir=LR
bgcolor="#0a0e17"
fontname="Helvetica"
node [fontname="Helvetica" fontsize=11 style=filled color="#1e2a4a" fontcolor="#e8eaf0" shape=box]
edge [fontname="Helvetica" fontsize=9 fontcolor="#8892a8" color="#4a5568"]
label="Estate topology — PLACEHOLDER"
labelloc=t
fontsize=16
fontcolor="#ffc107"
subgraph cluster_new {
label="New"
style=dashed
color="#1e2a4a"
fontcolor="#8892a8"
api [label="service under work\n(real: built and hot-reloaded)" fillcolor="#1a3a1a" fontcolor="#00c853"]
}
subgraph cluster_core {
label="Core (mocked)"
style=dashed
color="#1e2a4a"
fontcolor="#8892a8"
svc_a [label="upstream service\n(mock: canned responses)" fillcolor="#121829"]
db [label="datastore\n(mock)" fillcolor="#121829" shape=cylinder]
}
subgraph cluster_legacy {
label="Legacy estate (mocked)"
style=dashed
color="#1e2a4a"
fontcolor="#8892a8"
batch [label="batch drop\n(mock: writes files on a timer)" fillcolor="#121829"]
}
remote [label="external system\n(remote: ExternalName,\nreachable only from a VDI)" fillcolor="#3a1a1a" fontcolor="#ffc107" shape=octagon]
api -> svc_a [label="HTTP"]
api -> db [label="query"]
api -> batch [label="file handoff" style=dashed]
api -> remote [label="only when reachable" style=dashed color="#ffc107"]
}

View File

@@ -0,0 +1,94 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 14.1.2 (0)
-->
<!-- Title: estate Pages: 1 -->
<svg width="579pt" height="362pt"
viewBox="0.00 0.00 579.00 362.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 357.62)">
<title>estate</title>
<polygon fill="#0a0e17" stroke="none" points="-4,4 -4,-357.62 575.41,-357.62 575.41,4 -4,4"/>
<text xml:space="preserve" text-anchor="middle" x="285.7" y="-334.42" font-family="Helvetica,sans-Serif" font-size="16.00" fill="#ffc107">Estate topology — PLACEHOLDER</text>
<g id="clust1" class="cluster">
<title>cluster_new</title>
<polygon fill="#0a0e17" stroke="#1e2a4a" stroke-dasharray="5,2" points="8,-130.12 8,-210.12 199,-210.12 199,-130.12 8,-130.12"/>
<text xml:space="preserve" text-anchor="middle" x="103.5" y="-190.92" font-family="Helvetica,sans-Serif" font-size="16.00" fill="#8892a8">New</text>
</g>
<g id="clust2" class="cluster">
<title>cluster_core</title>
<polygon fill="#0a0e17" stroke="#1e2a4a" stroke-dasharray="5,2" points="357.33,-172.12 357.33,-318.12 534.83,-318.12 534.83,-172.12 357.33,-172.12"/>
<text xml:space="preserve" text-anchor="middle" x="446.08" y="-298.92" font-family="Helvetica,sans-Serif" font-size="16.00" fill="#8892a8">Core (mocked)</text>
</g>
<g id="clust3" class="cluster">
<title>cluster_legacy</title>
<polygon fill="#0a0e17" stroke="#1e2a4a" stroke-dasharray="5,2" points="341.83,-84.12 341.83,-164.12 551.33,-164.12 551.33,-84.12 341.83,-84.12"/>
<text xml:space="preserve" text-anchor="middle" x="446.58" y="-144.92" font-family="Helvetica,sans-Serif" font-size="16.00" fill="#8892a8">Legacy estate (mocked)</text>
</g>
<!-- api -->
<g id="node1" class="node">
<title>api</title>
<polygon fill="#1a3a1a" stroke="#1e2a4a" points="191,-174.12 16,-174.12 16,-138.12 191,-138.12 191,-174.12"/>
<text xml:space="preserve" text-anchor="middle" x="103.5" y="-159.17" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#00c853">service under work</text>
<text xml:space="preserve" text-anchor="middle" x="103.5" y="-145.67" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#00c853">(real: built and hot&#45;reloaded)</text>
</g>
<!-- svc_a -->
<g id="node2" class="node">
<title>svc_a</title>
<polygon fill="#121829" stroke="#1e2a4a" points="526.83,-282.12 365.33,-282.12 365.33,-246.12 526.83,-246.12 526.83,-282.12"/>
<text xml:space="preserve" text-anchor="middle" x="446.08" y="-267.17" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#e8eaf0">upstream service</text>
<text xml:space="preserve" text-anchor="middle" x="446.08" y="-253.67" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#e8eaf0">(mock: canned responses)</text>
</g>
<!-- api&#45;&gt;svc_a -->
<g id="edge1" class="edge">
<title>api&#45;&gt;svc_a</title>
<path fill="none" stroke="#4a5568" d="M149.07,-174.56C167.54,-182.07 189.23,-190.7 209,-198.12 258.25,-216.6 270.12,-222.85 320.75,-237.12 331.43,-240.13 342.71,-243 353.94,-245.67"/>
<polygon fill="#4a5568" stroke="#4a5568" points="353.12,-249.07 363.66,-247.92 354.71,-242.25 353.12,-249.07"/>
<text xml:space="preserve" text-anchor="middle" x="255.88" y="-234.24" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#8892a8">HTTP</text>
</g>
<!-- db -->
<g id="node3" class="node">
<title>db</title>
<path fill="#121829" stroke="#1e2a4a" d="M480.7,-223.81C480.7,-226.22 465.18,-228.18 446.08,-228.18 426.97,-228.18 411.45,-226.22 411.45,-223.81 411.45,-223.81 411.45,-184.43 411.45,-184.43 411.45,-182.02 426.97,-180.06 446.08,-180.06 465.18,-180.06 480.7,-182.02 480.7,-184.43 480.7,-184.43 480.7,-223.81 480.7,-223.81"/>
<path fill="none" stroke="#1e2a4a" d="M480.7,-223.81C480.7,-221.39 465.18,-219.43 446.08,-219.43 426.97,-219.43 411.45,-221.39 411.45,-223.81"/>
<text xml:space="preserve" text-anchor="middle" x="446.08" y="-207.17" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#e8eaf0">datastore</text>
<text xml:space="preserve" text-anchor="middle" x="446.08" y="-193.67" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#e8eaf0">(mock)</text>
</g>
<!-- api&#45;&gt;db -->
<g id="edge2" class="edge">
<title>api&#45;&gt;db</title>
<path fill="none" stroke="#4a5568" d="M191.32,-168.36C257.83,-177.73 346.91,-190.28 399.91,-197.75"/>
<polygon fill="#4a5568" stroke="#4a5568" points="399.4,-201.22 409.79,-199.15 400.38,-194.29 399.4,-201.22"/>
<text xml:space="preserve" text-anchor="middle" x="255.88" y="-185.69" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#8892a8">query</text>
</g>
<!-- batch -->
<g id="node4" class="node">
<title>batch</title>
<polygon fill="#121829" stroke="#1e2a4a" points="537.33,-128.12 354.83,-128.12 354.83,-92.12 537.33,-92.12 537.33,-128.12"/>
<text xml:space="preserve" text-anchor="middle" x="446.08" y="-113.17" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#e8eaf0">batch drop</text>
<text xml:space="preserve" text-anchor="middle" x="446.08" y="-99.67" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#e8eaf0">(mock: writes files on a timer)</text>
</g>
<!-- api&#45;&gt;batch -->
<g id="edge3" class="edge">
<title>api&#45;&gt;batch</title>
<path fill="none" stroke="#4a5568" stroke-dasharray="5,2" d="M191.32,-144.39C237.67,-138.13 294.98,-130.39 343.4,-123.85"/>
<polygon fill="#4a5568" stroke="#4a5568" points="343.61,-127.36 353.05,-122.55 342.67,-120.42 343.61,-127.36"/>
<text xml:space="preserve" text-anchor="middle" x="255.88" y="-143.94" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#8892a8">file handoff</text>
</g>
<!-- remote -->
<g id="node5" class="node">
<title>remote</title>
<polygon fill="#3a1a1a" stroke="#1e2a4a" points="571.41,-21.74 571.41,-52.5 497.99,-74.24 394.17,-74.24 320.75,-52.5 320.75,-21.74 394.17,0 497.99,0 571.41,-21.74"/>
<text xml:space="preserve" text-anchor="middle" x="446.08" y="-46.92" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#ffc107">external system</text>
<text xml:space="preserve" text-anchor="middle" x="446.08" y="-33.42" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#ffc107">(remote: ExternalName,</text>
<text xml:space="preserve" text-anchor="middle" x="446.08" y="-19.92" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#ffc107">reachable only from a VDI)</text>
</g>
<!-- api&#45;&gt;remote -->
<g id="edge4" class="edge">
<title>api&#45;&gt;remote</title>
<path fill="none" stroke="#ffc107" stroke-dasharray="5,2" d="M149.55,-137.68C167.9,-130.34 189.37,-121.97 209,-114.87 254.45,-98.43 305.32,-81.49 348.17,-67.64"/>
<polygon fill="#ffc107" stroke="#ffc107" points="349,-71.05 357.44,-64.65 346.85,-64.39 349,-71.05"/>
<text xml:space="preserve" text-anchor="middle" x="255.88" y="-117.57" font-family="Helvetica,sans-Serif" font-size="9.00" fill="#8892a8">only when reachable</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.9 KiB

588
rig/docs/index.html Normal file
View File

@@ -0,0 +1,588 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>rig — local environment installer</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap');
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: #0a0e17;
color: #e8eaf0;
font-family: 'Inter', sans-serif;
line-height: 1.6;
height: 100vh;
overflow: hidden;
display: flex;
flex-direction: column;
}
header {
padding: 16px 24px;
border-bottom: 1px solid #1e2a4a;
display: flex;
align-items: baseline;
gap: 16px;
flex-shrink: 0;
}
header h1 {
font-family: 'JetBrains Mono', monospace;
font-size: 22px;
font-weight: 600;
letter-spacing: 3px;
color: #0066ff;
}
header .subtitle {
font-size: 13px;
color: #4a5568;
letter-spacing: 1px;
text-transform: uppercase;
}
.layout { display: flex; flex: 1; min-height: 0; }
nav {
display: flex;
flex-direction: column;
width: 200px;
flex-shrink: 0;
background: #121829;
border-right: 1px solid #1e2a4a;
padding: 8px 0;
overflow-y: auto;
}
nav a {
padding: 10px 20px;
font-family: 'JetBrains Mono', monospace;
font-size: 12px;
color: #8892a8;
text-decoration: none;
border-left: 2px solid transparent;
transition: all 0.15s;
cursor: pointer;
}
nav a:hover { color: #e8eaf0; background: #1a2340; }
nav a.active { color: #0066ff; border-left-color: #0066ff; background: #0d1a33; }
main { flex: 1; overflow: auto; padding: 32px 48px; }
.section { display: none; animation: fadeIn 0.2s ease; }
.section.active { display: block; }
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
.section h2 {
font-family: 'JetBrains Mono', monospace;
font-size: 15px;
font-weight: 500;
color: #8892a8;
margin-bottom: 8px;
letter-spacing: 1px;
text-transform: uppercase;
}
.section > p.lede {
font-size: 13px;
color: #4a5568;
margin-bottom: 24px;
max-width: 800px;
}
.prose { max-width: 820px; }
.prose p { font-size: 14px; color: #b4bccf; line-height: 1.7; margin-bottom: 14px; }
.prose p b { color: #e8eaf0; }
.prose ul { margin: 0 0 16px 20px; }
.prose li { font-size: 14px; color: #b4bccf; margin-bottom: 6px; }
.prose h3 {
font-family: 'JetBrains Mono', monospace;
font-size: 13px;
text-transform: uppercase;
color: #e8eaf0;
margin: 32px 0 10px;
letter-spacing: 1px;
}
.prose code, pre code {
font-family: 'JetBrains Mono', monospace;
font-size: 12px;
color: #7ab0ff;
background: #121829;
padding: 1px 5px;
border-radius: 3px;
}
pre {
background: #121829;
border: 1px solid #1e2a4a;
padding: 16px;
overflow: auto;
margin-bottom: 16px;
}
pre code { background: none; padding: 0; }
pre .c { color: #4a5568; }
pre .k { color: #0066ff; }
.graph-container { margin: 16px 0; }
.graph-container img {
display: block;
max-width: 100%;
background: #0a0e17;
border: 1px solid #1e2a4a;
padding: 12px;
}
dl {
display: grid;
grid-template-columns: max-content 1fr;
gap: 10px 24px;
margin: 16px 0;
max-width: 820px;
}
dt {
font-family: 'JetBrains Mono', monospace;
color: #0066ff;
font-size: 13px;
padding-top: 2px;
}
dd { font-size: 14px; color: #b4bccf; line-height: 1.6; }
table { border-collapse: collapse; margin: 16px 0; max-width: 820px; }
th, td {
text-align: left;
padding: 7px 16px 7px 0;
font-size: 13px;
border-bottom: 1px solid #1e2a4a;
color: #b4bccf;
}
th {
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
text-transform: uppercase;
color: #8892a8;
letter-spacing: 1px;
}
td code { white-space: nowrap; }
.note {
border-left: 2px solid #ffc107;
background: #17130a;
padding: 12px 16px;
margin: 16px 0;
max-width: 820px;
}
.note p { margin: 0; font-size: 13px; color: #b4bccf; }
.note b { color: #ffc107; }
.menu-toggle {
display: none;
background: transparent;
border: 1px solid #1e2a4a;
color: #8892a8;
padding: 6px 10px;
font-size: 14px;
cursor: pointer;
line-height: 1;
margin-left: auto;
}
.menu-toggle:hover { background: #1a2340; }
.nav-backdrop {
display: none;
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 10;
}
.layout.nav-open .nav-backdrop { display: block; }
@media (max-width: 720px) {
header { padding: 10px 12px; gap: 8px; }
header h1 { font-size: 16px; letter-spacing: 1px; }
header .subtitle { display: none; }
.menu-toggle { display: inline-block; }
.layout { position: relative; }
nav {
position: absolute; left: 0; top: 0; bottom: 0;
width: 200px; z-index: 20;
transform: translateX(-100%);
transition: transform 0.2s ease;
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.5);
}
.layout.nav-open nav { transform: translateX(0); }
main { padding: 16px; }
.section h2 { font-size: 13px; }
.prose p, .prose li { font-size: 13px; }
}
</style>
</head>
<body>
<header>
<h1>RIG</h1>
<span class="subtitle">local environment installer</span>
<button class="menu-toggle">&#9776;</button>
</header>
<div class="layout">
<div class="nav-backdrop"></div>
<nav>
<a href="#start">Start here</a>
<a href="#steps">The steps</a>
<a href="#install">Installation</a>
<a href="#environments">Environments</a>
<a href="#profiles">Profiles</a>
<a href="#registry">Registry</a>
<a href="#architecture">Architecture</a>
<a href="#troubleshooting">Troubleshooting</a>
</nav>
<main>
<section class="section" id="start">
<h2>Start here</h2>
<p class="lede">A runnable local model of a large, regulated estate — legacy and new side by side.</p>
<div class="prose">
<p>rig builds a disposable Kubernetes environment on your machine so you can
explore how a system fits together without needing access to any of it. Its
job is <b>onboarding and exploration</b>, not a production replica.</p>
<p>Most services in it are deliberately <b>not real</b>. What has to be faithful
is the topology — the names, the ports, the dependency order, who can reach whom,
and how it fails. The workloads themselves are noise. This is what makes the
whole estate fit on a laptop: a real 20-service platform will not fit even once
on 14&nbsp;GB, but mocks are about 30&nbsp;MB each, so three faithful copies do.</p>
<h3>The only prerequisite</h3>
<p><b>Docker.</b> No curl, no jq, no python, no apt repositories to configure.</p>
<pre><code><span class="c"># then, in the environment directory:</span>
make station <span class="c"># is this workstation ready? reports, never fixes</span>
make deps <span class="c"># install the pinned toolchain</span>
make cluster up <span class="c"># build the cluster for the active profile</span>
</code></pre>
<p>Read <code>make station</code> before <code>make deps</code>. It never changes
anything — it prints what it found and, at the end, the steps it cannot perform
for you.</p>
</div>
</section>
<section class="section" id="steps">
<h2>The steps</h2>
<p class="lede">Start to finish, in order, with what each one actually does.</p>
<div class="prose">
<h3>1 &middot; make station</h3>
<p>Asks whether this workstation is ready. It <b>changes nothing</b> — it
reports what it found and, at the end, the things only a human can do
(anything needing <code>sudo</code>, or a Windows-side restart). Read it
before installing anything; it is faster than discovering the same problems
one failure at a time.</p>
<pre><code>make station</code></pre>
<h3>2 &middot; make setup</h3>
<p>Does the preparation that can be automated: installs the pinned
toolchain if it is missing, checks PATH, Docker, and this environment's
ports. Every step is independently checked, so running it twice is safe and
running it half-configured finishes the job.</p>
<p>It <b>does not stop at the first failure</b>. A setup script that dies at
step two hides that steps four and five would also have failed, and on an
unfamiliar machine the complete list is the point. The tail of the output is
a to-do list of only what is outstanding.</p>
<pre><code>make setup <span class="c"># host + toolchain</span>
make setup --share-docker <span class="c"># ...and offer this machine's Docker to other distros</span>
</code></pre>
<h3>3 &middot; make cluster up</h3>
<p>Builds the cluster for the active profile. It prints what the profile
locks in <i>before</i> spending the time, because the CNI and the audit
policy are fixed at creation and cannot be changed afterwards.</p>
<p>Re-running is safe and, more importantly, <b>convergent</b>: if a first
attempt was interrupted before the CNI was installed, running it again
finishes the job rather than reporting "already exists" and leaving every
node permanently NotReady.</p>
<pre><code>make cluster up <span class="c"># default profile</span>
make cluster up PROFILE=client <span class="c"># three nodes, audit on, cached registry</span>
make cluster reset <span class="c"># destroy and rebuild — the only way to change CNI or audit</span>
</code></pre>
<h3>4 &middot; make docs</h3>
<p>Serves this page from a throwaway container. Works with no cluster and
no toolchain, which is deliberate: these pages are the instructions for
building everything else, so they cannot depend on it.</p>
<pre><code>make docs</code></pre>
<h3>Checking on things</h3>
<dl>
<dt>make cluster list</dt><dd>Every cluster on the machine, its memory cost and its port block. The usual reason a new one will not start is an old one you forgot about; <code>make cluster free</code> frees them without deleting.</dd>
<dt>make ports</dt><dd>This environment's port block, and whether each is derived or overridden.</dd>
<dt>make registry</dt><dd>Which of the four registry modes is active, and where it points.</dd>
<dt>make dockerhost</dt><dd>Which WSL distro owns Docker and what this one is using.</dd>
</dl>
<h3>Running more than one</h3>
<p>Copy the directory, rename it, and repeat from step 2. Cluster name,
context, image tags and the port block all follow the directory name, so
the second environment collides with nothing and neither one's teardown can
reach the other.</p>
<pre><code>cp -r rig ../platform-v2 &amp;&amp; cd ../platform-v2
make setup &amp;&amp; make cluster up
</code></pre>
</div>
</section>
<section class="section" id="install">
<h2>Installation</h2>
<p class="lede">A container installs onto the host and then gets out of the way.</p>
<div class="graph-container">
<a href="viewer.html?src=graphs/01-install.svg"><img src="graphs/01-install.svg" alt="Installation flow"></a>
</div>
<div class="prose">
<p>The installer is a container, not a shell script, for a specific reason: a
stock slim Debian has no <code>curl</code>, no <code>wget</code>, no
<code>jq</code>, no <code>python3</code> and <b>no CA bundle</b>. A shell
installer could not make a verified HTTPS request, let alone check one. The
container carries its own toolchain, so the host needs nothing but Docker.</p>
<p>The cluster never runs inside that container. Everything it installs —
kind, kubectl, tilt, jq — runs natively afterwards, so nothing pays a
container tax during daily work.</p>
<h3>Pinned and verified</h3>
<p>Every tool is a single binary fetched at a pinned version and checked
against a published SHA256. Node images are pinned <b>by digest</b>, so
upgrading kind cannot silently move your Kubernetes version.</p>
<h3>Not every machine should get cluster tooling</h3>
<p>A managed or corporate-issued machine — the kind that holds the access
you cannot get anywhere else — is not somewhere to install development
tools by default. So the toolchain comes in two tiers:</p>
<table>
<tr><th>tier</th><th>installs</th><th>for</th></tr>
<tr><td><code>core</code></td><td>kubectl, jq</td><td>talk to a cluster someone else runs</td></tr>
<tr><td><code>dev</code></td><td>+ kind, tilt</td><td>build clusters and hot-reload into them</td></tr>
</table>
<pre><code>make deps core <span class="c"># kubectl and jq only — nothing that creates a cluster</span>
make deps <span class="c"># dev, the default</span>
make setup core <span class="c"># same distinction, via setup</span>
</code></pre>
<p>Testing <i>in situ</i> on a managed machine is still possible — install
the <code>dev</code> tier deliberately when you need it. The point is that
it should be a decision rather than a side effect of running setup.</p>
<p>The documentation itself needs neither tier: <code>make docs</code>
wants only Docker.</p>
<h3>Air-gapped</h3>
<pre><code>make wizard full <span class="c"># bakes every binary into the image</span>
docker save …-wizard:full | gzip &gt; rig.tgz
<span class="c"># carry that one file in, then:</span>
docker load &lt; rig.tgz &amp;&amp; make cluster up PROFILE=offline
</code></pre>
</div>
</section>
<section class="section" id="environments">
<h2>Environments</h2>
<p class="lede">One directory is one environment. Copy it, rename it, run it.</p>
<div class="graph-container">
<a href="viewer.html?src=graphs/02-environment.svg"><img src="graphs/02-environment.svg" alt="Environment derivation"></a>
</div>
<div class="prose">
<p>Running several versions of a system at once means several clusters on one
machine, not several machines. Everything that could collide is derived from
the directory name:</p>
<dl>
<dt>cluster + context</dt><dd><code>acmebank/</code> builds <code>acmebank</code> on <code>kind-acmebank</code>.</dd>
<dt>port block</dt><dd>Ten ports from a hash of the name, in the 20000+ range — clear of 80, 443, 3000, 5432, 8000 and 8080.</dd>
<dt>registry + images</dt><dd>Named after the environment, so two copies never share one.</dd>
</dl>
<p>Two copies therefore never collide, and neither one's
<code>make cluster down</code> can touch the other. <code>make ports</code>
shows the block; <code>make ports persist</code> freezes it into
<code>ctrl/.env</code> if you want it fixed rather than derived.</p>
<h3>Configuration layers</h3>
<p>Weakest first, later wins: pinned versions → the profile →
<code>ctrl/.env</code> → the environment. So
<code>make cluster up PROFILE=client</code> always beats every file.</p>
</div>
</section>
<section class="section" id="profiles">
<h2>Profiles</h2>
<p class="lede">Cluster shape is declared, not baked in.</p>
<div class="prose">
<table>
<tr><th>profile</th><th>nodes</th><th>audit</th><th>registry</th><th>for</th></tr>
<tr><td><code>minimal</code></td><td>1</td><td>off</td><td>none</td><td>first boot; assumes nothing</td></tr>
<tr><td><code>client</code></td><td>3</td><td>on</td><td>mirror</td><td>the regulated shape</td></tr>
<tr><td><code>offline</code></td><td>1</td><td>on</td><td>local</td><td>air-gapped</td></tr>
</table>
<div class="note"><p><b>The audit policy cannot be changed later.</b> It is an
apiserver flag, fixed when the cluster is created. <code>cluster up</code>
prints what a profile locks in before spending the time, and
<code>make cluster reset</code> is the way out.</p></div>
<h3>LoadBalancer services</h3>
<p>Real manifests use <code>type: LoadBalancer</code>, because a real
cluster has one. On a bare local cluster those Services sit at
<code>EXTERNAL-IP &lt;pending&gt;</code> forever, with no error anywhere —
the deployment looks healthy and simply is not reachable.</p>
<p>The <code>metallb</code> addon fixes that, so the same manifests work
here as upstream and nothing has to be rewritten to NodePort. Its address
pool is derived from the cluster's Docker network at install time rather
than hardcoded, because Docker picks that subnet and it differs between
machines.</p>
<div class="note"><p><b>Where those addresses are reachable from.</b> The
pool lives on the Docker bridge, so LoadBalancer IPs work from the Linux
side — including from inside WSL. A browser on Windows has no route to
them. Use the ingress host ports for anything you need to open in a
browser.</p></div>
<h3>Networking</h3>
<p>The cluster uses kind's built-in networking, which <b>does</b> enforce
standard NetworkPolicy — verified against a no-policy control, not assumed.
The widely repeated claim that it accepts policies and silently ignores
them is out of date.</p>
<p>A pluggable CNI was tried and removed: it only added
GlobalNetworkPolicy, policy tiers and egress-CIDR rules, none of which are
needed yet, in exchange for a slower boot and one more thing that has to be
right at creation time. Worth revisiting only when a policy the built-in
cannot express actually comes up.</p>
<h3>Memory</h3>
<p>Every cluster is a running container tree whether you are using it or not.
<code>make cluster list</code> shows what exists and what it costs;
<code>make cluster free</code> stops the others without deleting them.</p>
</div>
</section>
<section class="section" id="registry">
<h2>Registry</h2>
<p class="lede">Local, cached, or straight to the corporate registry.</p>
<div class="prose">
<table>
<tr><th>mode</th><th>what it does</th></tr>
<tr><td><code>none</code></td><td>images are built straight into the node</td></tr>
<tr><td><code>local</code></td><td>a registry container wired into the cluster</td></tr>
<tr><td><code>mirror</code></td><td>that container as a <b>pull-through cache</b> of the corporate registry</td></tr>
<tr><td><code>remote</code></td><td>no local container; pull direct with an imagePullSecret</td></tr>
</table>
<p><code>mirror</code> is what a locked-down network actually looks like:
images originate from the corporate registry, you do not hammer it, and you
keep working when the connection drops.</p>
<div class="note"><p><b>The corporate CA will bite you.</b> A corporate
registry is usually behind an internal CA, and trust has to reach
<b>three</b> places: the host Docker daemon, every cluster node's containerd
(nodes do <i>not</i> inherit host trust), and any in-cluster client. Set
<code>REGISTRY_CA_FILE</code> and <code>make station</code> reports which is
still missing. The symptom otherwise is an opaque
<code>x509: certificate signed by unknown authority</code>.</p></div>
<p>Reachability also depends on where you are: if the registry is only
routable from a managed workspace, <code>mirror</code> and <code>remote</code>
will not resolve from a laptop at all. That is what <code>local</code> and
<code>offline</code> are for.</p>
</div>
</section>
<section class="section" id="architecture">
<h2>Architecture</h2>
<p class="lede">The estate being modelled.</p>
<div class="note"><p><b>TODO — placeholder.</b> The diagram below is
illustrative only: it shows how a mocked dependency, a service under active
work, and an unreachable remote system sit together. It is not the real
topology. Replace <code>docs/graphs/03-architecture.dot</code> with the
extracted platform diagrams, then run <code>make docs graphs</code>.</p></div>
<div class="graph-container">
<a href="viewer.html?src=graphs/03-architecture.svg"><img src="graphs/03-architecture.svg" alt="Estate topology (placeholder)"></a>
</div>
<div class="prose">
<p>Each component is one of three things, and switching between them should be
a one-line change rather than a rewrite:</p>
<dl>
<dt>real</dt><dd>Built from source and hot-reloaded. The thing you are actually working on — usually exactly one.</dd>
<dt>mock</dt><dd>A generic stub with canned responses. Everything you do not care about today.</dd>
<dt>remote</dt><dd>No pod at all: a Service of type ExternalName pointing at the real system. In-cluster DNS resolves identically, so callers never change.</dd>
</dl>
<p>The intended end state is that this diagram is <b>generated from the
running cluster</b> rather than drawn by hand — so it becomes a report of
what exists instead of a picture of what was once intended.</p>
</div>
</section>
<section class="section" id="troubleshooting">
<h2>Troubleshooting</h2>
<p class="lede">The failures that are hard to diagnose from their symptoms.</p>
<div class="prose">
<h3>Tilt stops noticing file changes</h3>
<p>Almost always <code>inotify</code> limits, and it fails <i>silently</i>
nothing errors, changes just stop being picked up. Defaults on WSL are far too
low. <code>make station</code> reports it and prints the fix.</p>
<h3>Cluster creation dies halfway with a port error</h3>
<p>Docker reports <code>failed to bind host port … address already in use</code>
partway through creating the cluster. Run <code>make station</code> first — it
checks every port in this environment's block before anything is built.</p>
<h3>Every node stays NotReady</h3>
<p>Usually a cluster created with the default CNI disabled but the real CNI
never installed — typically an interrupted first run. Just run
<code>make cluster up</code> again: it converges rather than exiting early, and
will finish the missing steps.</p>
<h3>x509: certificate signed by unknown authority</h3>
<p>Corporate CA trust has not reached one of the three places it needs to be.
See <a href="#registry">Registry</a>.</p>
<h3>kubectl says the context does not exist</h3>
<p>The cluster can exist while its context does not — a reset or a switched
<code>KUBECONFIG</code> loses it. <code>make cluster up</code> detects this and
re-exports the context.</p>
</div>
</section>
</main>
</div>
<script>
(function () {
var layout = document.querySelector('.layout');
var main = document.querySelector('main');
function syncActive() {
var hash = location.hash.slice(1) || 'start';
document.querySelectorAll('.section').forEach(function (s) { s.classList.remove('active'); });
document.querySelectorAll('nav a').forEach(function (a) { a.classList.remove('active'); });
var section = document.getElementById(hash);
if (section) section.classList.add('active');
var link = document.querySelector('nav a[href="#' + hash + '"]');
if (link) link.classList.add('active');
if (main) main.scrollTop = 0;
layout.classList.remove('nav-open');
}
window.addEventListener('hashchange', syncActive);
window.addEventListener('DOMContentLoaded', syncActive);
syncActive();
document.addEventListener('click', function (e) {
if (e.target.closest('.menu-toggle') || e.target.closest('.nav-backdrop')) {
layout.classList.toggle('nav-open');
}
});
})();
</script>
</body>
</html>

101
rig/docs/viewer.html Normal file
View File

@@ -0,0 +1,101 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Graph Viewer</title>
<style>
* { margin: 0; padding: 0; }
body {
background: #0a0e17;
overflow: hidden;
width: 100vw;
height: 100vh;
}
#container {
width: 100vw;
height: 100vh;
overflow: hidden;
cursor: grab;
}
#container.dragging { cursor: grabbing; }
img {
transform-origin: 0 0;
user-select: none;
-webkit-user-drag: none;
}
</style>
</head>
<body>
<div id="container">
<img id="img" />
</div>
<script>
var src = new URLSearchParams(location.search).get('src');
var img = document.getElementById('img');
var container = document.getElementById('container');
img.src = src;
var scale = 1;
var x = 0, y = 0;
var dragging = false;
var startX, startY, startPanX, startPanY;
function apply() {
img.style.transform = 'translate(' + x + 'px,' + y + 'px) scale(' + scale + ')';
}
// Fit to screen on load
img.onload = function() {
var sw = window.innerWidth / img.naturalWidth;
var sh = window.innerHeight / img.naturalHeight;
scale = Math.min(sw, sh) * 0.95;
x = (window.innerWidth - img.naturalWidth * scale) / 2;
y = (window.innerHeight - img.naturalHeight * scale) / 2;
apply();
};
// Wheel zoom toward cursor
container.addEventListener('wheel', function(e) {
e.preventDefault();
var factor = e.deltaY < 0 ? 1.12 : 0.89;
var rect = container.getBoundingClientRect();
var mx = e.clientX - rect.left;
var my = e.clientY - rect.top;
x = mx - (mx - x) * factor;
y = my - (my - y) * factor;
scale *= factor;
apply();
}, { passive: false });
// Pan
container.addEventListener('mousedown', function(e) {
if (e.button !== 0) return;
dragging = true;
startX = e.clientX;
startY = e.clientY;
startPanX = x;
startPanY = y;
container.classList.add('dragging');
e.preventDefault();
});
window.addEventListener('mousemove', function(e) {
if (!dragging) return;
x = startPanX + (e.clientX - startX);
y = startPanY + (e.clientY - startY);
apply();
});
window.addEventListener('mouseup', function() {
dragging = false;
container.classList.remove('dragging');
});
// Double-click to reset
container.addEventListener('dblclick', function() {
img.onload();
});
</script>
</body>
</html>