Split release and package publish gates

This commit is contained in:
m1ngsama 2026-05-28 11:29:25 +08:00
parent fe7419709e
commit 0da5f51e2e
17 changed files with 232 additions and 51 deletions

View file

@ -35,6 +35,9 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Verify release tag matches source version
run: scripts/check_release_ref.sh "${GITHUB_REF_NAME}"
- name: Install dependencies (Ubuntu)
if: runner.os == 'Linux'
run: |
@ -97,6 +100,9 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Verify release tag matches source version
run: scripts/check_release_ref.sh "${GITHUB_REF_NAME}"
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
@ -122,12 +128,24 @@ jobs:
body: |
## Installation
Install the libssh runtime before running TNT:
```bash
# Ubuntu/Debian
sudo apt install libssh-4
# Arch
sudo pacman -S libssh
# macOS
brew install libssh
```
Download the binary for your platform:
**Linux AMD64:**
```bash
wget https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/tnt-linux-amd64
wget https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/tntctl-linux-amd64
curl -LO https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/tnt-linux-amd64
curl -LO https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/tntctl-linux-amd64
chmod +x tnt-linux-amd64
chmod +x tntctl-linux-amd64
sudo mv tnt-linux-amd64 /usr/local/bin/tnt
@ -136,8 +154,8 @@ jobs:
**Linux ARM64:**
```bash
wget https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/tnt-linux-arm64
wget https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/tntctl-linux-arm64
curl -LO https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/tnt-linux-arm64
curl -LO https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/tntctl-linux-arm64
chmod +x tnt-linux-arm64
chmod +x tntctl-linux-arm64
sudo mv tnt-linux-arm64 /usr/local/bin/tnt
@ -146,8 +164,8 @@ jobs:
**macOS Intel:**
```bash
wget https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/tnt-darwin-amd64
wget https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/tntctl-darwin-amd64
curl -LO https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/tnt-darwin-amd64
curl -LO https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/tntctl-darwin-amd64
chmod +x tnt-darwin-amd64
chmod +x tntctl-darwin-amd64
sudo mv tnt-darwin-amd64 /usr/local/bin/tnt
@ -156,8 +174,8 @@ jobs:
**macOS Apple Silicon:**
```bash
wget https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/tnt-darwin-arm64
wget https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/tntctl-darwin-arm64
curl -LO https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/tnt-darwin-arm64
curl -LO https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/tntctl-darwin-arm64
chmod +x tnt-darwin-arm64
chmod +x tntctl-darwin-arm64
sudo mv tnt-darwin-arm64 /usr/local/bin/tnt
@ -166,7 +184,15 @@ jobs:
**Verify checksums:**
```bash
sha256sum -c checksums.txt
curl -LO https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/checksums.txt
# Linux
sha256sum -c checksums.txt --ignore-missing
# macOS
for f in tnt-* tntctl-*; do
grep " $f$" checksums.txt | shasum -a 256 -c -
done
```
## What's Changed

View file

@ -35,7 +35,7 @@ MANDIR ?= $(PREFIX)/share/man
SYSTEMD_UNIT_DIR ?= $(PREFIX)/lib/systemd/system
CI_TEST_PORT ?= $(if $(PORT),$(PORT),2222)
.PHONY: all clean install install-systemd uninstall uninstall-systemd debug release release-check release-check-strict debian-source-package asan valgrind check test test-advisory ci-test unit-test script-test integration-test anonymous-access-test connection-limit-test security-test stress-test soak-test slow-client-test user-lifecycle-test info
.PHONY: all clean install install-systemd uninstall uninstall-systemd debug release release-check release-check-strict package-publish-check debian-source-package asan valgrind check test test-advisory ci-test unit-test script-test integration-test anonymous-access-test connection-limit-test security-test stress-test soak-test slow-client-test user-lifecycle-test info
all: $(TARGETS)
@ -95,6 +95,9 @@ release-check:
release-check-strict:
./scripts/release_check.sh --strict
package-publish-check:
./scripts/package_publish_check.sh
debian-source-package:
./scripts/package_debian_source.sh $${OUT_DIR:-dist/debian-source}

View file

