summaryrefslogtreecommitdiffstats
path: root/cmd/deploy/env.go
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/deploy/env.go')
-rw-r--r--cmd/deploy/env.go176
1 files changed, 176 insertions, 0 deletions
diff --git a/cmd/deploy/env.go b/cmd/deploy/env.go
new file mode 100644
index 0000000..135fb77
--- /dev/null
+++ b/cmd/deploy/env.go
@@ -0,0 +1,176 @@
1package main
2
3import (
4 "flag"
5 "fmt"
6 "os"
7 "strings"
8
9 "github.com/bdw/deploy/internal/config"
10 "github.com/bdw/deploy/internal/ssh"
11 "github.com/bdw/deploy/internal/state"
12)
13
14func runEnv(args []string) {
15 fs := flag.NewFlagSet("env", flag.ExitOnError)
16 host := fs.String("host", "", "VPS host (SSH config alias or user@host)")
17 var setVars envFlags
18 fs.Var(&setVars, "set", "Set environment variable (KEY=VALUE, can be specified multiple times)")
19 var unsetVars envFlags
20 fs.Var(&unsetVars, "unset", "Unset environment variable (KEY, can be specified multiple times)")
21 envFile := fs.String("file", "", "Load environment from file")
22 fs.Parse(args)
23
24 if len(fs.Args()) == 0 {
25 fmt.Fprintf(os.Stderr, "Error: app name is required\n")
26 fmt.Fprintf(os.Stderr, "Usage: deploy env <app-name> [--set KEY=VALUE] [--unset KEY] [--file .env] --host user@vps-ip\n")
27 os.Exit(1)
28 }
29
30 name := fs.Args()[0]
31
32 // Get host from flag or config
33 if *host == "" {
34 cfg, err := config.Load()
35 if err != nil {
36 fmt.Fprintf(os.Stderr, "Error loading config: %v\n", err)
37 os.Exit(1)
38 }
39 *host = cfg.Host
40 }
41
42 if *host == "" {
43 fmt.Fprintf(os.Stderr, "Error: --host is required\n")
44 fs.Usage()
45 os.Exit(1)
46 }
47
48 // Load state
49 st, err := state.Load()
50 if err != nil {
51 fmt.Fprintf(os.Stderr, "Error loading state: %v\n", err)
52 os.Exit(1)
53 }
54
55 // Get app info
56 app, err := st.GetApp(*host, name)
57 if err != nil {
58 fmt.Fprintf(os.Stderr, "Error: %v\n", err)
59 os.Exit(1)
60 }
61
62 if app.Type != "app" {
63 fmt.Fprintf(os.Stderr, "Error: env is only available for apps, not static sites\n")
64 os.Exit(1)
65 }
66
67 // If no flags, just display current env (masked)
68 if len(setVars) == 0 && len(unsetVars) == 0 && *envFile == "" {
69 fmt.Printf("Environment variables for %s:\n\n", name)
70 if len(app.Env) == 0 {
71 fmt.Println(" (none)")
72 } else {
73 for k, v := range app.Env {
74 // Mask sensitive-looking values
75 display := v
76 if isSensitive(k) {
77 display = "***"
78 }
79 fmt.Printf(" %s=%s\n", k, display)
80 }
81 }
82 return
83 }
84
85 // Initialize env if nil
86 if app.Env == nil {
87 app.Env = make(map[string]string)
88 }
89
90 // Apply changes
91 changed := false
92
93 // Unset variables
94 for _, key := range unsetVars {
95 if _, exists := app.Env[key]; exists {
96 delete(app.Env, key)
97 changed = true
98 fmt.Printf("Unset %s\n", key)
99 }
100 }
101
102 // Set variables from flags
103 for _, e := range setVars {
104 parts := strings.SplitN(e, "=", 2)
105 if len(parts) == 2 {
106 app.Env[parts[0]] = parts[1]
107 changed = true
108 fmt.Printf("Set %s\n", parts[0])
109 }
110 }
111
112 // Set variables from file
113 if *envFile != "" {
114 fileEnv, err := parseEnvFile(*envFile)
115 if err != nil {
116 fmt.Fprintf(os.Stderr, "Error reading env file: %v\n", err)
117 os.Exit(1)
118 }
119 for k, v := range fileEnv {
120 app.Env[k] = v
121 changed = true
122 fmt.Printf("Set %s\n", k)
123 }
124 }
125
126 if !changed {
127 fmt.Println("No changes made")
128 return
129 }
130
131 // Save state
132 if err := st.Save(); err != nil {
133 fmt.Fprintf(os.Stderr, "Error saving state: %v\n", err)
134 os.Exit(1)
135 }
136
137 // Connect to VPS and update env file
138 client, err := ssh.Connect(*host)
139 if err != nil {
140 fmt.Fprintf(os.Stderr, "Error connecting to VPS: %v\n", err)
141 os.Exit(1)
142 }
143 defer client.Close()
144
145 // Regenerate env file
146 fmt.Println("→ Updating environment file on VPS...")
147 envFilePath := fmt.Sprintf("/etc/deploy/env/%s.env", name)
148 envContent := ""
149 for k, v := range app.Env {
150 envContent += fmt.Sprintf("%s=%s\n", k, v)
151 }
152 if err := client.WriteSudoFile(envFilePath, envContent); err != nil {
153 fmt.Fprintf(os.Stderr, "Error updating env file: %v\n", err)
154 os.Exit(1)
155 }
156
157 // Restart service to pick up new env
158 fmt.Println("→ Restarting service...")
159 if _, err := client.RunSudo(fmt.Sprintf("systemctl restart %s", name)); err != nil {
160 fmt.Fprintf(os.Stderr, "Error restarting service: %v\n", err)
161 os.Exit(1)
162 }
163
164 fmt.Println("✓ Environment variables updated successfully")
165}
166
167func isSensitive(key string) bool {
168 key = strings.ToLower(key)
169 sensitiveWords := []string{"key", "secret", "password", "token", "api"}
170 for _, word := range sensitiveWords {
171 if strings.Contains(key, word) {
172 return true
173 }
174 }
175 return false
176}