package main import ( "flag" "fmt" "os" "strings" "github.com/bdw/deploy/internal/ssh" "github.com/bdw/deploy/internal/state" ) func runEnv(args []string) { fs := flag.NewFlagSet("env", flag.ExitOnError) host := fs.String("host", "", "VPS host (SSH config alias or user@host)") var setVars envFlags fs.Var(&setVars, "set", "Set environment variable (KEY=VALUE, can be specified multiple times)") var unsetVars envFlags fs.Var(&unsetVars, "unset", "Unset environment variable (KEY, can be specified multiple times)") envFile := fs.String("file", "", "Load environment from file") fs.Parse(args) if len(fs.Args()) == 0 { fmt.Fprintf(os.Stderr, "Error: app name is required\n") fmt.Fprintf(os.Stderr, "Usage: deploy env [--set KEY=VALUE] [--unset KEY] [--file .env] --host user@vps-ip\n") os.Exit(1) } name := fs.Args()[0] // Load state st, err := state.Load() if err != nil { fmt.Fprintf(os.Stderr, "Error loading state: %v\n", err) os.Exit(1) } // Get host from flag or state default if *host == "" { *host = st.GetDefaultHost() } if *host == "" { fmt.Fprintf(os.Stderr, "Error: --host is required\n") fs.Usage() os.Exit(1) } // Get app info app, err := st.GetApp(*host, name) if err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) os.Exit(1) } if app.Type != "app" { fmt.Fprintf(os.Stderr, "Error: env is only available for apps, not static sites\n") os.Exit(1) } // If no flags, just display current env (masked) if len(setVars) == 0 && len(unsetVars) == 0 && *envFile == "" { fmt.Printf("Environment variables for %s:\n\n", name) if len(app.Env) == 0 { fmt.Println(" (none)") } else { for k, v := range app.Env { // Mask sensitive-looking values display := v if isSensitive(k) { display = "***" } fmt.Printf(" %s=%s\n", k, display) } } return } // Initialize env if nil if app.Env == nil { app.Env = make(map[string]string) } // Apply changes changed := false // Unset variables for _, key := range unsetVars { if _, exists := app.Env[key]; exists { delete(app.Env, key) changed = true fmt.Printf("Unset %s\n", key) } } // Set variables from flags for _, e := range setVars { parts := strings.SplitN(e, "=", 2) if len(parts) == 2 { app.Env[parts[0]] = parts[1] changed = true fmt.Printf("Set %s\n", parts[0]) } } // Set variables from file if *envFile != "" { fileEnv, err := parseEnvFile(*envFile) if err != nil { fmt.Fprintf(os.Stderr, "Error reading env file: %v\n", err) os.Exit(1) } for k, v := range fileEnv { app.Env[k] = v changed = true fmt.Printf("Set %s\n", k) } } if !changed { fmt.Println("No changes made") return } // Save state if err := st.Save(); err != nil { fmt.Fprintf(os.Stderr, "Error saving state: %v\n", err) os.Exit(1) } // Connect to VPS and update env file client, err := ssh.Connect(*host) if err != nil { fmt.Fprintf(os.Stderr, "Error connecting to VPS: %v\n", err) os.Exit(1) } defer client.Close() // Regenerate env file fmt.Println("→ Updating environment file on VPS...") envFilePath := fmt.Sprintf("/etc/deploy/env/%s.env", name) envContent := "" for k, v := range app.Env { envContent += fmt.Sprintf("%s=%s\n", k, v) } if err := client.WriteSudoFile(envFilePath, envContent); err != nil { fmt.Fprintf(os.Stderr, "Error updating env file: %v\n", err) os.Exit(1) } // Restart service to pick up new env fmt.Println("→ Restarting service...") if _, err := client.RunSudo(fmt.Sprintf("systemctl restart %s", name)); err != nil { fmt.Fprintf(os.Stderr, "Error restarting service: %v\n", err) os.Exit(1) } fmt.Println("✓ Environment variables updated successfully") } func isSensitive(key string) bool { key = strings.ToLower(key) sensitiveWords := []string{"key", "secret", "password", "token", "api"} for _, word := range sensitiveWords { if strings.Contains(key, word) { return true } } return false }