diff --git a/docker/entrypoint-worker.sh b/docker/entrypoint-worker.sh index 017c2e6..98d0059 100644 --- a/docker/entrypoint-worker.sh +++ b/docker/entrypoint-worker.sh @@ -5,17 +5,38 @@ set -e # by app only) — just wait for app's migration to finish, then start the process. mkdir -p storage/app/public storage/framework/cache storage/framework/sessions storage/framework/views storage/logs storage/framework bootstrap/cache -if [ "$APP_ENV" != "production" ]; then - echo "[entrypoint] Waiting for migrations to complete..." - timeout=60 - while [ ! -f storage/framework/migrated ] && [ "$timeout" -gt 0 ]; do - sleep 1 - timeout=$((timeout - 1)) - done - if [ ! -f storage/framework/migrated ]; then - echo "[entrypoint] Timed out waiting for migrations" >&2 - exit 1 - fi +# Same reasoning as entrypoint.sh: this container gets replaced on every deploy +# (dev and production alike), so this is a fresh boot clearing stale artifacts +# left on disk (cached config, compiled views), not a running process being +# told to forget in-memory code. queue:work/schedule:work themselves still +# can't pick up a later code change without an actual process restart — this +# only fixes what's stale on disk at boot. +echo "[entrypoint] Clearing cached config/routes/views..." +php artisan optimize:clear --quiet +echo "[entrypoint] Caches cleared" + +# Universal, in both dev and production — app's entrypoint always writes this +# marker after `migrate --force` completes (single app instance, so there's +# exactly one writer), and queue/scheduler must never start against a database +# schema that migration hasn't finished bringing up to date yet. +echo "[entrypoint] Waiting for migrations to complete..." +timeout=60 +while [ ! -f storage/framework/migrated ] && [ "$timeout" -gt 0 ]; do + sleep 1 + timeout=$((timeout - 1)) +done +if [ ! -f storage/framework/migrated ]; then + echo "[entrypoint] Timed out waiting for migrations" >&2 + exit 1 +fi + +if [ "$APP_ENV" = "production" ]; then + # Same reasoning as entrypoint.sh's own production-only optimize step — + # queue:work/schedule:work read config on every job/tick too, so this + # avoids paying the same uncached-config cost app pays per request. + echo "[entrypoint] Caching config/routes/views for production..." + php artisan optimize --quiet + echo "[entrypoint] Production caches built" fi exec "$@" diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index a387d6c..db177d8 100644 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -4,6 +4,12 @@ set -e # Only the app container runs setup; queue/scheduler use entrypoint-worker.sh instead # and just wait on the migrated marker this script writes below. if [ "$APP_ENV" != "production" ]; then + # Dev-only: the app dir is bind-mounted from a fresh checkout, so there's no + # image-build step that already installed vendor/ or published assets — this + # container has to do it at boot instead. In production the Dockerfile already + # runs composer install and asset publishing at IMAGE BUILD time (see + # Dockerfile's `production` stage), so repeating them here would be wasteful, + # not just redundant-but-safe. echo "[entrypoint] Installing Composer dependencies..." git config --global --add safe.directory /var/www/html 2>/dev/null || true git config --global --add safe.directory /var/www/boboko-core 2>/dev/null || true @@ -21,38 +27,73 @@ mkdir -p storage/app/public storage/framework/cache storage/framework/sessions s chown -R www-data:www-data storage bootstrap/cache chmod -R 775 storage bootstrap/cache +# Composer packages (dev) may have just changed above, or (production) this is a +# freshly built image — either way, clear any cached config/routes/compiled views +# left over from a previous boot before anything below reads them. Production +# runs a single app instance that gets replaced on every deploy, not a running +# process being told to forget in-memory code — the actual bug this fixes is +# stale artifacts still sitting in bootstrap/cache or storage/framework/views on +# a fresh boot (e.g. after the Lunar 1.5/Filament v4 upgrade, a leftover compiled +# view referenced a class that upgrade removed). +echo "[entrypoint] Clearing cached config/routes/views..." +php artisan optimize:clear --quiet +echo "[entrypoint] Caches cleared" + php artisan storage:link --quiet 2>/dev/null || true if [ "$APP_ENV" != "production" ]; then - # The app dir is bind-mounted from a fresh checkout, so package assets (which the - # Dockerfile publishes at build time in production) need to be generated here - # instead. Cheap and idempotent, safe to repeat on every boot. + # Dev-only for the same reason as the composer step above — production's + # image already has these published at build time. php artisan vendor:publish --tag=core-assets --force --ansi --quiet php artisan vendor:publish --tag=public --force --ansi --quiet php artisan filament:assets --ansi --quiet +fi - echo "[entrypoint] Running migrations..." - rm -f storage/framework/migrated - php artisan migrate --force - echo "[entrypoint] Touching migrated file" - touch storage/framework/migrated - echo "[entrypoint] Touched migrated file" +# Everything below is universal, in both dev and production: application STATE +# that must be current on every boot, not a build-time concern composer/assets +# are. Safe to run unconditionally on every boot because production runs a +# single app instance — no concurrent replicas that would race each other +# running `migrate --force` at the same time. +echo "[entrypoint] Running migrations..." +rm -f storage/framework/migrated +php artisan migrate --force +echo "[entrypoint] Touching migrated file" +touch storage/framework/migrated +echo "[entrypoint] Touched migrated file" - # boboko/core overrides lunar:install to skip the interactive prompts (migrate - # confirm, admin creation, GitHub star) and just seed the idempotent store - # defaults: countries, channel, language, currency, tax zone, attributes, - # product type. queue/scheduler wait on the marker above rather than running - # this themselves, since the country import's check-then-insert isn't safe to - # run concurrently. - echo "[entrypoint] Trying Lunar install" - php artisan lunar:install --quiet || true +# boboko/core overrides lunar:install to skip the interactive prompts (migrate +# confirm, admin creation, GitHub star) and just seed the idempotent store +# defaults: countries, channel, language, currency, tax zone, attributes, +# product type. queue/scheduler wait on the marker above rather than running +# this themselves, since the country import's check-then-insert isn't safe to +# run concurrently. A fresh production install needs this seeding the same as +# a fresh dev one does — and re-running it against an already-seeded store is +# a no-op per key (see InstallLunarCommand's idempotent upserts). +echo "[entrypoint] Trying Lunar install" +php artisan lunar:install --quiet || true - # Upserts by primary key (no --refresh), so this stays cheap and idempotent on - # every boot rather than flushing and rebuilding the whole index each time. - echo "[entrypoint] Syncing search indexes..." - php artisan lunar:meilisearch:setup - php artisan lunar:search:index --quiet || true +# Upserts by primary key (no --refresh), so this stays cheap and idempotent on +# every boot rather than flushing and rebuilding the whole index each time. Runs +# in production too — a deploy that changed an indexer's field list needs this +# to keep Meilisearch's index settings and documents in sync with the code that +# just shipped, same reasoning as optimize:clear above. +echo "[entrypoint] Syncing search indexes..." +php artisan lunar:meilisearch:setup +php artisan lunar:search:index --quiet || true + +if [ "$APP_ENV" = "production" ]; then + # The counterpart to optimize:clear above: config/routes/views/events get + # compiled once here, at the end of boot, after everything that could + # change them (migrations, lunar:install, index sync) has already run — + # so production actually gets the request-time performance win caching is + # for, rather than staying permanently uncached. Dev deliberately never + # does this: caching config here would mean .env/config edits stop taking + # effect until the next optimize:clear, which is the opposite of what dev + # needs on every iteration. + echo "[entrypoint] Caching config/routes/views for production..." + php artisan optimize --quiet + echo "[entrypoint] Production caches built" fi exec "$@"