From cfcd7436f04ff79cfb72987b8e4b3dc584c5884d Mon Sep 17 00:00:00 2001 From: aram Date: Sun, 23 Mar 2025 13:43:30 -0700 Subject: [PATCH 1/5] support podman as local container provider, fix issue in compose file for locally available container --- cmd/deploy/deploy.go | 10 +++++++--- cmd/launch/launch.go | 10 +++++++--- utils/utils.go | 5 +++++ 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/cmd/deploy/deploy.go b/cmd/deploy/deploy.go index 457bec9..953af21 100644 --- a/cmd/deploy/deploy.go +++ b/cmd/deploy/deploy.go @@ -123,7 +123,11 @@ It assumes that your VPS is already configured and that your application is read cwd, _ := os.Getwd() imgFileName := fmt.Sprintf("%s-latest.tar", appConfig.Name) - dockerBuildCmd := exec.Command("docker", "build", "--tag", appConfig.Name, "--progress=plain", "--platform=linux/amd64", cwd) + localContainerProvider := "docker" + if utils.CommandExists("podman") { + localContainerProvider = "podman" + } + dockerBuildCmd := exec.Command(localContainerProvider, "build", "--tag", appConfig.Name, "--progress=plain", "--platform=linux/amd64", cwd) dockerBuildCmdErrPipe, _ := dockerBuildCmd.StderrPipe() go render.SendLogsToTUI(dockerBuildCmdErrPipe, p) @@ -135,7 +139,7 @@ It assumes that your VPS is already configured and that your application is read p.Send(render.NextStageMsg{}) - imgSaveCmd := exec.Command("docker", "save", "-o", imgFileName, appConfig.Name) + imgSaveCmd := exec.Command(localContainerProvider, "save", "-o", imgFileName, appConfig.Name) imgSaveCmdErrPipe, _ := imgSaveCmd.StderrPipe() go render.SendLogsToTUI(imgSaveCmdErrPipe, p) @@ -220,4 +224,4 @@ It assumes that your VPS is already configured and that your application is read os.Exit(1) } }, -} +} \ No newline at end of file diff --git a/cmd/launch/launch.go b/cmd/launch/launch.go index 4c15a81..3bb81f9 100644 --- a/cmd/launch/launch.go +++ b/cmd/launch/launch.go @@ -94,7 +94,7 @@ var LaunchCmd = &cobra.Command{ } // make a docker service - imageName := appName + imageName := "localhost/" + appName newService := utils.DockerService{ Image: imageName, Restart: "unless-stopped", @@ -156,7 +156,11 @@ var LaunchCmd = &cobra.Command{ p.Send(render.NextStageMsg{}) cwd, _ := os.Getwd() - dockerBuildCmd := exec.Command("docker", "build", "--tag", appName, "--progress=plain", "--platform=linux/amd64", cwd) + localContainerProvider := "docker" + if utils.CommandExists("podman") { + localContainerProvider = "podman" + } + dockerBuildCmd := exec.Command(localContainerProvider, "build", "--tag", appName, "--progress=plain", "--platform=linux/amd64", cwd) dockerBuildCmdErrPipe, _ := dockerBuildCmd.StderrPipe() go render.SendLogsToTUI(dockerBuildCmdErrPipe, p) @@ -169,7 +173,7 @@ var LaunchCmd = &cobra.Command{ p.Send(render.NextStageMsg{}) imgFileName := fmt.Sprintf("%s-latest.tar", appName) - imgSaveCmd := exec.Command("docker", "save", "-o", imgFileName, appName) + imgSaveCmd := exec.Command(localContainerProvider, "save", "-o", imgFileName, appName) imgSaveCmdErrPipe, _ := imgSaveCmd.StderrPipe() go render.SendLogsToTUI(imgSaveCmdErrPipe, p) diff --git a/utils/utils.go b/utils/utils.go index 9063ce8..d1b3982 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -205,3 +205,8 @@ func WriteEnvFile(filename string, env map[string]string) error { } return nil } + +func CommandExists(command string) bool { + _, err := exec.LookPath(command) + return err == nil +} \ No newline at end of file From 7ba37eafdae0b1423edcb238d59b57d088146f80 Mon Sep 17 00:00:00 2001 From: aram Date: Sun, 23 Mar 2025 14:05:40 -0700 Subject: [PATCH 2/5] add testserver to ignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 699593b..a4c56f9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ dist/ .DS_Store +testServer/* +default.yaml From 211da1aca2f37e8a746193871971ea6431906803 Mon Sep 17 00:00:00 2001 From: aram Date: Sun, 20 Jul 2025 13:46:33 -0700 Subject: [PATCH 3/5] backend work to use separate provider like 1passoword for ssh agent --- utils/auth.go | 101 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 97 insertions(+), 4 deletions(-) diff --git a/utils/auth.go b/utils/auth.go index 6c4a717..4bcc8ef 100644 --- a/utils/auth.go +++ b/utils/auth.go @@ -9,6 +9,7 @@ import ( "os/exec" "os/user" "path" + "runtime" "strings" "time" @@ -100,13 +101,13 @@ func GetSshClient(server string, sshUser string) (*ssh.Client, error) { innerCallback := kh.HostKeyCallback() err := innerCallback(hostname, remote, key) if knownhosts.IsHostKeyChanged(err) { - return fmt.Errorf("REMOTE HOST IDENTIFICATION HAS CHANGED for host %s! This may indicate a MitM attack.", hostname) + return fmt.Errorf("remote host identification has changed for host %s, this may indicate a MitM attack", hostname) } else if knownhosts.IsHostUnknown(err) { inspectServerPublicKey(key, hostname) f, ferr := os.OpenFile(khPath, os.O_APPEND|os.O_WRONLY, 0600) if ferr == nil { defer f.Close() - ferr = knownhosts.WriteKnownHost(f, hostname, remote, key) + _ = knownhosts.WriteKnownHost(f, hostname, remote, key) } else { log.Printf("Failed to add host %s to known_hosts: %v\n", hostname, ferr) } @@ -138,15 +139,107 @@ func GetSshClient(server string, sshUser string) (*ssh.Client, error) { break } if client == nil { - return nil, errors.New("Logging in failed with all available keys for the said user") + return nil, errors.New("logging in failed with all available keys for the said user") } return client, nil } -func Login(server string, user string) (*ssh.Client, error) { +func GetSshClientWith1PasswordProvider(server string, sshUser string) (*ssh.Client, error) { + sshPort := "22" + sshAgentSock := get1PasswordAgentSock() + + if sshAgentSock == "" { + return nil, fmt.Errorf("1password ssh agent socket not supported on this OS") + } + + if strings.HasPrefix(sshAgentSock, "~") { + currentUser, err := user.Current() + if err != nil { + return nil, fmt.Errorf("failed to get current user: %v", err) + } + sshAgentSock = strings.Replace(sshAgentSock, "~", currentUser.HomeDir, 1) + } + + conn, err := net.Dial("unix", sshAgentSock) + if err != nil { + return nil, fmt.Errorf("failed to connect to 1Password SSH agent: %v", err) + } + defer conn.Close() + + agentClient := agent.NewClient(conn) + + cb := ssh.HostKeyCallback(func(hostname string, remote net.Addr, key ssh.PublicKey) error { + currentUser, _ := user.Current() + khPath := fmt.Sprintf("%s/.ssh/known_hosts", currentUser.HomeDir) + kh, knErr := knownhosts.NewDB(khPath) + if knErr != nil { + return knErr + } + + innerCallback := kh.HostKeyCallback() + err := innerCallback(hostname, remote, key) + if knownhosts.IsHostKeyChanged(err) { + return fmt.Errorf("remote host identification has changed for host %s, this may indicate a MitM attack", hostname) + } else if knownhosts.IsHostUnknown(err) { + inspectServerPublicKey(key, hostname) + f, ferr := os.OpenFile(khPath, os.O_APPEND|os.O_WRONLY, 0600) + if ferr == nil { + defer f.Close() + _ = knownhosts.WriteKnownHost(f, hostname, remote, key) + } else { + log.Printf("Failed to add host %s to known_hosts: %v\n", hostname, ferr) + } + return nil + } + return err + }) + + config := &ssh.ClientConfig{ + User: sshUser, + Auth: []ssh.AuthMethod{ssh.PublicKeysCallback(agentClient.Signers)}, + HostKeyCallback: cb, + Timeout: 1 * time.Second, + } + + client, err := ssh.Dial("tcp", fmt.Sprintf("%s:%s", server, sshPort), config) + if err != nil { + return nil, fmt.Errorf("failed to connect to SSH server: %v", err) + } + + return client, nil +} + +func Login(server string, user string, provider string) (*ssh.Client, error) { + if provider == "1password" { + return LoginWith1Password(server, user) + } sshClient, err := GetSshClient(server, user) if err != nil { return nil, err } return sshClient, nil } + +func LoginWith1Password(server string, user string) (*ssh.Client, error) { + sshClient, err := GetSshClientWith1PasswordProvider(server, user) + if err != nil { + return nil, err + } + return sshClient, nil +} + +func get1PasswordAgentSock() string { + if customPath := os.Getenv("OP_SOCKET_PATH"); customPath != "" { + return customPath + } + switch os := runtime.GOOS; os { + case "darwin": + return "~/Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock" + case "linux": + return "~/.1password/agent.sock" + case "windows": + return "//./pipe/com.1password.op.agent" + default: + return "" + } +} From 20ed2dde05af7201a230e86b33855684fff24751 Mon Sep 17 00:00:00 2001 From: aram Date: Sun, 20 Jul 2025 13:47:54 -0700 Subject: [PATCH 4/5] use new provider --- cmd/deploy/deploy.go | 2 +- cmd/init.go | 18 ++++++++++++++++-- cmd/launch/launch.go | 2 +- cmd/preview/preview.go | 2 +- cmd/preview/remove/remove.go | 2 +- 5 files changed, 20 insertions(+), 6 deletions(-) diff --git a/cmd/deploy/deploy.go b/cmd/deploy/deploy.go index 953af21..f6ec45c 100644 --- a/cmd/deploy/deploy.go +++ b/cmd/deploy/deploy.go @@ -84,7 +84,7 @@ It assumes that your VPS is already configured and that your application is read ) go func() { - sshClient, err := utils.Login(viper.GetString("serverAddress"), "sidekick") + sshClient, err := utils.Login(viper.GetString("serverAddress"), "sidekick", viper.GetString("sshProvider")) if err != nil { p.Send(render.ErrorMsg{}) } diff --git a/cmd/init.go b/cmd/init.go index 68b7f31..48d0665 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -63,6 +63,10 @@ var InitCmd = &cobra.Command{ if emailFlagError != nil { fmt.Println(emailFlagError) } + sshProvider, sshProviderFlagErr := cmd.Flags().GetString("ssh-provider") + if sshProviderFlagErr != nil { + fmt.Println(sshProviderFlagErr) + } if server == "" { serverTextInput := pterm.DefaultInteractiveTextInput @@ -84,6 +88,15 @@ var InitCmd = &cobra.Command{ } } + if sshProvider == "" { + sshProviderTextInput := pterm.DefaultInteractiveTextInput + sshProviderTextInput.DefaultText = "Please enter the SSH provider you want to use (default: openSSH, options: 1password, openssh)" + sshProvider, _ = sshProviderTextInput.Show() + if sshProvider == "" { + sshProvider = "openssh" + } + } + // if keys exist -> a server is already setup publicKey := viper.GetString("publicKey") secretKey := viper.GetString("secretKey") @@ -105,6 +118,7 @@ var InitCmd = &cobra.Command{ viper.Set("serverAddress", server) viper.Set("certEmail", certEmail) + viper.Set("sshProvider", sshProvider) pterm.Println() pterm.DefaultHeader.WithFullWidth().Println("Sidekick booting up! 🚀") @@ -116,7 +130,7 @@ var InitCmd = &cobra.Command{ users := []string{"root", "sidekick"} canConnect := false for _, user := range users { - sshClient, initSessionErr = utils.Login(server, user) + sshClient, initSessionErr = utils.Login(server, user, sshProvider) if initSessionErr != nil { continue } @@ -196,7 +210,7 @@ var InitCmd = &cobra.Command{ stage0Spinner.Success(utils.UsersetupStage.SpinnerSuccessMessage) sidekickLoginSpinner.Sequence = []string{"▀ ", " ▀", " ▄", "▄ "} - sidekickSshClient, err := utils.Login(server, "sidekick") + sidekickSshClient, err := utils.Login(server, "sidekick", sshProvider) if err != nil { sidekickLoginSpinner.Fail("Something went wrong logging in to your VPS") log.Fatal(err) diff --git a/cmd/launch/launch.go b/cmd/launch/launch.go index 3bb81f9..cedd7f1 100644 --- a/cmd/launch/launch.go +++ b/cmd/launch/launch.go @@ -149,7 +149,7 @@ var LaunchCmd = &cobra.Command{ }) go func() { - sshClient, err := utils.Login(viper.GetString("serverAddress"), "sidekick") + sshClient, err := utils.Login(viper.GetString("serverAddress"), "sidekick", viper.GetString("sshProvider")) if err != nil { p.Send(render.ErrorMsg{ErrorStr: "Something went wrong logging in to your VPS"}) } diff --git a/cmd/preview/preview.go b/cmd/preview/preview.go index 87fe856..a878853 100644 --- a/cmd/preview/preview.go +++ b/cmd/preview/preview.go @@ -89,7 +89,7 @@ var PreviewCmd = &cobra.Command{ }) go func() { - sshClient, err := utils.Login(viper.GetString("serverAddress"), "sidekick") + sshClient, err := utils.Login(viper.GetString("serverAddress"), "sidekick", viper.GetString("sshProvider")) if err != nil { p.Send(render.ErrorMsg{}) } diff --git a/cmd/preview/remove/remove.go b/cmd/preview/remove/remove.go index d74b10c..9e252d9 100644 --- a/cmd/preview/remove/remove.go +++ b/cmd/preview/remove/remove.go @@ -111,7 +111,7 @@ func deletePreviewEnv(hash string) { if appConfigErr != nil { log.Fatalf("Unable to load your config file. Might be corrupted") } - sshClient, err := utils.Login(viper.GetString("serverAddress"), "sidekick") + sshClient, err := utils.Login(viper.GetString("serverAddress"), "sidekick", viper.GetString("sshProvider")) if err != nil { log.Fatal("Unable to login to your VPS") } From 0f66daacfadcbcaed548368c80d88bd5f9326fe5 Mon Sep 17 00:00:00 2001 From: aram Date: Sun, 20 Jul 2025 14:31:20 -0700 Subject: [PATCH 5/5] fixup custom port implementation --- cmd/deploy/deploy.go | 2 +- cmd/init.go | 36 ++++++++++++++++++++++++++++++------ cmd/launch/launch.go | 6 +++--- cmd/preview/preview.go | 2 +- cmd/preview/remove/remove.go | 2 +- 5 files changed, 36 insertions(+), 12 deletions(-) diff --git a/cmd/deploy/deploy.go b/cmd/deploy/deploy.go index f6ec45c..b963f2b 100644 --- a/cmd/deploy/deploy.go +++ b/cmd/deploy/deploy.go @@ -84,7 +84,7 @@ It assumes that your VPS is already configured and that your application is read ) go func() { - sshClient, err := utils.Login(viper.GetString("serverAddress"), "sidekick", viper.GetString("sshProvider")) + sshClient, err := utils.Login(viper.GetString("serverAddress"), "sidekick", viper.GetString("sshProvider"), viper.GetString("sshPort")) if err != nil { p.Send(render.ErrorMsg{}) } diff --git a/cmd/init.go b/cmd/init.go index 48d0665..e550109 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -59,6 +59,13 @@ var InitCmd = &cobra.Command{ if serverFlagErr != nil { fmt.Println(serverFlagErr) } + + port, portFlagErr := cmd.Flags().GetString("ssh-port") + if portFlagErr != nil { + fmt.Println(portFlagErr) + port = "22" + } + certEmail, emailFlagError := cmd.Flags().GetString("email") if emailFlagError != nil { fmt.Println(emailFlagError) @@ -66,6 +73,7 @@ var InitCmd = &cobra.Command{ sshProvider, sshProviderFlagErr := cmd.Flags().GetString("ssh-provider") if sshProviderFlagErr != nil { fmt.Println(sshProviderFlagErr) + sshProvider = "openssh" } if server == "" { @@ -78,6 +86,17 @@ var InitCmd = &cobra.Command{ } } + wasPortProvided := cmd.Flags().Changed("ssh-port") + if !wasPortProvided { + portTextInput := pterm.DefaultInteractiveTextInput + portTextInput.DefaultText = "Please enter the SSH port of your VPS (default: 22)" + userPort, _ := portTextInput.Show() + if userPort != "" { + port = userPort + } + } + pterm.Info.Printfln("Using SSH port: %s", port) + if certEmail == "" { certEmailTextInput := pterm.DefaultInteractiveTextInput certEmailTextInput.DefaultText = "Please enter an email for use with TLS certs" @@ -88,14 +107,16 @@ var InitCmd = &cobra.Command{ } } - if sshProvider == "" { + wasProviderProvided := cmd.Flags().Changed("ssh-provider") + if !wasProviderProvided { sshProviderTextInput := pterm.DefaultInteractiveTextInput sshProviderTextInput.DefaultText = "Please enter the SSH provider you want to use (default: openSSH, options: 1password, openssh)" - sshProvider, _ = sshProviderTextInput.Show() - if sshProvider == "" { - sshProvider = "openssh" + userProvider, _ := sshProviderTextInput.Show() + if userProvider != "" { + sshProvider = userProvider } } + pterm.Info.Printfln("Using SSH provider: %s", sshProvider) // if keys exist -> a server is already setup publicKey := viper.GetString("publicKey") @@ -119,6 +140,7 @@ var InitCmd = &cobra.Command{ viper.Set("serverAddress", server) viper.Set("certEmail", certEmail) viper.Set("sshProvider", sshProvider) + viper.Set("sshPort", port) pterm.Println() pterm.DefaultHeader.WithFullWidth().Println("Sidekick booting up! 🚀") @@ -130,7 +152,7 @@ var InitCmd = &cobra.Command{ users := []string{"root", "sidekick"} canConnect := false for _, user := range users { - sshClient, initSessionErr = utils.Login(server, user, sshProvider) + sshClient, initSessionErr = utils.Login(server, user, sshProvider, port) if initSessionErr != nil { continue } @@ -210,7 +232,7 @@ var InitCmd = &cobra.Command{ stage0Spinner.Success(utils.UsersetupStage.SpinnerSuccessMessage) sidekickLoginSpinner.Sequence = []string{"▀ ", " ▀", " ▄", "▄ "} - sidekickSshClient, err := utils.Login(server, "sidekick", sshProvider) + sidekickSshClient, err := utils.Login(server, "sidekick", sshProvider, port) if err != nil { sidekickLoginSpinner.Fail("Something went wrong logging in to your VPS") log.Fatal(err) @@ -327,5 +349,7 @@ func init() { InitCmd.Flags().StringP("server", "s", "", "Set the IP address of your Server") InitCmd.Flags().StringP("email", "e", "", "An email address to be used for SSL certs") + InitCmd.Flags().StringP("ssh-port", "p", "22", "SSH port for connecting to the server") + InitCmd.Flags().String("ssh-provider", "openssh", "SSH provider to use (openssh or 1password)") InitCmd.Flags().BoolP("yes", "y", false, "Skip all validation prompts") } diff --git a/cmd/launch/launch.go b/cmd/launch/launch.go index cedd7f1..5714fd6 100644 --- a/cmd/launch/launch.go +++ b/cmd/launch/launch.go @@ -149,7 +149,7 @@ var LaunchCmd = &cobra.Command{ }) go func() { - sshClient, err := utils.Login(viper.GetString("serverAddress"), "sidekick", viper.GetString("sshProvider")) + sshClient, err := utils.Login(viper.GetString("serverAddress"), "sidekick", viper.GetString("sshProvider"), viper.GetString("sshPort")) if err != nil { p.Send(render.ErrorMsg{ErrorStr: "Something went wrong logging in to your VPS"}) } @@ -185,13 +185,13 @@ var LaunchCmd = &cobra.Command{ p.Send(render.NextStageMsg{}) - _, _, sessionErr := utils.RunCommand(sshClient, fmt.Sprintf("mkdir %s", appName)) + _, _, sessionErr := utils.RunCommand(sshClient, fmt.Sprintf("mkdir -p %s", appName)) if sessionErr != nil { p.Send(render.ErrorMsg{ErrorStr: sessionErr.Error()}) } remoteDist := fmt.Sprintf("%s@%s:./%s", "sidekick", viper.GetString("serverAddress"), appName) - imgMoveCmd := exec.Command("scp", "-C", imgFileName, remoteDist) + imgMoveCmd := exec.Command("scp", "-C", imgFileName, remoteDist, "-P", viper.GetString("sshPort")) imgMoveCmdErrorPipe, _ := imgMoveCmd.StderrPipe() go render.SendLogsToTUI(imgMoveCmdErrorPipe, p) diff --git a/cmd/preview/preview.go b/cmd/preview/preview.go index a878853..16177cb 100644 --- a/cmd/preview/preview.go +++ b/cmd/preview/preview.go @@ -89,7 +89,7 @@ var PreviewCmd = &cobra.Command{ }) go func() { - sshClient, err := utils.Login(viper.GetString("serverAddress"), "sidekick", viper.GetString("sshProvider")) + sshClient, err := utils.Login(viper.GetString("serverAddress"), "sidekick", viper.GetString("sshProvider"), viper.GetString("sshPort")) if err != nil { p.Send(render.ErrorMsg{}) } diff --git a/cmd/preview/remove/remove.go b/cmd/preview/remove/remove.go index 9e252d9..7c85bd8 100644 --- a/cmd/preview/remove/remove.go +++ b/cmd/preview/remove/remove.go @@ -111,7 +111,7 @@ func deletePreviewEnv(hash string) { if appConfigErr != nil { log.Fatalf("Unable to load your config file. Might be corrupted") } - sshClient, err := utils.Login(viper.GetString("serverAddress"), "sidekick", viper.GetString("sshProvider")) + sshClient, err := utils.Login(viper.GetString("serverAddress"), "sidekick", viper.GetString("sshProvider"), viper.GetString("sshPort")) if err != nil { log.Fatal("Unable to login to your VPS") }