From 8da7f10c6b8cdd2d03506743bfc3215e082a358b Mon Sep 17 00:00:00 2001 From: mirivlad Date: Mon, 13 Jul 2026 05:01:57 +0800 Subject: [PATCH] build: package and publish sync server releases --- .gitignore | 1 + README.md | 23 +++++++ scripts/publish-github-release.sh | 74 +++++++++++++++++++++ scripts/release.sh | 33 ++++++++++ scripts/test-publish-github-release.sh | 91 ++++++++++++++++++++++++++ 5 files changed, 222 insertions(+) create mode 100755 scripts/publish-github-release.sh create mode 100755 scripts/release.sh create mode 100755 scripts/test-publish-github-release.sh diff --git a/.gitignore b/.gitignore index fd72b14..ab7b4e9 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ server-data/ # OS .DS_Store Thumbs.db +release/ diff --git a/README.md b/README.md index 376fa4d..e9ac40d 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,29 @@ This server provides synchronization between devices running Verstak2. It handle ./build/bin/verstak-sync-server --admin-user admin --admin-pass secret ``` +## Release packages + +Build a Linux amd64 archive locally: + +```bash +./scripts/release.sh v0.1.0-alpha.1 +``` + +It runs the server build and tests, then writes +`release/verstak-sync-server-linux-amd64-.tar.gz` and `SHA256SUMS`. +The archive contains the server binary, systemd service file and install +script. + +Publish those same assets to GitHub Releases: + +```bash +./scripts/publish-github-release.sh v0.1.0-alpha.1 +``` + +The publisher requires an authenticated [`gh`](https://cli.github.com/) CLI +and a clean local `main` equal to `origin/main`. It creates and pushes an +annotated tag when necessary, then creates or updates the GitHub Release. + ## Configuration | Flag | Default | Description | diff --git a/scripts/publish-github-release.sh b/scripts/publish-github-release.sh new file mode 100755 index 0000000..e7aaf1e --- /dev/null +++ b/scripts/publish-github-release.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +VERSION="${1:-}" +REPOSITORY="mirivlad/verstak-sync-server" +RELEASE_SCRIPT="${VERSTAK_RELEASE_SCRIPT:-$ROOT/scripts/release.sh}" +RELEASE_DIR="${VERSTAK_RELEASE_DIR:-$ROOT/release}" +GIT_BIN="${GIT_BIN:-git}" +GH_BIN="${GH_BIN:-gh}" + +cd "$ROOT" + +if [[ -z "$VERSION" || ! "$VERSION" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]]; then + echo "usage: $0 " >&2 + echo "example: $0 v0.1.0-alpha.1" >&2 + exit 2 +fi +if ! command -v "$GH_BIN" >/dev/null; then + echo "gh CLI is required to publish a GitHub Release" >&2 + exit 1 +fi +if [[ "$("$GIT_BIN" branch --show-current)" != "main" ]]; then + echo "GitHub releases must be published from main" >&2 + exit 1 +fi +if [[ -n "$("$GIT_BIN" status --porcelain)" ]]; then + echo "working tree must be clean before publishing a release" >&2 + exit 1 +fi + +"$GH_BIN" auth status +"$GIT_BIN" fetch origin main --tags +HEAD="$("$GIT_BIN" rev-parse HEAD)" +if [[ "$HEAD" != "$("$GIT_BIN" rev-parse origin/main)" ]]; then + echo "local main must match origin/main before publishing a release" >&2 + exit 1 +fi + +"$RELEASE_SCRIPT" "$VERSION" + +shopt -s nullglob +ASSETS=("$RELEASE_DIR"/*.tar.gz "$RELEASE_DIR"/*.tgz) +if [[ -f "$RELEASE_DIR/SHA256SUMS" ]]; then + ASSETS+=("$RELEASE_DIR/SHA256SUMS") +fi +if [[ ${#ASSETS[@]} -eq 0 ]]; then + echo "no release assets found in $RELEASE_DIR" >&2 + exit 1 +fi + +if "$GIT_BIN" rev-parse -q --verify "refs/tags/$VERSION" >/dev/null; then + if [[ "$("$GIT_BIN" rev-parse "${VERSION}^{commit}")" != "$HEAD" ]]; then + echo "existing tag $VERSION does not point at HEAD" >&2 + exit 1 + fi +else + "$GIT_BIN" tag -a "$VERSION" -m "Release $VERSION" + "$GIT_BIN" push origin "refs/tags/$VERSION" +fi + +if "$GH_BIN" release view "$VERSION" --repo "$REPOSITORY" >/dev/null 2>&1; then + "$GH_BIN" release upload "$VERSION" "${ASSETS[@]}" --repo "$REPOSITORY" --clobber +else + "$GH_BIN" release create "$VERSION" "${ASSETS[@]}" \ + --repo "$REPOSITORY" \ + --title "Verstak Sync Server $VERSION" \ + --generate-notes \ + --latest \ + --verify-tag +fi + +echo "GitHub release:" +"$GH_BIN" release view "$VERSION" --repo "$REPOSITORY" --json url --jq .url diff --git a/scripts/release.sh b/scripts/release.sh new file mode 100755 index 0000000..924690f --- /dev/null +++ b/scripts/release.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +VERSION="${1:-}" + +if [[ -z "$VERSION" || ! "$VERSION" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]]; then + echo "usage: $0 " >&2 + echo "example: $0 v0.1.0-alpha.1" >&2 + exit 2 +fi +if [[ "$(go env GOOS)" != "linux" || "$(go env GOARCH)" != "amd64" ]]; then + echo "sync-server release artifacts are currently built for linux/amd64" >&2 + exit 1 +fi + +echo "=== verstak sync server release $VERSION ===" +"$ROOT/scripts/build.sh" + +RELEASE_ROOT="$ROOT/release" +STAGING="$RELEASE_ROOT/verstak-sync-server-$VERSION-linux-amd64" +ARCHIVE="$RELEASE_ROOT/verstak-sync-server-linux-amd64-$VERSION.tar.gz" +rm -rf "$STAGING" "$ARCHIVE" +mkdir -p "$STAGING/scripts" + +cp "$ROOT/build/bin/verstak-sync-server" "$STAGING/verstak-sync-server" +cp "$ROOT/README.md" "$ROOT/LICENSE" "$ROOT/verstak-server.service" "$STAGING/" +cp "$ROOT/scripts/install.sh" "$STAGING/scripts/install.sh" +tar -C "$RELEASE_ROOT" -czf "$ARCHIVE" "$(basename "$STAGING")" +(cd "$RELEASE_ROOT" && sha256sum "$(basename "$ARCHIVE")" > SHA256SUMS) + +echo "release archive: $ARCHIVE" +echo "checksums: $RELEASE_ROOT/SHA256SUMS" diff --git a/scripts/test-publish-github-release.sh b/scripts/test-publish-github-release.sh new file mode 100755 index 0000000..7d258f1 --- /dev/null +++ b/scripts/test-publish-github-release.sh @@ -0,0 +1,91 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +PUBLISHER="$ROOT/scripts/publish-github-release.sh" +VERSION="v0.0.0-test" +REPOSITORY="mirivlad/verstak-sync-server" +ASSET_NAME="verstak-sync-server-linux-amd64-${VERSION}.tar.gz" +WORK="$(mktemp -d)" +trap 'rm -rf "$WORK"' EXIT + +if [[ ! -x "$PUBLISHER" ]]; then + echo "publisher is missing or not executable: $PUBLISHER" >&2 + exit 1 +fi + +mkdir -p "$WORK/bin" "$WORK/release" "$WORK/state" +LOG="$WORK/log" +export LOG + +cat > "$WORK/release.sh" <<'SCRIPT' +#!/usr/bin/env bash +set -euo pipefail +printf 'release:%s\n' "$1" >> "$LOG" +printf 'archive\n' > "$VERSTAK_RELEASE_DIR/verstak-sync-server-linux-amd64-$1.tar.gz" +printf 'checksum\n' > "$VERSTAK_RELEASE_DIR/SHA256SUMS" +SCRIPT +chmod +x "$WORK/release.sh" + +cat > "$WORK/bin/git" <<'SCRIPT' +#!/usr/bin/env bash +set -euo pipefail +if [[ "$PWD" != "${EXPECTED_ROOT:?}" ]]; then + echo "publisher did not enter repository root: $PWD" >&2 + exit 1 +fi +case "${1:-}" in + status) exit 0 ;; + branch) echo main ;; + fetch) printf 'fetch\n' >> "$LOG" ;; + rev-parse) + if [[ "${2:-}" == "-q" ]]; then + if [[ -f "$TEST_STATE/tag" ]]; then echo test-commit; exit 0; fi + exit 1 + fi + echo test-commit + ;; + tag) touch "$TEST_STATE/tag"; printf 'tag:%s\n' "${3:-}" >> "$LOG" ;; + push) printf 'push:%s:%s\n' "${2:-}" "${3:-}" >> "$LOG" ;; + *) echo "unexpected git invocation: $*" >&2; exit 1 ;; +esac +SCRIPT +chmod +x "$WORK/bin/git" + +cat > "$WORK/bin/gh" <<'SCRIPT' +#!/usr/bin/env bash +set -euo pipefail +case "${1:-}:${2:-}" in + auth:status) printf 'auth\n' >> "$LOG" ;; + release:view) + if [[ -f "$TEST_STATE/release" ]]; then echo https://github.example/release; else exit 1; fi + ;; + release:create) touch "$TEST_STATE/release"; printf 'create:%s\n' "$*" >> "$LOG" ;; + release:upload) printf 'upload:%s\n' "$*" >> "$LOG" ;; + *) echo "unexpected gh invocation: $*" >&2; exit 1 ;; +esac +SCRIPT +chmod +x "$WORK/bin/gh" + +run_publisher() { + VERSTAK_RELEASE_SCRIPT="$WORK/release.sh" \ + VERSTAK_RELEASE_DIR="$WORK/release" \ + GIT_BIN="$WORK/bin/git" \ + GH_BIN="$WORK/bin/gh" \ + EXPECTED_ROOT="$ROOT" \ + TEST_STATE="$WORK/state" \ + "$PUBLISHER" "$VERSION" +} + +run_publisher +grep -Fqx "release:$VERSION" "$LOG" +grep -Fqx "tag:$VERSION" "$LOG" +grep -Fqx "push:origin:refs/tags/$VERSION" "$LOG" +grep -F "release create $VERSION" "$LOG" >/dev/null +grep -F "$ASSET_NAME" "$LOG" >/dev/null +grep -F "SHA256SUMS" "$LOG" >/dev/null + +run_publisher +grep -F "release upload $VERSION" "$LOG" >/dev/null + +echo "GitHub release publisher test passed"