* feat: shellcode output via Donut (Windows) + memfd stub (Linux) - Donut (Windows): PE → PIC shellcode (.bin) via donut-manager.ts Flags: -f1 -a<arch> -b3 -x1 (continue on AMSI fail, thread exit) - Linux shellcode: 113-byte x86_64 memfd_create+execveat stub (linux-shellcode-manager.ts) wraps ELF for memfd_create+execveat - Two-pass build for shellcode+persistence: Pass 1 = normal agent with persist compiled in; Pass 2 = selfembed agent that drops+registers Pass 1 on first run (isRunningInMemory() gates the path) - Show console option: shellcode_console build tag calls AllocConsole() on start for debug builds; AttachConsole fallback included - isRunningInMemory() Windows: VirtualQuery on .data sentinel (MEM_PRIVATE = injected, MEM_IMAGE = normal disk load) replaces os.Executable() which returned host process path when injected - Build UI: Donut mode, Linux shellcode mode, show-console checkboxes; settings persisted across sessions via collectFormSettings() - GODEBUG=netdns=cgo hardcoded in build env (fixes IPv6 DNS on VPN) - Dockerfile: pre-seed Go module cache; add curl to runtime image - docker-compose.yml: switch healthcheck to wget (curl absent in slim) * fix: add missing shellcode UI functions and restore dynamic linking applyDonutMode, applyLinuxShellcodeMode, and updateShellcodeCheckboxVisibility were called throughout build.js but never defined, breaking platform selection and the build button. Add the missing implementations. Also remove forced -extldflags '-static' on Linux CGO builds; fully static glibc binaries cannot call dlopen, which broke native plugin loading entirely. * fix: call updateShellcodeCheckboxVisibility on page load Shellcode rows were hidden on initial load because the function was only wired to checkbox change events. Call it at startup and in applyFormSettings so pre-selected platforms correctly show their shellcode options immediately. * feat: Linux plugin subprocess shim for static-agent compatibility Static musl binaries cannot call dlopen. Introduce a small C shim (plugin_host.c) that is compiled at agent-build time into a dynamically-linked binary, embedded via go:embed, and fork+exec'd to load .so plugins on behalf of the static agent over a Unix socketpair. Empty placeholder files allow go:embed to compile without error; the real binaries are produced by build-process.ts before go build runs and are never committed. Documents the IPC protocol and build matrix in PLUGINS.md Section 12. * fix: define sp_memfd_create/sp_write_all in subproc CGO preamble CGO preamble functions are per-file translation units; so_memfd_create and so_write_all from loader_linux.go are not visible in loader_linux_subproc.go. Add local equivalents with sp_ prefix. * fix: use CGO helper for shim memfd_create instead of syscall.SYS_MEMFD_CREATE syscall.SYS_MEMFD_CREATE is not defined in Go stdlib syscall package. Replace with sp_memfd_create_nocloe() CGO helper (flags=0, no CLOEXEC). * fix: compile plugin host shim statically to avoid glibc version mismatch A dynamically-linked shim compiled on Debian Bookworm (glibc 2.36) fails on older glibc targets. Compile with -static so the shim is self-contained and runs on any glibc version. Static glibc can still call dlopen at runtime via the system ld-linux.so.2. Also surface subprocess errors instead of silently falling through to dlopen so failures are visible in the build log. * Restore docker build cache
Overlord
TELEGRAM SERVER JOIN NOW NO EXCUSES WE GIVE SUPPORT AND IT'S FUN
Hello, I made this project for fun.
The server is TypeScript on Node/Bun. The client is Go. Operators talk to the server through a web panel or the Electron desktop app, and agents connect over encrypted WebSockets.
Docker is the easiest way to run it.
- Quick Start (Docker)
- No Docker (.bat / .sh)
- Production Package Scripts
- WebRTC Streaming
- Docker Notes (TLS, reverse proxy, cache)
Quick Start (Docker)
Pick your OS below. Each section is self-contained: install Docker, get the project, start it.
Windows and macOS use
docker-compose.windows.yml. Linux uses the defaultdocker-compose.yml(host networking).
After the first start, open https://localhost:5173. Default login is admin / admin unless you set OVERLORD_USER / OVERLORD_PASS. First startup writes generated secrets to data/save.json (inside the container: /app/data/save.json) — keep that file private and back it up.
Windows
Step-by-step: Windows
1. Install Docker Desktop
Either from the website:
Or with winget:
winget install -e --id Docker.DockerDesktop
Start Docker Desktop once, then verify:
docker --version
docker compose version
2. Get the project
git clone https://github.com/vxaboveground/Overlord.git
cd Overlord
3. Start it
docker compose -f docker-compose.windows.yml up -d
4. Open the panel
https://localhost:5173
5. Update later
docker compose -f docker-compose.windows.yml down
docker compose -f docker-compose.windows.yml pull
docker compose -f docker-compose.windows.yml up -d
6. Stop
docker compose -f docker-compose.windows.yml down
Linux
Step-by-step: Linux (Debian / Ubuntu / Kali)
1. Install Docker
Official docs: https://docs.docker.com/engine/install/debian/
Set up Docker's apt repository:
# Add Docker's official GPG key:
sudo apt update
sudo apt install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
# Add the repository to Apt sources:
sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/debian
Suites: $(. /etc/os-release && echo "$VERSION_CODENAME")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF
sudo apt update
On a derivative distro (e.g. Kali), replace the codename expansion with the matching Debian codename, e.g. bookworm.
Install Docker:
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Make sure the daemon is running:
sudo systemctl status docker
2. Grab the compose file
Make a folder for it, drop in the file, and you're done:
mkdir overlord && cd overlord
wget https://raw.githubusercontent.com/vxaboveground/Overlord/refs/heads/main/docker-compose.yml
No wget? Use curl:
mkdir overlord && cd overlord
curl -O https://raw.githubusercontent.com/vxaboveground/Overlord/refs/heads/main/docker-compose.yml
3. Start it
docker compose up -d
The image is pulled automatically from ghcr.io/vxaboveground/overlord:latest on first run.
4. Open the panel
https://localhost:5173
or
https://IP:5173
5. Update later
From the same folder:
docker compose down
docker compose pull
docker compose up -d
6. Stop
docker compose down
macOS
Step-by-step: macOS
1. Install Docker Desktop
Either from the website:
Or with Homebrew:
brew install --cask docker
Start Docker Desktop once, then verify:
docker --version
docker compose version
2. Get the project
git clone https://github.com/vxaboveground/Overlord.git
cd Overlord
3. Start it
macOS uses the same compose file as Windows:
docker compose -f docker-compose.windows.yml up -d
4. Open the panel
https://localhost:5173
5. Update later
docker compose -f docker-compose.windows.yml down
docker compose -f docker-compose.windows.yml pull
docker compose -f docker-compose.windows.yml up -d
6. Stop
docker compose -f docker-compose.windows.yml down
No Docker (.bat / .sh)
If you don't want Docker, use the included scripts.
Prerequisites:
- Bun in PATH
- Go 1.21+ in PATH
Windows
Development mode (starts server + client):
start-dev.bat
Production mode (build + run server executable):
start-prod.bat
Build client binaries (adds client builds to the build queue):
build-clients.bat
Linux / macOS
Make scripts executable once:
chmod +x start-dev.sh start-dev-server.sh start-dev-client.sh start-prod.sh build-prod-package.sh
Development mode (server in background, client in foreground):
./start-dev.sh
Only server, or only client:
./start-dev.sh server
./start-dev.sh client
Production mode:
./start-prod.sh
Production Package Scripts
Build a production-ready package where the server can still build client binaries at runtime.
Windows:
build-prod-package.bat
Output: release/
Linux / macOS:
./build-prod-package.sh
Output: release/prod-package/
WebRTC Streaming
The remote desktop viewer has a Transport dropdown with three modes:
- Canvas (default): H.264 / JPEG / block frames over the existing WebSocket, decoded into a
<canvas>. Highest latency, works anywhere the WS does. - WebRTC P2P: browser ↔ agent direct. The server only relays SDP and ICE candidates over the existing WS — MediaMTX is not involved. Lowest latency. Fails when both sides are behind aggressive symmetric NAT.
- WebRTC Relayed: agent publishes to a MediaMTX sidecar via WHIP, browser plays via WHEP. The server proxies signaling so the existing JWT auth + per-client RBAC still apply. Lowest-effort fallback when P2P can't punch through.
Building agents with WebRTC
WebRTC is opt-in per agent build. In the builder UI, tick the WebRTC checkbox before clicking Build. Without it, the Pion stack (~6 MB of Go modules) is not compiled in — any WebRTC start attempt from the operator returns webrtc support not compiled in and the viewer falls back to Canvas. The Canvas path always works regardless of the build setting.
The build tag (overlord_webrtc) is also available if you build agents outside the UI:
go build -tags overlord_webrtc ./cmd/agent
MediaMTX sidecar
Compose starts an overlord-mediamtx service for Relayed mode. It needs:
- Port
8189/udpand8189/tcpreachable from operators (WebRTC ICE traffic). The Windows / macOS compose publishes these; Linux uses host networking and shares the host's interfaces directly. - No auth config — the Overlord server proxies every WHIP/WHEP request through
/api/webrtc/...and enforces the existing operator JWT + RBAC there.
If you only ever want P2P, you can comment out the mediamtx: service in your compose file — only Relayed mode depends on it.
LAN / public access
By default MediaMTX advertises 127.0.0.1 as an ICE candidate, which is enough for operators running their browser on the same machine as Docker.
For viewers on a different machine:
-
Linux (host networking): nothing to configure — MediaMTX sees the host's eth0/wlan0 and gathers those candidates automatically.
-
Windows / macOS (bridge networking): set
OVERLORD_WEBRTC_ADDITIONAL_HOSTSto a comma-separated list including the server's reachable LAN or public IP. Either export it in your shell, or add it to a.envfile next to the compose file:OVERLORD_WEBRTC_ADDITIONAL_HOSTS=127.0.0.1,192.168.1.42Then
docker compose -f docker-compose.windows.yml up -d mediamtxto apply.
Advanced MediaMTX customization
The compose file passes a minimal set of MTX_* environment variables to MediaMTX — enough for Overlord to work. To change anything else (codecs, paths, ICE servers, etc.), either:
-
Add more
MTX_*variables to themediamtxservice'senvironment:block (every option in MediaMTX's docs is supported as an env var), or -
Provide a full
mediamtx.ymlvia a bind mount:mediamtx: # ...existing config... volumes: - ./mediamtx.yml:/mediamtx.yml:roMake sure the file exists on the host before the container starts — Docker will otherwise auto-create an empty directory at that path and fail with "not a directory".
Docker Notes (TLS, reverse proxy, cache)
Notes on configs and workarounds.
BuildKit cache for faster rebuilds
docker-compose.yml ships with build.cache_from and build.cache_to pointing at .docker-cache/buildx. Local builds reuse it automatically — no extra setup.
Runtime client build cache
The compose setup uses a persistent volume for runtime client builds:
- Volume:
overlord-client-build-cache - Mount:
/app/client-build-cache - Env:
OVERLORD_CLIENT_BUILD_CACHE_DIR(default/app/client-build-cache)
Certbot TLS
To use Let's Encrypt certificates in production Docker:
- Set
OVERLORD_TLS_CERTBOT_ENABLED=true - Set
OVERLORD_TLS_CERTBOT_DOMAIN=your-domain.com - Mount letsencrypt into the container read-only, e.g.
/etc/letsencrypt:/etc/letsencrypt:ro
Default cert paths:
cert: /etc/letsencrypt/live/<domain>/fullchain.pem
key: /etc/letsencrypt/live/<domain>/privkey.pem
ca: /etc/letsencrypt/live/<domain>/chain.pem
Override with:
OVERLORD_TLS_CERTBOT_LIVE_PATHOVERLORD_TLS_CERTBOT_CERT_FILEOVERLORD_TLS_CERTBOT_KEY_FILEOVERLORD_TLS_CERTBOT_CA_FILE
Reverse proxy TLS offload
If your platform terminates TLS before traffic reaches Overlord (Render, Caddy, nginx, etc.), set:
OVERLORD_TLS_OFFLOAD=true
OVERLORD_HEALTHCHECK_URL=http://localhost:5173/health
OVERLORD_PUBLISH_HOST=127.0.0.1
When enabled:
- Container serves internal HTTP on
0.0.0.0:$PORT - External URL stays
https://...through your platform proxy - Health checks should use
http://localhost:$PORT/healthinside the container - Don't expose the internal container HTTP port directly to the internet
Notes
- Keep
HOST=0.0.0.0inside the container. Limit exposure withOVERLORD_PUBLISH_HOST, not the bind host. - If your
.envsecret/password contains$, escape it as$$to avoid Docker Compose variable-expansion warnings.
