<# Setup-HonorPro.ps1 - all-in-one installer + verifier + settings report ========================================================================= Run ONCE, elevated (Run as Administrator), on the MetaTrader 5 Windows server, from the folder that holds the three HonorPro plugin DLLs: honorpro-bridge.dll honorpro-copy-trading.dll honorpro-pick-the-level.dll .\Setup-HonorPro.ps1 -PluginsDir "C:\Path\To\MT5\Plugins" It will, in one pass: 1. INSTALL - copy the three DLLs into the MT5 Plugins directory and set their tokens + ports as machine environment variables (the plugins read env first, so nothing is typed by hand). 2. ACCESS - install OpenSSH, create a forwarding-only user locked to the three loopback ports with HonorPro's key, harden sshd, and open inbound SSH from HonorPro's IP only. 3. (you enable the 3 plugins in the MT5 Administrator and restart the server component - the one step a script cannot do in the GUI; the script waits for you here unless -SkipWait is given.) 4. VERIFY - call each plugin's loopback RPC and confirm it answers. 5. REPORT - write honorpro-settings.html: every plugin property and setting, the SSH/firewall config, the verification result, and the exact values to send HonorPro. Opens it at the end. No build step anywhere: the DLLs are shipped pre-compiled for MT5 Server API v6090. #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string] $PluginsDir, [string] $DllDir = $PSScriptRoot, [string] $SshUser = "hnr", [string] $HonorProIP = "78.141.244.255", [int] $BridgePort = 8091, [int] $CopyPort = 8092, [int] $PtlPort = 8093, [string] $BridgeToken = "", [string] $CopyToken = "", [string] $PtlToken = "", [switch] $NoPtl, # skip Pick The Level [switch] $SkipWait # do not pause for the Administrator step (verify may then fail until you enable+restart) ) $ErrorActionPreference = "Stop" $SdkApiVersion = "6090" $HonorProPubKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICVhQNC74YcXfHC1xub2cS7XjljejUa5i3hCFgbQiR6d vps-to-mt5-bridge" function New-Token { $b = New-Object 'System.Byte[]' 32 [System.Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($b) return ([Convert]::ToBase64String($b)) -replace '[+/=]', '' } function Set-MachineEnv([string]$k,[string]$v){ [Environment]::SetEnvironmentVariable($k,$v,"Machine") } function Step($m){ Write-Host "== $m ==" -ForegroundColor Cyan } function Ok($m){ Write-Host " $m" -ForegroundColor Green } function Info($m){ Write-Host " $m" } function Warn($m){ Write-Host " $m" -ForegroundColor Yellow } # ---- admin check ------------------------------------------------------------ $principal = New-Object System.Security.Principal.WindowsPrincipal([System.Security.Principal.WindowsIdentity]::GetCurrent()) if (-not $principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)) { throw "Run this in an elevated PowerShell (Run as Administrator)." } if (-not (Test-Path $PluginsDir)) { throw "Plugins directory not found: $PluginsDir" } # ---- token setup ------------------------------------------------------------ if (-not $BridgeToken) { $BridgeToken = New-Token } if (-not $CopyToken) { $CopyToken = New-Token } if (-not $NoPtl -and -not $PtlToken) { $PtlToken = New-Token } # Plugin catalogue: name, dll, port, token, and the full property set for the report. $plugins = @() $plugins += [ordered]@{ Name = "Bridge"; Dll = "honorpro-bridge.dll"; Port = $BridgePort; Token = $BridgeToken Props = [ordered]@{ "BRIDGE_TOKEN" = "(set - see handoff)" "BRIDGE_PORT" = "$BridgePort" "Bind address" = "127.0.0.1 (fixed, loopback only)" "BRIDGE_DEALER_LOGIN" = "0 (server chooses; set a dealer login to attribute trades)" "BRIDGE_LOG_LEVEL" = "info" "BRIDGE_IO_TIMEOUT_MS" = "30000" "BRIDGE_THREAD_POOL_SIZE"= "8" "BRIDGE_MAX_CONNECTIONS" = "64" "BRIDGE_STREAM_QUEUE_MAX"= "512" } } $plugins += [ordered]@{ Name = "Copy-Trading"; Dll = "honorpro-copy-trading.dll"; Port = $CopyPort; Token = $CopyToken Props = [ordered]@{ "COPY_TOKEN" = "(set - see handoff; must differ from Bridge)" "COPY_PORT" = "$CopyPort" "COPY_BIND" = "127.0.0.1 (loopback; non-loopback refused)" "Dealer Login" = "0 (optional attribution)" "Ignore Rival Engines"= "off (leave off unless another copy engine runs)" "Data Directory" = "default" "Copy Workers" = "default" "Broker Fee Share" = "default" "Stats Refresh Sec" = "default" "Copy Remotes" = "(none)" } } if (-not $NoPtl) { $plugins += [ordered]@{ Name = "Pick The Level"; Dll = "honorpro-pick-the-level.dll"; Port = $PtlPort; Token = $PtlToken Props = [ordered]@{ "PTL_TOKEN" = "(set - see handoff; must differ from the others)" "PTL_PORT" = "$PtlPort" "Bind address" = "127.0.0.1 (loopback only)" "PTL_JOURNAL" = "ptl-journal.jsonl" "PTL_MAX_TICK_AGE_MS" = "5000" } } } # ============================ 1. INSTALL ===================================== Step "1/5 Installing plugin binaries" foreach ($p in $plugins) { $src = Join-Path $DllDir $p.Dll if (-not (Test-Path $src)) { throw "Missing DLL: $src (put the shipped DLLs beside this script)" } Copy-Item $src -Destination (Join-Path $PluginsDir $p.Dll) -Force Ok ("copied {0}" -f $p.Dll) } Set-MachineEnv "BRIDGE_TOKEN" $BridgeToken; Set-MachineEnv "BRIDGE_PORT" "$BridgePort" Set-MachineEnv "COPY_TOKEN" $CopyToken; Set-MachineEnv "COPY_PORT" "$CopyPort" if (-not $NoPtl) { Set-MachineEnv "PTL_TOKEN" $PtlToken; Set-MachineEnv "PTL_PORT" "$PtlPort" } Ok "tokens + ports written as machine environment variables" # ============================ 2. ACCESS ====================================== Step "2/5 SSH access + firewall" $cap = Get-WindowsCapability -Online -Name "OpenSSH.Server*" if ($cap.State -ne "Installed") { Add-WindowsCapability -Online -Name $cap.Name | Out-Null; Ok "OpenSSH Server installed" } Set-Service -Name sshd -StartupType Automatic; Start-Service sshd if (-not (Get-LocalUser -Name $SshUser -ErrorAction SilentlyContinue)) { $pw = ConvertTo-SecureString (New-Token) -AsPlainText -Force New-LocalUser -Name $SshUser -Password $pw -FullName "HonorPro tunnel" ` -Description "Restricted port-forwarding account for HonorPro" ` -PasswordNeverExpires -UserMayNotChangePassword | Out-Null Ok "created key-only user '$SshUser'" } else { Info "user '$SshUser' already exists" } $permit = @("127.0.0.1:$BridgePort","127.0.0.1:$CopyPort") if (-not $NoPtl) { $permit += "127.0.0.1:$PtlPort" } $permitClause = ($permit | ForEach-Object { "permitopen=`"$_`"" }) -join "," $sshDir = "C:\Users\$SshUser\.ssh"; New-Item -ItemType Directory -Force -Path $sshDir | Out-Null $akPath = Join-Path $sshDir "authorized_keys" Set-Content -Path $akPath -Value "restrict,port-forwarding,$permitClause $HonorProPubKey" -Encoding ascii icacls $akPath /inheritance:r /grant "${SshUser}:R" "SYSTEM:F" "Administrators:F" | Out-Null Ok "installed HonorPro key (forwarding-only: $($permit -join ', '))" $sshdConfig = "C:\ProgramData\ssh\sshd_config" if ((Test-Path $sshdConfig) -and -not (Select-String -Path $sshdConfig -SimpleMatch "# HonorPro tunnel user" -Quiet)) { $block = "`n# HonorPro tunnel user`nMatch User $SshUser`n AllowTcpForwarding yes`n PermitOpen $($permit -join ' ')`n AllowAgentForwarding no`n X11Forwarding no`n PermitTTY no`n ForceCommand echo 'port forwarding only'`n" Add-Content -Path $sshdConfig -Value $block; Restart-Service sshd Ok "hardened sshd for '$SshUser'" } Get-NetFirewallRule -DisplayName "HonorPro SSH" -ErrorAction SilentlyContinue | Remove-NetFirewallRule -ErrorAction SilentlyContinue New-NetFirewallRule -DisplayName "HonorPro SSH" -Direction Inbound -Protocol TCP -LocalPort 22 -RemoteAddress $HonorProIP -Action Allow | Out-Null Ok "firewall: inbound TCP/22 from $HonorProIP only (plugin ports stay loopback)" # ============================ 3. ADMINISTRATOR STEP ========================== Step "3/5 Enable the plugins in the MT5 Administrator" Info "Open the MT5 Administrator -> Plugins -> Add, enable each HonorPro plugin" Info "(no parameters needed - they read the environment variables just set)," Info "then RESTART the affected MT5 server component so they load." if (-not $SkipWait) { Read-Host "Press Enter once the plugins are enabled and the server is restarted" | Out-Null } # ============================ 4. VERIFY ====================================== Step "4/5 Verifying each plugin on loopback" foreach ($p in $plugins) { $p.Status = "unknown"; $p.Detail = "" try { $r = Invoke-RestMethod -Method Post -Uri ("http://127.0.0.1:{0}/rpc" -f $p.Port) ` -Headers @{ Authorization = "Bearer $($p.Token)" } -ContentType "application/json" ` -Body '{"op":"status"}' -TimeoutSec 10 if ($r.ok) { $p.Status = "OK"; Ok ("{0}: status answered" -f $p.Name) } else { $p.Status = "reachable"; $p.Detail = ($r | ConvertTo-Json -Compress); Warn ("{0}: {1}" -f $p.Name,$p.Detail) } } catch { $p.Status = "FAILED"; $p.Detail = $_.Exception.Message Warn ("{0}: {1}" -f $p.Name, $p.Detail) } } # ============================ 5. REPORT ====================================== Step "5/5 Writing settings document" function H([string]$s){ [System.Web.HttpUtility]::HtmlEncode($s) } Add-Type -AssemblyName System.Web $badge = @{ "OK"="#137333;background:#e6f4ea"; "reachable"="#8a6d00;background:#fef7e0"; "FAILED"="#a50e0e;background:#fce8e6"; "unknown"="#5f6368;background:#f1f3f4" } $sb = New-Object System.Text.StringBuilder [void]$sb.Append(@" HonorPro MT5 Plugin Settings

