Merge pull request #22 from juanfont/json-output
Added JSON-formatted output to CLI
This commit is contained in:
commit
3cf599be64
10 changed files with 167 additions and 47 deletions
43
README.md
43
README.md
|
@ -6,9 +6,9 @@ An open source implementation of the Tailscale coordination server.
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
Tailscale is [a modern VPN](https://tailscale.com/) built on top of [Wireguard](https://www.wireguard.com/). It [works like an overlay network](https://tailscale.com/blog/how-tailscale-works/) between the computers of your networks - using all kinds of [NAT traversal sorcery](https://tailscale.com/blog/how-nat-traversal-works/).
|
Tailscale is [a modern VPN](https://tailscale.com/) built on top of [Wireguard](https://www.wireguard.com/). It [works like an overlay network](https://tailscale.com/blog/how-tailscale-works/) between the computers of your networks - using all kinds of [NAT traversal sorcery](https://tailscale.com/blog/how-nat-traversal-works/).
|
||||||
|
|
||||||
Everything in Tailscale is Open Source, except the GUI clients for proprietary OS (Windows and macOS/iOS), and the 'coordination/control server'.
|
Everything in Tailscale is Open Source, except the GUI clients for proprietary OS (Windows and macOS/iOS), and the 'coordination/control server'.
|
||||||
|
|
||||||
The control server works as an exchange point of cryptographic public keys for the nodes in the Tailscale network. It also assigns the IP addresses of the clients, creates the boundaries between each user, enables sharing machines between users, and exposes the advertised routes of your nodes.
|
The control server works as an exchange point of cryptographic public keys for the nodes in the Tailscale network. It also assigns the IP addresses of the clients, creates the boundaries between each user, enables sharing machines between users, and exposes the advertised routes of your nodes.
|
||||||
|
|
||||||
|
@ -20,9 +20,10 @@ Headscale implements this coordination server.
|
||||||
- [x] Node registration through the web flow
|
- [x] Node registration through the web flow
|
||||||
- [x] Network changes are relied to the nodes
|
- [x] Network changes are relied to the nodes
|
||||||
- [x] ~~Multiuser~~ Namespace support
|
- [x] ~~Multiuser~~ Namespace support
|
||||||
- [x] Basic routing (advertise & accept)
|
- [x] Basic routing (advertise & accept)
|
||||||
- [ ] Share nodes between ~~users~~ namespaces
|
- [ ] Share nodes between ~~users~~ namespaces
|
||||||
- [x] Node registration via pre-auth keys
|
- [x] Node registration via pre-auth keys
|
||||||
|
- [X] JSON-formatted output
|
||||||
- [ ] ACLs
|
- [ ] ACLs
|
||||||
- [ ] DNS
|
- [ ] DNS
|
||||||
|
|
||||||
|
@ -42,10 +43,10 @@ Suggestions/PRs welcomed!
|
||||||
```shell
|
```shell
|
||||||
make
|
make
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Get yourself a PostgreSQL DB running (yes, [I know](https://tailscale.com/blog/an-unlikely-database-migration/))
|
2. Get yourself a PostgreSQL DB running (yes, [I know](https://tailscale.com/blog/an-unlikely-database-migration/))
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
docker run --name headscale -e POSTGRES_DB=headscale -e \
|
docker run --name headscale -e POSTGRES_DB=headscale -e \
|
||||||
POSTGRES_USER=foo -e POSTGRES_PASSWORD=bar -p 5432:5432 -d postgres
|
POSTGRES_USER=foo -e POSTGRES_PASSWORD=bar -p 5432:5432 -d postgres
|
||||||
```
|
```
|
||||||
|
@ -53,7 +54,7 @@ Suggestions/PRs welcomed!
|
||||||
3. Set some stuff up (headscale Wireguard keys & the config.json file)
|
3. Set some stuff up (headscale Wireguard keys & the config.json file)
|
||||||
```shell
|
```shell
|
||||||
wg genkey > private.key
|
wg genkey > private.key
|
||||||
wg pubkey < private.key > public.key # not needed
|
wg pubkey < private.key > public.key # not needed
|
||||||
cp config.json.example config.json
|
cp config.json.example config.json
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -66,7 +67,7 @@ Suggestions/PRs welcomed!
|
||||||
```shell
|
```shell
|
||||||
./headscale serve
|
./headscale serve
|
||||||
```
|
```
|
||||||
|
|
||||||
6. Add your first machine
|
6. Add your first machine
|
||||||
```shell
|
```shell
|
||||||
tailscale up -login-server YOUR_HEADSCALE_URL
|
tailscale up -login-server YOUR_HEADSCALE_URL
|
||||||
|
@ -79,6 +80,22 @@ Suggestions/PRs welcomed!
|
||||||
./headscale -n myfirstnamespace node register YOURMACHINEKEY
|
./headscale -n myfirstnamespace node register YOURMACHINEKEY
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Alternatively, you can use Auth Keys to register your machines:
|
||||||
|
|
||||||
|
1. Create an authkey
|
||||||
|
```shell
|
||||||
|
./headscale -n myfirstnamespace preauthkey create --reusable --expiration 24h
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Use the authkey from your machine to register it
|
||||||
|
```shell
|
||||||
|
tailscale up -login-server YOUR_HEADSCALE_URL --authkey YOURAUTHKEY
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Please bear in mind that all the commands from headscale support adding `-o json` or `-o json-line` to get a nicely JSON-formatted output.
|
||||||
|
|
||||||
|
|
||||||
## Configuration reference
|
## Configuration reference
|
||||||
|
|
||||||
Headscale's configuration file is named `config.json` or `config.yaml`. Headscale will look for it in `/etc/headscale`, `~/.headscale` and finally the directory from where the Headscale binary is executed.
|
Headscale's configuration file is named `config.json` or `config.yaml`. Headscale will look for it in `/etc/headscale`, `~/.headscale` and finally the directory from where the Headscale binary is executed.
|
||||||
|
@ -131,7 +148,15 @@ To get a certificate automatically via [Let's Encrypt](https://letsencrypt.org/)
|
||||||
|
|
||||||
## Disclaimer
|
## Disclaimer
|
||||||
|
|
||||||
1. I have nothing to do with Tailscale, or Tailscale Inc.
|
1. We have nothing to do with Tailscale, or Tailscale Inc.
|
||||||
2. The purpose of writing this was to learn how Tailscale works.
|
2. The purpose of writing this was to learn how Tailscale works.
|
||||||
3. I don't use Headscale myself.
|
3. ~~I don't use Headscale myself.~~
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## More on Tailscale
|
||||||
|
|
||||||
|
- https://tailscale.com/blog/how-tailscale-works/
|
||||||
|
- https://tailscale.com/blog/tailscale-key-management/
|
||||||
|
- https://tailscale.com/blog/an-unlikely-database-migration/
|
||||||
|
|
||||||
|
|
23
cli.go
23
cli.go
|
@ -1,50 +1,45 @@
|
||||||
package headscale
|
package headscale
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"errors"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"tailscale.com/wgengine/wgcfg"
|
"tailscale.com/wgengine/wgcfg"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RegisterMachine is executed from the CLI to register a new Machine using its MachineKey
|
// RegisterMachine is executed from the CLI to register a new Machine using its MachineKey
|
||||||
func (h *Headscale) RegisterMachine(key string, namespace string) error {
|
func (h *Headscale) RegisterMachine(key string, namespace string) (*Machine, error) {
|
||||||
ns, err := h.GetNamespace(namespace)
|
ns, err := h.GetNamespace(namespace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
mKey, err := wgcfg.ParseHexKey(key)
|
mKey, err := wgcfg.ParseHexKey(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Cannot parse client key: %s", err)
|
return nil, err
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
db, err := h.db()
|
db, err := h.db()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Cannot open DB: %s", err)
|
log.Printf("Cannot open DB: %s", err)
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer db.Close()
|
defer db.Close()
|
||||||
m := Machine{}
|
m := Machine{}
|
||||||
if db.First(&m, "machine_key = ?", mKey.HexString()).RecordNotFound() {
|
if db.First(&m, "machine_key = ?", mKey.HexString()).RecordNotFound() {
|
||||||
log.Printf("Cannot find machine with machine key: %s", mKey.Base64())
|
return nil, errors.New("Machine not found")
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if m.isAlreadyRegistered() {
|
if m.isAlreadyRegistered() {
|
||||||
fmt.Println("This machine already registered")
|
return nil, errors.New("Machine already registered")
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ip, err := h.getAvailableIP()
|
ip, err := h.getAvailableIP()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
return nil, err
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
m.IPAddress = ip.String()
|
m.IPAddress = ip.String()
|
||||||
m.NamespaceID = ns.ID
|
m.NamespaceID = ns.ID
|
||||||
m.Registered = true
|
m.Registered = true
|
||||||
m.RegisterMethod = "cli"
|
m.RegisterMethod = "cli"
|
||||||
db.Save(&m)
|
db.Save(&m)
|
||||||
fmt.Println("Machine registered 🎉")
|
return &m, nil
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package cli
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
@ -22,16 +23,21 @@ var CreateNamespaceCmd = &cobra.Command{
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
o, _ := cmd.Flags().GetString("output")
|
||||||
h, err := getHeadscaleApp()
|
h, err := getHeadscaleApp()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error initializing: %s", err)
|
log.Fatalf("Error initializing: %s", err)
|
||||||
}
|
}
|
||||||
_, err = h.CreateNamespace(args[0])
|
namespace, err := h.CreateNamespace(args[0])
|
||||||
if err != nil {
|
if strings.HasPrefix(o, "json") {
|
||||||
fmt.Println(err)
|
JsonOutput(namespace, err, o)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Printf("Ook.\n")
|
if err != nil {
|
||||||
|
fmt.Printf("Error creating namespace: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("Namespace created\n")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,17 +45,22 @@ var ListNamespacesCmd = &cobra.Command{
|
||||||
Use: "list",
|
Use: "list",
|
||||||
Short: "List all the namespaces",
|
Short: "List all the namespaces",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
o, _ := cmd.Flags().GetString("output")
|
||||||
h, err := getHeadscaleApp()
|
h, err := getHeadscaleApp()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error initializing: %s", err)
|
log.Fatalf("Error initializing: %s", err)
|
||||||
}
|
}
|
||||||
ns, err := h.ListNamespaces()
|
namespaces, err := h.ListNamespaces()
|
||||||
|
if strings.HasPrefix(o, "json") {
|
||||||
|
JsonOutput(namespaces, err, o)
|
||||||
|
return
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Printf("ID\tName\n")
|
fmt.Printf("ID\tName\n")
|
||||||
for _, n := range *ns {
|
for _, n := range *namespaces {
|
||||||
fmt.Printf("%d\t%s\n", n.ID, n.Name)
|
fmt.Printf("%d\t%s\n", n.ID, n.Name)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,6 +3,7 @@ package cli
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
@ -21,17 +22,22 @@ var RegisterCmd = &cobra.Command{
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error getting namespace: %s", err)
|
log.Fatalf("Error getting namespace: %s", err)
|
||||||
}
|
}
|
||||||
|
o, _ := cmd.Flags().GetString("output")
|
||||||
|
|
||||||
h, err := getHeadscaleApp()
|
h, err := getHeadscaleApp()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error initializing: %s", err)
|
log.Fatalf("Error initializing: %s", err)
|
||||||
}
|
}
|
||||||
err = h.RegisterMachine(args[0], n)
|
m, err := h.RegisterMachine(args[0], n)
|
||||||
if err != nil {
|
if strings.HasPrefix(o, "json") {
|
||||||
fmt.Printf("Error: %s", err)
|
JsonOutput(m, err, o)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Println("Ook.")
|
if err != nil {
|
||||||
|
fmt.Printf("Cannot register machine: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("Machine registered\n")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,12 +49,18 @@ var ListNodesCmd = &cobra.Command{
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error getting namespace: %s", err)
|
log.Fatalf("Error getting namespace: %s", err)
|
||||||
}
|
}
|
||||||
|
o, _ := cmd.Flags().GetString("output")
|
||||||
|
|
||||||
h, err := getHeadscaleApp()
|
h, err := getHeadscaleApp()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error initializing: %s", err)
|
log.Fatalf("Error initializing: %s", err)
|
||||||
}
|
}
|
||||||
machines, err := h.ListMachinesInNamespace(n)
|
machines, err := h.ListMachinesInNamespace(n)
|
||||||
|
if strings.HasPrefix(o, "json") {
|
||||||
|
JsonOutput(machines, err, o)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error getting nodes: %s", err)
|
log.Fatalf("Error getting nodes: %s", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package cli
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/hako/durafmt"
|
"github.com/hako/durafmt"
|
||||||
|
@ -22,14 +23,20 @@ var ListPreAuthKeys = &cobra.Command{
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error getting namespace: %s", err)
|
log.Fatalf("Error getting namespace: %s", err)
|
||||||
}
|
}
|
||||||
|
o, _ := cmd.Flags().GetString("output")
|
||||||
|
|
||||||
h, err := getHeadscaleApp()
|
h, err := getHeadscaleApp()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error initializing: %s", err)
|
log.Fatalf("Error initializing: %s", err)
|
||||||
}
|
}
|
||||||
keys, err := h.GetPreAuthKeys(n)
|
keys, err := h.GetPreAuthKeys(n)
|
||||||
|
if strings.HasPrefix(o, "json") {
|
||||||
|
JsonOutput(keys, err, o)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Printf("Error getting the list of keys: %s\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, k := range *keys {
|
for _, k := range *keys {
|
||||||
|
@ -57,6 +64,7 @@ var CreatePreAuthKeyCmd = &cobra.Command{
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error getting namespace: %s", err)
|
log.Fatalf("Error getting namespace: %s", err)
|
||||||
}
|
}
|
||||||
|
o, _ := cmd.Flags().GetString("output")
|
||||||
|
|
||||||
h, err := getHeadscaleApp()
|
h, err := getHeadscaleApp()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -75,11 +83,15 @@ var CreatePreAuthKeyCmd = &cobra.Command{
|
||||||
expiration = &exp
|
expiration = &exp
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = h.CreatePreAuthKey(n, reusable, expiration)
|
k, err := h.CreatePreAuthKey(n, reusable, expiration)
|
||||||
|
if strings.HasPrefix(o, "json") {
|
||||||
|
JsonOutput(k, err, o)
|
||||||
|
return
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Printf("Ook.\n")
|
fmt.Printf("Key: %s\n", k.Key)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package cli
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
@ -26,16 +27,24 @@ var ListRoutesCmd = &cobra.Command{
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error getting namespace: %s", err)
|
log.Fatalf("Error getting namespace: %s", err)
|
||||||
}
|
}
|
||||||
|
o, _ := cmd.Flags().GetString("output")
|
||||||
|
|
||||||
h, err := getHeadscaleApp()
|
h, err := getHeadscaleApp()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error initializing: %s", err)
|
log.Fatalf("Error initializing: %s", err)
|
||||||
}
|
}
|
||||||
routes, err := h.GetNodeRoutes(n, args[0])
|
routes, err := h.GetNodeRoutes(n, args[0])
|
||||||
|
|
||||||
|
if strings.HasPrefix(o, "json") {
|
||||||
|
JsonOutput(routes, err, o)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(routes)
|
fmt.Println(routes)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -54,15 +63,22 @@ var EnableRouteCmd = &cobra.Command{
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error getting namespace: %s", err)
|
log.Fatalf("Error getting namespace: %s", err)
|
||||||
}
|
}
|
||||||
|
o, _ := cmd.Flags().GetString("output")
|
||||||
|
|
||||||
h, err := getHeadscaleApp()
|
h, err := getHeadscaleApp()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error initializing: %s", err)
|
log.Fatalf("Error initializing: %s", err)
|
||||||
}
|
}
|
||||||
err = h.EnableNodeRoute(n, args[0], args[1])
|
route, err := h.EnableNodeRoute(n, args[0], args[1])
|
||||||
|
if strings.HasPrefix(o, "json") {
|
||||||
|
JsonOutput(route, err, o)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
fmt.Printf("Enabled route %s\n", route)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
@ -13,6 +15,10 @@ import (
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type ErrorOutput struct {
|
||||||
|
Error string
|
||||||
|
}
|
||||||
|
|
||||||
func absPath(path string) string {
|
func absPath(path string) string {
|
||||||
// If a relative path is provided, prefix it with the the directory where
|
// If a relative path is provided, prefix it with the the directory where
|
||||||
// the config file was found.
|
// the config file was found.
|
||||||
|
@ -72,3 +78,35 @@ func loadDerpMap(path string) (*tailcfg.DERPMap, error) {
|
||||||
err = yaml.Unmarshal(b, &derpMap)
|
err = yaml.Unmarshal(b, &derpMap)
|
||||||
return &derpMap, err
|
return &derpMap, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func JsonOutput(result interface{}, errResult error, outputFormat string) {
|
||||||
|
var j []byte
|
||||||
|
var err error
|
||||||
|
switch outputFormat {
|
||||||
|
case "json":
|
||||||
|
if errResult != nil {
|
||||||
|
j, err = json.MarshalIndent(ErrorOutput{errResult.Error()}, "", "\t")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
j, err = json.MarshalIndent(result, "", "\t")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "json-line":
|
||||||
|
if errResult != nil {
|
||||||
|
j, err = json.Marshal(ErrorOutput{errResult.Error()})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
j, err = json.Marshal(result)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Println(string(j))
|
||||||
|
}
|
||||||
|
|
|
@ -19,6 +19,11 @@ var versionCmd = &cobra.Command{
|
||||||
Short: "Print the version.",
|
Short: "Print the version.",
|
||||||
Long: "The version of headscale.",
|
Long: "The version of headscale.",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
o, _ := cmd.Flags().GetString("output")
|
||||||
|
if strings.HasPrefix(o, "json") {
|
||||||
|
cli.JsonOutput(map[string]string{"version": version}, nil, o)
|
||||||
|
return
|
||||||
|
}
|
||||||
fmt.Println(version)
|
fmt.Println(version)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -123,6 +128,8 @@ func main() {
|
||||||
cli.CreatePreAuthKeyCmd.PersistentFlags().Bool("reusable", false, "Make the preauthkey reusable")
|
cli.CreatePreAuthKeyCmd.PersistentFlags().Bool("reusable", false, "Make the preauthkey reusable")
|
||||||
cli.CreatePreAuthKeyCmd.Flags().StringP("expiration", "e", "", "Human-readable expiration of the key (30m, 24h, 365d...)")
|
cli.CreatePreAuthKeyCmd.Flags().StringP("expiration", "e", "", "Human-readable expiration of the key (30m, 24h, 365d...)")
|
||||||
|
|
||||||
|
headscaleCmd.PersistentFlags().StringP("output", "o", "", "Output format. Empty for human-readable, 'json' or 'json-line'")
|
||||||
|
|
||||||
if err := headscaleCmd.Execute(); err != nil {
|
if err := headscaleCmd.Execute(); err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(-1)
|
os.Exit(-1)
|
||||||
|
|
18
routes.go
18
routes.go
|
@ -26,18 +26,18 @@ func (h *Headscale) GetNodeRoutes(namespace string, nodeName string) (*[]netaddr
|
||||||
|
|
||||||
// EnableNodeRoute enables a subnet route advertised by a node (identified by
|
// EnableNodeRoute enables a subnet route advertised by a node (identified by
|
||||||
// namespace and node name)
|
// namespace and node name)
|
||||||
func (h *Headscale) EnableNodeRoute(namespace string, nodeName string, routeStr string) error {
|
func (h *Headscale) EnableNodeRoute(namespace string, nodeName string, routeStr string) (*netaddr.IPPrefix, error) {
|
||||||
m, err := h.GetMachine(namespace, nodeName)
|
m, err := h.GetMachine(namespace, nodeName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
hi, err := m.GetHostInfo()
|
hi, err := m.GetHostInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
route, err := netaddr.ParseIPPrefix(routeStr)
|
route, err := netaddr.ParseIPPrefix(routeStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, rIP := range hi.RoutableIPs {
|
for _, rIP := range hi.RoutableIPs {
|
||||||
|
@ -45,7 +45,7 @@ func (h *Headscale) EnableNodeRoute(namespace string, nodeName string, routeStr
|
||||||
db, err := h.db()
|
db, err := h.db()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Cannot open DB: %s", err)
|
log.Printf("Cannot open DB: %s", err)
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
routes, _ := json.Marshal([]string{routeStr}) // TODO: only one for the time being, so overwriting the rest
|
routes, _ := json.Marshal([]string{routeStr}) // TODO: only one for the time being, so overwriting the rest
|
||||||
|
@ -53,6 +53,10 @@ func (h *Headscale) EnableNodeRoute(namespace string, nodeName string, routeStr
|
||||||
db.Save(&m)
|
db.Save(&m)
|
||||||
db.Close()
|
db.Close()
|
||||||
|
|
||||||
|
// THIS IS COMPLETELY USELESS.
|
||||||
|
// The peers map is stored in memory in the server process.
|
||||||
|
// Definetely not accessible from the CLI tool.
|
||||||
|
// We need RPC to the server - or some kind of 'needsUpdate' field in the DB
|
||||||
peers, _ := h.getPeers(*m)
|
peers, _ := h.getPeers(*m)
|
||||||
h.pollMu.Lock()
|
h.pollMu.Lock()
|
||||||
for _, p := range *peers {
|
for _, p := range *peers {
|
||||||
|
@ -61,9 +65,9 @@ func (h *Headscale) EnableNodeRoute(namespace string, nodeName string, routeStr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
h.pollMu.Unlock()
|
h.pollMu.Unlock()
|
||||||
return nil
|
return &rIP, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.New("could not find routable range")
|
return nil, errors.New("could not find routable range")
|
||||||
}
|
}
|
||||||
|
|
6
utils.go
6
utils.go
|
@ -105,8 +105,8 @@ func getRandomIP() (*net.IP, error) {
|
||||||
ipo, ipnet, err := net.ParseCIDR("100.64.0.0/10")
|
ipo, ipnet, err := net.ParseCIDR("100.64.0.0/10")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
ip := ipo.To4()
|
ip := ipo.To4()
|
||||||
fmt.Println("In Randomize IPAddr: IP ", ip, " IPNET: ", ipnet)
|
// fmt.Println("In Randomize IPAddr: IP ", ip, " IPNET: ", ipnet)
|
||||||
fmt.Println("Final address is ", ip)
|
// fmt.Println("Final address is ", ip)
|
||||||
// fmt.Println("Broadcast address is ", ipb)
|
// fmt.Println("Broadcast address is ", ipb)
|
||||||
// fmt.Println("Network address is ", ipn)
|
// fmt.Println("Network address is ", ipn)
|
||||||
r := mathrand.Uint32()
|
r := mathrand.Uint32()
|
||||||
|
@ -119,7 +119,7 @@ func getRandomIP() (*net.IP, error) {
|
||||||
ip[i] = ip[i] + (v &^ ipnet.Mask[i])
|
ip[i] = ip[i] + (v &^ ipnet.Mask[i])
|
||||||
// fmt.Println("IP After: ", ip[i])
|
// fmt.Println("IP After: ", ip[i])
|
||||||
}
|
}
|
||||||
fmt.Println("FINAL IP: ", ip.String())
|
// fmt.Println("FINAL IP: ", ip.String())
|
||||||
return &ip, nil
|
return &ip, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue