Files
akukanara f4a88f4b2c
golangci-lint / lint (push) Failing after 4s
feat(gui): redesign kanholec GUI with modern interface and WiX v7 installer
- Complete GUI overhaul with Fyne framework
  * Modern dark theme with indigo/pink accent colors
  * 4-step setup wizard (Welcome → Server → Auth → Finish)
  * Dashboard with proxy list, real-time logs, and config viewer
  * Windows Service manager (install/start/stop/uninstall)
  * System tray integration with quick controls

- WiX v7 MSI installer
  * Auto-registers as Windows Service (manual start)
  * Feature tree UI for optional components
  * Desktop shortcut and PATH options
  * Build script: packaging/windows/build-msi.ps1

- Build system updates
  * Added kanholec-windows-gui target (CGO required)
  * Added kanholec-windows-msi target
  * Separate main_gui.go entry point for GUI builds

GUI binary: bin/kanholec-windows-amd64.exe (31.4 MB)
MSI installer: bin/kanholec-0.69.0-amd64.msi (12.1 MB)
2026-05-29 22:53:41 +07:00

142 lines
3.1 KiB
Go

//go:build kanholec_gui
package gui
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
)
type ServiceManager struct {
binaryPath string
serviceName string
}
func NewServiceManager() *ServiceManager {
exe, _ := os.Executable()
return &ServiceManager{
binaryPath: exe,
serviceName: "kanholec",
}
}
func (s *ServiceManager) IsInstalled() bool {
if runtime.GOOS != "windows" {
return false
}
cmd := exec.Command("sc", "query", s.serviceName)
err := cmd.Run()
return err == nil
}
func (s *ServiceManager) Install(configPath string) error {
if runtime.GOOS != "windows" {
return fmt.Errorf("service installation is only supported on Windows")
}
if s.IsInstalled() {
return fmt.Errorf("service %q is already installed", s.serviceName)
}
args := []string{
"create", s.serviceName,
"binPath=", fmt.Sprintf(`"%s" -c "%s"`, s.binaryPath, configPath),
"DisplayName=", "kanholec - kanhole Client",
"start=", "auto",
"obj=", "LocalSystem",
}
cmd := exec.Command("sc", args...)
out, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("failed to install service: %s: %w", string(out), err)
}
descArgs := []string{"description", s.serviceName, "kanhole reverse proxy client service"}
cmd = exec.Command("sc", descArgs...)
cmd.Run()
return nil
}
func (s *ServiceManager) Uninstall() error {
if runtime.GOOS != "windows" {
return fmt.Errorf("service management is only supported on Windows")
}
if !s.IsInstalled() {
return fmt.Errorf("service %q is not installed", s.serviceName)
}
s.Stop()
cmd := exec.Command("sc", "delete", s.serviceName)
out, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("failed to uninstall service: %s: %w", string(out), err)
}
return nil
}
func (s *ServiceManager) Start() error {
if runtime.GOOS != "windows" {
return fmt.Errorf("service management is only supported on Windows")
}
cmd := exec.Command("sc", "start", s.serviceName)
out, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("failed to start service: %s: %w", string(out), err)
}
return nil
}
func (s *ServiceManager) Stop() error {
if runtime.GOOS != "windows" {
return fmt.Errorf("service management is only supported on Windows")
}
cmd := exec.Command("sc", "stop", s.serviceName)
out, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("failed to stop service: %s: %w", string(out), err)
}
return nil
}
func (s *ServiceManager) IsRunning() bool {
if runtime.GOOS != "windows" {
return false
}
cmd := exec.Command("sc", "query", s.serviceName)
out, err := cmd.CombinedOutput()
if err != nil {
return false
}
return contains(string(out), "RUNNING")
}
func (s *ServiceManager) GetConfigPath() string {
programData := os.Getenv("ProgramData")
if programData == "" {
programData = `C:\ProgramData`
}
return filepath.Join(programData, "kanholec", "kanholec.toml")
}
func contains(s, substr string) bool {
return len(s) >= len(substr) && searchString(s, substr)
}
func searchString(s, substr string) bool {
for i := 0; i <= len(s)-len(substr); i++ {
if s[i:i+len(substr)] == substr {
return true
}
}
return false
}