function Write-Info($Message) { Write-Host "[INFO] $Message" -ForegroundColor Green } function Write-Warn($Message) { Write-Host "[WARN] $Message" -ForegroundColor Yellow } function Write-Err($Message) { Write-Host "[ERROR] $Message" -ForegroundColor Red } function Test-Dependencies { if (-not (Get-Command docker -ErrorAction SilentlyContinue)) { throw "Docker не найден. Установите Docker Desktop." } docker compose version 2>$null | Out-Null if ($LASTEXITCODE -ne 0) { throw "Docker Compose v2 не найден." } } function Import-EnvFromFile { param([string]$Path) if (-not (Test-Path $Path)) { return } Get-Content $Path | ForEach-Object { if ($_ -match '^\s*#' -or $_ -match '^\s*$') { return } $parts = $_ -split '=', 2 if ($parts.Count -eq 2) { $name = $parts[0].Trim() $value = $parts[1].Trim() Set-Item -Path "env:$name" -Value $value -Force } } } function New-RandomPassword { param([int]$Length = 20) $bytes = New-Object byte[] 32 [System.Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($bytes) $token = [Convert]::ToBase64String($bytes) -replace '[^A-Za-z0-9]', '' if ($token.Length -ge $Length) { return $token.Substring(0, $Length) } return ($token + ('A' * $Length)).Substring(0, $Length) } function Read-PromptValue { param( [string]$Prompt, [string]$Default, [switch]$Secret ) if ($Secret) { Write-Host "$Prompt (Enter — автогенерация): " -NoNewline $secure = Read-Host -AsSecureString $input = [Runtime.InteropServices.Marshal]::PtrToStringAuto( [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secure) ) if ([string]::IsNullOrWhiteSpace($input)) { $input = New-RandomPassword Write-Warn "Сгенерирован пароль PostgreSQL: $input" } return $input } $input = Read-Host "$Prompt [$Default]" if ([string]::IsNullOrWhiteSpace($input)) { return $Default } return $input } function Write-EnvFile { $content = @" # Сгенерировано $(Get-Date -Format "yyyy-MM-ddTHH:mm:ss") # Домен или IP сервера (без http/https) GITEA_DOMAIN=$($env:GITEA_DOMAIN) # Порт веб-интерфейса Gitea GITEA_HTTP_PORT=$($env:GITEA_HTTP_PORT) # Порт SSH для git clone/push GITEA_SSH_PORT=$($env:GITEA_SSH_PORT) # Версия образа Gitea GITEA_VERSION=$($env:GITEA_VERSION) # База данных PostgreSQL POSTGRES_USER=$($env:POSTGRES_USER) POSTGRES_PASSWORD=$($env:POSTGRES_PASSWORD) POSTGRES_DB=$($env:POSTGRES_DB) # Часовой пояс TZ=$($env:TZ) "@ $path = Join-Path $script:ScriptDir ".env" $utf8NoBom = New-Object System.Text.UTF8Encoding $false [System.IO.File]::WriteAllText($path, $content + "`n", $utf8NoBom) } function Initialize-EnvDefaults { param([string]$DefaultsFile) Import-EnvFromFile $DefaultsFile if (-not $env:GITEA_DOMAIN) { $env:GITEA_DOMAIN = "localhost" } if (-not $env:GITEA_HTTP_PORT) { $env:GITEA_HTTP_PORT = "3000" } if (-not $env:GITEA_SSH_PORT) { $env:GITEA_SSH_PORT = "2222" } if (-not $env:GITEA_VERSION) { $env:GITEA_VERSION = "1.22.6" } if (-not $env:POSTGRES_USER) { $env:POSTGRES_USER = "gitea" } if (-not $env:POSTGRES_DB) { $env:POSTGRES_DB = "gitea" } if (-not $env:TZ) { $env:TZ = "Europe/Moscow" } } function Invoke-SetupEnvInteractive { param([switch]$NonInteractive) $defaultsFile = ".env.example" if (Test-Path ".env") { $defaultsFile = ".env" if (-not $NonInteractive) { Write-Warn "Файл .env уже существует." $confirm = Read-Host "Пересоздать конфигурацию? [y/N]" if ($confirm -notmatch '^[Yy]$') { Import-EnvFromFile ".env" return } } } if (-not (Test-Path $defaultsFile) -and -not (Test-Path ".env.example")) { throw "Не найден .env.example" } if (-not (Test-Path $defaultsFile)) { $defaultsFile = ".env.example" } Remove-Item Env:GITEA_DOMAIN, Env:GITEA_HTTP_PORT, Env:GITEA_SSH_PORT, Env:GITEA_VERSION -ErrorAction SilentlyContinue Remove-Item Env:POSTGRES_USER, Env:POSTGRES_PASSWORD, Env:POSTGRES_DB, Env:TZ -ErrorAction SilentlyContinue Initialize-EnvDefaults $defaultsFile if ($NonInteractive) { if ([string]::IsNullOrWhiteSpace($env:POSTGRES_PASSWORD) -or $env:POSTGRES_PASSWORD -eq "change_me_strong_password") { $env:POSTGRES_PASSWORD = New-RandomPassword Write-Warn "Сгенерирован пароль PostgreSQL: $($env:POSTGRES_PASSWORD)" } Write-EnvFile Import-EnvFromFile ".env" return } Write-Host "" Write-Host "=== Настройка Gitea ===" -ForegroundColor Cyan Write-Host "Enter — оставить значение в скобках." Write-Host "" $env:GITEA_DOMAIN = Read-PromptValue "Домен или IP сервера" $env:GITEA_DOMAIN $env:GITEA_HTTP_PORT = Read-PromptValue "Порт веб-интерфейса" $env:GITEA_HTTP_PORT $env:GITEA_SSH_PORT = Read-PromptValue "Порт SSH для Git" $env:GITEA_SSH_PORT $env:GITEA_VERSION = Read-PromptValue "Версия образа Gitea" $env:GITEA_VERSION $env:POSTGRES_USER = Read-PromptValue "Пользователь PostgreSQL" $env:POSTGRES_USER $env:POSTGRES_DB = Read-PromptValue "Имя базы данных" $env:POSTGRES_DB $env:TZ = Read-PromptValue "Часовой пояс (TZ)" $env:TZ Write-Host "" $env:POSTGRES_PASSWORD = Read-PromptValue "Пароль PostgreSQL" "" -Secret Write-EnvFile Import-EnvFromFile ".env" Write-Info "Конфигурация сохранена в .env" } function Import-EnvFile { if (-not (Test-Path ".env")) { throw "Файл .env не найден. Выполните: .\deploy.ps1 install" } Import-EnvFromFile ".env" if ($env:POSTGRES_PASSWORD -eq "change_me_strong_password") { Write-Warn "Используется пароль по умолчанию! Измените POSTGRES_PASSWORD в .env" } } function Confirm-OrExit { param([string]$Message = "Продолжить?") $confirm = Read-Host "$Message [y/N]" if ($confirm -notmatch '^[Yy]$') { Write-Info "Отменено." exit 0 } } function Get-ComposeProjectName { if ($env:COMPOSE_PROJECT_NAME) { return $env:COMPOSE_PROJECT_NAME } $name = Split-Path $script:ScriptDir -Leaf return ($name.ToLower() -replace '[^a-z0-9_-]', '_') } function Get-VolumeNames { $prefix = Get-ComposeProjectName return @("${prefix}_gitea_data", "${prefix}_postgres_data") }