- Creates dirs:
data/storage,data/shared/scripts,data/shared/config,data/shared/terminals,data/oem - Checks
/dev/kvmexists - Downloads
win.isoif missing - Copies
scripts/oem-install.bat→data/oem/install.bat - Copies
scripts/install.bat→data/shared/scripts/install.bat - Copies
scripts/start.bat→data/shared/scripts/start.bat - Copies
scripts/debloat.bat→data/shared/scripts/debloat.bat - Copies
scripts/defender-remover/→data/shared/scripts/defender-remover/ - Copies
config/*→data/shared/config/ - Copies
mt5installers/mt5setup-*.exe→data/shared/terminals/ - Copies
mt5api/→data/shared/mt5api/ - Clears stale lock dirs:
install.running,start.running - If
data/storage/data.imgmissing (fresh VM) → clears all*.doneflags - Generates
.envwithAPI_PORT_RANGEfromconfig/terminals.json - Starts docker-compose (
dockurr/windowsimage) - Waits for VM IP, sets up iptables port forwarding
- dockurr/windows runs its own setup, mounts
/oemand runsdata/oem/install.bat - Our
oem-install.batcreates a startup folder entry:- Path:
C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp\start.bat - Content:
@echo off+call "C:\Users\Docker\Desktop\Shared\scripts\start.bat"
- Path:
- Windows finishes OOBE, reboots into desktop
NOTE: We use the startup folder here (not schtasks) because Task Scheduler may not be running during OOBE. install.bat will later replace this with a schtask and delete the startup entry.
- Sets variables: SHARED, SCRIPTS, CONFIG, BROKERS (=terminals), LOGDIR, logs
- Sets
PYDIR=C:\Program Files\Python312, prepends to PATH - Creates
%SHARED%\start.runninglock dir (atomic mkdir)- If lock exists → "Another start.bat is already running" → exit 0
- Logs
====== Boot ======to both start.log and install.log - Logs "Running install.bat..." to start.log
- Calls install.bat
-
Creates
%SHARED%\install.runninglock -
Logs header
-
[1/9] Admin + UAC
net localgroup Administrators Docker /add(ensure Docker is admin)- Sets ConsentPromptBehaviorAdmin=0, PromptOnSecureDesktop=0 (immediate)
- Checks registry: EnableLUA != 0x0 → sets EnableLUA=0, NEEDS_REBOOT=1
- No flag file — checks actual registry value each time
-
[2/9] App Execution Aliases
- Deletes fake python registry entries + WindowsApps executables
-
[3/9] Custom hosts + Windows Update
- Appends
config/hoststo Windows hosts file (if marker missing) sc config wuauserv start= disabled+sc stop wuauserv
- Appends
-
[4/9] Schtask + startup cleanup
- Creates MT5Start schtask if missing (onlogon, HIGHEST privilege)
- No reboot — schtask works immediately
- Deletes all startup folder entries (OEM + legacy)
-
[5/9] Python
- Checks
if exist "%PYDIR%\python.exe"(NOTpython --version) - If missing: downloads python-3.12.7-amd64.exe, installs, verifies
- No reboot needed — continues to next step
- Checks
-
[6/9] MetaTrader5 pip package
python -m pip install --upgrade pippython -m pip install MetaTrader5- Strict error checking on both
-
[7/9] Debloat
- If
debloat.donemissing: runs debloat.bat, creates flag, NEEDS_REBOOT=1 - If
debloat.doneexists: skip - Only flag file in the system (debloat is destructive, shouldn't re-run)
- If
-
[8/9] MT5 terminals
- Migrates old flat-layout dirs to
base\if needed - For each
mt5setup-*.exe: installs if<broker>/base/terminal64.exemissing - Runs EVERY boot — picks up new installers automatically
- Verifies at least one terminal is installed, EXIT /B 1 if none
- Each new install sets NEEDS_REBOOT=1
- Migrates old flat-layout dirs to
-
[9/9] Firewall
- Deletes old rule, creates new with dynamic ports from terminals.json
-
Reboot decision
- NEEDS_REBOOT=1 (UAC + debloat + MT5 all set it) → ONE reboot, exit /b 3
- NEEDS_REBOOT=0 → "Setup complete!", exit /b 0
- errorlevel = 3 → "Reboot scheduled by install.bat, stopping."
- Cleans start.running lock, exit /b 0
VM reboots — ONE time.
BOTH startup folder AND schtask may fire (if startup entry wasn't deleted on boot 1 due to the reboot). start.bat's mkdir lock ensures only one runs.
- All 9 steps run but find everything already done:
- [1/9] UAC: EnableLUA already 0x0 → skip
- [2/9] Aliases: already deleted (idempotent)
- [3/9] Hosts: marker already in file → skip
- [4/9] Schtask: already exists → skip. Startup entries: already deleted (idempotent)
- [5/9] Python:
python.exeexists → skip - [6/9] pip: already installed (quick, idempotent)
- [7/9] Debloat:
debloat.doneexists → skip - [8/9] MT5:
terminal64.exeexists for each broker → skip - [9/9] Firewall: re-creates rule (idempotent)
- NEEDS_REBOOT=0 → "Setup complete!" → exit /b 0
- errorlevel = 0 → "install.bat done."
- Pip install:
python -m pip install --quiet -r "%CONFIG%\requirements.txt"→ Installs packages from requirements.txt (flask, etc.) → Output goes to pip.log - Kill lingering MT5 terminals: taskkill terminal64.exe if running
- Verify terminals.json exists: error + exit if missing
- Parse terminals.json: python one-liner reads broker/account/port
→ Output goes to temp file
%TEMP%\mt5_terminals.txt→ Parse errors captured to%TEMP%\mt5_parse_err.txt - Launch MT5 terminals: for each entry:
→ Check
%BROKERS%\<broker>\base\terminal64.exeexists (error + abort if not) → If%BROKERS%\<broker>\<account>\terminal64.exemissing → xcopy from base → Writemt5start.ini(login/server/password from accounts.json) → Start terminal64.exe with /portable /config flags - Wait 10s for terminals to initialize
- Launch API processes: for each entry:
→ Background (
start "" cmd /c ...) for all but last → Foreground (blocking) for last one → Each runs:python -m mt5api --broker X --account Y --port Z→ Logs go toapi-<broker>-<account>.log→ Cleans lock before starting foreground API
System is now running. Foreground API keeps start.bat alive. If the foreground API crashes, start.bat exits. Schtask will re-run on next logon.
- Boot 0 → OEM creates startup entry
- Boot 1 → install.bat does EVERYTHING (UAC + schtask + Python + debloat + MT5) → ONE reboot
- Boot 2 → operational
Total: 1 reboot before operational (down from 4).
UAC state is checked via reg query ... /v EnableLUA every boot. If already
0x0, skip. No flag file needed — survives VM reinstalls correctly because the
registry resets with the new Windows install.
schtasks /create takes effect immediately. The schtask will fire on the NEXT
logon. No reboot needed just to create it.
UAC (EnableLUA=0), debloat, and MT5 installs all need a reboot. Instead of rebooting after each one, we set NEEDS_REBOOT=1 and defer to the very end. One reboot applies all changes.
Debloat is destructive and shouldn't re-run. Everything else checks actual artifacts: python.exe exists, terminal64.exe exists, schtask exists, hosts marker exists, EnableLUA registry value.
If data/storage/data.img doesn't exist, run.sh clears all *.done flags.
This ensures debloat re-runs on a fresh Windows install even if the shared
mount was preserved.
New mt5setup-*.exe files dropped into terminals/ get installed automatically
on the next boot. Already-installed brokers (where base/terminal64.exe exists)
are skipped.
Every step checks errorlevel. Any failure → log error → clean lock → exit /b 1. start.bat checks install.bat return code. Terminal launch failures abort startup.