Files
zeekayandGitHub 2d67d98b01 env: drop LUX_ prefix from env vars (noise) (#38)
Renames LUX_-prefixed env vars to canonical, non-noisy forms:

- LUX_MNEMONIC      -> MNEMONIC      (deploy script + LLM doc)
- LUX_PRIVATE_KEY   -> PRIVATE_KEY   (deploy script + LLM doc)
- LUX_INSECURE_TLS  -> INSECURE_TLS
- LUX_NODE_ENDPOINT -> NODE_ENDPOINT
- LUX_NETWORK_TYPE  -> NETWORK_TYPE  (backend gRPC server selector)
- LUX_RPC_URL       -> RPC_URL
- LUX_NODE_ROOT     -> NODE_ROOT
- LUX_CHART_PATH    -> CHART_PATH
- LUX_CHAINS_FILE   -> CHAINS_FILE
- LUX_KMS_ROOT_KEY  -> KMS_ROOT_KEY
- LUX_WC_PROJECT_ID -> WC_PROJECT_ID (WalletConnect)
- LUX_BLS_KEY       -> BLS_KEY       (was already supported via getEnv())
- LUX_KEY_INDEX     -> KEY_INDEX     (was already supported via getEnv())

pkg/key/backend_env.go: removed the LUX_-prefix-stripping getEnv()
helper (now reads env vars by exact name) and dropped the broken
"MNEMONIC / MNEMONIC" / "PRIVATE_KEY / PRIVATE_KEY" doc comments
which were a previous incomplete rename.

NOT renamed (defined in upstream luxfi/constants — separate concern):
- LUX_NODE_PATH      (constants.EnvNodePath)
- LUX_NETRUNNER_PATH (constants.EnvNetrunnerPath)
- LUX_EVM_PATH       (constants.EnvEVMPath)
- LUX_PLUGINS_DIR    (constants.EnvPluginsDir)

Doc comments referencing those env vars (cmd/root.go,
cmd/networkcmd/start.go, cmd/nodecmd/link.go) describe the
constants-defined names accurately and are left alone.

NOT renamed (script-local variables, not env vars):
- LUX_ZONE in scripts/update-cloudflare-dns.sh (Cloudflare DNS zone ID)
- LUX_BIN_DIR in scripts/install.sh (~/.lux/bin path constant)

BREAKING: external callers/operators must update env var names. Per
CLAUDE.md no-backwards-compatibility rule, the old forms are removed.
2026-05-15 16:35:26 -07:00

255 lines
6.8 KiB
Go

// Copyright (C) 2022-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package nodecmd
import (
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"time"
"github.com/luxfi/cli/pkg/ux"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
var (
deployImage string
deployReplicas int32
chartPath string
helmDryRun bool
helmSet []string
)
// defaultChartPath returns the default Helm chart path.
// Searches: 1) $CHART_PATH, 2) ~/work/lux/devops/charts/lux
func defaultChartPath() string {
if p := os.Getenv("CHART_PATH"); p != "" {
return p
}
home, _ := os.UserHomeDir()
return filepath.Join(home, "work", "lux", "devops", "charts", "lux")
}
func newDeployCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "deploy",
Short: "Deploy luxd to Kubernetes via Helm",
Long: `Deploys the luxd Helm chart to Kubernetes using helm upgrade --install.
Uses the canonical Helm chart from ~/work/lux/devops/charts/lux/ as the
single source of truth. This ensures the CLI creates identical deployments
to the deploy-all.sh script — same startup.sh, staking keys, bootstrap
nodes, upgrade-file-content, chain configs, and per-pod services.
CHART DISCOVERY (in order):
1. --chart-path flag
2. $CHART_PATH environment variable
3. ~/work/lux/devops/charts/lux/
EXAMPLES:
lux node deploy --mainnet
lux node deploy --testnet --set image.tag=luxd-v1.23.15
lux node deploy --devnet --replicas 3
lux node deploy --mainnet --chart-path /path/to/chart
lux node deploy --mainnet --dry-run`,
RunE: runDeploy,
}
cmd.Flags().StringVar(&deployImage, "image", "", "override image tag (shorthand for --set image.tag=TAG)")
cmd.Flags().Int32Var(&deployReplicas, "replicas", 0, "override replica count (0 = use chart default)")
cmd.Flags().StringVar(&chartPath, "chart-path", "", "path to Helm chart (default: auto-detect)")
cmd.Flags().BoolVar(&helmDryRun, "dry-run", false, "helm dry-run mode (template only, no apply)")
cmd.Flags().StringArrayVar(&helmSet, "set", nil, "additional Helm --set overrides (repeatable)")
return cmd
}
func runDeploy(_ *cobra.Command, _ []string) error {
network, err := resolveNetwork()
if err != nil {
return err
}
namespace := "lux-" + network
// Resolve chart path
chart := chartPath
if chart == "" {
chart = defaultChartPath()
}
// Validate chart exists
if _, err := os.Stat(filepath.Join(chart, "Chart.yaml")); err != nil {
return fmt.Errorf("Helm chart not found at %s (set --chart-path or $CHART_PATH)", chart)
}
// Validate values file exists
valuesFile := filepath.Join(chart, fmt.Sprintf("values-%s.yaml", network))
if _, err := os.Stat(valuesFile); err != nil {
return fmt.Errorf("values file not found: %s", valuesFile)
}
// Check helm binary
helmBin, err := exec.LookPath("helm")
if err != nil {
return fmt.Errorf("helm not found in PATH — install from https://helm.sh/docs/intro/install/")
}
// Build helm command
releaseName := "luxd-" + network
args := []string{
"upgrade", "--install", releaseName, chart,
"--namespace", namespace,
"--create-namespace",
"-f", valuesFile,
}
// K8s context override
ctx := flagContext
if ctx == "" {
ctx = os.Getenv("KUBECONTEXT")
}
if ctx != "" {
args = append(args, "--kube-context", ctx)
}
// Image override
if deployImage != "" {
args = append(args, "--set", "image.tag="+deployImage)
}
// Replicas override
if deployReplicas > 0 {
args = append(args, "--set", fmt.Sprintf("replicas=%d", deployReplicas))
}
// Additional --set flags
for _, s := range helmSet {
args = append(args, "--set", s)
}
// Dry-run
if helmDryRun {
args = append(args, "--dry-run")
}
ux.Logger.PrintToUser("Deploying %s via Helm:", network)
ux.Logger.PrintToUser(" Release: %s", releaseName)
ux.Logger.PrintToUser(" Namespace: %s", namespace)
ux.Logger.PrintToUser(" Chart: %s", chart)
ux.Logger.PrintToUser(" Values: %s", valuesFile)
if deployImage != "" {
ux.Logger.PrintToUser(" Image: %s", deployImage)
}
if deployReplicas > 0 {
ux.Logger.PrintToUser(" Replicas: %d", deployReplicas)
}
ux.Logger.PrintToUser("")
// Run helm
helmCmd := exec.Command(helmBin, args...)
helmCmd.Stdout = os.Stdout
helmCmd.Stderr = os.Stderr
helmCmd.Env = os.Environ()
if err := helmCmd.Run(); err != nil {
return fmt.Errorf("helm upgrade --install failed: %w", err)
}
if helmDryRun {
ux.Logger.PrintToUser("\n[dry-run] No changes applied.")
return nil
}
// Wait for pods to be ready
ux.Logger.PrintToUser("\nWaiting for pods to be ready...")
if err := waitForDeployReady(namespace, 10*time.Minute); err != nil {
ux.Logger.PrintToUser("Warning: %v", err)
ux.Logger.PrintToUser("Check status with: lux node status --%s", network)
return nil
}
ux.Logger.PrintToUser("\nDeployed successfully!")
ux.Logger.PrintToUser(" Status: lux node status --%s", network)
ux.Logger.PrintToUser(" Logs: lux node logs --%s", network)
ux.Logger.PrintToUser(" Upgrade: lux node upgrade --%s --image TAG", network)
return nil
}
// resolveNetwork returns the network name from flags.
func resolveNetwork() (string, error) {
if flagNamespace != "" {
// Extract network from namespace like "lux-mainnet"
switch flagNamespace {
case "lux-mainnet":
return "mainnet", nil
case "lux-testnet":
return "testnet", nil
case "lux-devnet":
return "devnet", nil
default:
return "", fmt.Errorf("custom namespace %q not supported for Helm deploy — use --mainnet, --testnet, or --devnet", flagNamespace)
}
}
set := 0
if flagMainnet {
set++
}
if flagTestnet {
set++
}
if flagDevnet {
set++
}
if set > 1 {
return "", fmt.Errorf("specify exactly one of --mainnet, --testnet, or --devnet")
}
switch {
case flagMainnet:
return "mainnet", nil
case flagTestnet:
return "testnet", nil
case flagDevnet:
return "devnet", nil
default:
return "", fmt.Errorf("specify --mainnet, --testnet, or --devnet")
}
}
// waitForDeployReady waits for the StatefulSet to reach full readiness.
func waitForDeployReady(namespace string, timeout time.Duration) error {
client, err := newK8sClient()
if err != nil {
return fmt.Errorf("cannot connect to k8s: %w", err)
}
ctx := context.Background()
deadline := time.After(timeout)
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
for {
select {
case <-deadline:
return fmt.Errorf("timeout waiting for pods to be ready")
case <-ticker.C:
sts, err := client.AppsV1().StatefulSets(namespace).Get(ctx, statefulSetName, metav1.GetOptions{})
if err != nil {
continue
}
replicas := int32(5)
if sts.Spec.Replicas != nil {
replicas = *sts.Spec.Replicas
}
if sts.Status.ReadyReplicas == replicas {
ux.Logger.PrintToUser(" All %d pods ready", replicas)
return nil
}
ux.Logger.PrintToUser(" Ready: %d/%d", sts.Status.ReadyReplicas, replicas)
}
}
}