summaryrefslogtreecommitdiffstats
path: root/cmd/deploy/vps.go
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/deploy/vps.go')
-rw-r--r--cmd/deploy/vps.go229
1 files changed, 229 insertions, 0 deletions
diff --git a/cmd/deploy/vps.go b/cmd/deploy/vps.go
new file mode 100644
index 0000000..bd6278b
--- /dev/null
+++ b/cmd/deploy/vps.go
@@ -0,0 +1,229 @@
1package main
2
3import (
4 "flag"
5 "fmt"
6 "os"
7 "os/exec"
8
9 "github.com/bdw/deploy/internal/ssh"
10 "github.com/bdw/deploy/internal/state"
11)
12
13func runVPS(args []string) {
14 fs := flag.NewFlagSet("vps", flag.ExitOnError)
15 host := fs.String("host", "", "VPS host (SSH config alias or user@host)")
16 fs.Parse(args)
17
18 // Load state
19 st, err := state.Load()
20 if err != nil {
21 fmt.Fprintf(os.Stderr, "Error loading state: %v\n", err)
22 os.Exit(1)
23 }
24
25 // Get host from flag or state default
26 if *host == "" {
27 *host = st.GetDefaultHost()
28 }
29
30 if *host == "" {
31 fmt.Fprintf(os.Stderr, "Error: --host is required (no default host set)\n")
32 fs.Usage()
33 os.Exit(1)
34 }
35
36 fmt.Printf("Connecting to %s...\n\n", *host)
37
38 // Connect to VPS
39 client, err := ssh.Connect(*host)
40 if err != nil {
41 fmt.Fprintf(os.Stderr, "Error connecting to VPS: %v\n", err)
42 os.Exit(1)
43 }
44 defer client.Close()
45
46 // Uptime
47 fmt.Println("UPTIME")
48 if output, err := client.Run("uptime -p"); err == nil {
49 fmt.Printf(" %s", output)
50 }
51 if output, err := client.Run("uptime -s"); err == nil {
52 fmt.Printf(" Since: %s", output)
53 }
54 fmt.Println()
55
56 // Load average
57 fmt.Println("LOAD")
58 if output, err := client.Run("cat /proc/loadavg | awk '{print $1, $2, $3}'"); err == nil {
59 fmt.Printf(" 1m, 5m, 15m: %s", output)
60 }
61 fmt.Println()
62
63 // Memory
64 fmt.Println("MEMORY")
65 if output, err := client.Run("free -h | awk 'NR==2 {print \" Used: \" $3 \" / \" $2}'"); err == nil {
66 fmt.Print(output)
67 }
68 if output, err := client.Run("free -h | awk 'NR==2 {printf \" Available: %s\\n\", $7}'"); err == nil {
69 fmt.Print(output)
70 }
71 fmt.Println()
72
73 // Disk
74 fmt.Println("DISK")
75 if output, err := client.Run("df -h / | awk 'NR==2 {print \" Used: \" $3 \" / \" $2 \" (\" $5 \")\"}'"); err == nil {
76 fmt.Print(output)
77 }
78 if output, err := client.Run("df -h / | awk 'NR==2 {print \" Available: \" $4}'"); err == nil {
79 fmt.Print(output)
80 }
81 fmt.Println()
82
83 // Updates available
84 fmt.Println("UPDATES")
85 if output, err := client.Run("[ -f /var/lib/update-notifier/updates-available ] && cat /var/lib/update-notifier/updates-available | head -2 || echo ' (update info not available)'"); err == nil {
86 fmt.Print(output)
87 }
88 fmt.Println()
89
90 // Services
91 fmt.Println("SERVICES")
92 if output, err := client.Run("systemctl is-active caddy 2>/dev/null && echo ' Caddy: active' || echo ' Caddy: inactive'"); err == nil {
93 // Parse the output
94 if output == "active\n" {
95 fmt.Println(" Caddy: active")
96 } else {
97 fmt.Println(" Caddy: inactive")
98 }
99 }
100
101 // Count deployed apps that are running
102 hostState := st.GetHost(*host)
103 if hostState != nil && len(hostState.Apps) > 0 {
104 activeCount := 0
105 for name, app := range hostState.Apps {
106 if app.Type == "app" {
107 if output, err := client.Run(fmt.Sprintf("systemctl is-active %s 2>/dev/null", name)); err == nil && output == "active\n" {
108 activeCount++
109 }
110 }
111 }
112 appCount := 0
113 for _, app := range hostState.Apps {
114 if app.Type == "app" {
115 appCount++
116 }
117 }
118 fmt.Printf(" Deployed apps: %d/%d active\n", activeCount, appCount)
119 }
120}
121
122func runUpdate(args []string) {
123 fs := flag.NewFlagSet("vps-update", flag.ExitOnError)
124 host := fs.String("host", "", "VPS host (SSH config alias or user@host)")
125 yes := fs.Bool("y", false, "Skip confirmation prompt")
126 fs.Parse(args)
127
128 // Load state
129 st, err := state.Load()
130 if err != nil {
131 fmt.Fprintf(os.Stderr, "Error loading state: %v\n", err)
132 os.Exit(1)
133 }
134
135 // Get host from flag or state default
136 if *host == "" {
137 *host = st.GetDefaultHost()
138 }
139
140 if *host == "" {
141 fmt.Fprintf(os.Stderr, "Error: --host is required (no default host set)\n")
142 fs.Usage()
143 os.Exit(1)
144 }
145
146 // Confirm unless -y flag is set
147 if !*yes {
148 fmt.Printf("This will run apt update && apt upgrade on %s\n", *host)
149 fmt.Print("Continue? [y/N]: ")
150 var response string
151 fmt.Scanln(&response)
152 if response != "y" && response != "Y" {
153 fmt.Println("Aborted.")
154 return
155 }
156 }
157
158 fmt.Printf("Connecting to %s...\n", *host)
159
160 // Connect to VPS
161 client, err := ssh.Connect(*host)
162 if err != nil {
163 fmt.Fprintf(os.Stderr, "Error connecting to VPS: %v\n", err)
164 os.Exit(1)
165 }
166 defer client.Close()
167
168 // Run apt update
169 fmt.Println("\n→ Running apt update...")
170 if err := client.RunSudoStream("apt update"); err != nil {
171 fmt.Fprintf(os.Stderr, "Error running apt update: %v\n", err)
172 os.Exit(1)
173 }
174
175 // Run apt upgrade
176 fmt.Println("\n→ Running apt upgrade...")
177 if err := client.RunSudoStream("DEBIAN_FRONTEND=noninteractive apt upgrade -y"); err != nil {
178 fmt.Fprintf(os.Stderr, "Error running apt upgrade: %v\n", err)
179 os.Exit(1)
180 }
181
182 // Check if reboot is required
183 fmt.Println()
184 if output, err := client.Run("[ -f /var/run/reboot-required ] && echo 'yes' || echo 'no'"); err == nil {
185 if output == "yes\n" {
186 fmt.Println("Note: A reboot is required to complete the update.")
187 }
188 }
189
190 fmt.Println("āœ“ Update complete")
191}
192
193func runSSH(args []string) {
194 fs := flag.NewFlagSet("vps-ssh", flag.ExitOnError)
195 host := fs.String("host", "", "VPS host (SSH config alias or user@host)")
196 fs.Parse(args)
197
198 // Load state
199 st, err := state.Load()
200 if err != nil {
201 fmt.Fprintf(os.Stderr, "Error loading state: %v\n", err)
202 os.Exit(1)
203 }
204
205 // Get host from flag or state default
206 if *host == "" {
207 *host = st.GetDefaultHost()
208 }
209
210 if *host == "" {
211 fmt.Fprintf(os.Stderr, "Error: --host is required (no default host set)\n")
212 fs.Usage()
213 os.Exit(1)
214 }
215
216 // Launch interactive SSH session
217 cmd := exec.Command("ssh", *host)
218 cmd.Stdin = os.Stdin
219 cmd.Stdout = os.Stdout
220 cmd.Stderr = os.Stderr
221
222 if err := cmd.Run(); err != nil {
223 if exitErr, ok := err.(*exec.ExitError); ok {
224 os.Exit(exitErr.ExitCode())
225 }
226 fmt.Fprintf(os.Stderr, "Error: %v\n", err)
227 os.Exit(1)
228 }
229}