diff --git a/cmd/uncloud/machine/add.go b/cmd/uncloud/machine/add.go index 3d95d08e..dfba363d 100644 --- a/cmd/uncloud/machine/add.go +++ b/cmd/uncloud/machine/add.go @@ -20,12 +20,13 @@ import ( ) type addOptions struct { - name string - noCaddy bool - publicIP string - sshKey string - context string - version string + name string + noCaddy bool + noInstall bool + publicIP string + sshKey string + context string + version string } func NewAddCommand() *cobra.Command { @@ -56,6 +57,11 @@ func NewAddCommand() *cobra.Command { &opts.noCaddy, "no-caddy", false, "Don't deploy Caddy reverse proxy service to the machine.", ) + cmd.Flags().BoolVar( + &opts.noInstall, "no-install", false, + "Skip installation of Docker, Uncloud daemon, and dependencies on the machine. "+ + "Assumes they're already installed and running.", + ) cmd.Flags().StringVar( &opts.publicIP, "public-ip", "auto", "Public IP address of the machine for ingress configuration. Use 'auto' for automatic detection, "+ @@ -98,6 +104,7 @@ func add(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteMachine, MachineName: opts.name, PublicIP: publicIP, RemoteMachine: remoteMachine, + SkipInstall: opts.noInstall, Version: opts.version, }) if err != nil { diff --git a/cmd/uncloud/machine/init.go b/cmd/uncloud/machine/init.go index 0e34fb08..3c6a624d 100644 --- a/cmd/uncloud/machine/init.go +++ b/cmd/uncloud/machine/init.go @@ -22,6 +22,7 @@ type initOptions struct { network string noCaddy bool noDNS bool + noInstall bool publicIP string sshKey string version string @@ -74,6 +75,11 @@ func NewInitCommand() *cobra.Command { &opts.noDNS, "no-dns", false, "Don't reserve a cluster domain in Uncloud DNS.", ) + cmd.Flags().BoolVar( + &opts.noInstall, "no-install", false, + "Skip installation of Docker, Uncloud daemon, and dependencies on the machine. "+ + "Assumes they're already installed and running.", + ) cmd.Flags().StringVar( &opts.publicIP, "public-ip", "auto", "Public IP address of the machine for ingress configuration. Use 'auto' for automatic detection, "+ @@ -121,6 +127,7 @@ func initCluster(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteM Network: netPrefix, PublicIP: publicIP, RemoteMachine: remoteMachine, + SkipInstall: opts.noInstall, Version: opts.version, }) if err != nil { diff --git a/internal/cli/cli.go b/internal/cli/cli.go index da04e351..416e8b48 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -146,6 +146,7 @@ type InitClusterOptions struct { Network netip.Prefix PublicIP *netip.Addr RemoteMachine *RemoteMachine + SkipInstall bool Version string } @@ -165,7 +166,7 @@ func (cli *CLI) initRemoteMachine(ctx context.Context, opts InitClusterOptions) return nil, err } - machineClient, err := provisionRemoteMachine(ctx, opts.RemoteMachine, opts.Version) + machineClient, err := provisionOrConnectRemoteMachine(ctx, opts.RemoteMachine, opts.SkipInstall, opts.Version) if err != nil { return nil, err } @@ -268,6 +269,7 @@ type AddMachineOptions struct { MachineName string PublicIP *netip.Addr RemoteMachine *RemoteMachine + SkipInstall bool Version string } @@ -290,7 +292,7 @@ func (cli *CLI) AddMachine(ctx context.Context, opts AddMachineOptions) (*client } }() - machineClient, err := provisionRemoteMachine(ctx, opts.RemoteMachine, opts.Version) + machineClient, err := provisionOrConnectRemoteMachine(ctx, opts.RemoteMachine, opts.SkipInstall, opts.Version) if err != nil { return nil, nil, err } @@ -408,15 +410,16 @@ func (cli *CLI) AddMachine(ctx context.Context, opts AddMachineOptions) (*client return c, machineClient, nil } -// provisionRemoteMachine installs the Uncloud daemon and dependencies on the remote machine over SSH and returns -// a machine API client to interact with the machine. The client should be closed after use by the caller. +// provisionOrConnectRemoteMachine installs the Uncloud daemon and dependencies on the remote machine over SSH and +// returns a machine API client to interact with the machine. The client should be closed after use by the caller. // The version parameter specifies the version of the Uncloud daemon to install. If empty, the latest version is used. +// If skipInstall is true, the installation step is skipped, and it is assumed that the Uncloud daemon and dependencies +// are already installed and running. // The remoteMachine.SSHKeyPath could be updated to the default SSH key path if it is not set and the SSH agent // authentication fails. -func provisionRemoteMachine( - ctx context.Context, remoteMachine *RemoteMachine, version string, +func provisionOrConnectRemoteMachine( + ctx context.Context, remoteMachine *RemoteMachine, skipInstall bool, version string, ) (*client.Client, error) { - // Provision the remote machine by installing the Uncloud daemon and dependencies over SSH. sshClient, err := sshexec.Connect(remoteMachine.User, remoteMachine.Host, remoteMachine.Port, remoteMachine.KeyPath) // If the SSH connection using SSH agent fails and no key path is provided, try to use the default SSH key. if err != nil && remoteMachine.KeyPath == "" { @@ -431,14 +434,17 @@ func provisionRemoteMachine( config.NewSSHDestination(remoteMachine.User, remoteMachine.Host, remoteMachine.Port), err, ) } - exec := sshexec.NewRemote(sshClient) - // Install and run the Uncloud daemon and dependencies on the remote machine. - if err = provisionMachine(ctx, exec, version); err != nil { - return nil, fmt.Errorf("provision machine: %w", err) + + if !skipInstall { + // Provision the remote machine by installing the Uncloud daemon and dependencies over SSH. + exec := sshexec.NewRemote(sshClient) + if err = provisionMachine(ctx, exec, version); err != nil { + return nil, fmt.Errorf("provision machine: %w", err) + } } var machineClient *client.Client - if remoteMachine.User == "root" { + if remoteMachine.User == "root" || skipInstall { // Create a machine API client over the established SSH connection to the remote machine. machineClient, err = client.New(ctx, connector.NewSSHConnectorFromClient(sshClient)) } else {