Files
landing-api/docker/entrypoint.sh
Josep Piguillem 3897113313 Initial commit: Pireon Admin — headless admin panel and public API
Laravel 13 + Filament 5 admin panel and REST API for a non-profit developer
collective, backed by the Pireon OAuth identity server. The public site is a
separate, server-rendered application consuming /api/public/v1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 17:55:27 +02:00

92 lines
3.7 KiB
Bash

#!/bin/sh
set -e
###############################################################################
# Everything that has to be true before the application answers a request.
#
# Runs in every container built from this image, but not every container is a
# service: `docker compose run app php artisan key:generate` has to work on a
# stack that has never been started, which is exactly when there is no APP_KEY
# and no database yet. So the preparation below applies to the three
# long-running commands, and anything else is passed straight through.
###############################################################################
case "$*" in
frankenphp*|*queue:work*|*schedule:work*) is_service=true ;;
*) is_service=false ;;
esac
# A named volume mounted over storage/ starts as a copy of the image's, but a
# bind mount does not, and neither survives a container being recreated with an
# empty one. Cheap to guarantee, expensive to debug — and needed by one-off
# commands too, since most of them write a log.
mkdir -p \
storage/app/public \
storage/framework/cache/data \
storage/framework/sessions \
storage/framework/views \
storage/logs
chown -R www-data:www-data storage bootstrap/cache 2>/dev/null || true
if [ "$is_service" = "false" ]; then
exec "$@"
fi
if [ -z "${APP_KEY}" ]; then
echo "APP_KEY is not set. Generate one with:" >&2
echo " docker compose run --rm --no-deps app php artisan key:generate --show" >&2
echo "Without it sessions cannot be decrypted and preview links cannot be signed." >&2
exit 1
fi
# Wait for Postgres rather than crash-looping against it. Compose's own
# healthcheck covers the ordinary case; this covers the database restarting
# under a running application.
if [ "${DB_CONNECTION:-pgsql}" = "pgsql" ]; then
tries=0
until pg_isready -h "${DB_HOST:-postgres}" -p "${DB_PORT:-5432}" -U "${DB_USERNAME:-pireon}" -q; do
tries=$((tries + 1))
if [ "$tries" -ge 30 ]; then
echo "Database at ${DB_HOST:-postgres}:${DB_PORT:-5432} never became ready." >&2
exit 1
fi
echo "Waiting for the database… (${tries}/30)"
sleep 2
done
fi
# Opt-in rather than automatic: a migration is a change to somebody's data, and
# on a multi-container deploy it must run once, not once per container. Set it
# on the web service only.
if [ "${RUN_MIGRATIONS}" = "true" ]; then
echo "Running migrations…"
# CACHE_STORE is overridden for this one command, not passed to
# `--isolated` — that option's value is only an exit code, never a store.
#
# It matters because with CACHE_STORE=database the lock table is created by
# these very migrations, so on a brand new database the lock would be taken
# against a table that does not exist yet and the container would crash-loop
# for ever. The file store lives on the shared storage volume, so it still
# stops two containers migrating at once.
CACHE_STORE=file php artisan migrate --force --isolated
# The permission catalogue is code, so a deploy that adds one has to sync
# it or the new permission exists nowhere.
php artisan permissions:sync
fi
# Serves uploaded images from the storage volume. Idempotent, and it has to run
# here rather than at build time because the volume is mounted after the image
# is built.
php artisan storage:link --quiet 2>/dev/null || true
# Config, routes and views compiled once per container start. This is most of
# the difference between an API answering in 4ms and in 40, which matters when
# an SSR frontend makes several calls per page view.
php artisan config:cache --quiet
php artisan route:cache --quiet
php artisan view:cache --quiet
exec "$@"