With Docker Desktop on Windows the bind-mounted C:\hotel made every page take ~5-7 s; with the code on the Linux filesystem it takes ~0.1 s. - docker/Dockerfile.server bakes the repo into the image (.dockerignore keeps .env, settings.inc.php and caches out) - docker/server-entrypoint.sh copies the code into the app_code volume on start: pass 1 updates code but skips runtime folders (uploads, generated images, caches, downloaded translations, settings.inc.php), pass 2 adds missing files without overwriting - docker/php-server.ini: OPcache without timestamp checks, larger realpath cache - setup script waits for the container and checks the install marker inside the volume; .gitattributes keeps *.sh LF on Windows checkouts - TESTSERVER.md: speed section and one-time switch from the first version Tested with a fresh stack: full install + demo + hardening, re-run keeps DB/uploads, rebuilt image delivers code changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
128 lines
5.5 KiB
PowerShell
128 lines
5.5 KiB
PowerShell
<#
|
|
.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 'Wait for the app container'
|
|
# the entrypoint first copies the code into the volume; wait until Apache answers
|
|
$ready = $false
|
|
for ($i = 0; $i -lt 60; $i++) {
|
|
& docker @compose exec -T app sh -c 'test -f /var/www/html/index.php && curl -s -o /dev/null http://127.0.0.1/robots.txt' 2>$null
|
|
if ($LASTEXITCODE -eq 0) { $ready = $true; break }
|
|
Start-Sleep -Seconds 2
|
|
}
|
|
if (-not $ready) { throw 'App container did not come up; check: docker compose ... logs app' }
|
|
|
|
# the code lives in the Docker volume, so check the install marker inside the container
|
|
& docker @compose exec -T app test -f /var/www/html/config/settings.inc.php
|
|
if ($LASTEXITCODE -eq 0) {
|
|
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 'Shop domain and HTTPS (from .env: SHOP_DOMAIN, QLO_SSL)'
|
|
Invoke-Docker @compose exec -T -u www-data app php docker/set-domain.php $envVars['SHOP_DOMAIN'] $envVars['QLO_SSL']
|
|
|
|
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)."
|