@ -401,10 +401,11 @@ Longer local preflight can opt into runtime soak and slow-client coverage:
RUN_SOAK=1 RUN_SLOW_CLIENT=1 make release-check
```
Before publishing package recipes, replace placeholder checksums and run:
Before publishing package recipes, download the final GitHub source archive,
replace placeholder checksums, and run:
```sh
make release-check-strict
SOURCE_TARBALL=dist/tnt-chat-vX.Y.Z.tar.gz make package-publish-check
```
## Files

View file

@ -3,6 +3,10 @@
## Unreleased
### Added
- Added a release tag/version guard used by the GitHub release workflow, so a
`vX.Y.Z` tag must match `TNT_VERSION` before release assets are built.
- Added `make package-publish-check` for verifying Arch/Homebrew source
checksums against the final GitHub source archive after a tag exists.
- Added a dedicated `tntctl_text` module with unit coverage for local
`tntctl` help and validation diagnostics.
- Documented the stable SSH exec interface contract, including exit statuses
@ -37,6 +41,17 @@
and server survival stay responsive.
### Changed
- INSERT-mode chrome now only advertises message sending and `Esc` to NORMAL;
`? keys` appears only in NORMAL mode, matching where help keys work.
- Dismissing MOTD now returns first-time users to INSERT mode, and `Ctrl+C`
closes the full key reference before it disconnects from NORMAL mode.
- COMMAND mode now accepts an optional leading `:` in typed commands, matching
the way commands are written in the manual.
- `:search` output and docs now state that the command shows the last 15
matches, avoiding the impression that the pager is a complete result set.
- Release checks now separate tag/source-archive readiness from package-manager
checksum publishing, avoiding self-referential checksum requirements before
the final GitHub source archive exists.
- `tntctl --help` now gets its exec command list from `exec_catalog`, reducing
duplicate command metadata between the local wrapper and SSH exec mode.
- Updated `tnt(1)` to document the current TUI search and pager keys, and

View file

@ -35,8 +35,7 @@ Release policy:
- packaging/arch/PKGBUILD
- packaging/homebrew/tnt-chat.rb
- packaging/debian/debian/changelog
- package checksums and maintainer metadata, when preparing public package
recipes
- maintainer metadata, when preparing public package recipes
2. Run the local preflight:
make release-check
@ -46,7 +45,7 @@ Release policy:
3. Commit the release changes and create a local tag. Do not push the tag
until strict checks pass:
git tag v1.0.1
git tag vX.Y.Z
4. Run strict release checks:
make release-check-strict
@ -56,7 +55,7 @@ Release policy:
untracked and would be missing from GitHub's source archive.
5. Push the tag:
git push origin v1.0.1
git push origin vX.Y.Z
6. GitHub Actions automatically:
- Builds `tnt` and `tntctl` binaries (Linux/macOS, AMD64/ARM64)
@ -77,7 +76,9 @@ RELEASE REVIEW CHECKLIST
Before publishing a draft release:
- Confirm `git tag` points at the intended commit.
- Download every release asset from GitHub, not from the local workspace.
- Verify `checksums.txt` with `sha256sum -c checksums.txt`.
- Verify downloaded assets against `checksums.txt` (`sha256sum -c
checksums.txt --ignore-missing` on Linux, or `shasum -a 256 -c` for each
downloaded asset on macOS).
- Run downloaded `tnt --version` and `tntctl --version`.
- Start a temporary server and check:
ssh -p 2222 server health
@ -87,8 +88,10 @@ Before publishing a draft release:
ssh -p 2222 server "tail -n 1"
- Check runtime dynamic links (`ldd` on Linux, `otool -L` on macOS) and make
sure `libssh` is documented for the target install path.
- Confirm `make release-check-strict` passed after package checksums were
replaced.
- Confirm `make release-check-strict` passed before pushing the tag.
- For package-manager recipes, download the final GitHub source archive,
replace Arch/Homebrew source checksums, then run:
SOURCE_TARBALL=dist/tnt-chat-vX.Y.Z.tar.gz make package-publish-check
ROLLBACK
@ -158,8 +161,8 @@ make && make asan && make release-check
./tnt
# Create release
git tag v1.0.1
git push origin v1.0.1
git tag vX.Y.Z
git push origin vX.Y.Z
# Wait 5 minutes for builds
# Deploy to production manually after validation

View file

@ -9,7 +9,7 @@ curl -sSL https://raw.githubusercontent.com/m1ngsama/TNT/main/install.sh | sh
Specific version:
```bash
VERSION=v1.0.1 curl -sSL https://raw.githubusercontent.com/m1ngsama/TNT/main/install.sh | sh
VERSION=vX.Y.Z curl -sSL https://raw.githubusercontent.com/m1ngsama/TNT/main/install.sh | sh
```
## Manual Install
@ -18,12 +18,12 @@ Download binary for your platform from [releases](https://github.com/m1ngsama/TN
```bash
# Linux AMD64
wget https://github.com/m1ngsama/TNT/releases/latest/download/tnt-linux-amd64
curl -LO https://github.com/m1ngsama/TNT/releases/latest/download/tnt-linux-amd64
chmod +x tnt-linux-amd64
sudo mv tnt-linux-amd64 /usr/local/bin/tnt
# macOS ARM64 (Apple Silicon)
wget https://github.com/m1ngsama/TNT/releases/latest/download/tnt-darwin-arm64
curl -LO https://github.com/m1ngsama/TNT/releases/latest/download/tnt-darwin-arm64
chmod +x tnt-darwin-arm64
sudo mv tnt-darwin-arm64 /usr/local/bin/tnt
```

View file

@ -113,9 +113,9 @@ Goal: make regressions harder to introduce.
These are the next changes that should happen before new feature work expands the surface area.
1. Replace remaining release placeholders with real maintainer metadata and
source-archive checksums when cutting a public package release.
2. Create or move the `v1.0.1` tag only when the release commit is final, then
run `make release-check-strict`.
1. Replace remaining source-archive checksum placeholders only after the final
GitHub source archive exists, then run `make package-publish-check`.
2. Create or move the `vX.Y.Z` tag only when the release commit is final, then
run `make release-check-strict` before pushing it.
3. Decide whether admin-only moderation controls belong in 1.0.x or should
wait for a later minor release.

View file

@ -27,6 +27,34 @@ sha256_of() {
fi
}
warn_missing_libssh() {
case "$OS" in
linux)
if command -v ldconfig >/dev/null 2>&1 &&
ldconfig -p 2>/dev/null | grep -q 'libssh\.so'; then
return
fi
for path in /usr/lib/libssh.so* /usr/lib64/libssh.so* \
/lib/libssh.so* /lib64/libssh.so*; do
[ -e "$path" ] && return
done
echo "WARNING: TNT requires the libssh runtime library."
echo "Install it first, for example:"
echo " Ubuntu/Debian: sudo apt install libssh-4"
echo " Arch: sudo pacman -S libssh"
;;
darwin)
if [ -e /opt/homebrew/opt/libssh/lib/libssh.dylib ] ||
[ -e /usr/local/opt/libssh/lib/libssh.dylib ]; then
return
fi
echo "WARNING: TNT requires the libssh runtime library."
echo "Install it first:"
echo " brew install libssh"
;;
esac
}
need_cmd curl
need_cmd awk
@ -53,6 +81,7 @@ echo "OS: $OS"
echo "Arch: $ARCH"
echo "Version: $VERSION"
echo ""
warn_missing_libssh
# Get latest version if not specified
if [ "$VERSION" = "latest" ]; then

View file

@ -17,7 +17,7 @@ Package installs include both `tnt` and `tntctl`. `tnt` is the server process;
1. Confirm `TNT_VERSION` in `include/common.h` and the manpage version match.
Also update package versions in Arch, Homebrew, and Debian drafts.
2. Create a GitHub release tag such as `v1.0.1`.
2. Create a GitHub release tag such as `vX.Y.Z`.
3. Build and upload release tarballs or rely on GitHub source archives.
4. Replace placeholder checksums in package drafts.
5. Verify package contents in an isolated directory:
@ -35,10 +35,11 @@ Package installs include both `tnt` and `tntctl`. `tnt` is the server process;
Use `scripts/package_debian_source.sh --build` on a Debian/Ubuntu system
with `dpkg-buildpackage` installed to build the unsigned source package.
7. Before submitting package recipes, replace checksum placeholders and run:
7. Before submitting package recipes, download the final GitHub source archive,
replace checksum placeholders, and run:
```sh
make release-check-strict
SOURCE_TARBALL=dist/tnt-chat-vX.Y.Z.tar.gz make package-publish-check
```
8. Submit packages manually:

View file

@ -1,4 +1,4 @@
# Maintainer: M1ng <REPLACE_WITH_EMAIL>
# Maintainer: M1ng <contact@m1ng.space>
pkgname=tnt-chat
pkgver=1.0.1

View file

@ -26,11 +26,12 @@ After editing `PKGBUILD`, regenerate `.SRCINFO`:
makepkg --printsrcinfo > .SRCINFO
```
Before AUR submission, replace `sha256sums=('SKIP')` with the real release
archive checksum, then run the project-level strict check:
Before AUR submission, replace `sha256sums=('SKIP')` with the real GitHub
source archive checksum, regenerate `.SRCINFO`, then run the package publish
check:
```sh
make release-check-strict
SOURCE_TARBALL=dist/tnt-chat-vX.Y.Z.tar.gz make package-publish-check
```
## Manual AUR submission
@ -40,7 +41,7 @@ git clone ssh://aur@aur.archlinux.org/tnt-chat.git aur-tnt-chat
cp PKGBUILD .SRCINFO aur-tnt-chat/
cd aur-tnt-chat
git add PKGBUILD .SRCINFO
git commit -m "Update to 1.0.1"
git commit -m "Update to X.Y.Z"
git push
```

View file

@ -2,4 +2,4 @@ tnt-chat (1.0.1-1) unstable; urgency=medium
* Initial package draft.
-- M1ng <REPLACE_WITH_EMAIL> Thu, 21 May 2026 00:00:00 +0800
-- M1ng <contact@m1ng.space> Thu, 21 May 2026 00:00:00 +0800

View file

@ -1,7 +1,7 @@
Source: tnt-chat
Section: net
Priority: optional
Maintainer: M1ng <REPLACE_WITH_EMAIL>
Maintainer: M1ng <contact@m1ng.space>
Build-Depends:
debhelper-compat (= 13),
libssh-dev,

View file

@ -30,20 +30,20 @@ ruby -c packaging/homebrew/tnt-chat.rb
## Updating the formula
1. Publish a GitHub release tag such as `v1.0.1`.
1. Publish a GitHub release tag such as `vX.Y.Z`.
2. Download or hash the release source archive:
```sh
curl -L -o tnt-chat-1.0.1.tar.gz \
https://github.com/m1ngsama/TNT/archive/refs/tags/v1.0.1.tar.gz
shasum -a 256 tnt-chat-1.0.1.tar.gz
curl -L -o dist/tnt-chat-vX.Y.Z.tar.gz \
https://github.com/m1ngsama/TNT/archive/refs/tags/vX.Y.Z.tar.gz
shasum -a 256 dist/tnt-chat-vX.Y.Z.tar.gz
```
3. Replace `REPLACE_WITH_RELEASE_TARBALL_SHA256` in `tnt-chat.rb`.
4. Run:
```sh
make release-check-strict
SOURCE_TARBALL=dist/tnt-chat-vX.Y.Z.tar.gz make package-publish-check
```
5. Copy the formula into the tap repository and open a normal review PR.

31
scripts/check_release_ref.sh Executable file
View file

@ -0,0 +1,31 @@
#!/bin/sh
# Verify that a release tag matches TNT_VERSION.
set -eu
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
cd "$ROOT"
fail() {
echo "release-ref-check: $*" >&2
exit 1
}
ref=${1:-${GITHUB_REF_NAME:-}}
[ -n "$ref" ] || fail "missing release ref; pass vX.Y.Z or set GITHUB_REF_NAME"
case "$ref" in
refs/tags/*) tag=${ref#refs/tags/} ;;
*) tag=$ref ;;
esac
printf '%s\n' "$tag" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+$' ||
fail "release ref must be vMAJOR.MINOR.PATCH, got $tag"
version=$(sed -n 's/^#define TNT_VERSION "\([^"]*\)".*/\1/p' include/common.h)
[ -n "$version" ] || fail "could not read TNT_VERSION from include/common.h"
[ "$tag" = "v$version" ] ||
fail "release tag $tag does not match TNT_VERSION $version"
echo "release ref matches TNT_VERSION: $tag"

View file

@ -0,0 +1,68 @@
#!/bin/sh
# Verify package-manager recipes against a final release source archive.
set -eu
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
cd "$ROOT"
fail() {
echo "package-publish-check: $*" >&2
exit 1
}
sha256_of() {
if command -v sha256sum >/dev/null 2>&1; then
sha256sum "$1" | awk '{print $1}'
elif command -v shasum >/dev/null 2>&1; then
shasum -a 256 "$1" | awk '{print $1}'
else
fail "sha256sum or shasum is required"
fi
}
version=$(sed -n 's/^#define TNT_VERSION "\([^"]*\)".*/\1/p' include/common.h)
[ -n "$version" ] || fail "could not read TNT_VERSION from include/common.h"
source_tarball=${SOURCE_TARBALL:-${RELEASE_SOURCE_TARBALL:-}}
[ -n "$source_tarball" ] ||
fail "set SOURCE_TARBALL to the final GitHub source archive"
[ -f "$source_tarball" ] ||
fail "SOURCE_TARBALL does not exist: $source_tarball"
! grep -R "REPLACE_WITH_EMAIL" packaging/arch packaging/debian >/dev/null ||
fail "replace maintainer email placeholders before package publishing"
arch_sha=$(sed -n "s/^[[:space:]]*sha256sums=('\([^']*\)'.*/\1/p" \
packaging/arch/PKGBUILD | head -n 1)
srcinfo_sha=$(sed -n 's/^[[:space:]]*sha256sums = \([^[:space:]]*\).*/\1/p' \
packaging/arch/.SRCINFO | head -n 1)
brew_sha=$(sed -n 's/^[[:space:]]*sha256 "\([^"]*\)".*/\1/p' \
packaging/homebrew/tnt-chat.rb | head -n 1)
[ -n "$arch_sha" ] || fail "could not read PKGBUILD source checksum"
[ -n "$srcinfo_sha" ] || fail "could not read .SRCINFO source checksum"
[ -n "$brew_sha" ] || fail "could not read Homebrew source checksum"
[ "$arch_sha" != "SKIP" ] || fail "replace PKGBUILD sha256sums before publishing"
[ "$srcinfo_sha" != "SKIP" ] || fail "replace .SRCINFO sha256sums before publishing"
[ "$brew_sha" != "REPLACE_WITH_RELEASE_TARBALL_SHA256" ] ||
fail "replace Homebrew sha256 before publishing"
expected_sha=$(sha256_of "$source_tarball")
[ "$arch_sha" = "$expected_sha" ] ||
fail "PKGBUILD source checksum does not match SOURCE_TARBALL"
[ "$srcinfo_sha" = "$expected_sha" ] ||
fail ".SRCINFO source checksum does not match SOURCE_TARBALL"
[ "$brew_sha" = "$expected_sha" ] ||
fail "Homebrew source checksum does not match SOURCE_TARBALL"
grep -q "^pkgver=$version$" packaging/arch/PKGBUILD ||
fail "PKGBUILD pkgver does not match $version"
grep -q "pkgver = $version" packaging/arch/.SRCINFO ||
fail ".SRCINFO pkgver does not match $version"
grep -q "v${version}.tar.gz" packaging/homebrew/tnt-chat.rb ||
fail "Homebrew URL does not match v$version"
grep -q "^tnt-chat (${version}-1)" packaging/debian/debian/changelog ||
fail "Debian changelog version does not match $version"
echo "package recipes match SOURCE_TARBALL for $version: $expected_sha"

View file

@ -26,8 +26,9 @@ Environment:
PORT=12720 base port for integration tests
Strict checks additionally require a clean tree, a vX.Y.Z tag at HEAD, a
matching changelog release section, real package checksums, and non-placeholder
maintainer metadata, then build from the tagged source archive.
matching changelog release section, non-placeholder maintainer metadata, and a
build from the tagged source archive. Run `make package-publish-check` after
the final GitHub source archive exists to verify package checksums.
USAGE
}
@ -190,6 +191,14 @@ grep -q '^invalid_records 1$' "$recover_report" ||
step "checking installer syntax"
sh -n install.sh
sh -n scripts/check_release_ref.sh
sh -n scripts/package_publish_check.sh
scripts/check_release_ref.sh "v$version"
bad_ref=v0.0.0
[ "$version" != "0.0.0" ] || bad_ref=v9.9.9
if scripts/check_release_ref.sh "$bad_ref" >/dev/null 2>&1; then
fail "release ref check accepted a mismatched tag"
fi
step "checking Debian packaging metadata"
[ -x packaging/debian/debian/rules ] ||
@ -248,12 +257,6 @@ if [ "$STRICT" -eq 1 ]; then
fail "local tag v$version does not point at HEAD"
grep -q "^## $version " docs/CHANGELOG.md ||
fail "docs/CHANGELOG.md does not contain a release section for $version"
! grep -q "sha256sums=('SKIP')" packaging/arch/PKGBUILD ||
fail "replace PKGBUILD sha256sums before strict release"
! grep -q "sha256sums = SKIP" packaging/arch/.SRCINFO ||
fail "replace .SRCINFO sha256sums before strict release"
! grep -q "REPLACE_WITH_RELEASE_TARBALL_SHA256" packaging/homebrew/tnt-chat.rb ||
fail "replace Homebrew sha256 before strict release"
! grep -R "REPLACE_WITH_EMAIL" packaging/arch packaging/debian >/dev/null ||
fail "replace maintainer email placeholders before strict release"