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