diff options
| author | bndw <ben@bdw.to> | 2026-01-23 20:54:46 -0800 |
|---|---|---|
| committer | bndw <ben@bdw.to> | 2026-01-23 20:54:46 -0800 |
| commit | 98b9af372025595e8a4255538e2836e019311474 (patch) | |
| tree | 0a26fa5a8a19ea8565da6d63e1f19c21fc170d12 /cmd/deploy/init.go | |
| parent | 7fcb9dfa87310e91b527829ece9989decb6fda64 (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.go | 154 |
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 @@ | |||
| 1 | package main | ||
| 2 | |||
| 3 | import ( | ||
| 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 | |||
| 14 | func 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 | |||
| 77 | import /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 | |||
| 138 | func 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 | } | ||
