mirror of
https://github.com/projectsend/projectsend.git
synced 2026-09-11 22:38:54 +00:00
202a1d7ad5
#1658 reports that app, web, db and redis have no restart policy, so the
stack does not come back after a reboot. True, and fixed here — but the
file it is about is the development stack, and the production example has
had the policy all along. The reporter got there by following DOCKER.md,
which is #1627 again: 745f24c fixed the README's pointer and left this
page's body describing a stack no user should be running.
Against an image install almost every procedure on it was wrong. It said
uploads live in `storage/app/files/` "in the project directory" and `.env`
beside it — both are on the storage volume, and the entrypoint generates
that `.env` itself. Its compose.override.yaml recipe bind-mounted into
app, web, worker and scheduler, which are one container under supervisord
in the image, at a path one level too deep to carry APP_KEY. It told
people to chown a directory the entrypoint already chowns, to rsync from a
host path that does not exist, and to `git pull` to upgrade. Its mysqldump
read ${DB_ROOT_PASSWORD} from a .env an image install does not have, so
the documented backup silently fell back to `root` and failed. Docker Hub
links this page as "where your data lives, backups, moving to another
server".
So it is now about the image, and shorter for it: two volumes instead of
three loose things, the key explained where people actually lose it, no
override file because the compose file is the operator's own, and a
reboot section — the answer to the issue for anyone who wrote their own
compose. The clone-and-build stack keeps one pointer to CONTRIBUTING.md,
which has been the correct place for it since #1627.
The Docker Hub page keeps the two facts a reader who never leaves it
needs and hands off the procedures, so the drift that caused this has one
copy to go wrong instead of two.
Adminer and mailpit stay without a restart policy on purpose: those come
up for a session, not for the life of the machine.
Refs #1658
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
259 lines
12 KiB
Markdown
259 lines
12 KiB
Markdown
# Updating ProjectSend
|
|
|
|
How to move an existing installation to a newer version, for both ways of running it. If you are
|
|
installing for the first time, you want [Getting started](README.md#getting-started) for Docker or
|
|
[INSTALL.md](INSTALL.md) for your own server instead.
|
|
|
|
Two rules hold everywhere in this document:
|
|
|
|
- **Back up first.** Every time, including the update you are sure about. Migrations move forwards,
|
|
not backwards: there is no command that undoes them.
|
|
- **Read [CHANGELOG.md](CHANGELOG.md) for the version you are moving to.** Anything a release needs
|
|
beyond the steps below is written there. If a release raises the PHP requirement, that is where it
|
|
says so.
|
|
|
|
Which path you are on decides the rest:
|
|
|
|
| How you installed | What updating means | Manual steps |
|
|
|---|---|---|
|
|
| The official Docker image (`projectsend/projectsend`) | Pull a new image, recreate the container | None — the container migrates itself |
|
|
| Docker Compose built from a clone (CONTRIBUTING.md) | New code, rebuild the image | None — same entrypoint |
|
|
| A release zip on your own server (INSTALL.md) | Download the zip, run one script | `sudo ./update.sh`, and answer three questions |
|
|
|
|
ProjectSend also tells you which of these you are on: the **System** card on the dashboard prints
|
|
the update instructions for *this* server, and the notice that appears when a new version is
|
|
released links to the same thing.
|
|
|
|
---
|
|
|
|
## Before you start, whichever path you are on
|
|
|
|
1. **Back up the database.**
|
|
|
|
```sh
|
|
# Docker — --single-transaction is what makes this safe on a running database
|
|
docker compose exec -T db \
|
|
mysqldump -u root -p"${DB_ROOT_PASSWORD:-root}" \
|
|
--single-transaction --routines --triggers \
|
|
projectsend > projectsend-before-update.sql
|
|
|
|
# Your own server
|
|
mysqldump -u projectsend -p --single-transaction --routines --triggers \
|
|
projectsend > projectsend-before-update.sql
|
|
```
|
|
|
|
2. **Back up the uploads and `.env`.** On Docker these live on the `storage` volume (or the host
|
|
directory you pointed it at — see [DOCKER.md](DOCKER.md)); on a manual install they are
|
|
`storage/app/files/` and `.env` in the install directory.
|
|
|
|
3. **Note the version you are on**, from the dashboard's System card. If you have to go back, that
|
|
is the image tag or zip you go back to.
|
|
|
|
A backup that has never been restored is a hope, not a backup. If you have never tried, this is a
|
|
good moment: restoring into a scratch database takes five minutes and tells you something real.
|
|
|
|
---
|
|
|
|
## Updating a Docker installation
|
|
|
|
### The official image
|
|
|
|
If you are using the published image — the `compose.example.yaml` shipped with it pins
|
|
`projectsend/projectsend:2`, which follows every 2.x release — the whole update is:
|
|
|
|
```sh
|
|
docker compose pull
|
|
docker compose up -d
|
|
```
|
|
|
|
That is the complete procedure. There is no migration step to run, no cache to clear, and nothing
|
|
to do afterwards.
|
|
|
|
If you pinned an exact version instead (`projectsend/projectsend:2.0.0`), edit the tag in your
|
|
compose file first — `pull` on a pinned tag fetches the same image you already have.
|
|
|
|
### Compose built from source
|
|
|
|
Same idea, one extra step because the image is yours to build:
|
|
|
|
```sh
|
|
docker compose down # no -v, ever: -v deletes your data volumes
|
|
git pull # or unpack the new release over the directory
|
|
docker compose up -d --build
|
|
docker compose exec app composer install # if composer.lock moved
|
|
npm ci && npm run build # if package-lock.json or the frontend moved
|
|
```
|
|
|
|
The last two lines are what a source checkout has that an image does not: its dependencies and its
|
|
compiled frontend live outside git, so a release that changed either leaves them stale. If a page
|
|
comes back saying ProjectSend is "not installed yet" or "not built yet", it is naming which of the
|
|
two you skipped.
|
|
|
|
### What the container does on the way up
|
|
|
|
The entrypoint runs before the web server accepts a single request, in this order:
|
|
|
|
1. Recreates any missing `storage/` directories, and generates `APP_KEY` **only if one does not
|
|
already exist** — yours is on the storage volume and is left alone. A changed key would make
|
|
every existing session invalid and every encrypted column unreadable.
|
|
2. Waits up to 60 seconds for the database to accept connections, so a slow-starting database is a
|
|
pause rather than a crash loop.
|
|
3. Runs `php artisan projectsend:update` — the same command a manual install runs, and the only
|
|
definition of what an update does. It migrates the database, restores any built-in role a new
|
|
version added a permission to (without touching roles you customised), relinks storage, clears
|
|
the compiled caches and records the version it applied.
|
|
4. Starts php-fpm, nginx, the queue worker and the scheduler.
|
|
|
|
The queue worker and scheduler run inside that same container, so they are replaced with it and
|
|
never keep running old code.
|
|
|
|
### Checking it worked
|
|
|
|
```sh
|
|
docker compose logs app | grep -A5 "Running migrations"
|
|
docker compose ps # the app container should reach "healthy"
|
|
```
|
|
|
|
The first time the administrator opens ProjectSend after an update, they land on a page naming the
|
|
version now running and what the release brought, read from `CHANGELOG.md` inside the release
|
|
itself. It appears once, for the account that administers the installation; afterwards it stays
|
|
reachable from **About**, under the version line.
|
|
|
|
Then open the dashboard: the **System** card's *Version* line is the version now running, and the
|
|
"a new version is available" notice disappears on its own once the running version has caught up —
|
|
it compares against what is installed on every page load, so there is nothing to clear.
|
|
|
|
### If the container will not come up
|
|
|
|
The entrypoint stops on the first failure, so a container that restarts in a loop has told you why
|
|
in its own log:
|
|
|
|
```sh
|
|
docker compose logs app | tail -40
|
|
```
|
|
|
|
The two common ones are a database that never became reachable (`DB_HOST`, credentials, or a
|
|
database container that failed its healthcheck) and a migration that could not run. Neither leaves
|
|
a half-updated site serving traffic: nginx is not started until the entrypoint finishes.
|
|
|
|
---
|
|
|
|
## Updating a manual installation
|
|
|
|
```sh
|
|
cd /var/www/projectsend
|
|
sudo ./update.sh
|
|
```
|
|
|
|
That is the whole procedure. It asks three questions — whether to check GitHub for a newer release,
|
|
whether to download it (verifying the checksum published beside it), and whether you have a backup —
|
|
then does the rest. If you would rather fetch the zip yourself, hand it over instead:
|
|
|
|
```sh
|
|
sudo ./update.sh --zip ~/projectsend-2.1.0.zip
|
|
```
|
|
|
|
`./update.sh --check` reports what is installed and what is available, changes nothing, and does not
|
|
need root.
|
|
|
|
### What it does, in order
|
|
|
|
1. Refuses to run inside a container, where updating means pulling a new image.
|
|
2. Works out who your web server runs as, from the owner of `public/index.php`.
|
|
3. Reads the version out of the zip and refuses to go backwards — migrations only move forwards —
|
|
unless you pass `--force`. The *same* version is accepted, and is how you restore files that were
|
|
modified or lost.
|
|
4. Takes a database dump, if you asked for one (`--backup`).
|
|
5. Puts the site into maintenance mode, and guarantees it comes back out: if anything fails, or you
|
|
interrupt it, the site is brought back up before the script exits.
|
|
6. Unpacks the release over your installation, leaving `.env`, `storage/` and `public/storage`
|
|
alone, and replacing `vendor/` and `public/build/` wholesale rather than merging them.
|
|
7. Runs `php artisan projectsend:update`, which migrates the database, restores the built-in roles,
|
|
relinks storage, clears the compiled caches, rebuilds the optional ones **if you were using
|
|
them**, and records the version it applied.
|
|
8. Reloads PHP-FPM and restarts the queue worker.
|
|
9. Brings the site back up and tells you what is running.
|
|
|
|
### Useful options
|
|
|
|
| Option | What for |
|
|
|---|---|
|
|
| `--zip <path>` | Apply a zip you downloaded yourself. |
|
|
| `--backup` | Dump the database first, to `/var/backups/projectsend` (`--backup-dir` to change). |
|
|
| `--check` | Report versions and stop. No root needed. |
|
|
| `--user`, `--php-fpm`, `--worker` | Override what it detected. |
|
|
| `--no-restart` | Leave systemd alone. You must then reload PHP-FPM yourself. |
|
|
| `-y`, `--yes` | Unattended. Requires `--backup` or `--i-have-a-backup`. |
|
|
| `--force` | Apply an older release. Read the sentence about migrations again first. |
|
|
|
|
### Doing it by hand
|
|
|
|
The script is not magic, and there is no harm in running the steps yourself:
|
|
|
|
```sh
|
|
cd /var/www/projectsend
|
|
sudo -u www-data php artisan down
|
|
# unpack the release over this directory, keeping .env and storage/
|
|
sudo chown -R www-data:www-data .
|
|
sudo -u www-data php artisan projectsend:update
|
|
sudo systemctl reload php8.4-fpm # not optional — see below
|
|
sudo systemctl restart projectsend-worker
|
|
sudo -u www-data php artisan up
|
|
```
|
|
|
|
**The reload is the step that matters.** With OPcache configured the way production guides recommend
|
|
(`opcache.validate_timestamps=0`, which our own Docker image uses), PHP does not re-read a file it
|
|
has already compiled. Replacing the files changes nothing for the running site: the database ends up
|
|
on the new version and the web server keeps serving the old code, while `php artisan` reports the
|
|
new version the whole time you are trying to work out why.
|
|
|
|
If it happens anyway, ProjectSend now says so: staff see a banner naming the version being served,
|
|
the version that was installed, and the command that fixes it.
|
|
|
|
---
|
|
|
|
## When it goes wrong
|
|
|
|
**The site shows the old version after updating.**
|
|
Stale OPcache — reload PHP-FPM. `php artisan` reporting the new version while the browser reads the
|
|
old one is this exact symptom, and staff now see a banner saying so, naming both versions and the
|
|
command that fixes it. `sudo ./update.sh` does the reload for you unless you passed `--no-restart`.
|
|
|
|
**500 errors on every page, right after an update.**
|
|
Usually compiled caches from the previous version. `php artisan optimize:clear`, then reload
|
|
PHP-FPM. Check `storage/logs/` for the real error before assuming.
|
|
|
|
**"Base table or view not found" or a missing-column error.**
|
|
The migrations did not run, or did not finish. Run `php artisan migrate --force` and read its output.
|
|
On Docker, `docker compose logs app`.
|
|
|
|
**A setting looks stale — an old value the settings screen does not agree with.**
|
|
`php artisan cache:clear` is safe at any time and fixes it.
|
|
|
|
**The background worker is still running old code.**
|
|
It exits on `queue:restart` only once it finishes its current job, and something has to start it
|
|
again. Check the service INSTALL.md sets up: `sudo systemctl status projectsend-worker`.
|
|
|
|
**Permission errors after unpacking.**
|
|
Step 2's `chown`. `storage/` and `bootstrap/cache/` must be writable by the web server user — the
|
|
same requirement as [INSTALL.md](INSTALL.md) step 4.
|
|
|
|
**You need to go back.**
|
|
|
|
1. Restore the code: the previous image tag (Docker), or the previous directory or zip (manual).
|
|
2. Restore the database dump you took before starting.
|
|
3. On a manual install, run `sudo -u www-data php artisan projectsend:update` afterwards, so the
|
|
installation agrees with the code you restored. (A container does this itself on boot.) Until it
|
|
runs, staff see the banner described above — which is correct: the database is ahead of the code.
|
|
|
|
Restore both, not one. A newer database against older code is the one combination nothing in this
|
|
application expects, because migrations only ever move forwards.
|
|
|
|
---
|
|
|
|
## Version-specific notes
|
|
|
|
Anything a particular release needs beyond this document is in [CHANGELOG.md](CHANGELOG.md), under
|
|
that version. It is worth reading before you start rather than after, particularly for a release
|
|
that changes a requirement — the PHP floor, or a new extension.
|