diff --git a/api.go b/api.go
index a31cf529..0c1f3d41 100644
--- a/api.go
+++ b/api.go
@@ -43,7 +43,7 @@ func (h *Headscale) RegisterWebAPI(c *gin.Context) {
- headscale -n NAMESPACE nodes register %s
+ headscale -n NAMESPACE nodes register -k %s
diff --git a/cmd/headscale/cli/nodes.go b/cmd/headscale/cli/nodes.go
index c44aa5ed..cdf37efb 100644
--- a/cmd/headscale/cli/nodes.go
+++ b/cmd/headscale/cli/nodes.go
@@ -17,15 +17,50 @@ import (
func init() {
rootCmd.AddCommand(nodeCmd)
- nodeCmd.PersistentFlags().StringP("namespace", "n", "", "Namespace")
- err := nodeCmd.MarkPersistentFlagRequired("namespace")
+ listNodesCmd.Flags().StringP("namespace", "n", "", "Filter by namespace")
+ nodeCmd.AddCommand(listNodesCmd)
+
+ registerNodeCmd.Flags().StringP("namespace", "n", "", "Namespace")
+ err := registerNodeCmd.MarkFlagRequired("namespace")
+ if err != nil {
+ log.Fatalf(err.Error())
+ }
+ registerNodeCmd.Flags().StringP("key", "k", "", "Key")
+ err = registerNodeCmd.MarkFlagRequired("key")
if err != nil {
log.Fatalf(err.Error())
}
- nodeCmd.AddCommand(listNodesCmd)
nodeCmd.AddCommand(registerNodeCmd)
+
+ deleteNodeCmd.Flags().IntP("identifier", "i", 0, "Node identifier (ID)")
+ err = deleteNodeCmd.MarkFlagRequired("identifier")
+ if err != nil {
+ log.Fatalf(err.Error())
+ }
nodeCmd.AddCommand(deleteNodeCmd)
+
+ shareMachineCmd.Flags().StringP("namespace", "n", "", "Namespace")
+ err = shareMachineCmd.MarkFlagRequired("namespace")
+ if err != nil {
+ log.Fatalf(err.Error())
+ }
+ shareMachineCmd.Flags().IntP("identifier", "i", 0, "Node identifier (ID)")
+ err = shareMachineCmd.MarkFlagRequired("identifier")
+ if err != nil {
+ log.Fatalf(err.Error())
+ }
nodeCmd.AddCommand(shareMachineCmd)
+
+ unshareMachineCmd.Flags().StringP("namespace", "n", "", "Namespace")
+ err = unshareMachineCmd.MarkFlagRequired("namespace")
+ if err != nil {
+ log.Fatalf(err.Error())
+ }
+ unshareMachineCmd.Flags().IntP("identifier", "i", 0, "Node identifier (ID)")
+ err = unshareMachineCmd.MarkFlagRequired("identifier")
+ if err != nil {
+ log.Fatalf(err.Error())
+ }
nodeCmd.AddCommand(unshareMachineCmd)
}
@@ -35,14 +70,8 @@ var nodeCmd = &cobra.Command{
}
var registerNodeCmd = &cobra.Command{
- Use: "register machineID",
+ Use: "register",
Short: "Registers a machine to your network",
- Args: func(cmd *cobra.Command, args []string) error {
- if len(args) < 1 {
- return fmt.Errorf("missing parameters")
- }
- return nil
- },
Run: func(cmd *cobra.Command, args []string) {
n, err := cmd.Flags().GetString("namespace")
if err != nil {
@@ -54,7 +83,11 @@ var registerNodeCmd = &cobra.Command{
if err != nil {
log.Fatalf("Error initializing: %s", err)
}
- m, err := h.RegisterMachine(args[0], n)
+ machineIDStr, err := cmd.Flags().GetString("key")
+ if err != nil {
+ log.Fatalf("Error getting machine ID: %s", err)
+ }
+ m, err := h.RegisterMachine(machineIDStr, n)
if strings.HasPrefix(o, "json") {
JsonOutput(m, err, o)
return
@@ -69,7 +102,7 @@ var registerNodeCmd = &cobra.Command{
var listNodesCmd = &cobra.Command{
Use: "list",
- Short: "List the nodes in a given namespace",
+ Short: "List nodes",
Run: func(cmd *cobra.Command, args []string) {
n, err := cmd.Flags().GetString("namespace")
if err != nil {
@@ -82,23 +115,44 @@ var listNodesCmd = &cobra.Command{
log.Fatalf("Error initializing: %s", err)
}
- namespace, err := h.GetNamespace(n)
- if err != nil {
- log.Fatalf("Error fetching namespace: %s", err)
+ var namespaces []headscale.Namespace
+ var namespace *headscale.Namespace
+ var sharedMachines *[]headscale.Machine
+ if len(n) == 0 {
+ // no namespace provided, list all
+ tmp, err := h.ListNamespaces()
+ if err != nil {
+ log.Fatalf("Error fetching namespace: %s", err)
+ }
+ namespaces = *tmp
+ } else {
+ namespace, err = h.GetNamespace(n)
+ if err != nil {
+ log.Fatalf("Error fetching namespace: %s", err)
+ }
+ namespaces = append(namespaces, *namespace)
+
+ sharedMachines, err = h.ListSharedMachinesInNamespace(n)
+ if err != nil {
+ log.Fatalf("Error fetching shared machines: %s", err)
+ }
}
- machines, err := h.ListMachinesInNamespace(n)
- if err != nil {
- log.Fatalf("Error fetching machines: %s", err)
+ var allMachines []headscale.Machine
+ for _, namespace := range namespaces {
+ machines, err := h.ListMachinesInNamespace(namespace.Name)
+ if err != nil {
+ log.Fatalf("Error fetching machines: %s", err)
+ }
+ allMachines = append(allMachines, *machines...)
}
- sharedMachines, err := h.ListSharedMachinesInNamespace(n)
- if err != nil {
- log.Fatalf("Error fetching shared machines: %s", err)
+ // listing sharedMachines is only relevant when a particular namespace is
+ // requested
+ if sharedMachines != nil {
+ allMachines = append(allMachines, *sharedMachines...)
}
- allMachines := append(*machines, *sharedMachines...)
-
if strings.HasPrefix(o, "json") {
JsonOutput(allMachines, err, o)
return
@@ -108,7 +162,7 @@ var listNodesCmd = &cobra.Command{
log.Fatalf("Error getting nodes: %s", err)
}
- d, err := nodesToPtables(*namespace, allMachines)
+ d, err := nodesToPtables(namespace, allMachines)
if err != nil {
log.Fatalf("Error converting to table: %s", err)
}
@@ -121,21 +175,15 @@ var listNodesCmd = &cobra.Command{
}
var deleteNodeCmd = &cobra.Command{
- Use: "delete ID",
+ Use: "delete",
Short: "Delete a node",
- Args: func(cmd *cobra.Command, args []string) error {
- if len(args) < 1 {
- return fmt.Errorf("missing parameters")
- }
- return nil
- },
Run: func(cmd *cobra.Command, args []string) {
output, _ := cmd.Flags().GetString("output")
h, err := getHeadscaleApp()
if err != nil {
log.Fatalf("Error initializing: %s", err)
}
- id, err := strconv.Atoi(args[0])
+ id, err := cmd.Flags().GetInt("identifier")
if err != nil {
log.Fatalf("Error converting ID to integer: %s", err)
}
@@ -176,47 +224,42 @@ var deleteNodeCmd = &cobra.Command{
},
}
+func sharingWorker(cmd *cobra.Command, args []string) (*headscale.Headscale, string, *headscale.Machine, *headscale.Namespace) {
+ namespaceStr, err := cmd.Flags().GetString("namespace")
+ if err != nil {
+ log.Fatalf("Error getting namespace: %s", err)
+ }
+
+ output, _ := cmd.Flags().GetString("output")
+
+ h, err := getHeadscaleApp()
+ if err != nil {
+ log.Fatalf("Error initializing: %s", err)
+ }
+
+ namespace, err := h.GetNamespace(namespaceStr)
+ if err != nil {
+ log.Fatalf("Error fetching namespace %s: %s", namespaceStr, err)
+ }
+
+ id, err := cmd.Flags().GetInt("identifier")
+ if err != nil {
+ log.Fatalf("Error converting ID to integer: %s", err)
+ }
+ machine, err := h.GetMachineByID(uint64(id))
+ if err != nil {
+ log.Fatalf("Error getting node: %s", err)
+ }
+
+ return h, output, machine, namespace
+}
+
var shareMachineCmd = &cobra.Command{
- Use: "share ID namespace",
+ Use: "share",
Short: "Shares a node from the current namespace to the specified one",
- Args: func(cmd *cobra.Command, args []string) error {
- if len(args) < 2 {
- return fmt.Errorf("missing parameters")
- }
- return nil
- },
Run: func(cmd *cobra.Command, args []string) {
- namespace, err := cmd.Flags().GetString("namespace")
- if err != nil {
- log.Fatalf("Error getting namespace: %s", err)
- }
- output, _ := cmd.Flags().GetString("output")
-
- h, err := getHeadscaleApp()
- if err != nil {
- log.Fatalf("Error initializing: %s", err)
- }
-
- _, err = h.GetNamespace(namespace)
- if err != nil {
- log.Fatalf("Error fetching origin namespace: %s", err)
- }
-
- destinationNamespace, err := h.GetNamespace(args[1])
- if err != nil {
- log.Fatalf("Error fetching destination namespace: %s", err)
- }
-
- id, err := strconv.Atoi(args[0])
- if err != nil {
- log.Fatalf("Error converting ID to integer: %s", err)
- }
- machine, err := h.GetMachineByID(uint64(id))
- if err != nil {
- log.Fatalf("Error getting node: %s", err)
- }
-
- err = h.AddSharedMachineToNamespace(machine, destinationNamespace)
+ h, output, machine, namespace := sharingWorker(cmd, args)
+ err := h.AddSharedMachineToNamespace(machine, namespace)
if strings.HasPrefix(output, "json") {
JsonOutput(map[string]string{"Result": "Node shared"}, err, output)
return
@@ -231,41 +274,11 @@ var shareMachineCmd = &cobra.Command{
}
var unshareMachineCmd = &cobra.Command{
- Use: "unshare ID",
+ Use: "unshare",
Short: "Unshares a node from the specified namespace",
- Args: func(cmd *cobra.Command, args []string) error {
- if len(args) < 1 {
- return fmt.Errorf("missing parameters")
- }
- return nil
- },
Run: func(cmd *cobra.Command, args []string) {
- namespace, err := cmd.Flags().GetString("namespace")
- if err != nil {
- log.Fatalf("Error getting namespace: %s", err)
- }
- output, _ := cmd.Flags().GetString("output")
-
- h, err := getHeadscaleApp()
- if err != nil {
- log.Fatalf("Error initializing: %s", err)
- }
-
- n, err := h.GetNamespace(namespace)
- if err != nil {
- log.Fatalf("Error fetching namespace: %s", err)
- }
-
- id, err := strconv.Atoi(args[0])
- if err != nil {
- log.Fatalf("Error converting ID to integer: %s", err)
- }
- machine, err := h.GetMachineByID(uint64(id))
- if err != nil {
- log.Fatalf("Error getting node: %s", err)
- }
-
- err = h.RemoveSharedMachineFromNamespace(machine, n)
+ h, output, machine, namespace := sharingWorker(cmd, args)
+ err := h.RemoveSharedMachineFromNamespace(machine, namespace)
if strings.HasPrefix(output, "json") {
JsonOutput(map[string]string{"Result": "Node unshared"}, err, output)
return
@@ -279,7 +292,7 @@ var unshareMachineCmd = &cobra.Command{
},
}
-func nodesToPtables(currentNamespace headscale.Namespace, machines []headscale.Machine) (pterm.TableData, error) {
+func nodesToPtables(currentNamespace *headscale.Namespace, machines []headscale.Machine) (pterm.TableData, error) {
d := pterm.TableData{{"ID", "Name", "NodeKey", "Namespace", "IP address", "Ephemeral", "Last seen", "Online"}}
for _, machine := range machines {
@@ -307,9 +320,10 @@ func nodesToPtables(currentNamespace headscale.Namespace, machines []headscale.M
}
var namespace string
- if currentNamespace.ID == machine.NamespaceID {
+ if (currentNamespace == nil) || (currentNamespace.ID == machine.NamespaceID) {
namespace = pterm.LightMagenta(machine.Namespace.Name)
} else {
+ // Shared into this namespace
namespace = pterm.LightYellow(machine.Namespace.Name)
}
d = append(d, []string{strconv.FormatUint(machine.ID, 10), machine.Name, nodeKey.ShortString(), namespace, machine.IPAddress, strconv.FormatBool(ephemeral), lastSeenTime, online})
diff --git a/docs/Running.md b/docs/Running.md
index 08373653..dbb8704c 100644
--- a/docs/Running.md
+++ b/docs/Running.md
@@ -97,7 +97,7 @@
9. In the server, register your machine to a namespace with the CLI
```shell
- headscale -n myfirstnamespace nodes register YOURMACHINEKEY
+ headscale -n myfirstnamespace nodes register -k YOURMACHINEKEY
```
or docker:
```shell
@@ -106,11 +106,11 @@
-v $(pwd)/config.json:/config.json \
-v $(pwd)/derp.yaml:/derp.yaml \
headscale/headscale:x.x.x \
- headscale -n myfirstnamespace nodes register YOURMACHINEKEY
+ headscale -n myfirstnamespace nodes register -k YOURMACHINEKEY
```
or if your server is already running in docker:
```shell
- docker exec headscale -n myfirstnamespace nodes register YOURMACHINEKEY
+ docker exec headscale -n myfirstnamespace nodes register -k YOURMACHINEKEY
```
Alternatively, you can use Auth Keys to register your machines:
diff --git a/integration_test.go b/integration_test.go
index 53092423..f73d76f6 100644
--- a/integration_test.go
+++ b/integration_test.go
@@ -493,7 +493,7 @@ func (s *IntegrationTestSuite) TestSharedNodes() {
result, err := executeCommand(
&headscale,
- []string{"headscale", "nodes", "share", "--namespace", "shared", fmt.Sprint(machine.ID), "main"},
+ []string{"headscale", "nodes", "share", "--identifier", fmt.Sprint(machine.ID), "--namespace", "main"},
[]string{},
)
assert.Nil(s.T(), err)
diff --git a/sharing.go b/sharing.go
index 879ed06f..5f6a8f45 100644
--- a/sharing.go
+++ b/sharing.go
@@ -43,7 +43,8 @@ func (h *Headscale) AddSharedMachineToNamespace(m *Machine, ns *Namespace) error
// RemoveSharedMachineFromNamespace removes a shared machine from a namespace
func (h *Headscale) RemoveSharedMachineFromNamespace(m *Machine, ns *Namespace) error {
if m.NamespaceID == ns.ID {
- return errorSameNamespace
+ // Can't unshare from primary namespace
+ return errorMachineNotShared
}
sharedMachine := SharedMachine{}
diff --git a/sharing_test.go b/sharing_test.go
index 140b05f2..1133fd92 100644
--- a/sharing_test.go
+++ b/sharing_test.go
@@ -86,6 +86,9 @@ func (s *Suite) TestUnshare(c *check.C) {
err = h.RemoveSharedMachineFromNamespace(m2, n1)
c.Assert(err, check.Equals, errorMachineNotShared)
+
+ err = h.RemoveSharedMachineFromNamespace(m1, n1)
+ c.Assert(err, check.Equals, errorMachineNotShared)
}
func (s *Suite) TestAlreadyShared(c *check.C) {