From 6458c47f625b65a3527a64c4204b59909191d65e Mon Sep 17 00:00:00 2001 From: Darko Gjorgjijoski <5760249+gdarko@users.noreply.github.com> Date: Wed, 29 Jul 2026 15:04:20 +0200 Subject: [PATCH] ci: source release notes from CHANGELOG.md (#712) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Registration sent the GitHub release body to the updater, so the notes every install sees were written at publish time — after review, outside the repo, with nothing checking they existed or matched what shipped. CHANGELOG.md becomes the source. It is written and reviewed alongside the change itself, so what installs are offered cannot drift from what was merged, and the release body can simply point at it. A release with no section for its tag now fails the job rather than registering an empty changelog — the same reasoning as the token check added in #708. A manual dispatch falls back to the release body instead, since re-registering a release older than this file is legitimate. Section boundaries are matched on version headings rather than any "## ", because release notes routinely contain their own second-level headings: 5 of the 12 historical 2.x releases do. Verified by round-tripping 2.4.2 (2504 bytes, byte-identical) and 2.4.0 (5 inner headings, no content lost), and that "2.4" does not match the "2.4.2" section. Backfilled with the 2.4.x line; older releases stay on GitHub. --- .github/scripts/changelog-section.php | 71 +++++++++++++++++++++ .github/workflows/docker.yaml | 19 +++++- CHANGELOG.md | 88 +++++++++++++++++++++++++++ 3 files changed, 175 insertions(+), 3 deletions(-) create mode 100644 .github/scripts/changelog-section.php create mode 100644 CHANGELOG.md diff --git a/.github/scripts/changelog-section.php b/.github/scripts/changelog-section.php new file mode 100644 index 00000000..4c607db1 --- /dev/null +++ b/.github/scripts/changelog-section.php @@ -0,0 +1,71 @@ + [changelog-path] + * + * Exits 1 when the version has no section, so a release whose notes were + * forgotten stops the pipeline rather than registering an empty changelog on + * the updater. + * + * Section boundaries are found by matching *version* headings, not any "## ", + * because release notes routinely contain their own second-level headings. A + * section therefore runs until the next heading that looks like a version. + */ +$version = $argv[1] ?? ''; +$path = $argv[2] ?? dirname(__DIR__, 2).'/CHANGELOG.md'; + +if ($version === '') { + fwrite(STDERR, "usage: changelog-section.php [changelog-path]\n"); + exit(2); +} + +if (! is_readable($path)) { + fwrite(STDERR, "changelog not readable: {$path}\n"); + exit(2); +} + +$lines = preg_split('/\R/', (string) file_get_contents($path)); + +// A leading "v" is tolerated on either side so v2.4.2 and 2.4.2 both resolve. +$isVersionHeading = static fn (string $line): bool => (bool) preg_match('/^##\s+v?\d+\.\d+\.\d+/i', $line); +$wanted = ltrim($version, 'vV'); + +$section = []; +$capturing = false; + +foreach ($lines as $line) { + if ($isVersionHeading($line)) { + if ($capturing) { + break; + } + + // "## 2.4.2 — 2026-07-29" and "## 2.4.2" both match; a date or any other + // trailing text is ignored, but 2.4.2 must not match 2.4.20. + $capturing = (bool) preg_match( + '/^##\s+v?'.preg_quote($wanted, '/').'(?![\w.-])/i', + $line + ); + + continue; + } + + if ($capturing) { + $section[] = $line; + } +} + +$body = trim(implode("\n", $section)); + +if (! $capturing && $body === '') { + fwrite(STDERR, "no CHANGELOG.md section found for {$version}\n"); + exit(1); +} + +if ($body === '') { + fwrite(STDERR, "CHANGELOG.md section for {$version} is empty\n"); + exit(1); +} + +echo $body, "\n"; diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml index b2ec7a6c..600bf4d3 100644 --- a/.github/workflows/docker.yaml +++ b/.github/workflows/docker.yaml @@ -216,9 +216,22 @@ jobs: MIN_PHP=$(php -r '$c=require "config/installer.php"; echo $c["core"]["minPhpVersion"] ?? "";') EXTS=$(php -r '$c=require "config/installer.php"; echo implode(", ", $c["requirements"]["php"] ?? []);') - # Read the notes and pre-release flag from the release itself, so this behaves - # identically whether triggered by a publish or re-run later by hand. - gh release view "$TAG" --repo "$GITHUB_REPOSITORY" --json body --jq '.body' > /tmp/changelog.txt + # CHANGELOG.md is the source: it is written and reviewed alongside the + # change itself, so what installs are offered cannot drift from what was + # merged. A release with no section fails here rather than registering an + # empty changelog — except on a manual dispatch, where re-registering a + # release older than the file is legitimate and the GitHub body stands in. + if php .github/scripts/changelog-section.php "$TAG" > /tmp/changelog.txt; then + echo "Using the CHANGELOG.md section for $TAG" + elif [ "$EVENT" = "release" ]; then + echo "::error::No CHANGELOG.md section for $TAG — add one and re-run this job." + exit 1 + else + echo "::warning::No CHANGELOG.md section for $TAG — falling back to the release body." + gh release view "$TAG" --repo "$GITHUB_REPOSITORY" --json body --jq '.body' > /tmp/changelog.txt + fi + # The pre-release flag and timestamp come from the release itself, so this + # behaves identically whether triggered by a publish or re-run by hand. PRERELEASE=$(gh release view "$TAG" --repo "$GITHUB_REPOSITORY" --json isPrerelease --jq '.isPrerelease') PUBLISHED=$(gh release view "$TAG" --repo "$GITHUB_REPOSITORY" --json publishedAt --jq '.publishedAt') diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..f0f264b9 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,88 @@ +# Changelog + +Release notes for the 2.x line. Each `##` heading is a released version, and the +section beneath it is what CI publishes to the updater — see +`.github/scripts/changelog-section.php`. + +Releases before 2.4.0 are on GitHub: +https://github.com/InvoiceShelf/InvoiceShelf/releases + +## 2.4.2 — 2026-07-29 + +Maintenance release for the 2.x line, fixing five issues reported against the Docker images. Recommended for all self-hosted 2.x installs. + +### Containers failing to start on mounted volumes + +The entrypoint now recreates `storage/framework`, `storage/logs` and `storage/app` when a volume is missing them, instead of leaving the app to die with `Please provide a valid cache path`. Docker seeds a named volume only once, when it is empty, so a volume created by an older image never gained those directories on its own. + +It also no longer aborts on a `chown` it has no permission to make — a single file owned by your host user was previously enough to stop the container booting. If a mount genuinely is not writable, startup now says so and names the fix rather than failing later with a stack trace. + +Fixes [docker#75](https://github.com/InvoiceShelf/docker/issues/75), [docker#69](https://github.com/InvoiceShelf/docker/issues/69); improves [docker#77](https://github.com/InvoiceShelf/docker/issues/77) and [docker#63](https://github.com/InvoiceShelf/docker/issues/63). + +### The application timezone was ignored + +`APP_TIMEZONE` had no effect at all, so recurring invoices and scheduled tasks always ran on UTC no matter what you configured. Setting `TIMEZONE` on the container now works as documented. + +**Behaviour change:** if you have been setting `TIMEZONE` expecting it to work, your schedules will move to that timezone after upgrading. Fixes [docker#64](https://github.com/InvoiceShelf/docker/issues/64). + +### Setup wizard blank on MariaDB + +A fresh install using the shipped `docker-compose.mysql.yml` could not get past the database step, because that file sets `DB_CONNECTION=mariadb` and the wizard had no form for it. Fixes [docker#79](https://github.com/InvoiceShelf/docker/issues/79). + +### Gotenberg on a private network + +The SSRF guard added in 2.4.0 rejected `http://pdf:3000` — its own shipped default — which made the standard sidecar setup impossible to configure. Name the host you trust to permit it, and only it: + +``` +GOTENBERG_ALLOWED_PRIVATE_HOST=http://pdf:3000 +``` + +Every other private address stays blocked, so the setting cannot be repointed at an internal service. Fixes [#688](https://github.com/InvoiceShelf/InvoiceShelf/issues/688). + +### PHP 8.5 compatibility + +`PDO::MYSQL_ATTR_SSL_CA` is deprecated in PHP 8.5; the correct constant is now resolved per version. No change on PHP 8.4. + +--- + +Docker: `invoiceshelf/invoiceshelf:2.4.2` (also `:latest`, `:2`, `:2.4`). + +## 2.4.1 — 2026-06-14 + +Security patch for the 2.x line. + +Fixes a multi-tenant authorization issue in user management where a company owner could access user accounts belonging to another company. **All self-hosted 2.x installs should update.** + +Docker: `invoiceshelf/invoiceshelf:2.4.1` (also `:latest`, `:2`, `:2.4`). + +## 2.4.0 — 2026-06-12 + +### 2.x is entering feature freeze + +InvoiceShelf **2.4.0 marks the feature freeze for the 2.x line.** New feature development now moves to the next-generation **3.x** branch. From here, **2.x will receive security patches only, through September 1, 2027 (2027-09-01)** — no new features, but it stays supported and safe to run. We recommend planning your upgrade to 3.x before then; the in-app updater path to 3.x is being prepared. + +This release rolls up the final round of 2.x work: security hardening, dependency updates, groundwork for a clean v2 → v3 upgrade, a move to pnpm for the frontend toolchain, and a couple of contributor features. + +### Security +- Enforce company scope on notes, estimate→invoice conversion, and user bulk-delete — GHSA-85wc, GHSA-j2vg, GHSA-wxrv (#661) +- Harden public EmailLog token endpoints — GHSA-73q7 (#662) +- Validate `ORDER BY` input on list endpoints — GHSA-cp8p (#663) +- Restrict the Gotenberg renderer host to public addresses — GHSA-mfxg (#664) +- Recompute document totals server-side so client-supplied totals aren't trusted — GHSA-8c69 (#665) +- Update dependencies to patched versions (#674): `laravel/framework` (CVE-2026-48019), `symfony/*`, `guzzlehttp/psr7`, `vite` +- Patch frontend dependencies — axios, vite, postcss, follow-redirects (#653) + +### Improvements +- Add duplicate expense action (#617) — @mchev +- Support 3-decimal tax percentages, e.g. `6.625%` (#616) — @mchev +- Updater: manifest-based stale-file cleanup + cache clearing, preparing a clean v2 → v3 in-app upgrade path (#659) + +### Build & Tooling +- Migrate the frontend toolchain to **pnpm** and pin a stable Vite build (#666, #674) + +### Translations +- New Crowdin translation updates across many locales (#615) + +> ℹ️ The CSV export work (#649) was reverted before this release and is deferred. + +**Full Changelog**: https://github.com/InvoiceShelf/InvoiceShelf/compare/2.3.3...2.4.0