The screen that tells somebody how to update names one command and stops,
which is right — the command is the procedure. But two of the options
behind it are the ones an administrator wants at exactly that moment:
whether it can take the backup for them, and whether they can just look
without changing anything. Both were documented only in UPDATE.md and in
--help, neither of which is open on the screen they are reading.
They are behind an "Other options" link rather than printed, so the page
in its resting state is unchanged and the one command stays the thing you
see. Four lines, the two above plus the two for a run nobody is sitting in
front of.
It opens from the dashboard card and from the update dialog both, which
means a dialog on top of a dialog in the second case. That is the right
shape here: the reader asked for a footnote to what they are already
reading, and Escape puts them back.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
"Made with care in Argentina. Shared with the world." — projectsend.org's
own line, at the foot of the two screens where somebody is being greeted
rather than getting on with their work.
Drawn rather than set as the flag emoji the website uses. Regional
indicator pairs have no glyphs on Windows or on most Linux desktops, and
both fall back to a pair of small letters — the first render of this
component read "AR Made with care in Argentina" on the machine that
screenshotted it, which is what a good half of the people self-hosting
this would have seen. Eleven lines of SVG look the same everywhere.
The Sun of May is a plain disc: at sixteen pixels its rays are a smudge,
and a smudge reads as a rendering fault rather than as a flag.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both greeting pages read like status reports. The install page opened
":name is installed and yours" without saying which version, the update
page opened "The update finished, and everything came back up", and the
quick-start list was eight full-width rows of two-line descriptions —
about 1400px, with the last two steps and the invitation below the fold
on a laptop.
The install page now thanks somebody for installing ProjectSend and names
the version they are on. The update page thanks them for updating and for
continuing to trust it with their file sharing. Both revert to plain
wording when the page is opened later from a link: thanking a reader
again for something they did months ago is the cold thing, not the warm
one.
The list becomes a two-column grid of icon cards — four rows instead of
eight, 1000px against 1490px, which is one screen. Icons come from the
sidebar's own vocabulary, so the chip on a card is the icon on the screen
it opens. Descriptions are one short clause each; the screen at the other
end explains itself.
And the steps stop pretending to be equally urgent. QuickStart now says
which are essential — the two that make this application do anything at
all, the mail server, the scheduler — and those carry an amber chip and
a label, against the brand colour for everything else and green for the
done ones. Amber is not invented here: it is the warning Alert variant's
palette, reused verbatim so dark mode is somebody else's solved problem.
The Discord card was two identical copies within an hour of each other,
so it is one component now, before the pair could drift.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Updating a server install cost nine artisan invocations plus a PHP-FPM
reload, written out in three places that had already drifted apart. One
of those steps is silently fatal to skip: with opcache.validate_timestamps
off — what production guides recommend and what our own image ships — the
database moves to the new version while every visitor keeps being served
the old code, and artisan reports the new version throughout.
`sudo ./update.sh` is now the whole procedure. It asks whether to check
GitHub, asks whether to download the release and verifies the checksum
published beside it, and asks whether there is a backup — offering to dump
the database when the answer is no. Then it takes the site down, replaces
the files, runs the update, reloads PHP-FPM, restarts the worker and
brings the site back. The application still has no self-updater: nothing
is fetched or applied unless somebody runs this and answers yes.
Underneath it is `php artisan projectsend:update`, which is everything an
update does that needs no root — and now the only definition of it. Both
container entrypoints call it instead of carrying their own copy of the
sequence, so the two paths cannot drift again.
Three findings worth keeping in the record, all from rehearsing rather
than reasoning:
- queue:restart has to come last. It writes its signal into the cache,
so clearing the cache afterwards deletes it and the worker runs old
code forever.
- optimize:clear is not safe to recommend. It runs cache:clear, which
on Redis is FLUSHDB — harmless on the default two-database layout,
but on a single-database Redis it takes the sessions and the queue
with it. The compiled caches are cleared individually instead.
- update.sh overwrites itself mid-run, because the zip contains it and
bash reads its own script lazily by byte offset. It re-execs from a
temporary copy before touching anything.
And when the reload is skipped anyway, the application now says so:
projectsend:update records the version it applied, and any staff page
compares that with what the running process actually compiled. The same
check catches the mirror image — new files in place, update never run.
Rehearsed end to end against real installs: a container upgrade (69 to 73
migrations, key and data intact, healthy), a scripted update on a real
nginx + php-fpm install with OPcache pinned (web process moved 2.1.0 to
2.1.1), the skipped-reload case (banner appears naming both versions, and
clears on reload), the refusals (downgrade, non-release zip, truncated
zip, URL passed to --zip, non-root), a database taken down mid-update
(site comes back out of maintenance mode by itself), and a real download
of the published 2.0.0 zip with its checksum verified.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Updating had a numbered list inside INSTALL.md and a code block in the
dashboard, and between them they were missing the step that decides
whether an update works at all.
Rehearsed both paths end to end against a real 2.0.0 install before
writing any of this down. On Docker the whole update is `docker compose
pull && docker compose up -d`: the entrypoint waits for the database,
migrates, ensures the roles, and starts the workers, and it does not
start nginx until that finishes — so a failed migration is a container
that stays down rather than a half-updated site taking traffic. The
generated APP_KEY on the storage volume is left alone. Verified: 69
migrations to 73, key unchanged, data intact, healthcheck healthy.
The manual path is where the gap was. Replace the files, run the four
documented commands, and the site still serves the old version: with
`opcache.validate_timestamps=0` — what every production guide recommends,
and what our own image ships — PHP never re-reads a file it has already
compiled. The database is on the new version, every visitor is on the
old code, and `php artisan` cheerfully reports the new version the whole
time you are trying to work out why. Reproduced exactly that, then
watched a php-fpm reload fix it.
UPDATE.md is now the whole procedure for both, INSTALL.md keeps the short
sequence with the reload added, and the in-app instructions gained the
same line — in the code block, which is not translated, so no locale is
left saying something different.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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.