param( [string]$InstallRoot = "C:\Program Files\VpnProxy\sing-box", [string]$BinaryPath = "", [string]$ServiceName = "VpnProxySingBox", [switch]$PlanOnly, [switch]$Force ) $ErrorActionPreference = "Stop" function New-Result { param( [bool]$Success, [string]$Action, [bool]$Changed, [string]$Message, [hashtable]$Details = @{} ) [ordered]@{ success = $Success action = $Action changed = $Changed message = $Message details = $Details } | ConvertTo-Json -Depth 6 } function Test-IsAdministrator { $identity = [Security.Principal.WindowsIdentity]::GetCurrent() $principal = [Security.Principal.WindowsPrincipal]::new($identity) $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) } function Backup-File { param([string]$Path) if (Test-Path -LiteralPath $Path) { $backup = "$Path.bak" Copy-Item -LiteralPath $Path -Destination $backup -Force return $backup } return $null } try { $details = @{ installRoot = $InstallRoot binaryPath = $BinaryPath serviceName = $ServiceName planOnly = [bool]$PlanOnly } if ($PlanOnly) { New-Result -Success $true -Action "install-singbox" -Changed $false -Message "Local sing-box install plan is ready." -Details $details exit 0 } if (-not (Test-IsAdministrator)) { New-Result -Success $false -Action "install-singbox" -Changed $false -Message "Administrator rights are required." -Details $details exit 1 } if ([string]::IsNullOrWhiteSpace($BinaryPath) -or -not (Test-Path -LiteralPath $BinaryPath)) { New-Result -Success $false -Action "install-singbox" -Changed $false -Message "BinaryPath is required and must point to sing-box.exe." -Details $details exit 2 } $changed = $false if (-not (Test-Path -LiteralPath $InstallRoot)) { New-Item -ItemType Directory -Path $InstallRoot -Force | Out-Null $changed = $true } $configPath = Join-Path $InstallRoot "config.json" $backupPath = Backup-File -Path $configPath if ($backupPath) { $details.backupPath = $backupPath } $markerPath = Join-Path $InstallRoot "install-singbox.marker.json" if ((-not (Test-Path -LiteralPath $markerPath)) -or $Force) { @{ component = "singbox" binaryPath = $BinaryPath serviceName = $ServiceName installedAt = (Get-Date).ToString("o") } | ConvertTo-Json -Depth 4 | Set-Content -LiteralPath $markerPath -Encoding UTF8 $changed = $true } $details.markerPath = $markerPath New-Result -Success $true -Action "install-singbox" -Changed $changed -Message "Local sing-box install boundary completed." -Details $details } catch { New-Result -Success $false -Action "install-singbox" -Changed $false -Message $_.Exception.Message exit 1 }