Commit Graph

5 Commits

Author SHA1 Message Date
elibrachas 1aaab1bf66 Read TRUSTED_PROXIES late enough for it to be seen
The value was read with env() inside the withMiddleware closure in
bootstrap/app.php. That closure runs when the HTTP kernel is resolved,
which is before the dotenv bootstrapper reads .env — so on every web
request env() returned null for anything set in .env, and the proxy was
never trusted. It worked when the value came from a real environment
variable, which is why the Docker compose path was fine and the manual
install described in INSTALL.md, where we tell people to put it in .env,
was not. Artisan bootstraps in the other order, so a check from the
command line reported the setting as working the whole time.

Behind a TLS-terminating proxy the consequence is not subtle. Laravel
falls back to the connecting address and the plain scheme, builds every
link and redirect with http:// while the browser is on https://, and
marks the session cookie non-secure. The browser then declines to send
that cookie to what it reads as a different, less secure origin, the
session arrives empty, and the first write fails with a 419 that reads as
"your session expired" — most often on the create-your-admin form, which
is the first thing a new install submits. Afterwards each redirect leaves
and re-enters over the wrong scheme, which is the random bounce back to
the login screen people report as flakiness.

Moved to config/trustedproxy.php, the key the framework's TrustProxies
middleware already falls back to on its own. Config files load after
dotenv, so the value is there whether it comes from .env or from the
environment.

This was also the only env() read outside config/, which means
config:cache is no longer dangerous on this application.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 15:41:31 -03:00
ignacionelson 5a7c9938dd Work properly behind a reverse proxy
Three findings from one report of intermittent 502s behind Nginx Proxy
Manager, all of them ours.

Stop sending the Link: preload header. AddLinkHeadersForPreloadedAssets
copied every Vite preload into a response header, duplicating tags the
document already carried in its head — twenty on the login page. nginx
buffers a response's headers into a single block defaulting to 4 KB, so
/files, at 6060 bytes of headers, was refused with "upstream sent too big
header" and the proxy answered 502. Which pages went over depended on how
many assets they loaded, which is why it read as intermittent rather than
as a header that is always too big: the login screen fitted, the
application did not. Removing it takes /files to 1247 bytes and
/dashboard from 4544 to 1247. Nothing is lost — the browser reads the
tags in the document, and we send no 103 Early Hints.

Send nginx's logs to the container's streams. supervisord captures what
each program writes to its own stdout, but nginx opens the files named in
the package's nginx.conf as soon as it reads its config, so access and
error logs went to /var/log/nginx/ inside the container. That is where
the reason for every 502 and every 403 was written, and docker logs never
showed it — so a proxy problem presented as no logs on either side, which
is exactly how it was reported.

Document the thing neither guide covered. DOCKER.md had no reverse-proxy
section at all: no mention of proxies, of 502s, or of TRUSTED_PROXIES,
which until now was explained only in a comment in the compose example.
It gains one, including that TRUSTED_PROXIES cannot cause a 502 and is
the wrong place to dig. INSTALL.md's nginx-in-front-of-Apache path gains
the proxy_* buffer settings its fastcgi_* equivalents already had.

Reported by @denkfabrik-li (#1664), who traced it to the middleware
independently, and separately by a user running Nginx Proxy Manager who
found the too-big-header line in the proxy's own log.
2026-08-21 15:19:44 -03:00
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
Ignacio Nelson d53bb9a2f7 Own the application directory in the official image (#1620)
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>
2026-08-14 12:20:57 -03:00
ignacionelson 6e47d76ba6 ProjectSend 2.0.0
Client file sharing, rebuilt from the ground up: a private area per
client, resumable uploads, folders, groups and categories, sharing with
expiry dates and download limits, comments, file versions, an activity
log, a REST API, and sixteen languages.

This repository begins here. ProjectSend 2 was developed privately, and
that development history is not published — the previous generation
remains available, with its own history, at projectsend/legacy.

Free software under the GNU General Public License v2, or (at your
option) any later version.
2026-08-14 01:38:12 -03:00