Proxy/ConfigLoader.ps1

function Save-FRPConfig {
    [CmdletBinding()]  
    param(
        [Alias('s')]
        [Parameter(Mandatory)]
        [string]$Secret,

        [Alias('i')]
        [Parameter(Mandatory)]
        [string]$Identity,

        [Alias('r')]
        [Parameter(Mandatory)]
        [string]$Remote,

        [Alias('p')]
        [string]$Path = (Join-Path -Path $InstallationPath -ChildPath "config.yaml")
    )

    # Импортируем модуль powershell-yaml
    try {
        Import-Module powershell-yaml -ErrorAction Stop
    }
    catch {
        Write-Error "Error loading module powershell-yaml: $_"
        return
    }

    # Формируем данные и конвертируем в YAML
    $data = @{
        secret   = $Secret
        identity = $Identity
        remote   = $Remote
    }

    try {
        $yaml = $data | ConvertTo-Yaml
        $yaml | Out-File -FilePath $Path -Encoding utf8
        Write-Verbose "Config saved to '$Path'"
    }
    catch {
        Write-Error "Error converting to YAML: $_"
    }
}

function Get-FRPConfig {
    [CmdletBinding()]  
    param(
        [Alias('p')]
        [Parameter(Mandatory)]
        [string]$Path
    )

    # Проверяем, что файл существует
    if (-not (Test-Path -Path $Path -PathType Leaf)) {
        Throw "Файл не найден: $Path"
    }

    # Импортируем модуль powershell-yaml
    try {
        Import-Module powershell-yaml -ErrorAction Stop
    }
    catch {
        Write-Error "Error loading module powershell-yaml: $_"
        return
    }

    try {
        # Читаем содержимое файла и конвертируем из YAML
        $yamlContent = Get-Content -Path $Path -Raw -Encoding utf8
        $config = $yamlContent | ConvertFrom-Yaml
        return $config
    }
    catch {
        Write-Error "Error converting to YAML: $_"
    }
}

function Get-InitConfig {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [string]$Path
    )

    # Ensure the 'powershell-yaml' module is available
    if (-not (Get-Module -ListAvailable -Name powershell-yaml)) {
        throw "Module 'powershell-yaml' is required. Install it via 'Install-Module powershell-yaml'."
    }
    Import-Module powershell-yaml

    # Session cache: @{ Path => @{ LastWriteTime, Data } }
    if (-not $script:__YamlCache) { $script:__YamlCache = @{} }

    $resolvedPath = (Resolve-Path $Path).Path
    $lastWrite    = (Get-Item $resolvedPath).LastWriteTimeUtc

    # Return cached if unchanged
    if ($script:__YamlCache.ContainsKey($resolvedPath)) {
        $entry = $script:__YamlCache[$resolvedPath]
        if ($entry.LastWriteTime -eq $lastWrite) {
            return $entry.Data
        }
    }

    # Parse YAML
    $raw    = Get-Content $resolvedPath -Raw
    $parsed = ConvertFrom-Yaml $raw

    # Build custom macros from 'variables:' section
    $macros = @{}
    if ($parsed.variables) {
        foreach ($var in $parsed.variables) {
            if ($var.name -and $var.value) {
                $val = [Environment]::ExpandEnvironmentVariables($var.value)
                $macros[$var.name] = $val
            }
        }
    }

    # Recursive expander: ENV vars, custom macros, then normalize paths
    function Expand-Values($obj) {
        if ($obj -is [hashtable]) {
            $h = @{}
            foreach ($k in $obj.Keys) {
                if ($k -eq 'variables') { continue }
                $h[$k] = Expand-Values($obj[$k])
            }
            return $h
        }
        elseif ($obj -is [System.Collections.IEnumerable] -and -not ($obj -is [string])) {
            return $obj | ForEach-Object { Expand-Values($_) }
        }
        elseif ($obj -is [string]) {
            # 1) Expand Windows ENV vars (%VAR%)
            $s = [Environment]::ExpandEnvironmentVariables($obj)

            # 2) Expand each custom macro
            foreach ($name in $macros.Keys) {
                $val     = $macros[$name]
                $nameEsc = [regex]::Escape($name)

                # Windows style: %MacroName%
                $patternWin = [regex]::Escape("%$name%")
                $s = $s -replace $patternWin, $val

                # UNIX style: ${MacroName}
                $patternBr = [regex]::Escape('${') + $nameEsc + [regex]::Escape('}')
                $s = $s -replace $patternBr, $val

                # UNIX style: $MacroName (word boundary after)
                $patternSi = [regex]::Escape('$') + $nameEsc + '\b'
                $s = $s -replace $patternSi, $val
            }

            # 3) Normalize path separators and resolve
            # a) Turn all forward-slashes into backslashes
            $s = $s -replace '/', [System.IO.Path]::DirectorySeparatorChar
            # b) Let GetFullPath clean up things like '..'
            try {
                $s = [System.IO.Path]::GetFullPath($s)
            } catch {
                # leave as-is if not a valid path
            }

            return $s
        }
        else {
            return $obj
        }
    }

    $expanded = Expand-Values($parsed)

    # Cache and return
    $script:__YamlCache[$resolvedPath] = @{
        LastWriteTime = $lastWrite
        Data          = $expanded
    }

    return $expanded
}