From 8a3cff0dd7eb88cadb73a6df4e14f85450d63317 Mon Sep 17 00:00:00 2001 From: bndw Date: Sat, 24 Jan 2026 16:55:52 -0800 Subject: Add auto-generated subdomain feature When a base domain is configured on a host (e.g., apps.example.com), deployments automatically get a subdomain ({name}.apps.example.com). Custom --domain can still be provided to route both domains. - Add BaseDomain field to Host state - Add --base-domain flag to host init - Add 'ship host set-domain' command to update base domain - Update deploy flow to auto-generate subdomains - Fix error display (errors were being silently swallowed) - Remove placeholder email from Caddyfile template --- cmd/ship/root.go | 63 ++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 48 insertions(+), 15 deletions(-) (limited to 'cmd/ship/root.go') diff --git a/cmd/ship/root.go b/cmd/ship/root.go index e5d6753..81d33d1 100644 --- a/cmd/ship/root.go +++ b/cmd/ship/root.go @@ -43,14 +43,48 @@ func runDeploy(cmd *cobra.Command, args []string) error { return cmd.Help() } - if host == "" || domain == "" { - return fmt.Errorf("--host and --domain are required") + if host == "" { + return fmt.Errorf("--host is required") + } + + // Load state to check base domain + st, err := state.Load() + if err != nil { + return fmt.Errorf("error loading state: %w", err) + } + hostState := st.GetHost(host) + + if domain == "" && hostState.BaseDomain == "" { + return fmt.Errorf("--domain required (or configure base domain with 'ship host init --base-domain')") } + // Infer name early so we can use it for subdomain generation + inferredName := name + if inferredName == "" { + if static { + inferredName = domain + if inferredName == "" && hostState.BaseDomain != "" { + inferredName = filepath.Base(dir) + } + } else { + inferredName = filepath.Base(binary) + } + } + + // Generate subdomain if base domain configured + var domains []string + if hostState.BaseDomain != "" { + domains = append(domains, inferredName+"."+hostState.BaseDomain) + } + if domain != "" { + domains = append(domains, domain) + } + combinedDomains := strings.Join(domains, ", ") + if static { - return deployStatic(host, domain, name, dir) + return deployStatic(host, combinedDomains, inferredName, dir) } - return deployApp(host, domain, name, binary, port, envVars, envFile, binaryArgs, files) + return deployApp(host, combinedDomains, inferredName, binary, port, envVars, envFile, binaryArgs, files) } func deployApp(host, domain, name, binaryPath string, portOverride int, envVars []string, envFile, args string, files []string) error { @@ -58,16 +92,12 @@ func deployApp(host, domain, name, binaryPath string, portOverride int, envVars 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(" Domain(s): %s\n", domain) fmt.Printf(" Binary: %s\n", binaryPath) st, err := state.Load() @@ -260,21 +290,21 @@ func deployApp(host, domain, name, binaryPath string, portOverride int, envVars } fmt.Printf("\n App deployed successfully!\n") - fmt.Printf(" https://%s (may take a minute for HTTPS cert)\n", domain) + // Show first domain in the URL message + primaryDomain := strings.Split(domain, ",")[0] + primaryDomain = strings.TrimSpace(primaryDomain) + fmt.Printf(" https://%s (may take a minute for HTTPS cert)\n", primaryDomain) 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(" Domain(s): %s\n", domain) fmt.Printf(" Directory: %s\n", dir) st, err := state.Load() @@ -348,7 +378,10 @@ func deployStatic(host, domain, name, dir string) error { } fmt.Printf("\n Static site deployed successfully!\n") - fmt.Printf(" https://%s (may take a minute for HTTPS cert)\n", domain) + // Show first domain in the URL message + primaryDomain := strings.Split(domain, ",")[0] + primaryDomain = strings.TrimSpace(primaryDomain) + fmt.Printf(" https://%s (may take a minute for HTTPS cert)\n", primaryDomain) return nil } -- cgit v1.2.3