<# .SYNOPSIS Sets up the QloApps test server on Windows (Docker Desktop or Docker in WSL). .DESCRIPTION Run from the repository root in PowerShell: powershell -ExecutionPolicy Bypass -File docker\setup-testserver.ps1 -ShopDomain test.example.de - creates .env with random passwords (only if .env does not exist yet) - builds and starts the containers (docker-compose.yml + docker-compose.server.yml) - installs QloApps in German via CLI (skipped if config/settings.inc.php exists: no accidental wipe) - fills German shop texts, optionally loads the Wasserrad demo content, then hardens the install Re-running is safe: it never reinstalls over an existing shop. .PARAMETER ShopDomain Host name nginx serves the shop under, without https:// (e.g. test.example.de). .PARAMETER NoSsl nginx serves plain HTTP (LAN test without certificate). .PARAMETER NoDemo Skip the "Zum alten Wasserrad" demo content. #> param( [Parameter(Mandatory = $true)][string]$ShopDomain, [switch]$NoSsl, [switch]$NoDemo ) $ErrorActionPreference = 'Stop' $root = Split-Path -Parent $PSScriptRoot Set-Location $root $compose = @('compose', '-f', 'docker-compose.yml', '-f', 'docker-compose.server.yml') function Step($text) { Write-Host "`n==> $text" -ForegroundColor Cyan } function New-Secret([int]$bytes = 18) { $b = New-Object byte[] $bytes [System.Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($b) return ([Convert]::ToBase64String($b) -replace '[^A-Za-z0-9]', '') } function Invoke-Docker { & docker @args if ($LASTEXITCODE -ne 0) { throw "docker $($args -join ' ') failed ($LASTEXITCODE)" } } function Read-EnvFile { $h = @{} foreach ($line in Get-Content .env) { if ($line -match '^\s*([A-Z_]+)=(.*)$') { $h[$Matches[1]] = $Matches[2].Trim() } } return $h } Step 'Docker' docker version --format '{{.Server.Version}}' | Out-Null if ($LASTEXITCODE -ne 0) { throw 'Docker is not running (start Docker Desktop or the Docker engine in WSL).' } Step '.env' $ssl = if ($NoSsl) { '0' } else { '1' } if (-not (Test-Path .env)) { $content = (Get-Content .env.example -Raw) $content = $content -replace '(?m)^DB_PASSWORD=.*$', "DB_PASSWORD=$(New-Secret)" $content = $content -replace '(?m)^DB_ROOT_PASSWORD=.*$', "DB_ROOT_PASSWORD=$(New-Secret)" $content = $content -replace '(?m)^ADMIN_EMAIL=.*$', "ADMIN_EMAIL=admin@$ShopDomain" $content = $content -replace '(?m)^ADMIN_PASSWORD=.*$', "ADMIN_PASSWORD=$(New-Secret 15)" $content = $content -replace '(?m)^SHOP_DOMAIN=.*$', "SHOP_DOMAIN=$ShopDomain" $content = $content -replace '(?m)^QLO_SSL=.*$', "QLO_SSL=$ssl" [System.IO.File]::WriteAllText((Join-Path $root '.env'), $content) Write-Host '.env created with random passwords (keep it private, it is git-ignored).' } else { Write-Host '.env exists, keeping its values.' # older .env files lack the server keys: add them from the parameters $existing = Get-Content .env -Raw $add = @{ 'SHOP_DOMAIN' = $ShopDomain; 'APP_BIND' = '0.0.0.0:8080'; 'QLO_SSL' = $ssl } foreach ($k in $add.Keys) { if ($existing -notmatch "(?m)^$k=") { Add-Content -Path .env -Value "$k=$($add[$k])" Write-Host " added $k=$($add[$k])" } } } $envVars = Read-EnvFile Step 'Build and start containers' Invoke-Docker @compose up -d --build Step 'File permissions for www-data' Invoke-Docker @compose exec -T app sh -c 'cd /var/www/html && chown -R www-data:www-data img mails modules translations cache config upload download themes override log' if (Test-Path config/settings.inc.php) { Step 'QloApps already installed (config/settings.inc.php exists) - skipping install' } else { Step 'Install QloApps (German, newsletter off)' Invoke-Docker @compose exec -T -u www-data app php install-dev/index_cli.php ` --language=de --country=de --timezone=Europe/Berlin ` "--domain=$($envVars['SHOP_DOMAIN'])" "--ssl=$($envVars['QLO_SSL'])" ` --db_server=db "--db_user=$($envVars['DB_USER'])" "--db_password=$($envVars['DB_PASSWORD'])" "--db_name=$($envVars['DB_NAME'])" ` "--name=Zum alten Wasserrad" --firstname=Admin --lastname=Hotel ` "--email=$($envVars['ADMIN_EMAIL'])" "--password=$($envVars['ADMIN_PASSWORD'])" ` --newsletter=0 } Step 'German shop texts' Invoke-Docker @compose exec -T -u www-data app php docker/apply-translations.php de if (-not $NoDemo) { Step 'Wasserrad demo content' Invoke-Docker @compose exec -T -u www-data app php docker/seed-wasserrad-demo.php } Step 'Harden' Invoke-Docker @compose exec -T -u www-data app php docker/harden-test-server.php $scheme = if ($envVars['QLO_SSL'] -eq '1') { 'https' } else { 'http' } Write-Host "`nDone." -ForegroundColor Green Write-Host "Shop: $scheme`://$($envVars['SHOP_DOMAIN'])/" Write-Host "Admin: $scheme`://$($envVars['SHOP_DOMAIN'])/admin-dev/ (login: ADMIN_EMAIL / ADMIN_PASSWORD in .env)" Write-Host "App listens on $($envVars['APP_BIND']) - point nginx there (docker/nginx-qloapps.conf)."