From 87752492d0dc7df3cf78011d5ce315a3eb0cad51 Mon Sep 17 00:00:00 2001 From: bndw Date: Fri, 23 Jan 2026 21:52:50 -0800 Subject: 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. --- cmd/deploy/host/init.go | 137 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 cmd/deploy/host/init.go (limited to 'cmd/deploy/host/init.go') 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 @@ +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 +} -- cgit v1.2.3