From 5861e465a2ccf31d87ea25ac145770786f9cc96e Mon Sep 17 00:00:00 2001 From: bndw Date: Sat, 24 Jan 2026 09:48:34 -0800 Subject: Rename project from deploy to ship - Rename module to github.com/bdw/ship - Rename cmd/deploy to cmd/ship - Update all import paths - Update config path from ~/.config/deploy to ~/.config/ship - Update VPS env path from /etc/deploy to /etc/ship - Update README, Makefile, and docs --- cmd/deploy/root.go | 377 ----------------------------------------------------- 1 file changed, 377 deletions(-) delete mode 100644 cmd/deploy/root.go (limited to 'cmd/deploy/root.go') diff --git a/cmd/deploy/root.go b/cmd/deploy/root.go deleted file mode 100644 index adbc7c8..0000000 --- a/cmd/deploy/root.go +++ /dev/null @@ -1,377 +0,0 @@ -package main - -import ( - "bufio" - "fmt" - "os" - "path/filepath" - "strconv" - "strings" - - "github.com/bdw/deploy/internal/ssh" - "github.com/bdw/deploy/internal/state" - "github.com/bdw/deploy/internal/templates" - "github.com/spf13/cobra" -) - -func runDeploy(cmd *cobra.Command, args []string) error { - flags := cmd.Flags() - - binary, _ := flags.GetString("binary") - static, _ := flags.GetBool("static") - dir, _ := flags.GetString("dir") - domain, _ := flags.GetString("domain") - name, _ := flags.GetString("name") - port, _ := flags.GetInt("port") - envVars, _ := flags.GetStringArray("env") - envFile, _ := flags.GetString("env-file") - binaryArgs, _ := flags.GetString("args") - files, _ := flags.GetStringArray("file") - - // Get host from flag or state default - host := hostFlag - if host == "" { - st, err := state.Load() - if err != nil { - return fmt.Errorf("error loading state: %w", err) - } - host = st.GetDefaultHost() - } - - // If no flags provided, show help - if domain == "" && binary == "" && !static { - return cmd.Help() - } - - if host == "" || domain == "" { - return fmt.Errorf("--host and --domain are required") - } - - if static { - return deployStatic(host, domain, name, dir) - } - return deployApp(host, domain, name, binary, port, envVars, envFile, binaryArgs, files) -} - -func deployApp(host, domain, name, binaryPath string, portOverride int, envVars []string, envFile, args string, files []string) error { - if binaryPath == "" { - return fmt.Errorf("--binary is required") - } - - if name == "" { - name = filepath.Base(binaryPath) - } - - if _, err := os.Stat(binaryPath); err != nil { - return fmt.Errorf("binary not found: %s", binaryPath) - } - - fmt.Printf("Deploying app: %s\n", name) - fmt.Printf(" Domain: %s\n", domain) - fmt.Printf(" Binary: %s\n", binaryPath) - - st, err := state.Load() - if err != nil { - return fmt.Errorf("error loading state: %w", err) - } - - existingApp, _ := st.GetApp(host, name) - var port int - if existingApp != nil { - port = existingApp.Port - fmt.Printf(" Updating existing deployment (port %d)\n", port) - } else { - if portOverride > 0 { - port = portOverride - } else { - port = st.AllocatePort(host) - } - fmt.Printf(" Allocated port: %d\n", port) - } - - env := make(map[string]string) - if existingApp != nil { - for k, v := range existingApp.Env { - env[k] = v - } - if args == "" && existingApp.Args != "" { - args = existingApp.Args - } - if len(files) == 0 && len(existingApp.Files) > 0 { - files = existingApp.Files - } - } - - for _, e := range envVars { - parts := strings.SplitN(e, "=", 2) - if len(parts) == 2 { - env[parts[0]] = parts[1] - } - } - - if envFile != "" { - fileEnv, err := parseEnvFile(envFile) - if err != nil { - return fmt.Errorf("error reading env file: %w", err) - } - for k, v := range fileEnv { - env[k] = v - } - } - - env["PORT"] = strconv.Itoa(port) - - client, err := ssh.Connect(host) - if err != nil { - return fmt.Errorf("error connecting to VPS: %w", err) - } - defer client.Close() - - fmt.Println("-> Uploading binary...") - remoteTmpPath := fmt.Sprintf("/tmp/%s", name) - if err := client.Upload(binaryPath, remoteTmpPath); err != nil { - return fmt.Errorf("error uploading binary: %w", err) - } - - fmt.Println("-> Creating system user...") - client.RunSudo(fmt.Sprintf("useradd -r -s /bin/false %s", name)) - - fmt.Println("-> Setting up directories...") - workDir := fmt.Sprintf("/var/lib/%s", name) - if _, err := client.RunSudo(fmt.Sprintf("mkdir -p %s", workDir)); err != nil { - return fmt.Errorf("error creating work directory: %w", err) - } - if _, err := client.RunSudo(fmt.Sprintf("chown %s:%s %s", name, name, workDir)); err != nil { - return fmt.Errorf("error setting work directory ownership: %w", err) - } - - fmt.Println("-> Installing binary...") - binaryDest := fmt.Sprintf("/usr/local/bin/%s", name) - if _, err := client.RunSudo(fmt.Sprintf("mv %s %s", remoteTmpPath, binaryDest)); err != nil { - return fmt.Errorf("error moving binary: %w", err) - } - if _, err := client.RunSudo(fmt.Sprintf("chmod +x %s", binaryDest)); err != nil { - return fmt.Errorf("error making binary executable: %w", err) - } - - if len(files) > 0 { - fmt.Println("-> Uploading config files...") - for _, file := range files { - if _, err := os.Stat(file); err != nil { - return fmt.Errorf("config file not found: %s", file) - } - - remotePath := fmt.Sprintf("%s/%s", workDir, filepath.Base(file)) - remoteTmpPath := fmt.Sprintf("/tmp/%s_%s", name, filepath.Base(file)) - - if err := client.Upload(file, remoteTmpPath); err != nil { - return fmt.Errorf("error uploading config file %s: %w", file, err) - } - - if _, err := client.RunSudo(fmt.Sprintf("mv %s %s", remoteTmpPath, remotePath)); err != nil { - return fmt.Errorf("error moving config file %s: %w", file, err) - } - - if _, err := client.RunSudo(fmt.Sprintf("chown %s:%s %s", name, name, remotePath)); err != nil { - return fmt.Errorf("error setting config file ownership %s: %w", file, err) - } - - fmt.Printf(" Uploaded: %s\n", file) - } - } - - fmt.Println("-> Creating environment file...") - envFilePath := fmt.Sprintf("/etc/deploy/env/%s.env", name) - envContent := "" - for k, v := range env { - envContent += fmt.Sprintf("%s=%s\n", k, v) - } - if err := client.WriteSudoFile(envFilePath, envContent); err != nil { - return fmt.Errorf("error creating env file: %w", err) - } - if _, err := client.RunSudo(fmt.Sprintf("chmod 600 %s", envFilePath)); err != nil { - return fmt.Errorf("error setting env file permissions: %w", err) - } - if _, err := client.RunSudo(fmt.Sprintf("chown %s:%s %s", name, name, envFilePath)); err != nil { - return fmt.Errorf("error setting env file ownership: %w", err) - } - - fmt.Println("-> Creating systemd service...") - serviceContent, err := templates.SystemdService(map[string]string{ - "Name": name, - "User": name, - "WorkDir": workDir, - "BinaryPath": binaryDest, - "Port": strconv.Itoa(port), - "EnvFile": envFilePath, - "Args": args, - }) - if err != nil { - return fmt.Errorf("error generating systemd unit: %w", err) - } - - servicePath := fmt.Sprintf("/etc/systemd/system/%s.service", name) - if err := client.WriteSudoFile(servicePath, serviceContent); err != nil { - return fmt.Errorf("error creating systemd unit: %w", err) - } - - fmt.Println("-> Configuring Caddy...") - caddyContent, err := templates.AppCaddy(map[string]string{ - "Domain": domain, - "Port": strconv.Itoa(port), - }) - if err != nil { - return fmt.Errorf("error generating Caddy config: %w", err) - } - - caddyPath := fmt.Sprintf("/etc/caddy/sites-enabled/%s.caddy", name) - if err := client.WriteSudoFile(caddyPath, caddyContent); err != nil { - return fmt.Errorf("error creating Caddy config: %w", err) - } - - fmt.Println("-> Reloading systemd...") - if _, err := client.RunSudo("systemctl daemon-reload"); err != nil { - return fmt.Errorf("error reloading systemd: %w", err) - } - - fmt.Println("-> Starting service...") - if _, err := client.RunSudo(fmt.Sprintf("systemctl enable %s", name)); err != nil { - return fmt.Errorf("error enabling service: %w", err) - } - if _, err := client.RunSudo(fmt.Sprintf("systemctl restart %s", name)); err != nil { - return fmt.Errorf("error starting service: %w", err) - } - - fmt.Println("-> Reloading Caddy...") - if _, err := client.RunSudo("systemctl reload caddy"); err != nil { - return fmt.Errorf("error reloading Caddy: %w", err) - } - - st.AddApp(host, name, &state.App{ - Type: "app", - Domain: domain, - Port: port, - Env: env, - Args: args, - Files: files, - }) - if err := st.Save(); err != nil { - return fmt.Errorf("error saving state: %w", err) - } - - fmt.Printf("\n App deployed successfully!\n") - fmt.Printf(" https://%s (may take a minute for HTTPS cert)\n", domain) - return nil -} - -func deployStatic(host, domain, name, dir string) error { - if name == "" { - name = domain - } - - if _, err := os.Stat(dir); err != nil { - return fmt.Errorf("directory not found: %s", dir) - } - - fmt.Printf("Deploying static site: %s\n", name) - fmt.Printf(" Domain: %s\n", domain) - fmt.Printf(" Directory: %s\n", dir) - - st, err := state.Load() - if err != nil { - return fmt.Errorf("error loading state: %w", err) - } - - client, err := ssh.Connect(host) - if err != nil { - return fmt.Errorf("error connecting to VPS: %w", err) - } - defer client.Close() - - remoteDir := fmt.Sprintf("/var/www/%s", name) - fmt.Println("-> Creating remote directory...") - if _, err := client.RunSudo(fmt.Sprintf("mkdir -p %s", remoteDir)); err != nil { - return fmt.Errorf("error creating remote directory: %w", err) - } - - currentUser, err := client.Run("whoami") - if err != nil { - return fmt.Errorf("error getting current user: %w", err) - } - currentUser = strings.TrimSpace(currentUser) - - if _, err := client.RunSudo(fmt.Sprintf("chown -R %s:%s %s", currentUser, currentUser, remoteDir)); err != nil { - return fmt.Errorf("error setting temporary ownership: %w", err) - } - - fmt.Println("-> Uploading files...") - if err := client.UploadDir(dir, remoteDir); err != nil { - return fmt.Errorf("error uploading files: %w", err) - } - - fmt.Println("-> Setting permissions...") - if _, err := client.RunSudo(fmt.Sprintf("chown -R www-data:www-data %s", remoteDir)); err != nil { - return fmt.Errorf("error setting ownership: %w", err) - } - if _, err := client.RunSudo(fmt.Sprintf("chmod -R 755 %s", remoteDir)); err != nil { - return fmt.Errorf("error setting directory permissions: %w", err) - } - if _, err := client.RunSudo(fmt.Sprintf("find %s -type f -exec chmod 644 {} \\;", remoteDir)); err != nil { - return fmt.Errorf("error setting file permissions: %w", err) - } - - fmt.Println("-> Configuring Caddy...") - caddyContent, err := templates.StaticCaddy(map[string]string{ - "Domain": domain, - "RootDir": remoteDir, - }) - if err != nil { - return fmt.Errorf("error generating Caddy config: %w", err) - } - - caddyPath := fmt.Sprintf("/etc/caddy/sites-enabled/%s.caddy", name) - if err := client.WriteSudoFile(caddyPath, caddyContent); err != nil { - return fmt.Errorf("error creating Caddy config: %w", err) - } - - fmt.Println("-> Reloading Caddy...") - if _, err := client.RunSudo("systemctl reload caddy"); err != nil { - return fmt.Errorf("error reloading Caddy: %w", err) - } - - st.AddApp(host, name, &state.App{ - Type: "static", - Domain: domain, - }) - if err := st.Save(); err != nil { - return fmt.Errorf("error saving state: %w", err) - } - - fmt.Printf("\n Static site deployed successfully!\n") - fmt.Printf(" https://%s (may take a minute for HTTPS cert)\n", domain) - return nil -} - -func parseEnvFile(path string) (map[string]string, error) { - file, err := os.Open(path) - if err != nil { - return nil, err - } - defer file.Close() - - env := make(map[string]string) - scanner := bufio.NewScanner(file) - for scanner.Scan() { - line := strings.TrimSpace(scanner.Text()) - if line == "" || strings.HasPrefix(line, "#") { - continue - } - - parts := strings.SplitN(line, "=", 2) - if len(parts) == 2 { - env[parts[0]] = parts[1] - } - } - - return env, scanner.Err() -} -- cgit v1.2.3