Enhance release script with preflight checks and metadata
Refactor release script to improve preflight checks and metadata preparation. Update release steps for GitHub and Gitea.
This commit is contained in:
+219
-167
@@ -20,6 +20,11 @@
|
|||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Always operate from the repository root, regardless of the caller's cwd.
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
cd "$REPO_ROOT"
|
||||||
|
|
||||||
# Colors
|
# Colors
|
||||||
RED='\033[0;31m'
|
RED='\033[0;31m'
|
||||||
GREEN='\033[0;32m'
|
GREEN='\033[0;32m'
|
||||||
@@ -90,10 +95,80 @@ suggest_next_version() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
# Step 0: Fetch everything
|
# Step 0: Preflight and fetch everything
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
echo -e "${BLUE}Step 0: Fetching latest from all remotes...${NC}"
|
echo -e "${BLUE}Step 0: Running release preflight...${NC}"
|
||||||
git fetch --all --tags 2>/dev/null || true
|
|
||||||
|
# A release must start from an exact, committed, reproducible tree. This also
|
||||||
|
# prevents release metadata generated below from being mixed with local work.
|
||||||
|
if [[ -n "$(git status --porcelain)" ]]; then
|
||||||
|
echo -e "${RED}Error: the working tree is not clean. Commit, stash, or remove local changes first.${NC}" >&2
|
||||||
|
git status --short >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
for remote in "$GITHUB_REMOTE" "$GITEA_REMOTE"; do
|
||||||
|
if ! git remote | grep -q -x "$remote"; then
|
||||||
|
echo -e "${RED}Error: required remote '$remote' is not configured.${NC}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
for command_name in gh curl; do
|
||||||
|
if ! command -v "$command_name" >/dev/null 2>&1; then
|
||||||
|
echo -e "${RED}Error: required command '$command_name' is not installed.${NC}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if ! gh auth status >/dev/null 2>&1; then
|
||||||
|
echo -e "${RED}Error: GitHub CLI is not authenticated. Run 'gh auth login' first.${NC}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [[ -z "${GITEA_TOKEN:-}" ]]; then
|
||||||
|
echo
|
||||||
|
read -rsp "Enter your Gitea API token (input hidden): " GITEA_TOKEN
|
||||||
|
echo
|
||||||
|
fi
|
||||||
|
if [[ -z "${GITEA_TOKEN:-}" ]]; then
|
||||||
|
echo -e "${RED}Error: a GITEA_TOKEN with write:repository scope is required.${NC}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e " Fetching GitHub (${GITHUB_REMOTE}) and Gitea (${GITEA_REMOTE})..."
|
||||||
|
git fetch "$GITHUB_REMOTE" --prune --tags
|
||||||
|
git fetch "$GITEA_REMOTE" --prune --tags
|
||||||
|
|
||||||
|
GITHUB_MAIN_REF="refs/remotes/${GITHUB_REMOTE}/main"
|
||||||
|
GITEA_STAGING_REF="refs/remotes/${GITEA_REMOTE}/staging-dev"
|
||||||
|
|
||||||
|
if ! git rev-parse --verify "$GITHUB_MAIN_REF" >/dev/null 2>&1; then
|
||||||
|
echo -e "${RED}Error: cannot resolve GitHub main at ${GITHUB_MAIN_REF}.${NC}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! git rev-parse --verify "$GITEA_STAGING_REF" >/dev/null 2>&1; then
|
||||||
|
echo -e "${RED}Error: cannot resolve Gitea staging-dev at ${GITEA_STAGING_REF}.${NC}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
HEAD_COMMIT=$(git rev-parse HEAD)
|
||||||
|
GITHUB_MAIN_COMMIT=$(git rev-parse "$GITHUB_MAIN_REF")
|
||||||
|
GITEA_STAGING_COMMIT=$(git rev-parse "$GITEA_STAGING_REF")
|
||||||
|
|
||||||
|
if [[ "$GITHUB_MAIN_COMMIT" != "$GITEA_STAGING_COMMIT" ]]; then
|
||||||
|
echo -e "${RED}Error: GitHub main and Gitea staging-dev are not synchronized.${NC}" >&2
|
||||||
|
echo " GitHub main : $GITHUB_MAIN_COMMIT" >&2
|
||||||
|
echo " Gitea staging-dev : $GITEA_STAGING_COMMIT" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [[ "$HEAD_COMMIT" != "$GITHUB_MAIN_COMMIT" ]]; then
|
||||||
|
echo -e "${RED}Error: local HEAD is not the synchronized release candidate.${NC}" >&2
|
||||||
|
echo " Local HEAD : $HEAD_COMMIT" >&2
|
||||||
|
echo " Remote HEAD : $GITHUB_MAIN_COMMIT" >&2
|
||||||
|
echo "Update/check out the synchronized commit, then run the script again." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e " ${GREEN}✓${NC} Clean tree; GitHub main and Gitea staging-dev match local HEAD"
|
||||||
|
|
||||||
LATEST_TAG=$(get_latest_tag)
|
LATEST_TAG=$(get_latest_tag)
|
||||||
NEXT_VERSION=$(suggest_next_version "$LATEST_TAG")
|
NEXT_VERSION=$(suggest_next_version "$LATEST_TAG")
|
||||||
@@ -117,6 +192,15 @@ fi
|
|||||||
VERSION="${VERSION#v}"
|
VERSION="${VERSION#v}"
|
||||||
TAG="v${VERSION}"
|
TAG="v${VERSION}"
|
||||||
|
|
||||||
|
if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||||
|
echo -e "${RED}Error: version must use MAJOR.MINOR.PATCH format (for example, 1.1.1).${NC}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then
|
||||||
|
echo -e "${RED}Error: tag ${TAG} already exists. Refusing to move or overwrite a release tag.${NC}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
# Get release message
|
# Get release message
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
@@ -207,6 +291,7 @@ RELEASE_NOTES="$(generate_release_notes "$COMMIT_RANGE")"
|
|||||||
|
|
||||||
# Let the user review/edit the generated notes before publishing
|
# Let the user review/edit the generated notes before publishing
|
||||||
NOTES_FILE="$(mktemp "/tmp/release-notes-${TAG}.XXXXXX.md")"
|
NOTES_FILE="$(mktemp "/tmp/release-notes-${TAG}.XXXXXX.md")"
|
||||||
|
trap 'rm -f "$NOTES_FILE"' EXIT
|
||||||
{
|
{
|
||||||
echo "## Sovran_SystemsOS ${TAG}"
|
echo "## Sovran_SystemsOS ${TAG}"
|
||||||
echo
|
echo
|
||||||
@@ -246,95 +331,45 @@ if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
# Step 1: Push tested code to Gitea (stable & staging-dev)
|
# Step 1: Prepare and commit all release metadata
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
echo
|
echo
|
||||||
echo -e "${BLUE}Step 1: Pushing tested code to Gitea (stable & staging-dev)...${NC}"
|
echo -e "${BLUE}Step 1: Preparing release metadata for ${TAG}...${NC}"
|
||||||
if git remote | grep -q -x "${GITEA_REMOTE}"; then
|
|
||||||
git push "${GITEA_REMOTE}" HEAD:stable --force-with-lease || echo -e " ${YELLOW}⚠ Push to ${GITEA_REMOTE} (stable) failed.${NC}"
|
|
||||||
git push "${GITEA_REMOTE}" HEAD:staging-dev --force-with-lease || echo -e " ${YELLOW}⚠ Push to ${GITEA_REMOTE} (staging-dev) failed.${NC}"
|
|
||||||
else
|
|
||||||
echo -e " ${YELLOW}⚠ Remote '${GITEA_REMOTE}' not found in git — skipping Gitea push.${NC}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# VERSION drives ISO naming.
|
||||||
# Step 2: Create annotated tag
|
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
echo
|
|
||||||
echo -e "${BLUE}Step 2: Creating annotated tag ${TAG}...${NC}"
|
|
||||||
git tag -f -a "${TAG}" -m "${RELEASE_MESSAGE}
|
|
||||||
|
|
||||||
- Stable release of Sovran_SystemsOS
|
|
||||||
- See CHANGELOG.md for full details" || true
|
|
||||||
|
|
||||||
if git remote | grep -q -x "${GITEA_REMOTE}"; then
|
|
||||||
git push "${GITEA_REMOTE}" "${TAG}" || echo -e " ${YELLOW}⚠ Tag push to ${GITEA_REMOTE} failed.${NC}"
|
|
||||||
else
|
|
||||||
echo -e " ${YELLOW}⚠ Remote '${GITEA_REMOTE}' not found in git — skipping Gitea tag push.${NC}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if git remote | grep -q -x "${GITHUB_REMOTE}"; then
|
|
||||||
git push "${GITHUB_REMOTE}" "${TAG}" || echo -e " ${YELLOW}⚠ Tag push to ${GITHUB_REMOTE} failed.${NC}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Update VERSION file for ISO builds
|
|
||||||
echo "${VERSION}" > VERSION
|
echo "${VERSION}" > VERSION
|
||||||
git add VERSION
|
|
||||||
git commit -m "chore: bump VERSION to ${TAG} for ISO naming" || true
|
|
||||||
echo -e " ${GREEN}✓${NC} VERSION file updated to ${VERSION}"
|
|
||||||
|
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
# Step 2b: Auto-update README.md ISO download references
|
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
echo
|
|
||||||
echo -e "${BLUE}Step 2b: Updating README.md ISO references to ${VERSION}...${NC}"
|
|
||||||
|
|
||||||
|
# Update every versioned ISO filename in README.md.
|
||||||
README_FILE="README.md"
|
README_FILE="README.md"
|
||||||
if [ -f "$README_FILE" ]; then
|
if [[ ! -f "$README_FILE" ]]; then
|
||||||
# Extract the version currently used in the README's ISO filenames,
|
echo -e "${RED}Error: ${README_FILE} not found.${NC}" >&2
|
||||||
# e.g. "Sovran_SystemsOS-1.0.6.iso" -> "1.0.6"
|
exit 1
|
||||||
OLD_ISO_VER=$(grep -oE 'Sovran_SystemsOS-[0-9]+\.[0-9]+\.[0-9]+\.iso' "$README_FILE" | head -1 | sed 's/Sovran_SystemsOS-//; s/\.iso//')
|
fi
|
||||||
|
OLD_ISO_VER=$(grep -oE 'Sovran_SystemsOS-[0-9]+\.[0-9]+\.[0-9]+\.iso' "$README_FILE" \
|
||||||
if [ -n "$OLD_ISO_VER" ] && [ "$OLD_ISO_VER" != "$VERSION" ]; then
|
| head -1 | sed 's/Sovran_SystemsOS-//; s/\.iso//')
|
||||||
# Portable in-place edit (works with GNU sed and BSD/macOS sed)
|
if [[ -z "$OLD_ISO_VER" ]]; then
|
||||||
|
echo -e "${RED}Error: no versioned ISO reference found in ${README_FILE}.${NC}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [[ "$OLD_ISO_VER" != "$VERSION" ]]; then
|
||||||
sed "s/Sovran_SystemsOS-${OLD_ISO_VER}/Sovran_SystemsOS-${VERSION}/g" \
|
sed "s/Sovran_SystemsOS-${OLD_ISO_VER}/Sovran_SystemsOS-${VERSION}/g" \
|
||||||
"$README_FILE" > "$README_FILE.tmp" && mv "$README_FILE.tmp" "$README_FILE"
|
"$README_FILE" > "$README_FILE.tmp"
|
||||||
|
mv "$README_FILE.tmp" "$README_FILE"
|
||||||
UPDATED=$(grep -c "Sovran_SystemsOS-${VERSION}" "$README_FILE")
|
|
||||||
echo -e " ${GREEN}✓${NC} README.md: ${OLD_ISO_VER} -> ${VERSION} (${UPDATED} references updated)"
|
|
||||||
|
|
||||||
git add "$README_FILE"
|
|
||||||
git commit -m "docs: update README ISO download links to ${TAG}" || true
|
|
||||||
echo -e " ${GREEN}✓${NC} Committed README update"
|
|
||||||
else
|
|
||||||
echo -e " ${YELLOW}⚠${NC} README.md already references ${VERSION} or no versioned ISO links found — skipping"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo -e " ${YELLOW}⚠${NC} README.md not found — skipping"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# Add the changelog entry before tagging so the tag and stable branch contain it.
|
||||||
# Step 3: Auto-update CHANGELOG.md
|
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
echo
|
|
||||||
echo -e "${BLUE}Step 3: Updating ${CHANGELOG_FILE}...${NC}"
|
|
||||||
|
|
||||||
TODAY=$(date +%Y-%m-%d)
|
TODAY=$(date +%Y-%m-%d)
|
||||||
|
|
||||||
# Create new changelog entry from the generated notes (no placeholders)
|
|
||||||
NEW_ENTRY="## [${VERSION}] - ${TODAY}
|
NEW_ENTRY="## [${VERSION}] - ${TODAY}
|
||||||
|
|
||||||
${RELEASE_NOTES}
|
${RELEASE_NOTES}
|
||||||
[${VERSION}]: https://git.sovransystems.com/Sovran_Systems/Sovran_SystemsOS/releases/tag/${TAG}
|
[${VERSION}]: https://git.sovransystems.com/Sovran_Systems/Sovran_SystemsOS/releases/tag/${TAG}
|
||||||
"
|
"
|
||||||
|
|
||||||
# Prepend to changelog (after the header)
|
if [[ ! -f "$CHANGELOG_FILE" ]]; then
|
||||||
if [ -f "$CHANGELOG_FILE" ]; then
|
echo -e "${RED}Error: ${CHANGELOG_FILE} not found.${NC}" >&2
|
||||||
# Backup
|
exit 1
|
||||||
cp "$CHANGELOG_FILE" "${CHANGELOG_FILE}.bak"
|
fi
|
||||||
|
awk -v new_entry="$NEW_ENTRY" '
|
||||||
# Insert new section after the first --- line
|
|
||||||
awk -v new_entry="$NEW_ENTRY" '
|
|
||||||
BEGIN { printed=0 }
|
BEGIN { printed=0 }
|
||||||
/^---$/ && !printed {
|
/^---$/ && !printed {
|
||||||
print
|
print
|
||||||
@@ -344,116 +379,134 @@ if [ -f "$CHANGELOG_FILE" ]; then
|
|||||||
next
|
next
|
||||||
}
|
}
|
||||||
{ print }
|
{ print }
|
||||||
' "$CHANGELOG_FILE" > "${CHANGELOG_FILE}.tmp" && mv "${CHANGELOG_FILE}.tmp" "$CHANGELOG_FILE"
|
END { if (!printed) exit 2 }
|
||||||
rm -f "${CHANGELOG_FILE}.bak"
|
' "$CHANGELOG_FILE" > "${CHANGELOG_FILE}.tmp" || {
|
||||||
|
rm -f "${CHANGELOG_FILE}.tmp"
|
||||||
|
echo -e "${RED}Error: could not find the changelog insertion marker.${NC}" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
mv "${CHANGELOG_FILE}.tmp" "$CHANGELOG_FILE"
|
||||||
|
|
||||||
echo -e " ${GREEN}✓${NC} CHANGELOG.md updated with new section for ${TAG}"
|
git add VERSION "$README_FILE" "$CHANGELOG_FILE"
|
||||||
else
|
|
||||||
echo -e " ${YELLOW}⚠${NC} CHANGELOG.md not found — skipping"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Commit the changelog update
|
|
||||||
git add "$CHANGELOG_FILE"
|
|
||||||
if git diff --cached --quiet; then
|
if git diff --cached --quiet; then
|
||||||
echo " (No changes to commit in changelog)"
|
echo -e "${RED}Error: release preparation produced no changes.${NC}" >&2
|
||||||
else
|
exit 1
|
||||||
git commit -m "docs: update CHANGELOG.md for ${TAG}"
|
|
||||||
echo -e " ${GREEN}✓${NC} Committed changelog update"
|
|
||||||
|
|
||||||
# Ask if user wants to push
|
|
||||||
echo
|
|
||||||
read -rp "Push the changelog commit to GitHub (main) and Gitea (staging-dev) now? (y/N): " push_confirm
|
|
||||||
if [[ "$push_confirm" =~ ^[Yy]$ ]]; then
|
|
||||||
if git remote | grep -q -x "${GITHUB_REMOTE}"; then
|
|
||||||
echo -e "${BLUE}Pushing changelog commit to GitHub main...${NC}"
|
|
||||||
if git push "${GITHUB_REMOTE}" HEAD:main; then
|
|
||||||
echo -e " ${GREEN}✓${NC} Changelog pushed to GitHub main (${GITHUB_REMOTE})"
|
|
||||||
else
|
|
||||||
echo -e " ${YELLOW}⚠ Push to GitHub main failed — verify credentials or remote name.${NC}"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
if git remote | grep -q -x "${GITEA_REMOTE}"; then
|
|
||||||
echo -e "${BLUE}Pushing changelog commit to Gitea staging-dev...${NC}"
|
|
||||||
if git push "${GITEA_REMOTE}" HEAD:staging-dev; then
|
|
||||||
echo -e " ${GREEN}✓${NC} Changelog pushed to Gitea staging-dev (${GITEA_REMOTE})"
|
|
||||||
else
|
|
||||||
echo -e " ${YELLOW}⚠ Push to Gitea staging-dev failed.${NC}"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo " (Changelog commit left local — remember to push later)"
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
git commit -m "chore(release): prepare ${TAG}"
|
||||||
|
RELEASE_COMMIT=$(git rev-parse HEAD)
|
||||||
|
|
||||||
|
echo -e " ${GREEN}✓${NC} VERSION, README, and CHANGELOG committed"
|
||||||
|
echo -e " Release commit: ${CYAN}${RELEASE_COMMIT}${NC}"
|
||||||
|
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
# Step 4: Create GitHub Release via gh CLI
|
# Step 2: Publish the final release commit to every release branch
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
echo
|
echo
|
||||||
echo -e "${BLUE}Step 4: Creating GitHub Release...${NC}"
|
echo -e "${BLUE}Step 2: Publishing the final release commit...${NC}"
|
||||||
|
|
||||||
if command -v gh &>/dev/null; then
|
# GitHub main and Gitea staging-dev were verified equal during preflight, so
|
||||||
title_suffix="${RELEASE_MESSAGE#Sovran_SystemsOS v* — }"
|
# these are normal fast-forward pushes. Stable is an intentional promotion and
|
||||||
title_suffix="${title_suffix#Sovran_SystemsOS * — }"
|
# uses a lease to prevent overwriting a branch changed since the fetch.
|
||||||
if gh release create "${TAG}" \
|
git push "$GITHUB_REMOTE" HEAD:main
|
||||||
|
git push "$GITEA_REMOTE" HEAD:staging-dev
|
||||||
|
git push "$GITEA_REMOTE" HEAD:stable --force-with-lease
|
||||||
|
|
||||||
|
# Verify all three branch tips before creating an immutable release tag.
|
||||||
|
remote_branch_commit() {
|
||||||
|
local remote="$1"
|
||||||
|
local branch="$2"
|
||||||
|
git ls-remote "$remote" "refs/heads/${branch}" | awk 'NR == 1 { print $1 }'
|
||||||
|
}
|
||||||
|
|
||||||
|
PUBLISHED_GITHUB=$(remote_branch_commit "$GITHUB_REMOTE" main)
|
||||||
|
PUBLISHED_STAGING=$(remote_branch_commit "$GITEA_REMOTE" staging-dev)
|
||||||
|
PUBLISHED_STABLE=$(remote_branch_commit "$GITEA_REMOTE" stable)
|
||||||
|
if [[ "$PUBLISHED_GITHUB" != "$RELEASE_COMMIT" || \
|
||||||
|
"$PUBLISHED_STAGING" != "$RELEASE_COMMIT" || \
|
||||||
|
"$PUBLISHED_STABLE" != "$RELEASE_COMMIT" ]]; then
|
||||||
|
echo -e "${RED}Error: post-push verification failed; no release tag was created.${NC}" >&2
|
||||||
|
echo " Expected : $RELEASE_COMMIT" >&2
|
||||||
|
echo " GitHub main : ${PUBLISHED_GITHUB:-missing}" >&2
|
||||||
|
echo " Gitea staging-dev : ${PUBLISHED_STAGING:-missing}" >&2
|
||||||
|
echo " Gitea stable : ${PUBLISHED_STABLE:-missing}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo -e " ${GREEN}✓${NC} GitHub main, Gitea staging-dev, and Gitea stable all match"
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# Step 3: Tag the final release commit and publish the tag
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
echo
|
||||||
|
echo -e "${BLUE}Step 3: Creating annotated tag ${TAG} on ${RELEASE_COMMIT}...${NC}"
|
||||||
|
git tag -a "$TAG" -m "${RELEASE_MESSAGE}
|
||||||
|
|
||||||
|
- Stable release of Sovran_SystemsOS
|
||||||
|
- See CHANGELOG.md for full details" "$RELEASE_COMMIT"
|
||||||
|
|
||||||
|
git push "$GITHUB_REMOTE" "$TAG"
|
||||||
|
git push "$GITEA_REMOTE" "$TAG"
|
||||||
|
|
||||||
|
# For an annotated tag, ^{} resolves the commit referenced by the tag object.
|
||||||
|
remote_tag_commit() {
|
||||||
|
local remote="$1"
|
||||||
|
git ls-remote "$remote" "refs/tags/${TAG}^{}" | awk 'NR == 1 { print $1 }'
|
||||||
|
}
|
||||||
|
GITHUB_TAG_COMMIT=$(remote_tag_commit "$GITHUB_REMOTE")
|
||||||
|
GITEA_TAG_COMMIT=$(remote_tag_commit "$GITEA_REMOTE")
|
||||||
|
if [[ "$GITHUB_TAG_COMMIT" != "$RELEASE_COMMIT" || "$GITEA_TAG_COMMIT" != "$RELEASE_COMMIT" ]]; then
|
||||||
|
echo -e "${RED}Error: published tag verification failed.${NC}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo -e " ${GREEN}✓${NC} ${TAG} points to the final release commit on GitHub and Gitea"
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# Step 4: Create GitHub release via gh CLI
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
echo
|
||||||
|
echo -e "${BLUE}Step 4: Creating GitHub release...${NC}"
|
||||||
|
|
||||||
|
title_suffix="${RELEASE_MESSAGE#Sovran_SystemsOS v* — }"
|
||||||
|
title_suffix="${title_suffix#Sovran_SystemsOS * — }"
|
||||||
|
gh release create "$TAG" \
|
||||||
--repo naturallaw777/Sovran_SystemsOS \
|
--repo naturallaw777/Sovran_SystemsOS \
|
||||||
--title "${TAG} — ${title_suffix}" \
|
--title "${TAG} — ${title_suffix}" \
|
||||||
--notes-file "${NOTES_FILE}"; then
|
--notes-file "$NOTES_FILE"
|
||||||
echo -e " ${GREEN}✓${NC} GitHub release created successfully"
|
echo -e " ${GREEN}✓${NC} GitHub release created successfully"
|
||||||
else
|
|
||||||
echo -e " ${YELLOW}⚠${NC} GitHub release creation failed (check 'gh auth status' or create via web GUI)"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo -e " ${YELLOW}⚠${NC} gh CLI not found — skipping GitHub release (create via GitHub web GUI)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
# Step 5: Create Gitea Release via API
|
# Step 5: Create Gitea release via API
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
echo
|
echo
|
||||||
echo -e "${BLUE}Step 5: Creating Gitea Release via API...${NC}"
|
echo -e "${BLUE}Step 5: Creating Gitea release via API...${NC}"
|
||||||
|
|
||||||
# ── Gitea Token Handling ─────────────────────────────────────────────────────
|
GITEA_REPO="Sovran_Systems/Sovran_SystemsOS"
|
||||||
if [[ -z "${GITEA_TOKEN:-}" ]]; then
|
|
||||||
echo
|
# Build JSON safely because release notes may contain quotes and newlines.
|
||||||
echo -e "${YELLOW}GITEA_TOKEN is not set.${NC}"
|
if command -v jq >/dev/null 2>&1; then
|
||||||
read -rsp "Enter your Gitea API token (input will be hidden): " GITEA_TOKEN
|
PAYLOAD=$(jq -n \
|
||||||
echo
|
--arg tag "$TAG" \
|
||||||
if [[ -z "$GITEA_TOKEN" ]]; then
|
--arg name "$TAG — Stable Release" \
|
||||||
echo -e " ${YELLOW}⚠${NC} No token provided — skipping Gitea release"
|
--arg body "$RELEASE_BODY" \
|
||||||
GITEA_TOKEN=""
|
'{tag_name: $tag, name: $name, body: $body, draft: false, prerelease: false}')
|
||||||
fi
|
else
|
||||||
|
PAYLOAD=$(python3 -c "import json,sys; print(json.dumps({'tag_name': sys.argv[1], 'name': sys.argv[1] + ' — Stable Release', 'body': open(sys.argv[2]).read(), 'draft': False, 'prerelease': False}))" "$TAG" "$NOTES_FILE")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -n "${GITEA_TOKEN:-}" ]]; then
|
RESPONSE_FILE=$(mktemp "/tmp/gitea-release-${TAG}.XXXXXX.json")
|
||||||
GITEA_REPO="Sovran_Systems/Sovran_SystemsOS"
|
HTTP_STATUS=$(curl -sS -o "$RESPONSE_FILE" -w '%{http_code}' -X POST \
|
||||||
|
|
||||||
# Build JSON payload safely (release body may contain quotes/newlines)
|
|
||||||
if command -v jq &>/dev/null; then
|
|
||||||
PAYLOAD=$(jq -n \
|
|
||||||
--arg tag "${TAG}" \
|
|
||||||
--arg name "${TAG} — Stable Release" \
|
|
||||||
--arg body "${RELEASE_BODY}" \
|
|
||||||
'{tag_name: $tag, name: $name, body: $body, draft: false, prerelease: false}')
|
|
||||||
else
|
|
||||||
PAYLOAD=$(python3 -c "import json,sys; print(json.dumps({'tag_name': sys.argv[1], 'name': sys.argv[1] + ' — Stable Release', 'body': open(sys.argv[2]).read(), 'draft': False, 'prerelease': False}))" "${TAG}" "${NOTES_FILE}")
|
|
||||||
fi
|
|
||||||
|
|
||||||
RESPONSE=$(curl -s -X POST \
|
|
||||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d "${PAYLOAD}" \
|
-d "$PAYLOAD" \
|
||||||
"${GITEA_API_URL}/repos/${GITEA_REPO}/releases" 2>/dev/null || echo "")
|
"${GITEA_API_URL}/repos/${GITEA_REPO}/releases")
|
||||||
|
|
||||||
if echo "$RESPONSE" | grep -q '"id"'; then
|
if [[ "$HTTP_STATUS" != "201" ]]; then
|
||||||
echo -e " ${GREEN}✓${NC} Gitea release created successfully"
|
echo -e "${RED}Error: Gitea release creation failed (HTTP ${HTTP_STATUS}).${NC}" >&2
|
||||||
elif echo "$RESPONSE" | grep -q "write:repository"; then
|
cat "$RESPONSE_FILE" >&2
|
||||||
echo -e " ${YELLOW}⚠${NC} Gitea token scope issue: your token requires the 'write:repository' scope (currently has write:package)."
|
rm -f "$RESPONSE_FILE"
|
||||||
echo " To fix: In Gitea, navigate to Settings → Applications → Manage Access Tokens and generate a token with 'write:repository'."
|
exit 1
|
||||||
else
|
|
||||||
echo -e " ${YELLOW}⚠${NC} Gitea release creation failed or already exists"
|
|
||||||
echo " Response: $RESPONSE"
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
rm -f "$RESPONSE_FILE"
|
||||||
|
echo -e " ${GREEN}✓${NC} Gitea release created successfully"
|
||||||
|
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
# Final Summary
|
# Final Summary
|
||||||
@@ -468,8 +521,7 @@ echo " • Build the installer ISO:"
|
|||||||
echo " nix build .#nixosConfigurations.sovran_systemsos-iso.config.system.build.isoImage"
|
echo " nix build .#nixosConfigurations.sovran_systemsos-iso.config.system.build.isoImage"
|
||||||
echo " • Package, verify, and upload ISO to CDN:"
|
echo " • Package, verify, and upload ISO to CDN:"
|
||||||
echo " ./scripts/upload-cdn.sh --upload"
|
echo " ./scripts/upload-cdn.sh --upload"
|
||||||
echo " • Review and enhance the new section in CHANGELOG.md"
|
echo " • Verify the ISO download and checksum from the public CDN"
|
||||||
echo " • Push changes: git push ${GITHUB_REMOTE} HEAD:main && git push ${GITEA_REMOTE} HEAD:staging-dev"
|
|
||||||
echo " • Verify releases on both GitHub and Gitea"
|
echo " • Verify releases on both GitHub and Gitea"
|
||||||
echo
|
echo
|
||||||
echo -e "${CYAN}Tag created: ${TAG}${NC}"
|
echo -e "${CYAN}Tag created: ${TAG}${NC}"
|
||||||
|
|||||||
Reference in New Issue
Block a user