Files
timebank-cc-public/references/UPGRADE_LARAVEL_10_TO_11.md

17 KiB
Raw Permalink Blame History

Upgrade from Laravel 10 to Laravel 11, for any server and platform

This guide upgrades an installation of this application from Laravel 10 (any older version of the main branch) to Laravel 11 (the laravel-11 branch). It is the same for every platform that runs this code (Timebank.cc, Uuro or any other) and for every server: production, development or test.

Check where you start:

php artisan --version          # Laravel Framework 10.x: this guide applies
git branch --show-current      # main

The upgrade is done by scripts/upgrade.sh. The script checks the server, makes a verified database backup, runs the new deploy.sh while the site is in maintenance mode, and lets you test before visitors see the new version. If something is wrong, it rolls back.

Plan about 30 minutes. The site itself is offline (maintenance page) for about 5 to 15 minutes.

Upgrade one server at a time. Start with a test or development server if you have one.

Status (2026-10-19). Timebank.cc runs Laravel 11 since 2026-10-15. The last installation on Laravel 10 is the Uuro staging server, which runs a branch of its own (uuro) instead of main. It moves to main during the upgrade with upgrade.sh --leave-branch, after its config, logos and favicons are copied into its platform folder platform/. Its day-by-day checklist is RUNBOOK_LARAVEL_10_TO_11.md; this guide explains each step.


1. Before you start

Requirements

What Needed How to check
PHP for the website and the command line 8.2 or newer (Laravel 11 needs it) php -v; the script also asks the website itself
Git branch main git branch --show-current
tmux or screen so a dropped SSH connection does not stop the upgrade halfway tmux -V (capital V); install with apt-get install -y tmux
Free disk space about twice the size of vendor/ plus 2 GB df -h . and du -sh vendor
MySQL user that may drop and create the database (root or a deployment user) for migrations, and for a database restore during a rollback see .env below
Time and a quiet moment not during a busy period or a mailing

Local changes to tracked files stop the upgrade. Two kinds of change are expected and ignored:

  • public/build/, which deploy.sh rebuilds on every server
  • file modes (the executable bit), which deploy.sh's chmod -R 775 changes

Check with:

git -c core.fileMode=false status --short --untracked-files=no -- . ':(exclude)public/build'

This should print nothing. If it lists files, find out why they were changed on the server before you continue.

Check .env

These keys are read by deploy.sh and scripts/upgrade.sh. Show them without printing passwords:

grep -E '^(APP_URL|DEPLOY_ENVIRONMENT|DEPLOY_APP_DIR|DEPLOY_SERVER_TYPE|DEPLOY_WS_URL|TIMEBANK_CONFIG|TIMEBANK_THEME|DB_DEPLOY_USERNAME|SCOUT_QUEUE_TIMEOUT)=' .env
grep -c '^DB_DEPLOY_PASSWORD=.' .env    # 1 = set
Key Purpose
APP_URL The site's address. The script uses it to ask the website which PHP version serves it.
DEPLOY_ENVIRONMENT=server deploy.sh runs in server mode. Server mode also needs DEPLOY_APP_DIR, DEPLOY_SERVER_TYPE (dev or prod) and DEPLOY_WS_URL.
TIMEBANK_CONFIG The platform config, config/<name>.php. It can be any name, e.g. uuro.
TIMEBANK_THEME timebank_cc, uuro, vegetable or yellow.
DB_DEPLOY_USERNAME, DB_DEPLOY_PASSWORD The MySQL user for migrations and database restores. Without them you are asked for one: have the MySQL root password ready.
SCOUT_QUEUE_TIMEOUT=1800 Recommended: lets the nightly search re-index finish without the queue worker being killed.

Know your restart commands

After the deploy you restart long-running processes, because they still hold the old code. The script suggests commands for the services it finds. Typically:

sudo systemctl restart <your Reverb service>
sudo systemctl reload apache2          # or nginx
sudo systemctl reload php8.3-fpm       # if the site runs on PHP-FPM

deploy.sh restarts the queue workers itself.


2. Dry run (changes nothing)

Log in to the server, start tmux and go to the application root:

tmux new -s upgrade          # reconnect later with: tmux attach -t upgrade
cd /var/www/<your app>
git fetch origin
git show origin/laravel-11:scripts/upgrade.sh > /tmp/timebank-upgrade.sh
bash /tmp/timebank-upgrade.sh --dry-run --target origin/laravel-11

Always run the script from /tmp, taken from the target branch. The copy in your installation is older or absent.

A good dry run ends with Dry run: nothing was changed and shows:

  • 1. Checks: the PHP version is accepted for both the command line and the website, there is enough disk space, and Made on this server, differ from git: ... (if shown) is fine: deploy.sh makes those files again. A changed public/storage is never reported: it is this server's own storage link.
  • 2. Plan: current and target commit, Laravel major upgrade (10 -> 11), and the number of new migrations. If any migration changes existing tables, a rollback after going live needs the database restore.

