1 Commits
Author SHA1 Message Date
Eric Ireland d36bc234d8 Add reproducible release tooling 2026-07-26 18:58:37 +10:00
5 changed files with 68 additions and 2 deletions
+5
View File
@@ -7,6 +7,11 @@ and releases use [Semantic Versioning](https://semver.org/).
## [Unreleased]
### Added
- Reproducible tagged-source archives and SHA-256 checksums through `make dist`
- Automated release-version consistency testing
## [0.1.0] - 2026-07-26
### Added
+4
View File
@@ -14,6 +14,10 @@ Before proposing a code change:
6. Do not include real domains, addresses, passwords, hashes, logs or private
server configuration.
Release maintainers should create an annotated version tag before running
`make dist`. The target archives the tag corresponding to the executable's
version and writes a SHA-256 checksum beside the resulting archive.
Changes affecting transactions, rollback, password handling, ownership, path
validation or command execution require particular care and should include
failure-path tests.
+31 -1
View File
@@ -7,13 +7,21 @@ DESTDIR ?=
PYTHON ?= python3
INSTALL ?= install
GZIP ?= gzip
GIT ?= git
SHA256SUM ?= sha256sum
SBINDIR := $(DESTDIR)$(PREFIX)/sbin
MANDIR := $(DESTDIR)$(PREFIX)/share/man/man8
BACKUPDIR := $(DESTDIR)/var/backups/vmailctl
CONFIG := $(DESTDIR)$(SYSCONFDIR)/vmailctl.conf
VERSION := $(shell sed -n 's/^VERSION = "\(.*\)"/\1/p' vmailctl)
RELEASE_TAG ?= v$(VERSION)
DISTDIR ?= dist
DIST_NAME := vmailctl-$(VERSION)
DIST_ARCHIVE := $(DISTDIR)/$(DIST_NAME).tar.gz
DIST_CHECKSUM := $(DIST_ARCHIVE).sha256
.PHONY: all check man-check install
.PHONY: all check man-check dist install
all: check
@@ -24,6 +32,28 @@ check:
man-check:
groff -man -z -ww man/vmailctl.8
dist: check man-check
@test -n "$(VERSION)" || { echo "Cannot determine VERSION" >&2; exit 1; }
@$(GIT) rev-parse --verify --quiet \
"refs/tags/$(RELEASE_TAG)^{commit}" >/dev/null || { \
echo "Release tag $(RELEASE_TAG) does not exist" >&2; exit 1; \
}
@$(INSTALL) -d -m 0755 "$(DISTDIR)"
@set -eu; \
archive_tmp="$(DIST_ARCHIVE).tmp"; \
checksum_tmp="$(DIST_CHECKSUM).tmp"; \
trap 'rm -f -- "$$archive_tmp" "$$checksum_tmp"' EXIT; \
LC_ALL=C $(GIT) archive --format=tar \
--prefix="$(DIST_NAME)/" "$(RELEASE_TAG)" | \
$(GZIP) -n -9 >"$$archive_tmp"; \
mv -f -- "$$archive_tmp" "$(DIST_ARCHIVE)"; \
(cd "$(DISTDIR)" && \
$(SHA256SUM) "$(DIST_NAME).tar.gz") >"$$checksum_tmp"; \
mv -f -- "$$checksum_tmp" "$(DIST_CHECKSUM)"; \
trap - EXIT
@echo "Created $(DIST_ARCHIVE)"
@echo "Created $(DIST_CHECKSUM)"
install:
$(INSTALL) -d -m 0755 $(SBINDIR)
$(INSTALL) -m 0755 vmailctl $(SBINDIR)/vmailctl
+14
View File
@@ -137,6 +137,20 @@ make check
If GNU `groff` is installed, `make man-check` also validates the manual page.
## Release archives
Maintainers can create a reproducible source archive and SHA-256 checksum from
the annotated tag matching the version in `vmailctl`:
```sh
make dist
```
The files are written beneath `dist/`. The archive is generated from the tag,
not the working tree, so an existing release cannot silently acquire later
changes. This maintainer target requires Git, `gzip`, `sha256sum`, and GNU
`groff`.
## Development and provenance
The initial version was designed and developed by Eric Ireland with assistance
+14 -1
View File
@@ -14,7 +14,8 @@ import unittest
from unittest import mock
SOURCE = Path(__file__).resolve().parents[1] / "vmailctl"
PROJECT_ROOT = Path(__file__).resolve().parents[1]
SOURCE = PROJECT_ROOT / "vmailctl"
SPEC = importlib.util.spec_from_loader(
"vmailctl",
SourceFileLoader("vmailctl", os.fspath(SOURCE)),
@@ -208,6 +209,18 @@ class PasswordTests(unittest.TestCase):
self.assertEqual(mocked_run.call_args.kwargs["input_text"], "secret-value\nsecret-value\n")
class ReleaseMetadataTests(unittest.TestCase):
def test_version_is_consistent_across_public_documentation(self):
version = vmailctl.VERSION
readme = (PROJECT_ROOT / "README.md").read_text(encoding="utf-8")
changelog = (PROJECT_ROOT / "CHANGELOG.md").read_text(encoding="utf-8")
manual = (PROJECT_ROOT / "man" / "vmailctl.8").read_text(encoding="utf-8")
self.assertIn(f"`{version}` is a public beta", readme)
self.assertIn(f"## [{version}]", changelog)
self.assertIn(f'"vmailctl {version}"', manual)
class TransactionLogicTests(unittest.TestCase):
def fixture_settings(self, root: Path):
users = root / "virtual-users"