blob: 766ae5258a0f18fe645dd5a7707620fae10f623c [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
27func (c *client) createUser(name string) error {
28 cmd := exec.Command("headscale", c.config, "users", "create", name)
29 out, err := cmd.Output()
giob36178f2024-08-23 18:59:15 +040030 outStr := string(out)
31 if err != nil && strings.Contains(outStr, "User already exists") {
32 return ErrorAlreadyExists
33 }
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +040034 return err
35}
36
37func (c *client) createPreAuthKey(user string) (string, error) {
38 // TODO(giolekva): make expiration configurable, and auto-refresh
39 cmd := exec.Command("headscale", c.config, "--user", user, "preauthkeys", "create", "--reusable", "--expiration", "365d")
40 out, err := cmd.Output()
gio864b4332024-09-05 13:56:47 +040041 fmt.Println(string(out))
Giorgi Lekveishvili027ef432023-06-16 12:31:25 +040042 if err != nil {
43 return "", err
44 }
Giorgi Lekveishvili027ef432023-06-16 12:31:25 +040045 return extractLastLine(string(out))
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +040046}
47
gio864b4332024-09-05 13:56:47 +040048func (c *client) expirePreAuthKey(user, authKey string) error {
49 cmd := exec.Command("headscale", c.config, "--user", user, "preauthkeys", "expire", authKey)
50 out, err := cmd.Output()
51 fmt.Println(string(out))
52 if err != nil {
53 return err
54 }
55 return nil
56}
57
58func (c *client) expireUserNode(user, node string) error {
59 id, err := c.getNodeId(user, node)
60 if err != nil {
61 return err
62 }
63 cmd := exec.Command("headscale", c.config, "node", "expire", "--identifier", id)
64 out, err := cmd.Output()
65 fmt.Println(string(out))
66 if err != nil {
67 return err
68 }
69 return nil
70}
71
72func (c *client) removeUserNode(user, node string) error {
73 id, err := c.getNodeId(user, node)
74 if err != nil {
75 return err
76 }
77 cmd := exec.Command("headscale", c.config, "node", "delete", "--identifier", id, "--force")
78 out, err := cmd.Output()
79 fmt.Println(string(out))
80 if err != nil {
81 return err
82 }
83 return nil
84}
85
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +040086func (c *client) enableRoute(id string) error {
Giorgi Lekveishvili52814d92023-06-15 19:30:32 +040087 cmd := exec.Command("headscale", c.config, "routes", "enable", "-r", id)
88 out, err := cmd.Output()
89 fmt.Println(string(out))
90 return err
91}
Giorgi Lekveishvili027ef432023-06-16 12:31:25 +040092
gio864b4332024-09-05 13:56:47 +040093type nodeInfo struct {
giof6ad2982024-08-23 17:42:49 +040094 Id int `json:"id"`
95 Name string `json:"name"`
96 IPAddresses []net.IP `json:"ip_addresses"`
gio864b4332024-09-05 13:56:47 +040097}
98
gio43e0aad2025-08-01 16:17:27 +040099func (c *client) getUserNodes(user string) ([]nodeInfo, error) {
gio864b4332024-09-05 13:56:47 +0400100 cmd := exec.Command("headscale", c.config, "--user", user, "node", "list", "-o", "json")
101 out, err := cmd.Output()
102 if err != nil {
gio43e0aad2025-08-01 16:17:27 +0400103 return nil, err
gio864b4332024-09-05 13:56:47 +0400104 }
105 var nodes []nodeInfo
106 if err := json.NewDecoder(bytes.NewReader(out)).Decode(&nodes); err != nil {
gio43e0aad2025-08-01 16:17:27 +0400107 return nil, err
108 }
109 return nodes, nil
110}
111
112func (c *client) getNodeId(user, node string) (string, error) {
113 nodes, err := c.getUserNodes(user)
114 if err != nil {
gio864b4332024-09-05 13:56:47 +0400115 return "", err
116 }
117 for _, n := range nodes {
118 if n.Name == node {
119 return strconv.Itoa(n.Id), nil
120 }
121 }
gio92116ca2024-10-06 13:55:46 +0400122 return "", ErrorNotFound
gio864b4332024-09-05 13:56:47 +0400123}
124
giof6ad2982024-08-23 17:42:49 +0400125func (c *client) getNodeAddresses(user, node string) ([]net.IP, error) {
126 cmd := exec.Command("headscale", c.config, "--user", user, "node", "list", "-o", "json")
127 out, err := cmd.Output()
128 if err != nil {
129 return nil, err
130 }
131 var nodes []nodeInfo
132 if err := json.NewDecoder(bytes.NewReader(out)).Decode(&nodes); err != nil {
133 return nil, err
134 }
135 for _, n := range nodes {
136 if n.Name == node {
137 return n.IPAddresses, nil
138 }
139 }
140 return nil, ErrorNotFound
141}
142
Giorgi Lekveishvili027ef432023-06-16 12:31:25 +0400143func extractLastLine(s string) (string, error) {
144 items := strings.Split(s, "\n")
145 for i := len(items) - 1; i >= 0; i-- {
146 t := strings.TrimSpace(items[i])
147 if t != "" {
148 return t, nil
149 }
150 }
151 return "", fmt.Errorf("All lines are empty")
152}