package main import ( "encoding/json" "errors" "fmt" "isle/bootstrap" "isle/daemon" "isle/jsonutil" "isle/nebula" "net/netip" "os" "sort" ) var subCmdHostsCreate = subCmd{ name: "create", descr: "Creates a new host in the network, writing its new bootstrap.json to stdout", do: func(subCmdCtx subCmdCtx) error { var ( flags = subCmdCtx.flagSet(false) hostName nebula.HostName ip netip.Addr ) hostNameF := flags.VarPF( textUnmarshalerFlag{&hostName}, "hostname", "h", "Name of the host to generate bootstrap.json for", ) ipF := flags.VarPF( textUnmarshalerFlag{&ip}, "ip", "i", "IP of the new host", ) canCreateHosts := flags.Bool( "can-create-hosts", false, "The new host should have the ability to create hosts too", ) if err := flags.Parse(subCmdCtx.args); err != nil { return fmt.Errorf("parsing flags: %w", err) } if !hostNameF.Changed || !ipF.Changed { return errors.New("--hostname and --ip are required") } var res daemon.CreateHostResult err := subCmdCtx.daemonRCPClient.Call( subCmdCtx.ctx, &res, "CreateHost", daemon.CreateHostRequest{ HostName: hostName, IP: ip, Opts: daemon.CreateHostOpts{ CanCreateHosts: *canCreateHosts, }, }, ) if err != nil { return fmt.Errorf("calling CreateHost: %w", err) } return json.NewEncoder(os.Stdout).Encode(res.JoiningBootstrap) }, } var subCmdHostsList = subCmd{ name: "list", descr: "Lists all hosts in the network, and their IPs", do: func(subCmdCtx subCmdCtx) error { hostsRes, err := subCmdCtx.getHosts() if err != nil { return fmt.Errorf("calling GetHosts: %w", err) } type host struct { Name string VPN struct { IP string } Storage bootstrap.GarageHost `json:",omitempty"` } hosts := make([]host, 0, len(hostsRes.Hosts)) for _, h := range hostsRes.Hosts { host := host{ Name: string(h.Name), Storage: h.Garage, } host.VPN.IP = h.IP().String() hosts = append(hosts, host) } sort.Slice(hosts, func(i, j int) bool { return hosts[i].Name < hosts[j].Name }) return jsonutil.WriteIndented(os.Stdout, hosts) }, } var subCmdHostsRemove = subCmd{ name: "remove", descr: "Removes a host from the network", do: func(subCmdCtx subCmdCtx) error { var ( flags = subCmdCtx.flagSet(false) hostName nebula.HostName ) hostNameF := flags.VarPF( textUnmarshalerFlag{&hostName}, "hostname", "h", "Name of the host to remove", ) if err := flags.Parse(subCmdCtx.args); err != nil { return fmt.Errorf("parsing flags: %w", err) } if !hostNameF.Changed { return errors.New("--hostname is required") } err := subCmdCtx.daemonRCPClient.Call( subCmdCtx.ctx, nil, "RemoveHost", daemon.RemoveHostRequest{ HostName: hostName, }, ) if err != nil { return fmt.Errorf("calling RemoveHost: %w", err) } return nil }, } var subCmdHosts = subCmd{ name: "hosts", descr: "Sub-commands having to do with configuration of hosts in the network", do: func(subCmdCtx subCmdCtx) error { return subCmdCtx.doSubCmd( subCmdHostsCreate, subCmdHostsRemove, subCmdHostsList, ) }, }