#
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("
| Plugin DLL | $(H($p.Dll)) |
")
[void]$sb.Append("| Loopback RPC | http://127.0.0.1:$($p.Port)/rpc |
")
[void]$sb.Append("| Token | $(H($p.Token)) |
")
foreach ($k in $p.Props.Keys) { [void]$sb.Append("| $(H($k)) | $(H([string]$p.Props[$k])) |
") }
if ($p.Detail) { [void]$sb.Append("| Verify detail | $(H($p.Detail)) |
") }
[void]$sb.Append("
")
}
# 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 SSH | TCP/22 from $HonorProIP only (firewall rule “HonorPro SSH”) |
| Plugin ports in firewall | none — 8091/8092/8093 stay loopback |
Send to HonorPro
| SSH host / IP | this server's public IP (fill in) |
| SSH port / user | 22 / $(H($SshUser)) |
| MT5 build / API | fill in / $SdkApiVersion |
| Ports & tokens | as listed per plugin above |
| Test/demo account | a 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 {}