1
0
mirror of https://github.com/juanfont/headscale.git synced 2024-12-20 19:09:07 +01:00
juanfont.headscale/cmd/headscale/cli/nodes.go

717 lines
16 KiB
Go
Raw Normal View History

package cli
import (
"fmt"
"log"
2022-09-03 23:46:14 +02:00
"net/netip"
"slices"
2021-07-17 00:23:12 +02:00
"strconv"
2022-01-16 14:16:59 +01:00
"strings"
"time"
2021-07-17 11:09:42 +02:00
survey "github.com/AlecAivazis/survey/v2"
2021-11-04 23:44:35 +01:00
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
"github.com/juanfont/headscale/hscontrol/util"
2021-08-15 23:10:39 +02:00
"github.com/pterm/pterm"
"github.com/spf13/cobra"
"google.golang.org/grpc/status"
"tailscale.com/types/key"
)
2021-07-31 23:14:24 +02:00
func init() {
rootCmd.AddCommand(nodeCmd)
listNodesCmd.Flags().StringP("user", "u", "", "Filter by user")
listNodesCmd.Flags().BoolP("tags", "t", false, "Show tags")
listNodesCmd.Flags().StringP("namespace", "n", "", "User")
listNodesNamespaceFlag := listNodesCmd.Flags().Lookup("namespace")
listNodesNamespaceFlag.Deprecated = deprecateNamespaceMessage
listNodesNamespaceFlag.Hidden = true
nodeCmd.AddCommand(listNodesCmd)
registerNodeCmd.Flags().StringP("user", "u", "", "User")
registerNodeCmd.Flags().StringP("namespace", "n", "", "User")
registerNodeNamespaceFlag := registerNodeCmd.Flags().Lookup("namespace")
registerNodeNamespaceFlag.Deprecated = deprecateNamespaceMessage
registerNodeNamespaceFlag.Hidden = true
err := registerNodeCmd.MarkFlagRequired("user")
if err != nil {
fix tags not resolving to username if email is present (#2309) * ensure valid tags is populated on user gets too Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * ensure forced tags are added Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * remove unused envvar in test Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * debug log auth/unauth tags in policy man Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * defer shutdown in tags test Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * add tag test with groups Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * add email, display name, picture to create user Updates #2166 Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * add ability to set display and email to cli Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * add email to test users in integration Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * fix issue where tags were only assigned to email, not username Fixes #2300 Fixes #2307 Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * expand principles to correct login name and if fix an issue where nodeip principles might not expand to all relevant IPs instead of taking the first in a prefix. Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * fix ssh unit test Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * update cli and oauth tests for users with email Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * index by test email Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * fix last test Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> --------- Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-12-19 13:10:10 +01:00
log.Fatal(err.Error())
}
registerNodeCmd.Flags().StringP("key", "k", "", "Key")
err = registerNodeCmd.MarkFlagRequired("key")
if err != nil {
fix tags not resolving to username if email is present (#2309) * ensure valid tags is populated on user gets too Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * ensure forced tags are added Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * remove unused envvar in test Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * debug log auth/unauth tags in policy man Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * defer shutdown in tags test Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * add tag test with groups Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * add email, display name, picture to create user Updates #2166 Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * add ability to set display and email to cli Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * add email to test users in integration Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * fix issue where tags were only assigned to email, not username Fixes #2300 Fixes #2307 Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * expand principles to correct login name and if fix an issue where nodeip principles might not expand to all relevant IPs instead of taking the first in a prefix. Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * fix ssh unit test Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * update cli and oauth tests for users with email Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * index by test email Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * fix last test Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> --------- Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-12-19 13:10:10 +01:00
log.Fatal(err.Error())
}
2021-07-25 15:04:06 +02:00
nodeCmd.AddCommand(registerNodeCmd)
expireNodeCmd.Flags().Uint64P("identifier", "i", 0, "Node identifier (ID)")
2021-11-21 14:40:44 +01:00
err = expireNodeCmd.MarkFlagRequired("identifier")
if err != nil {
fix tags not resolving to username if email is present (#2309) * ensure valid tags is populated on user gets too Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * ensure forced tags are added Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * remove unused envvar in test Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * debug log auth/unauth tags in policy man Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * defer shutdown in tags test Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * add tag test with groups Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * add email, display name, picture to create user Updates #2166 Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * add ability to set display and email to cli Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * add email to test users in integration Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * fix issue where tags were only assigned to email, not username Fixes #2300 Fixes #2307 Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * expand principles to correct login name and if fix an issue where nodeip principles might not expand to all relevant IPs instead of taking the first in a prefix. Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * fix ssh unit test Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * update cli and oauth tests for users with email Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * index by test email Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * fix last test Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> --------- Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-12-19 13:10:10 +01:00
log.Fatal(err.Error())
2021-11-21 14:40:44 +01:00
}
nodeCmd.AddCommand(expireNodeCmd)
2022-03-13 22:03:20 +01:00
renameNodeCmd.Flags().Uint64P("identifier", "i", 0, "Node identifier (ID)")
err = renameNodeCmd.MarkFlagRequired("identifier")
if err != nil {
fix tags not resolving to username if email is present (#2309) * ensure valid tags is populated on user gets too Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * ensure forced tags are added Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * remove unused envvar in test Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * debug log auth/unauth tags in policy man Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * defer shutdown in tags test Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * add tag test with groups Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * add email, display name, picture to create user Updates #2166 Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * add ability to set display and email to cli Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * add email to test users in integration Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * fix issue where tags were only assigned to email, not username Fixes #2300 Fixes #2307 Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * expand principles to correct login name and if fix an issue where nodeip principles might not expand to all relevant IPs instead of taking the first in a prefix. Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * fix ssh unit test Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * update cli and oauth tests for users with email Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * index by test email Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * fix last test Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> --------- Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-12-19 13:10:10 +01:00
log.Fatal(err.Error())
2022-03-13 22:03:20 +01:00
}
nodeCmd.AddCommand(renameNodeCmd)
deleteNodeCmd.Flags().Uint64P("identifier", "i", 0, "Node identifier (ID)")
err = deleteNodeCmd.MarkFlagRequired("identifier")
if err != nil {
fix tags not resolving to username if email is present (#2309) * ensure valid tags is populated on user gets too Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * ensure forced tags are added Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * remove unused envvar in test Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * debug log auth/unauth tags in policy man Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * defer shutdown in tags test Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * add tag test with groups Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * add email, display name, picture to create user Updates #2166 Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * add ability to set display and email to cli Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * add email to test users in integration Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * fix issue where tags were only assigned to email, not username Fixes #2300 Fixes #2307 Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * expand principles to correct login name and if fix an issue where nodeip principles might not expand to all relevant IPs instead of taking the first in a prefix. Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * fix ssh unit test Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * update cli and oauth tests for users with email Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * index by test email Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * fix last test Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> --------- Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-12-19 13:10:10 +01:00
log.Fatal(err.Error())
}
2021-07-25 15:04:06 +02:00
nodeCmd.AddCommand(deleteNodeCmd)
moveNodeCmd.Flags().Uint64P("identifier", "i", 0, "Node identifier (ID)")
2022-05-02 06:32:33 +02:00
err = moveNodeCmd.MarkFlagRequired("identifier")
if err != nil {
fix tags not resolving to username if email is present (#2309) * ensure valid tags is populated on user gets too Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * ensure forced tags are added Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * remove unused envvar in test Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * debug log auth/unauth tags in policy man Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * defer shutdown in tags test Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * add tag test with groups Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * add email, display name, picture to create user Updates #2166 Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * add ability to set display and email to cli Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * add email to test users in integration Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * fix issue where tags were only assigned to email, not username Fixes #2300 Fixes #2307 Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * expand principles to correct login name and if fix an issue where nodeip principles might not expand to all relevant IPs instead of taking the first in a prefix. Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * fix ssh unit test Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * update cli and oauth tests for users with email Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * index by test email Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * fix last test Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> --------- Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-12-19 13:10:10 +01:00
log.Fatal(err.Error())
}
2022-05-02 06:32:33 +02:00
moveNodeCmd.Flags().StringP("user", "u", "", "New user")
moveNodeCmd.Flags().StringP("namespace", "n", "", "User")
moveNodeNamespaceFlag := moveNodeCmd.Flags().Lookup("namespace")
moveNodeNamespaceFlag.Deprecated = deprecateNamespaceMessage
moveNodeNamespaceFlag.Hidden = true
2022-05-02 06:32:33 +02:00
err = moveNodeCmd.MarkFlagRequired("user")
if err != nil {
fix tags not resolving to username if email is present (#2309) * ensure valid tags is populated on user gets too Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * ensure forced tags are added Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * remove unused envvar in test Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * debug log auth/unauth tags in policy man Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * defer shutdown in tags test Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * add tag test with groups Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * add email, display name, picture to create user Updates #2166 Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * add ability to set display and email to cli Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * add email to test users in integration Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * fix issue where tags were only assigned to email, not username Fixes #2300 Fixes #2307 Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * expand principles to correct login name and if fix an issue where nodeip principles might not expand to all relevant IPs instead of taking the first in a prefix. Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * fix ssh unit test Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * update cli and oauth tests for users with email Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * index by test email Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * fix last test Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> --------- Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-12-19 13:10:10 +01:00
log.Fatal(err.Error())
}
nodeCmd.AddCommand(moveNodeCmd)
tagCmd.Flags().Uint64P("identifier", "i", 0, "Node identifier (ID)")
err = tagCmd.MarkFlagRequired("identifier")
if err != nil {
fix tags not resolving to username if email is present (#2309) * ensure valid tags is populated on user gets too Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * ensure forced tags are added Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * remove unused envvar in test Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * debug log auth/unauth tags in policy man Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * defer shutdown in tags test Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * add tag test with groups Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * add email, display name, picture to create user Updates #2166 Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * add ability to set display and email to cli Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * add email to test users in integration Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * fix issue where tags were only assigned to email, not username Fixes #2300 Fixes #2307 Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * expand principles to correct login name and if fix an issue where nodeip principles might not expand to all relevant IPs instead of taking the first in a prefix. Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * fix ssh unit test Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * update cli and oauth tests for users with email Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * index by test email Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * fix last test Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> --------- Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-12-19 13:10:10 +01:00
log.Fatal(err.Error())
}
tagCmd.Flags().
StringSliceP("tags", "t", []string{}, "List of tags to add to the node")
nodeCmd.AddCommand(tagCmd)
nodeCmd.AddCommand(backfillNodeIPsCmd)
2021-07-25 15:04:06 +02:00
}
var nodeCmd = &cobra.Command{
Use: "nodes",
Short: "Manage the nodes of Headscale",
Aliases: []string{"node", "machine", "machines"},
2021-06-28 20:04:05 +02:00
}
2021-07-25 15:04:06 +02:00
var registerNodeCmd = &cobra.Command{
Use: "register",
2023-09-24 13:42:05 +02:00
Short: "Registers a node to your network",
Run: func(cmd *cobra.Command, args []string) {
2021-11-04 23:44:35 +01:00
output, _ := cmd.Flags().GetString("output")
user, err := cmd.Flags().GetString("user")
if err != nil {
ErrorOutput(err, fmt.Sprintf("Error getting user: %s", err), output)
}
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
2021-11-04 23:44:35 +01:00
defer cancel()
defer conn.Close()
machineKey, err := cmd.Flags().GetString("key")
if err != nil {
2021-11-13 09:36:45 +01:00
ErrorOutput(
err,
fmt.Sprintf("Error getting node key from flag: %s", err),
2021-11-13 09:36:45 +01:00
output,
)
}
2021-11-04 23:44:35 +01:00
2023-09-24 13:42:05 +02:00
request := &v1.RegisterNodeRequest{
Key: machineKey,
User: user,
2021-11-04 23:44:35 +01:00
}
2023-09-24 13:42:05 +02:00
response, err := client.RegisterNode(ctx, request)
if err != nil {
2021-11-13 09:36:45 +01:00
ErrorOutput(
err,
fmt.Sprintf(
2023-09-24 13:42:05 +02:00
"Cannot register node: %s\n",
2021-11-13 09:36:45 +01:00
status.Convert(err).Message(),
),
output,
)
}
2021-11-04 23:44:35 +01:00
SuccessOutput(
response.GetNode(),
fmt.Sprintf("Node %s registered", response.GetNode().GetGivenName()), output)
},
}
2021-07-25 15:04:06 +02:00
var listNodesCmd = &cobra.Command{
Use: "list",
Short: "List nodes",
Aliases: []string{"ls", "show"},
2021-05-01 20:00:25 +02:00
Run: func(cmd *cobra.Command, args []string) {
2021-11-04 23:44:35 +01:00
output, _ := cmd.Flags().GetString("output")
user, err := cmd.Flags().GetString("user")
2021-05-01 20:00:25 +02:00
if err != nil {
ErrorOutput(err, fmt.Sprintf("Error getting user: %s", err), output)
2021-05-01 20:00:25 +02:00
}
showTags, err := cmd.Flags().GetBool("tags")
if err != nil {
ErrorOutput(err, fmt.Sprintf("Error getting tags flag: %s", err), output)
}
2021-05-01 20:00:25 +02:00
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
2021-11-04 23:44:35 +01:00
defer cancel()
defer conn.Close()
2021-09-02 17:06:47 +02:00
2023-09-24 13:42:05 +02:00
request := &v1.ListNodesRequest{
User: user,
2021-09-03 10:23:45 +02:00
}
2023-09-24 13:42:05 +02:00
response, err := client.ListNodes(ctx, request)
2021-11-04 23:44:35 +01:00
if err != nil {
2021-11-13 09:36:45 +01:00
ErrorOutput(
err,
fmt.Sprintf("Cannot get nodes: %s", status.Convert(err).Message()),
output,
)
2021-05-08 13:58:51 +02:00
}
2021-11-04 23:44:35 +01:00
if output != "" {
SuccessOutput(response.GetNodes(), "", output)
2021-05-01 20:00:25 +02:00
}
tableData, err := nodesToPtables(user, showTags, response.GetNodes())
2021-08-15 23:10:39 +02:00
if err != nil {
2021-11-04 23:44:35 +01:00
ErrorOutput(err, fmt.Sprintf("Error converting to table: %s", err), output)
2021-05-01 20:00:25 +02:00
}
err = pterm.DefaultTable.WithHasHeader().WithData(tableData).Render()
2021-08-15 23:35:03 +02:00
if err != nil {
2021-11-13 09:36:45 +01:00
ErrorOutput(
err,
fmt.Sprintf("Failed to render pterm table: %s", err),
output,
)
2021-08-15 23:35:03 +02:00
}
2021-05-01 20:00:25 +02:00
},
}
2021-07-17 00:23:12 +02:00
2021-11-21 14:40:44 +01:00
var expireNodeCmd = &cobra.Command{
Use: "expire",
2023-09-24 13:42:05 +02:00
Short: "Expire (log out) a node in your network",
2021-11-21 22:35:36 +01:00
Long: "Expiring a node will keep the node in the database and force it to reauthenticate.",
Aliases: []string{"logout", "exp", "e"},
2021-11-21 14:40:44 +01:00
Run: func(cmd *cobra.Command, args []string) {
output, _ := cmd.Flags().GetString("output")
2021-11-21 22:34:03 +01:00
identifier, err := cmd.Flags().GetUint64("identifier")
2021-11-21 14:40:44 +01:00
if err != nil {
ErrorOutput(
err,
fmt.Sprintf("Error converting ID to integer: %s", err),
output,
)
return
}
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
2021-11-21 14:40:44 +01:00
defer cancel()
defer conn.Close()
2023-09-24 13:42:05 +02:00
request := &v1.ExpireNodeRequest{
NodeId: identifier,
2021-11-21 14:40:44 +01:00
}
2023-09-24 13:42:05 +02:00
response, err := client.ExpireNode(ctx, request)
2021-11-21 14:40:44 +01:00
if err != nil {
ErrorOutput(
err,
fmt.Sprintf(
2023-09-24 13:42:05 +02:00
"Cannot expire node: %s\n",
2021-11-21 14:40:44 +01:00
status.Convert(err).Message(),
),
output,
)
return
}
SuccessOutput(response.GetNode(), "Node expired", output)
2021-11-21 14:40:44 +01:00
},
}
2022-03-13 22:03:20 +01:00
var renameNodeCmd = &cobra.Command{
2022-04-24 21:57:15 +02:00
Use: "rename NEW_NAME",
2023-09-24 13:42:05 +02:00
Short: "Renames a node in your network",
2022-03-13 22:03:20 +01:00
Run: func(cmd *cobra.Command, args []string) {
output, _ := cmd.Flags().GetString("output")
identifier, err := cmd.Flags().GetUint64("identifier")
if err != nil {
ErrorOutput(
err,
fmt.Sprintf("Error converting ID to integer: %s", err),
output,
)
return
}
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
2022-03-13 22:03:20 +01:00
defer cancel()
defer conn.Close()
2022-03-13 22:10:41 +01:00
newName := ""
if len(args) > 0 {
newName = args[0]
}
2023-09-24 13:42:05 +02:00
request := &v1.RenameNodeRequest{
NodeId: identifier,
NewName: newName,
2022-03-13 22:03:20 +01:00
}
2023-09-24 13:42:05 +02:00
response, err := client.RenameNode(ctx, request)
2022-03-13 22:03:20 +01:00
if err != nil {
ErrorOutput(
err,
fmt.Sprintf(
2023-09-24 13:42:05 +02:00
"Cannot rename node: %s\n",
2022-03-13 22:03:20 +01:00
status.Convert(err).Message(),
),
output,
)
return
}
SuccessOutput(response.GetNode(), "Node renamed", output)
2022-03-13 22:03:20 +01:00
},
}
2021-07-25 15:04:06 +02:00
var deleteNodeCmd = &cobra.Command{
Use: "delete",
Short: "Delete a node",
Aliases: []string{"del"},
2021-07-17 00:23:12 +02:00
Run: func(cmd *cobra.Command, args []string) {
output, _ := cmd.Flags().GetString("output")
2021-11-04 23:44:35 +01:00
2021-11-21 22:34:03 +01:00
identifier, err := cmd.Flags().GetUint64("identifier")
2021-07-17 00:23:12 +02:00
if err != nil {
2021-11-13 09:36:45 +01:00
ErrorOutput(
err,
fmt.Sprintf("Error converting ID to integer: %s", err),
output,
)
2021-11-14 16:46:09 +01:00
2021-11-04 23:44:35 +01:00
return
2021-07-17 00:23:12 +02:00
}
2021-11-04 23:44:35 +01:00
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
2021-11-04 23:44:35 +01:00
defer cancel()
defer conn.Close()
2023-09-24 13:42:05 +02:00
getRequest := &v1.GetNodeRequest{
NodeId: identifier,
2021-11-04 23:44:35 +01:00
}
2023-09-24 13:42:05 +02:00
getResponse, err := client.GetNode(ctx, getRequest)
2021-07-17 00:23:12 +02:00
if err != nil {
2021-11-13 09:36:45 +01:00
ErrorOutput(
err,
fmt.Sprintf(
"Error getting node node: %s",
status.Convert(err).Message(),
),
output,
)
2021-11-14 16:46:09 +01:00
2021-11-04 23:44:35 +01:00
return
}
2023-09-24 13:42:05 +02:00
deleteRequest := &v1.DeleteNodeRequest{
NodeId: identifier,
2021-07-17 00:23:12 +02:00
}
2021-07-17 11:09:42 +02:00
confirm := false
force, _ := cmd.Flags().GetBool("force")
if !force {
prompt := &survey.Confirm{
2021-11-13 09:36:45 +01:00
Message: fmt.Sprintf(
"Do you want to remove the node %s?",
getResponse.GetNode().GetName(),
2021-11-13 09:36:45 +01:00
),
}
err = survey.AskOne(prompt, &confirm)
if err != nil {
return
}
}
if confirm || force {
2023-09-24 13:42:05 +02:00
response, err := client.DeleteNode(ctx, deleteRequest)
2021-11-04 23:44:35 +01:00
if output != "" {
SuccessOutput(response, "", output)
2021-11-14 16:46:09 +01:00
return
}
2021-07-17 11:09:42 +02:00
if err != nil {
2021-11-13 09:36:45 +01:00
ErrorOutput(
err,
fmt.Sprintf(
"Error deleting node: %s",
status.Convert(err).Message(),
),
output,
)
2021-11-14 16:46:09 +01:00
return
}
2021-11-13 09:36:45 +01:00
SuccessOutput(
map[string]string{"Result": "Node deleted"},
"Node deleted",
output,
)
2021-11-04 23:44:35 +01:00
} else {
SuccessOutput(map[string]string{"Result": "Node not deleted"}, "Node not deleted", output)
2021-07-17 00:23:12 +02:00
}
},
}
2021-08-15 23:10:39 +02:00
var moveNodeCmd = &cobra.Command{
Use: "move",
Short: "Move node to another user",
Aliases: []string{"mv"},
Run: func(cmd *cobra.Command, args []string) {
output, _ := cmd.Flags().GetString("output")
identifier, err := cmd.Flags().GetUint64("identifier")
if err != nil {
ErrorOutput(
err,
fmt.Sprintf("Error converting ID to integer: %s", err),
output,
)
return
}
user, err := cmd.Flags().GetString("user")
if err != nil {
ErrorOutput(
err,
fmt.Sprintf("Error getting user: %s", err),
output,
)
return
}
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
defer cancel()
defer conn.Close()
2023-09-24 13:42:05 +02:00
getRequest := &v1.GetNodeRequest{
NodeId: identifier,
}
2023-09-24 13:42:05 +02:00
_, err = client.GetNode(ctx, getRequest)
if err != nil {
ErrorOutput(
err,
fmt.Sprintf(
"Error getting node: %s",
status.Convert(err).Message(),
),
output,
)
return
}
2023-09-24 13:42:05 +02:00
moveRequest := &v1.MoveNodeRequest{
NodeId: identifier,
User: user,
}
2023-09-24 13:42:05 +02:00
moveResponse, err := client.MoveNode(ctx, moveRequest)
if err != nil {
ErrorOutput(
err,
fmt.Sprintf(
"Error moving node: %s",
status.Convert(err).Message(),
),
output,
)
return
}
SuccessOutput(moveResponse.GetNode(), "Node moved to another user", output)
},
}
var backfillNodeIPsCmd = &cobra.Command{
Use: "backfillips",
Short: "Backfill IPs missing from nodes",
Long: `
Backfill IPs can be used to add/remove IPs from nodes
based on the current configuration of Headscale.
If there are nodes that does not have IPv4 or IPv6
even if prefixes for both are configured in the config,
this command can be used to assign IPs of the sort to
all nodes that are missing.
If you remove IPv4 or IPv6 prefixes from the config,
it can be run to remove the IPs that should no longer
be assigned to nodes.`,
Run: func(cmd *cobra.Command, args []string) {
var err error
output, _ := cmd.Flags().GetString("output")
confirm := false
prompt := &survey.Confirm{
Message: "Are you sure that you want to assign/remove IPs to/from nodes?",
}
err = survey.AskOne(prompt, &confirm)
if err != nil {
return
}
if confirm {
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
defer cancel()
defer conn.Close()
changes, err := client.BackfillNodeIPs(ctx, &v1.BackfillNodeIPsRequest{Confirmed: confirm})
if err != nil {
ErrorOutput(
err,
fmt.Sprintf(
"Error backfilling IPs: %s",
status.Convert(err).Message(),
),
output,
)
return
}
SuccessOutput(changes, "Node IPs backfilled successfully", output)
}
},
}
2021-11-13 09:36:45 +01:00
func nodesToPtables(
currentUser string,
showTags bool,
2023-09-24 13:42:05 +02:00
nodes []*v1.Node,
2021-11-13 09:36:45 +01:00
) (pterm.TableData, error) {
tableHeader := []string{
"ID",
2022-07-22 19:33:11 +02:00
"Hostname",
"Name",
"MachineKey",
"NodeKey",
"User",
"IP addresses",
"Ephemeral",
"Last seen",
2023-01-11 13:35:47 +01:00
"Expiration",
"Connected",
"Expired",
2021-11-13 09:36:45 +01:00
}
if showTags {
tableHeader = append(tableHeader, []string{
"ForcedTags",
"InvalidTags",
"ValidTags",
}...)
2021-11-13 09:36:45 +01:00
}
tableData := pterm.TableData{tableHeader}
2021-08-15 23:10:39 +02:00
2023-09-24 13:42:05 +02:00
for _, node := range nodes {
2021-08-15 23:10:39 +02:00
var ephemeral bool
if node.GetPreAuthKey() != nil && node.GetPreAuthKey().GetEphemeral() {
2021-08-15 23:10:39 +02:00
ephemeral = true
}
2021-08-15 23:10:39 +02:00
var lastSeen time.Time
var lastSeenTime string
if node.GetLastSeen() != nil {
lastSeen = node.GetLastSeen().AsTime()
lastSeenTime = lastSeen.Format("2006-01-02 15:04:05")
2021-08-15 23:10:39 +02:00
}
var expiry time.Time
2023-01-11 13:35:47 +01:00
var expiryTime string
if node.GetExpiry() != nil {
expiry = node.GetExpiry().AsTime()
2023-01-11 13:35:47 +01:00
expiryTime = expiry.Format("2006-01-02 15:04:05")
} else {
expiryTime = "N/A"
}
var machineKey key.MachinePublic
err := machineKey.UnmarshalText(
[]byte(node.GetMachineKey()),
)
if err != nil {
machineKey = key.MachinePublic{}
}
var nodeKey key.NodePublic
err = nodeKey.UnmarshalText(
[]byte(node.GetNodeKey()),
)
2021-08-15 23:10:39 +02:00
if err != nil {
return nil, err
}
var online string
if node.GetOnline() {
online = pterm.LightGreen("online")
} else {
online = pterm.LightRed("offline")
}
var expired string
if expiry.IsZero() || expiry.After(time.Now()) {
expired = pterm.LightGreen("no")
2021-08-15 23:10:39 +02:00
} else {
expired = pterm.LightRed("yes")
2021-08-15 23:10:39 +02:00
}
2021-09-02 17:06:47 +02:00
var forcedTags string
for _, tag := range node.GetForcedTags() {
forcedTags += "," + tag
2022-04-16 13:32:00 +02:00
}
forcedTags = strings.TrimLeft(forcedTags, ",")
var invalidTags string
for _, tag := range node.GetInvalidTags() {
if !slices.Contains(node.GetForcedTags(), tag) {
invalidTags += "," + pterm.LightRed(tag)
2022-04-16 13:32:00 +02:00
}
}
invalidTags = strings.TrimLeft(invalidTags, ",")
var validTags string
for _, tag := range node.GetValidTags() {
if !slices.Contains(node.GetForcedTags(), tag) {
validTags += "," + pterm.LightGreen(tag)
2022-04-16 13:32:00 +02:00
}
}
validTags = strings.TrimLeft(validTags, ",")
2022-04-16 13:32:00 +02:00
var user string
if currentUser == "" || (currentUser == node.GetUser().GetName()) {
user = pterm.LightMagenta(node.GetUser().GetName())
2021-09-02 17:06:47 +02:00
} else {
// Shared into this user
user = pterm.LightYellow(node.GetUser().GetName())
2021-09-02 17:06:47 +02:00
}
2022-05-08 21:06:12 +02:00
2022-05-16 14:59:46 +02:00
var IPV4Address string
var IPV6Address string
for _, addr := range node.GetIpAddresses() {
2022-09-03 23:46:14 +02:00
if netip.MustParseAddr(addr).Is4() {
2022-05-16 14:59:46 +02:00
IPV4Address = addr
2022-05-08 21:21:10 +02:00
} else {
2022-05-16 14:59:46 +02:00
IPV6Address = addr
2022-05-08 21:21:10 +02:00
}
2022-05-08 21:06:12 +02:00
}
nodeData := []string{
strconv.FormatUint(node.GetId(), util.Base10),
node.GetName(),
2023-09-24 13:42:05 +02:00
node.GetGivenName(),
machineKey.ShortString(),
nodeKey.ShortString(),
user,
2022-05-16 14:59:46 +02:00
strings.Join([]string{IPV4Address, IPV6Address}, ", "),
strconv.FormatBool(ephemeral),
lastSeenTime,
2023-01-11 13:35:47 +01:00
expiryTime,
online,
expired,
}
if showTags {
nodeData = append(nodeData, []string{forcedTags, invalidTags, validTags}...)
}
tableData = append(
tableData,
nodeData,
2021-11-04 23:44:35 +01:00
)
2021-08-15 23:10:39 +02:00
}
2021-11-14 16:46:09 +01:00
return tableData, nil
2021-08-15 23:10:39 +02:00
}
var tagCmd = &cobra.Command{
Use: "tag",
Short: "Manage the tags of a node",
Aliases: []string{"tags", "t"},
Run: func(cmd *cobra.Command, args []string) {
output, _ := cmd.Flags().GetString("output")
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
defer cancel()
defer conn.Close()
// retrieve flags from CLI
identifier, err := cmd.Flags().GetUint64("identifier")
if err != nil {
ErrorOutput(
err,
fmt.Sprintf("Error converting ID to integer: %s", err),
output,
)
return
}
tagsToSet, err := cmd.Flags().GetStringSlice("tags")
if err != nil {
ErrorOutput(
err,
2023-09-24 13:42:05 +02:00
fmt.Sprintf("Error retrieving list of tags to add to node, %v", err),
output,
)
return
}
2023-09-24 13:42:05 +02:00
// Sending tags to node
request := &v1.SetTagsRequest{
2023-09-24 13:42:05 +02:00
NodeId: identifier,
Tags: tagsToSet,
}
resp, err := client.SetTags(ctx, request)
if err != nil {
ErrorOutput(
err,
fmt.Sprintf("Error while sending tags to headscale: %s", err),
output,
)
2022-05-13 10:17:52 +02:00
return
}
if resp != nil {
SuccessOutput(
2023-09-24 13:42:05 +02:00
resp.GetNode(),
"Node updated",
output,
)
}
},
}