Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ help: ## Show this help message
echo 'Usage: make [target]'
echo ''
echo 'Available targets:'
awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " \033[36m%s\033[0m\t%s\n", $$1, $$2}' $(MAKEFILE_LIST) | sort | column -t -s $$'\t'
awk -f scripts/make-help.awk $(MAKEFILE_LIST) | sort | column -t -s $$'\t'

init reconcile: ## Generate or repair declared initialization state
sitectl compose reconcile
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ The ingress component writes `INGRESS_HOSTNAMES` as comma-separated hostnames an

See the [Drupal sitectl plugin docs](https://sitectl.libops.io/plugins/drupal) for Drush helpers, development mode, sync operations, login links, and Drupal-specific jobs.

## Versioned runtime programs

Template v1.2.0 adds the container-side readiness, database-migration, and strict-verification programs under `scripts/`. The `drupal` service mounts each program read-only at a stable path under `/usr/local/lib/sitectl`; `sitectl-drupal` invokes those files instead of injecting PHP or shell source through the container command line.

Before stopping a running site, `sitectl deploy` checks that every required source is a regular tracked file, that the shell programs remain executable, and that the Compose service declares every mount read-only. It also asks Compose to confirm that the effective service can use each mounted program. A site created from template v1.1.0 or older must first incorporate the v1.2.0 template changes. An incompatible checkout fails before the existing containers are stopped and identifies the required template version; there is no inline fallback with behavior that could differ from the reviewed checkout.

## Makefile

The Makefile is intentionally small. It only keeps Drupal-specific targets that are not core sitectl operations:
Expand All @@ -99,6 +105,7 @@ Use `sitectl compose ...` and `sitectl set ...` directly for normal stack operat
- `mariadb` stores application data.
- `solr` provides search.
- Secrets are generated into `./secrets/`.
- Rollout and verification programs are versioned with the site and mounted read-only into `drupal`.

Drupal code is Composer-managed. Custom modules and themes belong under `web/modules/custom` and `web/themes/custom`.

Expand Down
7 changes: 7 additions & 0 deletions compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,13 @@ services:
- source: CERT_AUTHORITY
- source: UID
volumes:
- ./scripts/drupal-lint-custom.sh:/usr/local/lib/sitectl/drupal-lint-custom.sh:ro,z
- ./scripts/drupal-rollout-migrate.sh:/usr/local/lib/sitectl/drupal-rollout-migrate.sh:ro,z
- ./scripts/drupal-test-custom.sh:/usr/local/lib/sitectl/drupal-test-custom.sh:ro,z
- ./scripts/drupal-verify-config-drift.php:/usr/local/lib/sitectl/drupal-verify-config-drift.php:ro,z
- ./scripts/drupal-verify-cron-queue.php:/usr/local/lib/sitectl/drupal-verify-cron-queue.php:ro,z
- ./scripts/drupal-verify-solr.php:/usr/local/lib/sitectl/drupal-verify-solr.php:ro,z
- ./scripts/drupal-wait-installed.sh:/usr/local/lib/sitectl/drupal-wait-installed.sh:ro,z
- type: volume
source: drupal-public-files
target: /var/www/drupal/web/sites/default/files
Expand Down
43 changes: 43 additions & 0 deletions scripts/drupal-lint-custom.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/usr/bin/env bash

set -euo pipefail

custom_dir="${CUSTOM_DIR:-web/modules/custom}"

if [ ! -d "$custom_dir" ]; then
echo "No custom Drupal module directory found at $custom_dir; skipping Drupal code lint."
exit 0
fi

if ! find "$custom_dir" -type f \( -name '*.module' -o -name '*.php' -o -name '*.inc' \) | grep -q .; then
echo "No custom Drupal PHP files found under $custom_dir; skipping Drupal code lint."
exit 0
fi

cp web/core/phpcs.xml.dist . 2>/dev/null || true

if [ "${RUN_PHPCBF:-0}" = "1" ]; then
if [ ! -x vendor/bin/phpcbf ]; then
echo 'vendor/bin/phpcbf was not found in the Drupal container' >&2
exit 1
fi

php vendor/bin/phpcbf \
-n \
--standard=Drupal,DrupalPractice \
--extensions=module,php,inc \
"$custom_dir" || true
fi

if [ ! -x vendor/bin/phpcs ]; then
echo 'vendor/bin/phpcs was not found in the Drupal container' >&2
exit 1
fi

php vendor/bin/phpcs \
-n \
--standard=Drupal,DrupalPractice \
--extensions=module,php,inc \
"$custom_dir"

echo 'PHP codesniff passed'
8 changes: 8 additions & 0 deletions scripts/drupal-rollout-migrate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env sh

set -eu

cd /var/www/drupal

/var/www/drupal/vendor/bin/drush updb -y
/var/www/drupal/vendor/bin/drush cr
22 changes: 22 additions & 0 deletions scripts/drupal-test-custom.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env bash

set -euo pipefail

custom_dir="${CUSTOM_DIR:-web/modules/custom}"

if [ ! -d "$custom_dir" ]; then
echo "No custom Drupal module directory found at $custom_dir; baseline application assertions passed."
exit 0
fi

if ! find "$custom_dir" -type f \( -path '*/tests/src/*' -o -name '*Test.php' \) | grep -q .; then
echo "No custom Drupal tests found under $custom_dir; baseline application assertions passed."
exit 0
fi

if [ ! -x vendor/bin/phpunit ]; then
echo 'vendor/bin/phpunit was not found in the Drupal container' >&2
exit 1
fi

s6-setuidgid nginx php vendor/bin/phpunit -c phpunit.unit.xml --debug "$custom_dir"
35 changes: 35 additions & 0 deletions scripts/drupal-verify-config-drift.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

$active = \Drupal::service('config.storage');
$directory = \Drupal\Core\Site\Settings::get('config_sync_directory');
$result = [];

if (is_string($directory) && $directory !== '') {
$sync = new \Drupal\Core\Config\FileStorage($directory);
$names = array_unique(array_merge($active->listAll(), $sync->listAll()));
sort($names);

foreach ($names as $name) {
$activeData = $active->read($name);
$syncData = $sync->read($name);
if (!is_array($activeData) || !is_array($syncData) || $activeData === $syncData) {
continue;
}

$keys = array_unique(array_merge(array_keys($activeData), array_keys($syncData)));
sort($keys);
foreach ($keys as $key) {
if (
!array_key_exists($key, $activeData)
|| !array_key_exists($key, $syncData)
|| $activeData[$key] !== $syncData[$key]
) {
$result[$name][] = $key;
}
}
}
}

print json_encode($result, JSON_THROW_ON_ERROR);
11 changes: 11 additions & 0 deletions scripts/drupal-verify-cron-queue.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

$cron = \Drupal::service('cron');
$workers = \Drupal::service('plugin.manager.queue_worker')->getDefinitions();

print json_encode([
'cron' => $cron !== null,
'queue_workers' => count($workers),
], JSON_THROW_ON_ERROR);
13 changes: 13 additions & 0 deletions scripts/drupal-verify-solr.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

$server = \Drupal::entityTypeManager()
->getStorage('search_api_server')
->load('default_solr_server');

print json_encode([
'exists' => $server !== null,
'enabled' => $server ? (bool) $server->status() : false,
'available' => $server ? (bool) $server->isAvailable() : false,
], JSON_THROW_ON_ERROR);
13 changes: 13 additions & 0 deletions scripts/drupal-wait-installed.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/usr/bin/env sh

set -eu

attempt=0
until test -f /installed; do
attempt=$((attempt + 1))
if [ "${attempt}" -ge 150 ]; then
echo "Drupal did not become ready for database migration within 5 minutes" >&2
exit 1
fi
sleep 2
done
42 changes: 1 addition & 41 deletions scripts/lint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,44 +13,4 @@ docker compose exec -T \
-e CUSTOM_DIR="${custom_dir}" \
-e RUN_PHPCBF="${RUN_PHPCBF:-0}" \
"${service}" \
bash -lc '
set -euo pipefail

if [ ! -d "${CUSTOM_DIR}" ]; then
echo "No custom Drupal module directory found at ${CUSTOM_DIR}; skipping Drupal code lint."
exit 0
fi

if ! find "${CUSTOM_DIR}" -type f \( -name "*.module" -o -name "*.php" -o -name "*.inc" \) | grep -q .; then
echo "No custom Drupal PHP files found under ${CUSTOM_DIR}; skipping Drupal code lint."
exit 0
fi

cp web/core/phpcs.xml.dist . 2>/dev/null || true

if [ "${RUN_PHPCBF}" = "1" ]; then
if [ ! -x vendor/bin/phpcbf ]; then
echo "vendor/bin/phpcbf was not found in the Drupal container" >&2
exit 1
fi

php vendor/bin/phpcbf \
-n \
--standard=Drupal,DrupalPractice \
--extensions=module,php,inc \
"${CUSTOM_DIR}" || true
fi

if [ ! -x vendor/bin/phpcs ]; then
echo "vendor/bin/phpcs was not found in the Drupal container" >&2
exit 1
fi

php vendor/bin/phpcs \
-n \
--standard=Drupal,DrupalPractice \
--extensions=module,php,inc \
"${CUSTOM_DIR}"

echo "PHP codesniff passed"
'
/usr/local/lib/sitectl/drupal-lint-custom.sh
7 changes: 7 additions & 0 deletions scripts/make-help.awk
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
BEGIN {
FS = ":.*?## "
}

/^[a-zA-Z_-]+:.*?## / {
printf " \033[36m%s\033[0m\t%s\n", $1, $2
}
30 changes: 30 additions & 0 deletions scripts/sitectl-rollout-preflight.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/usr/bin/env bash

