summaryrefslogtreecommitdiff
path: root/cmd/installer
diff options
context:
space:
mode:
authorsergei <sergei@em-sysadmin.xyz>2026-04-14 06:23:55 +0400
committersergei <sergei@em-sysadmin.xyz>2026-04-14 06:23:55 +0400
commit3d51aa455006903345f554a2dd90034993796114 (patch)
tree62a7be2faf047f5eb7886feebc3b815556f03d7f /cmd/installer
downloadvpnem-3d51aa455006903345f554a2dd90034993796114.tar.gz
vpnem-3d51aa455006903345f554a2dd90034993796114.tar.bz2
vpnem-3d51aa455006903345f554a2dd90034993796114.zip
vpnem: VPN infrastructure with load-balanced multi-protocol nodesHEADmain
- Multi-protocol VPS nodes (VLESS-REALITY + Hysteria2 + SOCKS5) - Smart load balancing via recommendation API - Windows/Linux client (Go + Wails + sing-box) - Server API with RealIP detection and connection tracking - Auto-deployment via vpnui control plane - Silent Windows installer with UAC elevation - Load-based server recommendation (no sticky sessions) - Best Server one-click connection workflow
Diffstat (limited to 'cmd/installer')
-rw-r--r--cmd/installer/elevate_windows.go74
-rw-r--r--cmd/installer/main.go236
2 files changed, 310 insertions, 0 deletions
diff --git a/cmd/installer/elevate_windows.go b/cmd/installer/elevate_windows.go
new file mode 100644
index 0000000..f174390
--- /dev/null
+++ b/cmd/installer/elevate_windows.go
@@ -0,0 +1,74 @@
+//go:build windows
+
+package main
+
+import (
+ "fmt"
+ "os"
+ "strings"
+ "syscall"
+ "unsafe"
+)
+
+var (
+ shell32 = syscall.NewLazyDLL("shell32.dll")
+ shellExecuteW = shell32.NewProc("ShellExecuteW")
+ isUserAnAdminProc = shell32.NewProc("IsUserAnAdmin")
+ errCancelledByUser = uintptr(1223)
+)
+
+func isElevated() bool {
+ ret, _, _ := isUserAnAdminProc.Call()
+ return ret != 0
+}
+
+func relaunchElevated() error {
+ exePath, err := os.Executable()
+ if err != nil {
+ return err
+ }
+
+ verb, err := syscall.UTF16PtrFromString("runas")
+ if err != nil {
+ return err
+ }
+ file, err := syscall.UTF16PtrFromString(exePath)
+ if err != nil {
+ return err
+ }
+ args, err := syscall.UTF16PtrFromString(joinWindowsArgs(os.Args[1:]))
+ if err != nil {
+ return err
+ }
+
+ ret, _, callErr := shellExecuteW.Call(
+ 0,
+ uintptr(unsafe.Pointer(verb)),
+ uintptr(unsafe.Pointer(file)),
+ uintptr(unsafe.Pointer(args)),
+ 0,
+ 1,
+ )
+ if ret <= 32 {
+ if ret == errCancelledByUser {
+ return fmt.Errorf("administrator privileges were not granted")
+ }
+ if callErr != syscall.Errno(0) {
+ return fmt.Errorf("ShellExecuteW failed: %w", callErr)
+ }
+ return fmt.Errorf("ShellExecuteW failed with code %d", ret)
+ }
+ return nil
+}
+
+func joinWindowsArgs(args []string) string {
+ if len(args) == 0 {
+ return ""
+ }
+ quoted := make([]string, 0, len(args))
+ for _, arg := range args {
+ escaped := strings.ReplaceAll(arg, `"`, `\"`)
+ quoted = append(quoted, `"`+escaped+`"`)
+ }
+ return strings.Join(quoted, " ")
+}
diff --git a/cmd/installer/main.go b/cmd/installer/main.go
new file mode 100644
index 0000000..07f7fe8
--- /dev/null
+++ b/cmd/installer/main.go
@@ -0,0 +1,236 @@
+// vpnem-installer: Windows offline installer (GUI, no console window).
+// Bundles all binaries — no network download needed.
+// Installs to Program Files, creates Task Scheduler task for UAC-free launch.
+// Requires admin (one-time). Supports silent mode: vpnem-installer.exe /S
+// Cross-compiles from Linux with -ldflags "-H windowsgui"
+package main
+
+import (
+ "embed"
+ "fmt"
+ "log"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+ "time"
+)
+
+//go:embed files/*
+var bundledFiles embed.FS
+
+const (
+ installDir = `C:\Program Files\vpnem`
+ dataDir = `C:\ProgramData\vpnem`
+ taskName = "vpnem"
+
+ baseURL = "https://vpn.em-sysadmin.xyz/releases"
+ vpnemURL = baseURL + "/vpnem-windows-amd64.exe"
+)
+
+var (
+ silent bool
+ noShortcut bool
+ launch bool
+ logFile *os.File
+)
+
+func main() {
+ // Parse flags
+ for _, arg := range os.Args[1:] {
+ a := strings.ToLower(strings.TrimLeft(arg, "/-"))
+ switch a {
+ case "s", "silent":
+ silent = true
+ case "noshortcut":
+ noShortcut = true
+ case "launch":
+ launch = true
+ }
+ }
+
+ if !isElevated() {
+ if err := relaunchElevated(); err != nil {
+ fatal("request administrator privileges: %v", err)
+ }
+ return
+ }
+
+ // 1. Kill ALL running instances immediately
+ step("stopping running instances")
+ exec.Command("taskkill", "/F", "/IM", "vpnem.exe").Run()
+ exec.Command("taskkill", "/F", "/IM", "sing-box.exe").Run()
+ time.Sleep(2 * time.Second)
+
+ // 2. Remove OLD installation directories completely (clean slate)
+ step("removing old installation")
+ if err := os.RemoveAll(installDir); err != nil {
+ log.Printf("warning: could not remove old install dir: %v", err)
+ }
+ if err := os.RemoveAll(dataDir); err != nil {
+ log.Printf("warning: could not remove old data dir: %v", err)
+ }
+ // Also remove old ProxySwitcher if exists
+ os.RemoveAll(`C:\ProxySwitcher`)
+
+ // 3. Create fresh directories
+ if err := os.MkdirAll(installDir, 0o755); err != nil {
+ fatal("create install dir: %v", err)
+ }
+ if err := os.MkdirAll(dataDir, 0o755); err != nil {
+ fatal("create data dir: %v", err)
+ }
+
+ // Write bundled files instead of downloading
+ step("extracting bundled vpnem.exe")
+ if err := writeEmbedded("files/vpnem.exe", filepath.Join(installDir, "vpnem.exe")); err != nil {
+ fatal("extract vpnem.exe: %v", err)
+ }
+ step("extracting bundled sing-box.exe")
+ if err := writeEmbedded("files/sing-box.exe", filepath.Join(installDir, "sing-box.exe")); err != nil {
+ fatal("extract sing-box.exe: %v", err)
+ }
+ step("extracting bundled wintun.dll")
+ if err := writeEmbedded("files/wintun.dll", filepath.Join(installDir, "wintun.dll")); err != nil {
+ fatal("extract wintun.dll: %v", err)
+ }
+
+ // Create Task Scheduler task — runs vpnem as admin WITHOUT UAC prompt.
+ // This is the key: task created by admin runs with highest privileges silently.
+ step("creating scheduled task (UAC-free launch)")
+ createTask()
+
+ // Desktop shortcut — launches via schtasks (no UAC popup)
+ if !noShortcut {
+ step("creating desktop shortcut")
+ createShortcut()
+ }
+
+ step("installation complete")
+
+ if launch {
+ step("launching vpnem")
+ exec.Command("schtasks", "/Run", "/TN", taskName).Run()
+ }
+
+ if !silent {
+ showDoneMessage()
+ }
+}
+
+// createTask sets up a scheduled task that:
+// 1. Runs vpnem.exe with highest privileges (no UAC)
+// 2. Starts at logon with 15s delay (autostart)
+// The same task is used for both manual launch and autostart.
+func createTask() {
+ exec.Command("schtasks", "/Delete", "/TN", taskName, "/F").Run()
+
+ // Create XML task definition for full control
+ exePath := filepath.Join(installDir, "vpnem.exe")
+ xml := fmt.Sprintf(`<?xml version="1.0" encoding="UTF-16"?>
+<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
+ <RegistrationInfo>
+ <Description>vpnem VPN client</Description>
+ </RegistrationInfo>
+ <Triggers>
+ <LogonTrigger>
+ <Enabled>true</Enabled>
+ <Delay>PT15S</Delay>
+ </LogonTrigger>
+ </Triggers>
+ <Principals>
+ <Principal>
+ <LogonType>InteractiveToken</LogonType>
+ <RunLevel>HighestAvailable</RunLevel>
+ </Principal>
+ </Principals>
+ <Settings>
+ <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
+ <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
+ <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
+ <ExecutionTimeLimit>PT0S</ExecutionTimeLimit>
+ <AllowStartOnDemand>true</AllowStartOnDemand>
+ <AllowHardTerminate>true</AllowHardTerminate>
+ </Settings>
+ <Actions>
+ <Exec>
+ <Command>%s</Command>
+ <Arguments>--data "%s"</Arguments>
+ <WorkingDirectory>%s</WorkingDirectory>
+ </Exec>
+ </Actions>
+</Task>`, exePath, dataDir, installDir)
+
+ xmlPath := filepath.Join(dataDir, "task.xml")
+ os.WriteFile(xmlPath, []byte(xml), 0o644)
+
+ cmd := exec.Command("schtasks", "/Create", "/TN", taskName, "/XML", xmlPath, "/F")
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ log.Printf("task create failed: %v\n%s", err, string(out))
+ } else {
+ log.Println("task created ok")
+ }
+ os.Remove(xmlPath)
+}
+
+// createShortcut makes a desktop shortcut that runs the scheduled task (no UAC)
+// IconLocation points to vpnem.exe so the shortcut has the app icon
+func createShortcut() {
+ exePath := filepath.Join(installDir, "vpnem.exe")
+ ps := `
+$ws = New-Object -ComObject WScript.Shell
+$s = $ws.CreateShortcut("$env:USERPROFILE\Desktop\vpnem.lnk")
+$s.TargetPath = "schtasks.exe"
+$s.Arguments = "/Run /TN vpnem"
+$s.WorkingDirectory = "` + installDir + `"
+$s.IconLocation = "` + exePath + `,0"
+$s.Description = "vpnem VPN client"
+$s.Save()
+`
+ cmd := exec.Command("powershell", "-NoProfile", "-WindowStyle", "Hidden", "-Command", ps)
+ if err := cmd.Run(); err != nil {
+ log.Printf("shortcut failed: %v (non-critical)", err)
+ }
+}
+
+func step(msg string) {
+ log.Println(msg)
+}
+
+func writeEmbedded(name, dest string) error {
+ data, err := bundledFiles.ReadFile(name)
+ if err != nil {
+ return fmt.Errorf("read embedded file %s: %w", name, err)
+ }
+ tmp := dest + ".tmp"
+ if err := os.WriteFile(tmp, data, 0o755); err != nil {
+ return fmt.Errorf("write %s: %w", dest, err)
+ }
+ info, _ := os.Stat(tmp)
+ log.Printf(" %s (%.1f MB)", filepath.Base(dest), float64(info.Size())/1024/1024)
+ return os.Rename(tmp, dest)
+}
+
+func fatal(format string, args ...any) {
+ msg := fmt.Sprintf(format, args...)
+ log.Printf("FATAL: %s", msg)
+ if silent {
+ os.Exit(1)
+ }
+ showError(msg)
+ os.Exit(1)
+}
+
+func showDoneMessage() {
+ msg := fmt.Sprintf("vpnem installed to %s\\n\\nDesktop shortcut created.\\nAutostart at logon enabled.\\n\\nNo admin prompts needed to launch.", installDir)
+ exec.Command("powershell", "-NoProfile", "-WindowStyle", "Hidden", "-Command",
+ fmt.Sprintf(`Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.MessageBox]::Show("%s", "vpnem installer", "OK", "Information")`, msg),
+ ).Run()
+}
+
+func showError(msg string) {
+ exec.Command("powershell", "-NoProfile", "-WindowStyle", "Hidden", "-Command",
+ fmt.Sprintf(`Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.MessageBox]::Show("Installation failed:\n%s", "vpnem installer", "OK", "Error")`, msg),
+ ).Run()
+}