deploy — the fleet, declared
Every service in this cluster is one values file:
deploy/<namespace>/<name>.yaml
That is the whole inventory. Adding a service is adding a file; there is no second list to keep in step with it.
Each file is values for one shared chart, pulled by version:
oci://oci.hanzo.ai/charts/app:0.1.0
The chart is referenced, never copied. A copy per repository is how two fleets end up with two definitions of the same thing and neither one is wrong.
helm template <name> oci://oci.hanzo.ai/charts/app --version 0.1.0 \
-n <namespace> -f deploy/<namespace>/<name>.yaml
These files were generated from what is RUNNING
Not from the manifests in k8s/. Those were applied by hand over a long period
and their currency is unknown — some are ahead of the cluster, some behind, some
describe things that were deleted. The running fleet is the only statement of
what is true, so it is what was read.
That makes k8s/ and deploy/ disagree on purpose. The disagreement is a drift
audit waiting to be done, not a bug to paper over.
Verifying, before changing anything
# regenerate from live
python3 <universe>/charts/app/hack/live2values.py --context <ctx> --out deploy
# render every file and diff it against the live object
python3 <universe>/charts/app/hack/diff-live.py --context <ctx> --values deploy
The gate grades what it finds:
- BREAKING — the apiserver would refuse the apply. Only a workload's
spec.selectorqualifies; it is immutable. - DRIFT — live differs from the file. Someone changed one of them.
- BENIGN — an intended consequence of moving to the chart.
Current state for this cluster: BREAKING = 0. Every workload here can be adopted in place.
selectorLabels — why most files have one
spec.selector cannot be changed after a workload is created. Most services here
select on app: <name>, not the chart's app.kubernetes.io/name + instance,
so each file records the selector its workload already carries. Without that the
chart would emit its own, and the apply would be refused.
It is the selector only — the full label set on metadata is still the chart's, so an adopted workload gains the standard labels for everything that reads them without selecting on them.
What is NOT here
Chain CRs (LuxChain, LuxNetwork, LuxIndexer, LuxGateway, LuxMPC) are
not services and have their own controllers. They stay in k8s/.
Secrets are not here and never will be. The chart has no way to carry a secret
value: kmsSecrets names a path in KMS and the operator syncs it. The
placeholder Secret manifests in k8s/ that say CHANGE_ME_VIA_KMS are the
older pattern and should become KMSSecret resources.
Deploying
There is no CD in this repository yet, which is why k8s/ drifted in the first
place. applicationset.yaml here is ready to apply once this cluster runs one.
It deliberately has no automated block: the chart adds labels to the pod
template, and a pod template change is a rollout, so switching it on would
restart the whole fleet at once. Services move over one at a time.
lux-system/s3 is NOT declared here, deliberately
There was a lux-system/s3.yaml, generated by hack/live2values.py from
Deployment/s3. Both halves of what it described were wrong:
Deployment/s3is a 0-replica husk. Nothing has run from it in 57 days.Service/s3selectsapp: seaweedfsand its single endpoint isseaweedfs-0, a StatefulSet. The generated file declaredselectorLabels: {app: s3}, so a sync would have repointed that Service at a selector matching zero pods and taken S3 down for every in-cluster consumer. It also declared an Ingress fors3.lux.network, a host with no working record today (curl 000).
So the file was deleted rather than corrected: correcting it means picking ONE
name for the thing. ghcr.io/hanzoai/s3 IS the SeaweedFS fork, so s3 is the
honest name and seaweedfs is the leftover — but renaming a StatefulSet is a
recreate against its bound volume, which is a sequenced change against live
storage and not a values edit. Until that lands, lux-system S3 is visibly
outside CD, which is the truthful state; a values file that claims otherwise is
worse than an absence.