HonorPro MT5 — plugin settings & verification

Generated $(H((Get-Date -Format u))) · MT5 Server API v$SdkApiVersion · this server: $(H($env:COMPUTERNAME))

All three plugins bind 127.0.0.1 only and require a bearer token. HonorPro reaches them through an SSH tunnel from $HonorProIP; nothing is exposed to the network. This file contains secret tokens — share it only with HonorPro, over a secure channel.
"@) foreach ($p in $plugins) { $col = $badge[$p.Status] [void]$sb.Append("

$(H($p.Name))   $(H($p.Status))

") [void]$sb.Append("") [void]$sb.Append("") [void]$sb.Append("") foreach ($k in $p.Props.Keys) { [void]$sb.Append("") } if ($p.Detail) { [void]$sb.Append("") } [void]$sb.Append("
Plugin DLL$(H($p.Dll))
Loopback RPChttp://127.0.0.1:$($p.Port)/rpc
Token$(H($p.Token))
$(H($k))$(H([string]$p.Props[$k]))
Verify detail$(H($p.Detail))
") } # SSH + firewall + handoff $permitList = ($permit | ForEach-Object { "$_" }) -join ", " [void]$sb.Append(@"

SSH access

User$(H($SshUser)) (key-only, forwarding-only)
Allowed forwards$permitList
HonorPro public key$(H($HonorProPubKey))
Inbound SSHTCP/22 from $HonorProIP only (firewall rule “HonorPro SSH”)
Plugin ports in firewallnone — 8091/8092/8093 stay loopback

Send to HonorPro

SSH host / IPthis server's public IP (fill in)
SSH port / user22 / $(H($SshUser))
MT5 build / APIfill in / $SdkApiVersion
Ports & tokensas listed per plugin above
Test/demo accounta login we may use to validate (fill in)
"@) $reportPath = Join-Path $PSScriptRoot "honorpro-settings.html" Set-Content -Path $reportPath -Value $sb.ToString() -Encoding utf8 Ok "settings document: $reportPath" $allOk = ($plugins | Where-Object { $_.Status -ne "OK" }).Count -eq 0 Write-Host "" if ($allOk) { Write-Host "ALL PLUGINS VERIFIED. Send honorpro-settings.html to HonorPro." -ForegroundColor Green } else { Write-Host "Some plugins did not verify - open the report, enable+restart in the Administrator, and re-run with -SkipWait." -ForegroundColor Yellow } try { Start-Process $reportPath } catch {}