If it stops instead, see Troubleshooting.


3. The upgrade

bash /tmp/timebank-upgrade.sh --target origin/laravel-11

The script goes through eight steps and waits for you at each question. What to answer:

Step Question or action Answer
1. Checks Not running inside tmux or screen... Continue anyway? (only outside tmux) Stop (N) and start tmux.
2. Plan Upgrade this installation to <commit>? y
3. Backup Make a database backup now with scripts/backup-database.sh? y. The backup is verified and kept with the upgrade state, together with a snapshot of vendor/ and public/build.
4. Maintenance The script prints a command with a secret. In a second tmux window (Ctrl-b c), in the application root, run that command: php artisan down --retry=60 --secret=<secret>. Then press Enter in the first window.
5. Deploy The new deploy.sh runs and asks its own questions. See below.
6. Version check Shows Laravel Framework 11.x. Nothing to do.
7. Restart Shows restart commands for this server. Run them in the second window, then press Enter.
8. Go / no-go Go live (g) or roll back now (r)? First test (see below), then g or r.

Questions from deploy.sh (step 5)

  • New configuration keys available ... Would you like to merge new keys now? (y/N): press y (a single key press). For each config file it then asks Merge these keys into <file>? (yes/no): type yes. New settings are added and your own values stay as they are. A backup of each file goes to storage/config-backups/.
  • MySQL username [root] / MySQL password: only when DB_DEPLOY_* is not in .env.
  • You have uncommitted changes. Continue anyway? (y/n): this should not appear any more. If it does, check what git status lists before you answer.

If deploy.sh fails, the script stops and the site stays in maintenance mode. See Troubleshooting.

Test before going live (step 8)

The site is still in maintenance mode. Open <APP_URL>/<secret> once in your browser: that browser can then use the site while visitors still see the maintenance page. Check:

  • log in, and switch to an organisation or bank profile
  • a payment
  • a chat message with an image
  • a poll, if your platform uses polls
  • search
  • the error log: tail -n 50 storage/logs/laravel-$(date +%F).log (or laravel.log)

Then:

  • All fine: answer g. Run php artisan up in the second window, then press Enter.
  • Something wrong: answer r. The rollback starts right away and may also restore the database, which is safe at this point because nothing went live.

4. After the upgrade

  1. Health check (once this version has it): php artisan upgrade:verify checks profiles, permissions, white-label settings, payment protection, search and background services, read-only. Run it before and after an upgrade; exit code 1 means a check failed.

  2. Platform settings (all platforms). See which new settings arrived, and check that nothing is waiting:

    php artisan config:merge --all --dry-run      # should report nothing new
    

    On a platform other than Timebank.cc, review these in its platform config (platform/config.php, or config/<TIMEBANK_CONFIG>.php without a platform folder). They came from the default template:

    Setting Default for a new platform Meaning
    platform_translations.* your existing names Platform name, currency and terms per language.
    mail.* your existing addresses Sender and recipient addresses.
    local_pages all false Timebank.cc's city pages (the-hague, lekkernassuh, amst-brus-lisb). A page that is off has no URL, and its footer link disappears.
    android_app.enabled false The Android app page and the app's update check (/app/version).
    account_info.*_self, *_privileged true Which balances and totals owners and privileged viewers (admins, banks) see.

    A setting a config file does not have yet keeps its old behaviour until it is merged.

  3. Email logo, favicons and preview image (platforms other than Timebank.cc; all branding is described in MULTI_PLATFORM_INSTANCES.md, sections 3 and 6). Without its own logo file, a platform gets a text logo with its name in emails and PDFs. Put the files in the platform folder (Uuro's runbook copies them there from its branch):

    platform/public/mail-logo.png            dark logo, for white backgrounds (PNG, about 900×700, transparent)
    platform/public/mail-logo-inverted.png   light logo, for the newsletter header
    platform/public/favicon.ico, favicon-32x32.png, favicon-16x16.png, apple-touch-icon.png
    platform/public/og-image.png             preview for shared links (about 1200×630)
    

    deploy.sh links them at /platform (php artisan platform:link); php artisan platform:check confirms it.

  4. Workers and scheduler. Check that the queue workers run, with all queues:

    sudo systemctl status 'timebank-*' --no-pager | grep -E 'Active|●'
    
  5. The next days. Watch the error log. When all is well, free the disk space of the snapshot. The script prints the exact command:

    rm storage/app/upgrades/<stamp>/vendor.tar.gz
    

    Keep the database backup in that folder a little longer.

Routine deploys afterwards

./deploy.sh -e server keeps working as before. It pulls main. Until main itself has been fast-forwarded to the Laravel 11 version, that pull finds nothing new on an upgraded server, which is harmless.

main is fast-forwarded to Laravel 11 only after every server has been upgraded, so after Uuro: a Laravel 10 server's own deploy.sh would otherwise pull Laravel 11 without the protection of this script. From Laravel 11 on, deploy.sh stops a routine server deploy that would raise the Laravel major version.

In its "pull conflict" question, never choose option 2 (reset and git clean): it deletes untracked files, such as uploads in storage/.


5. Rollback

Roll back with the copy of the script in the upgrade's state folder. The script printed the exact command; without a stamp it takes the latest upgrade:

bash storage/app/upgrades/<stamp>/upgrade.sh --rollback

It asks for confirmation, switches on maintenance mode and resets the code to the previous commit. It then restores vendor/, bootstrap/cache and the server's own public/build from the snapshot (no download needed), clears the caches and tells you what to restart.

The database:

  • Before going live, the rollback offers to restore the backup. That is safe.
  • After going live, it rolls back the code only, unless you type RESTORE. A restore deletes everything written since the backup: payments, messages, votes and new profiles. Usually the code-only rollback is right, because the old code ignores new tables.

The restore connects as DB_DEPLOY_USERNAME, or asks for a MySQL user that may drop and create the database.

After a rollback, do not run ./deploy.sh until the problem is fixed: it would pull the version you just left.


6. What the upgrade changes

  • Laravel 10 to 11. Laravel 11.57 and the matching package versions (Livewire 3.8 among them); the application code was adapted. No database changes are needed for it. PHP 8.2 or newer is required.
  • Platform config. A platform may use its own config name: TIMEBANK_CONFIG=myorg with config/myorg.php. deploy.sh creates the file from config/timebank-default.php.example when it is missing, and config:merge keeps it up to date. A config file that is not in git is excluded from git on that server (.git/info/exclude), so deploys no longer report it as an uncommitted change.
  • Settings for platform-specific pages. local_pages and android_app, see After the upgrade.
  • Branding. Texts that showed "Timebank" or "Timebank.cc" now use the platform name. Emails use the theme's logo or the generated text logo.
  • Deploys. deploy.sh no longer stops at a server-built public/build or at mode-only changes, and stops a routine server deploy that would raise the Laravel major version.
  • Platform folder. Everything platform-specific may live in platform/ (outside git): config, theme, text and template overrides, logos, favicons and the preview image. See MULTI_PLATFORM_INSTANCES.md, section 3. A platform branch of its own is no longer needed; upgrade.sh --leave-branch moves an installation off one.
  • Open Graph tags (preview of shared links) and optional favicon formats for every platform.
  • Admin roles. The legacy roles Admin\{id}\admin are replaced by the roles from admin_roles (database:update runs admin-roles:migrate).

7. Troubleshooting

Message Meaning and what to do
There are local changes to tracked files Files were edited on the server. Look at them with git diff. Commit the change upstream, or save a copy and git checkout -- <file>.
This installation has commits that are not on <target> The server has local commits. A fast-forward is not possible: ask the developers.
This server's PHP does not meet the requirements / This site is served by PHP x Install PHP 8.2 or newer (with the extensions listed) and switch the site to it first.
Could not ask the site which PHP serves it APP_URL is unreachable from the server itself. The script lists the PHP versions it finds and asks you to confirm.
Only N MB free Free disk space (old backups in backups/, old snapshots in storage/app/upgrades/).
deploy.sh stopped with an error The site stays in maintenance mode. Read the error above it. Either fix the cause, run bash deploy.sh -e server again and continue with steps 6 to 8 by hand (restart, test through the bypass URL, php artisan up), or roll back with the command the script printed (database restore allowed: nothing went live).
⊘ <file>: N new keys available (not applied) or not merged - ... config:merge did not change that file; it keeps working with the old settings. Run php artisan config:merge <name> --dry-run to see the keys, and add a reported key by hand from the .example file if needed.
✗ <file>: Merged config is invalid The merge checked its own result and restored the file. Nothing changed. Report the message to the developers.
Restore fails with Access denied ... DROP Add DB_DEPLOY_USERNAME and DB_DEPLOY_PASSWORD to .env, or restore by hand: gzip -dc <backup>.sql.gz | mysql -u root -p
The site still shows the maintenance page Run php artisan up in the application root.
apt-get update: GPG error: https://packages.sury.org/php ... EXPKEYSIG The signing key of the Sury PHP repository expired: no PHP updates arrive and apt-get install stops. Install Sury's key package and point the source at it (backup first):
cp -a /etc/apt/sources.list.d/php.list /root/php.list.bak-$(date +%F)
curl -fsSLo /tmp/sury-keyring.deb https://packages.sury.org/debsuryorg-archive-keyring.deb && dpkg -i /tmp/sury-keyring.deb
echo "deb [signed-by=/usr/share/keyrings/debsuryorg-archive-keyring.gpg] https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/php.list
apt-get update

All output of an upgrade and a rollback is logged in storage/app/upgrades/<stamp>/ (upgrade.log, rollback.log), together with state.env. Send these files along when you ask for help.