diff --git a/cmd/uncloud/machine/add.go b/cmd/uncloud/machine/add.go index 1bdb3ecc..d9e5d289 100644 --- a/cmd/uncloud/machine/add.go +++ b/cmd/uncloud/machine/add.go @@ -10,6 +10,7 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/emptypb" + "net/netip" "time" "uncloud/internal/cli" "uncloud/internal/cli/client" @@ -18,10 +19,11 @@ import ( ) type addOptions struct { - name string - noCaddy bool - sshKey string - cluster string + name string + noCaddy bool + publicIP string + sshKey string + cluster string } func NewAddCommand() *cobra.Command { @@ -52,6 +54,11 @@ func NewAddCommand() *cobra.Command { &opts.noCaddy, "no-caddy", false, "Don't deploy Caddy reverse proxy service to the machine.", ) + cmd.Flags().StringVar( + &opts.publicIP, "public-ip", "auto", + "Public IP address of the machine for ingress configuration. Use 'auto' for automatic detection, "+ + "blank '' or 'none' to disable ingress on this machine, or specify an IP address.", + ) cmd.Flags().StringVarP( &opts.sshKey, "ssh-key", "i", "", "path to SSH private key for SSH remote login. (default ~/.ssh/id_*)", @@ -64,7 +71,21 @@ func NewAddCommand() *cobra.Command { } func add(ctx context.Context, uncli *cli.CLI, remoteMachine cli.RemoteMachine, opts addOptions) error { - machineClient, err := uncli.AddMachine(ctx, remoteMachine, opts.cluster, opts.name) + var publicIP *netip.Addr + switch opts.publicIP { + case "auto": + publicIP = &netip.Addr{} + case "", "none": + publicIP = nil + default: + ip, err := netip.ParseAddr(opts.publicIP) + if err != nil { + return fmt.Errorf("parse public IP: %w", err) + } + publicIP = &ip + } + + machineClient, err := uncli.AddMachine(ctx, remoteMachine, opts.cluster, opts.name, publicIP) if err != nil { return err } diff --git a/internal/cli/cli.go b/internal/cli/cli.go index fa1768b8..398812e9 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -204,9 +204,10 @@ func (cli *CLI) initRemoteMachine( return machineClient, nil } -// TODO: +// AddMachine provisions a remote machine and adds it to the cluster. It returns a client to interact with the machine +// which should be closed after use by the caller. func (cli *CLI) AddMachine( - ctx context.Context, remoteMachine RemoteMachine, clusterName, machineName string, + ctx context.Context, remoteMachine RemoteMachine, clusterName, machineName string, publicIP *netip.Addr, ) (*client.Client, error) { c, err := cli.ConnectCluster(ctx, clusterName) if err != nil { @@ -256,6 +257,15 @@ func (cli *CLI) AddMachine( PublicKey: token.PublicKey, }, } + if publicIP != nil { + if publicIP.IsValid() { + addReq.PublicIp = pb.NewIP(*publicIP) + } else if token.PublicIP.IsValid() { + // Invalid or in other words zero IP means to use an automatically detected public IP from the token. + addReq.PublicIp = pb.NewIP(token.PublicIP) + } + } + addResp, err := c.AddMachine(ctx, addReq) if err != nil { return nil, fmt.Errorf("add machine to cluster: %w", err) diff --git a/internal/daemon/token.go b/internal/daemon/token.go index 90a017b3..aa99e984 100644 --- a/internal/daemon/token.go +++ b/internal/daemon/token.go @@ -37,5 +37,5 @@ func MachineToken(dataDir string) (machine.Token, error) { for i, ip := range ips { endpoints[i] = netip.AddrPortFrom(ip, network.WireGuardPort) } - return machine.NewToken(state.Network.PublicKey, endpoints), nil + return machine.NewToken(state.Network.PublicKey, publicIP, endpoints), nil } diff --git a/internal/machine/machine.go b/internal/machine/machine.go index 61a5ef75..492a7be4 100644 --- a/internal/machine/machine.go +++ b/internal/machine/machine.go @@ -538,9 +538,9 @@ func (m *Machine) InitCluster(ctx context.Context, req *pb.InitClusterRequest) ( if err != nil { return nil, status.Errorf(codes.Internal, "list routable IPs: %v", err) } - publicIP, err := network.GetPublicIP() + publicIP, pubIPErr := network.GetPublicIP() // Ignore the error if failed to get the public IP using API services. - if err == nil { + if pubIPErr == nil { ips = append(ips, publicIP) } endpoints := make([]*pb.IPPort, len(ips)) @@ -560,7 +560,7 @@ func (m *Machine) InitCluster(ctx context.Context, req *pb.InitClusterRequest) ( } if req.GetPublicIp() != nil { addReq.PublicIp = req.GetPublicIp() - } else if req.GetPublicIpAuto() { + } else if req.GetPublicIpAuto() && pubIPErr == nil { addReq.PublicIp = pb.NewIP(publicIP) } @@ -690,7 +690,7 @@ func (m *Machine) Token(_ context.Context, _ *emptypb.Empty) (*pb.TokenResponse, endpoints[i] = netip.AddrPortFrom(ip, network.WireGuardPort) } - token := NewToken(m.state.Network.PublicKey, endpoints) + token := NewToken(m.state.Network.PublicKey, publicIP, endpoints) tokenStr, err := token.String() if err != nil { return nil, status.Error(codes.Internal, err.Error()) diff --git a/internal/machine/token.go b/internal/machine/token.go index 68ded8f2..8ef97bf5 100644 --- a/internal/machine/token.go +++ b/internal/machine/token.go @@ -16,13 +16,15 @@ const ( // Token represents the machine's token for joining a cluster. type Token struct { PublicKey secret.Secret + PublicIP netip.Addr Endpoints []netip.AddrPort } // NewToken creates a new machine token with the given public key and endpoints. -func NewToken(publicKey secret.Secret, endpoints []netip.AddrPort) Token { +func NewToken(publicKey secret.Secret, publicIP netip.Addr, endpoints []netip.AddrPort) Token { return Token{ PublicKey: publicKey, + PublicIP: publicIP, Endpoints: endpoints, } }