Refactor: Rename NanoKVM to BatchuKVM and update server URL
This commit is contained in:
147
server/service/extensions/tailscale/cli.go
Normal file
147
server/service/extensions/tailscale/cli.go
Normal file
@@ -0,0 +1,147 @@
|
||||
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"
|
||||
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"
|
||||
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()
|
||||
}
|
||||
118
server/service/extensions/tailscale/install.go
Normal file
118
server/service/extensions/tailscale/install.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package tailscale
|
||||
|
||||
import (
|
||||
"NanoKVM-Server/utils"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
OriginalURL = "https://pkgs.tailscale.com/stable/tailscale_latest_riscv64.tgz"
|
||||
Workspace = "/root/.tailscale"
|
||||
)
|
||||
|
||||
func isInstalled() bool {
|
||||
_, err1 := os.Stat(TailscalePath)
|
||||
_, err2 := os.Stat(TailscaledPath)
|
||||
|
||||
return err1 == nil && err2 == nil
|
||||
}
|
||||
|
||||
func install() error {
|
||||
_ = os.MkdirAll(Workspace, 0o755)
|
||||
defer func() {
|
||||
_ = os.RemoveAll(Workspace)
|
||||
}()
|
||||
|
||||
tarFile := fmt.Sprintf("%s/tailscale_riscv64.tgz", Workspace)
|
||||
|
||||
// download
|
||||
if err := download(tarFile); err != nil {
|
||||
log.Errorf("failed to download tailscale: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// decompress
|
||||
dir, err := utils.UnTarGz(tarFile, Workspace)
|
||||
if err != nil {
|
||||
log.Errorf("failed to decompress tailscale: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// move
|
||||
tailscalePath := fmt.Sprintf("%s/tailscale", dir)
|
||||
err = utils.MoveFile(tailscalePath, TailscalePath)
|
||||
if err != nil {
|
||||
log.Errorf("failed to move tailscale: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
tailscaledPath := fmt.Sprintf("%s/tailscaled", dir)
|
||||
err = utils.MoveFile(tailscaledPath, TailscaledPath)
|
||||
if err != nil {
|
||||
log.Errorf("failed to move tailscaled: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("install tailscale successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
func download(target string) error {
|
||||
url, err := getDownloadURL()
|
||||
if err != nil {
|
||||
log.Errorf("failed to get Tailscale download url: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
log.Errorf("failed to download Tailscale: %s", err)
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
out, err := os.Create(target)
|
||||
if err != nil {
|
||||
log.Errorf("failed to create file: %s", err)
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = out.Close()
|
||||
}()
|
||||
|
||||
_, err = io.Copy(out, resp.Body)
|
||||
if err != nil {
|
||||
log.Errorf("failed to copy response body to file: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("download Tailscale successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
func getDownloadURL() (string, error) {
|
||||
resp, err := (&http.Client{}).Get(OriginalURL)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusFound {
|
||||
return "", fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return resp.Request.URL.String(), nil
|
||||
}
|
||||
237
server/service/extensions/tailscale/service.go
Normal file
237
server/service/extensions/tailscale/service.go
Normal file
@@ -0,0 +1,237 @@
|
||||
package tailscale
|
||||
|
||||
import (
|
||||
"NanoKVM-Server/proto"
|
||||
"NanoKVM-Server/utils"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type Service struct{}
|
||||
|
||||
const (
|
||||
TailscalePath = "/usr/bin/tailscale"
|
||||
TailscaledPath = "/usr/sbin/tailscaled"
|
||||
|
||||
GoMemLimit int64 = 75
|
||||
)
|
||||
|
||||
var StateMap = map[string]proto.TailscaleState{
|
||||
"NoState": proto.TailscaleNotRunning,
|
||||
"Starting": proto.TailscaleNotRunning,
|
||||
"NeedsLogin": proto.TailscaleNotLogin,
|
||||
"Running": proto.TailscaleRunning,
|
||||
"Stopped": proto.TailscaleStopped,
|
||||
}
|
||||
|
||||
func NewService() *Service {
|
||||
return &Service{}
|
||||
}
|
||||
|
||||
func (s *Service) Install(c *gin.Context) {
|
||||
var rsp proto.Response
|
||||
|
||||
if !isInstalled() {
|
||||
if err := install(); err != nil {
|
||||
rsp.ErrRsp(c, -1, "install failed")
|
||||
return
|
||||
}
|
||||
|
||||
_ = NewCli().Start()
|
||||
}
|
||||
|
||||
rsp.OkRsp(c)
|
||||
log.Debugf("install tailscale successfully")
|
||||
}
|
||||
|
||||
func (s *Service) Uninstall(c *gin.Context) {
|
||||
var rsp proto.Response
|
||||
|
||||
_ = NewCli().Stop()
|
||||
_ = utils.DelGoMemLimit()
|
||||
|
||||
_ = os.Remove(TailscalePath)
|
||||
_ = os.Remove(TailscaledPath)
|
||||
|
||||
rsp.OkRsp(c)
|
||||
log.Debugf("uninstall tailscale successfully")
|
||||
}
|
||||
|
||||
func (s *Service) Start(c *gin.Context) {
|
||||
var rsp proto.Response
|
||||
|
||||
err := NewCli().Start()
|
||||
if err != nil {
|
||||
rsp.ErrRsp(c, -1, "start failed")
|
||||
log.Errorf("failed to run tailscale start: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !utils.IsGoMemLimitExist() {
|
||||
_ = utils.SetGoMemLimit(GoMemLimit)
|
||||
}
|
||||
|
||||
rsp.OkRsp(c)
|
||||
log.Debugf("tailscale start successfully")
|
||||
}
|
||||
|
||||
func (s *Service) Restart(c *gin.Context) {
|
||||
var rsp proto.Response
|
||||
|
||||
err := NewCli().Restart()
|
||||
if err != nil {
|
||||
rsp.ErrRsp(c, -1, "restart failed")
|
||||
log.Errorf("failed to run tailscale restart: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
rsp.OkRsp(c)
|
||||
log.Debugf("tailscale restart successfully")
|
||||
}
|
||||
|
||||
func (s *Service) Stop(c *gin.Context) {
|
||||
var rsp proto.Response
|
||||
|
||||
err := NewCli().Stop()
|
||||
if err != nil {
|
||||
rsp.ErrRsp(c, -1, "stop failed")
|
||||
log.Errorf("failed to run tailscale stop: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
_ = utils.DelGoMemLimit()
|
||||
|
||||
rsp.OkRsp(c)
|
||||
log.Debugf("tailscale stop successfully")
|
||||
}
|
||||
|
||||
func (s *Service) Up(c *gin.Context) {
|
||||
var rsp proto.Response
|
||||
|
||||
err := NewCli().Up()
|
||||
if err != nil {
|
||||
rsp.ErrRsp(c, -1, "tailscale up failed")
|
||||
log.Errorf("failed to run tailscale up: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
rsp.OkRsp(c)
|
||||
log.Debugf("run tailscale up successfully")
|
||||
}
|
||||
|
||||
func (s *Service) Down(c *gin.Context) {
|
||||
var rsp proto.Response
|
||||
|
||||
err := NewCli().Down()
|
||||
if err != nil {
|
||||
rsp.ErrRsp(c, -1, "tailscale down failed")
|
||||
log.Errorf("failed to run tailscale down: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
rsp.OkRsp(c)
|
||||
log.Debugf("run tailscale down successfully")
|
||||
}
|
||||
|
||||
func (s *Service) Login(c *gin.Context) {
|
||||
var rsp proto.Response
|
||||
|
||||
// check tailscale status
|
||||
cli := NewCli()
|
||||
status, err := cli.Status()
|
||||
if err != nil {
|
||||
_ = cli.Start()
|
||||
status, err = cli.Status()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("failed to get tailscale status: %s", err)
|
||||
rsp.ErrRsp(c, -1, "unknown status")
|
||||
return
|
||||
}
|
||||
|
||||
if status.BackendState == "Running" {
|
||||
rsp.OkRspWithData(c, &proto.LoginTailscaleRsp{})
|
||||
return
|
||||
}
|
||||
|
||||
// get login url
|
||||
url, err := cli.Login()
|
||||
if err != nil {
|
||||
log.Errorf("failed to run tailscale login: %s", err)
|
||||
rsp.ErrRsp(c, -2, "login failed")
|
||||
return
|
||||
}
|
||||
|
||||
if !utils.IsGoMemLimitExist() {
|
||||
_ = utils.SetGoMemLimit(GoMemLimit)
|
||||
}
|
||||
|
||||
rsp.OkRspWithData(c, &proto.LoginTailscaleRsp{
|
||||
Url: url,
|
||||
})
|
||||
|
||||
log.Debugf("tailscale login url: %s", url)
|
||||
}
|
||||
|
||||
func (s *Service) Logout(c *gin.Context) {
|
||||
var rsp proto.Response
|
||||
|
||||
err := NewCli().Logout()
|
||||
if err != nil {
|
||||
rsp.ErrRsp(c, -1, "logout failed")
|
||||
log.Errorf("failed to run tailscale logout: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
rsp.OkRsp(c)
|
||||
log.Debugf("tailscale logout successfully")
|
||||
}
|
||||
|
||||
func (s *Service) GetStatus(c *gin.Context) {
|
||||
var rsp proto.Response
|
||||
|
||||
if !isInstalled() {
|
||||
rsp.OkRspWithData(c, &proto.GetTailscaleStatusRsp{
|
||||
State: proto.TailscaleNotInstall,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
status, err := NewCli().Status()
|
||||
if err != nil {
|
||||
log.Debugf("failed to get tailscale status: %s", err)
|
||||
rsp.OkRspWithData(c, &proto.GetTailscaleStatusRsp{
|
||||
State: proto.TailscaleNotRunning,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
state, ok := StateMap[status.BackendState]
|
||||
if !ok {
|
||||
log.Errorf("unknown tailscale state: %s", status.BackendState)
|
||||
rsp.ErrRsp(c, -1, "unknown state")
|
||||
return
|
||||
}
|
||||
|
||||
ipv4 := ""
|
||||
for _, tailscaleIp := range status.Self.TailscaleIPs {
|
||||
ip := net.ParseIP(tailscaleIp)
|
||||
if ip != nil && ip.To4() != nil {
|
||||
ipv4 = ip.String()
|
||||
}
|
||||
}
|
||||
|
||||
data := proto.GetTailscaleStatusRsp{
|
||||
State: state,
|
||||
IP: ipv4,
|
||||
Name: status.Self.HostName,
|
||||
Account: status.CurrentTailnet.Name,
|
||||
}
|
||||
|
||||
rsp.OkRspWithData(c, &data)
|
||||
log.Debugf("get tailscale status successfully")
|
||||
}
|
||||
Reference in New Issue
Block a user