From b9120489c454877ff623e65db48ec97f402bf8ed Mon Sep 17 00:00:00 2001 From: bndw Date: Sat, 14 Feb 2026 08:11:33 -0800 Subject: Store deployment configs locally for binary deployments For binary and static deployments, .ship/service and .ship/Caddyfile are now written to the local working directory (similar to git deployments) instead of being regenerated on every deployment. - On initial deployment, create local .ship/ directory with generated configs - On subsequent deployments, upload from local .ship/ files - Caddyfile is never regenerated, preserving custom routes - Systemd service is regenerated only when --memory, --cpu, or --args change This prevents custom Caddyfile routes from being overwritten and makes binary deployment workflow consistent with git deployment workflow. --- cmd/ship/deploy.go | 124 +++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 88 insertions(+), 36 deletions(-) (limited to 'cmd/ship/deploy.go') diff --git a/cmd/ship/deploy.go b/cmd/ship/deploy.go index 86d4878..6894e21 100644 --- a/cmd/ship/deploy.go +++ b/cmd/ship/deploy.go @@ -337,39 +337,65 @@ func deployApp(st *state.State, opts DeployOptions) error { return fmt.Errorf("error setting env file ownership: %w", err) } - fmt.Println("-> Creating systemd service...") - serviceContent, err := templates.SystemdService(map[string]string{ - "Name": opts.Name, - "User": opts.Name, - "WorkDir": workDir, - "BinaryPath": binaryDest, - "Port": strconv.Itoa(port), - "EnvFile": envFilePath, - "Args": opts.Args, - "Memory": opts.Memory, - "CPU": opts.CPU, - }) - if err != nil { - return fmt.Errorf("error generating systemd unit: %w", err) + // Create local .ship directory for deployment configs + if !opts.IsUpdate { + fmt.Println("-> Creating local .ship directory...") + if err := os.MkdirAll(".ship", 0755); err != nil { + return fmt.Errorf("error creating .ship directory: %w", err) + } + + fmt.Println("-> Generating systemd service...") + serviceContent, err := templates.SystemdService(map[string]string{ + "Name": opts.Name, + "User": opts.Name, + "WorkDir": workDir, + "BinaryPath": binaryDest, + "Port": strconv.Itoa(port), + "EnvFile": envFilePath, + "Args": opts.Args, + "Memory": opts.Memory, + "CPU": opts.CPU, + }) + if err != nil { + return fmt.Errorf("error generating systemd unit: %w", err) + } + if err := os.WriteFile(".ship/service", []byte(serviceContent), 0644); err != nil { + return fmt.Errorf("error writing .ship/service: %w", err) + } + + fmt.Println("-> Generating Caddyfile...") + caddyContent, err := templates.AppCaddy(map[string]string{ + "Domain": opts.Domain, + "Port": strconv.Itoa(port), + }) + if err != nil { + return fmt.Errorf("error generating Caddy config: %w", err) + } + if err := os.WriteFile(".ship/Caddyfile", []byte(caddyContent), 0644); err != nil { + return fmt.Errorf("error writing .ship/Caddyfile: %w", err) + } } + // Upload systemd service from .ship/service + fmt.Println("-> Installing systemd service...") + serviceContent, err := os.ReadFile(".ship/service") + if err != nil { + return fmt.Errorf("error reading .ship/service: %w (run initial deployment first)", err) + } servicePath := fmt.Sprintf("/etc/systemd/system/%s.service", opts.Name) - if err := client.WriteSudoFile(servicePath, serviceContent); err != nil { - return fmt.Errorf("error creating systemd unit: %w", err) + if err := client.WriteSudoFile(servicePath, string(serviceContent)); err != nil { + return fmt.Errorf("error installing systemd unit: %w", err) } - fmt.Println("-> Configuring Caddy...") - caddyContent, err := templates.AppCaddy(map[string]string{ - "Domain": opts.Domain, - "Port": strconv.Itoa(port), - }) + // Upload Caddyfile from .ship/Caddyfile + fmt.Println("-> Installing Caddy config...") + caddyContent, err := os.ReadFile(".ship/Caddyfile") if err != nil { - return fmt.Errorf("error generating Caddy config: %w", err) + return fmt.Errorf("error reading .ship/Caddyfile: %w (run initial deployment first)", err) } - caddyPath := fmt.Sprintf("/etc/caddy/sites-enabled/%s.caddy", opts.Name) - if err := client.WriteSudoFile(caddyPath, caddyContent); err != nil { - return fmt.Errorf("error creating Caddy config: %w", err) + if err := client.WriteSudoFile(caddyPath, string(caddyContent)); err != nil { + return fmt.Errorf("error installing Caddy config: %w", err) } fmt.Println("-> Reloading systemd...") @@ -447,7 +473,7 @@ func updateAppConfig(st *state.State, opts DeployOptions) error { // For git-app, the systemd unit comes from .ship/service in the repo, // so we only update the env file and restart. if existingApp.Type != "git-app" { - // Regenerate systemd unit + // Regenerate systemd unit to .ship/service (resource flags are being updated) fmt.Println("-> Updating systemd service...") workDir := fmt.Sprintf("/var/lib/%s", opts.Name) binaryDest := fmt.Sprintf("/usr/local/bin/%s", opts.Name) @@ -466,9 +492,18 @@ func updateAppConfig(st *state.State, opts DeployOptions) error { return fmt.Errorf("error generating systemd unit: %w", err) } + // Write to local .ship/service + if err := os.MkdirAll(".ship", 0755); err != nil { + return fmt.Errorf("error creating .ship directory: %w", err) + } + if err := os.WriteFile(".ship/service", []byte(serviceContent), 0644); err != nil { + return fmt.Errorf("error writing .ship/service: %w", err) + } + + // Upload to server servicePath := fmt.Sprintf("/etc/systemd/system/%s.service", opts.Name) if err := client.WriteSudoFile(servicePath, serviceContent); err != nil { - return fmt.Errorf("error creating systemd unit: %w", err) + return fmt.Errorf("error installing systemd unit: %w", err) } fmt.Println("-> Reloading systemd...") @@ -546,18 +581,35 @@ func deployStatic(st *state.State, opts DeployOptions) error { return fmt.Errorf("error setting file permissions: %w", err) } - fmt.Println("-> Configuring Caddy...") - caddyContent, err := templates.StaticCaddy(map[string]string{ - "Domain": opts.Domain, - "RootDir": remoteDir, - }) - if err != nil { - return fmt.Errorf("error generating Caddy config: %w", err) + // Create local .ship directory and Caddyfile for static sites + if !opts.IsUpdate { + fmt.Println("-> Creating local .ship directory...") + if err := os.MkdirAll(".ship", 0755); err != nil { + return fmt.Errorf("error creating .ship directory: %w", err) + } + + fmt.Println("-> Generating Caddyfile...") + caddyContent, err := templates.StaticCaddy(map[string]string{ + "Domain": opts.Domain, + "RootDir": remoteDir, + }) + if err != nil { + return fmt.Errorf("error generating Caddy config: %w", err) + } + if err := os.WriteFile(".ship/Caddyfile", []byte(caddyContent), 0644); err != nil { + return fmt.Errorf("error writing .ship/Caddyfile: %w", err) + } } + // Upload Caddyfile from .ship/Caddyfile + fmt.Println("-> Installing Caddy config...") + caddyContent, err := os.ReadFile(".ship/Caddyfile") + if err != nil { + return fmt.Errorf("error reading .ship/Caddyfile: %w (run initial deployment first)", err) + } caddyPath := fmt.Sprintf("/etc/caddy/sites-enabled/%s.caddy", opts.Name) - if err := client.WriteSudoFile(caddyPath, caddyContent); err != nil { - return fmt.Errorf("error creating Caddy config: %w", err) + if err := client.WriteSudoFile(caddyPath, string(caddyContent)); err != nil { + return fmt.Errorf("error installing Caddy config: %w", err) } fmt.Println("-> Reloading Caddy...") -- cgit v1.2.3