mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-26 19:13:34 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec3de3a099 | ||
|
|
2c02139369 | ||
|
|
6c244bb8f9 | ||
|
|
fc0bf4a91b | ||
|
|
bc577fe405 | ||
|
|
6cc0611d75 |
@@ -41,7 +41,7 @@ func NewAddCommand() *cobra.Command {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("parse remote machine: %w", err)
|
return fmt.Errorf("parse remote machine: %w", err)
|
||||||
}
|
}
|
||||||
remoteMachine := cli.RemoteMachine{
|
remoteMachine := &cli.RemoteMachine{
|
||||||
User: user,
|
User: user,
|
||||||
Host: host,
|
Host: host,
|
||||||
Port: port,
|
Port: port,
|
||||||
@@ -62,8 +62,9 @@ func NewAddCommand() *cobra.Command {
|
|||||||
fmt.Sprintf("blank '' or '%s' to disable ingress on this machine, or specify an IP address.", PublicIPNone),
|
fmt.Sprintf("blank '' or '%s' to disable ingress on this machine, or specify an IP address.", PublicIPNone),
|
||||||
)
|
)
|
||||||
cmd.Flags().StringVarP(
|
cmd.Flags().StringVarP(
|
||||||
&opts.sshKey, "ssh-key", "i", "~/.ssh/id_ed25519",
|
&opts.sshKey, "ssh-key", "i", "",
|
||||||
"Path to SSH private key for remote login (if not already added to SSH agent).",
|
fmt.Sprintf("Path to SSH private key for remote login (if not already added to SSH agent). (default %q)",
|
||||||
|
cli.DefaultSSHKeyPath),
|
||||||
)
|
)
|
||||||
cmd.Flags().StringVar(
|
cmd.Flags().StringVar(
|
||||||
&opts.version, "version", "latest",
|
&opts.version, "version", "latest",
|
||||||
@@ -77,7 +78,7 @@ func NewAddCommand() *cobra.Command {
|
|||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func add(ctx context.Context, uncli *cli.CLI, remoteMachine cli.RemoteMachine, opts addOptions) error {
|
func add(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteMachine, opts addOptions) error {
|
||||||
var publicIP *netip.Addr
|
var publicIP *netip.Addr
|
||||||
switch opts.publicIP {
|
switch opts.publicIP {
|
||||||
case "auto":
|
case "auto":
|
||||||
|
|||||||
@@ -80,8 +80,9 @@ func NewInitCommand() *cobra.Command {
|
|||||||
fmt.Sprintf("blank '' or '%s' to disable ingress on this machine, or specify an IP address.", PublicIPNone),
|
fmt.Sprintf("blank '' or '%s' to disable ingress on this machine, or specify an IP address.", PublicIPNone),
|
||||||
)
|
)
|
||||||
cmd.Flags().StringVarP(
|
cmd.Flags().StringVarP(
|
||||||
&opts.sshKey, "ssh-key", "i", "~/.ssh/id_ed25519",
|
&opts.sshKey, "ssh-key", "i", "",
|
||||||
"Path to SSH private key for remote login (if not already added to SSH agent).",
|
fmt.Sprintf("Path to SSH private key for remote login (if not already added to SSH agent). (default %q)",
|
||||||
|
cli.DefaultSSHKeyPath),
|
||||||
)
|
)
|
||||||
cmd.Flags().StringVar(
|
cmd.Flags().StringVar(
|
||||||
&opts.version, "version", "latest",
|
&opts.version, "version", "latest",
|
||||||
|
|||||||
@@ -90,11 +90,13 @@ func remove(ctx context.Context, uncli *cli.CLI, nameOrID string, opts removeOpt
|
|||||||
|
|
||||||
reset := !opts.noReset
|
reset := !opts.noReset
|
||||||
var containers []api.ServiceContainer
|
var containers []api.ServiceContainer
|
||||||
|
reachable := false
|
||||||
if reset {
|
if reset {
|
||||||
// Check if the machine is up and has service containers.
|
// Check if the machine is up and has service containers.
|
||||||
listOpts := container.ListOptions{All: true}
|
listOpts := container.ListOptions{All: true}
|
||||||
machineContainers, err := client.Docker.ListServiceContainers(mctx, "", listOpts)
|
machineContainers, err := client.Docker.ListServiceContainers(mctx, "", listOpts)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
reachable = true
|
||||||
containers = machineContainers[0].Containers
|
containers = machineContainers[0].Containers
|
||||||
if len(containers) > 0 {
|
if len(containers) > 0 {
|
||||||
plural := ""
|
plural := ""
|
||||||
@@ -104,7 +106,7 @@ func remove(ctx context.Context, uncli *cli.CLI, nameOrID string, opts removeOpt
|
|||||||
fmt.Printf("Found %d service container%s on machine '%s':\n", len(containers), plural, m.Name)
|
fmt.Printf("Found %d service container%s on machine '%s':\n", len(containers), plural, m.Name)
|
||||||
fmt.Println(formatContainerTree(containers))
|
fmt.Println(formatContainerTree(containers))
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Println("This will remove all service containers on the machine, remove it from the cluster, " +
|
fmt.Println("This will remove all service containers from the machine, remove it from the cluster, " +
|
||||||
"and reset it to the uninitialised state.")
|
"and reset it to the uninitialised state.")
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("No service containers found on machine '%s'.\n", m.Name)
|
fmt.Printf("No service containers found on machine '%s'.\n", m.Name)
|
||||||
@@ -129,8 +131,7 @@ func remove(ctx context.Context, uncli *cli.CLI, nameOrID string, opts removeOpt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if reset {
|
if reset && len(containers) > 0 {
|
||||||
if len(containers) > 0 {
|
|
||||||
err = progress.RunWithTitle(ctx, func(ctx context.Context) error {
|
err = progress.RunWithTitle(ctx, func(ctx context.Context) error {
|
||||||
return removeContainers(ctx, client, containers)
|
return removeContainers(ctx, client, containers)
|
||||||
}, uncli.ProgressOut(), "Removing containers")
|
}, uncli.ProgressOut(), "Removing containers")
|
||||||
@@ -139,14 +140,13 @@ func remove(ctx context.Context, uncli *cli.CLI, nameOrID string, opts removeOpt
|
|||||||
}
|
}
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if _, err = client.RemoveMachine(ctx, &pb.RemoveMachineRequest{Id: m.Id}); err != nil {
|
if _, err = client.RemoveMachine(ctx, &pb.RemoveMachineRequest{Id: m.Id}); err != nil {
|
||||||
return fmt.Errorf("remove machine from cluster: %w", err)
|
return fmt.Errorf("remove machine from cluster: %w", err)
|
||||||
}
|
}
|
||||||
fmt.Printf("Machine '%s' removed from the cluster.\n", m.Name)
|
fmt.Printf("Machine '%s' removed from the cluster.\n", m.Name)
|
||||||
|
|
||||||
if reset {
|
if reset && reachable {
|
||||||
_, err = client.MachineClient.Reset(mctx, &pb.ResetRequest{})
|
_, err = client.MachineClient.Reset(mctx, &pb.ResetRequest{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("WARNING: Failed to reset machine: %v\n", err)
|
fmt.Printf("WARNING: Failed to reset machine: %v\n", err)
|
||||||
|
|||||||
+35
-35
@@ -6,8 +6,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
|
"slices"
|
||||||
|
|
||||||
"github.com/charmbracelet/huh"
|
|
||||||
"github.com/docker/cli/cli/streams"
|
"github.com/docker/cli/cli/streams"
|
||||||
"github.com/psviderski/uncloud/internal/cli/config"
|
"github.com/psviderski/uncloud/internal/cli/config"
|
||||||
"github.com/psviderski/uncloud/internal/fs"
|
"github.com/psviderski/uncloud/internal/fs"
|
||||||
@@ -22,7 +22,12 @@ import (
|
|||||||
"google.golang.org/protobuf/types/known/emptypb"
|
"google.golang.org/protobuf/types/known/emptypb"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultContextName = "default"
|
const (
|
||||||
|
// DefaultSSHKeyPath is the fallback location for the SSH private key when provisioning remote machines.
|
||||||
|
// Used when no key is explicitly provided and SSH agent authentication fails.
|
||||||
|
DefaultSSHKeyPath = "~/.ssh/id_ed25519"
|
||||||
|
defaultContextName = "default"
|
||||||
|
)
|
||||||
|
|
||||||
type CLI struct {
|
type CLI struct {
|
||||||
Config *config.Config
|
Config *config.Config
|
||||||
@@ -173,7 +178,7 @@ func (cli *CLI) initRemoteMachine(ctx context.Context, opts InitClusterOptions)
|
|||||||
return nil, fmt.Errorf("cluster context '%s' already exists", contextName)
|
return nil, fmt.Errorf("cluster context '%s' already exists", contextName)
|
||||||
}
|
}
|
||||||
|
|
||||||
machineClient, err := cli.provisionRemoteMachine(ctx, *opts.RemoteMachine, opts.Version)
|
machineClient, err := provisionRemoteMachine(ctx, opts.RemoteMachine, opts.Version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -190,7 +195,7 @@ func (cli *CLI) initRemoteMachine(ctx context.Context, opts InitClusterOptions)
|
|||||||
return nil, fmt.Errorf("inspect machine: %w", err)
|
return nil, fmt.Errorf("inspect machine: %w", err)
|
||||||
}
|
}
|
||||||
if minfo.Id != "" {
|
if minfo.Id != "" {
|
||||||
if err = cli.promptResetMachine(); err != nil {
|
if err = promptResetMachine(ctx, machineClient.MachineClient); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -249,7 +254,7 @@ type AddMachineOptions struct {
|
|||||||
Context string
|
Context string
|
||||||
MachineName string
|
MachineName string
|
||||||
PublicIP *netip.Addr
|
PublicIP *netip.Addr
|
||||||
RemoteMachine RemoteMachine
|
RemoteMachine *RemoteMachine
|
||||||
Version string
|
Version string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,7 +277,7 @@ func (cli *CLI) AddMachine(ctx context.Context, opts AddMachineOptions) (*client
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
machineClient, err := cli.provisionRemoteMachine(ctx, opts.RemoteMachine, opts.Version)
|
machineClient, err := provisionRemoteMachine(ctx, opts.RemoteMachine, opts.Version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@@ -288,7 +293,18 @@ func (cli *CLI) AddMachine(ctx context.Context, opts AddMachineOptions) (*client
|
|||||||
return nil, nil, fmt.Errorf("inspect machine: %w", err)
|
return nil, nil, fmt.Errorf("inspect machine: %w", err)
|
||||||
}
|
}
|
||||||
if minfo.Id != "" {
|
if minfo.Id != "" {
|
||||||
if err = cli.promptResetMachine(); err != nil {
|
// Check if the machine is already a member of this cluster.
|
||||||
|
machines, err := c.ListMachines(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("list cluster machines: %w", err)
|
||||||
|
}
|
||||||
|
if slices.ContainsFunc(machines, func(m *pb.MachineMember) bool {
|
||||||
|
return m.Machine.Id == minfo.Id
|
||||||
|
}) {
|
||||||
|
return nil, nil, fmt.Errorf("machine is already a member of this cluster (%s)", minfo.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = promptResetMachine(ctx, machineClient.MachineClient); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -339,7 +355,7 @@ func (cli *CLI) AddMachine(ctx context.Context, opts AddMachineOptions) (*client
|
|||||||
return nil, nil, fmt.Errorf("add machine to cluster (context '%s'): %w", contextName, err)
|
return nil, nil, fmt.Errorf("add machine to cluster (context '%s'): %w", contextName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// List other machines in the cluster to include them in the join request.
|
// Get the most up-to-date list of other machines in the cluster to include them in the join request.
|
||||||
machines, err := c.ListMachines(ctx, nil)
|
machines, err := c.ListMachines(ctx, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("list cluster machines: %w", err)
|
return nil, nil, fmt.Errorf("list cluster machines: %w", err)
|
||||||
@@ -382,11 +398,20 @@ func (cli *CLI) AddMachine(ctx context.Context, opts AddMachineOptions) (*client
|
|||||||
// provisionRemoteMachine installs the Uncloud daemon and dependencies on the remote machine over SSH and returns
|
// 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.
|
// 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.
|
// The version parameter specifies the version of the Uncloud daemon to install. If empty, the latest version is used.
|
||||||
func (cli *CLI) provisionRemoteMachine(
|
// The remoteMachine.SSHKeyPath could be updated to the default SSH key path if it is not set and the SSH agent
|
||||||
ctx context.Context, remoteMachine RemoteMachine, version string,
|
// authentication fails.
|
||||||
|
func provisionRemoteMachine(
|
||||||
|
ctx context.Context, remoteMachine *RemoteMachine, version string,
|
||||||
) (*client.Client, error) {
|
) (*client.Client, error) {
|
||||||
// Provision the remote machine by installing the Uncloud daemon and dependencies over SSH.
|
// 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)
|
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 == "" {
|
||||||
|
remoteMachine.KeyPath = DefaultSSHKeyPath
|
||||||
|
sshClient, err = sshexec.Connect(
|
||||||
|
remoteMachine.User, remoteMachine.Host, remoteMachine.Port, remoteMachine.KeyPath,
|
||||||
|
)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf(
|
return nil, fmt.Errorf(
|
||||||
"SSH login to remote machine %s: %w",
|
"SSH login to remote machine %s: %w",
|
||||||
@@ -420,31 +445,6 @@ func (cli *CLI) provisionRemoteMachine(
|
|||||||
return machineClient, nil
|
return machineClient, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *CLI) promptResetMachine() error {
|
|
||||||
var confirm bool
|
|
||||||
form := huh.NewForm(
|
|
||||||
huh.NewGroup(
|
|
||||||
huh.NewConfirm().
|
|
||||||
Title(
|
|
||||||
"The remote machine is already initialised as a cluster member. Do you want to reset it first?",
|
|
||||||
).
|
|
||||||
Affirmative("Yes!").
|
|
||||||
Negative("No").
|
|
||||||
Value(&confirm),
|
|
||||||
),
|
|
||||||
).WithAccessible(true)
|
|
||||||
if err := form.Run(); err != nil {
|
|
||||||
return fmt.Errorf("prompt user to confirm: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !confirm {
|
|
||||||
return fmt.Errorf("remote machine is already initialised as a cluster member")
|
|
||||||
}
|
|
||||||
// TODO: implement resetting the remote machine.
|
|
||||||
return fmt.Errorf("resetting the remote machine is not implemented yet. " +
|
|
||||||
"Please manually run 'uncloud-uninstall' on the remote machine to fully uninstall Uncloud from it")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProgressOut returns an output stream for progress writer.
|
// ProgressOut returns an output stream for progress writer.
|
||||||
func (cli *CLI) ProgressOut() *streams.Out {
|
func (cli *CLI) ProgressOut() *streams.Out {
|
||||||
return streams.NewOut(os.Stdout)
|
return streams.NewOut(os.Stdout)
|
||||||
|
|||||||
+84
-3
@@ -5,12 +5,20 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/cenkalti/backoff/v4"
|
||||||
|
"github.com/charmbracelet/huh"
|
||||||
|
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
||||||
"github.com/psviderski/uncloud/internal/sshexec"
|
"github.com/psviderski/uncloud/internal/sshexec"
|
||||||
|
"google.golang.org/protobuf/types/known/emptypb"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: support pinning the script version to the CLI version.
|
const (
|
||||||
const installScriptURL = "https://raw.githubusercontent.com/psviderski/uncloud/refs/heads/main/scripts/install.sh"
|
// TODO: support pinning the script version to the CLI version.
|
||||||
|
installScriptURL = "https://raw.githubusercontent.com/psviderski/uncloud/refs/heads/main/scripts/install.sh"
|
||||||
|
rootUser = "root"
|
||||||
|
)
|
||||||
|
|
||||||
type RemoteMachine struct {
|
type RemoteMachine struct {
|
||||||
User string
|
User string
|
||||||
@@ -24,7 +32,7 @@ func installCmd(user string, version string) string {
|
|||||||
var env []string
|
var env []string
|
||||||
|
|
||||||
// Add the SSH user (non-root) to the uncloud group to allow access to the Uncloud daemon unix socket.
|
// Add the SSH user (non-root) to the uncloud group to allow access to the Uncloud daemon unix socket.
|
||||||
if user != "root" {
|
if user != rootUser {
|
||||||
sudoPrefix = "sudo"
|
sudoPrefix = "sudo"
|
||||||
env = append(env, "UNCLOUD_GROUP_ADD_USER="+sshexec.Quote(user))
|
env = append(env, "UNCLOUD_GROUP_ADD_USER="+sshexec.Quote(user))
|
||||||
}
|
}
|
||||||
@@ -46,6 +54,26 @@ func provisionMachine(ctx context.Context, exec sshexec.Executor, version string
|
|||||||
return fmt.Errorf("run whoami: %w", err)
|
return fmt.Errorf("run whoami: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if user != rootUser {
|
||||||
|
// 'sudo -n' is not used because it fails with 'sudo: a password is required' when the user has no password
|
||||||
|
// in /etc/shadow even though it may have valid sudo access.
|
||||||
|
out, err := exec.Run(ctx, "sudo true")
|
||||||
|
if err != nil {
|
||||||
|
if strings.Contains(out, "password is required") {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"user '%[1]s' requires a password for sudo, but Uncloud needs passwordless sudo or root access "+
|
||||||
|
"to install and configure the uncloudd daemon on the remote machine.\n\n"+
|
||||||
|
"Possible solutions:\n"+
|
||||||
|
"1. Use root user or a user with passwordless sudo instead.\n"+
|
||||||
|
"2. Configure passwordless sudo for the user '%[1]s' by running on the remote machine:\n"+
|
||||||
|
" echo '%[1]s ALL=(ALL) NOPASSWD:ALL' | sudo tee /etc/sudoers.d/%[1]s",
|
||||||
|
user)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("sudo command failed for user '%s': %w. "+
|
||||||
|
"Please ensure the user has sudo privileges or use root user instead", user, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cmd := installCmd(user, version)
|
cmd := installCmd(user, version)
|
||||||
|
|
||||||
fmt.Println("Downloading Uncloud install script:", installScriptURL)
|
fmt.Println("Downloading Uncloud install script:", installScriptURL)
|
||||||
@@ -56,3 +84,56 @@ func provisionMachine(ctx context.Context, exec sshexec.Executor, version string
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func promptResetMachine(ctx context.Context, machineClient pb.MachineClient) error {
|
||||||
|
var confirm bool
|
||||||
|
form := huh.NewForm(
|
||||||
|
huh.NewGroup(
|
||||||
|
huh.NewConfirm().
|
||||||
|
Title(
|
||||||
|
"The remote machine is already initialised as a cluster member. Do you want to reset it first?\n" +
|
||||||
|
"This will:\n" +
|
||||||
|
"- Remove all service containers from the machine\n" +
|
||||||
|
"- Reset the machine to the uninitialised state",
|
||||||
|
).
|
||||||
|
Affirmative("Yes!").
|
||||||
|
Negative("No").
|
||||||
|
Value(&confirm),
|
||||||
|
),
|
||||||
|
).WithAccessible(true)
|
||||||
|
if err := form.Run(); err != nil {
|
||||||
|
return fmt.Errorf("prompt user to confirm: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !confirm {
|
||||||
|
return fmt.Errorf("remote machine is already initialised as a cluster member")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := machineClient.Reset(ctx, &pb.ResetRequest{}); err != nil {
|
||||||
|
return fmt.Errorf("reset remote machine: %w. You can also manually run 'uncloud-uninstall' "+
|
||||||
|
"on the remote machine to fully uninstall Uncloud from it", err)
|
||||||
|
}
|
||||||
|
fmt.Println("Resetting the remote machine...")
|
||||||
|
if err := waitMachineReady(ctx, machineClient, 1*time.Minute); err != nil {
|
||||||
|
return fmt.Errorf("wait for machine to be ready after reset: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// waitMachineReady waits for the machine to be ready to serve requests.
|
||||||
|
func waitMachineReady(ctx context.Context, machineClient pb.MachineClient, timeout time.Duration) error {
|
||||||
|
boff := backoff.WithContext(backoff.NewExponentialBackOff(
|
||||||
|
backoff.WithMaxInterval(1*time.Second),
|
||||||
|
backoff.WithMaxElapsedTime(timeout),
|
||||||
|
), ctx)
|
||||||
|
|
||||||
|
inspect := func() error {
|
||||||
|
_, err := machineClient.Inspect(ctx, &emptypb.Empty{})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("inspect machine: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return backoff.Retry(inspect, boff)
|
||||||
|
}
|
||||||
|
|||||||
@@ -446,10 +446,7 @@ func (m *Machine) Run(ctx context.Context) error {
|
|||||||
slog.Info("Local API proxy server stopped.")
|
slog.Info("Local API proxy server stopped.")
|
||||||
|
|
||||||
// Clean up the machine data and resources if the machine shutdown was initiated by a reset.
|
// Clean up the machine data and resources if the machine shutdown was initiated by a reset.
|
||||||
m.mu.RLock()
|
if m.resetting {
|
||||||
resetting := m.resetting
|
|
||||||
m.mu.RUnlock()
|
|
||||||
if resetting {
|
|
||||||
slog.Info("Cleaning up machine data and resources.")
|
slog.Info("Cleaning up machine data and resources.")
|
||||||
if err = m.cleanup(); err != nil {
|
if err = m.cleanup(); err != nil {
|
||||||
slog.Error("Failed to clean up machine data and resources.", "err", err)
|
slog.Error("Failed to clean up machine data and resources.", "err", err)
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ func (c *SSHConnector) Connect(ctx context.Context) (*grpc.ClientConn, error) {
|
|||||||
conn, dErr := c.client.DialContext(ctx, "unix", addr)
|
conn, dErr := c.client.DialContext(ctx, "unix", addr)
|
||||||
if dErr != nil {
|
if dErr != nil {
|
||||||
return nil, fmt.Errorf(
|
return nil, fmt.Errorf(
|
||||||
"connect to machine API socket '%s' through SSH tunnel (is the Uncloud daemon running "+
|
"connect to machine API socket '%s' through SSH tunnel (is uncloud.service running "+
|
||||||
"on the remote machine and does the SSH user '%s' have permissions to access the socket?):"+
|
"on the remote machine and does the SSH user '%s' have permissions to access the socket?):"+
|
||||||
" %w",
|
" %w",
|
||||||
addr, c.client.User(), dErr,
|
addr, c.client.User(), dErr,
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ infrastructure with secure internet access.
|
|||||||
Before you begin, you'll need:
|
Before you begin, you'll need:
|
||||||
|
|
||||||
- **Uncloud CLI** [installed](1-install-cli.md) on your local machine
|
- **Uncloud CLI** [installed](1-install-cli.md) on your local machine
|
||||||
- A **Ubuntu or Debian server** with **public IP address** and **SSH access** (as `root` or a user with `sudo`
|
- A **Ubuntu or Debian server** with **public IP address** and **SSH access** using a **private key** (as `root` or a
|
||||||
privileges) using a **private key**.
|
user with **passwordless** `sudo` privileges).
|
||||||
|
|
||||||
:::tip Need a server?
|
:::tip Need a server?
|
||||||
|
|
||||||
@@ -273,9 +273,11 @@ Add a CNAME record `excalidraw.example.com` in your DNS provider (Cloudflare, Na
|
|||||||
|
|
||||||
:::info note
|
:::info note
|
||||||
|
|
||||||
These instructions set up your own domain _in addition to_ uncloud's managed DNS service.
|
These instructions set up your own domain **in addition to** the Uncloud managed DNS name
|
||||||
|
`excalidraw.7za6s7.cluster.uncloud.run`.
|
||||||
|
|
||||||
If you want to avoid the managed service altogether, add `--no-dns` to your `uc machine init` command, and point an `A`-type DNS record to your server(s)'s IP(s).
|
If you want to avoid the managed service altogether, add `--no-dns` to your `uc machine init` command, and point an A
|
||||||
|
DNS record to your server(s)'s IP(s).
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user