Skip to content

Fix settings.php written to wrong directory when core/ is a symlink, fixes #38#47

Open
rfay wants to merge 6 commits into
joachim-n:mainfrom
rfay:20260429_rfay_use_correct_docroot
Open

Fix settings.php written to wrong directory when core/ is a symlink, fixes #38#47
rfay wants to merge 6 commits into
joachim-n:mainfrom
rfay:20260429_rfay_use_correct_docroot

Conversation

@rfay

@rfay rfay commented Apr 29, 2026

Copy link
Copy Markdown

Problem

This project places the Drupal git clone at repos/drupal/ and symlinks web/core -> ../repos/drupal/core/. PHP resolves symlinks when computing __DIR__, realpath(), and chdir('..'), so several places in Drupal's installer compute repos/drupal/ as the application root instead of web/. The result: settings.php is written to repos/drupal/sites/default/ instead of web/sites/default/, DDEV's settings.ddev.php is never loaded, the web installer prompts for database credentials it should already know, and drush appends database credentials to settings.php unnecessarily.

Root causes and fixes

Three files in Drupal core needed patching, each applied from repos/drupal/ (not through web/core) because GNU patch 2.7+ refuses to traverse symlinks.

The key discriminator throughout is is_link($path . '/core'): in repos/drupal/, core/ is a real directory; in web/, core/ is a symlink. When core/ is a real directory, we search ancestor directories for a sibling whose core/ symlink resolves to the same real path.

core/install.php
chdir('..') from inside the symlinked core/ directory sets CWD to repos/drupal/. All relative paths (including './' . $site_path . '/settings.php' in install.core.inc) then resolve under repos/drupal/. Fix: use $_SERVER['SCRIPT_FILENAME'] (set by nginx to the unresolved docroot path) to compute $root_path, then call chdir($root_path) to correct CWD.

core/includes/install.core.inc
install_begin_request() calls Settings::initialize(dirname(__DIR__, 2), ...). __DIR__ resolves via the real filesystem path to repos/drupal/core/includes, so Settings::initialize() looks for settings.php in repos/drupal/sites/default/ and finds nothing — causing the web installer to prompt for database config and drush to write credentials into settings.php. In web context, use $_SERVER['DOCUMENT_ROOT']. In CLI context (drush), detect that core/ is a real directory and search ancestor directories for the web root.

core/lib/Drupal/Core/DrupalKernel.php
guessApplicationRoot() uses __DIR__ for the same reason. Same fix: DOCUMENT_ROOT for web context, ancestor directory search for CLI.

Testing with DDEV

Requirements: DDEV installed.

mkdir ~/tmp/drupal-test && cd ~/tmp/drupal-test
ddev config --project-type=drupal12 --docroot=web
ddev start
ddev composer create-project \
  --repository='{"url":"git@github.com:rfay/drupal-core-development-project","type":"vcs"}' \
  --stability=dev \
  "joachim-n/drupal-core-development-project:dev-20260429_rfay_use_correct_docroot"

Test 1 — drush install:

ddev composer require drush/drush
ddev drush site:install standard --yes --account-name=admin --account-pass=admin

Expected:

  • Install completes successfully
  • settings.php exists only at web/sites/default/settings.php
  • Nothing at repos/drupal/sites/default/settings.php
  • No database credentials appended to the end of web/sites/default/settings.php

Test 2 — web installer:

Drop the database and remove settings.php to start fresh:

ddev drush sql-drop -y
rm web/sites/default/settings.php
ddev restart   # DDEV recreates settings.php and settings.ddev.php
ddev exec "chmod 777 /var/www/html/web/sites/default && chmod 666 /var/www/html/web/sites/default/settings.php"

Visit https://drupal-test.ddev.site/core/install.php in a browser.

