From 83f99b71a36fd6782346517e6dc97948cf281b32 Mon Sep 17 00:00:00 2001 From: Zequi Vazquez Date: Thu, 18 Dec 2025 16:10:34 +0100 Subject: [PATCH 01/16] #263: Refactor Tugboat integration --- README.md | 73 ++- composer.json | 3 +- scaffold/tugboat/config.yml.twig | 82 --- .../tugboat/scripts/custom-init-command.sh | 25 + .../tugboat/scripts/install-mysql-client.sh | 0 scaffold/tugboat/steps/1-init.sh.twig | 105 ---- scaffold/tugboat/steps/2-update.sh.twig | 18 - scaffold/tugboat/steps/3-build.sh.twig | 11 - scaffold/tugboat/steps/4-online.sh.twig | 9 - scaffold/tugboat/templates/config.yml.twig | 73 +++ scaffold/tugboat/templates/php-build.yml.twig | 5 + scaffold/tugboat/templates/php-init.yml.twig | 66 ++ .../tugboat/templates/php-update.yml.twig | 15 + .../{ => templates}/settings.tugboat.php.twig | 2 +- src/ScaffoldInstallerPlugin.php | 264 -------- src/TugboatConfigPlugin.php | 584 ++++++++++++++++++ 16 files changed, 832 insertions(+), 503 deletions(-) delete mode 100644 scaffold/tugboat/config.yml.twig create mode 100644 scaffold/tugboat/scripts/custom-init-command.sh mode change 100755 => 100644 scaffold/tugboat/scripts/install-mysql-client.sh delete mode 100644 scaffold/tugboat/steps/1-init.sh.twig delete mode 100644 scaffold/tugboat/steps/2-update.sh.twig delete mode 100644 scaffold/tugboat/steps/3-build.sh.twig delete mode 100644 scaffold/tugboat/steps/4-online.sh.twig create mode 100644 scaffold/tugboat/templates/config.yml.twig create mode 100644 scaffold/tugboat/templates/php-build.yml.twig create mode 100644 scaffold/tugboat/templates/php-init.yml.twig create mode 100644 scaffold/tugboat/templates/php-update.yml.twig rename scaffold/tugboat/{ => templates}/settings.tugboat.php.twig (97%) create mode 100644 src/TugboatConfigPlugin.php diff --git a/README.md b/README.md index 61f170b66..2e3a1b0aa 100644 --- a/README.md +++ b/README.md @@ -94,8 +94,8 @@ upstream dependencies e.g. https://github.com/go-task/task/releases ### Tugboat The Tugboat configuration file is not a static file; it is dynamically generated -based on your `.ddev/config` file. To see the implementation details, check -[/src/ScaffoldInstallerPlugin.php](https://github.com/Lullabot/drainpipe/blob/main/src/ScaffoldInstallerPlugin.php#L451). +based on your `.ddev/config.yaml` file. To see the implementation details, check +[/src/TugboatConfigPlugin.php](https://github.com/Lullabot/drainpipe/blob/main/src/TugboatConfigPlugin.php). ### Node JS @@ -790,26 +790,53 @@ Add the following to `composer.json` to add Tugboat configuration: } ``` +Then, run `ddev composer install` to generate: +- `.tugboat/config.yml` - Complete Tugboat configuration +- `.tugboat/scripts/` - Helper scripts (if needed) +- `web/sites/default/settings.tugboat.php` - Drupal settings for Tugboat + The following will be autodetected based on your `.ddev/config.yml`: - Web server (nginx or apache) - PHP version - Database type and version -- nodejs version +- Nodejs version - Redis (Obtained with `ddev get ddev/ddev-redis`) +- Solr (Obtained with `ddev get ddev/ddev-solr`) -Additionally, Pantheon Terminus can be added: +Additionally, Pantheon integration can be added: ```json { "extra": { "drainpipe": { "tugboat": { - "terminus": true + "pantheon": true } } } } ``` +### Custom Templates + +For the main `php` service, you can override any build phase template by copying +it to `.tugboat/drainpipe-templates/`. Example: + +``` +mkdir -p .tugboat/drainpipe-templates +cp vendor/lullabot/drainpipe/scaffold/tugboat/templates/php-init.yml.twig \ + .tugboat/drainpipe-templates/ +``` + +Edit `.tugboat/drainpipe-templates/php-init.yml.twig` to add, remove, or modify +commands, then regenerate the Tugboat configuration file with `ddev composer install`. + +Available Templates: + +- `php-init.yml.twig` - Init phase commands +- `php-update.yml.twig` - Update phase commands +- `php-build.yml.twig` - Build phase commands +- `config.yml.twig` - Complete Tugboat configuration (advanced) + ### Tasks Tugboat specific tasks are contained in [`tasks/tugboat.yml`](tasks/tugboat.yml). @@ -857,8 +884,20 @@ task to your `Taskfile.yml` for the first time). configuration. >>> -You can hook into the `init` step of images by adding them to your -`Taskfile.yml`, e.g. +#### Custom init commands + +You can hook into the `init` step of any service by adding them to your +`Taskfile.yml`. These additional commands will be run at the end of the +init phase for the specific Tugboat service. + +Supported services: + +- Webserver: `tugboat:php:init` +- Database: `tugboat:mysql:init` / `tugboat:mariadb:init` / `tugboat:postgres:init` +- Memory cache: `tugboat:redis:init` / `tugboat:memcached:init` +- Search: `tugboat:solr:init` / `tugboat:elasticsearch:init` + +Example: ``` tugboat:php:init: @@ -867,8 +906,21 @@ tugboat:php:init: - docker-php-ext-install ldap ``` -You can also add an `online` step by adding a task named `online:tugboat` -and re-running `composer install`. +If using MySQL, you should add an init task to install the MySQL client: + +``` +tugboat:mysql:init: + desc: "Install MySQL client" + cmds: + - ./.tugboat/scripts/install-mysql-client.sh +``` + +#### Custom online commands + +You can also add an `online` step to the `php` service by adding a task +named `online:tugboat` and re-running `composer install`. + +### Additional Tugboat keys Drainpipe will fully manage your `.tugboat/config.yml` file, you should not edit it. The following keys can be added to your `config.yml` via a @@ -880,11 +932,8 @@ php: screenshot: visualdiff: solr: - commands: checkout: depends: - aliases: - urls: volumes: environment: ``` diff --git a/composer.json b/composer.json index 5d4fb92be..73cf8b352 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,8 @@ "extra": { "class": [ "\\Lullabot\\Drainpipe\\TaskfileInstallerPlugin", - "\\Lullabot\\Drainpipe\\ScaffoldInstallerPlugin" + "\\Lullabot\\Drainpipe\\ScaffoldInstallerPlugin", + "\\Lullabot\\Drainpipe\\TugboatConfigPlugin" ], "drupal-scaffold": { "gitignore": true, diff --git a/scaffold/tugboat/config.yml.twig b/scaffold/tugboat/config.yml.twig deleted file mode 100644 index 1c76ca7e6..000000000 --- a/scaffold/tugboat/config.yml.twig +++ /dev/null @@ -1,82 +0,0 @@ -# DO NOT EDIT THIS FILE -# This file is controlled by Drainpipe, run composer install to apply pending -# updates. You can add values to the php service using .tugboat/config.drainpipe-override.yml. -# -# Example config.drainpipe-override.yml -# php: -# aliases: -# urls: -# screenshot: -# visualdiff: -# solr: -# commands: -# checkout: -# depends: -# volumes: -# environment: -services: - php: - http: false - image: {{ webserver_image }} - default: true - - depends: - - {{ database_type }} -{% if memory_cache_type %} - - {{ memory_cache_type }} -{% endif %} - - commands: - init: ./.tugboat/steps/1-init.sh - update: ./.tugboat/steps/2-update.sh - build: ./.tugboat/steps/3-build.sh -{% if online_command|length > 0 %} - online: ./.tugboat/steps/4-online.sh -{% endif %} - -{% if overrides.php|length > 0 %} -{{ overrides.php|raw }} -{% endif %} - - {{ database_type }}: - image: tugboatqa/{{ database_type }}:{{ database_version }} -{% if init.mysql %} - checkout: true - commands: - init: - - sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d $(cat .taskfile) -b /usr/local/bin - - wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -O /usr/bin/yq && chmod +x /usr/bin/yq - - yq -i 'del(."includes")' Taskfile.yml - - task tugboat:mysql:init -{% endif %} -{% if memory_cache_type %} - - {{ memory_cache_type }}: - image: tugboatqa/{{ memory_cache_type }}:{{ memory_cache_version }} -{% if init.redis %} - checkout: true - commands: - init: - - sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d $(cat .taskfile) -b /usr/local/bin - - wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -O /usr/bin/yq && chmod +x /usr/bin/yq - - yq -i 'del(."includes")' Taskfile.yml - - task tugboat:redis:init -{% endif %} -{% endif %} -{% if search_type %} - - {{ search_type }}: - image: tugboatqa/{{ search_type }}:{{ search_version }} -{% if search_type == 'solr' and init.solr %} - checkout: true - commands: - init: - - sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d $(cat .taskfile) -b /usr/local/bin - - wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -O /usr/bin/yq && chmod +x /usr/bin/yq - - yq -i 'del(."includes")' Taskfile.yml - - task tugboat:solr:init -{% endif %} -{% if search_type == 'solr' and overrides.solr|length > 0 %} -{{ overrides.solr|raw }} -{% endif %} -{% endif %} diff --git a/scaffold/tugboat/scripts/custom-init-command.sh b/scaffold/tugboat/scripts/custom-init-command.sh new file mode 100644 index 000000000..7570e0a23 --- /dev/null +++ b/scaffold/tugboat/scripts/custom-init-command.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# DO NOT EDIT THIS FILE +# This file is controlled by Drainpipe, run composer install to apply pending +# updates. + +set -euo pipefail + +type="${1:-}" +if [[ -z "$type" ]]; then + echo "Usage: $0 " >&2 + exit 2 +fi + +# Install Taskfile +TASKFILE=$(cat ${TUGBOAT_ROOT}/vendor/lullabot/drainpipe/.taskfile) +sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d ${TASKFILE} -b /usr/local/bin + +# Install YQ +wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -O /usr/bin/yq && chmod +x /usr/bin/yq + +# Ensure includes are not executed +yq -i 'del(."includes")' Taskfile.yml + +# Execute the custom command +task "tugboat:${type}:init" diff --git a/scaffold/tugboat/scripts/install-mysql-client.sh b/scaffold/tugboat/scripts/install-mysql-client.sh old mode 100755 new mode 100644 diff --git a/scaffold/tugboat/steps/1-init.sh.twig b/scaffold/tugboat/steps/1-init.sh.twig deleted file mode 100644 index f40428294..000000000 --- a/scaffold/tugboat/steps/1-init.sh.twig +++ /dev/null @@ -1,105 +0,0 @@ -#!/bin/bash -# DO NOT EDIT THIS FILE -# This file is controlled by Drainpipe, run composer install to apply pending -# updates. - -set -eux -echo "Initializing..." - -# Install task -sh -c "$(curl --location https://raw.githubusercontent.com/go-task/task/v3.45.4/install-task.sh)" -- -d -b /usr/local/bin - -# Install mysql or mariadb client. -apt-get update -{% if database_type == "mysql" %} -# mysql was replaced by mariadb in Debian, so we have to install it manually. -# https://dev.mysql.com/doc/mysql-apt-repo-quick-guide/en/#repo-qg-apt-repo-manual-setup -# apt-key is also deprecated, so here we install the key manually. -./.tugboat/scripts/install-mysql-client.sh -{% else %} -apt-get install -y {{ database_type }}-client -{% endif %} - -# Link the document root to the expected path. Tugboat uses /docroot -# by default. So, if Drupal is located at any other path in your git -# repository, change that here. This example links /web to the docroot -ln -snf "${TUGBOAT_ROOT}/web" "${DOCROOT}" - -# Install the PHP opcache as it's not included by default and needed for -# decent performance. -docker-php-ext-install opcache - -# GD dependencies. -apt-get install -y libpng-dev libjpeg-dev libfreetype6-dev - -# WebP dependencies. -apt-get install -y libwebp-dev libwebp7 webp libmagickwand-dev - -# Build and install gd. -docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp -docker-php-ext-install gd - -# Install ImageMagick. This is recommended by both Acquia and Pantheon instead -# of GD. Lullabot will likely be publishing an ADR recommending it too. -apt-get install -y imagemagick -{% if memory_cache_type == "memcached" %} - -# Install the PHP memcache extension. - -apt-get install -y zlib1g-dev -yes '' | pecl install -f memcache -echo 'extension=memcache.so' > /usr/local/etc/php/conf.d/memcache.ini -{% elseif memory_cache_type == "redis" %} - -# Install the PHP redis extension. -yes '' | pecl install -f redis -echo 'extension=redis.so' > /usr/local/etc/php/conf.d/redis.ini -{% endif %} - -# Install node -apt-get install -y ca-certificates gnupg -mkdir -p /etc/apt/keyrings -curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg -NODE_MAJOR={{ nodejs_version }} -echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list - -# Ensure we use nodesource packages, even if Debian ships newer major versions. -cat << EOD > /etc/apt/preferences.d/nodesource-nodejs -Package: * -Pin: origin deb.nodesource.com -Pin-Priority: 1001 -EOD - -apt-get update -apt-get -qq install nodejs -apt-get clean -# This only works for node > 16, but that version is unsupported now anyway. -corepack enable - -# Validate we have the right nodejs version. -nodejs -v | grep -q v$NODE_MAJOR - -{% if 'apache' in webserver_image %} -# Apache -a2enmod headers rewrite -{% endif %} -{% if pantheon %} - -# Pantheon -curl --fail -sSL https://github.com/pantheon-systems/terminus/releases/download/$(curl -L --fail --silent "https://api.github.com/repos/pantheon-systems/terminus/releases/latest" | perl -nle'print $& while m{"tag_name": "\K.*?(?=")}g')/terminus.phar --output /usr/local/bin/terminus -chmod 777 /usr/local/bin/terminus -{% endif %} -{% if init.php %} - -# Bring in tasks. -composer install --ignore-platform-reqs -task tugboat:php:init -{% endif %} - -# Create the Drupal private and public files directories if they aren't -# already present. -mkdir -p "${DOCROOT}/sites/default/files" -chmod 777 "${DOCROOT}/sites/default/files" -chgrp -R www-data "${DOCROOT}/sites/default/files" - -composer install diff --git a/scaffold/tugboat/steps/2-update.sh.twig b/scaffold/tugboat/steps/2-update.sh.twig deleted file mode 100644 index d582d6927..000000000 --- a/scaffold/tugboat/steps/2-update.sh.twig +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash -# DO NOT EDIT THIS FILE -# This file is controlled by Drainpipe, run composer install to apply pending -# updates. - -set -eux -echo "Updating..." - -composer install -./vendor/bin/task {{ sync_command }} - -# Ensure the sites/default directory is writable. -chmod 755 ${DOCROOT}/sites/default - -# Set file permissions such that Drupal will not complain. -chgrp -R www-data "${DOCROOT}/sites/default/files" -find "${DOCROOT}/sites/default/files" -type d -exec chmod 2775 {} \; -find "${DOCROOT}/sites/default/files" -type f -exec chmod 0664 {} \; diff --git a/scaffold/tugboat/steps/3-build.sh.twig b/scaffold/tugboat/steps/3-build.sh.twig deleted file mode 100644 index fe3994920..000000000 --- a/scaffold/tugboat/steps/3-build.sh.twig +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash -# DO NOT EDIT THIS FILE -# This file is controlled by Drainpipe, run composer install to apply pending -# updates. - -set -eux -echo "Building..." - -./vendor/bin/task {{ build_command }} -./vendor/bin/task {{ update_command }} -./vendor/bin/task tugboat:drush-uli-ready diff --git a/scaffold/tugboat/steps/4-online.sh.twig b/scaffold/tugboat/steps/4-online.sh.twig deleted file mode 100644 index 3fafdf16c..000000000 --- a/scaffold/tugboat/steps/4-online.sh.twig +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash -# DO NOT EDIT THIS FILE -# This file is controlled by Drainpipe, run composer install to apply pending -# updates. - -set -eux -echo "Online..." - -./vendor/bin/task {{ online_command }} diff --git a/scaffold/tugboat/templates/config.yml.twig b/scaffold/tugboat/templates/config.yml.twig new file mode 100644 index 000000000..47d1a4b2c --- /dev/null +++ b/scaffold/tugboat/templates/config.yml.twig @@ -0,0 +1,73 @@ +# DO NOT EDIT THIS FILE +# This file is controlled by Drainpipe, run composer install to apply pending +# updates. You can add values to the php and solr services adding a file named +# config.drainpipe-override.yml to the .tugboat folder. +# +# Example config.drainpipe-override.yml +# php: +# aliases: +# urls: +# screenshot: +# visualdiff: +# solr: +# commands: +# checkout: +# depends: +# volumes: +# environment: +services: + php: + http: false + image: {{ services.php.image }} + default: true + depends: +{% for item in services.php.depends %} + - {{ item }} +{% endfor %} + commands: + init: +{{ include('php-init.yml.twig')|indent(8)|raw }} + update: +{{ include('php-update.yml.twig')|indent(8)|raw }} + build: +{{ include('php-build.yml.twig')|indent(8)|raw }} +{% if services.php.commands.online is defined and services.php.commands.online %} + online: + - task {{ services.php.commands.online }} +{% endif %} +{% if services.php.overrides|length > 0 %} +{{ services.php.overrides|yaml_encode|indent(4)|raw }} +{% endif %} + + {{ services.database.type }}: + image: {{ services.database.image }} +{% if services.database.commands.init|default(false) %} + commands: + init: + - ./.tugboat/scripts/custom-init-command.sh {{ services.database.type }} +{% endif %} +{% if services.memory_cache %} + + {{ services.memory_cache.type }}: + image: {{ services.memory_cache.image }} +{% if services.memory_cache.commands.init|default(false) %} + checkout: true + commands: + init: + - ./.tugboat/scripts/custom-init-command.sh {{ services.memory_cache.type }} +{% endif %} +{% endif %} +{% if services.search %} + + {{ services.search.type }}: + image: {{ services.search.image }} +{% if services.search.commands.init|default(false) %} + checkout: true + commands: + init: + - ./.tugboat/scripts/custom-init-command.sh {{ services.search.type }} +{% endif %} +{% if services.search.type == 'solr' and services.search.overrides|length > 0 %} +{{ services.search.overrides|yaml_encode|indent(4)|raw }} +{% endif %} +{% endif %} diff --git a/scaffold/tugboat/templates/php-build.yml.twig b/scaffold/tugboat/templates/php-build.yml.twig new file mode 100644 index 000000000..65adda301 --- /dev/null +++ b/scaffold/tugboat/templates/php-build.yml.twig @@ -0,0 +1,5 @@ +- task build +- task update +{% if services.php.commands.build is defined and services.php.commands.build %} +- task {{ services.php.commands.build }} +{% endif %} diff --git a/scaffold/tugboat/templates/php-init.yml.twig b/scaffold/tugboat/templates/php-init.yml.twig new file mode 100644 index 000000000..81fc99a3e --- /dev/null +++ b/scaffold/tugboat/templates/php-init.yml.twig @@ -0,0 +1,66 @@ +- | + # Install mysql or mariadb client. + apt-get update + apt-get install -y mariadb-client +- | + # Link the document root to the expected path. Tugboat uses /docroot + # by default. So, if Drupal is located at any other path in your git + # repository, change that here. This example links /web to the docroot + ln -snf "${TUGBOAT_ROOT}/web" "${DOCROOT}" +- | + # Install the PHP opcache as it's not included by default and needed for + # decent performance. + docker-php-ext-install opcache +- | + # GD dependencies. + apt-get install -y libpng-dev libjpeg-dev libfreetype6-dev +- | + # WebP dependencies. + apt-get install -y libwebp-dev libwebp7 webp libmagickwand-dev +- | + # Build and install gd. + docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp + docker-php-ext-install gd +- | + # Install ImageMagick. This is recommended by both Acquia and Pantheon instead + # of GD. Lullabot will likely be publishing an ADR recommending it too. + apt-get install -y imagemagick +- | + # Install the PHP redis extension. + yes '' | pecl install -f redis + echo 'extension=redis.so' > /usr/local/etc/php/conf.d/redis.ini +- | + # Create the Drupal private and public files directories if they aren't + # already present. + mkdir -p "${DOCROOT}/sites/default/files" + chmod 777 "${DOCROOT}/sites/default/files" + chgrp -R www-data "${DOCROOT}/sites/default/files" +- composer install +- | + # Install node + apt-get install -y ca-certificates gnupg + mkdir -p /etc/apt/keyrings + curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg + NODE_MAJOR=$(cat ${TUGBOAT_ROOT}/.nvmrc) + echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list +- | + # Ensure we use nodesource packages, even if Debian ships newer major versions. + echo 'Package: *' > /etc/apt/preferences.d/nodesource-nodejs + echo 'Pin: origin deb.nodesource.com' >> /etc/apt/preferences.d/nodesource-nodejs + echo 'Pin-Priority: 1001' >> /etc/apt/preferences.d/nodesource-nodejs +- apt-get update +- apt-get -qq install nodejs +- apt-get clean +- | + # This only works for node > 16, but that version is unsupported now anyway. + corepack enable +- | + # Validate we have the right nodejs version. + nodejs -v | grep -q v$NODE_MAJOR +- | + # Install task + TASKFILE=$(cat ${TUGBOAT_ROOT}/vendor/lullabot/drainpipe/.taskfile) + sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d ${TASKFILE} -b /usr/local/bin +{% if services.php.commands.init|default(false) %} +- task tugboat:php:init +{% endif %} diff --git a/scaffold/tugboat/templates/php-update.yml.twig b/scaffold/tugboat/templates/php-update.yml.twig new file mode 100644 index 000000000..cdbdeff67 --- /dev/null +++ b/scaffold/tugboat/templates/php-update.yml.twig @@ -0,0 +1,15 @@ +- composer install +{% if services.php.commands.sync is defined and services.php.commands.sync %} +- task {{ services.php.commands.sync }} +{% endif %} +- | + # Ensure the sites/default directory is writable. + chmod 755 ${DOCROOT}/sites/default +- | + # Set file permissions such that Drupal will not complain. + chgrp -R www-data "${DOCROOT}/sites/default/files" + find "${DOCROOT}/sites/default/files" -type d -exec chmod 2775 {} \; + find "${DOCROOT}/sites/default/files" -type f -exec chmod 0664 {} \; +{% if services.php.commands.update is defined and services.php.commands.update %} +- task {{ services.php.commands.update }} +{% endif %} diff --git a/scaffold/tugboat/settings.tugboat.php.twig b/scaffold/tugboat/templates/settings.tugboat.php.twig similarity index 97% rename from scaffold/tugboat/settings.tugboat.php.twig rename to scaffold/tugboat/templates/settings.tugboat.php.twig index 55944c4dd..2a8a083c6 100644 --- a/scaffold/tugboat/settings.tugboat.php.twig +++ b/scaffold/tugboat/templates/settings.tugboat.php.twig @@ -16,7 +16,7 @@ if (getenv('TUGBOAT_REPO')) { 'username' => 'tugboat', 'password' => 'tugboat', 'prefix' => '', - 'host' => '{{ database_type }}', + 'host' => '{{ services.database.type }}', 'port' => '3306', 'namespace' => 'Drupal\\Core\\Database\\Driver\\mysql', 'driver' => 'mysql', diff --git a/src/ScaffoldInstallerPlugin.php b/src/ScaffoldInstallerPlugin.php index 40f74b27f..300bc3862 100644 --- a/src/ScaffoldInstallerPlugin.php +++ b/src/ScaffoldInstallerPlugin.php @@ -371,7 +371,6 @@ private function installCICommands(Composer $composer): void $scaffoldPath = $this->config->get('vendor-dir') . '/lullabot/drainpipe/scaffold'; $this->installGitlabCI($scaffoldPath, $composer); $this->installGitHubActions($scaffoldPath, $composer); - $this->installTugboat($scaffoldPath); } /** @@ -543,267 +542,4 @@ private function installGitHubActions(string $scaffoldPath, Composer $composer): } } - /** - * Installs Tugboat if defined in composer.json. - * - * @param string $scaffoldPath - */ - private function installTugboat(string $scaffoldPath): void { - $fs = new Filesystem(); - - if (!isset($this->extra['drainpipe']['tugboat']) || !is_array($this->extra['drainpipe']['tugboat'])) { - return; - } - - // Look for a config override file before we wipe the directory. - $tugboatConfigOverride = []; - $tugboatConfigOverridePath = './.tugboat/config.drainpipe-override.yml'; - if (file_exists($tugboatConfigOverridePath)) { - $tugboatConfigOverride = Yaml::parseFile($tugboatConfigOverridePath); - $tugboatConfigOverrideFile = file_get_contents($tugboatConfigOverridePath); - } - - // Wipe the Tugboat directory and define base config. - $filesToRemove = [ - './.tugboat/config.drainpipe-override.yml', - './.tugboat/config.yml', - './.tugboat/steps/1-init.sh', - './.tugboat/steps/2-update.sh', - './.tugboat/steps/3-build.sh', - './.tugboat/steps/4-online.sh', - './.tugboat/scripts/install-mysql-client.sh', - ]; - foreach ($filesToRemove as $file) { - if (file_exists($file)) { - $fs->remove($file); - } - } - $tugboatConfig = [ - 'nodejs_version' => '$(cat ${TUGBOAT_ROOT}/.nvmrc)', - 'webserver_image' => 'tugboatqa/php-nginx:8.1-fpm-bookworm', - 'database_type' => 'mariadb', - 'database_version' => '10.11', - 'php_version' => '8.1', - 'sync_command' => 'sync', - 'build_command' => 'build', - 'update_command' => 'drupal:update', - 'init' => [], - 'pantheon' => isset($this->extra['drainpipe']['tugboat']['pantheon']), - 'overrides' => ['php' => '', 'solr' => ''], - ]; - - // Read DDEV config. - if (file_exists('./.ddev/config.yaml')) { - $ddevConfig = Yaml::parseFile('./.ddev/config.yaml'); - $tugboatConfig['database_type'] = $ddevConfig['database']['type']; - $tugboatConfig['database_version'] = $ddevConfig['database']['version']; - $tugboatConfig['webserver_image'] = 'tugboatqa/php-nginx:' . $ddevConfig['php_version'] . '-fpm-bookworm'; - - if (!empty($ddevConfig['webserver_type']) && $ddevConfig['webserver_type'] === 'apache-fpm') { - $tugboatConfig['webserver_image'] = 'tugboatqa/php:' . $ddevConfig['php_version'] . '-apache-bookworm'; - } - } - - // Process PHP config overrides. - $tugboatConfig['overrides']['php'] = $this->processTugboatOverride( - $tugboatConfigOverride, - 'php', - ['aliases', 'urls', 'visualdiff', 'screenshot'] - ); - - // Extract Solr image configuration before filtering for service detection - $solrOverrideImage = null; - if (!empty($tugboatConfigOverride['solr']) && is_array($tugboatConfigOverride['solr'])) { - $solrOverrideImage = $tugboatConfigOverride['solr']['image'] ?? null; - } - - // Process Solr config overrides. - $tugboatConfig['overrides']['solr'] = $this->processTugboatOverride( - $tugboatConfigOverride, - 'solr', - ['commands', 'depends', 'aliases', 'urls', 'volumes', 'environment', 'checkout'] - ); - - // Add Redis service. - if (file_exists('./.ddev/docker-compose.redis.yaml')) { - $redisConfig = Yaml::parseFile('.ddev/docker-compose.redis.yaml'); - $image = $redisConfig['services']['redis']['image'] ?? ''; - - $version = self::extractRedisImageVersion($image); - $tugboatConfig['memory_cache_type'] = 'redis'; - $tugboatConfig['memory_cache_version'] = $version; - } - - // Add search service (mutually exclusive). - // Priority: Solr override -> Solr DDEV -> Elasticsearch DDEV - if (!empty($solrOverrideImage)) { - $solrImage = explode(':', $solrOverrideImage); - $tugboatConfig['search_type'] = 'solr'; - $tugboatConfig['search_version'] = array_pop($solrImage); - } - // Fall back to DDEV docker-compose configuration if not specified in override - elseif (file_exists('./.ddev/docker-compose.solr.yaml')) { - $solrConfig = Yaml::parseFile('.ddev/docker-compose.solr.yaml'); - $solrImage = explode(':', - $solrConfig['services']['solr']['image']); - $tugboatConfig['search_type'] = 'solr'; - $tugboatConfig['search_version'] = array_pop($solrImage); - } - elseif (file_exists('./.ddev/docker-compose.elasticsearch.yaml')) { - $esConfig = Yaml::parseFile('.ddev/docker-compose.elasticsearch.yaml'); - $esImage = explode(':', - $esConfig['services']['elasticsearch']['image']); - $tugboatConfig['search_type'] = 'elasticsearch'; - $tugboatConfig['search_version'] = array_pop($esImage); - } - - // Add commands to Task. - if (file_exists('Taskfile.yml')) { - // Get steps out of the Taskfile. - $taskfile = Yaml::parseFile('./Taskfile.yml'); - if (isset($taskfile['tasks']['sync:tugboat'])) { - $tugboatConfig['sync_command'] = 'sync:tugboat'; - } - if (isset($taskfile['tasks']['build:tugboat'])) { - $tugboatConfig['build_command'] = 'build:tugboat'; - } - if (isset($taskfile['tasks']['update'])) { - $tugboatConfig['update_command'] = 'update'; - } - if (isset($taskfile['tasks']['update:tugboat'])) { - $tugboatConfig['update_command'] = 'update:tugboat'; - } - if (isset($taskfile['tasks']['online:tugboat'])) { - $tugboatConfig['online_command'] = 'online:tugboat'; - } - if (isset($taskfile['tasks']['tugboat:php:init'])) { - $tugboatConfig['init']['php'] = TRUE; - } - if (isset($taskfile['tasks']['tugboat:mysql:init'])) { - $tugboatConfig['init']['mysql'] = TRUE; - } - if (isset($taskfile['tasks']['tugboat:redis:init'])) { - $tugboatConfig['init']['redis'] = TRUE; - } - if (isset($taskfile['tasks']['tugboat:solr:init'])) { - $tugboatConfig['init']['solr'] = TRUE; - } - } - - // Write the config.yml and settings.tugboat.php files. - if (count($tugboatConfig) > 0) { - $fs->ensureDirectoryExists('./.tugboat'); - $fs->ensureDirectoryExists('./.tugboat/steps'); - $loader = new FilesystemLoader(__DIR__ . '/../scaffold/tugboat'); - $twig = new Environment($loader); - // Reinstate the override file. - if (isset($tugboatConfigOverrideFile)) { - file_put_contents('./.tugboat/config.drainpipe-override.yml', - $tugboatConfigOverrideFile); - } - file_put_contents('./.tugboat/config.yml', - $twig->render('config.yml.twig', $tugboatConfig)); - file_put_contents('./.tugboat/steps/1-init.sh', - $twig->render('steps/1-init.sh.twig', $tugboatConfig)); - file_put_contents('./.tugboat/steps/2-update.sh', - $twig->render('steps/2-update.sh.twig', $tugboatConfig)); - file_put_contents('./.tugboat/steps/3-build.sh', - $twig->render('steps/3-build.sh.twig', $tugboatConfig)); - chmod('./.tugboat/steps/1-init.sh', 0755); - chmod('./.tugboat/steps/2-update.sh', 0755); - chmod('./.tugboat/steps/3-build.sh', 0755); - if (!empty($tugboatConfig['online_command'])) { - file_put_contents('./.tugboat/steps/4-online.sh', - $twig->render('steps/4-online.sh.twig', - $tugboatConfig)); - chmod('./.tugboat/steps/4-online.sh', 0755); - } - - if ($tugboatConfig['database_type'] === 'mysql') { - $fs->ensureDirectoryExists('./.tugboat/scripts'); - $fs->copy("$scaffoldPath/tugboat/scripts/install-mysql-client.sh", - './.tugboat/scripts/install-mysql-client.sh'); - chmod('./.tugboat/scripts/install-mysql-client.sh', 0755); - } - - file_put_contents('./web/sites/default/settings.tugboat.php', - $twig->render('settings.tugboat.php.twig', $tugboatConfig)); - if (file_exists('./web/sites/default/settings.php')) { - $settings = file_get_contents('./web/sites/default/settings.php'); - if (strpos($settings, 'settings.tugboat.php') === FALSE) { - $include = <<<'EOT' -include __DIR__ . "/settings.tugboat.php"; -EOT; - file_put_contents('./web/sites/default/settings.php', - $include . PHP_EOL, - FILE_APPEND); - } - } - } - } - - /** - * Processes Tugboat service configuration overrides. - * - * @param array $configOverride The configuration override array. - * @param string $service The service name (e.g., 'php', 'solr'). - * @param array $allowedKeys The keys allowed for this service override. - * @return string The formatted YAML string for the overrides. - */ - private function processTugboatOverride(array $configOverride, string $service, array $allowedKeys): string - { - if (empty($configOverride[$service]) || !is_array($configOverride[$service])) { - return ''; - } - - $filteredOverride = array_filter($configOverride[$service], - function($key) use ($allowedKeys) { - return in_array($key, $allowedKeys); - }, - ARRAY_FILTER_USE_KEY); - - $overrideOutput = []; - foreach (explode(PHP_EOL, Yaml::dump($filteredOverride, 2, 2)) as $line) { - $overrideOutput[] = str_repeat(' ', 4) . $line; - } - - return rtrim(implode("\n", $overrideOutput)); - } - - /** - * Extracts the Redis version tag from a Docker image string. - * - * Supports formats using environment variable fallbacks such as: - * - ${REDIS_DOCKER_IMAGE:-redis:7} - * - redis:${REDIS_TAG:-6-bullseye} - * As well as direct image values like: - * - redis:7-alpine - * - tugboatqa/redis:bookworm - * - * @param string $image The raw or interpolated image string from docker-compose.redis.yaml. - * - * @return string The extracted Redis version/tag (e.g. "7", "6-bullseye", "bookworm"). - * - * @throws \RuntimeException If the version tag cannot be extracted. - */ - public static function extractRedisImageVersion(string $image): string - { - // Normalize image from possible environment syntax. - // Example: $image = '${REDIS_DOCKER_IMAGE:-redis:7}'. - if (preg_match('/^\$\{[^:}]+:-(.+)\}$/', $image, $matches)) { - $image = $matches[1]; - // Example: $image = 'redis:${REDIS_TAG:-6-bullseye}'. - } elseif (preg_match('/^([^:]+):\$\{[^:}]+:-(.+)\}$/', $image, $matches)) { - $image = "{$matches[1]}:{$matches[2]}"; - } - - // Extract the version/tag from the image string. - // Example: $image = 'redis:6-bullseye' → $version = '6-bullseye' - if (!preg_match('/:([a-zA-Z0-9._-]+)$/', $image, $versionMatch)) { - throw new \RuntimeException("Unable to extract Redis version from image: {$image}"); - } - - return $versionMatch[1]; - } - } diff --git a/src/TugboatConfigPlugin.php b/src/TugboatConfigPlugin.php new file mode 100644 index 000000000..f74e26a95 --- /dev/null +++ b/src/TugboatConfigPlugin.php @@ -0,0 +1,584 @@ + 'redis', + '.ddev/docker-compose.memcached.yaml' => 'memcached', + '.ddev/docker-compose.solr.yaml' => 'solr', + '.ddev/docker-compose.elasticsearch.yaml' => 'elasticsearch', + ]; + + /** + * @var IOInterface + */ + protected $io; + + /** + * Composer instance configuration. + * + * @var Config + */ + protected $config; + + /** + * Composer extra field configuration. + * + * @var array + */ + protected $extra; + + /** + * {@inheritDoc} + */ + public function activate(Composer $composer, IOInterface $io) + { + $this->io = $io; + $this->config = $composer->getConfig(); + $this->extra = $composer->getPackage()->getExtra(); + } + + /** + * {@inheritDoc} + */ + public function deactivate(Composer $composer, IOInterface $io) + { + } + + /** + * {@inheritDoc} + */ + public function uninstall(Composer $composer, IOInterface $io) + { + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() + { + return [ + ScriptEvents::POST_INSTALL_CMD => 'onPostCmd', + ScriptEvents::POST_UPDATE_CMD => 'onPostCmd', + ]; + } + + /** + * Handle post install/update command events. + * + * @param Event $event the event to handle + */ + public function onPostCmd(Event $event) + { + $this->generateTugboatConfiguration(); + } + + /** + * Generate Tugboat configuration from DDEV settings. + */ + private function generateTugboatConfiguration(): void + { + // Check if Tugboat integration is enabled + if (!isset($this->extra['drainpipe']['tugboat']) || !is_array($this->extra['drainpipe']['tugboat'])) { + return; + } + + // Check if DDEV configuration exists + if (!file_exists('./.ddev/config.yaml')) { + $this->io->warning('🪠 [Drainpipe] DDEV configuration not found. Tugboat integration requires DDEV.'); + return; + } + + $this->io->write('🪠 [Drainpipe] Generating Tugboat configuration from DDEV...'); + + // Parse base services from DDEV configuration + $services = $this->parseBaseConfiguration(); + + // Detect and merge additional services from DDEV add-ons + $addons = $this->detectDdevAddons(); + $services = array_merge($services, $addons); + + // Webserver depends by default on all other services + foreach ($services as $name => $config) { + if ($name === 'php') { + continue; + } + $services['php']['depends'][] = $config['type']; + } + + // Parse and apply custom commands from Taskfile + $taskCommands = $this->parseTaskfileCommands(); + foreach ($taskCommands as $service => $commands) { + if (array_key_exists($service, $services)) { + $services[$service]['commands'] = $commands; + } + } + + // Load and apply override configuration + $overrideConfig = $this->loadOverrideConfig(); + $services = $this->applyOverrides($services, $overrideConfig); + + // Check for Pantheon integration + $isPantheon = isset($this->extra['drainpipe']['tugboat']['pantheon']) && + $this->extra['drainpipe']['tugboat']['pantheon']; + + + // Generate Tugboat config.yml + $this->generateConfigYml($services, $isPantheon); + + // Generate settings.tugboat.php + $this->generateSettingsFile($services); + + $this->io->write('🪠 [Drainpipe] Tugboat configuration generated successfully!'); + } + + /** + * Load configuration overrides from .tugboat/config.drainpipe-override.yml + * + * @return array The override configuration, or empty array if file doesn't exist + */ + private function loadOverrideConfig(): array + { + $overridePath = './.tugboat/config.drainpipe-override.yml'; + + if (!file_exists($overridePath)) { + return []; + } + + return Yaml::parseFile($overridePath); + } + + /** + * Install custom script. + */ + private function installScript(string $script): void + { + $fs = new Filesystem(); + $vendor = $this->config->get('vendor-dir'); + $scaffoldPath = $vendor . '/lullabot/drainpipe/scaffold'; + + $fs->ensureDirectoryExists('./.tugboat/scripts'); + $fs->copy( + "$scaffoldPath/tugboat/scripts/$script", + "./.tugboat/scripts/$script" + ); + chmod("./.tugboat/scripts/$script", 0755); + } + + /** + * Parse base services from DDEV main configuration file. + * + * @return array Array of core services (webserver, database) + */ + private function parseBaseConfiguration(): array + { + $ddevConfig = Yaml::parseFile('./.ddev/config.yaml'); + $services = []; + + // Webserver configuration + $phpVersion = $ddevConfig['php_version']; + $webserverType = $ddevConfig['webserver_type']; + + $services['php'] = [ + 'type' => $webserverType, + 'php_version' => $phpVersion, + 'image' => $this->mapToTugboatImage('php', $phpVersion, $webserverType), + 'default' => true, + 'depends' => [], + ]; + + // Database configuration + $dbType = $ddevConfig['database']['type']; + $dbVersion = $ddevConfig['database']['version']; + + $services['database'] = [ + 'type' => $dbType, + 'version' => $dbVersion, + 'image' => $this->mapToTugboatImage('database', $dbVersion, $dbType), + ]; + + // Copy MySQL client script if needed + if ($dbType === 'mysql') { + $this->installScript('install-mysql-client.sh'); + $services['database']['commands']['init'] = 1; + $this->io->warning("🪠 [Drainpipe] MySQL database detected. Remember to add a tugboat:mysql:init task to call .tugboat/scripts/install-mysql-client.sh"); + } + + return $services; + } + + /** + * Detect additional services from DDEV docker-compose add-on files. + * + * @return array Array of detected services + */ + private function detectDdevAddons(): array + { + $addons = []; + + foreach (self::DDEV_ADDON_SERVICES as $file => $serviceName) { + if (file_exists($file)) { + $config = Yaml::parseFile($file); + + $name = $serviceName; + switch ($serviceName) { + case 'memcached': + case 'redis': + $name = 'memory_cache'; + break; + case 'solr': + case 'elasticsearch': + $name = 'search'; + break; + } + + // Get the image from the service definition + $image = $config['services'][$serviceName]['image'] ?? null; + + if ($image) { + $version = $this->extractVersionFromImage($image); + + $addons[$name] = [ + 'type' => $serviceName, + 'version' => $version, + 'image' => $this->mapToTugboatImage($serviceName, $version), + ]; + + $this->io->write("🪠 [Drainpipe] Detected $serviceName:$version from DDEV"); + } + } + } + + return $addons; + } + + /** + * Extract version from Docker image string. + * + * Handles various formats: + * - redis:7 + * - redis:7-alpine + * - tugboatqa/redis:7 + * - ${VAR:-redis:7} + * - redis:${TAG:-7} + * + * @param string $image The Docker image string + * @return string The extracted version + */ + private function extractVersionFromImage(string $image): string + { + // Handle environment variable syntax with fallback + // Example: ${REDIS_DOCKER_IMAGE:-redis:7} + if (preg_match('/^\$\{[^:}]+:-(.+)\}$/', $image, $matches)) { + $image = $matches[1]; + } + // Example: redis:${REDIS_TAG:-7-alpine} + elseif (preg_match('/^([^:]+):\$\{[^:}]+:-(.+)\}$/', $image, $matches)) { + return $matches[2]; + } + + // Extract version from image:version format + if (preg_match('/:([a-zA-Z0-9._-]+)$/', $image, $matches)) { + return $matches[1]; + } + + // Fallback to 'latest' if we can't extract version + $this->io->warning("🪠 [Drainpipe] Unable to extract version from image: $image. Using 'latest'."); + return 'latest'; + } + + /** + * Map DDEV services to Tugboat Docker images. + * + * @param string $serviceType The service type (php, database, redis, etc.) + * @param string $version The version number + * @param string|null $subtype Additional type info (e.g., nginx-fpm vs apache-fpm) + * @return string The Tugboat Docker image string + */ + private function mapToTugboatImage(string $serviceType, string $version, ?string $subtype = null): string + { + switch ($serviceType) { + case 'php': + if ($subtype === 'apache-fpm') { + return "tugboatqa/php:$version-apache-bookworm"; + } + return "tugboatqa/php-nginx:$version-fpm-bookworm"; + + case 'database': + // $subtype is the database type (mariadb, mysql, postgres) + $dbType = $subtype ?? 'mariadb'; + return "tugboatqa/$dbType:$version"; + + case 'redis': + return "tugboatqa/redis:$version"; + + case 'memcached': + return "tugboatqa/memcached:$version"; + + case 'solr': + return "tugboatqa/solr:$version"; + + case 'elasticsearch': + return "tugboatqa/elasticsearch:$version"; + + default: + $this->io->warning("🪠 [Drainpipe] Unknown service type: $serviceType"); + return "tugboatqa/$serviceType:$version"; + } + } + + /** + * Parse Taskfile.yml for custom Tugboat commands. + * + * @return array Array of command names for different phases + */ + private function parseTaskfileCommands(): array + { + $commands = []; + + if (!file_exists('./Taskfile.yml')) { + return $commands; + } + + $taskfile = Yaml::parseFile('./Taskfile.yml'); + $tasks = $taskfile['tasks'] ?? []; + + // Services that can have a custom init task + $initTasks = [ + 'php', + 'mysql', + 'mariadb', + 'postgres', + 'redis', + 'memcached', + 'solr', + 'elasticsearch', + ]; + + // Check for service-specific init tasks + $shouldScaffoldInitScript = false; + foreach ($initTasks as $service) { + if (isset($tasks["tugboat:$service:init"])) { + if (in_array($service, ['mariadb', 'mysql', 'postgres'])) { + $commands['database']['init'] = true; + } + elseif (in_array($service, ['redis', 'memcached'])) { + $commands['memory_cache']['init'] = true; + } + elseif (in_array($service, ['solr', 'elasticsearch'])) { + $commands['search']['init'] = true; + } + else { + $commands[$service]['init'] = true; + } + $shouldScaffoldInitScript = true; + } + } + + if ($shouldScaffoldInitScript) { + $this->installScript('custom-init-command.sh'); + } + + // Check for Tugboat-specific tasks for main service (php) + $commands['php']['update'] = 'build:drupal'; + if (isset($tasks['update'])) { + $commands['php']['update'] = 'update'; + } + if (isset($tasks['update:tugboat'])) { + $commands['php']['update'] = 'update:tugboat'; + } + if (isset($tasks['sync:tugboat'])) { + $commands['php']['sync'] = 'sync:tugboat'; + } + if (isset($tasks['build:tugboat'])) { + $commands['php']['build'] = 'build:tugboat'; + } + if (isset($tasks['online:tugboat'])) { + $commands['php']['online'] = 'online:tugboat'; + } + + return $commands; + } + + /** + * Apply configuration overrides to services. + * + * Only allows overriding specific presentational properties. + * Does not allow overriding: commands, volumes, environment, image, or depends. + * + * @param array $services The services configuration + * @param array $overrideConfig The override configuration + * @return array Services with overrides applied + */ + private function applyOverrides(array $services, array $overrideConfig): array + { + if (empty($overrideConfig)) { + return $services; + } + + // Define allowed override keys per service + // Only presentational properties are allowed + $allowedOverrides = [ + 'php' => ['aliases', 'visualdiff', 'screenshot', 'urls'], + 'solr' => ['checkout', 'depends', 'volumes', 'environment'], + ]; + + foreach ($overrideConfig as $serviceName => $overrides) { + if (!is_array($overrides)) { + continue; + } + + // Only php and solr services allow overrides + if (!isset($allowedOverrides[$serviceName])) { + $this->io->warning("🪠 [Drainpipe] Service '$serviceName' does not support overrides. Only 'php' and 'solr' services can be overridden."); + continue; + } + + // Map service name to actual service key in $services array + // php -> php (no change) + // solr -> search (since solr is detected as 'search' service) + $targetService = $serviceName === 'solr' ? 'search' : $serviceName; + + // Only allow overrides for services that exist in DDEV configuration + if (!isset($services[$targetService])) { + $this->io->warning("🪠 [Drainpipe] Cannot override '$serviceName': service not detected in DDEV configuration"); + continue; + } + + // Get allowed keys for this service + $allowed = $allowedOverrides[$serviceName]; + + // Filter and apply overrides + foreach ($overrides as $key => $value) { + if (in_array($key, $allowed)) { + $services[$targetService]['overrides'][$key] = $value; + $this->io->write("🪠 [Drainpipe] Applied override: $serviceName.$key"); + } else { + $this->io->warning("🪠 [Drainpipe] Ignoring disallowed override: $serviceName.$key"); + } + } + } + + return $services; + } + + private function initTwigLoader(Environment &$twig = null): Environment + { + $vendor = $this->config->get('vendor-dir'); + $scaffoldPath = $vendor . '/lullabot/drainpipe/scaffold/tugboat/templates'; + $loader = new FilesystemLoader(); + + // Project overrides first so they take precedence. + if (is_dir('./.tugboat/drainpipe-templates')) { + $loader->addPath('./.tugboat/drainpipe-templates'); + $this->io->write('🪠 [Drainpipe] Using custom templates from .tugboat/drainpipe-templates/'); + } + + $loader->addPath($scaffoldPath); + + $twig = new Environment($loader); + + $twig->addFilter(new TwigFilter('yaml_encode', function ($value) { + return Yaml::dump($value, 4, 2); + })); + + $twig->addFilter(new \Twig\TwigFilter('indent', function (string $text, int $spaces = 4): string { + $pad = str_repeat(' ', max(0, $spaces)); + return preg_replace('/^/m', $pad, $text); + })); + + return $twig; + } + + /** + * Generate the Tugboat config.yml file using Twig templates. + * + * @param array $services The services configuration + * @param bool $isPantheon Whether Pantheon integration is enabled + */ + private function generateConfigYml(array $services, bool $isPantheon): void + { + $fs = new Filesystem(); + $fs->ensureDirectoryExists('./.tugboat'); + + // Load Twig templates + $twig = $this->initTwigLoader(); + + // Prepare template variables + $templateVars = [ + 'services' => $services, + 'pantheon' => $isPantheon, + ]; + + // Generate config.yml + $configYml = $twig->render('config.yml.twig', $templateVars); + file_put_contents('./.tugboat/config.yml', $configYml); + + $this->io->write('🪠 [Drainpipe] Generated .tugboat/config.yml'); + } + + /** + * Generate settings.tugboat.php file. + * + * @param array $services The services configuration + */ + private function generateSettingsFile(array $services): void + { + $vendor = $this->config->get('vendor-dir'); + $scaffoldPath = $vendor . '/lullabot/drainpipe/scaffold/tugboat/templates'; + $loader = new FilesystemLoader($scaffoldPath); + $twig = new Environment($loader); + + $templateVars = [ + 'services' => $services, + ]; + + $settingsPhp = $twig->render('settings.tugboat.php.twig', $templateVars); + file_put_contents('./web/sites/default/settings.tugboat.php', $settingsPhp); + + // Add include to settings.php if not already present + if (file_exists('./web/sites/default/settings.php')) { + $settings = file_get_contents('./web/sites/default/settings.php'); + if (strpos($settings, 'settings.tugboat.php') === false) { + $include = <<<'EOT' +include __DIR__ . "/settings.tugboat.php"; +EOT; + file_put_contents( + './web/sites/default/settings.php', + $include . PHP_EOL, + FILE_APPEND + ); + } + } + + $this->io->write('🪠 [Drainpipe] Generated web/sites/default/settings.tugboat.php'); + } +} From 03969e25be62072f964dbf3c6ae54a0f5f0ab987 Mon Sep 17 00:00:00 2001 From: Zequi Vazquez Date: Mon, 5 Jan 2026 17:43:05 +0100 Subject: [PATCH 02/16] #263: Fix tests and minor improvements --- .github/workflows/TestTugboat.yml | 13 +--- .tugboat/config.yml | 70 ++++++++++++++++--- .../tugboat/scripts/custom-init-command.sh | 3 +- scaffold/tugboat/templates/config.yml.twig | 10 +-- src/TugboatConfigPlugin.php | 9 ++- 5 files changed, 77 insertions(+), 28 deletions(-) diff --git a/.github/workflows/TestTugboat.yml b/.github/workflows/TestTugboat.yml index dee8fb79b..9e190c9d2 100644 --- a/.github/workflows/TestTugboat.yml +++ b/.github/workflows/TestTugboat.yml @@ -62,23 +62,13 @@ jobs: - name: Install Drainpipe, again, now with Taskfile updated run: | - ddev composer require lullabot/drainpipe --with-all-dependencies # Compare the generated files to the ones used to build this repository # preview - they should be the same. - name: Test Generated Files run: | - diff -up drainpipe/.tugboat/config.yml .tugboat/config.yml - sed -i '/#drainpipe-start/,/#drainpipe-end/d' drainpipe/.tugboat/steps/1-init.sh - sed -i '/#drainpipe-start/,/#drainpipe-end/d' drainpipe/.tugboat/steps/2-update.sh - sed -i '/#drainpipe-start/,/#drainpipe-end/d' drainpipe/.tugboat/steps/3-build.sh - sed -i '/#drainpipe-start/,/#drainpipe-end/d' drainpipe/.tugboat/steps/4-online.sh - diff -up drainpipe/.tugboat/steps/1-init.sh .tugboat/steps/1-init.sh - diff -up drainpipe/.tugboat/steps/2-update.sh .tugboat/steps/2-update.sh - diff -up drainpipe/.tugboat/steps/3-build.sh .tugboat/steps/3-build.sh - diff -up drainpipe/.tugboat/steps/4-online.sh .tugboat/steps/4-online.sh # Run the online task to generate drush/drush.yml. ddev task tugboat:drush-uli-ready @@ -119,7 +109,6 @@ jobs: run: | ddev config --auto ddev config --php-version "8.3" - ddev config --nodejs-version "16" ddev restart ddev exec --raw composer config extra.drupal-scaffold.gitignore true ddev exec --raw composer config --json extra.drupal-scaffold.allowed-packages \[\"lullabot/drainpipe\"] @@ -135,6 +124,6 @@ jobs: - name: Test NODE_MAJOR is correct run: | export TUGBOAT_ROOT=. && \ - eval "$(grep 'NODE_MAJOR=' .tugboat/steps/1-init.sh)" && \ + eval "$(grep 'NODE_MAJOR=' .tugboat/config.yml)" && \ echo $NODE_MAJOR | grep -q 16 diff --git a/.tugboat/config.yml b/.tugboat/config.yml index 4b20fca66..2535530fa 100644 --- a/.tugboat/config.yml +++ b/.tugboat/config.yml @@ -1,6 +1,7 @@ # DO NOT EDIT THIS FILE # This file is controlled by Drainpipe, run composer install to apply pending -# updates. You can add values to the php service using .tugboat/config.drainpipe-override.yml. +# updates. You can add values to the php and solr services adding a file named +# config.drainpipe-override.yml to the .tugboat folder. # # Example config.drainpipe-override.yml # php: @@ -19,17 +20,70 @@ services: http: false image: tugboatqa/php-nginx:8.3-fpm-bookworm default: true - depends: - mariadb - redis - + - elasticsearch commands: - init: ./.tugboat/steps/1-init.sh - update: ./.tugboat/steps/2-update.sh - build: ./.tugboat/steps/3-build.sh - online: ./.tugboat/steps/4-online.sh - + init: + - | + apt-get update + apt-get install -y mariadb-client + - | + ln -snf "${TUGBOAT_ROOT}/web" "${DOCROOT}" + - | + docker-php-ext-install opcache + - | + apt-get install -y libpng-dev libjpeg-dev libfreetype6-dev + - | + apt-get install -y libwebp-dev libwebp7 webp libmagickwand-dev + - | + docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp + docker-php-ext-install gd + - | + apt-get install -y imagemagick + - | + yes '' | pecl install -f redis + echo 'extension=redis.so' > /usr/local/etc/php/conf.d/redis.ini + - | + mkdir -p "${DOCROOT}/sites/default/files" + chmod 777 "${DOCROOT}/sites/default/files" + chgrp -R www-data "${DOCROOT}/sites/default/files" + - composer install + - | + apt-get install -y ca-certificates gnupg + mkdir -p /etc/apt/keyrings + curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg + NODE_MAJOR=$(cat ${TUGBOAT_ROOT}/.nvmrc) + echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list + - | + echo 'Package: *' > /etc/apt/preferences.d/nodesource-nodejs + echo 'Pin: origin deb.nodesource.com' >> /etc/apt/preferences.d/nodesource-nodejs + echo 'Pin-Priority: 1001' >> /etc/apt/preferences.d/nodesource-nodejs + - apt-get update + - apt-get -qq install nodejs + - apt-get clean + - | + corepack enable + - | + nodejs -v | grep -q v$NODE_MAJOR + - | + TASKFILE=$(cat ${TUGBOAT_ROOT}/vendor/lullabot/drainpipe/.taskfile) + sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d ${TASKFILE} -b /usr/local/bin + update: + - composer install + - | + chmod 755 ${DOCROOT}/sites/default + - | + chgrp -R www-data "${DOCROOT}/sites/default/files" + find "${DOCROOT}/sites/default/files" -type d -exec chmod 2775 {} \; + find "${DOCROOT}/sites/default/files" -type f -exec chmod 0664 {} \; + - task update + build: + - task build + - task update + online: + - task online:tugboat aliases: - foo urls: diff --git a/scaffold/tugboat/scripts/custom-init-command.sh b/scaffold/tugboat/scripts/custom-init-command.sh index 7570e0a23..04fb8736f 100644 --- a/scaffold/tugboat/scripts/custom-init-command.sh +++ b/scaffold/tugboat/scripts/custom-init-command.sh @@ -16,7 +16,8 @@ TASKFILE=$(cat ${TUGBOAT_ROOT}/vendor/lullabot/drainpipe/.taskfile) sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d ${TASKFILE} -b /usr/local/bin # Install YQ -wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -O /usr/bin/yq && chmod +x /usr/bin/yq +YQ_VERSION=v4.50.1 +wget https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/yq_linux_amd64 -O /usr/bin/yq && chmod +x /usr/bin/yq # Ensure includes are not executed yq -i 'del(."includes")' Taskfile.yml diff --git a/scaffold/tugboat/templates/config.yml.twig b/scaffold/tugboat/templates/config.yml.twig index 47d1a4b2c..467827dd3 100644 --- a/scaffold/tugboat/templates/config.yml.twig +++ b/scaffold/tugboat/templates/config.yml.twig @@ -26,17 +26,17 @@ services: {% endfor %} commands: init: -{{ include('php-init.yml.twig')|indent(8)|raw }} +{{ include('php-init.yml.twig')|no_comments|trim|indent(8)|raw }} update: -{{ include('php-update.yml.twig')|indent(8)|raw }} +{{ include('php-update.yml.twig')|no_comments|trim|indent(8)|raw }} build: -{{ include('php-build.yml.twig')|indent(8)|raw }} +{{ include('php-build.yml.twig')|no_comments|trim|indent(8)|raw }} {% if services.php.commands.online is defined and services.php.commands.online %} online: - task {{ services.php.commands.online }} {% endif %} {% if services.php.overrides|length > 0 %} -{{ services.php.overrides|yaml_encode|indent(4)|raw }} +{{ services.php.overrides|yaml_encode|trim|indent(4)|raw }} {% endif %} {{ services.database.type }}: @@ -68,6 +68,6 @@ services: - ./.tugboat/scripts/custom-init-command.sh {{ services.search.type }} {% endif %} {% if services.search.type == 'solr' and services.search.overrides|length > 0 %} -{{ services.search.overrides|yaml_encode|indent(4)|raw }} +{{ services.search.overrides|yaml_encode|trim|indent(4)|raw }} {% endif %} {% endif %} diff --git a/src/TugboatConfigPlugin.php b/src/TugboatConfigPlugin.php index f74e26a95..6a2df1e87 100644 --- a/src/TugboatConfigPlugin.php +++ b/src/TugboatConfigPlugin.php @@ -490,7 +490,7 @@ private function applyOverrides(array $services, array $overrideConfig): array return $services; } - private function initTwigLoader(Environment &$twig = null): Environment + private function initTwigLoader(): Environment { $vendor = $this->config->get('vendor-dir'); $scaffoldPath = $vendor . '/lullabot/drainpipe/scaffold/tugboat/templates'; @@ -510,11 +510,15 @@ private function initTwigLoader(Environment &$twig = null): Environment return Yaml::dump($value, 4, 2); })); - $twig->addFilter(new \Twig\TwigFilter('indent', function (string $text, int $spaces = 4): string { + $twig->addFilter(new TwigFilter('indent', function (string $text, int $spaces = 4): string { $pad = str_repeat(' ', max(0, $spaces)); return preg_replace('/^/m', $pad, $text); })); + $twig->addFilter(new TwigFilter('no_comments', function (string $text): string { + return preg_replace('/^[ \t]*#.*$\n?/m', '', $text); + })); + return $twig; } @@ -556,6 +560,7 @@ private function generateSettingsFile(array $services): void $scaffoldPath = $vendor . '/lullabot/drainpipe/scaffold/tugboat/templates'; $loader = new FilesystemLoader($scaffoldPath); $twig = new Environment($loader); + $twig = $this->initTwigLoader(); $templateVars = [ 'services' => $services, From bb2cd3ee80cf45d118eb552db3a0d7195db444b0 Mon Sep 17 00:00:00 2001 From: Zequi Vazquez Date: Mon, 5 Jan 2026 17:59:27 +0100 Subject: [PATCH 03/16] Fix Tugboat configuration file for Drainpipe --- .tugboat/steps/1-init.sh | 91 -------------------- .tugboat/steps/2-update.sh | 24 ------ .tugboat/steps/3-build.sh | 15 ---- .tugboat/steps/4-online.sh | 12 --- README.md | 9 -- scaffold/tugboat/templates/php-init.yml.twig | 25 +++++- src/TugboatConfigPlugin.php | 2 - 7 files changed, 24 insertions(+), 154 deletions(-) delete mode 100755 .tugboat/steps/1-init.sh delete mode 100755 .tugboat/steps/2-update.sh delete mode 100755 .tugboat/steps/3-build.sh delete mode 100755 .tugboat/steps/4-online.sh diff --git a/.tugboat/steps/1-init.sh b/.tugboat/steps/1-init.sh deleted file mode 100755 index 025ab74bc..000000000 --- a/.tugboat/steps/1-init.sh +++ /dev/null @@ -1,91 +0,0 @@ -#!/bin/bash -# DO NOT EDIT THIS FILE -# This file is controlled by Drainpipe, run composer install to apply pending -# updates. - -set -eux -echo "Initializing..." - -# Install task -sh -c "$(curl --location https://raw.githubusercontent.com/go-task/task/v3.45.4/install-task.sh)" -- -d -b /usr/local/bin - -# Install mysql or mariadb client. -apt-get update -apt-get install -y mariadb-client - -# Link the document root to the expected path. Tugboat uses /docroot -# by default. So, if Drupal is located at any other path in your git -# repository, change that here. This example links /web to the docroot -ln -snf "${TUGBOAT_ROOT}/web" "${DOCROOT}" - -# Install the PHP opcache as it's not included by default and needed for -# decent performance. -docker-php-ext-install opcache - -# GD dependencies. -apt-get install -y libpng-dev libjpeg-dev libfreetype6-dev - -# WebP dependencies. -apt-get install -y libwebp-dev libwebp7 webp libmagickwand-dev - -# Build and install gd. -docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp -docker-php-ext-install gd - -# Install ImageMagick. This is recommended by both Acquia and Pantheon instead -# of GD. Lullabot will likely be publishing an ADR recommending it too. -apt-get install -y imagemagick - -# Install the PHP redis extension. -yes '' | pecl install -f redis -echo 'extension=redis.so' > /usr/local/etc/php/conf.d/redis.ini - -# Install node -apt-get install -y ca-certificates gnupg -mkdir -p /etc/apt/keyrings -curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg -NODE_MAJOR=$(cat ${TUGBOAT_ROOT}/.nvmrc) -echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list - -# Ensure we use nodesource packages, even if Debian ships newer major versions. -cat << EOD > /etc/apt/preferences.d/nodesource-nodejs -Package: * -Pin: origin deb.nodesource.com -Pin-Priority: 1001 -EOD - -apt-get update -apt-get -qq install nodejs -apt-get clean -# This only works for node > 16, but that version is unsupported now anyway. -corepack enable - -# Validate we have the right nodejs version. -nodejs -v | grep -q v$NODE_MAJOR - -#drainpipe-start -# This is necessary for testing as this repository doesn't hold a Drupal site. -shopt -s dotglob -rm -rf /var/www/html/* -composer create-project drupal/recommended-project /var/www/html -ln -snf "/var/www/html/web" "${DOCROOT}" -cd /var/www/html -composer config extra.drupal-scaffold.gitignore true -composer config --json extra.drupal-scaffold.allowed-packages \[\"lullabot/drainpipe\"] -composer config --no-plugins allow-plugins.composer/installers true -composer config --no-plugins allow-plugins.drupal/core-composer-scaffold true -composer config --no-plugins allow-plugins.lullabot/drainpipe true -composer config repositories.drainpipe --json "{\"type\": \"path\", \"url\": \"${TUGBOAT_ROOT}\", \"options\": {\"symlink\": true}}" -composer config extra.drainpipe --json '{"tugboat": {}}' -composer config minimum-stability dev -composer require lullabot/drainpipe --with-all-dependencies -cp web/sites/default/default.settings.php web/sites/default/settings.php -#drainpipe-end - -# Create the Drupal private and public files directories if they aren't -# already present. -mkdir -p "${DOCROOT}/sites/default/files" -chmod 777 "${DOCROOT}/sites/default/files" -chgrp -R www-data "${DOCROOT}/sites/default/files" - -composer install diff --git a/.tugboat/steps/2-update.sh b/.tugboat/steps/2-update.sh deleted file mode 100755 index 96ffdc8a7..000000000 --- a/.tugboat/steps/2-update.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash -# DO NOT EDIT THIS FILE -# This file is controlled by Drainpipe, run composer install to apply pending -# updates. - -set -eux -echo "Updating..." - -#drainpipe-start -cd /var/www/html -#drainpipe-end -composer install -./vendor/bin/task sync - -# Ensure the sites/default directory is writable. -chmod 755 ${DOCROOT}/sites/default - -# Set file permissions such that Drupal will not complain. -chgrp -R www-data "${DOCROOT}/sites/default/files" -find "${DOCROOT}/sites/default/files" -type d -exec chmod 2775 {} \; -find "${DOCROOT}/sites/default/files" -type f -exec chmod 0664 {} \; -#drainpipe-start -./vendor/bin/drush config:export --yes -#drainpipe-end diff --git a/.tugboat/steps/3-build.sh b/.tugboat/steps/3-build.sh deleted file mode 100755 index f42ecdfac..000000000 --- a/.tugboat/steps/3-build.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash -# DO NOT EDIT THIS FILE -# This file is controlled by Drainpipe, run composer install to apply pending -# updates. - -set -eux -echo "Building..." - -#drainpipe-start -cd /var/www/html -composer install -#drainpipe-end -./vendor/bin/task build -./vendor/bin/task update -./vendor/bin/task tugboat:drush-uli-ready diff --git a/.tugboat/steps/4-online.sh b/.tugboat/steps/4-online.sh deleted file mode 100755 index 570de6ae7..000000000 --- a/.tugboat/steps/4-online.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash -# DO NOT EDIT THIS FILE -# This file is controlled by Drainpipe, run composer install to apply pending -# updates. - -set -eux -echo "Online..." - -#drainpipe-start -cd /var/www/html -#drainpipe-end -./vendor/bin/task online:tugboat diff --git a/README.md b/README.md index 2e3a1b0aa..233a14059 100644 --- a/README.md +++ b/README.md @@ -906,15 +906,6 @@ tugboat:php:init: - docker-php-ext-install ldap ``` -If using MySQL, you should add an init task to install the MySQL client: - -``` -tugboat:mysql:init: - desc: "Install MySQL client" - cmds: - - ./.tugboat/scripts/install-mysql-client.sh -``` - #### Custom online commands You can also add an `online` step to the `php` service by adding a task diff --git a/scaffold/tugboat/templates/php-init.yml.twig b/scaffold/tugboat/templates/php-init.yml.twig index 81fc99a3e..d1c213cc2 100644 --- a/scaffold/tugboat/templates/php-init.yml.twig +++ b/scaffold/tugboat/templates/php-init.yml.twig @@ -1,7 +1,14 @@ - | # Install mysql or mariadb client. apt-get update - apt-get install -y mariadb-client +{% if services.database.type == 'mysql' %} + # mysql was replaced by mariadb in Debian, so we have to install it manually. + # https://dev.mysql.com/doc/mysql-apt-repo-quick-guide/en/#repo-qg-apt-repo-manual-setup + # apt-key is also deprecated, so here we install the key manually. + ./.tugboat/scripts/install-mysql-client.sh +{% else %} + apt-get install -y {{ services.database.type }}-client +{% endif %} - | # Link the document root to the expected path. Tugboat uses /docroot # by default. So, if Drupal is located at any other path in your git @@ -25,10 +32,18 @@ # Install ImageMagick. This is recommended by both Acquia and Pantheon instead # of GD. Lullabot will likely be publishing an ADR recommending it too. apt-get install -y imagemagick +{% if services.memory_cache.type == "memcached" %} +- | + # Install the PHP memcache extension. + apt-get install -y zlib1g-dev + yes '' | pecl install -f memcache + echo 'extension=memcache.so' > /usr/local/etc/php/conf.d/memcache.ini +{% elseif services.memory_cache.type == "redis" %} - | # Install the PHP redis extension. yes '' | pecl install -f redis echo 'extension=redis.so' > /usr/local/etc/php/conf.d/redis.ini +{% endif %} - | # Create the Drupal private and public files directories if they aren't # already present. @@ -61,6 +76,14 @@ # Install task TASKFILE=$(cat ${TUGBOAT_ROOT}/vendor/lullabot/drainpipe/.taskfile) sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d ${TASKFILE} -b /usr/local/bin +{% if services.php.type == 'apache-fpm' %} +- a2enmod headers rewrite +{% endif %} +{% if pantheon %} +- | + curl --fail -sSL https://github.com/pantheon-systems/terminus/releases/download/$(curl -L --fail --silent "https://api.github.com/repos/pantheon-systems/terminus/releases/latest" | perl -nle'print $& while m{"tag_name": "\K.*?(?=")"}g')/terminus.phar --output /usr/local/bin/terminus + chmod 777 /usr/local/bin/terminus +{% endif %} {% if services.php.commands.init|default(false) %} - task tugboat:php:init {% endif %} diff --git a/src/TugboatConfigPlugin.php b/src/TugboatConfigPlugin.php index 6a2df1e87..9cc354ec6 100644 --- a/src/TugboatConfigPlugin.php +++ b/src/TugboatConfigPlugin.php @@ -228,8 +228,6 @@ private function parseBaseConfiguration(): array // Copy MySQL client script if needed if ($dbType === 'mysql') { $this->installScript('install-mysql-client.sh'); - $services['database']['commands']['init'] = 1; - $this->io->warning("🪠 [Drainpipe] MySQL database detected. Remember to add a tugboat:mysql:init task to call .tugboat/scripts/install-mysql-client.sh"); } return $services; From 18bc8106f2d66e8bfe22e3e074c77a57eae0b2ee Mon Sep 17 00:00:00 2001 From: Zequi Vazquez Date: Mon, 5 Jan 2026 18:07:43 +0100 Subject: [PATCH 04/16] Fix Tugboat configuration file --- .tugboat/config.yml | 1 + scaffold/tugboat/templates/php-init.yml.twig | 1 + 2 files changed, 2 insertions(+) diff --git a/.tugboat/config.yml b/.tugboat/config.yml index 2535530fa..022e328d3 100644 --- a/.tugboat/config.yml +++ b/.tugboat/config.yml @@ -30,6 +30,7 @@ services: apt-get update apt-get install -y mariadb-client - | + mkdir -p "${TUGBOAT_ROOT}/web" ln -snf "${TUGBOAT_ROOT}/web" "${DOCROOT}" - | docker-php-ext-install opcache diff --git a/scaffold/tugboat/templates/php-init.yml.twig b/scaffold/tugboat/templates/php-init.yml.twig index d1c213cc2..d98b4532f 100644 --- a/scaffold/tugboat/templates/php-init.yml.twig +++ b/scaffold/tugboat/templates/php-init.yml.twig @@ -13,6 +13,7 @@ # Link the document root to the expected path. Tugboat uses /docroot # by default. So, if Drupal is located at any other path in your git # repository, change that here. This example links /web to the docroot + mkdir -p "${TUGBOAT_ROOT}/web" ln -snf "${TUGBOAT_ROOT}/web" "${DOCROOT}" - | # Install the PHP opcache as it's not included by default and needed for From d699ab610f3f4d218db6db3327d70a01b7bf5c6b Mon Sep 17 00:00:00 2001 From: Zequi Vazquez Date: Mon, 5 Jan 2026 18:42:31 +0100 Subject: [PATCH 05/16] Ensure Tugboat tests pass, and preview can be created --- .github/workflows/TestTugboat.yml | 1 + .tugboat/config.yml | 27 ++++++++++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/.github/workflows/TestTugboat.yml b/.github/workflows/TestTugboat.yml index 9e190c9d2..c6ef3ef4f 100644 --- a/.github/workflows/TestTugboat.yml +++ b/.github/workflows/TestTugboat.yml @@ -68,6 +68,7 @@ jobs: # preview - they should be the same. - name: Test Generated Files run: | + sed -i '/#drainpipe-start/,/#drainpipe-end/d' drainpipe/.tugboat/config.yml diff -up drainpipe/.tugboat/config.yml .tugboat/config.yml # Run the online task to generate drush/drush.yml. diff --git a/.tugboat/config.yml b/.tugboat/config.yml index 022e328d3..5409ad59e 100644 --- a/.tugboat/config.yml +++ b/.tugboat/config.yml @@ -30,7 +30,6 @@ services: apt-get update apt-get install -y mariadb-client - | - mkdir -p "${TUGBOAT_ROOT}/web" ln -snf "${TUGBOAT_ROOT}/web" "${DOCROOT}" - | docker-php-ext-install opcache @@ -46,6 +45,25 @@ services: - | yes '' | pecl install -f redis echo 'extension=redis.so' > /usr/local/etc/php/conf.d/redis.ini +#drainpipe-start +# This is necessary for testing as this repository doesn't hold a Drupal site. + - | + shopt -s dotglob + rm -rf /var/www/html/* + composer create-project drupal/recommended-project /var/www/html + ln -snf "/var/www/html/web" "${DOCROOT}" + cd /var/www/html + composer config extra.drupal-scaffold.gitignore true + composer config --json extra.drupal-scaffold.allowed-packages \[\"lullabot/drainpipe\"] + composer config --no-plugins allow-plugins.composer/installers true + composer config --no-plugins allow-plugins.drupal/core-composer-scaffold true + composer config --no-plugins allow-plugins.lullabot/drainpipe true + composer config repositories.drainpipe --json "{\"type\": \"path\", \"url\": \"${TUGBOAT_ROOT}\", \"options\": {\"symlink\": true}}" + composer config extra.drainpipe --json '{"tugboat": {}}' + composer config minimum-stability dev + composer require lullabot/drainpipe --with-all-dependencies + cp web/sites/default/default.settings.php web/sites/default/settings.php +#drainpipe-end - | mkdir -p "${DOCROOT}/sites/default/files" chmod 777 "${DOCROOT}/sites/default/files" @@ -73,16 +91,23 @@ services: sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d ${TASKFILE} -b /usr/local/bin update: - composer install + - task sync - | chmod 755 ${DOCROOT}/sites/default - | chgrp -R www-data "${DOCROOT}/sites/default/files" find "${DOCROOT}/sites/default/files" -type d -exec chmod 2775 {} \; find "${DOCROOT}/sites/default/files" -type f -exec chmod 0664 {} \; +#drainpipe-start + - ./vendor/bin/drush config:export --yes +#drainpipe-end - task update build: - task build - task update +#drainpipe-start + - task tugboat:drush-uli-ready +#drainpipe-end online: - task online:tugboat aliases: From 4917ea5f4f48941c3fbd3ab059a487bd4a49ec20 Mon Sep 17 00:00:00 2001 From: Zequi Vazquez Date: Mon, 5 Jan 2026 18:50:44 +0100 Subject: [PATCH 06/16] Fix Tugboat integration --- .tugboat/config.yml | 11 +++++++++++ scaffold/tugboat/templates/php-init.yml.twig | 1 - 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.tugboat/config.yml b/.tugboat/config.yml index 5409ad59e..facb9791a 100644 --- a/.tugboat/config.yml +++ b/.tugboat/config.yml @@ -90,8 +90,13 @@ services: TASKFILE=$(cat ${TUGBOAT_ROOT}/vendor/lullabot/drainpipe/.taskfile) sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d ${TASKFILE} -b /usr/local/bin update: +#drainpipe-start + - cd /var/www/html +#drainpipe-end - composer install +#drainpipe-start - task sync +#drainpipe-end - | chmod 755 ${DOCROOT}/sites/default - | @@ -103,12 +108,18 @@ services: #drainpipe-end - task update build: +#drainpipe-start + - cd /var/www/html +#drainpipe-end - task build - task update #drainpipe-start - task tugboat:drush-uli-ready #drainpipe-end online: +#drainpipe-start + - cd /var/www/html +#drainpipe-end - task online:tugboat aliases: - foo diff --git a/scaffold/tugboat/templates/php-init.yml.twig b/scaffold/tugboat/templates/php-init.yml.twig index d98b4532f..d1c213cc2 100644 --- a/scaffold/tugboat/templates/php-init.yml.twig +++ b/scaffold/tugboat/templates/php-init.yml.twig @@ -13,7 +13,6 @@ # Link the document root to the expected path. Tugboat uses /docroot # by default. So, if Drupal is located at any other path in your git # repository, change that here. This example links /web to the docroot - mkdir -p "${TUGBOAT_ROOT}/web" ln -snf "${TUGBOAT_ROOT}/web" "${DOCROOT}" - | # Install the PHP opcache as it's not included by default and needed for From f64e6e8d796df8f11c6acaee53bc7972cf573ec3 Mon Sep 17 00:00:00 2001 From: Zequi Vazquez Date: Mon, 5 Jan 2026 19:16:53 +0100 Subject: [PATCH 07/16] Allow Tugboat to create settings.php file --- .tugboat/config.yml | 46 ++++++------------- scaffold/tugboat/templates/php-build.yml.twig | 7 +-- scaffold/tugboat/templates/php-init.yml.twig | 27 +++-------- .../tugboat/templates/php-update.yml.twig | 9 ++-- src/TugboatConfigPlugin.php | 3 +- 5 files changed, 31 insertions(+), 61 deletions(-) diff --git a/.tugboat/config.yml b/.tugboat/config.yml index facb9791a..0994fe716 100644 --- a/.tugboat/config.yml +++ b/.tugboat/config.yml @@ -29,25 +29,17 @@ services: - | apt-get update apt-get install -y mariadb-client - - | ln -snf "${TUGBOAT_ROOT}/web" "${DOCROOT}" - - | docker-php-ext-install opcache - - | apt-get install -y libpng-dev libjpeg-dev libfreetype6-dev - - | apt-get install -y libwebp-dev libwebp7 webp libmagickwand-dev - - | docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp docker-php-ext-install gd - - | apt-get install -y imagemagick - - | yes '' | pecl install -f redis echo 'extension=redis.so' > /usr/local/etc/php/conf.d/redis.ini #drainpipe-start # This is necessary for testing as this repository doesn't hold a Drupal site. - - | shopt -s dotglob rm -rf /var/www/html/* composer create-project drupal/recommended-project /var/www/html @@ -64,57 +56,49 @@ services: composer require lullabot/drainpipe --with-all-dependencies cp web/sites/default/default.settings.php web/sites/default/settings.php #drainpipe-end - - | mkdir -p "${DOCROOT}/sites/default/files" chmod 777 "${DOCROOT}/sites/default/files" chgrp -R www-data "${DOCROOT}/sites/default/files" - - composer install - - | + composer install apt-get install -y ca-certificates gnupg mkdir -p /etc/apt/keyrings curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg NODE_MAJOR=$(cat ${TUGBOAT_ROOT}/.nvmrc) echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list - - | echo 'Package: *' > /etc/apt/preferences.d/nodesource-nodejs echo 'Pin: origin deb.nodesource.com' >> /etc/apt/preferences.d/nodesource-nodejs echo 'Pin-Priority: 1001' >> /etc/apt/preferences.d/nodesource-nodejs - - apt-get update - - apt-get -qq install nodejs - - apt-get clean - - | + apt-get update + apt-get -qq install nodejs + apt-get clean corepack enable - - | nodejs -v | grep -q v$NODE_MAJOR - - | TASKFILE=$(cat ${TUGBOAT_ROOT}/vendor/lullabot/drainpipe/.taskfile) sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d ${TASKFILE} -b /usr/local/bin update: + - | #drainpipe-start - - cd /var/www/html -#drainpipe-end - - composer install -#drainpipe-start - - task sync + cd /var/www/html #drainpipe-end - - | + composer install + task sync chmod 755 ${DOCROOT}/sites/default - - | chgrp -R www-data "${DOCROOT}/sites/default/files" find "${DOCROOT}/sites/default/files" -type d -exec chmod 2775 {} \; find "${DOCROOT}/sites/default/files" -type f -exec chmod 0664 {} \; #drainpipe-start - - ./vendor/bin/drush config:export --yes + ./vendor/bin/drush config:export --yes #drainpipe-end - - task update + task update build: + - | #drainpipe-start - - cd /var/www/html + cd /var/www/html #drainpipe-end - - task build - - task update + task build + task update #drainpipe-start - - task tugboat:drush-uli-ready + task tugboat:drush-uli-ready #drainpipe-end online: #drainpipe-start diff --git a/scaffold/tugboat/templates/php-build.yml.twig b/scaffold/tugboat/templates/php-build.yml.twig index 65adda301..7ab76c1c6 100644 --- a/scaffold/tugboat/templates/php-build.yml.twig +++ b/scaffold/tugboat/templates/php-build.yml.twig @@ -1,5 +1,6 @@ -- task build -- task update +- | + task build + task update {% if services.php.commands.build is defined and services.php.commands.build %} -- task {{ services.php.commands.build }} + task {{ services.php.commands.build }} {% endif %} diff --git a/scaffold/tugboat/templates/php-init.yml.twig b/scaffold/tugboat/templates/php-init.yml.twig index d1c213cc2..00250fd01 100644 --- a/scaffold/tugboat/templates/php-init.yml.twig +++ b/scaffold/tugboat/templates/php-init.yml.twig @@ -9,81 +9,66 @@ {% else %} apt-get install -y {{ services.database.type }}-client {% endif %} -- | # Link the document root to the expected path. Tugboat uses /docroot # by default. So, if Drupal is located at any other path in your git # repository, change that here. This example links /web to the docroot ln -snf "${TUGBOAT_ROOT}/web" "${DOCROOT}" -- | # Install the PHP opcache as it's not included by default and needed for # decent performance. docker-php-ext-install opcache -- | # GD dependencies. apt-get install -y libpng-dev libjpeg-dev libfreetype6-dev -- | # WebP dependencies. apt-get install -y libwebp-dev libwebp7 webp libmagickwand-dev -- | # Build and install gd. docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp docker-php-ext-install gd -- | # Install ImageMagick. This is recommended by both Acquia and Pantheon instead # of GD. Lullabot will likely be publishing an ADR recommending it too. apt-get install -y imagemagick {% if services.memory_cache.type == "memcached" %} -- | # Install the PHP memcache extension. apt-get install -y zlib1g-dev yes '' | pecl install -f memcache echo 'extension=memcache.so' > /usr/local/etc/php/conf.d/memcache.ini {% elseif services.memory_cache.type == "redis" %} -- | # Install the PHP redis extension. yes '' | pecl install -f redis echo 'extension=redis.so' > /usr/local/etc/php/conf.d/redis.ini {% endif %} -- | # Create the Drupal private and public files directories if they aren't # already present. mkdir -p "${DOCROOT}/sites/default/files" chmod 777 "${DOCROOT}/sites/default/files" chgrp -R www-data "${DOCROOT}/sites/default/files" -- composer install -- | + composer install # Install node apt-get install -y ca-certificates gnupg mkdir -p /etc/apt/keyrings curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg NODE_MAJOR=$(cat ${TUGBOAT_ROOT}/.nvmrc) echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list -- | # Ensure we use nodesource packages, even if Debian ships newer major versions. echo 'Package: *' > /etc/apt/preferences.d/nodesource-nodejs echo 'Pin: origin deb.nodesource.com' >> /etc/apt/preferences.d/nodesource-nodejs echo 'Pin-Priority: 1001' >> /etc/apt/preferences.d/nodesource-nodejs -- apt-get update -- apt-get -qq install nodejs -- apt-get clean -- | + apt-get update + apt-get -qq install nodejs + apt-get clean # This only works for node > 16, but that version is unsupported now anyway. corepack enable -- | # Validate we have the right nodejs version. nodejs -v | grep -q v$NODE_MAJOR -- | # Install task TASKFILE=$(cat ${TUGBOAT_ROOT}/vendor/lullabot/drainpipe/.taskfile) sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d ${TASKFILE} -b /usr/local/bin {% if services.php.type == 'apache-fpm' %} -- a2enmod headers rewrite + a2enmod headers rewrite {% endif %} {% if pantheon %} -- | curl --fail -sSL https://github.com/pantheon-systems/terminus/releases/download/$(curl -L --fail --silent "https://api.github.com/repos/pantheon-systems/terminus/releases/latest" | perl -nle'print $& while m{"tag_name": "\K.*?(?=")"}g')/terminus.phar --output /usr/local/bin/terminus chmod 777 /usr/local/bin/terminus {% endif %} {% if services.php.commands.init|default(false) %} -- task tugboat:php:init + task tugboat:php:init {% endif %} diff --git a/scaffold/tugboat/templates/php-update.yml.twig b/scaffold/tugboat/templates/php-update.yml.twig index cdbdeff67..0c33f57ee 100644 --- a/scaffold/tugboat/templates/php-update.yml.twig +++ b/scaffold/tugboat/templates/php-update.yml.twig @@ -1,15 +1,14 @@ -- composer install +- | + composer install {% if services.php.commands.sync is defined and services.php.commands.sync %} -- task {{ services.php.commands.sync }} + task {{ services.php.commands.sync }} {% endif %} -- | # Ensure the sites/default directory is writable. chmod 755 ${DOCROOT}/sites/default -- | # Set file permissions such that Drupal will not complain. chgrp -R www-data "${DOCROOT}/sites/default/files" find "${DOCROOT}/sites/default/files" -type d -exec chmod 2775 {} \; find "${DOCROOT}/sites/default/files" -type f -exec chmod 0664 {} \; {% if services.php.commands.update is defined and services.php.commands.update %} -- task {{ services.php.commands.update }} + task {{ services.php.commands.update }} {% endif %} diff --git a/src/TugboatConfigPlugin.php b/src/TugboatConfigPlugin.php index 9cc354ec6..dc32a8c3c 100644 --- a/src/TugboatConfigPlugin.php +++ b/src/TugboatConfigPlugin.php @@ -113,7 +113,8 @@ private function generateTugboatConfiguration(): void // Check if DDEV configuration exists if (!file_exists('./.ddev/config.yaml')) { - $this->io->warning('🪠 [Drainpipe] DDEV configuration not found. Tugboat integration requires DDEV.'); + $this->io->warning('🪠 [Drainpipe] DDEV configuration not found. Generating settings.php file, assuming Mariadb.'); + $this->generateSettingsFile(['database' => ['type' => 'mariadb']]); return; } From b76a73d84b622b22526d06b5c4b05a547da0db57 Mon Sep 17 00:00:00 2001 From: Zequi Vazquez Date: Mon, 5 Jan 2026 19:20:44 +0100 Subject: [PATCH 08/16] Fix bad indentation error on config.yml --- .tugboat/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tugboat/config.yml b/.tugboat/config.yml index 0994fe716..89564d0cf 100644 --- a/.tugboat/config.yml +++ b/.tugboat/config.yml @@ -39,7 +39,7 @@ services: yes '' | pecl install -f redis echo 'extension=redis.so' > /usr/local/etc/php/conf.d/redis.ini #drainpipe-start -# This is necessary for testing as this repository doesn't hold a Drupal site. + # This is necessary for testing as this repository doesn't hold a Drupal site. shopt -s dotglob rm -rf /var/www/html/* composer create-project drupal/recommended-project /var/www/html From e9876a8f35f97faa9ee11acd1dfc6fe80b716c91 Mon Sep 17 00:00:00 2001 From: Zequi Vazquez Date: Mon, 5 Jan 2026 19:22:09 +0100 Subject: [PATCH 09/16] Fix bad indentation error on config.yml --- .tugboat/config.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.tugboat/config.yml b/.tugboat/config.yml index 89564d0cf..c3b8530b9 100644 --- a/.tugboat/config.yml +++ b/.tugboat/config.yml @@ -39,7 +39,8 @@ services: yes '' | pecl install -f redis echo 'extension=redis.so' > /usr/local/etc/php/conf.d/redis.ini #drainpipe-start - # This is necessary for testing as this repository doesn't hold a Drupal site. +# This is necessary for testing as this repository doesn't hold a Drupal site. + - | shopt -s dotglob rm -rf /var/www/html/* composer create-project drupal/recommended-project /var/www/html @@ -55,6 +56,7 @@ services: composer config minimum-stability dev composer require lullabot/drainpipe --with-all-dependencies cp web/sites/default/default.settings.php web/sites/default/settings.php + - | #drainpipe-end mkdir -p "${DOCROOT}/sites/default/files" chmod 777 "${DOCROOT}/sites/default/files" From 8bd523f0b32f05f0fd911c61f90a01944c3613bb Mon Sep 17 00:00:00 2001 From: Zequi Vazquez Date: Mon, 5 Jan 2026 19:28:59 +0100 Subject: [PATCH 10/16] Remove unused task sync command --- .tugboat/config.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.tugboat/config.yml b/.tugboat/config.yml index c3b8530b9..211c3e8a3 100644 --- a/.tugboat/config.yml +++ b/.tugboat/config.yml @@ -83,7 +83,6 @@ services: cd /var/www/html #drainpipe-end composer install - task sync chmod 755 ${DOCROOT}/sites/default chgrp -R www-data "${DOCROOT}/sites/default/files" find "${DOCROOT}/sites/default/files" -type d -exec chmod 2775 {} \; @@ -103,9 +102,6 @@ services: task tugboat:drush-uli-ready #drainpipe-end online: -#drainpipe-start - - cd /var/www/html -#drainpipe-end - task online:tugboat aliases: - foo From 2633b3c8ff50c3dc0f23ade832f61f12a2ff105d Mon Sep 17 00:00:00 2001 From: Zequi Vazquez Date: Mon, 5 Jan 2026 19:29:50 +0100 Subject: [PATCH 11/16] Fix bad indentation error --- .tugboat/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tugboat/config.yml b/.tugboat/config.yml index 211c3e8a3..08deecd93 100644 --- a/.tugboat/config.yml +++ b/.tugboat/config.yml @@ -57,7 +57,7 @@ services: composer require lullabot/drainpipe --with-all-dependencies cp web/sites/default/default.settings.php web/sites/default/settings.php - | -#drainpipe-end + #drainpipe-end mkdir -p "${DOCROOT}/sites/default/files" chmod 777 "${DOCROOT}/sites/default/files" chgrp -R www-data "${DOCROOT}/sites/default/files" From 4c3214ea80aabb1d978a3ba9695f8e40a55a3597 Mon Sep 17 00:00:00 2001 From: Zequi Vazquez Date: Mon, 5 Jan 2026 19:31:00 +0100 Subject: [PATCH 12/16] Fix bad indentation error --- .tugboat/config.yml | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/.tugboat/config.yml b/.tugboat/config.yml index 08deecd93..9c68cb031 100644 --- a/.tugboat/config.yml +++ b/.tugboat/config.yml @@ -38,9 +38,8 @@ services: apt-get install -y imagemagick yes '' | pecl install -f redis echo 'extension=redis.so' > /usr/local/etc/php/conf.d/redis.ini -#drainpipe-start -# This is necessary for testing as this repository doesn't hold a Drupal site. - - | + #drainpipe-start + # This is necessary for testing as this repository doesn't hold a Drupal site. shopt -s dotglob rm -rf /var/www/html/* composer create-project drupal/recommended-project /var/www/html @@ -56,7 +55,6 @@ services: composer config minimum-stability dev composer require lullabot/drainpipe --with-all-dependencies cp web/sites/default/default.settings.php web/sites/default/settings.php - - | #drainpipe-end mkdir -p "${DOCROOT}/sites/default/files" chmod 777 "${DOCROOT}/sites/default/files" @@ -79,28 +77,28 @@ services: sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d ${TASKFILE} -b /usr/local/bin update: - | -#drainpipe-start + #drainpipe-start cd /var/www/html -#drainpipe-end + #drainpipe-end composer install chmod 755 ${DOCROOT}/sites/default chgrp -R www-data "${DOCROOT}/sites/default/files" find "${DOCROOT}/sites/default/files" -type d -exec chmod 2775 {} \; find "${DOCROOT}/sites/default/files" -type f -exec chmod 0664 {} \; -#drainpipe-start + #drainpipe-start ./vendor/bin/drush config:export --yes -#drainpipe-end + #drainpipe-end task update build: - | -#drainpipe-start + #drainpipe-start cd /var/www/html -#drainpipe-end + #drainpipe-end task build task update -#drainpipe-start + #drainpipe-start task tugboat:drush-uli-ready -#drainpipe-end + #drainpipe-end online: - task online:tugboat aliases: From df768c610045ea5c49dd27f9f8158a9d30bb19b1 Mon Sep 17 00:00:00 2001 From: Zequi Vazquez Date: Mon, 5 Jan 2026 19:43:36 +0100 Subject: [PATCH 13/16] Ensure Tugboat can install Drupal --- .tugboat/config.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.tugboat/config.yml b/.tugboat/config.yml index 9c68cb031..b0761f05e 100644 --- a/.tugboat/config.yml +++ b/.tugboat/config.yml @@ -79,6 +79,8 @@ services: - | #drainpipe-start cd /var/www/html + composer install + task sync #drainpipe-end composer install chmod 755 ${DOCROOT}/sites/default From fbe3ccaaba56a6de2ae51d086a49c68678827932 Mon Sep 17 00:00:00 2001 From: Zequi Vazquez Date: Mon, 5 Jan 2026 19:52:23 +0100 Subject: [PATCH 14/16] Prevent error when running Tugboat online phase --- .tugboat/config.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.tugboat/config.yml b/.tugboat/config.yml index b0761f05e..2bf72bc86 100644 --- a/.tugboat/config.yml +++ b/.tugboat/config.yml @@ -102,6 +102,9 @@ services: task tugboat:drush-uli-ready #drainpipe-end online: + #drainpipe-start + - alias task='cd /var/www/html && command task' + #drainpipe-end - task online:tugboat aliases: - foo From 4f9764620d685c214357733f34d76bcb50e1fdef Mon Sep 17 00:00:00 2001 From: Zequi Vazquez Date: Mon, 5 Jan 2026 19:59:38 +0100 Subject: [PATCH 15/16] Remove alias command as it is not working as expected --- .tugboat/config.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.tugboat/config.yml b/.tugboat/config.yml index 2bf72bc86..b0761f05e 100644 --- a/.tugboat/config.yml +++ b/.tugboat/config.yml @@ -102,9 +102,6 @@ services: task tugboat:drush-uli-ready #drainpipe-end online: - #drainpipe-start - - alias task='cd /var/www/html && command task' - #drainpipe-end - task online:tugboat aliases: - foo From 58c2fbba3b1ac6cf59cb1b2df6ed585b3530318f Mon Sep 17 00:00:00 2001 From: Zequi Vazquez Date: Thu, 8 Jan 2026 20:07:36 +0100 Subject: [PATCH 16/16] #263: Fix permissions for Terminus, add flag to first composer install --- scaffold/tugboat/templates/php-init.yml.twig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scaffold/tugboat/templates/php-init.yml.twig b/scaffold/tugboat/templates/php-init.yml.twig index 00250fd01..2461e555a 100644 --- a/scaffold/tugboat/templates/php-init.yml.twig +++ b/scaffold/tugboat/templates/php-init.yml.twig @@ -41,7 +41,7 @@ mkdir -p "${DOCROOT}/sites/default/files" chmod 777 "${DOCROOT}/sites/default/files" chgrp -R www-data "${DOCROOT}/sites/default/files" - composer install + composer install --ignore-platform-reqs # Install node apt-get install -y ca-certificates gnupg mkdir -p /etc/apt/keyrings @@ -67,7 +67,7 @@ {% endif %} {% if pantheon %} curl --fail -sSL https://github.com/pantheon-systems/terminus/releases/download/$(curl -L --fail --silent "https://api.github.com/repos/pantheon-systems/terminus/releases/latest" | perl -nle'print $& while m{"tag_name": "\K.*?(?=")"}g')/terminus.phar --output /usr/local/bin/terminus - chmod 777 /usr/local/bin/terminus + chmod 755 /usr/local/bin/terminus {% endif %} {% if services.php.commands.init|default(false) %} task tugboat:php:init