blob: ebeb8b26d985e0e28aefc1480c933f5d9bbde6bd [file] [log] [blame]
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +04001package main
2
3import (
gio864b4332024-09-05 13:56:47 +04004 "bytes"
5 "encoding/json"
giob36178f2024-08-23 18:59:15 +04006 "errors"
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +04007 "fmt"
giof6ad2982024-08-23 17:42:49 +04008 "net"
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +04009 "os/exec"
gio864b4332024-09-05 13:56:47 +040010 "strconv"
Giorgi Lekveishvili027ef432023-06-16 12:31:25 +040011 "strings"
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +040012)
13
giob36178f2024-08-23 18:59:15 +040014var ErrorAlreadyExists = errors.New("already exists")
giof6ad2982024-08-23 17:42:49 +040015var ErrorNotFound = errors.New("not found")
giob36178f2024-08-23 18:59:15 +040016
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +040017type client struct {
18 config string
19}
20
21func newClient(config string) *client {
22 return &client{
23 config: fmt.Sprintf("--config=%s", config),
24 }
25}
26
gio6439d442025-08-03 06:18:15 +040027func (c *client) run(cc ...string) (string, error) {
28 // TODO(giolekva): make expiration configurable, and auto-refresh
29 cc = append(cc, c.config)
30 cmd := exec.Command("headscale", cc...)
31 out, err := cmd.Output()
32 if err != nil {
33 return "", err
34 }
35 return string(out), nil
36
37}
38
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +040039func (c *client) createUser(name string) error {
40 cmd := exec.Command("headscale", c.config, "users", "create", name)
41 out, err := cmd.Output()
giob36178f2024-08-23 18:59:15 +040042 outStr := string(out)
43 if err != nil && strings.Contains(outStr, "User already exists") {
44 return ErrorAlreadyExists
45 }
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +040046 return err
47}
48
49func (c *client) createPreAuthKey(user string) (string, error) {
50 // TODO(giolekva): make expiration configurable, and auto-refresh
51 cmd := exec.Command("headscale", c.config, "--user", user, "preauthkeys", "create", "--reusable", "--expiration", "365d")
52 out, err := cmd.Output()
gio864b4332024-09-05 13:56:47 +040053 fmt.Println(string(out))
Giorgi Lekveishvili027ef432023-06-16 12:31:25 +040054 if err != nil {
55 return "", err
56 }
Giorgi Lekveishvili027ef432023-06-16 12:31:25 +040057 return extractLastLine(string(out))
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +040058}
59
gio864b4332024-09-05 13:56:47 +040060func (c *client) expirePreAuthKey(user, authKey string) error {
61 cmd := exec.Command("headscale", c.config, "--user", user, "preauthkeys", "expire", authKey)
62 out, err := cmd.Output()
63 fmt.Println(string(out))
64 if err != nil {
65 return err
66 }
67 return nil
68}
69
70func (c *client) expireUserNode(user, node string) error {
71 id, err := c.getNodeId(user, node)
72 if err != nil {
73 return err
74 }
75 cmd := exec.Command("headscale", c.config, "node", "expire", "--identifier", id)
76 out, err := cmd.Output()
77 fmt.Println(string(out))
78 if err != nil {
79 return err
80 }
81 return nil
82}
83
84func (c *client) removeUserNode(user, node string) error {
85 id, err := c.getNodeId(user, node)
86 if err != nil {
87 return err
88 }
89 cmd := exec.Command("headscale", c.config, "node", "delete", "--identifier", id, "--force")
90 out, err := cmd.Output()
91 fmt.Println(string(out))
92 if err != nil {
93 return err
94 }
95 return nil
96}
97
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +040098func (c *client) enableRoute(id string) error {
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +040099 cmd := exec.Command("headscale", c.config, "routes", "enable", "-r", id)
100 out, err := cmd.Output()
101 fmt.Println(string(out))
102 return err
103}
Giorgi Lekveishvili027ef432023-06-16 12:31:25 +0400104
gio6439d442025-08-03 06:18:15 +0400105type timeInfo struct {
106 Seconds int `json:"seconds"`
107 Nanos int `json:"nanos"`
108}
109
gio864b4332024-09-05 13:56:47 +0400110type nodeInfo struct {
giof6ad2982024-08-23 17:42:49 +0400111 Id int `json:"id"`
112 Name string `json:"name"`
gio6439d442025-08-03 06:18:15 +0400113 GivenName string `json:"given_name"`
giof6ad2982024-08-23 17:42:49 +0400114 IPAddresses []net.IP `json:"ip_addresses"`
gio6439d442025-08-03 06:18:15 +0400115 LastSeen timeInfo `json:"last_seen"`
gio864b4332024-09-05 13:56:47 +0400116}
117
gio43e0aad2025-08-01 16:17:27 +0400118func (c *client) getUserNodes(user string) ([]nodeInfo, error) {
gio864b4332024-09-05 13:56:47 +0400119 cmd := exec.Command("headscale", c.config, "--user", user, "node", "list", "-o", "json")
120 out, err := cmd.Output()
121 if err != nil {
gio43e0aad2025-08-01 16:17:27 +0400122 return nil, err
gio864b4332024-09-05 13:56:47 +0400123 }
124 var nodes []nodeInfo
125 if err := json.NewDecoder(bytes.NewReader(out)).Decode(&nodes); err != nil {
gio43e0aad2025-08-01 16:17:27 +0400126 return nil, err
127 }
128 return nodes, nil
129}
130
131func (c *client) getNodeId(user, node string) (string, error) {
132 nodes, err := c.getUserNodes(user)
133 if err != nil {
gio864b4332024-09-05 13:56:47 +0400134 return "", err
135 }
136 for _, n := range nodes {
137 if n.Name == node {
138 return strconv.Itoa(n.Id), nil
139 }
140 }
gio92116ca2024-10-06 13:55:46 +0400141 return "", ErrorNotFound
gio864b4332024-09-05 13:56:47 +0400142}
143
giof6ad2982024-08-23 17:42:49 +0400144func (c *client) getNodeAddresses(user, node string) ([]net.IP, error) {
145 cmd := exec.Command("headscale", c.config, "--user", user, "node", "list", "-o", "json")
146 out, err := cmd.Output()
147 if err != nil {
148 return nil, err
149 }
150 var nodes []nodeInfo
151 if err := json.NewDecoder(bytes.NewReader(out)).Decode(&nodes); err != nil {
152 return nil, err
153 }
154 for _, n := range nodes {
155 if n.Name == node {
156 return n.IPAddresses, nil
157 }
158 }
159 return nil, ErrorNotFound
160}
161
Giorgi Lekveishvili027ef432023-06-16 12:31:25 +0400162func extractLastLine(s string) (string, error) {
163 items := strings.Split(s, "\n")
164 for i := len(items) - 1; i >= 0; i-- {
165 t := strings.TrimSpace(items[i])
166 if t != "" {
167 return t, nil
168 }
169 }
170 return "", fmt.Errorf("All lines are empty")
171}