Files
universe/scripts/migrate-luxnetwork-crds.sh
zeekay c7f64a18b3 phase-a: lux.network/v1alpha1 → lux.cloud/v1 migration scaffolding
Phase A status: BLOCKED on luxfi/operator v0.6.3 + v0.7.0 binaries
(see luxfi/operator d2ecca5 — duplicate Network reconciler
registration makes both crashloop at startup). Cluster rolled back
to v0.3.12 on lux.network/v1alpha1 reconcile, all 3 chains 5/5
healthy.

What this commit lays down for the next attempt (once a v0.6.4 or
v0.7.1 image ships with the operator fix):

- `scripts/migrate-luxnetwork-crds.sh <ctx>` — race-clean CR
  migration script. Walks lux.network/v1alpha1 LuxNetwork,
  LuxChain, LuxGateway, LuxIndexer, LuxExplorer, LuxMPC CRs,
  apiVersion-swaps each to lux.cloud/v1 (dropping the Lux
  prefix on every Kind except LuxNetwork which v0.6.x also
  calls LuxNetwork in the canonical group). Spec/status pass
  through opaque — both CRDs use
  x-kubernetes-preserve-unknown-fields: true. Strips server-side
  metadata. Idempotent.
- `k8s/backups/pre-phase-a-20260602-020926/` — full live state
  snapshot for the 3 DOKS lux clusters, captured BEFORE any
  in-cluster mutation. Used to verify migration and as the rollback
  source if the next attempt needs to re-restore CRs.
- `k8s/PHASE_A_ROLLBACK.md` — step-by-step rollback procedure.

Inventory (lux-k8s only — test-k8s / dev-k8s have zero lux CRs):
- 3 LuxNetworks (luxnetworks.lux.network) — mainnet/testnet/devnet luxd
- 11 LuxChains (luxchains.lux.network) — mainnet a/b/d/g/i/k/m/o/q/r/z
- 3 LuxGateways (luxgateways.lux.network) — */api-gateway
- 1 LuxMPC (luxmpcs.lux.network) — mainnet/lux-mpc
- 0 LuxIndexers, 0 LuxExplorers

In-cluster prep that survived rollback:
- lux.cloud/v1 CRDs installed alongside lux.network/v1alpha1 (idempotent
  apply of `~/work/lux/operator/spec/crd/bases/lux.cloud/`).
- Migrated lux.cloud/v1 CRs already created (apply succeeded, no
  controller bound — inert until v0.6.4+ operator ships).

Next attempt blocked on:
1. CI build of v0.6.4 or v0.7.1 operator image with luxfi/operator
   d2ecca5 fix (push triggers via `hanzo-build-linux-amd64` ARC pool).
2. Bump `k8s/lux-k8s/operator/deployment.yaml` image tag.
3. Re-run scale-down → image-bump → scale-up cycle.

Forward-only, no AI slop, no API breakage, current chains stay alive
during entire procedure.
2026-06-02 02:22:54 -07:00

