package main import ( "flag" "fmt" "os" "github.com/bdw/deploy/internal/ssh" "github.com/bdw/deploy/internal/state" ) func runRemove(args []string) { fs := flag.NewFlagSet("remove", flag.ExitOnError) host := fs.String("host", "", "VPS host (SSH config alias or user@host)") fs.Parse(args) if len(fs.Args()) == 0 { fmt.Fprintf(os.Stderr, "Error: app name is required\n") fmt.Fprintf(os.Stderr, "Usage: deploy remove --host user@vps-ip\n") os.Exit(1) } name := fs.Args()[0] // Load state st, err := state.Load() if err != nil { fmt.Fprintf(os.Stderr, "Error loading state: %v\n", err) os.Exit(1) } // Get host from flag or state default if *host == "" { *host = st.GetDefaultHost() } if *host == "" { fmt.Fprintf(os.Stderr, "Error: --host is required\n") fs.Usage() os.Exit(1) } // Get app info app, err := st.GetApp(*host, name) if err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) os.Exit(1) } fmt.Printf("Removing deployment: %s\n", name) // Connect to VPS client, err := ssh.Connect(*host) if err != nil { fmt.Fprintf(os.Stderr, "Error connecting to VPS: %v\n", err) os.Exit(1) } defer client.Close() if app.Type == "app" { // Stop and disable service fmt.Println("→ Stopping service...") client.RunSudo(fmt.Sprintf("systemctl stop %s", name)) client.RunSudo(fmt.Sprintf("systemctl disable %s", name)) // Remove systemd unit client.RunSudo(fmt.Sprintf("rm -f /etc/systemd/system/%s.service", name)) client.RunSudo("systemctl daemon-reload") // Remove binary client.RunSudo(fmt.Sprintf("rm -f /usr/local/bin/%s", name)) // Remove working directory client.RunSudo(fmt.Sprintf("rm -rf /var/lib/%s", name)) // Remove env file client.RunSudo(fmt.Sprintf("rm -f /etc/deploy/env/%s.env", name)) // Remove user client.RunSudo(fmt.Sprintf("userdel %s", name)) } else { // Remove static site files fmt.Println("→ Removing files...") client.RunSudo(fmt.Sprintf("rm -rf /var/www/%s", name)) } // Remove Caddy config fmt.Println("→ Removing Caddy config...") client.RunSudo(fmt.Sprintf("rm -f /etc/caddy/sites-enabled/%s.caddy", name)) // Reload Caddy fmt.Println("→ Reloading Caddy...") if _, err := client.RunSudo("systemctl reload caddy"); err != nil { fmt.Fprintf(os.Stderr, "Warning: Error reloading Caddy: %v\n", err) } // Update state if err := st.RemoveApp(*host, name); err != nil { fmt.Fprintf(os.Stderr, "Error updating state: %v\n", err) os.Exit(1) } if err := st.Save(); err != nil { fmt.Fprintf(os.Stderr, "Error saving state: %v\n", err) os.Exit(1) } fmt.Printf("✓ Deployment removed successfully\n") } func runLogs(args []string) { fs := flag.NewFlagSet("logs", flag.ExitOnError) host := fs.String("host", "", "VPS host (SSH config alias or user@host)") follow := fs.Bool("f", false, "Follow logs") lines := fs.Int("n", 50, "Number of lines to show") fs.Parse(args) if len(fs.Args()) == 0 { fmt.Fprintf(os.Stderr, "Error: app name is required\n") fmt.Fprintf(os.Stderr, "Usage: deploy logs --host user@vps-ip\n") os.Exit(1) } name := fs.Args()[0] // Load state st, err := state.Load() if err != nil { fmt.Fprintf(os.Stderr, "Error loading state: %v\n", err) os.Exit(1) } // Get host from flag or state default if *host == "" { *host = st.GetDefaultHost() } if *host == "" { fmt.Fprintf(os.Stderr, "Error: --host is required\n") fs.Usage() os.Exit(1) } app, err := st.GetApp(*host, name) if err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) os.Exit(1) } if app.Type != "app" { fmt.Fprintf(os.Stderr, "Error: logs are only available for apps, not static sites\n") os.Exit(1) } // Connect to VPS client, err := ssh.Connect(*host) if err != nil { fmt.Fprintf(os.Stderr, "Error connecting to VPS: %v\n", err) os.Exit(1) } defer client.Close() // Build journalctl command cmd := fmt.Sprintf("journalctl -u %s -n %d", name, *lines) if *follow { cmd += " -f" } // Run command if *follow { // Stream output for follow mode (no sudo needed for journalctl) if err := client.RunStream(cmd); err != nil { fmt.Fprintf(os.Stderr, "Error fetching logs: %v\n", err) os.Exit(1) } } else { // Buffer output for non-follow mode (no sudo needed for journalctl) output, err := client.Run(cmd) if err != nil { fmt.Fprintf(os.Stderr, "Error fetching logs: %v\n", err) os.Exit(1) } fmt.Print(output) } } func runStatus(args []string) { fs := flag.NewFlagSet("status", flag.ExitOnError) host := fs.String("host", "", "VPS host (SSH config alias or user@host)") fs.Parse(args) if len(fs.Args()) == 0 { fmt.Fprintf(os.Stderr, "Error: app name is required\n") fmt.Fprintf(os.Stderr, "Usage: deploy status --host user@vps-ip\n") os.Exit(1) } name := fs.Args()[0] // Load state st, err := state.Load() if err != nil { fmt.Fprintf(os.Stderr, "Error loading state: %v\n", err) os.Exit(1) } // Get host from flag or state default if *host == "" { *host = st.GetDefaultHost() } if *host == "" { fmt.Fprintf(os.Stderr, "Error: --host is required\n") fs.Usage() os.Exit(1) } app, err := st.GetApp(*host, name) if err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) os.Exit(1) } if app.Type != "app" { fmt.Fprintf(os.Stderr, "Error: status is only available for apps, not static sites\n") os.Exit(1) } // Connect to VPS client, err := ssh.Connect(*host) if err != nil { fmt.Fprintf(os.Stderr, "Error connecting to VPS: %v\n", err) os.Exit(1) } defer client.Close() // Get status output, err := client.RunSudo(fmt.Sprintf("systemctl status %s", name)) if err != nil { // systemctl status returns non-zero for non-active services // but we still want to show the output fmt.Print(output) return } fmt.Print(output) } func runRestart(args []string) { fs := flag.NewFlagSet("restart", flag.ExitOnError) host := fs.String("host", "", "VPS host (SSH config alias or user@host)") fs.Parse(args) if len(fs.Args()) == 0 { fmt.Fprintf(os.Stderr, "Error: app name is required\n") fmt.Fprintf(os.Stderr, "Usage: deploy restart --host user@vps-ip\n") os.Exit(1) } name := fs.Args()[0] // Load state st, err := state.Load() if err != nil { fmt.Fprintf(os.Stderr, "Error loading state: %v\n", err) os.Exit(1) } // Get host from flag or state default if *host == "" { *host = st.GetDefaultHost() } if *host == "" { fmt.Fprintf(os.Stderr, "Error: --host is required\n") fs.Usage() os.Exit(1) } app, err := st.GetApp(*host, name) if err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) os.Exit(1) } if app.Type != "app" { fmt.Fprintf(os.Stderr, "Error: restart is only available for apps, not static sites\n") os.Exit(1) } // Connect to VPS client, err := ssh.Connect(*host) if err != nil { fmt.Fprintf(os.Stderr, "Error connecting to VPS: %v\n", err) os.Exit(1) } defer client.Close() // Restart service fmt.Printf("Restarting %s...\n", name) if _, err := client.RunSudo(fmt.Sprintf("systemctl restart %s", name)); err != nil { fmt.Fprintf(os.Stderr, "Error restarting service: %v\n", err) os.Exit(1) } fmt.Println("✓ Service restarted successfully") }