mirror of
https://oauth2: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@github.com/m1ngsama/TNT.git
synced 2026-06-26 04:34:38 +08:00
Add multiplatform CI governance gates
This commit is contained in:
parent
d4b260c160
commit
b6f92968d0
18 changed files with 775 additions and 184 deletions
98
.github/workflows/ci.yml
vendored
98
.github/workflows/ci.yml
vendored
|
|
@ -2,22 +2,34 @@ name: CI
|
|||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
branches: [ main, 'release/**' ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
branches: [ main, 'release/**' ]
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '17 3 * * *'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build-and-test:
|
||||
pr-gate:
|
||||
name: PR gate (${{ matrix.os }})
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
os: [ubuntu-24.04, macos-latest]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Install dependencies (Ubuntu)
|
||||
if: runner.os == 'Linux'
|
||||
|
|
@ -42,11 +54,26 @@ jobs:
|
|||
- name: Run release preflight
|
||||
run: make release-check
|
||||
|
||||
extended-linux-runtime:
|
||||
name: Extended Linux runtime
|
||||
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || github.event_name == 'push'
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y expect libssh-dev valgrind
|
||||
|
||||
- name: Run extended release preflight
|
||||
run: |
|
||||
RUN_INTEGRATION=1 RUN_SOAK=1 RUN_SLOW_CLIENT=1 make release-check
|
||||
|
||||
- name: Check for memory leaks
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
set -eu
|
||||
sudo apt-get install -y valgrind
|
||||
STATE_DIR=$(mktemp -d)
|
||||
SERVER_LOG="$STATE_DIR/server.log"
|
||||
VALGRIND_LOG="$STATE_DIR/valgrind.log"
|
||||
|
|
@ -101,3 +128,60 @@ jobs:
|
|||
cat "$VALGRIND_LOG"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
portable-container-builds:
|
||||
name: Portable build (${{ matrix.name }})
|
||||
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || github.event_name == 'push'
|
||||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- name: debian-stable-glibc
|
||||
image: debian:stable-slim
|
||||
setup: apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends build-essential libssh-dev ca-certificates
|
||||
- name: ubuntu-24.04-glibc
|
||||
image: ubuntu:24.04
|
||||
setup: apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends build-essential libssh-dev ca-certificates
|
||||
- name: alpine-musl
|
||||
image: alpine:3.20
|
||||
setup: apk add --no-cache build-base libssh-dev ca-certificates
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Build in container
|
||||
run: |
|
||||
docker run --rm -v "$PWD:/src:ro" "${{ matrix.image }}" sh -c '
|
||||
set -eu
|
||||
${{ matrix.setup }}
|
||||
mkdir /work
|
||||
cp -R /src/. /work/
|
||||
cd /work
|
||||
make clean
|
||||
make
|
||||
./tnt --version
|
||||
./tntctl --version
|
||||
'
|
||||
|
||||
package-recipe-gate:
|
||||
name: Package recipe gate
|
||||
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || github.event_name == 'push'
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Install packaging tools
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y ruby cpio
|
||||
|
||||
- name: Validate packaging metadata
|
||||
run: |
|
||||
for script in scripts/*.sh; do
|
||||
sh -n "$script"
|
||||
done
|
||||
bash -n packaging/arch/PKGBUILD
|
||||
ruby -c packaging/homebrew/tnt-chat.rb
|
||||
scripts/package_debian_source.sh "$RUNNER_TEMP/debian-source"
|
||||
|
|
|
|||
92
.github/workflows/release.yml
vendored
92
.github/workflows/release.yml
vendored
|
|
@ -8,11 +8,15 @@ on:
|
|||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build ${{ matrix.target }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-24.04
|
||||
|
|
@ -33,7 +37,7 @@ jobs:
|
|||
ctl_artifact: tntctl-darwin-arm64
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Verify release tag matches source version
|
||||
run: scripts/check_release_ref.sh "${GITHUB_REF_NAME}"
|
||||
|
|
@ -91,40 +95,91 @@ jobs:
|
|||
${{ matrix.artifact }}
|
||||
${{ matrix.ctl_artifact }}
|
||||
|
||||
source-archive:
|
||||
name: Source archive
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Verify release tag matches source version
|
||||
run: scripts/check_release_ref.sh "${GITHUB_REF_NAME}"
|
||||
|
||||
- name: Build source archive
|
||||
run: |
|
||||
set -eu
|
||||
version=$(sed -n 's/^#define TNT_VERSION "\([^"]*\)".*/\1/p' include/common.h)
|
||||
commit=$(git rev-list -n 1 "${GITHUB_REF_NAME}")
|
||||
mkdir -p dist
|
||||
git archive --format=tar.gz --prefix="TNT-${version}/" \
|
||||
-o "dist/tnt-chat-v${version}-source.tar.gz" "$commit"
|
||||
tar -tzf "dist/tnt-chat-v${version}-source.tar.gz" >/dev/null
|
||||
tar -tzf "dist/tnt-chat-v${version}-source.tar.gz" | grep -q "^TNT-${version}/LICENSE$"
|
||||
tar -tzf "dist/tnt-chat-v${version}-source.tar.gz" | grep -q "^TNT-${version}/packaging/README.md$"
|
||||
sha256sum "dist/tnt-chat-v${version}-source.tar.gz"
|
||||
|
||||
- name: Upload source archive
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: tnt-chat-source
|
||||
path: dist/tnt-chat-v*-source.tar.gz
|
||||
|
||||
artifact-gate:
|
||||
name: Release artifact gate
|
||||
needs: [build, source-archive]
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Verify release tag matches source version
|
||||
run: scripts/check_release_ref.sh "${GITHUB_REF_NAME}"
|
||||
|
||||
- name: Download build artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: ./artifacts
|
||||
|
||||
- name: Verify and package release assets
|
||||
run: scripts/package_release_assets.sh ./artifacts ./dist/release-assets
|
||||
|
||||
- name: Upload release asset bundle
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-assets
|
||||
path: dist/release-assets/*
|
||||
|
||||
release:
|
||||
needs: build
|
||||
needs: [artifact-gate, source-archive]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Verify release tag matches source version
|
||||
run: scripts/check_release_ref.sh "${GITHUB_REF_NAME}"
|
||||
|
||||
- name: Download all artifacts
|
||||
- name: Download release asset bundle
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: ./artifacts
|
||||
name: release-assets
|
||||
path: ./release-assets
|
||||
|
||||
- name: Create checksums
|
||||
- name: Verify release checksums
|
||||
run: |
|
||||
cd artifacts
|
||||
: > checksums.txt
|
||||
for artifact in */tnt-* */tntctl-*; do
|
||||
[ -f "$artifact" ] || continue
|
||||
sha256sum "$artifact" | sed "s# $artifact# $(basename "$artifact")#" >> checksums.txt
|
||||
done
|
||||
cd release-assets
|
||||
sha256sum -c checksums.txt
|
||||
cat checksums.txt
|
||||
|
||||
- name: Create Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: |
|
||||
artifacts/*/tnt-*
|
||||
artifacts/*/tntctl-*
|
||||
artifacts/checksums.txt
|
||||
release-assets/*
|
||||
body: |
|
||||
## Installation
|
||||
|
||||
|
|
@ -190,11 +245,16 @@ jobs:
|
|||
sha256sum -c checksums.txt --ignore-missing
|
||||
|
||||
# macOS
|
||||
for f in tnt-* tntctl-*; do
|
||||
for f in tnt-* tntctl-* tnt-chat-*-source.tar.gz; do
|
||||
grep " $f$" checksums.txt | shasum -a 256 -c -
|
||||
done
|
||||
```
|
||||
|
||||
The release also includes `tnt-chat-${{ github.ref_name }}-source.tar.gz`
|
||||
for package-manager recipes. Verify it with the same `checksums.txt`
|
||||
before updating Arch, Homebrew, Debian, Ubuntu, or container package
|
||||
metadata.
|
||||
|
||||
## What's Changed
|
||||
See [docs/CHANGELOG.md](https://github.com/${{ github.repository }}/blob/${{ github.ref_name }}/docs/CHANGELOG.md)
|
||||
draft: true
|
||||
|
|
|
|||
1
Makefile
1
Makefile
|
|
@ -134,6 +134,7 @@ script-test: all
|
|||
@cd tests && ./test_docs_help_surface.sh
|
||||
@cd tests && ./test_logrotate.sh
|
||||
@cd tests && ./test_message_log_tool.sh
|
||||
@cd tests && ./test_release_artifact_gate.sh
|
||||
|
||||
integration-test: all
|
||||
@echo "Running integration tests..."
|
||||
|
|
|
|||
|
|
@ -398,14 +398,14 @@ make release-check
|
|||
Longer local preflight can opt into runtime soak and slow-client coverage:
|
||||
|
||||
```sh
|
||||
RUN_SOAK=1 RUN_SLOW_CLIENT=1 make release-check
|
||||
RUN_INTEGRATION=1 RUN_SOAK=1 RUN_SLOW_CLIENT=1 make release-check
|
||||
```
|
||||
|
||||
Before publishing package recipes, download the final GitHub source archive,
|
||||
Before publishing package recipes, download the explicit release source archive,
|
||||
replace placeholder checksums, and run:
|
||||
|
||||
```sh
|
||||
SOURCE_TARBALL=dist/tnt-chat-vX.Y.Z.tar.gz make package-publish-check
|
||||
SOURCE_TARBALL=dist/tnt-chat-vX.Y.Z-source.tar.gz make package-publish-check
|
||||
```
|
||||
|
||||
## Files
|
||||
|
|
|
|||
|
|
@ -6,7 +6,12 @@
|
|||
- 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.
|
||||
checksums against the explicit release source archive after a tag exists.
|
||||
- Added a release artifact gate that bundles Linux/macOS binaries, the explicit
|
||||
release source archive, and `checksums.txt` before opening the draft release.
|
||||
- Added CI governance layers for fast PR checks, release-branch validation,
|
||||
extended runtime validation, container portability builds, and package recipe
|
||||
validation.
|
||||
- Added a `config_defaults` module and unit coverage for runtime default
|
||||
values, env keys, and accepted numeric ranges.
|
||||
- Added a dedicated `tntctl_text` module with unit coverage for local
|
||||
|
|
@ -53,7 +58,7 @@
|
|||
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.
|
||||
the explicit release 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
|
||||
|
|
|
|||
367
docs/CICD.md
367
docs/CICD.md
|
|
@ -1,171 +1,268 @@
|
|||
CI / RELEASE GUIDE
|
||||
==================
|
||||
# CI/CD and Release Governance
|
||||
|
||||
AUTOMATIC TESTING
|
||||
-----------------
|
||||
Every push or PR automatically runs:
|
||||
- Build on Ubuntu
|
||||
- AddressSanitizer build
|
||||
- `make ci-test` (strict integration, anonymous access, connection limits,
|
||||
and security feature checks)
|
||||
- Release/package preflight (`make release-check`)
|
||||
TNT is a C SSH terminal chat server. The CI/CD system is designed for a public
|
||||
open-source project: fast feedback on pull requests, broader scheduled
|
||||
validation across target environments, reproducible release artifacts, and a
|
||||
manual production deployment boundary.
|
||||
|
||||
Check status:
|
||||
https://github.com/m1ngsama/TNT/actions
|
||||
Production deployment is intentionally manual. Workflows must not SSH into
|
||||
production, restart services, upload to OSS buckets, publish package-manager
|
||||
recipes, or mutate running servers.
|
||||
|
||||
Production deployment is intentionally manual. The CI workflow must not SSH
|
||||
into production or restart services on push.
|
||||
## Pipeline Layers
|
||||
|
||||
### PR Fast Gate
|
||||
|
||||
CREATING RELEASES
|
||||
-----------------
|
||||
Release policy:
|
||||
- Use SemVer-style tags: vMAJOR.MINOR.PATCH.
|
||||
- Bump PATCH for compatible bug fixes and release hardening.
|
||||
- Bump MINOR for new commands, new documented flags, JSON field additions,
|
||||
or visible user-interface behavior changes.
|
||||
- Bump MAJOR for incompatible command, config, storage, or package behavior.
|
||||
- Keep GitHub draft release review manual. Do not auto-publish releases.
|
||||
- Keep production deployment manual. Do not SSH into production from CI.
|
||||
Workflow: `.github/workflows/ci.yml`
|
||||
|
||||
1. Update version metadata:
|
||||
- include/common.h
|
||||
- tnt.1
|
||||
- docs/CHANGELOG.md
|
||||
- packaging/arch/PKGBUILD
|
||||
- packaging/homebrew/tnt-chat.rb
|
||||
- packaging/debian/debian/changelog
|
||||
- maintainer metadata, when preparing public package recipes
|
||||
Runs on pull requests targeting `main` or `release/**`, and pushes to `main`
|
||||
or `release/**`:
|
||||
|
||||
2. Run the local preflight:
|
||||
make release-check
|
||||
- Ubuntu 24.04 and macOS latest builds.
|
||||
- Normal build with `make`.
|
||||
- AddressSanitizer build with `make asan`.
|
||||
- Integration/security gate with `make ci-test`.
|
||||
- Local release/package preflight with `make release-check`.
|
||||
|
||||
For a longer local runtime gate before publishing or production rollout:
|
||||
RUN_SOAK=1 RUN_SLOW_CLIENT=1 make release-check
|
||||
Purpose:
|
||||
|
||||
3. Commit the release changes and create a local tag. Do not push the tag
|
||||
until strict checks pass:
|
||||
git tag vX.Y.Z
|
||||
- Keep contributor feedback fast enough for normal review.
|
||||
- Catch build, integration, packaging metadata, and release-preflight regressions
|
||||
before merge.
|
||||
- Avoid slow soak, valgrind, and container matrix jobs on every PR.
|
||||
|
||||
4. Run strict release checks:
|
||||
make release-check-strict
|
||||
### Extended and Nightly Validation
|
||||
|
||||
Strict mode requires the local `vX.Y.Z` tag to point at HEAD. It also
|
||||
builds from the tagged source archive, so it catches files that were left
|
||||
untracked and would be missing from GitHub's source archive.
|
||||
Workflow: `.github/workflows/ci.yml`
|
||||
|
||||
5. Push the tag:
|
||||
git push origin vX.Y.Z
|
||||
Runs on `main` or `release/**` pushes, manual dispatch, and the nightly
|
||||
schedule:
|
||||
|
||||
6. GitHub Actions automatically:
|
||||
- Builds `tnt` and `tntctl` binaries (Linux/macOS, AMD64/ARM64)
|
||||
- Creates a draft release
|
||||
- Uploads binaries
|
||||
- Generates one `checksums.txt` file
|
||||
- Verifies that artifact architecture matches the asset name
|
||||
- `extended-linux-runtime`
|
||||
- Runs `RUN_INTEGRATION=1 RUN_SOAK=1 RUN_SLOW_CLIENT=1 make release-check`.
|
||||
- Runs a valgrind smoke test against a temporary server.
|
||||
- `portable-container-builds`
|
||||
- Builds in Debian stable glibc.
|
||||
- Builds in Ubuntu 24.04 glibc.
|
||||
- Builds in Alpine musl.
|
||||
- `package-recipe-gate`
|
||||
- Syntax-checks shell scripts.
|
||||
- Syntax-checks the Arch `PKGBUILD`.
|
||||
- Syntax-checks the Homebrew formula.
|
||||
- Assembles the Debian source tree.
|
||||
|
||||
7. Review the draft release, smoke-test downloaded assets, then publish it
|
||||
manually from GitHub.
|
||||
Purpose:
|
||||
|
||||
8. Release appears at:
|
||||
https://github.com/m1ngsama/TNT/releases
|
||||
- Broaden platform confidence without making every PR wait for the full matrix.
|
||||
- Detect musl/glibc portability issues early.
|
||||
- Keep package metadata reviewable before public registry submission.
|
||||
|
||||
### Release Artifact Gates
|
||||
|
||||
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 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
|
||||
ssh -p 2222 server stats --json
|
||||
ssh -p 2222 server users --json
|
||||
ssh -p 2222 operator@server post "release smoke"
|
||||
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 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
|
||||
Workflow: `.github/workflows/release.yml`
|
||||
|
||||
Runs only for SemVer tags matching `vMAJOR.MINOR.PATCH`:
|
||||
|
||||
ROLLBACK
|
||||
--------
|
||||
Production rollback stays manual:
|
||||
1. Keep the previous binary before replacing it.
|
||||
2. Stop or restart only the intended `tnt` service.
|
||||
3. Restore the previous binary if smoke checks fail.
|
||||
4. Re-run `health`, `stats --json`, and one post/tail smoke test.
|
||||
- Verifies the tag matches `TNT_VERSION` through `scripts/check_release_ref.sh`.
|
||||
- Builds Linux glibc AMD64 and ARM64 binaries.
|
||||
- Builds macOS Intel and Apple Silicon binaries.
|
||||
- Verifies binary architecture labels.
|
||||
- Builds an explicit source archive: `tnt-chat-vX.Y.Z-source.tar.gz`.
|
||||
- Runs `scripts/package_release_assets.sh` to collect release assets, verify
|
||||
expected asset names, verify binary architecture labels again after artifact
|
||||
download, verify source archive contents, generate `checksums.txt`, and verify
|
||||
the checksum file.
|
||||
- Creates a GitHub draft release only. Publishing stays manual.
|
||||
|
||||
Do not overwrite `TNT_STATE_DIR` during rollback. If a future release changes
|
||||
the message log format, its release notes must include the downgrade behavior.
|
||||
The release workflow does not publish package-manager recipes or deploy
|
||||
production servers.
|
||||
|
||||
## Platform Policy
|
||||
|
||||
DEPLOYING TO SERVERS
|
||||
--------------------
|
||||
Deployments are operator-driven:
|
||||
1. Build and test locally or in a temporary server directory.
|
||||
2. Back up the installed binary.
|
||||
3. Install the new binary.
|
||||
4. Restart the service.
|
||||
5. Run black-box checks (`health`, `stats --json`, `users --json`,
|
||||
and a post/tail smoke test).
|
||||
Current release assets:
|
||||
|
||||
The installer can still be used manually on a server:
|
||||
curl -sSL https://raw.githubusercontent.com/m1ngsama/TNT/main/install.sh | sh
|
||||
- Linux glibc AMD64: `tnt-linux-amd64`, `tntctl-linux-amd64`
|
||||
- Linux glibc ARM64: `tnt-linux-arm64`, `tntctl-linux-arm64`
|
||||
- macOS Intel: `tnt-darwin-amd64`, `tntctl-darwin-amd64`
|
||||
- macOS Apple Silicon: `tnt-darwin-arm64`, `tntctl-darwin-arm64`
|
||||
- Source archive: `tnt-chat-vX.Y.Z-source.tar.gz`
|
||||
|
||||
Current CI validation:
|
||||
|
||||
PRODUCTION SETUP (systemd)
|
||||
---------------------------
|
||||
1. Install binary (see above)
|
||||
- Ubuntu 24.04
|
||||
- macOS latest
|
||||
- Debian stable glibc container build
|
||||
- Ubuntu 24.04 glibc container build
|
||||
- Alpine musl container build
|
||||
|
||||
2. Setup service:
|
||||
sudo useradd -r -s /bin/false tnt
|
||||
sudo mkdir -p /var/lib/tnt
|
||||
sudo chown tnt:tnt /var/lib/tnt
|
||||
sudo cp tnt.service /etc/systemd/system/
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now tnt
|
||||
Package-manager routes:
|
||||
|
||||
3. Check status:
|
||||
sudo systemctl status tnt
|
||||
sudo journalctl -u tnt -f
|
||||
- Debian/Ubuntu: maintain draft Debian metadata and start with a Launchpad PPA.
|
||||
- Arch/AUR: maintain `packaging/arch/PKGBUILD` and `.SRCINFO`; submit manually.
|
||||
- Homebrew/macOS: maintain a tap formula first; Homebrew core can wait for a
|
||||
stable release cadence and broader adoption.
|
||||
- Source archive: every public package recipe must pin the final GitHub release
|
||||
source archive checksum.
|
||||
- Containers: first stage is Docker-based build validation in CI. Publishing
|
||||
images should wait until image labels, SBOM, provenance, CVE scanning, and
|
||||
registry ownership are defined.
|
||||
|
||||
## Release Policy
|
||||
|
||||
UPDATING SERVERS
|
||||
----------------
|
||||
Manual binary replacement pattern:
|
||||
backup=/usr/local/bin/tnt.bak-$(date +%Y%m%d%H%M%S)
|
||||
sudo cp -a /usr/local/bin/tnt "$backup"
|
||||
sudo install -m 755 ./tnt /usr/local/bin/tnt
|
||||
sudo systemctl restart tnt
|
||||
- Use SemVer-style tags: `vMAJOR.MINOR.PATCH`.
|
||||
- Bump PATCH for compatible bug fixes and release hardening.
|
||||
- Bump MINOR for new commands, new documented flags, JSON field additions, or
|
||||
visible user-interface behavior changes.
|
||||
- Bump MAJOR for incompatible command, config, storage, or package behavior.
|
||||
- Keep GitHub release publishing manual by using draft releases.
|
||||
- Keep production deployment manual.
|
||||
|
||||
Update version metadata before tagging:
|
||||
|
||||
PLATFORMS SUPPORTED
|
||||
-------------------
|
||||
✓ Linux AMD64 (x86_64)
|
||||
✓ Linux ARM64 (aarch64)
|
||||
✓ macOS Intel (x86_64)
|
||||
✓ macOS Apple Silicon (arm64)
|
||||
- `include/common.h`
|
||||
- `tnt.1`
|
||||
- `tntctl.1`
|
||||
- `docs/CHANGELOG.md`
|
||||
- `packaging/arch/PKGBUILD`
|
||||
- `packaging/arch/.SRCINFO`
|
||||
- `packaging/homebrew/tnt-chat.rb`
|
||||
- `packaging/debian/debian/changelog`
|
||||
|
||||
Local preflight:
|
||||
|
||||
EXAMPLE WORKFLOW
|
||||
----------------
|
||||
# Local development
|
||||
make && make asan && make release-check
|
||||
./tnt
|
||||
```sh
|
||||
make release-check
|
||||
```
|
||||
|
||||
# Create release
|
||||
Longer local runtime gate:
|
||||
|
||||
```sh
|
||||
RUN_INTEGRATION=1 RUN_SOAK=1 RUN_SLOW_CLIENT=1 make release-check
|
||||
```
|
||||
|
||||
Strict local release gate before pushing a tag:
|
||||
|
||||
```sh
|
||||
git tag vX.Y.Z
|
||||
git push origin vX.Y.Z
|
||||
# Wait 5 minutes for builds
|
||||
make release-check-strict
|
||||
```
|
||||
|
||||
# Deploy to production manually after validation
|
||||
ssh server "sudo install -m 755 /tmp/tnt-build/tnt /usr/local/bin/tnt"
|
||||
ssh server "sudo systemctl restart tnt"
|
||||
ssh -p 2222 server health
|
||||
Strict mode requires the local `vX.Y.Z` tag to point at `HEAD` and builds from
|
||||
the tagged source archive, so it catches files that were left untracked and
|
||||
would be missing from the release source archive.
|
||||
|
||||
After strict checks pass:
|
||||
|
||||
```sh
|
||||
git push origin vX.Y.Z
|
||||
```
|
||||
|
||||
GitHub Actions then builds artifacts and opens a draft release. Review and
|
||||
publish that draft manually.
|
||||
|
||||
## Release Review Checklist
|
||||
|
||||
Before publishing a draft release:
|
||||
|
||||
- Confirm the Git tag points at the intended commit.
|
||||
- Confirm the release workflow passed.
|
||||
- Download every release asset from GitHub, not from the local workspace.
|
||||
- Verify downloaded assets against `checksums.txt`.
|
||||
- Run downloaded `tnt --version` and `tntctl --version`.
|
||||
- Start a temporary server and check:
|
||||
|
||||
```sh
|
||||
ssh -p 2222 server health
|
||||
ssh -p 2222 server stats --json
|
||||
ssh -p 2222 server users --json
|
||||
ssh -p 2222 operator@server post "release smoke"
|
||||
ssh -p 2222 server "tail -n 1"
|
||||
```
|
||||
|
||||
- Check runtime dynamic links with `ldd` on Linux or `otool -L` on macOS.
|
||||
- Confirm `libssh` runtime installation is documented for the target install
|
||||
path.
|
||||
- Verify the explicit source archive checksum before updating Arch, Homebrew,
|
||||
Debian, Ubuntu, or container package metadata.
|
||||
- Run package publication preflight after package recipes pin final source
|
||||
checksums:
|
||||
|
||||
```sh
|
||||
SOURCE_TARBALL=dist/tnt-chat-vX.Y.Z-source.tar.gz make package-publish-check
|
||||
```
|
||||
|
||||
## Checksums
|
||||
|
||||
Release assets include `checksums.txt`.
|
||||
|
||||
Linux:
|
||||
|
||||
```sh
|
||||
sha256sum -c checksums.txt --ignore-missing
|
||||
```
|
||||
|
||||
macOS:
|
||||
|
||||
```sh
|
||||
for f in tnt-* tntctl-* tnt-chat-*-source.tar.gz; do
|
||||
grep " $f$" checksums.txt | shasum -a 256 -c -
|
||||
done
|
||||
```
|
||||
|
||||
## Supply Chain Roadmap
|
||||
|
||||
Stage 1, implemented now:
|
||||
|
||||
- Tag/version gate.
|
||||
- Draft release, manual publish.
|
||||
- Binary architecture validation.
|
||||
- Source archive validation.
|
||||
- SHA-256 checksums for every release asset.
|
||||
- Package recipe checksum preflight.
|
||||
|
||||
Stage 2, next:
|
||||
|
||||
- Generate an SBOM for release artifacts, preferably CycloneDX or SPDX.
|
||||
- Attach SBOM files to draft releases.
|
||||
- Add package lint jobs for Debian source packages, Arch packages, Homebrew
|
||||
audit, and container image metadata.
|
||||
|
||||
Stage 3, later:
|
||||
|
||||
- Sign release checksums and/or artifacts with a documented maintainer key or
|
||||
Sigstore flow.
|
||||
- Add SLSA provenance for GitHub Actions builds.
|
||||
- Define container image ownership, tag policy, vulnerability scan policy, and
|
||||
rollback behavior before publishing images.
|
||||
|
||||
## Manual Production Deployment
|
||||
|
||||
Deployment remains operator-driven:
|
||||
|
||||
1. Build and test locally or in a temporary server directory.
|
||||
2. Back up the installed binary.
|
||||
3. Install the new binary.
|
||||
4. Restart only the intended `tnt` service.
|
||||
5. Run black-box checks: `health`, `stats --json`, `users --json`, and one
|
||||
post/tail smoke test.
|
||||
|
||||
Manual binary replacement pattern:
|
||||
|
||||
```sh
|
||||
backup=/usr/local/bin/tnt.bak-$(date +%Y%m%d%H%M%S)
|
||||
sudo cp -a /usr/local/bin/tnt "$backup"
|
||||
sudo install -m 755 ./tnt /usr/local/bin/tnt
|
||||
sudo systemctl restart tnt
|
||||
```
|
||||
|
||||
## Rollback
|
||||
|
||||
Production rollback stays manual:
|
||||
|
||||
1. Keep the previous binary before replacing it.
|
||||
2. Stop or restart only the intended `tnt` service.
|
||||
3. Restore the previous binary if smoke checks fail.
|
||||
4. Re-run `health`, `stats --json`, and one post/tail smoke test.
|
||||
|
||||
Do not overwrite `TNT_STATE_DIR` during rollback. If a future release changes
|
||||
the message log format, its release notes must include downgrade behavior.
|
||||
|
|
|
|||
|
|
@ -113,8 +113,8 @@ 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 source-archive checksum placeholders only after the final
|
||||
GitHub source archive exists, then run `make package-publish-check`.
|
||||
1. Replace remaining source-archive checksum placeholders only after the
|
||||
explicit release 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
|
||||
|
|
|
|||
|
|
@ -13,12 +13,30 @@ any public registry.
|
|||
Package installs include both `tnt` and `tntctl`. `tnt` is the server process;
|
||||
`tntctl` is a thin wrapper around the documented SSH exec interface.
|
||||
|
||||
## CI governance
|
||||
|
||||
Package recipes are validated in stages:
|
||||
|
||||
- PR fast gate: `make release-check` verifies package metadata stays aligned
|
||||
with `TNT_VERSION`.
|
||||
- Extended CI: package syntax and Debian source-tree assembly run on `main` and
|
||||
`release/**` pushes, nightly, and manual workflow dispatch.
|
||||
- Release gate: the workflow builds an explicit release source archive, verifies
|
||||
it, and includes it in `checksums.txt`.
|
||||
- Publishing gate: after final source checksums are pinned, run
|
||||
`SOURCE_TARBALL=... make package-publish-check`.
|
||||
|
||||
All package-manager submissions remain manual. CI must not push to AUR, open or
|
||||
merge Homebrew tap updates, upload Debian/PPA packages, publish container
|
||||
images, or deploy production servers.
|
||||
|
||||
## Release checklist
|
||||
|
||||
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 `vX.Y.Z`.
|
||||
3. Build and upload release tarballs or rely on GitHub source archives.
|
||||
3. Let the release workflow build the explicit release source archive and draft
|
||||
release assets.
|
||||
4. Replace placeholder checksums in package drafts.
|
||||
5. Verify package contents in an isolated directory:
|
||||
|
||||
|
|
@ -35,11 +53,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, download the final GitHub source archive,
|
||||
7. Before submitting package recipes, download the explicit release source archive,
|
||||
replace checksum placeholders, and run:
|
||||
|
||||
```sh
|
||||
SOURCE_TARBALL=dist/tnt-chat-vX.Y.Z.tar.gz make package-publish-check
|
||||
SOURCE_TARBALL=dist/tnt-chat-vX.Y.Z-source.tar.gz make package-publish-check
|
||||
```
|
||||
|
||||
8. Submit packages manually:
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ pkgbase = tnt-chat
|
|||
makedepends = gcc
|
||||
makedepends = make
|
||||
depends = libssh
|
||||
source = tnt-chat-1.0.1.tar.gz::https://github.com/m1ngsama/TNT/archive/refs/tags/v1.0.1.tar.gz
|
||||
source = tnt-chat-v1.0.1-source.tar.gz::https://github.com/m1ngsama/TNT/releases/download/v1.0.1/tnt-chat-v1.0.1-source.tar.gz
|
||||
source = tnt-chat.sysusers
|
||||
sha256sums = SKIP
|
||||
sha256sums = 8a1f7dfbdc9f1305c4ed50d80e89f91333ffdf937890c497f93e41abaf76e3ed
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ url='https://github.com/m1ngsama/TNT'
|
|||
license=('MIT')
|
||||
depends=('libssh')
|
||||
makedepends=('gcc' 'make')
|
||||
source=("${pkgname}-${pkgver}.tar.gz::${url}/archive/refs/tags/v${pkgver}.tar.gz"
|
||||
source=("${pkgname}-v${pkgver}-source.tar.gz::${url}/releases/download/v${pkgver}/${pkgname}-v${pkgver}-source.tar.gz"
|
||||
"${pkgname}.sysusers")
|
||||
sha256sums=('SKIP'
|
||||
'8a1f7dfbdc9f1305c4ed50d80e89f91333ffdf937890c497f93e41abaf76e3ed')
|
||||
|
|
|
|||
|
|
@ -26,12 +26,12 @@ After editing `PKGBUILD`, regenerate `.SRCINFO`:
|
|||
makepkg --printsrcinfo > .SRCINFO
|
||||
```
|
||||
|
||||
Before AUR submission, replace `sha256sums=('SKIP')` with the real GitHub
|
||||
Before AUR submission, replace `sha256sums=('SKIP')` with the real release
|
||||
source archive checksum, regenerate `.SRCINFO`, then run the package publish
|
||||
check:
|
||||
|
||||
```sh
|
||||
SOURCE_TARBALL=dist/tnt-chat-vX.Y.Z.tar.gz make package-publish-check
|
||||
SOURCE_TARBALL=dist/tnt-chat-vX.Y.Z-source.tar.gz make package-publish-check
|
||||
```
|
||||
|
||||
## Manual AUR submission
|
||||
|
|
|
|||
|
|
@ -34,16 +34,16 @@ ruby -c packaging/homebrew/tnt-chat.rb
|
|||
2. Download or hash the release source archive:
|
||||
|
||||
```sh
|
||||
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
|
||||
curl -L -o dist/tnt-chat-vX.Y.Z-source.tar.gz \
|
||||
https://github.com/m1ngsama/TNT/releases/download/vX.Y.Z/tnt-chat-vX.Y.Z-source.tar.gz
|
||||
shasum -a 256 dist/tnt-chat-vX.Y.Z-source.tar.gz
|
||||
```
|
||||
|
||||
3. Replace `REPLACE_WITH_RELEASE_TARBALL_SHA256` in `tnt-chat.rb`.
|
||||
4. Run:
|
||||
|
||||
```sh
|
||||
SOURCE_TARBALL=dist/tnt-chat-vX.Y.Z.tar.gz make package-publish-check
|
||||
SOURCE_TARBALL=dist/tnt-chat-vX.Y.Z-source.tar.gz make package-publish-check
|
||||
```
|
||||
|
||||
5. Copy the formula into the tap repository and open a normal review PR.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
class TntChat < Formula
|
||||
desc "SSH-native terminal chat server with a Vim-style interface"
|
||||
homepage "https://github.com/m1ngsama/TNT"
|
||||
url "https://github.com/m1ngsama/TNT/archive/refs/tags/v1.0.1.tar.gz"
|
||||
url "https://github.com/m1ngsama/TNT/releases/download/v1.0.1/tnt-chat-v1.0.1-source.tar.gz"
|
||||
sha256 "REPLACE_WITH_RELEASE_TARBALL_SHA256"
|
||||
license "MIT"
|
||||
|
||||
|
|
|
|||
|
|
@ -23,12 +23,21 @@ sha256_of() {
|
|||
|
||||
version=$(sed -n 's/^#define TNT_VERSION "\([^"]*\)".*/\1/p' include/common.h)
|
||||
[ -n "$version" ] || fail "could not read TNT_VERSION from include/common.h"
|
||||
release_source="tnt-chat-v${version}-source.tar.gz"
|
||||
|
||||
source_tarball=${SOURCE_TARBALL:-${RELEASE_SOURCE_TARBALL:-}}
|
||||
[ -n "$source_tarball" ] ||
|
||||
fail "set SOURCE_TARBALL to the final GitHub source archive"
|
||||
fail "set SOURCE_TARBALL to the explicit release source archive"
|
||||
[ -f "$source_tarball" ] ||
|
||||
fail "SOURCE_TARBALL does not exist: $source_tarball"
|
||||
tar -tzf "$source_tarball" >/dev/null ||
|
||||
fail "SOURCE_TARBALL is not a readable tar.gz archive"
|
||||
tar -tzf "$source_tarball" | grep -q "^TNT-$version/LICENSE$" ||
|
||||
fail "SOURCE_TARBALL is missing LICENSE"
|
||||
tar -tzf "$source_tarball" | grep -q "^TNT-$version/packaging/README.md$" ||
|
||||
fail "SOURCE_TARBALL is missing packaging/README.md"
|
||||
tar -tzf "$source_tarball" | grep -q "^TNT-$version/src/tntctl.c$" ||
|
||||
fail "SOURCE_TARBALL is missing src/tntctl.c"
|
||||
|
||||
! grep -R "REPLACE_WITH_EMAIL" packaging/arch packaging/debian >/dev/null ||
|
||||
fail "replace maintainer email placeholders before package publishing"
|
||||
|
|
@ -60,8 +69,12 @@ 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 '${pkgname}-v${pkgver}-source.tar.gz' packaging/arch/PKGBUILD ||
|
||||
fail "PKGBUILD source must use the release source archive"
|
||||
grep -q "$release_source" packaging/arch/.SRCINFO ||
|
||||
fail ".SRCINFO source does not match $release_source"
|
||||
grep -q "$release_source" packaging/homebrew/tnt-chat.rb ||
|
||||
fail "Homebrew URL does not match $release_source"
|
||||
grep -q "^tnt-chat (${version}-1)" packaging/debian/debian/changelog ||
|
||||
fail "Debian changelog version does not match $version"
|
||||
|
||||
|
|
|
|||
150
scripts/package_release_assets.sh
Executable file
150
scripts/package_release_assets.sh
Executable file
|
|
@ -0,0 +1,150 @@
|
|||
#!/bin/sh
|
||||
# Collect release workflow artifacts into one flat, checksum-verified bundle.
|
||||
|
||||
set -eu
|
||||
|
||||
usage() {
|
||||
cat <<'USAGE'
|
||||
Usage: scripts/package_release_assets.sh ARTIFACT_ROOT [OUT_DIR]
|
||||
|
||||
ARTIFACT_ROOT is the directory produced by actions/download-artifact.
|
||||
OUT_DIR defaults to dist/release-assets.
|
||||
|
||||
The script validates the expected release asset names, verifies binary
|
||||
architecture labels, verifies the source archive shape, writes checksums.txt,
|
||||
and verifies that checksums.txt matches the assembled bundle. It never
|
||||
publishes a release.
|
||||
USAGE
|
||||
}
|
||||
|
||||
fail() {
|
||||
echo "release-artifact-gate: $*" >&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
|
||||
}
|
||||
|
||||
find_unique() {
|
||||
name=$1
|
||||
matches=$(find "$ARTIFACT_ROOT" -type f -name "$name" -print)
|
||||
count=$(printf '%s\n' "$matches" | sed '/^$/d' | wc -l | tr -d ' ')
|
||||
[ "$count" = "1" ] ||
|
||||
fail "expected exactly one artifact named $name, found $count"
|
||||
printf '%s\n' "$matches"
|
||||
}
|
||||
|
||||
require_file_label() {
|
||||
path=$1
|
||||
pattern=$2
|
||||
label=$(file "$path")
|
||||
printf '%s\n' "$label" | grep -E "$pattern" >/dev/null ||
|
||||
fail "unexpected file type for $(basename "$path"): $label"
|
||||
}
|
||||
|
||||
verify_asset() {
|
||||
name=$1
|
||||
path=$2
|
||||
|
||||
[ -s "$path" ] || fail "empty artifact: $name"
|
||||
|
||||
case "$name" in
|
||||
tnt-linux-amd64|tntctl-linux-amd64)
|
||||
require_file_label "$path" 'ELF 64-bit.*x86-64'
|
||||
;;
|
||||
tnt-linux-arm64|tntctl-linux-arm64)
|
||||
require_file_label "$path" 'ELF 64-bit.*(aarch64|ARM aarch64)'
|
||||
;;
|
||||
tnt-darwin-amd64|tntctl-darwin-amd64)
|
||||
require_file_label "$path" 'Mach-O 64-bit.*x86_64'
|
||||
;;
|
||||
tnt-darwin-arm64|tntctl-darwin-arm64)
|
||||
require_file_label "$path" 'Mach-O 64-bit.*arm64'
|
||||
;;
|
||||
tnt-chat-v*-source.tar.gz)
|
||||
tar -tzf "$path" >/dev/null
|
||||
tar -tzf "$path" | grep -q "^TNT-$VERSION/LICENSE$" ||
|
||||
fail "source archive is missing LICENSE"
|
||||
tar -tzf "$path" | grep -q "^TNT-$VERSION/src/tntctl.c$" ||
|
||||
fail "source archive is missing src/tntctl.c"
|
||||
tar -tzf "$path" | grep -q "^TNT-$VERSION/packaging/README.md$" ||
|
||||
fail "source archive is missing packaging/README.md"
|
||||
;;
|
||||
*)
|
||||
fail "unexpected release artifact: $name"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
[ "${1:-}" != "-h" ] && [ "${1:-}" != "--help" ] || {
|
||||
usage
|
||||
exit 0
|
||||
}
|
||||
|
||||
ARTIFACT_ROOT=${1:-}
|
||||
OUT_DIR=${2:-dist/release-assets}
|
||||
|
||||
[ -n "$ARTIFACT_ROOT" ] || {
|
||||
usage >&2
|
||||
exit 2
|
||||
}
|
||||
[ -d "$ARTIFACT_ROOT" ] || fail "ARTIFACT_ROOT does not exist: $ARTIFACT_ROOT"
|
||||
ARTIFACT_ROOT=$(CDPATH= cd -- "$ARTIFACT_ROOT" && pwd)
|
||||
|
||||
ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||
cd "$ROOT"
|
||||
|
||||
case "$OUT_DIR" in
|
||||
/*) ;;
|
||||
*) OUT_DIR="$ROOT/$OUT_DIR" ;;
|
||||
esac
|
||||
|
||||
VERSION=$(sed -n 's/^#define TNT_VERSION "\([^"]*\)".*/\1/p' include/common.h)
|
||||
[ -n "$VERSION" ] || fail "could not read TNT_VERSION"
|
||||
|
||||
SOURCE_ASSET="tnt-chat-v$VERSION-source.tar.gz"
|
||||
EXPECTED_ASSETS="
|
||||
tnt-linux-amd64
|
||||
tntctl-linux-amd64
|
||||
tnt-linux-arm64
|
||||
tntctl-linux-arm64
|
||||
tnt-darwin-amd64
|
||||
tntctl-darwin-amd64
|
||||
tnt-darwin-arm64
|
||||
tntctl-darwin-arm64
|
||||
$SOURCE_ASSET
|
||||
"
|
||||
|
||||
mkdir -p "$OUT_DIR"
|
||||
|
||||
for name in $EXPECTED_ASSETS; do
|
||||
dst="$OUT_DIR/$name"
|
||||
[ ! -e "$dst" ] || fail "output already exists: $dst"
|
||||
src=$(find_unique "$name")
|
||||
verify_asset "$name" "$src"
|
||||
cp "$src" "$dst"
|
||||
done
|
||||
|
||||
(
|
||||
cd "$OUT_DIR"
|
||||
: > checksums.txt
|
||||
for name in $EXPECTED_ASSETS; do
|
||||
printf '%s %s\n' "$(sha256_of "$name")" "$name" >> checksums.txt
|
||||
done
|
||||
|
||||
while read -r expected name; do
|
||||
[ -n "$expected" ] || continue
|
||||
actual=$(sha256_of "$name")
|
||||
[ "$actual" = "$expected" ] ||
|
||||
fail "checksum mismatch for $name"
|
||||
done < checksums.txt
|
||||
)
|
||||
|
||||
echo "release artifact bundle ready: $OUT_DIR"
|
||||
|
|
@ -28,7 +28,7 @@ Environment:
|
|||
Strict checks additionally require a clean tree, a vX.Y.Z tag at HEAD, a
|
||||
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.
|
||||
the explicit release source archive exists to verify package checksums.
|
||||
USAGE
|
||||
}
|
||||
|
||||
|
|
@ -78,8 +78,12 @@ grep -q "^pkgname=tnt-chat$" packaging/arch/PKGBUILD ||
|
|||
fail "packaging/arch/PKGBUILD pkgname is not tnt-chat"
|
||||
grep -q "^pkgname = tnt-chat$" packaging/arch/.SRCINFO ||
|
||||
fail "packaging/arch/.SRCINFO pkgname is not tnt-chat"
|
||||
grep -q "v${version}.tar.gz" packaging/homebrew/tnt-chat.rb ||
|
||||
fail "packaging/homebrew/tnt-chat.rb URL does not match v$version"
|
||||
grep -q '${pkgname}-v${pkgver}-source.tar.gz' packaging/arch/PKGBUILD ||
|
||||
fail "packaging/arch/PKGBUILD source must use the release source archive"
|
||||
grep -q "tnt-chat-v${version}-source.tar.gz" packaging/arch/.SRCINFO ||
|
||||
fail "packaging/arch/.SRCINFO source does not match v$version release archive"
|
||||
grep -q "tnt-chat-v${version}-source.tar.gz" packaging/homebrew/tnt-chat.rb ||
|
||||
fail "packaging/homebrew/tnt-chat.rb URL does not match v$version release archive"
|
||||
grep -q "^class TntChat < Formula$" packaging/homebrew/tnt-chat.rb ||
|
||||
fail "packaging/homebrew/tnt-chat.rb formula class is not TntChat"
|
||||
grep -q 'depends_on "libssh"' packaging/homebrew/tnt-chat.rb ||
|
||||
|
|
@ -193,6 +197,7 @@ step "checking installer syntax"
|
|||
sh -n install.sh
|
||||
sh -n scripts/check_release_ref.sh
|
||||
sh -n scripts/package_publish_check.sh
|
||||
sh -n scripts/package_release_assets.sh
|
||||
scripts/check_release_ref.sh "v$version"
|
||||
bad_ref=v0.0.0
|
||||
[ "$version" != "0.0.0" ] || bad_ref=v9.9.9
|
||||
|
|
|
|||
158
tests/test_release_artifact_gate.sh
Executable file
158
tests/test_release_artifact_gate.sh
Executable file
|
|
@ -0,0 +1,158 @@
|
|||
#!/bin/sh
|
||||
# Release-artifact gate regression tests.
|
||||
|
||||
set -u
|
||||
|
||||
PASS=0
|
||||
FAIL=0
|
||||
SCRIPT="../scripts/package_release_assets.sh"
|
||||
STATE_DIR=$(mktemp -d "${TMPDIR:-/tmp}/tnt-release-artifact-test.XXXXXX")
|
||||
|
||||
cleanup() {
|
||||
rm -rf "$STATE_DIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
pass() {
|
||||
echo "✓ $1"
|
||||
PASS=$((PASS + 1))
|
||||
}
|
||||
|
||||
fail() {
|
||||
echo "✗ $1"
|
||||
if [ "${2:-}" ]; then
|
||||
printf '%s\n' "$2"
|
||||
fi
|
||||
FAIL=$((FAIL + 1))
|
||||
}
|
||||
|
||||
version() {
|
||||
sed -n 's/^#define TNT_VERSION "\([^"]*\)".*/\1/p' ../include/common.h
|
||||
}
|
||||
|
||||
write_elf_x86_64() {
|
||||
printf '\177ELF\002\001\001\000\000\000\000\000\000\000\000\000\002\000\076\000\001\000\000\000' > "$1"
|
||||
}
|
||||
|
||||
write_elf_aarch64() {
|
||||
printf '\177ELF\002\001\001\000\000\000\000\000\000\000\000\000\002\000\267\000\001\000\000\000' > "$1"
|
||||
}
|
||||
|
||||
write_macho_x86_64() {
|
||||
printf '\317\372\355\376\007\000\000\001\003\000\000\000\002\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000' > "$1"
|
||||
}
|
||||
|
||||
write_macho_arm64() {
|
||||
printf '\317\372\355\376\014\000\000\001\000\000\000\000\002\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000' > "$1"
|
||||
}
|
||||
|
||||
build_artifact_tree() {
|
||||
artifact_root=$1
|
||||
include_source=$2
|
||||
ver=$(version)
|
||||
|
||||
mkdir -p \
|
||||
"$artifact_root/linux-amd64" \
|
||||
"$artifact_root/linux-arm64" \
|
||||
"$artifact_root/darwin-amd64" \
|
||||
"$artifact_root/darwin-arm64"
|
||||
|
||||
write_elf_x86_64 "$artifact_root/linux-amd64/tnt-linux-amd64"
|
||||
write_elf_x86_64 "$artifact_root/linux-amd64/tntctl-linux-amd64"
|
||||
write_elf_aarch64 "$artifact_root/linux-arm64/tnt-linux-arm64"
|
||||
write_elf_aarch64 "$artifact_root/linux-arm64/tntctl-linux-arm64"
|
||||
write_macho_x86_64 "$artifact_root/darwin-amd64/tnt-darwin-amd64"
|
||||
write_macho_x86_64 "$artifact_root/darwin-amd64/tntctl-darwin-amd64"
|
||||
write_macho_arm64 "$artifact_root/darwin-arm64/tnt-darwin-arm64"
|
||||
write_macho_arm64 "$artifact_root/darwin-arm64/tntctl-darwin-arm64"
|
||||
|
||||
if [ "$include_source" = "yes" ]; then
|
||||
source_root="$STATE_DIR/source-$ver/TNT-$ver"
|
||||
mkdir -p "$source_root/src" "$source_root/packaging" "$artifact_root/source"
|
||||
printf 'MIT\n' > "$source_root/LICENSE"
|
||||
printf 'int main(void) { return 0; }\n' > "$source_root/src/tntctl.c"
|
||||
printf '# Packaging\n' > "$source_root/packaging/README.md"
|
||||
(cd "$STATE_DIR/source-$ver" && tar -czf "$artifact_root/source/tnt-chat-v$ver-source.tar.gz" "TNT-$ver")
|
||||
fi
|
||||
}
|
||||
|
||||
expect_file() {
|
||||
path=$1
|
||||
name=$2
|
||||
[ -f "$path" ] && pass "$name" || fail "$name missing"
|
||||
}
|
||||
|
||||
echo "=== TNT Release Artifact Gate Tests ==="
|
||||
|
||||
if [ ! -x "$SCRIPT" ]; then
|
||||
echo "Error: script $SCRIPT not found or not executable."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VER=$(version)
|
||||
ARTIFACT_ROOT="$STATE_DIR/artifacts"
|
||||
OUT_DIR="$STATE_DIR/out"
|
||||
build_artifact_tree "$ARTIFACT_ROOT" yes
|
||||
|
||||
OUTPUT=$("$SCRIPT" "$ARTIFACT_ROOT" "$OUT_DIR" 2>&1)
|
||||
STATUS=$?
|
||||
if [ "$STATUS" -eq 0 ] &&
|
||||
printf '%s\n' "$OUTPUT" | grep -q 'release artifact bundle ready'; then
|
||||
pass "complete artifact set is accepted"
|
||||
else
|
||||
fail "complete artifact set failed" "$OUTPUT"
|
||||
fi
|
||||
|
||||
for asset in \
|
||||
tnt-linux-amd64 \
|
||||
tntctl-linux-amd64 \
|
||||
tnt-linux-arm64 \
|
||||
tntctl-linux-arm64 \
|
||||
tnt-darwin-amd64 \
|
||||
tntctl-darwin-amd64 \
|
||||
tnt-darwin-arm64 \
|
||||
tntctl-darwin-arm64 \
|
||||
"tnt-chat-v$VER-source.tar.gz" \
|
||||
checksums.txt
|
||||
do
|
||||
expect_file "$OUT_DIR/$asset" "bundles $asset"
|
||||
done
|
||||
|
||||
if grep -q " tnt-linux-amd64$" "$OUT_DIR/checksums.txt" &&
|
||||
grep -q " tnt-chat-v$VER-source.tar.gz$" "$OUT_DIR/checksums.txt"; then
|
||||
pass "checksums include binaries and source archive"
|
||||
else
|
||||
fail "checksums are incomplete" "$(cat "$OUT_DIR/checksums.txt" 2>/dev/null)"
|
||||
fi
|
||||
|
||||
DUP_ROOT="$STATE_DIR/duplicate"
|
||||
DUP_OUT="$STATE_DIR/duplicate-out"
|
||||
build_artifact_tree "$DUP_ROOT" yes
|
||||
mkdir -p "$DUP_ROOT/duplicate"
|
||||
cp "$DUP_ROOT/linux-amd64/tnt-linux-amd64" "$DUP_ROOT/duplicate/tnt-linux-amd64"
|
||||
DUP_OUTPUT=$("$SCRIPT" "$DUP_ROOT" "$DUP_OUT" 2>&1)
|
||||
DUP_STATUS=$?
|
||||
if [ "$DUP_STATUS" -ne 0 ] &&
|
||||
printf '%s\n' "$DUP_OUTPUT" | grep -q 'expected exactly one artifact named tnt-linux-amd64'; then
|
||||
pass "duplicate artifact is rejected"
|
||||
else
|
||||
fail "duplicate artifact handling" "$DUP_OUTPUT"
|
||||
fi
|
||||
|
||||
MISSING_ROOT="$STATE_DIR/missing"
|
||||
MISSING_OUT="$STATE_DIR/missing-out"
|
||||
build_artifact_tree "$MISSING_ROOT" no
|
||||
MISSING_OUTPUT=$("$SCRIPT" "$MISSING_ROOT" "$MISSING_OUT" 2>&1)
|
||||
MISSING_STATUS=$?
|
||||
if [ "$MISSING_STATUS" -ne 0 ] &&
|
||||
printf '%s\n' "$MISSING_OUTPUT" | grep -q "expected exactly one artifact named tnt-chat-v$VER-source.tar.gz"; then
|
||||
pass "missing source archive is rejected"
|
||||
else
|
||||
fail "missing source archive handling" "$MISSING_OUTPUT"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "PASSED: $PASS"
|
||||
echo "FAILED: $FAIL"
|
||||
[ "$FAIL" -eq 0 ] && echo "All tests passed" || echo "Some tests failed"
|
||||
exit "$FAIL"
|
||||
|
|
@ -106,7 +106,7 @@ else
|
|||
fi
|
||||
|
||||
IDLE_READY="$STATE_DIR/idle.ready"
|
||||
IDLE_HOLD=$((DURATION + 2))
|
||||
IDLE_HOLD=$((DURATION + 20))
|
||||
cat >"$STATE_DIR/idle.expect" <<EOF
|
||||
set timeout [expr {$IDLE_HOLD + 20}]
|
||||
spawn ssh $SSH_TTY_OPTS idle@127.0.0.1
|
||||
|
|
|
|||
Loading…
Reference in a new issue