Protokoll Update Drupal 10 > 11 (hbmhealthcare)
Detailliertes Protokoll der Schritte zum Update von hbmhealthcare von Drupal 10.6 auf Drupal 11, lokal bis stage. Live folgt als eigener Abschnitt.
Intro
Ziel: hbmhealthcare.com (hci) von D10.6.19 auf D11. Voraussetzung war MariaDB ≥ 10.6 auf dem Live-Server, der Hoster hat auf 11.8 aktualisiert.
Arbeitskopie: ~/01_webroot/10-drupal-dev/drupal-11/hbmhealthcare.com, DB hbmhealthcare_drupal_local. Neues Namensschema für DBs: Endung _local, _dev, _stage, _live, damit immer klar ist, in welcher DB wir arbeiten.
Drush: Standard-Alias drushh (= ./vendor/bin/drush, im Projektordner). Der alte projektspezifische Alias drushci aus der D8-Portierung entfällt. Single-Site, darum braucht es kein -l mehr.
1 -- Ausgangsstand lokal
1.1 -- Lokal mit live abgleichen
Auf live mit Backup and Migrate einen Dump in den Ordner 53-live schreiben, herunterladen, lokal in denselben Ordner legen und unter «Saved backups» einspielen.
Ordner für manuelle Dumps (pro Umgebung gleich): 50-local, 51-dev, 52-stage, 53-live. Der Ordner zeigt, aus welcher Umgebung ein Dump stammt. Die Ziele sind Konfiguration (DB, config/sync), die Ordner selbst nicht. Backup and Migrate legt sie bei Bedarf selbst an.
1.2 -- Sichern
Dump: --d10-11--start-synchron-live
git statusErwartet: «working tree clean».
2 -- Upgrade Status
drushh en upgrade_statusReports > Upgrade status. Ergebnis: 90 %, 3 Projekte zum Update (flood_control, jquery_ui_datepicker, Seven), 2 eigene Projekte zum Scannen (hbmcustom, hbm).
### WICHTIG: Hinweis oben: Von D10.6 sind 11.0 bis 11.2 nicht als Ziel unterstützt, mindestens 11.3.
3 -- Eigene Module und Theme
Scan: einzig core_version_requirement war zu alt, der Code selbst sauber.
web/modules/custom/hbmcustom/hbmcustom.info.yml und web/themes/custom/hbm/hbm.info.yml, Zeile 5:
core_version_requirement: ^10 || ^11
drushh cr
git add -A
git commit -m "D11: core_version_requirement hbmcustom und hbm auf ^10 || ^11"4 -- Contrib-Module vorbereiten
4.1 -- Flood control 2 > 3
composer require drupal/flood_control:^3.0 --with-all-dependencies
drushh updb
drushh crScaffold hat dabei die .htaccess neu geschrieben: assert.active (in PHP 8.3 veraltet) ersetzt durch zend.assertions. Korrekt, keine eigenen Regeln verloren.
### TIPP: Nach jedem Composer-Lauf mit «Copy [web-root]/.htaccess» per git status / Diff prüfen, ob eigene Ergänzungen verloren gingen.
4.2 -- jQuery UI Datepicker: ungenutzt, entfernt
Erst auf 2.x angehoben, dann geprüft, ob es überhaupt gebraucht wird:
composer why drupal/jquery_ui_datepicker
grep -rl datepicker config/sync web/modules/custom web/themes/customNur die eigene composer.json verlangt es, nur core.extension erwähnt es. Überbleibsel aus D8 (damals war jQuery UI im Core).
### TIPP: Das Kalendersymbol bei Datumsfeldern (Platzhalter «tt.mm.jjjj») ist der Browser-eigene HTML5-Datumswähler, nicht jQuery UI.
Zuerst deinstallieren (Code noch da), dann aus Composer entfernen:
drushh pmu jquery_ui_datepicker
composer remove drupal/jquery_ui_datepicker
git add -A
git commit -m "D11: jquery_ui_datepicker entfernt (ungenutzt)"### WICHTIG: Reihenfolge einhalten: erst pmu, dann composer remove. Umgekehrt bleibt das Modul in der DB als installiert stehen, und updb bricht ab (siehe 12.6).
4.3 -- Seven bleibt (Beta)
Entscheid: Seven bleibt bis zum Relaunch, damit der Kunde keine ungewohnte Umgebung hat. Claro erst mit dem Relaunch. Seven ist nur Admin-Theme, eine Beta ohne Security-Support ist dort vertretbar.
composer require drupal/seven:^2.0@beta --with-all-dependenciesComposer nahm 2.0.0-beta2, nicht beta8. Grund:
composer why-not drupal/seven 2.0.0-beta8beta8 verlangt drupal/core ^11.3. Darum beta2 jetzt, beta8 nach dem Core-Update.
### TIPP: why-not braucht Paket und Version mit Leerschlag, nicht mit Doppelpunkt.
drushh updb
drushh cr
git add -A
git commit -m "D11: Seven auf 2.0.0-beta2 (beta8 nach Core-Update)"4.4 -- Sichern
Dump: --d10-11--module-bereit
5 -- Core-Wechsel D10 > D11
5.1 -- Core-Pakete in composer.json
grep -n "drupal/core" composer.jsonDas Projekt nutzt drupal/core direkt, nicht drupal/core-recommended (alte Vorlage drupal-composer/drupal-project). Alle drei Core-Pakete müssen zusammen springen:
composer require drupal/core:^11.3 drupal/core-composer-scaffold:^11.3 --no-update
composer require --dev drupal/core-dev:^11.3 --no-update5.2 -- Update
composer update drupal/core drupal/core-composer-scaffold drupal/core-dev drush/drush --with-all-dependenciesErster Versuch ohne drush/drush brach ab: D11.3 braucht symfony/console ^7.4. Blockiert hat chi-teck/drupal-code-generator (Hilfspaket von Drush, nur Symfony 6). Gefunden mit:
composer why-not symfony/console 7.4.0Mit drush/drush in der Liste durfte Composer den Code-Generator mitziehen. Drush selbst blieb bei 13.8.0.
Frage «Do you trust symfony/runtime …»: y. Offizielles Symfony-Paket, neue Abhängigkeit von D11.
Ergebnis: Drupal 11.4.11 (^11.3 = ab 11.3, darum gleich die neueste).
5.3 -- Datenbank
drushh st
drushh updb
drushh crLange Liste von System-, Views-, Search-, Block- und CKEditor-Updates, alle sauber. Die Suche ist neu aufgeteilt (Search Node, Help Search), das erledigt updb selbst.
5.4 -- Seven auf neueste Beta
composer update drupal/seven --with-all-dependencies
drushh updb
drushh cr
git add -A
git commit -m "D11: Core 11.4.11, Seven 2.0.0-beta8, Scaffold-Dateien aktualisiert, symfony/runtime erlaubt"Seven 2: neue Schrift, Felder in voller Breite, Randspalte bei wenig Platz unter dem Inhalt. Neu sichtbar ist bei Paragraphs der Reiter Behavior neben Content. Seven 1 hat ihn ausgeblendet. Ohne Behavior-Plugins (bei hbm keine) zeigt er nur eine zugeklappte Übersicht, harmlos. Dem Kunden sagen: immer im Reiter Content arbeiten.
6 -- Fehler nach dem Core-Wechsel: Filter Medienmitteilungen
Symptom: Bei den Medienmitteilungen zeigte «Ad hoc» nichts, «Corporate» alles (DE und EN). Die Buttons filtern per JavaScript nach einer Klasse, die das versteckte Feld «Title (News-Type)» in der View view_hbm_media_releases erzeugt.
Ursache im Rewrite des Felds:
{% if '##ADHOCSTART##' in title_1|render %}Unter D11 (neueres Twig) liefert |render ein Markup-Objekt statt Text. in kann darin nicht suchen, also war die Prüfung immer falsch.
Lösung: |striptags anhängen, das macht wieder Text daraus:
{% if '##ADHOCSTART##' in title_1|render|striptags %}Apply, Save, drushh cr. Falls eine Sprache weiter falsch ist: unter «Translate view» prüfen, ob die Übersetzung einen eigenen Text hat.
Weitere Stellen suchen:
grep -rn "in .*|render" config/sync web/themes/customNur dieser eine Treffer.
### WICHTIG: Twig-Vergleiche mit in …|render nach D11 immer mit |striptags absichern.
### TIPP: Zur richtigen View: im Frontend eingeloggt über die Liste fahren, Stift oben rechts > Edit view.
7 -- Scaffold-Dateien geprüft
Composer hat beim Core-Wechsel u.a. .htaccess, robots.txt, index.php, update.php, development.services.yml, default.services.yml, default.settings.php neu kopiert.
- .htaccess: Schutz für package-lock.json, WebP-Dateityp, Statistics-Ausnahme entfernt (Modul nicht mehr im Core), Brotli-Regeln. Alles Standard.
- robots.txt: avif/webp erlaubt,
/web.configentfernt,/search?gesperrt. Nichts Eigenes verloren. Diff ohne Zeilenenden-Rauschen:
git diff --ignore-all-space --ignore-blank-lines aaa08f7 -- web/robots.txt- development.services.yml: neu ein Config-Schema-Prüfer. Betrifft uns nicht, wir laden
development.pernet.services.yml. Fürs Starter-Kit notiert. - default.services.yml / default.settings.php: nur Vorlagen, werden nie geladen. Nichts zu übernehmen.
### TIPP: Lange Diffs, bei denen jede Zeile geändert scheint, sind meist nur Zeilenenden. Mit --ignore-all-space sieht man die echten Änderungen.
8 -- Sicherheitsupdates
composer auditRabbit Hole < 1.3.0 und Shield < 1.9.0 betroffen (Moderately critical, 21.10.2026). Auch live.
composer update drupal/rabbit_hole drupal/shield --with-all-dependencies
drushh updb
drushh cr
git add -A
git commit -m "Sicherheitsupdates: rabbit_hole 1.3.0, shield 1.10.0"Shield-Update 8013 speichert Zugangsdaten «als Listen». Geprüft: Die Schlüssel credentials > shield > user / pass in settings.stage.php bleiben gültig, nichts anzupassen.
grep -rn "shield" web/sites/default/settings*.php private/deployment-settings
cat web/modules/contrib/shield/config/install/shield.settings.yml9 -- Plesk-Dateien aus dem Git
web/.php-ini und web/.php-version legt Plesk pro Domain an. Im Git stand noch der D8-Stand (7.4).
git rm --cached web/.php-ini web/.php-versionIn .gitignore:
# Plesk-Dateien (PHP-Version und Pfad zur php.ini der Domain), von Plesk pro Umgebung angelegt
# Auf dem Server nötig und nicht löschen, lokal (MAMP) unnötig
/web/.php-ini
/web/.php-versionLokal danach gelöscht.
git add -A
git commit -m "Plesk-Dateien .php-ini und .php-version aus Git genommen und ignoriert"### WICHTIG: Ein git pull auf dem Server löscht danach diese Dateien. Vorher sichern (siehe 12.2). Kommentar in der .gitignore anpassen, siehe 12.4.
10 -- Abschluss lokal
10.1 -- Testen
Seiten DE/EN durchgeklickt (Startseite, Team, Portfolio, Investors, Finanzberichte, Bedeutende Finanzanlagen, Formulare, Login), Reports > Recent log messages.
10.2 -- Status Report
Vier Warnungen, keine dringend:
- Configuration files nicht schreibgeschützt: lokal egal, auf stage/live Rechte setzen.
- Deprecated modules: Contact, History: fallen mit D12 aus dem Core. Für den Relaunch notiert.
- GD library: AVIF nicht unterstützt: ignoriert.
- HTML5 validation: Standard ändert sich mit D12. Für später notiert.
Hinweis PHP 8.4 empfohlen: eigener Schritt nach dem Go-live.
10.3 -- Upgrade Status deinstallieren, Konfiguration exportieren
drushh pmu upgrade_status### TIPP: Nach dem Core-Wechsel prüft Upgrade Status gegen D12, darum sieht der Bericht plötzlich «schlimm» aus. Normal.
drushh cexListe vor dem Bestätigen prüfen. Erwartet: Views, Editoren, Suche, Shield, Rabbit Hole, System, die reparierte Medienmitteilungs-View, neue Backup-Ziele 50–53. Gelöscht: alter daily_schedule (leer) und Ziel private_files (bewusst).
git add -A
git commit -m "D11: Konfiguration exportiert (Core-Updates, Views-Fix Medienmitteilungen, Backup and Migrate neu)"
git push11 -- Drush auf dem Server: open_basedir
Symptom auf dem Server: Drush startet nicht:
[preflight] The following restricted PHP modes have non-empty values: {open_basedir}.Ursache: Beim D10-Go-live (19.10., 20:35) hat Plesk beim Umstellen der Domain im Home-Ordner ~/.php-ini und ~/.php-version angelegt. .php-ini verwies auf die php.ini der Domain mit gesetztem open_basedir. Das Terminal sucht diese Dateien im aktuellen Ordner und darüber, darum galt das für stage und live. Drush verweigert grundsätzlich den Start, sobald open_basedir einen Wert hat.
Prüfen:
php --ini
ls -la ~ | grep php
cat ~/.php-iniLösung: Verweis im Home auf die Standard-php.ini umstellen (vorher sichern):
cp ~/.php-ini ~/plesk-stage/home-php-ini.bak
echo "/opt/plesk/php/8.3/etc/php.ini" > ~/.php-iniKontrolle: php --ini zeigt «Loaded Configuration File: /opt/plesk/php/8.3/etc/php.ini», drushh st läuft.
Danach überflüssig und gelöscht:
.php-ini/.php-versionin~/httpdocs/web/(zeigte zudem fälschlich auf stage) und~/stage.hbmhealthcare.com/web/usephp83in~/.bash_profile: Die Version kommt jetzt aus~/.php-version(8.3)
Plan B, falls Plesk die Datei wieder überschreibt (Empfehlung des Providers):
alias drushh='/opt/plesk/php/8.3/bin/php ./vendor/bin/drush.php'### WICHTIG: Nach einem PHP-Wechsel in Plesk cat ~/.php-ini prüfen.
Ausführlich im Artikel «PHP im Terminal: Plesk bei MVA vs. Metanet».
12 -- Stage
12.1 -- Frische Live-DB
Live-Dump über Backup and Migrate, per FTP auf stage, eingespielt. Stage hat damit den aktuellen Inhalt, noch mit D10-Code.
12.2 -- Plesk-Dateien sichern
cd ~/stage.hbmhealthcare.com
git status
mkdir ~/plesk-stage
cp web/.php-ini web/.php-version ~/plesk-stage/12.3 -- Pull
Lokale Änderungen an den zwei Dateien verwerfen, sonst verweigert Git den Pull:
git checkout -- web/.php-ini web/.php-version
git pullAbbruch: unable to unlink old 'web/sites/default/default.services.yml': Permission denied. sites/default ist schreibgeschützt (555). Git hatte die neuen Dateien schon teilweise geschrieben, war aber auf dem alten Stand stehen geblieben.
chmod 755 web/sites/default
git reset --hard origin/master
git statusreset --hard setzt exakt auf den GitLab-Stand. settings.stage.php, private/ und files/ sind ignoriert und bleiben unberührt.
### WICHTIG: Vor jedem Pull mit Core-Update: chmod 755 web/sites/default, danach wieder 555 (siehe Teil 1, Abschnitt 14).
12.4 -- Plesk-Dateien
Zuerst zurückkopiert, nach Abschnitt 11 aber ganz gelöscht. Kommentar in der lokalen .gitignore entsprechend anpassen («werden nicht gebraucht»).
12.5 -- Pakete
composer install --no-devD11.4.11, alle Module, jquery_ui_datepicker entfernt.
12.6 -- Fehlendes Modul jquery_ui_datepicker
drushh updb brach ab: «marked as installed in the core.extension configuration, but it is missing: jquery_ui_datepicker». Auf stage kam die DB von live, wo das Modul noch aktiv war, der Code war aber schon weg. Frage «continue?» mit No beantworten.
Modul von Hand aus der DB austragen (Weg laut drupal.org, bei diesem Modul ohne eigene Daten unbedenklich):
drushh php:eval "\Drupal::configFactory()->getEditable('core.extension')->clear('module.jquery_ui_datepicker')->save();"
drushh sql:query "DELETE FROM key_value WHERE collection='system.schema' AND name='jquery_ui_datepicker';"### WICHTIG: Auf live das Modul vor dem Pull mit drushh pmu jquery_ui_datepicker deinstallieren, solange der Code noch da ist.
12.7 -- Datenbank und Konfiguration
drushh updb
drushh cim
drushh crcim-Liste: Views (inkl. Medienmitteilungen), Search-Ansichten (Create/Delete-Paare = Wechsel zu Search Node, harmlos). Backup-Ziele 50–53 waren auf live schon angelegt.
12.8 -- Dateien von live
Fehlende Bilder und PDFs per rsync von live geholt (beides auf demselben Server):
rsync -a --info=progress2 --exclude=styles --exclude=css --exclude=js --exclude=php --exclude=languages --exclude=translations --exclude=library-definitions ~/httpdocs/web/sites/default/files/ ~/stage.hbmhealthcare.com/web/sites/default/files/Details im Artikel «rsync».
12.9 -- Testen
Shield-Abfrage, Medienmitteilungen-Filter DE/EN, Seiten, Status report, Log. Alles gut.
Cron auf stage nicht dauerhaft einrichten (sonst laufen auch die automatischen Backups). Bei Bedarf von Hand:
drushh cron13 -- Nebenbei auf live gefunden
~/httpdocs/web/index.html: Plesk-Platzhalter, angelegt beim Go-live (19.10., 20:35). Unsichtbar, weil DirectoryIndex index.php bevorzugt, aber direkt aufrufbar. Löschen:
rm ~/httpdocs/web/index.html✎ Claude – offen:
Go-live D11 auf live:
- Backup nach Schema (_backups_website/drupal/…, DB/ und Dateikopie)
drushh pmu jquery_ui_datepickervor dem Pullchmod 755 web/sites/default,git pull,composer install --no-devdrushh updb,drushh cim,drushh cr- Rechte zurück:
chmod 555 web/sites/default,chmod 444für die settings-Dateien - Tests: Medienmitteilungen-Filter DE/EN, Seiten, Status report
- Cron prüfen (Plesk > Geplante Aufgaben, Status report > Last cron run)
- Backup and Migrate: einen manuellen Dump nach 53-live schreiben (Pfad und Rechte)
- index.html gelöscht?
Danach:
.gitignore-Kommentar zu den Plesk-Dateien anpassen- PHP 8.4: auf stage testen, ob Plesk dabei
.php-version/.php-inineu schreibt - Alte
hbmcustomFeed-before-…phpundhbmcustomFeed_2025…phpaus dem Webroot entfernen (von aussen aufrufbar) - Für D12 / Relaunch: Contact und History (fallen aus dem Core),
enable_html5_validation, Seven > Claro, Behavior-Reiter bei Paragraphs, Umstellung aufdrupal/core-recommended
Falls du willst, kann ich daraus nach dem Live-Go-live noch den Abschnitt 14 «Go-live D11» im gleichen Stil ergänzen.