From c49a067ac84ac5c1691ecf4db6a9bf791246899f Mon Sep 17 00:00:00 2001 From: bndw Date: Tue, 10 Feb 2026 21:29:20 -0800 Subject: Remove --module flag, add --public, make hooks smarter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop git-module type — the post-receive hook now checks for a Dockerfile before building, so repos without one simply skip deploy on push. This covers Go modules and libraries that only need vanity imports. Add --public flag to ship init for per-repo HTTPS clone visibility. Show visibility column in ship list. --- cmd/ship/init.go | 113 +++++++++++++++++++++++++++++++++---------------------- cmd/ship/list.go | 11 +++++- 2 files changed, 76 insertions(+), 48 deletions(-) (limited to 'cmd') diff --git a/cmd/ship/init.go b/cmd/ship/init.go index 167347d..b495702 100644 --- a/cmd/ship/init.go +++ b/cmd/ship/init.go @@ -3,6 +3,7 @@ package main import ( "fmt" "os" + "os/exec" "path/filepath" "strconv" @@ -18,7 +19,8 @@ var initCmd = &cobra.Command{ Long: `Create a bare git repo on the VPS and generate local .ship/ config files. Pushing to the remote triggers an automatic docker build and deploy (for apps) -or a static file checkout (for static sites). +or a static file checkout (for static sites). If no Dockerfile is present in an +app repo, pushes are accepted without triggering a deploy. Examples: # Initialize an app (Docker-based) @@ -28,19 +30,27 @@ Examples: ship init myapp --domain custom.example.com # Initialize a static site - ship init mysite --static`, + ship init mysite --static + + # Initialize a public repo (cloneable via go get / git clone over HTTPS) + ship init mylib --public`, Args: cobra.ExactArgs(1), RunE: runInit, } func init() { initCmd.Flags().Bool("static", false, "Initialize as static site") + initCmd.Flags().Bool("public", false, "Make repo publicly cloneable over HTTPS (for go get)") initCmd.Flags().String("domain", "", "Custom domain (default: name.basedomain)") } func runInit(cmd *cobra.Command, args []string) error { name := args[0] + if err := validateName(name); err != nil { + return err + } static, _ := cmd.Flags().GetBool("static") + public, _ := cmd.Flags().GetBool("public") domain, _ := cmd.Flags().GetString("domain") st, err := state.Load() @@ -66,6 +76,11 @@ func runInit(cmd *cobra.Command, args []string) error { return fmt.Errorf("app %s already exists", name) } + appType := "git-app" + if static { + appType = "git-static" + } + // Resolve domain if domain == "" && hostState.BaseDomain != "" { domain = name + "." + hostState.BaseDomain @@ -74,12 +89,7 @@ func runInit(cmd *cobra.Command, args []string) error { return fmt.Errorf("--domain required (or configure base domain)") } - appType := "git-app" - if static { - appType = "git-static" - } - - // Allocate port for apps + // Allocate port for apps only port := 0 if !static { port = st.AllocatePort(host) @@ -96,10 +106,16 @@ func runInit(cmd *cobra.Command, args []string) error { // Create bare repo fmt.Println("-> Creating bare git repo...") repo := fmt.Sprintf("/srv/git/%s.git", name) - if _, err := client.RunSudo(fmt.Sprintf("sudo -u git git init --bare %s", repo)); err != nil { + if _, err := client.RunSudo(fmt.Sprintf("sudo -u git git init --bare -b main %s", repo)); err != nil { return fmt.Errorf("error creating bare repo: %w", err) } + if public { + if _, err := client.RunSudo(fmt.Sprintf("sudo -u git touch %s/git-daemon-export-ok", repo)); err != nil { + return fmt.Errorf("error setting repo public: %w", err) + } + } + if static { // Create web root fmt.Println("-> Creating web root...") @@ -118,32 +134,10 @@ func runInit(cmd *cobra.Command, args []string) error { if err != nil { return fmt.Errorf("error generating hook: %w", err) } - hookPath := fmt.Sprintf("%s/hooks/post-receive", repo) - if err := client.WriteSudoFile(hookPath, hookContent); err != nil { - return fmt.Errorf("error writing hook: %w", err) - } - if _, err := client.RunSudo(fmt.Sprintf("chmod +x %s", hookPath)); err != nil { - return fmt.Errorf("error making hook executable: %w", err) - } - if _, err := client.RunSudo(fmt.Sprintf("chown git:git %s", hookPath)); err != nil { - return fmt.Errorf("error setting hook ownership: %w", err) + if err := writeHook(client, repo, hookContent); err != nil { + return err } } else { - // Create directories for app - fmt.Println("-> Creating app directories...") - dirs := []string{ - fmt.Sprintf("/var/lib/%s/data", name), - fmt.Sprintf("/var/lib/%s/src", name), - } - for _, dir := range dirs { - if _, err := client.RunSudo(fmt.Sprintf("mkdir -p %s", dir)); err != nil { - return fmt.Errorf("error creating directory %s: %w", dir, err) - } - } - if _, err := client.RunSudo(fmt.Sprintf("chown -R git:git /var/lib/%s", name)); err != nil { - return fmt.Errorf("error setting directory ownership: %w", err) - } - // Create env file fmt.Println("-> Creating environment file...") envContent := fmt.Sprintf("PORT=%d\nDATA_DIR=/data\n", port) @@ -152,7 +146,7 @@ func runInit(cmd *cobra.Command, args []string) error { return fmt.Errorf("error creating env file: %w", err) } - // Write post-receive hook + // Write post-receive hook (handles dir creation on first push) fmt.Println("-> Writing post-receive hook...") hookContent, err := templates.PostReceiveHook(map[string]string{ "Name": name, @@ -160,15 +154,8 @@ func runInit(cmd *cobra.Command, args []string) error { if err != nil { return fmt.Errorf("error generating hook: %w", err) } - hookPath := fmt.Sprintf("%s/hooks/post-receive", repo) - if err := client.WriteSudoFile(hookPath, hookContent); err != nil { - return fmt.Errorf("error writing hook: %w", err) - } - if _, err := client.RunSudo(fmt.Sprintf("chmod +x %s", hookPath)); err != nil { - return fmt.Errorf("error making hook executable: %w", err) - } - if _, err := client.RunSudo(fmt.Sprintf("chown git:git %s", hookPath)); err != nil { - return fmt.Errorf("error setting hook ownership: %w", err) + if err := writeHook(client, repo, hookContent); err != nil { + return err } } @@ -178,6 +165,7 @@ func runInit(cmd *cobra.Command, args []string) error { Domain: domain, Port: port, Repo: repo, + Public: public, }) if err := st.Save(); err != nil { return fmt.Errorf("error saving state: %w", err) @@ -224,8 +212,25 @@ func runInit(cmd *cobra.Command, args []string) error { } } - // Resolve SSH hostname for git remote URL + // Initialize local git repo if needed + if _, err := os.Stat(".git"); os.IsNotExist(err) { + fmt.Println("-> Initializing git repo...") + gitInit := exec.Command("git", "init") + gitInit.Stdout = os.Stdout + gitInit.Stderr = os.Stderr + if err := gitInit.Run(); err != nil { + return fmt.Errorf("error initializing git repo: %w", err) + } + } + + // Add origin remote (replace if it already exists) sshHost := host + remoteURL := fmt.Sprintf("git@%s:%s", sshHost, repo) + exec.Command("git", "remote", "remove", "origin").Run() // ignore error if not exists + addRemote := exec.Command("git", "remote", "add", "origin", remoteURL) + if err := addRemote.Run(); err != nil { + return fmt.Errorf("error adding git remote: %w", err) + } fmt.Printf("\nProject initialized: %s\n", name) fmt.Println("\nGenerated:") @@ -240,8 +245,24 @@ func runInit(cmd *cobra.Command, args []string) error { fmt.Println(" git add .ship/ Dockerfile") } fmt.Println(" git commit -m \"initial deploy\"") - fmt.Printf(" git remote add ship git@%s:%s\n", sshHost, repo) - fmt.Println(" git push ship main") + fmt.Println(" git push origin main") + if !static { + fmt.Println("\n (No Dockerfile? Just push — deploy is skipped until one is added.)") + } return nil } + +func writeHook(client *ssh.Client, repo, content string) error { + hookPath := fmt.Sprintf("%s/hooks/post-receive", repo) + if err := client.WriteSudoFile(hookPath, content); err != nil { + return fmt.Errorf("error writing hook: %w", err) + } + if _, err := client.RunSudo(fmt.Sprintf("chmod +x %s", hookPath)); err != nil { + return fmt.Errorf("error making hook executable: %w", err) + } + if _, err := client.RunSudo(fmt.Sprintf("chown git:git %s", hookPath)); err != nil { + return fmt.Errorf("error setting hook ownership: %w", err) + } + return nil +} diff --git a/cmd/ship/list.go b/cmd/ship/list.go index a10b2ca..af5baf8 100644 --- a/cmd/ship/list.go +++ b/cmd/ship/list.go @@ -37,7 +37,7 @@ func runList(cmd *cobra.Command, args []string) error { } w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0) - fmt.Fprintln(w, "NAME\tTYPE\tDOMAIN\tPORT") + fmt.Fprintln(w, "NAME\tTYPE\tVISIBILITY\tDOMAIN\tPORT") for name, app := range apps { port := "" if app.Type == "app" || app.Type == "git-app" { @@ -47,7 +47,14 @@ func runList(cmd *cobra.Command, args []string) error { if domain == "" { domain = "-" } - fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", name, app.Type, domain, port) + visibility := "" + if app.Repo != "" { + visibility = "private" + if app.Public { + visibility = "public" + } + } + fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", name, app.Type, visibility, domain, port) } w.Flush() return nil -- cgit v1.2.3