From 34c5cd84e117445792c145a72464f8d0a6321ca4 Mon Sep 17 00:00:00 2001 From: Chingis S Date: Mon, 28 Sep 2026 00:37:46 +0400 Subject: [PATCH] Create or extend Drupal settings in workspaces Workspace setup refused to add the Wodby include to a tracked settings.php, so preparation failed for projects that commit their settings. It now works as in a build: it creates settings.php from default.settings.php when it's missing and appends the include when the file doesn't have one. In a workspace the include is skipped when Wodby's settings file doesn't exist, so the file keeps working elsewhere and the developer can commit it. Multisite sites.php gets the same include. Setup still never changes files outside the checkout, and it replaces the files directory with a link only when Git ignores it and doesn't track it. Standard initialization output is unchanged. --- README.md | 29 ++++++++------- bin/init_drupal | 52 +++++++++++++++++---------- tests/workspace-checkout.sh | 71 ++++++++++++++++++++++++++++++------- 3 files changed, 107 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 1b1bb4e..eaf834d 100644 --- a/README.md +++ b/README.md @@ -108,13 +108,10 @@ When adding a supported base version or variant, add its image index digest to ### Workspace preparation and Git -With `WODBY_WORKSPACE=1`, initialization preflights settings and upload paths before -changing the checkout. It refuses to append configuration to tracked settings or -replace tracked upload files. Generated settings must be ignored and untracked; -ignoring an already tracked file alone is insufficient. - -For tracked settings, add the bootstrap include deliberately and commit it with -application configuration. For example, in `settings.php`: +With `WODBY_WORKSPACE=1`, initialization creates `settings.php` from +`default.settings.php` when it's missing, and appends the Wodby include when the file +doesn't have one. The include is skipped when Wodby's settings file doesn't exist, +so the file keeps working elsewhere and you can commit it: ```php $wodbyConfig = (getenv('CONF_DIR') ?: '/var/www/conf') . '/wodby.settings.php'; @@ -123,9 +120,15 @@ if (is_file($wodbyConfig)) { } ``` -Multisite projects need the equivalent `wodby.sites.php` include in `sites/sites.php`. -Keep uploaded files and generated local settings out of Git. Retry preparation after -fixing the reported paths. Standard initialization retains its existing behavior. -Dependency managers and project scripts can still change source files; inspect the -Git diff before committing. Inherited workspace support requires a PHP base image -that declares workspace contract version 1. +A tracked `settings.php` shows the include as a change to commit. Settings that +already mention `wodby.settings.php` are left unchanged. Multisite projects get the +equivalent `wodby.sites.php` include in `sites/sites.php`. + +Initialization checks every path before changing the checkout. It never changes +files outside the checkout. It replaces the site's `files` directory with a link to +the files volume, so that directory must be ignored by Git and not tracked; ignoring +an already tracked path alone is insufficient. Retry preparation after fixing the +reported path. Standard initialization retains its existing behavior. Dependency +managers and project scripts can still change source files; inspect the Git diff +before committing. Inherited workspace support requires a PHP base image that +declares workspace contract version 1. diff --git a/bin/init_drupal b/bin/init_drupal index 8102385..75338f9 100755 --- a/bin/init_drupal +++ b/bin/init_drupal @@ -10,33 +10,47 @@ disclaimer="\n// Generated by Wodby." settings_php="${DRUPAL_SITE_DIR}/settings.php" default_settings_php="${DRUPAL_SITE_DIR}/default.settings.php" sites_php="${DRUPAL_ROOT}/sites/sites.php" +settings_include="include '${CONF_DIR}/wodby.settings.php';" +sites_include="include '${CONF_DIR}/wodby.sites.php';" -# A workspace checkout belongs to the developer. Preflight every generated path -# before making any changes; only ignored, untracked paths may be generated. +# A workspace checkout belongs to the developer. Settings are created or +# extended as in a build, but the include is skipped where Wodby's file doesn't +# exist, so the developer can commit it. Every path is checked before anything +# changes: nothing outside the checkout is modified, and the files directory +# becomes a link only when Git ignores it and doesn't track it. if [[ "${WODBY_WORKSPACE:-}" == 1 ]]; then root=$(git -C "${APP_ROOT}" rev-parse --show-toplevel) || exit 1 - workspace_generated_path() { - local target relative tracked - target="$(cd "$(dirname "$1")" && pwd -P)/$(basename "$1")" || return 1 + # Prints the path relative to the checkout, or fails for a path outside it. + workspace_path() { + local parent target + parent=$(dirname "$1") + # The site directory may not exist yet. + while [[ ! -d "$parent" ]]; do parent=$(dirname "$parent"); done + target="$(cd "$parent" && pwd -P)${1#"$parent"}" || return 1 if [[ -L "$1" ]]; then target=$(realpath "$1") || return 1; fi - case "$target" in "$root"/*) relative="${target#"$root"/}" ;; *) + case "$target" in "$root"/*) echo "${target#"$root"/}" ;; *) echo "Workspace setup refuses to modify a path outside the checkout: $1" >&2; return 1 ;; esac - tracked=$(git -C "$root" ls-files -- "$relative") || return 1 - if [[ -n "$tracked" ]] || ! git -C "$root" check-ignore -q -- "$relative"; then - echo "Workspace setup would change $relative. Add the required Wodby settings include explicitly, or ignore this generated path and remove it from Git tracking before retrying preparation." >&2 - return 1 - fi } - if [[ ! -f "$settings_php" ]] || ! grep -qi 'wodby.settings.php' "$settings_php"; then - workspace_generated_path "$settings_php" - fi - if [[ "${DRUPAL_SITE}" != default ]] && { [[ ! -f "$sites_php" ]] || ! grep -qi 'wodby.sites.php' "$sites_php"; }; then - workspace_generated_path "$sites_php" + # Prints PHP that includes Wodby's file $1 through the variable $2 when it exists. + workspace_include() { + printf '%s\n' "\$$2 = (getenv('CONF_DIR') ?: '/var/www/conf') . '/$1';" "if (is_file(\$$2)) {" " include \$$2;" "}" + } + workspace_path "$settings_php" >/dev/null + if [[ "${DRUPAL_SITE}" != default ]]; then + workspace_path "$sites_php" >/dev/null fi + # The link replaces the directory, so its contents must not be in Git. if [[ ! -L "${DRUPAL_SITE_DIR}/files" ]]; then - workspace_generated_path "${DRUPAL_SITE_DIR}/files" + files=$(workspace_path "${DRUPAL_SITE_DIR}/files") + tracked=$(git -C "$root" ls-files -- "$files") + if [[ -n "$tracked" ]] || ! git -C "$root" check-ignore -q -- "$files"; then + echo "Workspace setup replaces $files with a link to the files volume. Ignore it in Git and remove it from Git tracking before retrying setup." >&2 + exit 1 + fi fi + settings_include=$(workspace_include wodby.settings.php wodbyConfig) + sites_include=$(workspace_include wodby.sites.php wodbySites) fi mkdir -p "${DRUPAL_SITE_DIR}" @@ -58,7 +72,7 @@ fi if [[ $( grep -ic "wodby.settings.php" "${settings_php}" ) -eq 0 ]]; then chmod 644 "${settings_php}" echo -e "${disclaimer}" >> "${settings_php}" - echo -e "include '${CONF_DIR}/wodby.settings.php';" >> "${settings_php}" + printf '%s\n' "${settings_include}" >> "${settings_php}" fi if [[ "${DRUPAL_SITE}" != "default" ]]; then @@ -68,7 +82,7 @@ if [[ "${DRUPAL_SITE}" != "default" ]]; then if [[ $( grep -ic "wodby.sites.php" "${sites_php}" ) -eq 0 ]]; then echo -e "${disclaimer}" >> "${sites_php}" - echo -e "include '${CONF_DIR}/wodby.sites.php';" >> "${sites_php}" + printf '%s\n' "${sites_include}" >> "${sites_php}" fi fi diff --git a/tests/workspace-checkout.sh b/tests/workspace-checkout.sh index 6722107..a212458 100644 --- a/tests/workspace-checkout.sh +++ b/tests/workspace-checkout.sh @@ -3,38 +3,83 @@ set -euo pipefail script="$(cd "$(dirname "$0")/.." && pwd)/bin/init_drupal" fixture=$(mktemp -d) -trap 'rm -rf "$fixture"' EXIT +outside=$(mktemp -d) +trap 'rm -rf "$fixture" "$outside"' EXIT export APP_ROOT="$fixture" DRUPAL_ROOT="$fixture/web" DRUPAL_SITE=default export DRUPAL_SITE_DIR="$DRUPAL_ROOT/sites/default" CONF_DIR=/var/www/conf FILES_DIR="$fixture/uploads" WODBY_WORKSPACE=1 DEBUG='' +settings="$DRUPAL_SITE_DIR/settings.php" mkdir -p "$DRUPAL_SITE_DIR" "$fixture/bin" printf '#!/bin/sh\n[ -L "$1" ] || ln -s "$FILES_DIR/public" "$1"\n' > "$fixture/bin/files_link" chmod +x "$fixture/bin/files_link" export PATH="$fixture/bin:$PATH" git -C "$fixture" init -q -printf ' "$DRUPAL_SITE_DIR/settings.php" -printf 'web/sites/default/files\n' > "$fixture/.gitignore" +printf ' "$settings" +printf 'web/sites/*/files\n' > "$fixture/.gitignore" git -C "$fixture" add .gitignore web/sites/default/settings.php + +# PHP checks run where PHP is installed, such as CI and the image itself. +php_check() { + if command -v php >/dev/null; then php -l "$1" >/dev/null; fi +} + +# Tracked settings get an include that is skipped outside Wodby, as a change to commit. +bash "$script" +test -L "$DRUPAL_SITE_DIR/files" +grep -q '// customer settings' "$settings" +grep -Fq "\$wodbyConfig = (getenv('CONF_DIR') ?: '/var/www/conf') . '/wodby.settings.php';" "$settings" +grep -Fq 'if (is_file($wodbyConfig)) {' "$settings" +test "$(git -C "$fixture" diff --numstat | cut -f1-2)" = "$(printf '6\t0')" +php_check "$settings" +if command -v php >/dev/null; then + conf=$(mktemp -d "$outside/conf.XXXXXX") + printf ' "$conf/wodby.settings.php" + test "$(CONF_DIR="$conf" php -r 'include $argv[1]; echo empty($settings["wodby"]) ? "skipped" : "included";' "$settings")" = included + test "$(CONF_DIR="$outside/missing" php -r 'include $argv[1]; echo empty($settings["wodby"]) ? "skipped" : "included";' "$settings")" = skipped +fi + +# Running setup again changes nothing. before=$(git -C "$fixture" diff --binary) -if bash "$script" > "$fixture/error" 2>&1; then echo 'Modified tracked settings' >&2; exit 1; fi +bash "$script" test "$before" = "$(git -C "$fixture" diff --binary)" -test ! -L "$DRUPAL_SITE_DIR/files" -grep -q 'Add the required Wodby settings include' "$fixture/error" + # Explicitly configured tracked settings are left byte-for-byte unchanged. -printf " "$DRUPAL_SITE_DIR/settings.php" +printf " "$settings" git -C "$fixture" add web/sites/default/settings.php bash "$script" test -z "$(git -C "$fixture" diff --binary)" -# Missing settings can be generated only when ignored and untracked. + +# Missing settings are created from default.settings.php, tracked or not. git -C "$fixture" rm --cached -q web/sites/default/settings.php -rm "$DRUPAL_SITE_DIR/settings.php" -if bash "$script" > "$fixture/error" 2>&1; then echo 'Generated visible settings' >&2; exit 1; fi -printf 'web/sites/default/settings.php\n' >> "$fixture/.gitignore" +rm "$settings" +printf ' "$DRUPAL_SITE_DIR/default.settings.php" bash "$script" -grep -q wodby.settings.php "$DRUPAL_SITE_DIR/settings.php" -# Tracked upload placeholders must never be removed, even if .gitignored. +grep -q '// Drupal defaults' "$settings" +grep -Fq 'include $wodbyConfig;' "$settings" +php_check "$settings" + +# Multisite projects get the same guarded include in sites.php. +DRUPAL_SITE=example DRUPAL_SITE_DIR="$DRUPAL_ROOT/sites/example" bash "$script" +grep -Fq "\$wodbySites = (getenv('CONF_DIR') ?: '/var/www/conf') . '/wodby.sites.php';" "$DRUPAL_ROOT/sites/sites.php" +grep -Fq 'include $wodbyConfig;' "$DRUPAL_ROOT/sites/example/settings.php" +php_check "$DRUPAL_ROOT/sites/sites.php" + +# Settings linked outside the checkout are never changed. +rm "$settings" +printf ' "$outside/settings.php" +ln -s "$outside/settings.php" "$settings" +if bash "$script" > "$fixture/error" 2>&1; then echo 'Modified settings outside the checkout' >&2; exit 1; fi +grep -q 'outside the checkout' "$fixture/error" +test "$(cat "$outside/settings.php")" = ' "$settings" + +# Tracked upload placeholders must never be removed, even if .gitignored, and +# settings stay unchanged when setup stops. rm "$DRUPAL_SITE_DIR/files" mkdir "$DRUPAL_SITE_DIR/files" printf '*' > "$DRUPAL_SITE_DIR/files/.gitignore" git -C "$fixture" add -f web/sites/default/files/.gitignore if bash "$script" > "$fixture/error" 2>&1; then echo 'Replaced tracked uploads' >&2; exit 1; fi +grep -q 'link to the files volume' "$fixture/error" test -f "$DRUPAL_SITE_DIR/files/.gitignore" +test "$(cat "$settings")" = '