mirror of
https://github.com/luckfox-eng29/kvm.git
synced 2026-01-17 19:22:15 +01:00
833 lines
20 KiB
Go
833 lines
20 KiB
Go
package kvm
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"syscall"
|
|
"time"
|
|
)
|
|
|
|
type TailScaleSettings struct {
|
|
State string `json:"state"`
|
|
LoginUrl string `json:"loginUrl"`
|
|
IP string `json:"ip"`
|
|
XEdge bool `json:"xEdge"`
|
|
}
|
|
|
|
func rpcCancelTailScale() error {
|
|
_, err := CallVpnCtrlAction("cancel_tailscale", map[string]interface{}{"type": "no_param"})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func rpcLoginTailScale(xEdge bool) (TailScaleSettings, error) {
|
|
settings := TailScaleSettings{
|
|
State: "connecting",
|
|
XEdge: xEdge,
|
|
LoginUrl: "",
|
|
IP: "",
|
|
}
|
|
|
|
_, err := CallVpnCtrlAction("login_tailscale", map[string]interface{}{"xEdge": xEdge})
|
|
if err != nil {
|
|
return settings, err
|
|
}
|
|
|
|
for i := 0; i < 15; i++ {
|
|
time.Sleep(2 * time.Second)
|
|
|
|
resp, err := CallVpnCtrlAction("get_tailscale_state", map[string]interface{}{"type": "no_param"})
|
|
if err != nil {
|
|
return settings, err
|
|
}
|
|
if resp.Event == "tailscale_state" {
|
|
if _, ok := resp.Result["state"]; ok {
|
|
settings.State = resp.Result["state"].(string)
|
|
}
|
|
if _, ok := resp.Result["ip"]; ok {
|
|
settings.IP = resp.Result["ip"].(string)
|
|
}
|
|
if _, ok := resp.Result["loginUrl"]; ok {
|
|
settings.LoginUrl = resp.Result["loginUrl"].(string)
|
|
}
|
|
if _, ok := resp.Result["xEdge"]; ok {
|
|
settings.XEdge = resp.Result["xEdge"].(bool)
|
|
}
|
|
}
|
|
|
|
switch settings.State {
|
|
case "logined":
|
|
config.TailScaleAutoStart = true
|
|
config.TailScaleXEdge = settings.XEdge
|
|
err := SaveConfig()
|
|
if err != nil {
|
|
vpnLogger.Error().Err(err).Msg("failed to save config")
|
|
}
|
|
return settings, err
|
|
case "connected":
|
|
config.TailScaleAutoStart = true
|
|
config.TailScaleXEdge = settings.XEdge
|
|
err = SaveConfig()
|
|
if err != nil {
|
|
vpnLogger.Error().Err(err).Msg("failed to save config")
|
|
}
|
|
return settings, err
|
|
case "connecting":
|
|
if i >= 10 {
|
|
settings.State = "disconnected"
|
|
} else {
|
|
settings.State = "connecting"
|
|
}
|
|
case "cancel":
|
|
err := rpcLogoutTailScale()
|
|
if err != nil {
|
|
vpnLogger.Error().Err(err).Msg("failed to logout tailscale")
|
|
}
|
|
settings.State = "disconnected"
|
|
return settings, nil
|
|
default:
|
|
settings.State = "disconnected"
|
|
}
|
|
}
|
|
|
|
return settings, nil
|
|
}
|
|
|
|
func rpcLogoutTailScale() error {
|
|
_, err := CallVpnCtrlAction("logout_tailscale", map[string]interface{}{"type": "no_param"})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
config.TailScaleAutoStart = false
|
|
|
|
if err := SaveConfig(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func rpcGetTailScaleSettings() (TailScaleSettings, error) {
|
|
settings := TailScaleSettings{}
|
|
|
|
resp, err := CallVpnCtrlAction("get_tailscale_state", map[string]interface{}{"type": "no_param"})
|
|
if err != nil {
|
|
return settings, err
|
|
}
|
|
if resp.Event == "tailscale_state" {
|
|
if _, ok := resp.Result["state"]; ok {
|
|
settings.State = resp.Result["state"].(string)
|
|
}
|
|
if _, ok := resp.Result["ip"]; ok {
|
|
settings.IP = resp.Result["ip"].(string)
|
|
}
|
|
if _, ok := resp.Result["loginUrl"]; ok {
|
|
settings.LoginUrl = resp.Result["loginUrl"].(string)
|
|
}
|
|
if _, ok := resp.Result["xEdge"]; ok {
|
|
settings.XEdge = resp.Result["xEdge"].(bool)
|
|
}
|
|
}
|
|
|
|
return settings, nil
|
|
}
|
|
|
|
type ZeroTierSettings struct {
|
|
State string `json:"state"`
|
|
NetworkID string `json:"networkID"`
|
|
IP string `json:"ip"`
|
|
}
|
|
|
|
func rpcLoginZeroTier(networkID string) (ZeroTierSettings, error) {
|
|
LoadConfig()
|
|
settings := ZeroTierSettings{
|
|
State: "connecting",
|
|
NetworkID: networkID,
|
|
IP: "",
|
|
}
|
|
|
|
resp, err := CallVpnCtrlAction("login_zerotier", map[string]interface{}{
|
|
"network_id": networkID,
|
|
"config_network_id": config.ZeroTierNetworkID,
|
|
})
|
|
if err != nil {
|
|
return ZeroTierSettings{}, err
|
|
}
|
|
if resp.Event == "zerotier_state" {
|
|
if _, ok := resp.Result["state"]; ok {
|
|
settings.State = resp.Result["state"].(string)
|
|
}
|
|
if _, ok := resp.Result["network_id"]; ok {
|
|
settings.NetworkID = resp.Result["network_id"].(string)
|
|
}
|
|
if _, ok := resp.Result["ip"]; ok {
|
|
settings.IP = resp.Result["ip"].(string)
|
|
}
|
|
}
|
|
|
|
switch settings.State {
|
|
case "closed":
|
|
config.ZeroTierAutoStart = false
|
|
config.ZeroTierNetworkID = ""
|
|
if err := SaveConfig(); err != nil {
|
|
vpnLogger.Error().Err(err).Msg("failed to save config")
|
|
}
|
|
case "connected", "logined":
|
|
config.ZeroTierAutoStart = true
|
|
config.ZeroTierNetworkID = settings.NetworkID
|
|
if err := SaveConfig(); err != nil {
|
|
vpnLogger.Error().Err(err).Msg("failed to save config")
|
|
}
|
|
}
|
|
/* disconnected - does not handle */
|
|
|
|
return settings, nil
|
|
}
|
|
|
|
func rpcLogoutZeroTier(networkID string) error {
|
|
_, err := CallVpnCtrlAction("logout_zerotier", map[string]interface{}{
|
|
"network_id": networkID,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
config.ZeroTierAutoStart = false
|
|
config.ZeroTierNetworkID = ""
|
|
if err := SaveConfig(); err != nil {
|
|
return fmt.Errorf("failed to save config: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func rpcGetZeroTierSettings() (ZeroTierSettings, error) {
|
|
LoadConfig()
|
|
configNetworkID := fmt.Sprintf("%v", config.ZeroTierNetworkID)
|
|
settings := ZeroTierSettings{
|
|
State: "disconnected",
|
|
NetworkID: configNetworkID,
|
|
IP: "",
|
|
}
|
|
|
|
resp, err := CallVpnCtrlAction("get_zerotier_state", map[string]interface{}{
|
|
"network_id": configNetworkID,
|
|
})
|
|
if err != nil {
|
|
return settings, err
|
|
}
|
|
if resp.Event == "zerotier_state" {
|
|
if _, ok := resp.Result["state"]; ok {
|
|
settings.State = resp.Result["state"].(string)
|
|
}
|
|
if _, ok := resp.Result["network_id"]; ok {
|
|
settings.NetworkID = resp.Result["network_id"].(string)
|
|
}
|
|
if _, ok := resp.Result["ip"]; ok {
|
|
settings.IP = resp.Result["ip"].(string)
|
|
}
|
|
}
|
|
|
|
return settings, nil
|
|
}
|
|
|
|
type VpnUpdateDisplayState struct {
|
|
TailScaleState string `json:"tailscale_state"`
|
|
ZeroTierState string `json:"zerotier_state"`
|
|
Error string `json:"error,omitempty"` //no_signal, no_lock, out_of_range
|
|
}
|
|
|
|
func HandleVpnDisplayUpdateMessage(event CtrlResponse) {
|
|
waitDisplayUpdate.Lock()
|
|
defer waitDisplayUpdate.Unlock()
|
|
waitDisplayCtrlClientConnected()
|
|
|
|
vpnUpdateDisplayState := VpnUpdateDisplayState{}
|
|
err := json.Unmarshal(event.Data, &vpnUpdateDisplayState)
|
|
if err != nil {
|
|
vpnLogger.Warn().Err(err).Msg("Error parsing vpn state json")
|
|
return
|
|
}
|
|
|
|
switch vpnUpdateDisplayState.TailScaleState {
|
|
case "connected":
|
|
updateLabelIfChanged("Network_TailScale_Label", "Connected")
|
|
case "logined":
|
|
updateLabelIfChanged("Network_TailScale_Label", "Logined")
|
|
default:
|
|
updateLabelIfChanged("Network_TailScale_Label", "Disconnected")
|
|
}
|
|
|
|
switch vpnUpdateDisplayState.ZeroTierState {
|
|
case "connected":
|
|
updateLabelIfChanged("Network_ZeroTier_Label", "Connected")
|
|
case "logined":
|
|
updateLabelIfChanged("Network_ZeroTier_Label", "Logined")
|
|
default:
|
|
updateLabelIfChanged("Network_ZeroTier_Label", "Disconnected")
|
|
}
|
|
}
|
|
|
|
type FrpcStatus struct {
|
|
Running bool `json:"running"`
|
|
}
|
|
|
|
var (
|
|
frpcTomlPath = "/userdata/frpc/frpc.toml"
|
|
frpcLogPath = "/tmp/frpc.log"
|
|
)
|
|
|
|
func frpcRunning() bool {
|
|
cmd := exec.Command("pgrep", "-x", "frpc")
|
|
return cmd.Run() == nil
|
|
}
|
|
|
|
func rpcGetFrpcLog() (string, error) {
|
|
f, err := os.Open(frpcLogPath)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return "", fmt.Errorf("frpc log file not exist")
|
|
}
|
|
return "", err
|
|
}
|
|
defer f.Close()
|
|
|
|
const want = 30
|
|
lines := make([]string, 0, want+10)
|
|
sc := bufio.NewScanner(f)
|
|
for sc.Scan() {
|
|
lines = append(lines, sc.Text())
|
|
if len(lines) > want {
|
|
lines = lines[1:]
|
|
}
|
|
}
|
|
if err := sc.Err(); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
var buf []byte
|
|
for _, l := range lines {
|
|
buf = append(buf, l...)
|
|
buf = append(buf, '\n')
|
|
}
|
|
return string(buf), nil
|
|
}
|
|
|
|
func rpcGetFrpcToml() (string, error) {
|
|
return config.FrpcToml, nil
|
|
}
|
|
|
|
func rpcStartFrpc(frpcToml string) error {
|
|
if frpcRunning() {
|
|
_ = exec.Command("pkill", "-x", "frpc").Run()
|
|
}
|
|
|
|
if frpcToml != "" {
|
|
_ = os.MkdirAll(filepath.Dir(frpcTomlPath), 0700)
|
|
if err := os.WriteFile(frpcTomlPath, []byte(frpcToml), 0600); err != nil {
|
|
return err
|
|
}
|
|
cmd := exec.Command("frpc", "-c", frpcTomlPath)
|
|
cmd.Stdout = nil
|
|
cmd.Stderr = nil
|
|
logFile, err := os.OpenFile(frpcLogPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer logFile.Close()
|
|
cmd.Stdout = logFile
|
|
cmd.Stderr = logFile
|
|
|
|
cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
|
|
|
|
if err := cmd.Start(); err != nil {
|
|
return fmt.Errorf("start frpc failed: %w", err)
|
|
} else {
|
|
config.FrpcAutoStart = true
|
|
config.FrpcToml = frpcToml
|
|
if err := SaveConfig(); err != nil {
|
|
return fmt.Errorf("failed to save config: %w", err)
|
|
}
|
|
}
|
|
} else {
|
|
return fmt.Errorf("frpcToml is empty")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func rpcStopFrpc() error {
|
|
if frpcRunning() {
|
|
err := exec.Command("pkill", "-x", "frpc").Run()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to stop frpc: %w", err)
|
|
}
|
|
}
|
|
|
|
config.FrpcAutoStart = false
|
|
err := SaveConfig()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to save config: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func rpcGetFrpcStatus() (FrpcStatus, error) {
|
|
return FrpcStatus{Running: frpcRunning()}, nil
|
|
}
|
|
|
|
type CloudflaredStatus struct {
|
|
Running bool `json:"running"`
|
|
}
|
|
|
|
func cloudflaredRunning() bool {
|
|
cmd := exec.Command("pgrep", "-x", "cloudflared")
|
|
return cmd.Run() == nil
|
|
}
|
|
|
|
var (
|
|
cloudflaredLogPath = "/tmp/cloudflared.log"
|
|
)
|
|
|
|
func rpcStartCloudflared(token string) error {
|
|
if cloudflaredRunning() {
|
|
_ = exec.Command("pkill", "-x", "cloudflared").Run()
|
|
}
|
|
if token == "" {
|
|
return fmt.Errorf("cloudflared token is empty")
|
|
}
|
|
cmd := exec.Command("cloudflared", "tunnel", "run", "--token", token)
|
|
logFile, err := os.OpenFile(cloudflaredLogPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer logFile.Close()
|
|
cmd.Stdout = logFile
|
|
cmd.Stderr = logFile
|
|
cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
|
|
if err := cmd.Start(); err != nil {
|
|
return fmt.Errorf("start cloudflared failed: %w", err)
|
|
}
|
|
config.CloudflaredAutoStart = true
|
|
config.CloudflaredToken = token
|
|
if err := SaveConfig(); err != nil {
|
|
return fmt.Errorf("failed to save config: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func rpcStopCloudflared() error {
|
|
if cloudflaredRunning() {
|
|
err := exec.Command("pkill", "-x", "cloudflared").Run()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to stop cloudflared: %w", err)
|
|
}
|
|
}
|
|
config.CloudflaredAutoStart = false
|
|
if err := SaveConfig(); err != nil {
|
|
return fmt.Errorf("failed to save config: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func rpcGetCloudflaredStatus() (CloudflaredStatus, error) {
|
|
return CloudflaredStatus{Running: cloudflaredRunning()}, nil
|
|
}
|
|
|
|
func rpcGetCloudflaredLog() (string, error) {
|
|
f, err := os.Open(cloudflaredLogPath)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return "", fmt.Errorf("cloudflared log file not exist")
|
|
}
|
|
return "", err
|
|
}
|
|
defer f.Close()
|
|
|
|
const want = 30
|
|
lines := make([]string, 0, want+10)
|
|
sc := bufio.NewScanner(f)
|
|
for sc.Scan() {
|
|
lines = append(lines, sc.Text())
|
|
if len(lines) > want {
|
|
lines = lines[1:]
|
|
}
|
|
}
|
|
if err := sc.Err(); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
var buf []byte
|
|
for _, l := range lines {
|
|
buf = append(buf, l...)
|
|
buf = append(buf, '\n')
|
|
}
|
|
return string(buf), nil
|
|
}
|
|
|
|
type EasytierStatus struct {
|
|
Running bool `json:"running"`
|
|
}
|
|
|
|
type EasytierConfig struct {
|
|
Name string `json:"name"`
|
|
Secret string `json:"secret"`
|
|
Node string `json:"node"`
|
|
}
|
|
|
|
var (
|
|
easytierLogPath = "/tmp/easytier.log"
|
|
)
|
|
|
|
func easytierRunning() bool {
|
|
cmd := exec.Command("pgrep", "-x", "easytier-core")
|
|
return cmd.Run() == nil
|
|
}
|
|
|
|
func rpcGetEasyTierLog() (string, error) {
|
|
f, err := os.Open(easytierLogPath)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return "", fmt.Errorf("easytier log file not exist")
|
|
}
|
|
return "", err
|
|
}
|
|
defer f.Close()
|
|
|
|
const want = 30
|
|
lines := make([]string, 0, want+10)
|
|
sc := bufio.NewScanner(f)
|
|
for sc.Scan() {
|
|
lines = append(lines, sc.Text())
|
|
if len(lines) > want {
|
|
lines = lines[1:]
|
|
}
|
|
}
|
|
if err := sc.Err(); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
var buf []byte
|
|
for _, l := range lines {
|
|
buf = append(buf, l...)
|
|
buf = append(buf, '\n')
|
|
}
|
|
return string(buf), nil
|
|
}
|
|
|
|
func rpcGetEasyTierNodeInfo() (string, error) {
|
|
cmd := exec.Command("easytier-cli", "node")
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to get easytier node info: %w", err)
|
|
}
|
|
|
|
return string(output), nil
|
|
}
|
|
|
|
func rpcGetEasyTierConfig() (EasytierConfig, error) {
|
|
return config.EasytierConfig, nil
|
|
}
|
|
|
|
func rpcStartEasyTier(name, secret, node string) error {
|
|
if easytierRunning() {
|
|
_ = exec.Command("pkill", "-x", "easytier-core").Run()
|
|
}
|
|
|
|
if name == "" || secret == "" || node == "" {
|
|
return fmt.Errorf("easytier config is invalid")
|
|
}
|
|
|
|
cmd := exec.Command("easytier-core", "-d", "--network-name", name, "--network-secret", secret, "-p", node)
|
|
cmd.Stdout = nil
|
|
cmd.Stderr = nil
|
|
logFile, err := os.OpenFile(easytierLogPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open easytier log file: %w", err)
|
|
}
|
|
defer logFile.Close()
|
|
cmd.Stdout = logFile
|
|
cmd.Stderr = logFile
|
|
|
|
cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
|
|
|
|
if err := cmd.Start(); err != nil {
|
|
return fmt.Errorf("start easytier failed: %w", err)
|
|
} else {
|
|
config.EasytierAutoStart = true
|
|
config.EasytierConfig = EasytierConfig{
|
|
Name: name,
|
|
Secret: secret,
|
|
Node: node,
|
|
}
|
|
if err := SaveConfig(); err != nil {
|
|
return fmt.Errorf("failed to save config: %w", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func rpcStopEasyTier() error {
|
|
if easytierRunning() {
|
|
err := exec.Command("pkill", "-x", "easytier-core").Run()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to stop easytier: %w", err)
|
|
}
|
|
}
|
|
|
|
config.EasytierAutoStart = false
|
|
err := SaveConfig()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to save config: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func rpcGetEasyTierStatus() (EasytierStatus, error) {
|
|
return EasytierStatus{Running: easytierRunning()}, nil
|
|
}
|
|
|
|
type VntStatus struct {
|
|
Running bool `json:"running"`
|
|
}
|
|
|
|
var (
|
|
vntLogPath = "/tmp/vnt.log"
|
|
vntConfigFilePath = "/userdata/vnt/vnt.ini"
|
|
)
|
|
|
|
func vntRunning() bool {
|
|
cmd := exec.Command("pgrep", "-x", "vnt-cli")
|
|
return cmd.Run() == nil
|
|
}
|
|
|
|
func rpcGetVntLog() (string, error) {
|
|
f, err := os.Open(vntLogPath)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return "", fmt.Errorf("vnt log file not exist")
|
|
}
|
|
return "", err
|
|
}
|
|
defer f.Close()
|
|
|
|
const want = 30
|
|
lines := make([]string, 0, want+10)
|
|
sc := bufio.NewScanner(f)
|
|
for sc.Scan() {
|
|
lines = append(lines, sc.Text())
|
|
if len(lines) > want {
|
|
lines = lines[1:]
|
|
}
|
|
}
|
|
if err := sc.Err(); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
var buf []byte
|
|
for _, l := range lines {
|
|
buf = append(buf, l...)
|
|
buf = append(buf, '\n')
|
|
}
|
|
return string(buf), nil
|
|
}
|
|
|
|
func rpcGetVntInfo() (string, error) {
|
|
cmd := exec.Command("vnt-cli", "--info")
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to get vnt info: %w", err)
|
|
}
|
|
|
|
return string(output), nil
|
|
}
|
|
|
|
func rpcGetVntConfig() (VntConfig, error) {
|
|
return config.VntConfig, nil
|
|
}
|
|
|
|
func rpcGetVntConfigFile() (string, error) {
|
|
return config.VntConfig.ConfigFile, nil
|
|
}
|
|
|
|
func rpcStartVnt(configMode, token, deviceId, name, serverAddr, configFile string, model string, password string) error {
|
|
if vntRunning() {
|
|
_ = exec.Command("pkill", "-x", "vnt-cli").Run()
|
|
}
|
|
|
|
var args []string
|
|
|
|
if configMode == "file" {
|
|
// Use config file mode
|
|
if configFile == "" {
|
|
return fmt.Errorf("vnt config file is required in file mode")
|
|
}
|
|
|
|
// Save config file
|
|
_ = os.MkdirAll(filepath.Dir(vntConfigFilePath), 0700)
|
|
if err := os.WriteFile(vntConfigFilePath, []byte(configFile), 0600); err != nil {
|
|
return fmt.Errorf("failed to write vnt config file: %w", err)
|
|
}
|
|
|
|
args = []string{"-f", vntConfigFilePath}
|
|
} else {
|
|
// Use params mode (default)
|
|
if token == "" {
|
|
return fmt.Errorf("vnt token is required in params mode")
|
|
}
|
|
|
|
args = []string{"-k", token}
|
|
|
|
if deviceId != "" {
|
|
args = append(args, "-d", deviceId)
|
|
}
|
|
|
|
if name != "" {
|
|
args = append(args, "-n", name)
|
|
}
|
|
|
|
if serverAddr != "" {
|
|
args = append(args, "-s", serverAddr)
|
|
}
|
|
|
|
// Encryption model and password
|
|
if model != "" {
|
|
args = append(args, "--model", model)
|
|
}
|
|
if password != "" {
|
|
args = append(args, "-w", password)
|
|
}
|
|
|
|
args = append(args, "--compressor", "lz4")
|
|
}
|
|
|
|
cmd := exec.Command("vnt-cli", args...)
|
|
cmd.Stdout = nil
|
|
cmd.Stderr = nil
|
|
logFile, err := os.OpenFile(vntLogPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open vnt log file: %w", err)
|
|
}
|
|
defer logFile.Close()
|
|
cmd.Stdout = logFile
|
|
cmd.Stderr = logFile
|
|
|
|
cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
|
|
|
|
if err := cmd.Start(); err != nil {
|
|
return fmt.Errorf("start vnt failed: %w", err)
|
|
} else {
|
|
config.VntAutoStart = true
|
|
config.VntConfig = VntConfig{
|
|
ConfigMode: configMode,
|
|
Token: token,
|
|
DeviceId: deviceId,
|
|
Name: name,
|
|
ServerAddr: serverAddr,
|
|
ConfigFile: configFile,
|
|
Model: model,
|
|
Password: password,
|
|
}
|
|
if err := SaveConfig(); err != nil {
|
|
return fmt.Errorf("failed to save config: %w", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func rpcStopVnt() error {
|
|
if vntRunning() {
|
|
err := exec.Command("pkill", "-x", "vnt-cli").Run()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to stop vnt: %w", err)
|
|
}
|
|
}
|
|
|
|
config.VntAutoStart = false
|
|
err := SaveConfig()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to save config: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func rpcGetVntStatus() (VntStatus, error) {
|
|
return VntStatus{Running: vntRunning()}, nil
|
|
}
|
|
|
|
func initVPN() {
|
|
waitVpnCtrlClientConnected()
|
|
go func() {
|
|
for {
|
|
if !networkState.IsOnline() {
|
|
vpnLogger.Warn().Msg("waiting for network to be online, will retry in 3 seconds")
|
|
time.Sleep(3 * time.Second)
|
|
continue
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
|
|
if config.TailScaleAutoStart {
|
|
if _, err := rpcLoginTailScale(config.TailScaleXEdge); err != nil {
|
|
vpnLogger.Error().Err(err).Msg("Failed to auto start TailScale")
|
|
}
|
|
}
|
|
|
|
if config.ZeroTierAutoStart && config.ZeroTierNetworkID != "" {
|
|
if _, err := rpcLoginZeroTier(config.ZeroTierNetworkID); err != nil {
|
|
vpnLogger.Error().Err(err).Msg("Failed to auto start ZeroTier")
|
|
}
|
|
}
|
|
|
|
if config.FrpcAutoStart && config.FrpcToml != "" {
|
|
if err := rpcStartFrpc(config.FrpcToml); err != nil {
|
|
vpnLogger.Error().Err(err).Msg("Failed to auto start frpc")
|
|
}
|
|
}
|
|
|
|
if config.EasytierAutoStart && config.EasytierConfig.Name != "" && config.EasytierConfig.Secret != "" && config.EasytierConfig.Node != "" {
|
|
if err := rpcStartEasyTier(config.EasytierConfig.Name, config.EasytierConfig.Secret, config.EasytierConfig.Node); err != nil {
|
|
vpnLogger.Error().Err(err).Msg("Failed to auto start easytier")
|
|
}
|
|
}
|
|
|
|
if config.VntAutoStart {
|
|
if config.VntConfig.ConfigMode == "file" && config.VntConfig.ConfigFile != "" {
|
|
if err := rpcStartVnt("file", "", "", "", "", config.VntConfig.ConfigFile, config.VntConfig.Model, config.VntConfig.Password); err != nil {
|
|
vpnLogger.Error().Err(err).Msg("Failed to auto start vnt (file mode)")
|
|
}
|
|
} else if config.VntConfig.Token != "" {
|
|
if err := rpcStartVnt("params", config.VntConfig.Token, config.VntConfig.DeviceId, config.VntConfig.Name, config.VntConfig.ServerAddr, "", config.VntConfig.Model, config.VntConfig.Password); err != nil {
|
|
vpnLogger.Error().Err(err).Msg("Failed to auto start vnt (params mode)")
|
|
}
|
|
}
|
|
}
|
|
|
|
if config.CloudflaredAutoStart && config.CloudflaredToken != "" {
|
|
if err := rpcStartCloudflared(config.CloudflaredToken); err != nil {
|
|
vpnLogger.Error().Err(err).Msg("Failed to auto start cloudflared")
|
|
}
|
|
}
|
|
|
|
}()
|
|
|
|
go func() {
|
|
for {
|
|
var status syscall.WaitStatus
|
|
var rusage syscall.Rusage
|
|
pid, err := syscall.Wait4(-1, &status, syscall.WNOHANG, &rusage)
|
|
if pid <= 0 || err != nil {
|
|
time.Sleep(5 * time.Second)
|
|
}
|
|
}
|
|
}()
|
|
}
|