summaryrefslogtreecommitdiffstats
path: root/cmd/deploy/host/init.go
diff options
context:
space:
mode:
authorbndw <ben@bdw.to>2026-01-23 21:52:50 -0800
committerbndw <ben@bdw.to>2026-01-23 21:52:50 -0800
commit87752492d0dc7df3cf78011d5ce315a3eb0cad51 (patch)
tree76843c127fece33f5c28dd7bd533044043478825 /cmd/deploy/host/init.go
parent57eb67df265a7a6bb544cde83a3be5eadf53fdf2 (diff)
Restructure CLI with Cobra
Replace custom switch-based routing with Cobra for cleaner command hierarchy. Reorganize commands into logical groups: - Root command handles deployment (--binary, --static, --domain, etc.) - App management at top level: list, logs, status, restart, remove - env subcommand group: list, set, unset - host subcommand group: init, status, update, ssh - Standalone: ui (renamed from webui), version Add version command with ldflags support for build info.
Diffstat (limited to 'cmd/deploy/host/init.go')
-rw-r--r--cmd/deploy/host/init.go137
1 files changed, 137 insertions, 0 deletions
diff --git a/cmd/deploy/host/init.go b/cmd/deploy/host/init.go
new file mode 100644
index 0000000..984e5d3
--- /dev/null
+++ b/cmd/deploy/host/init.go
@@ -0,0 +1,137 @@
1package host
2
3import (
4 "fmt"
5 "strings"
6
7 "github.com/bdw/deploy/internal/ssh"
8 "github.com/bdw/deploy/internal/state"
9 "github.com/spf13/cobra"
10)
11
12var initCmd = &cobra.Command{
13 Use: "init",
14 Short: "Initialize VPS (one-time setup)",
15 Long: "Set up a fresh VPS with Caddy for automatic HTTPS and required directories",
16 RunE: runInit,
17}
18
19func runInit(cmd *cobra.Command, args []string) error {
20 st, err := state.Load()
21 if err != nil {
22 return fmt.Errorf("error loading state: %w", err)
23 }
24
25 host, _ := cmd.Flags().GetString("host")
26 if host == "" {
27 host = st.GetDefaultHost()
28 }
29
30 if host == "" {
31 return fmt.Errorf("--host is required")
32 }
33
34 fmt.Printf("Initializing VPS: %s\n", host)
35
36 client, err := ssh.Connect(host)
37 if err != nil {
38 return fmt.Errorf("error connecting to VPS: %w", err)
39 }
40 defer client.Close()
41
42 fmt.Println("-> Detecting OS...")
43 osRelease, err := client.Run("cat /etc/os-release")
44 if err != nil {
45 return fmt.Errorf("error detecting OS: %w", err)
46 }
47
48 if !strings.Contains(osRelease, "Ubuntu") && !strings.Contains(osRelease, "Debian") {
49 return fmt.Errorf("unsupported OS (only Ubuntu and Debian are supported)")
50 }
51 fmt.Println(" Detected Ubuntu/Debian")
52
53 fmt.Println("-> Checking for Caddy...")
54 _, err = client.Run("which caddy")
55 if err == nil {
56 fmt.Println(" Caddy already installed")
57 } else {
58 fmt.Println(" Installing Caddy...")
59 if err := installCaddy(client); err != nil {
60 return err
61 }
62 fmt.Println(" Caddy installed")
63 }
64
65 fmt.Println("-> Configuring Caddy...")
66 caddyfile := `{
67 email admin@example.com
68}
69
70import /etc/caddy/sites-enabled/*
71`
72 if err := client.WriteSudoFile("/etc/caddy/Caddyfile", caddyfile); err != nil {
73 return fmt.Errorf("error creating Caddyfile: %w", err)
74 }
75 fmt.Println(" Caddyfile created")
76
77 fmt.Println("-> Creating directories...")
78 if _, err := client.RunSudo("mkdir -p /etc/deploy/env"); err != nil {
79 return fmt.Errorf("error creating /etc/deploy/env: %w", err)
80 }
81 if _, err := client.RunSudo("mkdir -p /etc/caddy/sites-enabled"); err != nil {
82 return fmt.Errorf("error creating /etc/caddy/sites-enabled: %w", err)
83 }
84 fmt.Println(" Directories created")
85
86 fmt.Println("-> Starting Caddy...")
87 if _, err := client.RunSudo("systemctl enable caddy"); err != nil {
88 return fmt.Errorf("error enabling Caddy: %w", err)
89 }
90 if _, err := client.RunSudo("systemctl restart caddy"); err != nil {
91 return fmt.Errorf("error starting Caddy: %w", err)
92 }
93 fmt.Println(" Caddy started")
94
95 fmt.Println("-> Verifying installation...")
96 output, err := client.RunSudo("systemctl is-active caddy")
97 if err != nil || strings.TrimSpace(output) != "active" {
98 fmt.Println(" Warning: Caddy may not be running properly")
99 } else {
100 fmt.Println(" Caddy is active")
101 }
102
103 st.GetHost(host)
104 if st.GetDefaultHost() == "" {
105 st.SetDefaultHost(host)
106 fmt.Printf(" Set %s as default host\n", host)
107 }
108 if err := st.Save(); err != nil {
109 return fmt.Errorf("error saving state: %w", err)
110 }
111
112 fmt.Println("\nVPS initialized successfully!")
113 fmt.Println("\nNext steps:")
114 fmt.Println(" 1. Deploy a Go app:")
115 fmt.Printf(" deploy --binary ./myapp --domain api.example.com\n")
116 fmt.Println(" 2. Deploy a static site:")
117 fmt.Printf(" deploy --static --dir ./dist --domain example.com\n")
118 return nil
119}
120
121func installCaddy(client *ssh.Client) error {
122 commands := []string{
123 "apt-get update",
124 "apt-get install -y debian-keyring debian-archive-keyring apt-transport-https curl",
125 "curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg",
126 "curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list",
127 "apt-get update",
128 "apt-get install -y caddy",
129 }
130
131 for _, cmd := range commands {
132 if _, err := client.RunSudo(cmd); err != nil {
133 return fmt.Errorf("error running: %s: %w", cmd, err)
134 }
135 }
136 return nil
137}