Generic deployment/build orchestration — remove remaining minikube special-casing

Goal

docs/plans/done/minikube-provisioning-plugin.md (commit 407c30f, "Moved minikube support to separate plugin") moved the backend Python pieces of minikube support out of the main repo — backend/services/cluster_providers/minikube.py, backend/services/registry_protocols/docker_v2.py, backend/services/minio_service.py, backend/services/storage_protocols/minio.py, dev/setup-minikube.sh/setup-minio.sh/setup-registry.sh — into plugins/ymerflow-minikube. It did not finish the job: orchestration shell scripts (docker/build.sh, prod/runall-minikube.sh, dev/runall.sh, dev/prepull-images.sh, dev/cleanup-*.sh, scripts/install-deps-on-debian.sh) and even one backend router (backend/routers/admin.py's self-service cluster-registration callback) still hardcode minikube/docker-v2/MinIO assumptions directly.

This plan finishes that migration, fully: no minikube-specific behavior anywhere in the main repo that isn't reached through the ClusterProvider/RegistryProtocolHandler/ StorageProtocolHandler hook system (backend/services/{cluster_providers,registry_protocols, storage_protocols}/__init__.py). Every script in dev/, prod/, docker/ should call only generic Python entry points that dispatch through those hooks; every backend code path that branches on cluster type should do so via a provider flag/method, never a literal "minikube" string. Anything that only makes sense for a self-hosted Minikube-on-Docker setup moves into plugins/ymerflow-minikube. A hypothetical GKE-only deployment should need nothing from dev/, prod/, docker/, or scripts/ that assumes Minikube exists.

Explicitly not a goal: build/deploy performance. That's a separate, deprioritized concern — don't let it creep into this plan's scope or reviews.

Background — current state

(Confirmed by reading the actual code, not assumed. A full repo-wide grep for minikube outside plugins/, node_modules/, env/, .git/, docs/ was run to find everything below — see Verification for the exact command.)

Already fully generic — do not touch

Hardcoded to minikube/docker-v2/MinIO — in scope for this plan

Build/push: - docker/build.sh: gates on minikube status, does eval $(minikube docker-env) to build and docker push against minikube's internal Docker daemon. The plain docker push only works because minikube's internal dockerd was started with --insecure-registry for the self-signed registry cert — DockerV2ProtocolHandler.configure_push_auth() (plugins/ymerflow-minikube/minikube_plugin/registry_protocol.py:232) only does docker login (credentials), not TLS trust, and its own docstring admits it "preserves today's docker/build.sh behavior" — this was always a workaround, never a designed contract. - prod/runall-minikube.sh Step 5: a hand-rolled push_image() bash function (docker savecrane push --insecure) solving the same TLS-trust problem for the host daemon, duplicated instead of living on RegistryProtocolHandler.

Config/secrets: - prod/runall-minikube.sh Step 6: the nagelfluh-backend-config ConfigMap hardcodes STORAGE_PROTOCOL: "s3", STORAGE_ENDPOINT: "https://minio.minio.svc.cluster.local:9000", MINIO_ROOT_USER. Grepped backend/config.py and backend/services/storage_protocols/*.py — zero hits. Likely dead weight from before STORAGE_CONFIG_JSON existed (verify and drop). - prod/runall-minikube.sh Step 6c: builds the deploy-Job's own image-pull secret via kubectl create secret docker-registry --docker-username=... --docker-password=... — assumes docker-v2's basic-auth shape directly instead of RegistryProtocolHandler.pull_credentials() (already resolved generically in Step 3's bootstrap-provision output).

Readiness/connectivity checks: - dev/runall.sh Step 6: hand-rolled "wait for registry Deployment, then curl its /v2/ endpoint in a retry loop" — duplicates DockerV2ProtocolHandler.test_connection(), and assumes a registry that's a k8s Deployment at all (meaningless for a managed registry like GAR).

Image pre-pull: - dev/prepull-images.sh + dev/images.env: minikube ssh -- docker pull for MINIO_IMAGE/REGISTRY_IMAGE. Both images are already independently declared as constants inside the plugin (storage_protocol.py:39, registry_protocol.py:54) — dev/images.env is a second, out-of-sync source of truth for the same values.

Teardown/cleanup: - dev/cleanup-minikube.sh: despite the name, its actual content (delete Kueue clusterqueue/resourceflavor, delete the nagelfluh-jobs namespace) is not minikube-specific at all — it's the teardown mirror of ensure_cluster_job_ready(). No generic teardown counterpart to that function exists yet. - dev/cleanup-all.sh: genuinely minikube-specific — gates on minikube status, hardcodes deleting the registry/minio namespaces by name, prints minikube stop/minikube delete hints. No generic teardown entry point exists to mirror nagelfluh-bootstrap-provision.

Host prep: - scripts/install-deps-on-debian.sh: unconditionally installs the minikube binary and docker.io, regardless of which cluster plugin (if any) is actually going to be used.

Self-service cluster registration (backend router): - backend/routers/admin.py, cluster_register_callback: hardcodes cluster_type="minikube" when lazily creating the pending Cluster row for an unrecognized registration token. Its own comment: "Only 'minikube' self-service-registers today; hardcoded here... cluster_type would need to travel with the callback (e.g. as a query param) to generalize this to a second self-service provider." The /static/assets/setup-minikube-remote.sh endpoint and dev/setup-minikube-remote.sh.in template are similarly named/scoped to a single provider. (admin_create_cluster/admin_test_cluster_connection elsewhere in the same file already dispatch via provider.self_service_registration/get_cluster_provider(cluster_type) — this is the one hardcoded exception, not a systemic problem in this file.)

Not touched by this plan (noted so reviewers don't expect it)

Design decisions (settled in discussion)

  1. push_image() becomes a method on RegistryProtocolHandler, with the crane-based save-then-push mechanism shared rather than reimplemented per protocol — either a concrete base-class method parameterized by an overridable "insecure/skip-verify" signal, or a shared helper function both protocol implementations call (pick whichever reads cleaner once written). DockerV2ProtocolHandler.push_image() supplies insecure=True; a future GAR handler would supply insecure=False or override entirely if crane's auth model doesn't fit Workload Identity.
  2. One generic build-and-push entry point, used identically for backend, frontend, and runner images. Replaces docker/build.sh's bespoke build+push and prod/runall-minikube.sh Step 2/5's bespoke build+push. Builds always run against the host's own Docker daemon — never minikube docker-env.
  3. dev/prepull-images.sh and dev/images.env are deleted. MinioProtocolHandler.bootstrap() and DockerV2ProtocolHandler.bootstrap() each pre-pull their own image as part of their own bootstrap() — no new hook needed.
  4. prod/runall-minikube.sh is renamed to prod/runall-production.sh. Update every reference.
  5. Self-service registration callback takes cluster_type as a query param (POST /admin/clusters/register-callback?cluster_type=X) instead of hardcoding "minikube". The per-provider setup-script template is rendered with that type already known, so it can post it back; the callback creates the pending row with whatever type is given.
  6. ClusterProvider.teardown() (new, optional hook) never runs minikube stop/minikube delete. It only removes the k8s-level resources this provider's bootstrap() created (namespaces, Kueue config, etc.). Stopping/deleting the VM itself stays a manual, explicit operation — printed as guidance, same as today, never automated. Lower blast radius than automating destruction of a local VM.
  7. dev/cleanup-minikube.sh is deleted outright. Its Kueue/jobs-namespace teardown logic becomes a shared, provider-agnostic helper (mirroring ensure_cluster_job_ready()), called by the new generic teardown entry point dev/cleanup-all.sh uses. No separate minikube-named script remains.
  8. scripts/install-deps-on-debian.sh drops minikube/docker.io entirely, keeping only genuinely generic host deps (python, node, kubectl, etc.). plugins/ymerflow-minikube ships its own install-deps script/doc for whoever chooses that plugin, invoked separately.

Phases

Phase 1 — push_image() on RegistryProtocolHandler

Phase 2 — one generic build-and-push entry point

Phase 3 — fold image pre-pull into protocol bootstrap()

Phase 4 — replace hand-rolled registry-wait/test logic

Phase 5 — drop hardcoded ConfigMap/Secret literals in prod/runall-production.sh

Phase 6 — rename

Phase 7 — generic teardown hooks

Phase 8 — generic cleanup scripts

Phase 9 — plugin-owned host prep

Phase 10 — generalize self-service cluster registration

Verification