Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@

dist/
.DS_Store
testServer/*
default.yaml
12 changes: 8 additions & 4 deletions cmd/deploy/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"), viper.GetString("sshPort"))
if err != nil {
p.Send(render.ErrorMsg{})
}
Expand Down Expand Up @@ -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)

Expand All @@ -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)

Expand Down Expand Up @@ -220,4 +224,4 @@ It assumes that your VPS is already configured and that your application is read
os.Exit(1)
}
},
}
}
42 changes: 40 additions & 2 deletions cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,22 @@ 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)
}
sshProvider, sshProviderFlagErr := cmd.Flags().GetString("ssh-provider")
if sshProviderFlagErr != nil {
fmt.Println(sshProviderFlagErr)
sshProvider = "openssh"
}

if server == "" {
serverTextInput := pterm.DefaultInteractiveTextInput
Expand All @@ -74,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"
Expand All @@ -84,6 +107,17 @@ var InitCmd = &cobra.Command{
}
}

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)"
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")
secretKey := viper.GetString("secretKey")
Expand All @@ -105,6 +139,8 @@ 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! 🚀")
Expand All @@ -116,7 +152,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, port)
if initSessionErr != nil {
continue
}
Expand Down Expand Up @@ -196,7 +232,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, port)
if err != nil {
sidekickLoginSpinner.Fail("Something went wrong logging in to your VPS")
log.Fatal(err)
Expand Down Expand Up @@ -313,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")
}
16 changes: 10 additions & 6 deletions cmd/launch/launch.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -149,14 +149,18 @@ 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"), viper.GetString("sshPort"))
if err != nil {
p.Send(render.ErrorMsg{ErrorStr: "Something went wrong logging in to your VPS"})
}
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)

Expand All @@ -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)

Expand All @@ -181,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)

Expand Down
2 changes: 1 addition & 1 deletion cmd/preview/preview.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"), viper.GetString("sshPort"))
if err != nil {
p.Send(render.ErrorMsg{})
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/preview/remove/remove.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"), viper.GetString("sshPort"))
if err != nil {
log.Fatal("Unable to login to your VPS")
}
Expand Down
101 changes: 97 additions & 4 deletions utils/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"os/exec"
"os/user"
"path"
"runtime"
"strings"
"time"

Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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 ""
}
}
5 changes: 5 additions & 0 deletions utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}