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
67 changes: 67 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,49 @@ jobs:
fi
echo "$logs" | grep 'from the baked skeleton'

# The one behaviour baking Flarum into the image could plausibly disturb.
# The volume is still meant to be a writable Composer project — merely
# SEEDED from the image rather than resolved from the network — and the
# Extension Manager depends on exactly that. A wrong owner or mode on the
# copied tree would leave the forum serving perfectly while silently
# refusing every extension install, which is not a failure anyone would
# attribute to this change months later.
#
# Runs the same way the Extension Manager does: composer, as www-data,
# against the volume.
#
# The extension is chosen so a failure here can only mean the volume is
# wrong. linkrobins/clipboard declares flarum/core ^2.0, has zero
# dependencies of its own, and runs on a live 2.0 forum. An earlier
# version of this step used fof/doorman and failed — not because the
# seeded volume was broken (composer require, extension:enable and
# cache:clear all succeeded) but because doorman's Flarum 2 release is a
# beta that 500s against an RC core. That conflated "can the Extension
# Manager write here" with "is this extension compatible", and only the
# first has anything to do with baking Flarum into the image.
#
# Removed again afterwards: the restore steps below rebuild the volume
# from the image, which would not contain a third-party extension the
# backup's database still had enabled.
- name: Extension Manager can still install into the seeded volume
run: |
docker compose exec -T -u www-data \
-e COMPOSER_HOME=/var/www/html/storage/composer-cache \
-e COMPOSER_MEMORY_LIMIT=-1 \
flarum composer require linkrobins/clipboard --no-interaction --no-progress
docker compose exec -T -u www-data flarum php flarum extension:enable linkrobins-clipboard
docker compose exec -T -u www-data flarum php flarum cache:clear
curl -fsS http://localhost/ | grep -qi 'flarum' \
|| { echo "::error::forum stopped serving after an extension install"; exit 1; }
docker compose exec -T -u www-data flarum php flarum extension:disable linkrobins-clipboard
docker compose exec -T -u www-data \
-e COMPOSER_HOME=/var/www/html/storage/composer-cache \
-e COMPOSER_MEMORY_LIMIT=-1 \
flarum composer remove linkrobins/clipboard --no-interaction --no-progress
docker compose exec -T -u www-data flarum php flarum cache:clear
curl -fsS http://localhost/ | grep -qi 'flarum' \
|| { echo "::error::forum stopped serving after the extension was removed"; exit 1; }

- name: Plant a marker in the database
run: |
docker compose exec -T mariadb mariadb -uroot -p"${DB_ROOT_PASS}" flarum \
Expand Down Expand Up @@ -238,6 +281,24 @@ jobs:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# Read the Flarum version out of the committed lock so image tags can name
# the forum they ship. An image tag that only versions the wrapper cannot
# answer the question users actually have — "which Flarum is this?" — and
# nothing else in the tag list ever will.
- uses: actions/checkout@v4
- name: Read the pinned Flarum version
id: flarum
run: |
v="$(php -r '
$l = json_decode(file_get_contents("skeleton/composer.lock"), true);
foreach ($l["packages"] as $p) {
if ($p["name"] === "flarum/core") { echo ltrim($p["version"], "v"); exit; }
}
exit(1);
')"
echo "version=$v" >> "$GITHUB_OUTPUT"
echo "Flarum $v"

- name: Image tags + labels
id: meta
uses: docker/metadata-action@v5
Expand All @@ -248,6 +309,12 @@ jobs:
type=sha,format=short
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
# flarum-<version>: what forum this image contains, independent of
# the wrapper's own release number. Moves only when the lock does.
type=raw,value=flarum-${{ steps.flarum.outputs.version }},enable=${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') }}
labels: |
org.opencontainers.image.version=${{ steps.flarum.outputs.version }}
com.linkrobins.flarum.version=${{ steps.flarum.outputs.version }}
- name: Create the multi-arch manifest
working-directory: /tmp/digests
run: |
Expand Down
33 changes: 27 additions & 6 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,41 @@ RUN apt-get update \

# install-php-extensions pinned to a release tag (not /latest) so a compromised
# release can't slip into a build without a git diff.
#
# redis is pinned too. Everything else here is compiled from the PHP source
# already in the base image, but redis is fetched from pecl.php.net at build
# time — which is why v1.0.0 needed three attempts while PECL returned 504s.
# A pin does not make PECL more reliable, but it does mean a retry produces the
# SAME extension rather than whatever is newest by then.
ARG PECL_REDIS_VERSION=6.3.0
RUN curl -sSLf --retry 5 --retry-delay 2 --retry-connrefused \
-o /usr/local/bin/install-php-extensions \
https://github.com/mlocati/docker-php-extension-installer/releases/download/2.11.1/install-php-extensions \
&& chmod +x /usr/local/bin/install-php-extensions \
&& install-php-extensions \
bcmath ctype curl dom exif fileinfo filter gd hash intl json \
mbstring openssl pcre pdo session sodium tokenizer xml \
pdo_mysql opcache redis pcntl sockets zip
pdo_mysql opcache "redis-${PECL_REDIS_VERSION}" pcntl sockets zip

# Composer baked in (Flarum create-project / extension requires run at runtime
# against the data volume, but the binary is present in the image).
RUN curl -sS --retry 5 --retry-delay 2 --retry-connrefused \
https://getcomposer.org/installer \
| php -- --install-dir=/usr/local/bin --filename=composer
# Composer pinned, and the installer's signature checked before it is run.
#
# This used to pipe whatever getcomposer.org served straight into php,
# unverified and unversioned: a bad day upstream became a bad image, and two
# builds of the same commit could ship different Composer versions. The
# signature check is the one the Composer project publishes for exactly this.
ARG COMPOSER_VERSION=2.10.2
RUN set -eux; \
curl -sSLf --retry 5 --retry-delay 2 --retry-connrefused \
-o /tmp/composer-setup.php https://getcomposer.org/installer; \
expected="$(curl -sSLf --retry 5 https://composer.github.io/installer.sig)"; \
actual="$(php -r 'echo hash_file("sha384", "/tmp/composer-setup.php");')"; \
if [ "$expected" != "$actual" ]; then \
echo "composer installer signature mismatch" >&2; exit 1; \
fi; \
php /tmp/composer-setup.php --install-dir=/usr/local/bin --filename=composer \
--version="${COMPOSER_VERSION}"; \
rm -f /tmp/composer-setup.php; \
composer --version

# ── The pinned Flarum skeleton ───────────────────────────────────────────────
# Flarum itself, resolved at BUILD time from the committed lock rather than
Expand Down
Loading