diff options
| author | sergei <sergei@em-sysadmin.xyz> | 2026-04-14 06:23:55 +0400 |
|---|---|---|
| committer | sergei <sergei@em-sysadmin.xyz> | 2026-04-14 06:23:55 +0400 |
| commit | 3d51aa455006903345f554a2dd90034993796114 (patch) | |
| tree | 62a7be2faf047f5eb7886feebc3b815556f03d7f /cmd/installer/main.go | |
| download | vpnem-3d51aa455006903345f554a2dd90034993796114.tar.gz vpnem-3d51aa455006903345f554a2dd90034993796114.tar.bz2 vpnem-3d51aa455006903345f554a2dd90034993796114.zip | |
- 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/main.go')
| -rw-r--r-- | cmd/installer/main.go | 236 |
1 files changed, 236 insertions, 0 deletions
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() +} |
