Files
projectsend/docker/app/entrypoint.sh
Ignacio Nelson f446398dfd Say which step is missing instead of failing blankly (#1633)
Somebody followed the README's Docker quickstart, which starts the
development stack, and got three failures in a row with nothing to search
for (#1627): the worker died once a second on a missing autoloader, the
site answered a bare 500, and once dependencies were installed by hand the
setup screen threw ViteManifestNotFoundException.

None of that is wrong behaviour for a clone — vendor/ and public/build/
are deliberately not in git — but every one of those failures kept its
cause to itself.

The preflight guard exists to turn "this was never set up" into a
sentence, and it runs before the autoloader precisely so it can. It now
answers two more questions: dependencies not installed, and frontend not
built. The dependency check goes first, before the .env one, because the
fix that branch prints — php artisan key:generate — cannot itself run
without the autoloader, so reporting the key first hands somebody a second
and more confusing error. A running vite dev server counts as built:
public/hot means the assets come from there, and blocking a developer
mid-session would be worse than the exception this replaces.

The worker and scheduler exec straight into artisan, so before composer
install they died instantly and restarted forever, filling the log that
had to be read to fix it. They now print what is missing and exit slowly,
and recover on their own once it is there. The scheduler gains the restart
policy the worker already had — without one it exits during that window
and stays exited, and scheduled work then silently never happens.

Rehearsed on a genuine clone of the public repository, following the
reporter's exact path: worker prints instructions instead of fatals (2
restarts in 30s, not 30), the browser gets "ProjectSend is not installed
yet" naming composer install, then "not configured yet", then "not built
yet" naming npm run build, then the setup screen.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 02:21:29 -03:00

63 lines
3.1 KiB
Bash

#!/bin/sh
set -e
# php-fpm's master process must stay root: it forks workers as www-data
# itself via the pool config (php-fpm can't reopen its own error_log as
# non-root at startup). queue:work/schedule:work have no such fork model,
# so they drop straight to www-data (uid/gid matches the host user, see
# Dockerfile ARGs) instead of running as root for their whole lifetime.
# The app writes to these as www-data, and boot is the one moment this
# container runs as root — so repair ownership here, every boot. Root-run
# `docker compose exec` shells and uid changes across image rebuilds leave
# root-owned directories behind, and the first symptom is an opaque
# "mkdir(): Permission denied" 500 on upload. The file library's contents
# are deliberately NOT chowned recursively: -R over a large library on
# every boot is not free, and new writes only need the directories.
mkdir -p storage/app/files storage/app/private storage/app/public storage/app/uploads-tmp \
storage/framework/cache storage/framework/sessions storage/framework/testing storage/framework/views \
storage/logs bootstrap/cache
chown www-data:www-data storage storage/app storage/app/files storage/app/private storage/app/public
chown -R www-data:www-data storage/app/uploads-tmp storage/framework storage/logs bootstrap/cache
# The worker and the scheduler exec straight into artisan, which cannot run
# without the autoloader — so on a fresh clone, before `composer install`,
# both died instantly and `restart: unless-stopped` brought them back to die
# again, once a second, forever. The log that was filling up is the same one
# somebody needs to read to find out what to do (#1627).
#
# Exit rather than wait: this container has nothing useful to do until the
# dependencies exist, and compose will keep retrying — but slowly enough to
# leave the log readable, and it recovers on its own the moment they appear.
if [ "$1" != "php-fpm" ] && [ ! -f vendor/autoload.php ]; then
echo "projectsend: the PHP dependencies are not installed — there is no vendor/autoload.php." >&2
echo "projectsend: run 'docker compose exec app composer install' (see CONTRIBUTING.md)." >&2
sleep 15
exit 1
fi
# First-boot bootstrap runs only for the FPM service (the worker execs
# straight through) and only when the app is actually installed.
if [ "$1" = "php-fpm" ] && [ -f vendor/autoload.php ]; then
# The same command the official image and update.sh run — migrate,
# roles, storage link, compiled caches, workers. See
# UpdateInstallation; there is deliberately only one definition of it.
su-exec www-data php artisan projectsend:update
# Unattended provisioning: create the first administrator from the
# environment. Without these, the web setup screen prompts instead.
if [ -n "$ADMIN_EMAIL" ] && [ -n "$ADMIN_PASSWORD" ]; then
su-exec www-data php artisan projectsend:admin --if-none \
--name="${ADMIN_NAME:-Administrator}" \
--email="$ADMIN_EMAIL" \
--password="$ADMIN_PASSWORD"
fi
fi
if [ "$1" = "php-fpm" ]; then
exec "$@"
else
exec su-exec www-data "$@"
fi