build: M1 Dockerized development stack with PHP skeleton, config/logging, Tailwind assets, and wrappers

Implements MeshVault M1 (issues #6–11):

- Four-service Docker Compose stack (nginx, PHP-FPM 8.3, PostgreSQL 17, Node assets)
- Slim 4 + PHP-DI routing with escaped PHP views and production/dev error handling
- Immutable validated AppConfig with redacted diagnostics
- JSON stdout Monolog logging with job-scoped context
- Tailwind CSS v4 deterministic locked builds with template scanning
- POSIX and PowerShell dev wrappers (up/down/test/lint/assets/watch)
- Startup config validation fails before FPM serves as unprivileged www-data
- migrate/seed explicitly unavailable until M2 (#12); #10 remains open

Verified via clean-start acceptance on remote host (keith@192.168.2.23).
This commit is contained in:
Keith Solomon committed 2026-10-11 16:45:45 -05:00
1 parent c2747a5762
commit d3e8cf8fae
39 files changed
+5291 -38

No files matched your search

+42
View File
@@ -0,0 +1,42 @@
# Deliberately use $args: unknown switches belong to the underlying command.
$ErrorActionPreference = 'Stop'
$Command = if ($args.Count) { $args[0] } else { '' }
$Forward = @($args | Select-Object -Skip 1)
try {
switch ($Command) {
{ $_ -in 'migrate', 'seed' } {
[Console]::Error.WriteLine("$Command unavailable until M2 (#12); #10 remains open")
exit 1
}
'up' { $ComposeArgs = @('up', '-d', '--build') }
'down' {
foreach ($Argument in $Forward) {
if ($Argument -match '^--volumes|^-v$') {
[Console]::Error.WriteLine('Volume removal is forbidden by this wrapper')
exit 2
}
}
$ComposeArgs = @('down')
}
{ $_ -in 'test', 'lint' } {
$ComposeArgs = @('run', '--rm', '--no-deps', '-T', '--entrypoint', 'composer', 'app', $Command)
}
'assets' { $ComposeArgs = @('run', '--rm', '--no-deps', '-T', 'assets', 'npm', 'run', 'build', '--') }
{ $_ -in 'watch', 'assets:watch' } {
$ComposeArgs = @('run', '--rm', '--no-deps', '-T', 'assets', 'npm', 'run', 'watch', '--')
}
default {
[Console]::Error.WriteLine('Usage: dev.ps1 up|down|test|lint|assets|watch|migrate|seed [arguments]')
exit 2
}
}
Push-Location $PSScriptRoot
try {
& docker compose @ComposeArgs @Forward
$Result = $LASTEXITCODE
} finally { Pop-Location }
exit $Result
} catch {
[Console]::Error.WriteLine($_.Exception.Message)
exit 1
}