init
This commit is contained in:
130
internal/services/config.go
Normal file
130
internal/services/config.go
Normal file
@@ -0,0 +1,130 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Host string `json:"host"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Servers []Server `json:"servers"`
|
||||
SelectedServerID string `json:"selectedServerId"`
|
||||
}
|
||||
|
||||
type ConfigService struct {
|
||||
path string
|
||||
config Config
|
||||
}
|
||||
|
||||
func NewConfigService() (*ConfigService, error) {
|
||||
configDir, err := os.UserConfigDir()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get config dir: %w", err)
|
||||
}
|
||||
|
||||
dir := filepath.Join(configDir, "tailscale-vpn")
|
||||
if err := os.MkdirAll(dir, 0o700); err != nil {
|
||||
return nil, fmt.Errorf("create config dir: %w", err)
|
||||
}
|
||||
|
||||
path := filepath.Join(dir, "settings.json")
|
||||
|
||||
if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) {
|
||||
if err := os.WriteFile(path, []byte(`{"servers":[]}`), 0o600); err != nil {
|
||||
return nil, fmt.Errorf("create config file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read config file: %w", err)
|
||||
}
|
||||
|
||||
var cfg Config
|
||||
if err := json.Unmarshal(data, &cfg); err != nil {
|
||||
return nil, fmt.Errorf("parse config: %w", err)
|
||||
}
|
||||
|
||||
return &ConfigService{path: path, config: cfg}, nil
|
||||
}
|
||||
|
||||
func (s *ConfigService) GetServers() []Server {
|
||||
return s.config.Servers
|
||||
}
|
||||
|
||||
func (s *ConfigService) GetServer(id string) (Server, bool) {
|
||||
for _, srv := range s.config.Servers {
|
||||
if srv.ID == id {
|
||||
return srv, true
|
||||
}
|
||||
}
|
||||
return Server{}, false
|
||||
}
|
||||
|
||||
func (s *ConfigService) AddServer(name, host string) error {
|
||||
srv := Server{
|
||||
ID: host,
|
||||
Name: name,
|
||||
Host: host,
|
||||
}
|
||||
s.config.Servers = append(s.config.Servers, srv)
|
||||
return s.Save()
|
||||
}
|
||||
|
||||
func (s *ConfigService) RemoveServer(id string) error {
|
||||
s.config.Servers = slices.DeleteFunc(s.config.Servers, func(srv Server) bool {
|
||||
return srv.ID == id
|
||||
})
|
||||
return s.Save()
|
||||
}
|
||||
|
||||
func (s *ConfigService) GetSelectedServer() (Server, bool) {
|
||||
for _, srv := range s.config.Servers {
|
||||
if srv.ID == s.config.SelectedServerID {
|
||||
return srv, true
|
||||
}
|
||||
}
|
||||
return Server{}, false
|
||||
}
|
||||
|
||||
func (s *ConfigService) SetSelectedServer(id string) error {
|
||||
for _, srv := range s.config.Servers {
|
||||
if srv.ID == id {
|
||||
s.config.SelectedServerID = id
|
||||
return s.Save()
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("server not found")
|
||||
}
|
||||
|
||||
func (s *ConfigService) UpdateServer(id, name, host string) error {
|
||||
for i, srv := range s.config.Servers {
|
||||
if srv.ID == id {
|
||||
s.config.Servers[i].Name = name
|
||||
s.config.Servers[i].Host = host
|
||||
s.config.Servers[i].ID = host
|
||||
return s.Save()
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("server not found")
|
||||
}
|
||||
|
||||
func (s *ConfigService) Save() error {
|
||||
data, err := json.MarshalIndent(s.config, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal config: %w", err)
|
||||
}
|
||||
if err := os.WriteFile(s.path, data, 0o600); err != nil {
|
||||
return fmt.Errorf("write config: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
11
internal/services/services_store.go
Normal file
11
internal/services/services_store.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package services
|
||||
|
||||
type ServicesStore struct {
|
||||
VPN *VPNService
|
||||
Config *ConfigService
|
||||
}
|
||||
|
||||
func NewServicesStore() (*ServicesStore, error) {
|
||||
cfg, _ := NewConfigService()
|
||||
return &ServicesStore{VPN: NewVPNService(cfg), Config: cfg}, nil
|
||||
}
|
||||
66
internal/services/vpn_service.go
Normal file
66
internal/services/vpn_service.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"log/slog"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
||||
type VPNService struct{
|
||||
Config *ConfigService
|
||||
}
|
||||
|
||||
func NewVPNService(cfg *ConfigService) *VPNService {
|
||||
return &VPNService{
|
||||
Config: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (v *VPNService) CheckStatus(ctx context.Context) (bool, error) {
|
||||
cmd := exec.CommandContext(ctx, "tailscale", "exit-node", "list")
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
slog.ErrorContext(ctx, "failed to check VPN status", "error", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
output := out.String()
|
||||
return strings.Contains(output, "selected"), nil
|
||||
}
|
||||
|
||||
func (v *VPNService) TurnOn(ctx context.Context) error {
|
||||
selectedServer, _ := v.Config.GetSelectedServer()
|
||||
cmd := exec.CommandContext(ctx, "sudo", "tailscale", "set", "--exit-node="+selectedServer.Host)
|
||||
if err := cmd.Run(); err != nil {
|
||||
slog.ErrorContext(ctx, "failed to turn on VPN", "error", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *VPNService) TurnOff(ctx context.Context) error {
|
||||
cmd := exec.CommandContext(ctx, "sudo", "tailscale", "set", "--exit-node=")
|
||||
if err := cmd.Run(); err != nil {
|
||||
slog.ErrorContext(ctx, "failed to turn off VPN", "error", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *VPNService) ExitNodeList(ctx context.Context) (string, error) {
|
||||
cmd := exec.CommandContext(ctx, "tailscale", "exit-node", "list")
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
slog.ErrorContext(ctx, "failed to get exit node list", "error", err)
|
||||
return "", err
|
||||
}
|
||||
return out.String(), nil
|
||||
}
|
||||
Reference in New Issue
Block a user