135 lines
4.6 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# migrate-luxnetwork-crds.sh — Phase A: lux.network/v1alpha1 → lux.cloud/v1
#
# Migrates all live CRs from the legacy `lux.network` API group at v1alpha1
# to the canonical `lux.cloud` API group at v1 (operator v0.6.3).
#
# Kind mapping:
# LuxNetwork → LuxNetwork (name unchanged)
# LuxChain → Chain (drop Lux prefix)
# LuxGateway → Gateway (drop Lux prefix)
# LuxIndexer → Indexer (drop Lux prefix)
# LuxExplorer → Explorer (drop Lux prefix)
# LuxMPC → MPC (drop Lux prefix)
#
# Spec/status pass through opaque (both CRDs use
# x-kubernetes-preserve-unknown-fields: true).
#
# Mode of operation:
# 1. List source kind in source group
# 2. For each item: read full yaml, swap apiVersion + kind,
# strip server-side metadata (resourceVersion, uid, generation,
# managedFields, creationTimestamp, status), apply under new group
# 3. Verify both CRs coexist (legacy + migrated) before any deletion
#
# Idempotent: re-applying produces same target CR (server-side merge by name).
#
# Usage:
# ./migrate-luxnetwork-crds.sh <kubectl-context> [--dry-run]
#
# Env:
# YQ — yq v4 binary path (default: yq). Required.
set -euo pipefail
CTX="${1:-}"
DRY_RUN=""
if [[ "${2:-}" == "--dry-run" ]]; then
DRY_RUN="--dry-run=client"
fi
if [[ -z "$CTX" ]]; then
echo "usage: $0 <kubectl-context> [--dry-run]" >&2
exit 2
fi
YQ="${YQ:-yq}"
if ! command -v "$YQ" >/dev/null 2>&1; then
echo "FATAL: yq v4+ required on PATH (or set YQ=)" >&2
exit 2
fi
# kind → (sourceGVR, targetGVR, targetKind)
# kubectl-friendly: lux<resource>.lux.network/v1alpha1 → <target>.lux.cloud/v1
declare -a KINDS=(
"luxnetwork:luxnetworks.lux.network:luxnetworks.lux.cloud:LuxNetwork:lux.cloud/v1"
"luxchain:luxchains.lux.network:chains.lux.cloud:Chain:lux.cloud/v1"
"luxgateway:luxgateways.lux.network:gateways.lux.cloud:Gateway:lux.cloud/v1"
"luxindexer:luxindexers.lux.network:indexers.lux.cloud:Indexer:lux.cloud/v1"
"luxexplorer:luxexplorers.lux.network:explorers.lux.cloud:Explorer:lux.cloud/v1"
"luxmpc:luxmpcs.lux.network:mpcs.lux.cloud:MPC:lux.cloud/v1"
)
TMP=$(mktemp -d -t lux-cr-migrate-XXXXXXXX)
trap 'rm -rf "$TMP"' EXIT
echo "Working dir: $TMP"
echo "Context: $CTX"
echo
migrated_total=0
for entry in "${KINDS[@]}"; do
IFS=':' read -r tag src_gvr dst_gvr dst_kind dst_apiver <<<"$entry"
echo "=== $tag : $src_gvr → $dst_gvr ($dst_kind / $dst_apiver) ==="
# List items in source — succeeds even if 0 items
if ! list_yaml=$(kubectl --context="$CTX" get "$src_gvr" -A -o yaml 2>/dev/null); then
echo " (source CRD $src_gvr not present, skipping)"
continue
fi
# Count items
count=$(printf '%s' "$list_yaml" | "$YQ" '.items | length' -)
if [[ "$count" == "0" || -z "$count" ]]; then
echo " (no items)"
continue
fi
echo " found $count item(s)"
# Emit each item, swap apiVersion + kind, strip server fields, apply
for i in $(seq 0 $((count - 1))); do
item_file="$TMP/${tag}-${i}.yaml"
printf '%s' "$list_yaml" | "$YQ" ".items[$i]
| .apiVersion = \"$dst_apiver\"
| .kind = \"$dst_kind\"
| del(.metadata.resourceVersion)
| del(.metadata.uid)
| del(.metadata.generation)
| del(.metadata.managedFields)
| del(.metadata.creationTimestamp)
| del(.metadata.selfLink)
| del(.metadata.ownerReferences)
| del(.metadata.annotations[\"kubectl.kubernetes.io/last-applied-configuration\"])
| del(.status)" - > "$item_file"
ns=$("$YQ" '.metadata.namespace' "$item_file")
name=$("$YQ" '.metadata.name' "$item_file")
echo " → $ns/$name"
if [[ -n "$DRY_RUN" ]]; then
kubectl --context="$CTX" apply $DRY_RUN -f "$item_file" -o name | sed 's/^/ /'
else
kubectl --context="$CTX" apply -f "$item_file" -o name | sed 's/^/ /'
fi
migrated_total=$((migrated_total + 1))
done
done
echo
echo "=== TOTAL: $migrated_total CR(s) migrated (or planned in dry-run) ==="
echo
echo "=== POST-MIGRATION INVENTORY ==="
echo "--- legacy lux.network CRs (should still be present) ---"
for entry in "${KINDS[@]}"; do
IFS=':' read -r tag src_gvr _ _ _ <<<"$entry"
count=$(kubectl --context="$CTX" get "$src_gvr" -A --no-headers 2>/dev/null | wc -l | tr -d ' ')
printf " %-30s %d\n" "$src_gvr" "$count"
done
echo "--- new lux.cloud CRs (should match counts) ---"
for entry in "${KINDS[@]}"; do
IFS=':' read -r tag _ dst_gvr _ _ <<<"$entry"
count=$(kubectl --context="$CTX" get "$dst_gvr" -A --no-headers 2>/dev/null | wc -l | tr -d ' ')
printf " %-30s %d\n" "$dst_gvr" "$count"
done