summaryrefslogtreecommitdiffstats
path: root/cmd/deploy/init.go
diff options
context:
space:
mode:
authorbndw <ben@bdw.to>2026-01-23 20:54:46 -0800
committerbndw <ben@bdw.to>2026-01-23 20:54:46 -0800
commit98b9af372025595e8a4255538e2836e019311474 (patch)
tree0a26fa5a8a19ea8565da6d63e1f19c21fc170d12 /cmd/deploy/init.go
parent7fcb9dfa87310e91b527829ece9989decb6fda64 (diff)
Add deploy command and fix static site naming
Static sites now default to using the domain as the name instead of the source directory basename, preventing conflicts when multiple sites use the same directory name (e.g., dist). Also fixes .gitignore to not exclude cmd/deploy/ directory.
Diffstat (limited to 'cmd/deploy/init.go')
-rw-r--r--cmd/deploy/init.go154
1 files changed, 154 insertions, 0 deletions
diff --git a/cmd/deploy/init.go b/cmd/deploy/init.go
new file mode 100644
index 0000000..72c7d53
--- /dev/null
+++ b/cmd/deploy/init.go
@@ -0,0 +1,154 @@
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 runInit(args []string) {
15 fs := flag.NewFlagSet("init", flag.ExitOnError)
16 host := fs.String("host", "", "VPS host (SSH config alias or user@host)")
17 fs.Parse(args)
18
19 // Get host from flag or config
20 if *host == "" {
21 cfg, err := config.Load()
22 if err != nil {
23 fmt.Fprintf(os.Stderr, "Error loading config: %v\n", err)
24 os.Exit(1)
25 }
26 *host = cfg.Host
27 }
28
29 if *host == "" {
30 fmt.Fprintf(os.Stderr, "Error: --host is required\n")
31 fs.Usage()
32 os.Exit(1)
33 }
34
35 fmt.Printf("Initializing VPS: %s\n", *host)
36
37 // Connect to VPS
38 client, err := ssh.Connect(*host)
39 if err != nil {
40 fmt.Fprintf(os.Stderr, "Error connecting to VPS: %v\n", err)
41 os.Exit(1)
42 }
43 defer client.Close()
44
45 // Detect OS
46 fmt.Println("→ Detecting OS...")
47 osRelease, err := client.Run("cat /etc/os-release")
48 if err != nil {
49 fmt.Fprintf(os.Stderr, "Error detecting OS: %v\n", err)
50 os.Exit(1)
51 }
52
53 if !strings.Contains(osRelease, "Ubuntu") && !strings.Contains(osRelease, "Debian") {
54 fmt.Fprintf(os.Stderr, "Error: Unsupported OS (only Ubuntu and Debian are supported)\n")
55 os.Exit(1)
56 }
57 fmt.Println(" ✓ Detected Ubuntu/Debian")
58
59 // Check if Caddy is already installed
60 fmt.Println("→ Checking for Caddy...")
61 _, err = client.Run("which caddy")
62 if err == nil {
63 fmt.Println(" ✓ Caddy already installed")
64 } else {
65 // Install Caddy
66 fmt.Println(" Installing Caddy...")
67 installCaddy(client)
68 fmt.Println(" ✓ Caddy installed")
69 }
70
71 // Create Caddyfile
72 fmt.Println("→ Configuring Caddy...")
73 caddyfile := `{
74 email admin@example.com
75}
76
77import /etc/caddy/sites-enabled/*
78`
79 if err := client.WriteSudoFile("/etc/caddy/Caddyfile", caddyfile); err != nil {
80 fmt.Fprintf(os.Stderr, "Error creating Caddyfile: %v\n", err)
81 os.Exit(1)
82 }
83 fmt.Println(" ✓ Caddyfile created")
84
85 // Create directories
86 fmt.Println("→ Creating directories...")
87 if _, err := client.RunSudo("mkdir -p /etc/deploy/env"); err != nil {
88 fmt.Fprintf(os.Stderr, "Error creating /etc/deploy/env: %v\n", err)
89 os.Exit(1)
90 }
91 if _, err := client.RunSudo("mkdir -p /etc/caddy/sites-enabled"); err != nil {
92 fmt.Fprintf(os.Stderr, "Error creating /etc/caddy/sites-enabled: %v\n", err)
93 os.Exit(1)
94 }
95 fmt.Println(" ✓ Directories created")
96
97 // Enable and start Caddy
98 fmt.Println("→ Starting Caddy...")
99 if _, err := client.RunSudo("systemctl enable caddy"); err != nil {
100 fmt.Fprintf(os.Stderr, "Error enabling Caddy: %v\n", err)
101 os.Exit(1)
102 }
103 if _, err := client.RunSudo("systemctl restart caddy"); err != nil {
104 fmt.Fprintf(os.Stderr, "Error starting Caddy: %v\n", err)
105 os.Exit(1)
106 }
107 fmt.Println(" ✓ Caddy started")
108
109 // Verify Caddy is running
110 fmt.Println("→ Verifying installation...")
111 output, err := client.RunSudo("systemctl is-active caddy")
112 if err != nil || strings.TrimSpace(output) != "active" {
113 fmt.Fprintf(os.Stderr, "Warning: Caddy may not be running properly\n")
114 } else {
115 fmt.Println(" ✓ Caddy is active")
116 }
117
118 // Initialize local state if needed
119 st, err := state.Load()
120 if err != nil {
121 fmt.Fprintf(os.Stderr, "Error loading state: %v\n", err)
122 os.Exit(1)
123 }
124 st.GetHost(*host) // Ensure host exists in state
125 if err := st.Save(); err != nil {
126 fmt.Fprintf(os.Stderr, "Error saving state: %v\n", err)
127 os.Exit(1)
128 }
129
130 fmt.Println("\n✓ VPS initialized successfully!")
131 fmt.Println("\nNext steps:")
132 fmt.Println(" 1. Deploy a Go app:")
133 fmt.Printf(" deploy deploy --host %s --binary ./myapp --domain api.example.com\n", *host)
134 fmt.Println(" 2. Deploy a static site:")
135 fmt.Printf(" deploy deploy --host %s --static --dir ./dist --domain example.com\n", *host)
136}
137
138func installCaddy(client *ssh.Client) {
139 commands := []string{
140 "apt-get update",
141 "apt-get install -y debian-keyring debian-archive-keyring apt-transport-https curl",
142 "curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg",
143 "curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list",
144 "apt-get update",
145 "apt-get install -y caddy",
146 }
147
148 for _, cmd := range commands {
149 if _, err := client.RunSudo(cmd); err != nil {
150 fmt.Fprintf(os.Stderr, "Error running: %s\nError: %v\n", cmd, err)
151 os.Exit(1)
152 }
153 }
154}