The scheduler and queue-worker containers were crashing with
RequiredEnvironmentVariableException because the APP_KEY_FILE
environment variable was not set, even though the app_key secret
was mounted. The Framework's Environment class needs the *_FILE
pattern to read Docker Secrets.
Replace direct $_ENV/$_SERVER access with framework's Environment class
to follow proper framework patterns and enable Docker Secrets support.
Changes:
- Add Environment and EnvKey imports
- Use $container->get(Environment::class) for environment access
- Replace $_ENV['APP_ENV'] with $env->getString(EnvKey::APP_ENV, ...)
- Rename internal method to registerServices for clarity
- Add documentation explaining the pattern
Co-Authored-By: Claude <noreply@anthropic.com>
- Add docker volume prune to deploy.sh to prevent stale code issues
- Add automatic migrations and cache warmup to staging entrypoint
- Fix nginx race condition by waiting for PHP-FPM before starting
- Improve PHP healthcheck to use php-fpm-healthcheck
- Add curl to production nginx Dockerfile for healthchecks
- Add ensureSeedsTable() to SeedRepository for automatic table creation
- Update SeedCommand to ensure seeds table exists before operations
This prevents 502 Bad Gateway errors during deployment and ensures
fresh code is deployed without volume cache issues.
Root cause: ExceptionHandlingInitializer attempted to autowire
EnvironmentType directly, but it was never registered in the DI
container. This caused the debug mode resolution to fail silently.
Changes:
- Use TypedConfiguration instead of EnvironmentType for proper DI
- Create ErrorHandlingConfig value object to centralize config
- Access debug mode via AppConfig.isDebugEnabled() which respects
both APP_DEBUG env var AND EnvironmentType.isDebugEnabled()
- Register ErrorHandlingConfig as singleton in container
- Remove diagnostic logging from ResponseErrorRenderer
This ensures that staging/production environments (where
EnvironmentType != DEV) will not display stack traces, code context,
or file paths in error responses.
This temporary logging will help verify that:
- EnvironmentType is correctly detected as STAGING
- isDebugEnabled() returns false for STAGING
- ResponseErrorRenderer receives isDebugMode=false
Remove after verification is complete.
Changed APP_DEBUG from ${APP_DEBUG:-false} to hardcoded false value
in all 4 services (php, nginx, queue-worker, scheduler).
This prevents any server-side .env or environment variables from
accidentally enabling debug mode in staging, which was causing
detailed error pages to be displayed.
The deployment was only pulling code via git but not rebuilding the
Docker images, causing containers to run with stale code from the
registry image. This fixes the debug error pages still showing on
staging despite APP_DEBUG=false.
Change the default value of $isDebugMode constructor parameter from
true to false, following the security-by-default principle. This ensures
that even if the factory is instantiated without explicit debug mode
configuration, it won't leak sensitive debugging information like
stack traces, file paths, and code context.
Staging environment should not expose detailed error messages,
stack traces, or debug information to end users.
Changed default from 'true' to 'false' in all services:
- php
- nginx
- queue-worker
- scheduler
Shell variables like $SECRETS_DIR in docker-compose command blocks
must be escaped as $$SECRETS_DIR. Without escaping, docker-compose
interprets them as environment variable interpolation and expands
them to empty strings, causing:
- mkdir: cannot create directory ''
- Secrets copied to wrong path (/redis_password instead of /var/www/html/storage/secrets/redis_password)
- PHP TypeError: RedisConfig::__construct() argument #3 must be string, null given
The fix applies $$ escaping to all shell variables in the PHP
service entrypoint script.
- Add explicit sed pattern for production-php:9000 → php:9000
- Fix character class [a-f0-9_]* to [a-zA-Z0-9_-]* to match full container names
- Loop over both sites-enabled and sites-available configs
- Add fastcgi_pass replacement for production-php
The --force-recreate flag alone doesn't handle containers that exist
outside the compose project context. Now explicitly:
1. Run docker compose down first
2. Force remove any orphaned containers with known names
3. Then create fresh containers
Fixes deployment error where existing containers with same name
blocked recreation. This ensures clean deployments by:
- Force recreating containers even if unchanged
- Removing orphan containers not in compose file
The Gitea Actions Runner doesn't have Node.js installed, causing
actions/checkout@v3 (a JavaScript action) to fail with
"Cannot find: node in PATH".
Replace with native shell-based git checkout that works without
Node.js and uses Gitea context variables for repository URL.
- Fix Enter key detection: handle multiple Enter key formats (\n, \r, \r\n)
- Reduce flickering: lower render frequency from 60 FPS to 30 FPS
- Fix menu bar visibility: re-render menu bar after content to prevent overwriting
- Fix content positioning: explicit line positioning for categories and commands
- Fix line shifting: clear lines before writing, control newlines manually
- Limit visible items: prevent overflow with maxVisibleCategories/Commands
- Improve CPU usage: increase sleep interval when no events processed
This fixes:
- Enter key not working for selection
- Strong flickering of the application
- Menu bar not visible or being overwritten
- Top half of selection list not displayed
- Lines being shifted/misaligned
- Fix TuiRenderer rendering: correct line positioning for categories
- Fix category item formatting: remove tabs, ensure consistent spacing
- Improve clearContentArea: preserve menu bar (lines 2-3) when clearing content
- Add ConsoleContext: mutable context container for readonly ConsoleOutput
- Add context awareness to ConsoleOutput: setContext/getContext/isInTuiContext
- Auto-detect TUI context in InteractivePrompter: automatically set LayoutAreas
- Set TUI context in TuiFactory and TuiCommandExecutor
- Add tests for TuiRenderer: menu bar preservation, category formatting
This fixes rendering issues where:
- Menu bar was not displayed or overwritten
- Category items had tab/space misalignment
- Content area clearing overwrote the menu bar
- Add comprehensive test suite for CMS and Asset modules using Pest Framework
- Implement ContentTypeService::delete() protection against deletion of in-use content types
- Add CannotDeleteContentTypeInUseException for better error handling
- Fix DerivatPipelineRegistry::getAllPipelines() to handle object uniqueness correctly
- Fix VariantName::getScale() to correctly parse scales with file extensions
- Update CMS module documentation with new features, exceptions, and test coverage
- Add CmsTestHelpers and AssetTestHelpers for test data factories
- Fix BlockTypeRegistry to be immutable after construction
- Update ContentTypeService to check for associated content before deletion
- Improve BlockRendererRegistry initialization
Test coverage:
- Value Objects: All CMS and Asset value objects
- Services: ContentService, ContentTypeService, SlugGenerator, BlockValidator, ContentLocalizationService, AssetService, DeduplicationService, MetadataExtractor
- Repositories: All database repositories with mocked connections
- Rendering: Block renderers and ContentRenderer
- Controllers: API endpoints for both modules
254 tests passing, 38 remaining (mostly image processing pipeline tests)
- Add LayoutAreas and LayoutArea value objects for coordinated screen rendering
- Add ScreenRendererInterface for testable screen operations
- Extend ScreenManager with clearContentArea() for selective clearing
- Refactor InteractiveMenu to support LayoutAreas via setLayoutAreas()
- Add prepareScreen() method that handles both standalone and layout-aware modes
- Fix cursor positioning to prevent menu bar overwriting
- Add comprehensive tests for layout areas and rendering behavior
This fixes rendering issues where InteractiveMenu would overwrite the menu bar
and cause misalignment of menu items when used within TUI layouts.
- Move all Gitea configuration from docker-compose.yml environment variables to app.ini
- Enable Redis cache with proper connection string format (redis://)
- Fix Redis password to use Gitea Redis instance password (gitea_redis_password) instead of application Redis stack password
- Add database connection pool settings to prevent timeout errors
- Configure Redis for cache, session, and queue using app.ini
- Update Ansible task to use correct Redis password for Gitea Redis instance
Benefits:
- Cache now works correctly (environment variables had a bug in Gitea 1.25)
- All settings are versioned in Git
- Better maintainability and reliability
- Configuration follows Gitea documentation recommendations
- Runtime-base job now uses repository artifact instead of cloning (saves 1 git clone per run)
- Health checks are now optional via workflow_dispatch input (default: true)
- Health checks still run automatically on push events
- Reduces additional load on Gitea by ~10-15%
- Use repository artifacts in test and build jobs (reduces 2-3 git clones per run)
- Add comprehensive workflow performance monitoring system
- Add monitoring playbook and Gitea workflow for automated metrics collection
- Add monitoring documentation and scripts
Optimizations:
- Repository artifact caching: changes job uploads repo, test/build jobs download it
- Reduces Gitea load by eliminating redundant git operations
- Faster job starts (artifact download is typically faster than git clone)
Monitoring:
- Script for local workflow metrics collection via Gitea API
- Ansible playbook for server-side system and Gitea metrics
- Automated Gitea workflow that runs every 6 hours
- Tracks workflow durations, system load, Gitea API response times, and more
- Replace http://localhost:8080/api/http/services with traefik show providers docker
- Replace http://localhost:8080/api/http/routers with traefik show providers docker
- Update debug messages to reference CLI command instead of HTTP API
- Fixes false 'NOT_FOUND' errors since api.insecure: false is set in traefik.yml
The Traefik CLI (traefik show providers docker) works without credentials
and is the recommended method for Traefik v3. It shows all Docker providers
including services, routers, and middlewares, so Gitea will be visible if
registered correctly.
- Fix YAML parsing error by quoting task name with colon
- Add PostgreSQL Staging Stack check and auto-start for staging deployments
- Ensures postgres-staging-internal network is created by the stack itself
- Network creation remains as fallback if stack doesn't create them
- Improves deployment reliability by ensuring dependencies are available
This addresses the root cause: PostgreSQL Staging Stack should be running
before the application stack tries to use its network.
- Extract external networks from docker-compose.base.yml and compose override files
- Extract network names from 'name:' fields when external: true
- Create all required external networks before docker compose up
- Fixes error: 'network postgres-staging-internal declared as external, but could not be found'
This ensures all external networks (traefik-public, app-internal, postgres-staging-internal, etc.)
are created before attempting to start containers.
- fix-gitea-timeouts.yml: Add when conditions to wait_for and uri tasks
- Wait for Traefik only if traefik_restart.changed
- Wait for Gitea via Traefik only if traefik_restart or gitea_restart changed
- fix-gitea-complete.yml: Same fixes as fix-gitea-timeouts.yml
- Wait for Traefik only if traefik_restart.changed
- Wait for Gitea and service discovery checks only if restart occurred
- fix-gitea-traefik-connection.yml: Fix wait and test tasks
- Register traefik_restart to track if restart happened
- Wait for Traefik only if traefik_restart.changed
- Test Gitea via Traefik only if traefik_restart.changed
- Update message to reflect actual restart status
- update-gitea-traefik-service.yml: Fix pause block
- Register traefik_restart to track if restart happened
- Wait for Traefik only if traefik_restart.changed
This prevents unnecessary blocking when traefik_auto_restart=false and
ensures wait/healthcheck tasks only run when a restart actually occurred.
- Replace docker_image module with shell command for more reliable pulling
- Add detailed error output from pull command (stdout/stderr)
- Show actual docker pull error messages when pull fails
- Simplify pull logic - always attempt pull regardless of local existence
- Check if image exists before pull to determine if force pull is needed
- Use docker images command to verify image exists locally (more reliable)
- Fix registries_to_login Jinja2 template to ensure it's always a list
- Add better error messages when image pull fails
- Only tag image if it was successfully verified to exist
- Add debug output for image pull process
- Improve error handling: verify image exists after pull before tagging
- Fix registries_to_login Jinja2 template to handle undefined variables
- Add explicit failure if image pull fails
- Only tag image if it was successfully pulled
- Change docker-compose.staging.yml: git.michaelschiemer.de:5000 -> localhost:5000
- Update deploy-image.yml playbook to:
- Pull images from registry.michaelschiemer.de (source registry)
- Tag and push to localhost:5000 (local registry) for local containers
- Remove hardcoded git.michaelschiemer.de:5000 logic
- Use local_registry from compose files for deployment
This ensures:
- Workflow pushes to registry.michaelschiemer.de (external, HTTPS)
- Containers use localhost:5000 (local, faster, no HTTPS overhead)
- Consistent registry usage across staging and production
The playbook was using docker_registry (registry.michaelschiemer.de) but
docker-compose.staging.yml uses git.michaelschiemer.de:5000. Now the playbook:
- Extracts the actual registry URL from docker-compose files
- Uses that registry for deploy_image
- Updates docker-compose file with the correct registry
This ensures the image is pulled from and deployed to the correct registry.
The previous regex matched 'redis:7-alpine' because it saw ':7' as a port.
New regex requires:
- TLD with optional port (e.g. git.michaelschiemer.de:5000)
- Hostname with numeric port only (e.g. localhost:5000)
- localhost with optional port
This excludes image names like 'redis:7-alpine' or 'minio/minio:latest'.
Ansible doesn't allow .append() on lists in Jinja2 templates.
Changed to use select() filter with regex match instead of loop with append.
This is the same filtering logic but using immutable operations.
The previous regex was removing port numbers from registry URLs.
Now using sed to only remove the image name part after the slash,
preserving the full registry URL including port (e.g. git.michaelschiemer.de:5000)