Expected: the installer proceeds language → profile → "Installing Drupal" without prompting for database credentials (DDEV's settings.ddev.php is loaded automatically). After installation completes, settings.php should exist only at web/sites/default/settings.php.

Verify the wrong location is empty:

ls repos/drupal/sites/default/
# Should show only: default.services.yml  default.settings.php
# settings.php must NOT be present

rfay and others added 5 commits April 29, 2026 11:37
When web/core is a symlinked directory (as in this dev project layout),
PHP resolves __DIR__ and realpath() to the real filesystem path, so
core/install.php and DrupalKernel::guessApplicationRoot() both compute
repos/drupal/ as the app root instead of web/. This causes the installer
to write settings.php to repos/drupal/sites/default/ rather than
web/sites/default/.

Add two patches applied during post-drupal-scaffold-cmd:
- scaffold-patch-install-php.patch: use $_SERVER['SCRIPT_FILENAME'] to
  derive the web root without resolving symlinks.
- scaffold-patch-drupal-kernel-php.patch: use $_SERVER['DOCUMENT_ROOT']
  in guessApplicationRoot() so InstallerKernel (constructed without an
  explicit app root) also resolves to the correct web root.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…aping

Two bugs in the initial DrupalKernel patch:
- PHP_SAPI !== 'cli' guard meant the fix was skipped for drush site:install
- Shell escaping produced '/\\\\' (two backslashes) instead of '/\\' (one)

The new guessApplicationRoot() fix:
1. Checks $_SERVER['DOCUMENT_ROOT'] unconditionally (it is only set by web
   servers, so it is naturally absent in CLI context).
2. For CLI (e.g. drush), falls back to the __DIR__-based computation, then
   verifies the result has autoload.php. If not, walks ancestor directories
   looking for a web root whose core/ symlink resolves to the same real path.

Verified with drush site:install: settings.php now lands in web/sites/default/
rather than repos/drupal/sites/default/.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
GNU patch 2.7+ refuses to traverse symlinks in file paths. Applying the
core patches from web/ via the web/core symlink therefore silently failed
on Linux (DDEV containers), leaving DrupalKernel.php and install.php
unpatched.

Fix by applying the two core patches from repos/drupal/ directly, where
the files exist at their real paths with no symlink traversal needed. The
--follow-symlinks flag is also added in case any paths inside core/ are
themselves symlinks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
install.php does chdir('..') which, when core/ is a symlink, resolves to
repos/drupal/. Our previous patch correctly set $root_path using
SCRIPT_FILENAME, but left CWD as repos/drupal/. The installer in
install.core.inc constructs settings file paths with './' . $site_path,
which is relative to CWD, so settings.php was still written to
repos/drupal/sites/default/ during a web install.

Fix by also calling chdir($root_path) after computing the correct web
root, so CWD and $root_path are consistent.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
install_begin_request() uses dirname(__DIR__, 2) to pass the app root to
Settings::initialize(), but __DIR__ resolves via the real filesystem path
when core/ is a symlinked directory, giving repos/drupal/ instead of web/.
Use DOCUMENT_ROOT (set by the web server, symlink-unaware) as the app root
in web context, falling back to dirname(__DIR__, 2) for CLI.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@rfay rfay changed the title Fix settings.php written to wrong directory when core/ is a symlink Fix settings.php written to wrong directory when core/ is a symlink, fixes #38 Apr 29, 2026
repos/drupal/ has its own autoload.php, so the previous condition
!is_file(autoload.php) never triggered and the ancestor search was
skipped. The correct discriminator is whether core/ is a real directory
(repos/drupal/) vs a symlink (web/): use !is_link(core/) so drush
finds web/sites/default/settings.php and reads settings.ddev.php,
preventing unnecessary rewrite of database credentials into settings.php.

Applies same condition fix to both install.core.inc and DrupalKernel.php.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@rpkoller

Copy link
Copy Markdown
Contributor

i can confirm, the PR in combination with ddev/ddev#8366 fixes #38

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants