The e2e gate rebuilt selfpost:e2e through compose.override.yml with its
own VERSION=e2e, and that rebuild is the image release.yml pushed: every
published image printed "e2e", and the backup/restore version check
compared a backup's version with "e2e".
- compose.override.yml: VERSION: ${SELFPOST_E2E_VERSION:-e2e}.
- release.yml: the e2e step gets SELFPOST_E2E_VERSION = the release's
X.Y.Z; a new step before the push checks panel -version and
selfpost-backup -version of selfpost:e2e against it.
- e2e: image_stamped_with_version checks both binaries against
expectedVersion() (the variable, or "e2e" when unset).
- development.md, CHANGELOG [Unreleased], audit plan line ticked,
roadmap 1/61.
Evidence: make e2e on the test server for this tree with
SELFPOST_E2E_VERSION=2.4.2-stamp - exit 0, TestE2E/image_stamped_with_version
PASS, and `docker run --rm --entrypoint <bin> selfpost:e2e -version` prints
2.4.2-stamp for panel and selfpost-backup. GOOS=linux go vet in test/e2e
clean. Published images are left as they are.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -89,8 +89,27 @@ jobs:
|
|||||||
cache-dependency-path: test/e2e/go.sum
|
cache-dependency-path: test/e2e/go.sum
|
||||||
|
|
||||||
- name: e2e (gates publishing — see docs/development.md)
|
- name: e2e (gates publishing — see docs/development.md)
|
||||||
|
# The e2e builds selfpost:e2e again through compose.override.yml, and
|
||||||
|
# that rebuild is the image pushed below. Without the version here it
|
||||||
|
# was stamped "e2e" (audit 2026-10-12, H1); the suite now checks the
|
||||||
|
# stamp against this variable.
|
||||||
|
env:
|
||||||
|
SELFPOST_E2E_VERSION: ${{ needs.prepare.outputs.version }}
|
||||||
run: cd test/e2e && go test -v -timeout 20m ./...
|
run: cd test/e2e && go test -v -timeout 20m ./...
|
||||||
|
|
||||||
|
- name: Check the version stamp of the image about to be pushed
|
||||||
|
env:
|
||||||
|
WANT: ${{ needs.prepare.outputs.version }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
for bin in panel selfpost-backup; do
|
||||||
|
got=$(docker run --rm --entrypoint "$bin" selfpost:e2e -version)
|
||||||
|
if [ "$got" != "$WANT" ]; then
|
||||||
|
echo "::error::selfpost:e2e $bin -version = '$got', want '$WANT'; refusing to push"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
- name: Log in to ghcr.io
|
- name: Log in to ghcr.io
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -5,6 +5,19 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); version
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **A release image says which release it is.** The e2e gate in
|
||||||
|
`release.yml` rebuilt the image it was about to push with its own
|
||||||
|
`VERSION=e2e`, so every published image printed `e2e` for `-version` and in
|
||||||
|
the panel's footer, and the backup/restore version check compared a
|
||||||
|
backup's version with `e2e` - any backup restored into any release (audit
|
||||||
|
2026-10-12, H1). The gate now builds with the release's version
|
||||||
|
(`SELFPOST_E2E_VERSION`), the e2e checks `panel -version` and
|
||||||
|
`selfpost-backup -version` against it, and `release.yml` checks the stamp
|
||||||
|
again on the image right before pushing it. Images already published are
|
||||||
|
left as they are.
|
||||||
|
|
||||||
## [2.4.1] - 2026-10-12
|
## [2.4.1] - 2026-10-12
|
||||||
|
|
||||||
Documentation only; no product code changes.
|
Documentation only; no product code changes.
|
||||||
|
|||||||
@@ -545,7 +545,7 @@ has already run is never edited — the next file is added; the published
|
|||||||
|
|
||||||
### Stage 0 — release hygiene (no product code) → `2.4.2`
|
### Stage 0 — release hygiene (no product code) → `2.4.2`
|
||||||
|
|
||||||
- [ ] **Stamp the right version** (H1). Make the e2e build take the version from
|
- [x] **Stamp the right version** (H1). Make the e2e build take the version from
|
||||||
the environment (`VERSION: ${SELFPOST_E2E_VERSION:-e2e}` in
|
the environment (`VERSION: ${SELFPOST_E2E_VERSION:-e2e}` in
|
||||||
`compose.override.yml`, set by `release.yml`), or have the e2e reuse the
|
`compose.override.yml`, set by `release.yml`), or have the e2e reuse the
|
||||||
image already built instead of rebuilding. Done when the e2e asserts
|
image already built instead of rebuilding. Done when the e2e asserts
|
||||||
|
|||||||
+10
-3
@@ -269,7 +269,12 @@ The release image is published **only** for a SemVer version `X.Y.Z`: a
|
|||||||
that supplies that version. Pushing a git tag alone does not publish. Ordinary
|
that supplies that version. Pushing a git tag alone does not publish. Ordinary
|
||||||
commits, and a dispatch from `main` without a version input, do not publish.
|
commits, and a dispatch from `main` without a version input, do not publish.
|
||||||
The version is the single source that drives the image tag and `-ldflags` in
|
The version is the single source that drives the image tag and `-ldflags` in
|
||||||
the binaries so they cannot drift apart.
|
the binaries so they cannot drift apart. The e2e gate rebuilds the image it
|
||||||
|
then pushes, so `release.yml` hands it the version as `SELFPOST_E2E_VERSION`;
|
||||||
|
the suite's `image_stamped_with_version` and a check right before the push
|
||||||
|
both compare `panel -version` and `selfpost-backup -version` with it. (Until
|
||||||
|
`2.4.2` the rebuild stamped every published image `e2e`; those images stay as
|
||||||
|
they are.)
|
||||||
|
|
||||||
**Steps (on explicit request):**
|
**Steps (on explicit request):**
|
||||||
|
|
||||||
@@ -395,7 +400,8 @@ make e2e
|
|||||||
[test/e2e/compose.override.yml](../test/e2e/compose.override.yml) — same
|
[test/e2e/compose.override.yml](../test/e2e/compose.override.yml) — same
|
||||||
`cap_drop`/`cap_add`/`no-new-privileges` as production. Override: high ports
|
`cap_drop`/`cap_add`/`no-new-privileges` as production. Override: high ports
|
||||||
(`20465`/`20587`/`20080`), test hostname, self-signed TLS,
|
(`20465`/`20587`/`20080`), test hostname, self-signed TLS,
|
||||||
`PANEL_COOKIE_SECURE=false`, isolated compose project. Mail is hermetic:
|
`PANEL_COOKIE_SECURE=false`, isolated compose project, and the image's
|
||||||
|
`VERSION` from `SELFPOST_E2E_VERSION` (unset locally: `e2e`). Mail is hermetic:
|
||||||
CoreDNS (fake zone) + Postfix `smtp-sink` as sink-MX; DKIM TXT is scraped from
|
CoreDNS (fake zone) + Postfix `smtp-sink` as sink-MX; DKIM TXT is scraped from
|
||||||
the panel and published into the zone — the test verifies the records an
|
the panel and published into the zone — the test verifies the records an
|
||||||
operator would actually use.
|
operator would actually use.
|
||||||
@@ -440,7 +446,8 @@ release: published
|
|||||||
prepare (version from release tag or workflow_dispatch input; checkout vX.Y.Z)
|
prepare (version from release tag or workflow_dispatch input; checkout vX.Y.Z)
|
||||||
→ build [matrix: ubuntu-latest / ubuntu-24.04-arm]
|
→ build [matrix: ubuntu-latest / ubuntu-24.04-arm]
|
||||||
→ docker build --load (VERSION from prepare)
|
→ docker build --load (VERSION from prepare)
|
||||||
→ e2e (test/e2e)
|
→ e2e (test/e2e; rebuilds selfpost:e2e with SELFPOST_E2E_VERSION=X.Y.Z)
|
||||||
|
→ check panel / selfpost-backup -version = X.Y.Z
|
||||||
→ push ghcr.io/...:X.Y.Z-amd64 | X.Y.Z-arm64
|
→ push ghcr.io/...:X.Y.Z-amd64 | X.Y.Z-arm64
|
||||||
→ merge
|
→ merge
|
||||||
→ docker buildx imagetools create → unified manifest X.Y.Z
|
→ docker buildx imagetools create → unified manifest X.Y.Z
|
||||||
|
|||||||
+1
-1
@@ -42,7 +42,7 @@ in `git log` and [CHANGELOG.md](../CHANGELOG.md).
|
|||||||
| from-header-binding | Hold a message's `From:` header against the application that sends it | shipped `2.4.0` | 9/9 | [plans/from-header-binding.md](plans/from-header-binding.md) |
|
| from-header-binding | Hold a message's `From:` header against the application that sends it | shipped `2.4.0` | 9/9 | [plans/from-header-binding.md](plans/from-header-binding.md) |
|
||||||
| panel-redesign | Panel redesign (accepted mockups, design contract) | shipped `2.0.0` | 27/27 | [plans/panel-redesign.md](plans/panel-redesign.md) |
|
| panel-redesign | Panel redesign (accepted mockups, design contract) | shipped `2.0.0` | 27/27 | [plans/panel-redesign.md](plans/panel-redesign.md) |
|
||||||
| schema-squash | Squash SQLite migrations into a 2.x baseline | built in panel-redesign (stage 1) | — | [plans/panel-redesign.md](plans/panel-redesign.md) |
|
| schema-squash | Squash SQLite migrations into a 2.x baseline | built in panel-redesign (stage 1) | — | [plans/panel-redesign.md](plans/panel-redesign.md) |
|
||||||
| audit-2026-10-12 | Follow-ups of the 2026-10-12 full-tree code audit, seven stages | agreed (2026-10-12: stage 0 first, then 1+2; the rest after) | 0/61 | [audit-2026-10-12.md](audit-2026-10-12.md) |
|
| audit-2026-10-12 | Follow-ups of the 2026-10-12 full-tree code audit, seven stages | agreed (2026-10-12: stage 0 first, then 1+2; the rest after) | 1/61 | [audit-2026-10-12.md](audit-2026-10-12.md) |
|
||||||
|
|
||||||
**Recommended order** (not binding; owner, 2026-10-09: feature order follows
|
**Recommended order** (not binding; owner, 2026-10-09: feature order follows
|
||||||
development convenience): **panel-redesign** shipped as `2.0.0` (2026-10-11) and
|
development convenience): **panel-redesign** shipped as `2.0.0` (2026-10-11) and
|
||||||
|
|||||||
@@ -28,7 +28,11 @@ services:
|
|||||||
context: ../../..
|
context: ../../..
|
||||||
dockerfile: build/Dockerfile
|
dockerfile: build/Dockerfile
|
||||||
args:
|
args:
|
||||||
VERSION: e2e
|
# release.yml sets SELFPOST_E2E_VERSION to the release's X.Y.Z, so the
|
||||||
|
# image this build tags selfpost:e2e - the one release.yml then pushes -
|
||||||
|
# carries the release's version, not "e2e". A local `make e2e` leaves it
|
||||||
|
# unset. The suite checks the stamp (image_stamped_with_version).
|
||||||
|
VERSION: ${SELFPOST_E2E_VERSION:-e2e}
|
||||||
image: selfpost:e2e
|
image: selfpost:e2e
|
||||||
environment:
|
environment:
|
||||||
SELFPOST_HOSTNAME: mail.e2e.test
|
SELFPOST_HOSTNAME: mail.e2e.test
|
||||||
|
|||||||
@@ -130,6 +130,23 @@ func TestE2E(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// The image is pushed as the release, so it must say it is the release:
|
||||||
|
// the footer, -version and the backup/restore version check all read this
|
||||||
|
// stamp. Release images were once stamped "e2e" because this stand rebuilt
|
||||||
|
// them with its own VERSION (audit 2026-10-12, H1).
|
||||||
|
run("image_stamped_with_version", func(t *testing.T) {
|
||||||
|
want := expectedVersion()
|
||||||
|
for _, bin := range []string{"panel", "selfpost-backup"} {
|
||||||
|
out, err := h.execIn("selfpost", bin, "-version")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s -version: %v", bin, err)
|
||||||
|
}
|
||||||
|
if got := strings.TrimSpace(out); got != want {
|
||||||
|
t.Fatalf("%s -version = %q, want %q", bin, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
run("setup_and_login", func(t *testing.T) {
|
run("setup_and_login", func(t *testing.T) {
|
||||||
token, err := readSetupToken(h)
|
token, err := readSetupToken(h)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -89,6 +89,16 @@ func (s *stack) execIn(service string, args ...string) (string, error) {
|
|||||||
return string(out), err
|
return string(out), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// expectedVersion is the version the stand's image is built with: the
|
||||||
|
// release's X.Y.Z when release.yml runs the suite, "e2e" for a local run. Its
|
||||||
|
// default must stay the default of VERSION in compose.override.yml.
|
||||||
|
func expectedVersion() string {
|
||||||
|
if v := os.Getenv("SELFPOST_E2E_VERSION"); v != "" {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return "e2e"
|
||||||
|
}
|
||||||
|
|
||||||
func (s *stack) build() error {
|
func (s *stack) build() error {
|
||||||
_, err := s.compose("build")
|
_, err := s.compose("build")
|
||||||
return err
|
return err
|
||||||
|
|||||||
Reference in New Issue
Block a user