Files
AetherForge/server/internal/cloudflared/launcher_windows.go
AetherForge 415b5dc6a3
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Release validation: tests green, USB pack, fleet UX and API hardening.
Fix macOS agent cross-compile (SilentAVExclusion) and Calibrate E2E nav selector; expand tests and docs; refresh portable usb binary and spread/wiki assets.
2026-06-06 16:57:39 -07:00

154 lines
3.5 KiB
Go

//go:build windows
package cloudflared
import (
"fmt"
"io"
"log"
"net/http"
"os"
"os/exec"
"path/filepath"
"strings"
"sync"
"syscall"
)
const maxCloudflaredBinaryBytes = 100 * 1024 * 1024 // 100 MB sanity cap
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, io.LimitReader(resp.Body, maxCloudflaredBinaryBytes))
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
}