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 Expand-Macros {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string] $Text,

        [Parameter(Mandatory)]
        [hashtable] $Variables
    )

    # Заменяем все вхождения ${VAR} на значение из $Variables
    return ($Text -replace '\$\{(\w+)\}', {
        param($match)
        $name = $match.Groups[1].Value
        if ($Variables.ContainsKey($name)) {
            return [string]$Variables[$name]
        }
        else {
            throw "Variable '$name' not found in hashtable."
        }
    })
}

function Expand-ObjectMacros {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [psobject] $Object,

        [Parameter(Mandatory)]
        [hashtable] $Variables
    )

    # Рекурсивно обходим структуры PS (словарь, массив, примитив)
    switch ($Object) {
        { $_ -is [string] } {
            return Expand-Macros -Text $Object -Variables $Variables
        }
        { $_ -is [System.Collections.IDictionary] } {
            foreach ($key in $Object.Keys) {
                $Object[$key] = Expand-ObjectMacros -Object $Object[$key] -Variables $Variables
            }
            return $Object
        }
        { $_ -is [System.Collections.IEnumerable] } {
            # Для списков и массивов
            $result = @()
            foreach ($item in $Object) {
                $result += Expand-ObjectMacros -Object $item -Variables $Variables
            }
            return $result
        }
        default {
            # Не строка, не коллекция — возвращаем как есть
            return $Object
        }
    }
}

function envsubst {
  param(
    [Parameter(ValueFromPipeline)]
    
        [string]
        $InputObject
    )

  Get-ChildItem Env: | Set-Variable
  $ExecutionContext.InvokeCommand.ExpandString($InputObject)
}

function Get-InitConfig {
    <#
    .SYNOPSIS
    Load a YAML file and return a PowerShell object.
 
    .DESCRIPTION
    Tries to use built-in ConvertFrom-Yaml (available in newer PS versions).
    If not present, tries to import the 'powershell-yaml' module.
    Returns a PSCustomObject / arrays as produced by the YAML parser.
    Optionally returns a nested Hashtable if -AsHashtable is used.
 
    .PARAMETER Path
    Path to the YAML file.
 
    .PARAMETER AsHashtable
    If set, return a nested hashtable structure instead of PSObjects.
 
    .PARAMETER ThrowOnError
    If set, throw exceptions on error instead of writing errors and returning $null.
    #>


    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [string]$Path,

        [switch]$AsHashtable,

        [switch]$ThrowOnError
    )

    function Convert-ToHashtable {
        param($InputObject)

        if ($null -eq $InputObject) { return $null }

        if ($InputObject -is [System.Collections.Hashtable]) { return $InputObject }

        if ($InputObject -is [System.Collections.IDictionary]) {
            $ht = @{}
            foreach ($k in $InputObject.Keys) {
                $ht[$k] = Convert-ToHashtable $InputObject[$k]
            }
            return $ht
        }

        # Enumerable (array/list) but not string
        if ($InputObject -is [System.Collections.IEnumerable] -and -not ($InputObject -is [string])) {
            $list = @()
            foreach ($item in $InputObject) {
                $list += (Convert-ToHashtable $item)
            }
            return $list
        }

        # PSObject / PSCustomObject
        if ($InputObject -is [PSObject] -or $InputObject -is [System.Management.Automation.PSObject]) {
            $ht = @{}
            foreach ($p in $InputObject.psobject.Properties) {
                $ht[$p.Name] = Convert-ToHashtable $p.Value
            }
            return $ht
        }

        # Scalar value
        return $InputObject
    }

    try {
        $resolved = Resolve-Path -LiteralPath $Path -ErrorAction Stop
        $yamlText = Get-Content -LiteralPath $resolved -Raw -ErrorAction Stop
    }
    catch {
        $msg = "Cannot read file '$Path': $($_.Exception.Message)"
        if ($ThrowOnError) { throw $msg } else { Write-Error $msg; return $null }
    }

    # choose parser
    try {
        if (Get-Command -Name ConvertFrom-Yaml -ErrorAction SilentlyContinue) {
            $obj = $yamlText | ConvertFrom-Yaml
        }
        elseif (Get-Module -ListAvailable -Name powershell-yaml) {
            Import-Module powershell-yaml -ErrorAction Stop
            $obj = ConvertFrom-Yaml $yamlText
        }
        else {
            $msg = "No YAML parser available. Use a PowerShell version with ConvertFrom-Yaml or install the module 'powershell-yaml': 'Install-Module -Name powershell-yaml -Scope CurrentUser -Force'"
            if ($ThrowOnError) { throw $msg } else { Write-Error $msg; return $null }
        }
    }
    catch {
        $msg = "Error parsing YAML: $($_.Exception.Message)"
        if ($ThrowOnError) { throw $msg } else { Write-Error $msg; return $null }
    }

    if ($AsHashtable) {
        return Convert-ToHashtable $obj
    }
    else {
        return $obj
    }
}