Files
2025-12-10 22:50:35 +09:00

148 lines
3.0 KiB
Go

package tailscale
import (
"NanoKVM-Server/utils"
"bufio"
"encoding/json"
"errors"
"fmt"
"os"
"os/exec"
"regexp"
"strings"
)
const (
ScriptPath = "/etc/init.d/S98tailscaled"
ScriptBackupPath = "/kvmapp/system/init.d/S98tailscaled"
)
type Cli struct{}
type TsStatus struct {
BackendState string `json:"BackendState"`
Self struct {
HostName string `json:"HostName"`
TailscaleIPs []string `json:"TailscaleIPs"`
} `json:"Self"`
CurrentTailnet struct {
Name string `json:"Name"`
} `json:"CurrentTailnet"`
}
func NewCli() *Cli {
return &Cli{}
}
func (c *Cli) Start() error {
for _, filePath := range []string{TailscalePath, TailscaledPath} {
if err := utils.EnsurePermission(filePath, 0o100); err != nil {
return err
}
}
commands := []string{
fmt.Sprintf("cp -f %s %s", ScriptBackupPath, ScriptPath),
fmt.Sprintf("%s start", ScriptPath),
}
command := strings.Join(commands, " && ")
return exec.Command("sh", "-c", command).Run()
}
func (c *Cli) Restart() error {
commands := []string{
fmt.Sprintf("cp -f %s %s", ScriptBackupPath, ScriptPath),
fmt.Sprintf("%s restart", ScriptPath),
}
command := strings.Join(commands, " && ")
return exec.Command("sh", "-c", command).Run()
}
func (c *Cli) Stop() error {
command := fmt.Sprintf("%s stop", ScriptPath)
err := exec.Command("sh", "-c", command).Run()
if err != nil {
return err
}
return os.Remove(ScriptPath)
}
func (c *Cli) Up() error {
command := "tailscale up --accept-dns=false --login-server=https://headscale.tindevil.com --hostname=batchuKVM"
return exec.Command("sh", "-c", command).Run()
}
func (c *Cli) Down() error {
command := "tailscale down"
return exec.Command("sh", "-c", command).Run()
}
func (c *Cli) Status() (*TsStatus, error) {
command := "tailscale status --json"
cmd := exec.Command("sh", "-c", command)
output, err := cmd.CombinedOutput()
if err != nil {
return nil, err
}
// output is not in standard json format
if outputStr := string(output); !strings.HasPrefix(outputStr, "{") {
index := strings.Index(outputStr, "{")
if index == -1 {
return nil, errors.New("unknown output")
}
output = []byte(outputStr[index:])
}
var status TsStatus
err = json.Unmarshal(output, &status)
if err != nil {
return nil, err
}
return &status, nil
}
func (c *Cli) Login() (string, error) {
command := "tailscale login --accept-dns=false --timeout=10m --login-server=https://headscale.tindevil.com --hostname=batchuKVM"
cmd := exec.Command("sh", "-c", command)
stderr, err := cmd.StderrPipe()
if err != nil {
return "", err
}
defer func() {
_ = stderr.Close()
}()
go func() {
_ = cmd.Run()
}()
reader := bufio.NewReader(stderr)
for {
line, err := reader.ReadString('\n')
if err != nil {
return "", err
}
if strings.Contains(line, "https") {
reg := regexp.MustCompile(`\s+`)
url := reg.ReplaceAllString(line, "")
return url, nil
}
}
}
func (c *Cli) Logout() error {
command := "tailscale logout"
return exec.Command("sh", "-c", command).Run()
}