summaryrefslogtreecommitdiffstats
path: root/cmd/deploy/manage.go
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/deploy/manage.go')
-rw-r--r--cmd/deploy/manage.go327
1 files changed, 327 insertions, 0 deletions
diff --git a/cmd/deploy/manage.go b/cmd/deploy/manage.go
new file mode 100644
index 0000000..3cee1f4
--- /dev/null
+++ b/cmd/deploy/manage.go
@@ -0,0 +1,327 @@
1package main
2
3import (
4 "flag"
5 "fmt"
6 "os"
7
8 "github.com/bdw/deploy/internal/config"
9 "github.com/bdw/deploy/internal/ssh"
10 "github.com/bdw/deploy/internal/state"
11)
12
13func runRemove(args []string) {
14 fs := flag.NewFlagSet("remove", flag.ExitOnError)
15 host := fs.String("host", "", "VPS host (SSH config alias or user@host)")
16 fs.Parse(args)
17
18 if len(fs.Args()) == 0 {
19 fmt.Fprintf(os.Stderr, "Error: app name is required\n")
20 fmt.Fprintf(os.Stderr, "Usage: deploy remove <app-name> --host user@vps-ip\n")
21 os.Exit(1)
22 }
23
24 name := fs.Args()[0]
25
26 // Get host from flag or config
27 if *host == "" {
28 cfg, err := config.Load()
29 if err != nil {
30 fmt.Fprintf(os.Stderr, "Error loading config: %v\n", err)
31 os.Exit(1)
32 }
33 *host = cfg.Host
34 }
35
36 if *host == "" {
37 fmt.Fprintf(os.Stderr, "Error: --host is required\n")
38 fs.Usage()
39 os.Exit(1)
40 }
41
42 // Load state
43 st, err := state.Load()
44 if err != nil {
45 fmt.Fprintf(os.Stderr, "Error loading state: %v\n", err)
46 os.Exit(1)
47 }
48
49 // Get app info
50 app, err := st.GetApp(*host, name)
51 if err != nil {
52 fmt.Fprintf(os.Stderr, "Error: %v\n", err)
53 os.Exit(1)
54 }
55
56 fmt.Printf("Removing deployment: %s\n", name)
57
58 // Connect to VPS
59 client, err := ssh.Connect(*host)
60 if err != nil {
61 fmt.Fprintf(os.Stderr, "Error connecting to VPS: %v\n", err)
62 os.Exit(1)
63 }
64 defer client.Close()
65
66 if app.Type == "app" {
67 // Stop and disable service
68 fmt.Println("→ Stopping service...")
69 client.RunSudo(fmt.Sprintf("systemctl stop %s", name))
70 client.RunSudo(fmt.Sprintf("systemctl disable %s", name))
71
72 // Remove systemd unit
73 client.RunSudo(fmt.Sprintf("rm -f /etc/systemd/system/%s.service", name))
74 client.RunSudo("systemctl daemon-reload")
75
76 // Remove binary
77 client.RunSudo(fmt.Sprintf("rm -f /usr/local/bin/%s", name))
78
79 // Remove working directory
80 client.RunSudo(fmt.Sprintf("rm -rf /var/lib/%s", name))
81
82 // Remove env file
83 client.RunSudo(fmt.Sprintf("rm -f /etc/deploy/env/%s.env", name))
84
85 // Remove user
86 client.RunSudo(fmt.Sprintf("userdel %s", name))
87 } else {
88 // Remove static site files
89 fmt.Println("→ Removing files...")
90 client.RunSudo(fmt.Sprintf("rm -rf /var/www/%s", name))
91 }
92
93 // Remove Caddy config
94 fmt.Println("→ Removing Caddy config...")
95 client.RunSudo(fmt.Sprintf("rm -f /etc/caddy/sites-enabled/%s.caddy", name))
96
97 // Reload Caddy
98 fmt.Println("→ Reloading Caddy...")
99 if _, err := client.RunSudo("systemctl reload caddy"); err != nil {
100 fmt.Fprintf(os.Stderr, "Warning: Error reloading Caddy: %v\n", err)
101 }
102
103 // Update state
104 if err := st.RemoveApp(*host, name); err != nil {
105 fmt.Fprintf(os.Stderr, "Error updating state: %v\n", err)
106 os.Exit(1)
107 }
108 if err := st.Save(); err != nil {
109 fmt.Fprintf(os.Stderr, "Error saving state: %v\n", err)
110 os.Exit(1)
111 }
112
113 fmt.Printf("✓ Deployment removed successfully\n")
114}
115
116func runLogs(args []string) {
117 fs := flag.NewFlagSet("logs", flag.ExitOnError)
118 host := fs.String("host", "", "VPS host (SSH config alias or user@host)")
119 follow := fs.Bool("f", false, "Follow logs")
120 lines := fs.Int("n", 50, "Number of lines to show")
121 fs.Parse(args)
122
123 if len(fs.Args()) == 0 {
124 fmt.Fprintf(os.Stderr, "Error: app name is required\n")
125 fmt.Fprintf(os.Stderr, "Usage: deploy logs <app-name> --host user@vps-ip\n")
126 os.Exit(1)
127 }
128
129 name := fs.Args()[0]
130
131 // Get host from flag or config
132 if *host == "" {
133 cfg, err := config.Load()
134 if err != nil {
135 fmt.Fprintf(os.Stderr, "Error loading config: %v\n", err)
136 os.Exit(1)
137 }
138 *host = cfg.Host
139 }
140
141 if *host == "" {
142 fmt.Fprintf(os.Stderr, "Error: --host is required\n")
143 fs.Usage()
144 os.Exit(1)
145 }
146
147 // Load state to verify app exists
148 st, err := state.Load()
149 if err != nil {
150 fmt.Fprintf(os.Stderr, "Error loading state: %v\n", err)
151 os.Exit(1)
152 }
153
154 app, err := st.GetApp(*host, name)
155 if err != nil {
156 fmt.Fprintf(os.Stderr, "Error: %v\n", err)
157 os.Exit(1)
158 }
159
160 if app.Type != "app" {
161 fmt.Fprintf(os.Stderr, "Error: logs are only available for apps, not static sites\n")
162 os.Exit(1)
163 }
164
165 // Connect to VPS
166 client, err := ssh.Connect(*host)
167 if err != nil {
168 fmt.Fprintf(os.Stderr, "Error connecting to VPS: %v\n", err)
169 os.Exit(1)
170 }
171 defer client.Close()
172
173 // Build journalctl command
174 cmd := fmt.Sprintf("journalctl -u %s -n %d", name, *lines)
175 if *follow {
176 cmd += " -f"
177 }
178
179 // Run command
180 if *follow {
181 // Stream output for follow mode (no sudo needed for journalctl)
182 if err := client.RunStream(cmd); err != nil {
183 fmt.Fprintf(os.Stderr, "Error fetching logs: %v\n", err)
184 os.Exit(1)
185 }
186 } else {
187 // Buffer output for non-follow mode (no sudo needed for journalctl)
188 output, err := client.Run(cmd)
189 if err != nil {
190 fmt.Fprintf(os.Stderr, "Error fetching logs: %v\n", err)
191 os.Exit(1)
192 }
193 fmt.Print(output)
194 }
195}
196
197func runStatus(args []string) {
198 fs := flag.NewFlagSet("status", flag.ExitOnError)
199 host := fs.String("host", "", "VPS host (SSH config alias or user@host)")
200 fs.Parse(args)
201
202 if len(fs.Args()) == 0 {
203 fmt.Fprintf(os.Stderr, "Error: app name is required\n")
204 fmt.Fprintf(os.Stderr, "Usage: deploy status <app-name> --host user@vps-ip\n")
205 os.Exit(1)
206 }
207
208 name := fs.Args()[0]
209
210 // Get host from flag or config
211 if *host == "" {
212 cfg, err := config.Load()
213 if err != nil {
214 fmt.Fprintf(os.Stderr, "Error loading config: %v\n", err)
215 os.Exit(1)
216 }
217 *host = cfg.Host
218 }
219
220 if *host == "" {
221 fmt.Fprintf(os.Stderr, "Error: --host is required\n")
222 fs.Usage()
223 os.Exit(1)
224 }
225
226 // Load state to verify app exists
227 st, err := state.Load()
228 if err != nil {
229 fmt.Fprintf(os.Stderr, "Error loading state: %v\n", err)
230 os.Exit(1)
231 }
232
233 app, err := st.GetApp(*host, name)
234 if err != nil {
235 fmt.Fprintf(os.Stderr, "Error: %v\n", err)
236 os.Exit(1)
237 }
238
239 if app.Type != "app" {
240 fmt.Fprintf(os.Stderr, "Error: status is only available for apps, not static sites\n")
241 os.Exit(1)
242 }
243
244 // Connect to VPS
245 client, err := ssh.Connect(*host)
246 if err != nil {
247 fmt.Fprintf(os.Stderr, "Error connecting to VPS: %v\n", err)
248 os.Exit(1)
249 }
250 defer client.Close()
251
252 // Get status
253 output, err := client.RunSudo(fmt.Sprintf("systemctl status %s", name))
254 if err != nil {
255 // systemctl status returns non-zero for non-active services
256 // but we still want to show the output
257 fmt.Print(output)
258 return
259 }
260
261 fmt.Print(output)
262}
263
264func runRestart(args []string) {
265 fs := flag.NewFlagSet("restart", flag.ExitOnError)
266 host := fs.String("host", "", "VPS host (SSH config alias or user@host)")
267 fs.Parse(args)
268
269 if len(fs.Args()) == 0 {
270 fmt.Fprintf(os.Stderr, "Error: app name is required\n")
271 fmt.Fprintf(os.Stderr, "Usage: deploy restart <app-name> --host user@vps-ip\n")
272 os.Exit(1)
273 }
274
275 name := fs.Args()[0]
276
277 // Get host from flag or config
278 if *host == "" {
279 cfg, err := config.Load()
280 if err != nil {
281 fmt.Fprintf(os.Stderr, "Error loading config: %v\n", err)
282 os.Exit(1)
283 }
284 *host = cfg.Host
285 }
286
287 if *host == "" {
288 fmt.Fprintf(os.Stderr, "Error: --host is required\n")
289 fs.Usage()
290 os.Exit(1)
291 }
292
293 // Load state to verify app exists
294 st, err := state.Load()
295 if err != nil {
296 fmt.Fprintf(os.Stderr, "Error loading state: %v\n", err)
297 os.Exit(1)
298 }
299
300 app, err := st.GetApp(*host, name)
301 if err != nil {
302 fmt.Fprintf(os.Stderr, "Error: %v\n", err)
303 os.Exit(1)
304 }
305
306 if app.Type != "app" {
307 fmt.Fprintf(os.Stderr, "Error: restart is only available for apps, not static sites\n")
308 os.Exit(1)
309 }
310
311 // Connect to VPS
312 client, err := ssh.Connect(*host)
313 if err != nil {
314 fmt.Fprintf(os.Stderr, "Error connecting to VPS: %v\n", err)
315 os.Exit(1)
316 }
317 defer client.Close()
318
319 // Restart service
320 fmt.Printf("Restarting %s...\n", name)
321 if _, err := client.RunSudo(fmt.Sprintf("systemctl restart %s", name)); err != nil {
322 fmt.Fprintf(os.Stderr, "Error restarting service: %v\n", err)
323 os.Exit(1)
324 }
325
326 fmt.Println("✓ Service restarted successfully")
327}