feat(ssh+cli): Support SSH CLI on machine init and add commands (#173)

* Add SSHCLIRemote type with buildSSHArgs

Implement's Executor interface (Run, Stream, Close)

* Add UseSSHCLI field to RemoteMachine struct

Allow us to differentiate regular ssh from ssh+cli.

* Parse ssh+cli:// scheme on machine init/add

* Support SSH CLI when provisioning

* Update docs to reflect ssh+cli:// support

* Revert removal of `init` examples

Fixes copy & pasta mistake when documenting the new ssh+cli connection
methods.
This commit is contained in:
Luis Lavena
2025-11-17 20:11:31 +10:00
committed by GitHub
parent bbdeedb44e
commit eea81723b8
6 changed files with 230 additions and 19 deletions
+19 -6
View File
@@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"net/netip"
"strings"
"time"
"github.com/cenkalti/backoff/v4"
@@ -34,19 +35,31 @@ func NewAddCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "add [USER@]HOST[:PORT]",
Short: "Add a remote machine to a cluster.",
Args: cobra.ExactArgs(1),
Long: `Add a new machine to an existing Uncloud cluster.
Connection methods:
ssh://user@host - Use built-in SSH library (default, no prefix required)
ssh+cli://user@host - Use system SSH command (supports ProxyJump, SSH config)`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
uncli := cmd.Context().Value("cli").(*cli.CLI)
user, host, port, err := config.SSHDestination(args[0]).Parse()
// Determine if SSH CLI needs to be used and strip scheme
destination := args[0]
useSSHCLI := strings.HasPrefix(destination, "ssh+cli://")
destination = strings.TrimPrefix(destination, "ssh+cli://")
destination = strings.TrimPrefix(destination, "ssh://")
user, host, port, err := config.SSHDestination(destination).Parse()
if err != nil {
return fmt.Errorf("parse remote machine: %w", err)
}
remoteMachine := &cli.RemoteMachine{
User: user,
Host: host,
Port: port,
KeyPath: opts.sshKey,
User: user,
Host: host,
Port: port,
KeyPath: opts.sshKey,
UseSSHCLI: useSSHCLI,
}
return add(cmd.Context(), uncli, remoteMachine, opts)
+19 -7
View File
@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"net/netip"
"strings"
"github.com/docker/compose/v2/pkg/progress"
"github.com/psviderski/uncloud/cmd/uncloud/caddy"
@@ -34,8 +35,12 @@ func NewInitCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "init [USER@HOST:PORT]",
Short: "Initialise a new cluster with a remote machine as the first member.",
Long: "Initialise a new cluster by setting up a remote machine as the first member.\n" +
"This command creates a new context in your Uncloud config to manage the cluster.",
Long: `Initialise a new cluster by setting up a remote machine as the first member.
This command creates a new context in your Uncloud config to manage the cluster.
Connection methods:
ssh://user@host - Use built-in SSH library (default, no prefix required)
ssh+cli://user@host - Use system SSH command (supports ProxyJump, SSH config)`,
Example: ` # Initialise a new cluster with default settings.
uc machine init root@<your-server-ip>
@@ -55,15 +60,22 @@ func NewInitCommand() *cobra.Command {
var remoteMachine *cli.RemoteMachine
if len(args) > 0 {
user, host, port, err := config.SSHDestination(args[0]).Parse()
// Determine if SSH CLI is requested and strip scheme
destination := args[0]
useSSHCLI := strings.HasPrefix(destination, "ssh+cli://")
destination = strings.TrimPrefix(destination, "ssh+cli://")
destination = strings.TrimPrefix(destination, "ssh://")
user, host, port, err := config.SSHDestination(destination).Parse()
if err != nil {
return fmt.Errorf("parse remote machine: %w", err)
}
remoteMachine = &cli.RemoteMachine{
User: user,
Host: host,
Port: port,
KeyPath: opts.sshKey,
User: user,
Host: host,
Port: port,
KeyPath: opts.sshKey,
UseSSHCLI: useSSHCLI,
}
}