mirror of
https://github.com/projectsend/projectsend.git
synced 2026-09-22 03:23:23 +00:00
d53bb9a2f7
The base php:*-fpm image creates /var/www/html owned by its own www-data (uid 82) and mode 1777, so that an image can run as an arbitrary user. This image replaces www-data with a fixed uid 1000 and copies the release in with COPY --chown — which re-owns what it copies into the directory, never the directory itself. It was left world-writable, sticky, and owned by a uid the container no longer has. fs.protected_symlinks — on by default on Ubuntu, Debian and most current distributions — then refuses to let a non-root process follow a symlink in such a directory, and .env is exactly that: the entrypoint keeps it on the storage volume so a generated APP_KEY survives container replacement, and links it into place. So every request 503'd with "ProjectSend is not configured yet" while `docker exec ... cat .env`, run as root, printed the file back perfectly (#1615). Three changes, each independently sufficient for the reported case, and deliberately so — this failure is silent and its symptom points away from its cause: - the image owns /var/www/html as the runtime user, at mode 755; - the entrypoint owns the symlink it creates, so it stays followable even if that directory's mode ever drifts back; - preflight distinguishes "no .env" from ".env is there and cannot be read", instead of reporting the second as the first and sending the operator off to create a file they already have. Verified by building the production image before and after: every request 503s beforehand, with /var/www/html at uid 82 mode 1777 and www-data denied on the symlink while root reads it; afterwards /up answers 200, the container reports healthy, and / redirects to /setup. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
92 lines
3.9 KiB
Bash
Executable File
92 lines
3.9 KiB
Bash
Executable File
#!/bin/sh
|
|
set -e
|
|
|
|
# First-boot bootstrap for the official image. Adapted from
|
|
# docker/app/entrypoint.sh, with the differences that matter in production:
|
|
#
|
|
# - The database is external and may not be up yet, so migrations wait
|
|
# for it instead of failing the container into a restart loop.
|
|
# - APP_KEY is generated once and persisted, because a key that changes
|
|
# between restarts invalidates every session and makes every encrypted
|
|
# column unreadable.
|
|
# - Nothing is bind-mounted, so there is no uid juggling to do.
|
|
#
|
|
# Deliberately NOT run: `config:cache`. It stops .env from being read, which
|
|
# silently disables TRUSTED_PROXIES — every visitor then appears to come
|
|
# from the proxy, the login rate limiter treats all users as one attacker,
|
|
# and the download log records the wrong address. See INSTALL.md.
|
|
|
|
cd /var/www/html
|
|
|
|
# storage/ is the declared volume, so anything that must outlive the
|
|
# container lives there. Recreate the tree first: a bind-mounted host
|
|
# directory arrives empty, unlike a named volume which Docker seeds from
|
|
# the image.
|
|
mkdir -p storage/app/files \
|
|
storage/framework/cache \
|
|
storage/framework/sessions \
|
|
storage/framework/views \
|
|
storage/logs
|
|
chown -R www-data:www-data storage
|
|
|
|
# .env is kept on that volume and symlinked into place, so a generated
|
|
# APP_KEY survives container replacement. A key that changes between
|
|
# restarts invalidates every session and makes every encrypted column
|
|
# permanently unreadable — silently, since nothing errors at boot.
|
|
#
|
|
# Operators who set APP_KEY (and the rest) in the environment need none of
|
|
# this: Laravel reads the environment directly and it wins over .env.
|
|
if [ ! -f storage/.env ]; then
|
|
cp .env.example storage/.env
|
|
chown www-data:www-data storage/.env
|
|
fi
|
|
#
|
|
# The symlink is owned by the runtime user, not by root who creates it:
|
|
# fs.protected_symlinks lets a process follow a symlink in a world-writable
|
|
# sticky directory only when it owns the link (or the directory). The image
|
|
# no longer leaves /var/www/html in that state, so this is the second lock
|
|
# on the same door — cheap, and it is what keeps the failure from coming
|
|
# back silently if that directory's mode ever drifts.
|
|
[ -L .env ] || ln -sf storage/.env .env
|
|
chown -h www-data:www-data .env
|
|
|
|
if [ -z "$APP_KEY" ] && ! grep -q '^APP_KEY=base64:' storage/.env; then
|
|
echo "projectsend: generating APP_KEY (first boot, persisted to the storage volume)"
|
|
su-exec www-data php artisan key:generate --force --no-interaction
|
|
fi
|
|
|
|
# Wait for the database. `migrate` against a database still starting up is
|
|
# the single most common first-run failure, and a bare failure here would
|
|
# restart-loop the container with a stack trace instead of a clear message.
|
|
if [ "$1" = "supervisord" ] || [ "$1" = "/usr/bin/supervisord" ]; then
|
|
i=0
|
|
until su-exec www-data php artisan db:show --quiet >/dev/null 2>&1; do
|
|
i=$((i + 1))
|
|
if [ "$i" -ge 60 ]; then
|
|
echo "projectsend: database unreachable after 60s — check DB_HOST, DB_DATABASE and credentials" >&2
|
|
exit 1
|
|
fi
|
|
[ "$i" = 1 ] && echo "projectsend: waiting for the database..."
|
|
sleep 1
|
|
done
|
|
|
|
su-exec www-data php artisan migrate --force
|
|
su-exec www-data php artisan storage:link --force
|
|
su-exec www-data php artisan projectsend:ensure-roles
|
|
|
|
# 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
|
|
|
|
# Cheap and safe to repeat; keeps a restarted container from serving
|
|
# stale compiled views after an image upgrade.
|
|
su-exec www-data php artisan view:clear
|
|
fi
|
|
|
|
exec "$@"
|