set -euo pipefail

require_regular_file() {
local path="$1"

if [ ! -f "${path}" ] || [ -L "${path}" ]; then
echo "This checkout is missing a required Drupal template file (${path}); migrate it to template v1.2.0 or newer before deploying" >&2
exit 1
fi
}

require_executable_file() {
local path="$1"

require_regular_file "${path}"
if [ ! -x "${path}" ]; then
echo "This checkout has a non-executable Drupal template program (${path}); restore it from template v1.2.0 or newer before deploying" >&2
exit 1
fi
}

require_executable_file "${BASH_SOURCE[0]}"
require_regular_file compose.yaml
require_executable_file scripts/drupal-wait-installed.sh
require_executable_file scripts/drupal-rollout-migrate.sh
require_regular_file scripts/drupal-verify-cron-queue.php
require_regular_file scripts/drupal-verify-solr.php
require_regular_file scripts/drupal-verify-config-drift.php
22 changes: 2 additions & 20 deletions scripts/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

set -euo pipefail

bash scripts/sitectl-rollout-preflight.sh
docker compose run --rm -e HOST_UID="$(id -u)" -e HOST_GID="$(id -g)" init

service="${DRUPAL_SERVICE:-drupal}"
Expand All @@ -14,23 +15,4 @@ sitectl verify --strict
docker compose exec -T \
-e CUSTOM_DIR="${custom_dir}" \
"${service}" \
bash -lc '
set -euo pipefail

if [ ! -d "${CUSTOM_DIR}" ]; then
echo "No custom Drupal module directory found at ${CUSTOM_DIR}; baseline application assertions passed."
exit 0
fi

if ! find "${CUSTOM_DIR}" -type f \( -path "*/tests/src/*" -o -name "*Test.php" \) | grep -q .; then
echo "No custom Drupal tests found under ${CUSTOM_DIR}; baseline application assertions passed."
exit 0
fi

if [ ! -x vendor/bin/phpunit ]; then
echo "vendor/bin/phpunit was not found in the Drupal container" >&2
exit 1
fi

su nginx -s /bin/bash -c "php vendor/bin/phpunit -c phpunit.unit.xml --debug ${CUSTOM_DIR}"
'
/usr/local/lib/sitectl/drupal-test-custom.sh