curl -fsSL https://github.com/runovelhq/wolt/releases/latest/download/install.sh | sudo bash -s -- --install-dir /data/WOLTTranslate standard Wake-on-LAN magic packets from Guacamole or another PAM system into native edge-device wake commands.
The installer creates only the required runtime files under /data/WOLT; generates the
database, bootstrap, and encryption secrets locally; verifies and pins the signed image
digest; starts PostgreSQL and WOLT; and prints the one-time Owner setup token. It does not
clone the repository. Docker Engine, Docker Compose v2, curl, OpenSSL, Python 3, and
sha256sum must already be installed; the installer adds a checksum-verified Cosign binary
when needed.
Current stable image:
runovel/wolt:v1.1.3(also published as1.1.3,1.1,1, andlatest)
Guacamole can send a standard Wake-on-LAN magic packet, but the target workstation may sit behind a routed VLAN where a local broadcast is unavailable. WOLT acts as a small, controlled translation boundary:
- receives the packet on a listener-specific UDP port;
- permits traffic only from the configured Guacamole/guacd source IP;
- validates the exact 102-byte magic-packet structure;
- extracts and validates the destination MAC address;
- maps the listener port to an edge interface and gateway/broadcast address;
- rate-limits repeated wake requests;
- executes the native wake command through a pinned SSH connection;
- emits structured operational logs without logging passwords or packet contents.
WOLT v1.1 includes the Vue management UI, FastAPI API, PostgreSQL persistence, encrypted device credentials, first-run Owner setup, role-based users, SMTP invitations and recovery, listener management, live engine controls, event analytics, audit history, and the optional restricted Host Agent. The driver contract remains extensible for future edge-device providers.
The one-line installer starts the digest-pinned published image. For local development from a source checkout, build an image explicitly and then start it:
./scripts/init-web-env.sh
docker build --target production --build-arg WOLT_VERSION=v1.1.3-dev -t wolt:local .
WOLT_IMAGE=wolt:local docker compose --env-file .env.web -f compose.web.yml up -d --no-buildThe initializer generates independent cryptographically random database and bootstrap
secrets, writes .env.web with mode 600, refuses to overwrite an existing file,
and prints the first-run token with setup guidance. No manual secret editing is needed.
Open http://WOLT_HOST:8080 and enter the printed bootstrap token to create the first Owner.
Save the one-time recovery code in a password manager. The application starts as non-root with a read-only
root filesystem, runs the initial migration, and keeps PostgreSQL on an internal
Docker network. The deployment envelope defaults to UDP 40000–40099; its start and
end can be chosen in .env.web before container creation. Published and active ports
must remain between 1024 and 65535, and a deployment range may contain at most 100
ports. The active allocation range can then be narrowed from the Settings page without
recreating the container.
An existing source-based installation can move to the minimal runtime without losing its environment, certificates, PostgreSQL volume, or Host Agent backups. The same command repairs the Host Agent service boundary for v1.1.0 or v1.1.1 installations. A v1.1.2 Host Agent still trusts and discovers the legacy Docker namespace, so it also requires this one-time CLI migration before future upgrades can run from the UI. Run it once from any directory:
curl -fsSL https://github.com/runovelhq/wolt/releases/download/v1.1.3/install.sh | \
sudo bash -s -- --install-dir /data/WOLT --upgrade-existingThe migration first copies the existing managed runtime into
/var/lib/wolt-agent/runtime-backups/. Back up .env.web securely before and after the
migration. Losing WOLT_MASTER_KEY makes encrypted device credentials unrecoverable; the
key is deliberately not stored in PostgreSQL.
For an HTTPS deployment, set WOLT_SESSION_SECURE=true. WOLT_SESSION_HOURS
controls the server-side session lifetime and defaults to 12 hours. The bootstrap
token only authorizes creation of the first Owner; subsequent setup attempts are
rejected by the database-backed Owner invariant. The bootstrap token only needs to
remain private until Owner creation is complete. The Recovery Code shown in the UI is
the long-term secret that must be retained.
When adding a FortiGate in the web UI, enter its address and service-account credential, then select Discover key & test connection. WOLT performs the SSH handshake from the server, displays the SHA-256 fingerprint for operator confirmation, tests authentication, and fills the pinned host-key automatically. The credential is never returned by the API or included in the audit record.
After the first Owner is created, open SMTP to configure mail delivery and the public HTTPS URL used in one-time links. Send a test message before enabling the configuration. Then use Users & sessions to invite an Administrator or Operator; recipients set their own password through a 24-hour single-use link. Email password recovery uses a 30-minute single-use link and revokes all existing sessions. The original Owner offline recovery code remains available when email is unavailable.
If the SMTP server uses a certificate issued by a private corporate CA, copy the CA/root
bundle in PEM format to certs/smtp-ca.pem and add this line to .env.web:
WOLT_SMTP_CA_FILE=/etc/wolt/certs/smtp-ca.pemThen recreate only the application container:
docker compose --env-file .env.web -f compose.web.yml up -d --no-build --force-recreate appKeep certificate verification enabled. Use the issuing root/intermediate CA bundle rather than disabling TLS validation or permanently trusting a replaceable server leaf certificate.
For a deliberately plaintext, IP-restricted internal relay, select None — trusted internal relay in the SMTP page. This works on any configured port, including 587, and performs no TLS handshake or certificate validation. Credentials and message content are unencrypted in this mode, so do not use it across untrusted or routed networks. Leaving the username blank also skips SMTP authentication for relays that authorize the WOLT host by source IP. A connector that advertises only NTLM/GSSAPI authentication is not made compatible merely by disabling TLS; use an IP-authorized relay or a supported SMTP AUTH mechanism.
The one-line installer installs the restricted Host Agent automatically. It manages UFW, published UDP ports, signed upgrades, health verification, and rollback from the Owner UI. For a source-development installation, install it manually with:
sudo ./scripts/install-host-agent.sh "$(pwd)"For a published minimal installation under /data/WOLT, repair or reinstall the Host
Agent with:
sudo /data/WOLT/runtime/scripts/install-host-agent.sh /data/WOLTThis command preserves the existing Host Agent token, refreshes its systemd service, and recreates the app container with the authenticated Unix-socket override.
The app container remains non-root and never receives the Docker socket. Communication uses an authenticated, group-restricted Unix socket. Upgrade images are accepted only after Cosign verifies the official WOLT GitHub release workflow, and the verified digest—not the mutable tag—is deployed. See Phase 7 Host Operations before enabling this capability.
Stop WOLT without deleting its database:
docker compose --env-file .env.web -f compose.web.yml downflowchart LR
PAM["PAM / Guacamole<br/>Wake request"]
UDP["WOLT UDP ingress<br/>Dedicated listener port"]
VALIDATE["Source allow-list<br/>Magic-packet validation"]
LIMIT["Per-port + MAC<br/>rate limiter"]
MAP["Port mapping<br/>interface + gateway"]
DRIVER["Edge-device adapter<br/>FortiGate SSH in v0.1"]
EDGE["FortiGate<br/>native WOL command"]
LAN["Destination VLAN<br/>target workstation"]
LOGS[("Structured logs")]
PAM -->|"UDP magic packet"| UDP
UDP --> VALIDATE
VALIDATE --> LIMIT
LIMIT --> MAP
MAP --> DRIVER
DRIVER -->|"Pinned SSH host key"| EDGE
EDGE -->|"LAN broadcast"| LAN
VALIDATE -.-> LOGS
LIMIT -.-> LOGS
DRIVER -.-> LOGS
classDef source fill:#ecfdf5,stroke:#14b8a6,color:#0f172a
classDef core fill:#0f172a,stroke:#2dd4bf,color:#f8fafc
classDef edge fill:#eff6ff,stroke:#3b82f6,color:#0f172a
class PAM,LAN source
class UDP,VALIDATE,LIMIT,MAP,DRIVER core
class EDGE edge
The standard magic packet contains the destination MAC address, but it does not carry a FortiGate interface, VDOM, or routed destination network. WOLT therefore uses the UDP destination port on its own host as an explicit and deterministic network selector. The listener record selected by that port contains:
- the only PAM/guacd source IP allowed to use it;
- the edge Device and its VDOM-scoped credential;
- the exact FortiGate interface;
- the destination broadcast IP.
Three port numbers appear in the end-to-end path and must not be confused:
| Port | Path | Purpose |
|---|---|---|
40016/UDP (example) |
PAM / guacd → WOLT | Listener and mapping key; configurable in WOLT and the PAM connection |
22/TCP (default) |
WOLT → FortiGate | SSH management connection configured on the Device |
9/UDP |
FortiGate → destination LAN | Native Wake-on-LAN datagram generated by the FortiGate command |
The incoming listener port is never copied into the FortiGate command.
| Incoming request | WOLT mapping | Native action |
|---|---|---|
WOLT_HOST:40016/UDP |
Device A + demo-vlan-16 + 198.51.100.255 |
SSH to Device A, then send UDP/9 on VLAN 16 |
WOLT_HOST:40067/UDP |
Device B + demo-vlan-67 + 203.0.113.255 |
SSH to Device B, then send UDP/9 on VLAN 67 |
This convention lets a PAM connection select the destination network by using a dedicated UDP port while remaining unaware of the edge-device CLI.
- In Settings, choose the Docker-published UDP range above port 1024; expose the same range in the host firewall, or use the optional Host Agent to manage UFW.
- In Devices, create the VDOM-restricted FortiGate connection and confirm its SSH fingerprint.
- In Listeners, allocate one UDP port per destination network and map it to the Device, interface, broadcast IP, and PAM/guacd source IP.
- In Guacamole or the PAM platform, set the WOLT server address and the exact UDP port assigned to that listener.
- Enable the listener and engine, send a test wake, and verify the Events page.
WOLT v1.0 supports the system-interface syntax documented for the FortiOS 7.2, 7.4, and 7.6 release branches:
execute wake-on-lan <interface> <host-mac> <protocol> <port> <ip> <password>
WOLT uses protocol 2 (UDP), destination port 9, the listener's broadcast IP, and no
SecureOn password:
execute wake-on-lan <interface> <host-mac> 2 9 <broadcast-ip>
Before registering the Device, log in with the exact restricted service account and run this safe-shaped verification command using a real interface and broadcast IP from its VDOM:
execute wake-on-lan <interface> 02:00:00:00:00:01 2 9 <broadcast-ip>
02:00:00:00:00:01 is a locally administered example MAC; confirm that it is unused in
the destination LAN before testing. A permission, parse, or unknown-command error means the account
or FortiOS variant is not ready for WOLT. A successful command commonly produces little or
no output. WOLT v1.0 does not claim compatibility with FortiSwitch syntax, which adds an
interface_type argument, or with untested FortiOS branches. Confirm availability on the
appliance with execute wake-on-lan ? before deployment. See the official
FortiOS 7.2 CLI reference,
FortiOS 7.4 CLI reference,
and FortiOS 7.6 CLI reference.
- The container runs as the non-root user
woltwith UID/GID10001. - The FortiGate account should be restricted to its assigned VDOM and the required wake command.
- The tested FortiGate profile requires CLI Execute: Enabled, Network Group: Custom, and Packet Capture: Read/Write; permission labels may vary by FortiOS version.
- WOLT uses a direct SSH
exec_command; it does not open an interactive shell. config vdom,edit, andendare not sent because the service account is already scoped to its working VDOM.- Unknown or changed SSH host keys are rejected.
- SSH agent forwarding and discovery of arbitrary client keys are disabled.
.env, live mappings, andknown_hostsare excluded from Git.- Passwords, complete payloads, and environment dumps are never logged.
- A malformed packet or failed SSH request does not terminate its listener.
Multi-VDOM deployment rule: one WOLT Device represents one FortiGate administrative
VDOM scope. If the required interfaces are distributed across different VDOMs, create a
separate WOLT Device and a separate least-privilege FortiGate service account restricted to
each VDOM. Do not use a global or super_admin account. WOLT intentionally does not send
config vdom, edit, execute enter, or other VDOM-switching commands in this mode.
- Linux host with Docker Engine and Docker Compose v2
- Network access from the host to FortiGate SSH
- UDP access from Guacamole/guacd to the selected WOLT listener ports
- FortiGate service account with least-privilege access to the required VDOM
- OpenSSH client utilities for
ssh-keyscan
Published images support linux/amd64 and linux/arm64.
Every GitHub release contains the exact installer used for that version and its checksum. To review and verify it before execution:
curl -fLO https://github.com/runovelhq/wolt/releases/download/v1.1.3/install.sh
curl -fLO https://github.com/runovelhq/wolt/releases/download/v1.1.3/install.sh.sha256
sha256sum --check install.sh.sha256
sudo bash install.sh --install-dir /data/WOLTAfter installation, /data/WOLT contains only .env.web, the two Compose files,
VERSION, certs/, and the managed runtime/ directory. PostgreSQL remains in its Docker
volume; database and runtime rollback files remain under /var/lib/wolt-agent. Git is not
required on the server.
The generated .env.web is mode 600 and contains the database password, first-run
bootstrap token, and master encryption key. Back it up in a secure password-management
system; never commit it. Normal Device, Listener, SMTP, retention, and engine settings are
managed in the UI.
| Variable | Required | Default | Description |
|---|---|---|---|
POSTGRES_PASSWORD |
Yes | generated | PostgreSQL credential |
WOLT_BOOTSTRAP_TOKEN |
First setup | generated | One-time authorization for creating the first Owner |
WOLT_MASTER_KEY |
Yes | generated | Encrypts Device and SMTP credentials; loss makes them unrecoverable |
WOLT_WEB_PORT |
No | 8080 |
Web UI and API TCP port |
WOLT_SESSION_SECURE |
Production HTTPS | false |
Requires Secure browser cookies when set to true |
WOLT_SESSION_HOURS |
No | 12 |
Server-side session lifetime |
WOLT_SMTP_CA_FILE |
No | system trust store | PEM CA bundle used in addition to public system CAs for SMTP TLS |
WOLT_UDP_PUBLISHED_START |
No | 40000 |
First Docker-published listener port; must be at least 1024 |
WOLT_UDP_PUBLISHED_END |
No | 40099 |
Last Docker-published listener port; maximum range size is 100 |
WOLT_IMAGE |
Installer-managed | verified digest | Immutable image digest selected by installation or UI upgrade |
For a connection assigned to listener 40016:
Wake-on-LAN MAC Address: 02:AA:BB:CC:DD:16
Wake-on-LAN Broadcast Address: <WOLT server IP>
Wake-on-LAN UDP Port: 40016
Wake-on-LAN Wait Time: 30
The Guacamole broadcast-address field points to the WOLT server—not the destination LAN. The listener's Allowed PAM / guacd source IP must be the address actually observed from the guacd host. The random UDP source port is irrelevant; WOLT validates the source IP and the destination listener port.
docker compose --env-file .env.web -f compose.web.yml -f compose.host-agent.yml ps
docker compose --env-file .env.web -f compose.web.yml -f compose.host-agent.yml logs -f appsudo ss -lunp | grep -E '40016|40067'sudo tcpdump -ni any 'udp portrange 40000-40099' -s0 -vvv -XXOwners can select a canonical release in Settings → Updates. WOLT verifies the keyless
Cosign identity of the official GitHub release workflow, pins the verified digest, backs up
PostgreSQL and the managed host runtime, refreshes the Compose/Host Agent files, performs a
health check, and restarts the Host Agent. .env.web, certs/, database data, and existing
backups are preserved. A failed health check restores the previous runtime, image, and
database automatically; the previous successful deployment can also be rolled back from the
same page.
| Event or reason | Meaning |
|---|---|
listener_started |
UDP listener successfully bound |
wol_request_received |
Valid packet received from the allowed source |
wol_request_rate_limited |
Repeated port/MAC request rejected during cooldown |
source_not_allowed |
Packet source does not match the configured guacd IP |
invalid_magic_packet |
Payload length, header, or repeated MAC pattern is invalid |
ssh_authentication_failed |
FortiGate rejected the configured credentials |
host_key_verification_failed |
FortiGate host key is unknown or changed |
ssh_timeout |
Connection or command exceeded its timeout |
command_failed |
FortiGate returned a non-zero command result |
fortigate_wol_success |
Native wake command completed successfully |
Run the isolated container test target:
docker build --target test -t wolt:test .
docker run --rm wolt:testOr use Python 3.12 locally:
python -m venv .venv
. .venv/bin/activate
pip install -r requirements-dev.txt
pytest -qThe CI workflow runs the tests, performs a production-image build, and scans the repository for committed secrets. Semantic version tags publish multi-architecture images with provenance and an SBOM.
- Web foundation: Vue shell, management API, PostgreSQL, and migrations — implemented on
main - First-run Owner setup, authentication, recovery, and session management — implemented in v1.0
- Device and listener CRUD with live validation and engine reconciliation — implemented in Phase 4
- Event explorer, dashboard analytics, CSV export, audit history, and retention jobs — implemented in Phase 5
- Configurable active/published UDP ranges, build metadata, FortiGate permission guidance, favicon, and UI readability improvements — implemented in Phase 5.1
- Owner-managed users and sessions, enforced Administrator/Operator roles, encrypted SMTP, invitations, and email password reset — implemented in Phase 6
- Restricted Host Agent, managed UFW ingress, published-range recreation, signed digest-pinned upgrades, health verification, and rollback — implemented in Phase 7
- Phase 8 after v1.0: optional external SQL Server compatibility suite and deployment profile
- Phase 9 after v1.0: additional edge-device providers and provider-specific command profiles
- Encrypted device credentials — implemented in Phase 4; encrypted SMTP credentials implemented in Phase 6
- Schema-driven edge-device providers beyond FortiGate
See Product UX, Technical Architecture, UI Design Specification, Phase 4 Operations, Phase 5 Observability, Phase 5.1 Hardening, Phase 6 Identity & Email, and Phase 7 Host Operations.
Read CONTRIBUTING.md before opening a pull request. Security issues must follow SECURITY.md and must not be reported with live credentials or internal network details.
WOLT is released under the MIT License.
