summaryrefslogtreecommitdiffstats
path: root/cmd
diff options
context:
space:
mode:
Diffstat (limited to 'cmd')
-rw-r--r--cmd/ship/host_v2.go84
1 files changed, 82 insertions, 2 deletions
diff --git a/cmd/ship/host_v2.go b/cmd/ship/host_v2.go
index 6e1850c..b19c376 100644
--- a/cmd/ship/host_v2.go
+++ b/cmd/ship/host_v2.go
@@ -1,7 +1,11 @@
1package main 1package main
2 2
3import ( 3import (
4 "encoding/json"
4 "fmt" 5 "fmt"
6 "os"
7 "os/exec"
8 "path/filepath"
5 "strings" 9 "strings"
6 10
7 "github.com/bdw/ship/internal/output" 11 "github.com/bdw/ship/internal/output"
@@ -38,10 +42,27 @@ func runHostInitV2(cmd *cobra.Command, args []string) error {
38 output.PrintAndExit(output.Err(output.ErrInvalidArgs, "--domain is required")) 42 output.PrintAndExit(output.Err(output.ErrInvalidArgs, "--domain is required"))
39 } 43 }
40 44
41 // Connect 45 // Ensure SSH key exists
46 keyPath, pubkey, err := ensureSSHKey()
47 if err != nil {
48 output.PrintAndExit(output.Err(output.ErrSSHAuthFailed, "failed to setup SSH key: "+err.Error()))
49 }
50
51 // Try to connect first (to verify key is authorized)
42 client, err := ssh.Connect(host) 52 client, err := ssh.Connect(host)
43 if err != nil { 53 if err != nil {
44 output.PrintAndExit(output.Err(output.ErrSSHConnectFailed, err.Error())) 54 // Connection failed - provide helpful error with pubkey
55 resp := map[string]interface{}{
56 "status": "error",
57 "code": "SSH_AUTH_FAILED",
58 "message": "SSH connection failed. Add this public key to your VPS authorized_keys:",
59 "public_key": pubkey,
60 "key_path": keyPath,
61 "host": host,
62 "setup_command": fmt.Sprintf("ssh-copy-id -i %s %s", keyPath, host),
63 }
64 printJSON(resp)
65 os.Exit(output.ExitSSHFailed)
45 } 66 }
46 defer client.Close() 67 defer client.Close()
47 68
@@ -363,3 +384,62 @@ func setupGitDeployV2(client *ssh.Client, baseDomain string, hostState *state.Ho
363 hostState.GitSetup = true 384 hostState.GitSetup = true
364 return nil 385 return nil
365} 386}
387
388// ensureSSHKey checks for an existing SSH key or generates a new one.
389// Returns the key path, public key contents, and any error.
390func ensureSSHKey() (keyPath string, pubkey string, err error) {
391 home, err := os.UserHomeDir()
392 if err != nil {
393 return "", "", err
394 }
395
396 // Check common key locations
397 keyPaths := []string{
398 filepath.Join(home, ".ssh", "id_ed25519"),
399 filepath.Join(home, ".ssh", "id_rsa"),
400 filepath.Join(home, ".ssh", "id_ecdsa"),
401 }
402
403 for _, kp := range keyPaths {
404 pubPath := kp + ".pub"
405 if _, err := os.Stat(kp); err == nil {
406 if _, err := os.Stat(pubPath); err == nil {
407 // Key exists, read public key
408 pub, err := os.ReadFile(pubPath)
409 if err != nil {
410 continue
411 }
412 return kp, strings.TrimSpace(string(pub)), nil
413 }
414 }
415 }
416
417 // No key found, generate one
418 keyPath = filepath.Join(home, ".ssh", "id_ed25519")
419 sshDir := filepath.Dir(keyPath)
420
421 // Ensure .ssh directory exists
422 if err := os.MkdirAll(sshDir, 0700); err != nil {
423 return "", "", fmt.Errorf("failed to create .ssh directory: %w", err)
424 }
425
426 // Generate key
427 cmd := exec.Command("ssh-keygen", "-t", "ed25519", "-f", keyPath, "-N", "", "-C", "ship")
428 if err := cmd.Run(); err != nil {
429 return "", "", fmt.Errorf("failed to generate SSH key: %w", err)
430 }
431
432 // Read public key
433 pub, err := os.ReadFile(keyPath + ".pub")
434 if err != nil {
435 return "", "", fmt.Errorf("failed to read public key: %w", err)
436 }
437
438 return keyPath, strings.TrimSpace(string(pub)), nil
439}
440
441// printJSON outputs a value as JSON to stdout
442func printJSON(v interface{}) {
443 enc := json.NewEncoder(os.Stdout)
444 enc.Encode(v)
445}