feat: static binary builds and automated release script

Switch reqwest from native-tls (openssl) to rustls-tls for a pure-Rust
TLS stack, enabling fully static musl builds. Add `nix build .#static`
for portable Linux binaries and `scripts/release.sh` for automated
Gitea releases with changelog generation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-18 19:06:33 -05:00
parent c23d9c7078
commit 7c55b76dae
4 changed files with 290 additions and 143 deletions

175
scripts/release.sh Executable file
View File

@@ -0,0 +1,175 @@
#!/usr/bin/env bash
set -euo pipefail
# Lux Release Script
# Builds a static binary, generates changelog, and creates a Gitea release.
#
# Usage:
# ./scripts/release.sh [version]
#
# Environment:
# GITEA_TOKEN - API token for git.qrty.ink (prompted if not set)
# GITEA_URL - Gitea instance URL (default: https://git.qrty.ink)
GITEA_URL="${GITEA_URL:-https://git.qrty.ink}"
REPO_OWNER="blu"
REPO_NAME="lux"
API_BASE="$GITEA_URL/api/v1"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m'
info() { printf "${CYAN}::${NC} %s\n" "$1"; }
ok() { printf "${GREEN}ok${NC} %s\n" "$1"; }
warn() { printf "${YELLOW}!!${NC} %s\n" "$1"; }
err() { printf "${RED}error:${NC} %s\n" "$1" >&2; exit 1; }
# --- Determine version ---
VERSION="${1:-}"
if [ -z "$VERSION" ]; then
VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)".*/\1/')
info "Version from Cargo.toml: v$VERSION"
fi
# Ensure v prefix
[[ "$VERSION" == v* ]] || VERSION="v$VERSION"
TAG="$VERSION"
# --- Check for clean working tree ---
if [ -n "$(git status --porcelain)" ]; then
warn "Working tree has uncommitted changes:"
git status --short
printf "\n"
read -rp "Continue anyway? [y/N] " confirm
[[ "$confirm" =~ ^[Yy]$ ]] || exit 1
fi
# --- Check if tag already exists ---
if git rev-parse "$TAG" >/dev/null 2>&1; then
err "Tag $TAG already exists. Bump version in Cargo.toml or choose a different version."
fi
# --- Generate changelog ---
info "Generating changelog..."
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
if [ -n "$LAST_TAG" ]; then
RANGE="$LAST_TAG..HEAD"
info "Changes since $LAST_TAG:"
else
RANGE="HEAD"
info "First release — summarizing recent commits:"
fi
CHANGELOG=$(git log "$RANGE" --pretty=format:"- %s" --no-merges 2>/dev/null | head -50)
if [ -z "$CHANGELOG" ]; then
CHANGELOG="- Initial release"
fi
# --- Build static binary ---
info "Building static binary (nix build .#static)..."
nix build .#static
BINARY="result/bin/lux"
if [ ! -f "$BINARY" ]; then
err "Static binary not found at $BINARY"
fi
BINARY_SIZE=$(ls -lh "$BINARY" | awk '{print $5}')
BINARY_TYPE=$(file "$BINARY" | sed 's/.*: //')
ok "Binary: $BINARY_SIZE, $BINARY_TYPE"
# --- Prepare release artifact ---
ARTIFACT="/tmp/lux-${TAG}-linux-x86_64"
cp "$BINARY" "$ARTIFACT"
chmod +x "$ARTIFACT"
# --- Show release summary ---
printf "\n"
printf "${BOLD}═══ Release Summary ═══${NC}\n"
printf "\n"
printf " ${BOLD}Tag:${NC} %s\n" "$TAG"
printf " ${BOLD}Binary:${NC} %s (%s)\n" "lux-${TAG}-linux-x86_64" "$BINARY_SIZE"
printf " ${BOLD}Commit:${NC} %s\n" "$(git rev-parse --short HEAD)"
printf "\n"
printf "${BOLD}Changelog:${NC}\n"
printf "%s\n" "$CHANGELOG"
printf "\n"
# --- Confirm ---
read -rp "Create release $TAG? [y/N] " confirm
[[ "$confirm" =~ ^[Yy]$ ]] || { info "Aborted."; exit 0; }
# --- Get Gitea token ---
if [ -z "${GITEA_TOKEN:-}" ]; then
printf "\n"
info "Gitea API token required (create at $GITEA_URL/user/settings/applications)"
read -rsp "Token: " GITEA_TOKEN
printf "\n"
fi
if [ -z "$GITEA_TOKEN" ]; then
err "No token provided"
fi
# --- Create and push tag ---
info "Creating tag $TAG..."
git tag -a "$TAG" -m "Release $TAG" --no-sign
ok "Tag created"
info "Pushing tag to origin..."
git push origin "$TAG"
ok "Tag pushed"
# --- Create Gitea release ---
info "Creating release on Gitea..."
RELEASE_BODY=$(printf "## Lux %s\n\n### Changes\n\n%s\n\n### Installation\n\n\`\`\`bash\ncurl -Lo lux %s/%s/%s/releases/download/%s/lux-linux-x86_64\nchmod +x lux\n./lux --version\n\`\`\`" \
"$TAG" "$CHANGELOG" "$GITEA_URL" "$REPO_OWNER" "$REPO_NAME" "$TAG")
RELEASE_JSON=$(jq -n \
--arg tag "$TAG" \
--arg name "Lux $TAG" \
--arg body "$RELEASE_BODY" \
'{tag_name: $tag, name: $name, body: $body, draft: false, prerelease: false}')
RELEASE_RESPONSE=$(curl -s -X POST \
"$API_BASE/repos/$REPO_OWNER/$REPO_NAME/releases" \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
-d "$RELEASE_JSON")
RELEASE_ID=$(echo "$RELEASE_RESPONSE" | jq -r '.id // empty')
if [ -z "$RELEASE_ID" ]; then
echo "$RELEASE_RESPONSE" | jq . 2>/dev/null || echo "$RELEASE_RESPONSE"
err "Failed to create release"
fi
ok "Release created (id: $RELEASE_ID)"
# --- Upload binary ---
info "Uploading binary..."
UPLOAD_RESPONSE=$(curl -s -X POST \
"$API_BASE/repos/$REPO_OWNER/$REPO_NAME/releases/$RELEASE_ID/assets?name=lux-linux-x86_64" \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/octet-stream" \
--data-binary "@$ARTIFACT")
ASSET_NAME=$(echo "$UPLOAD_RESPONSE" | jq -r '.name // empty')
if [ -z "$ASSET_NAME" ]; then
echo "$UPLOAD_RESPONSE" | jq . 2>/dev/null || echo "$UPLOAD_RESPONSE"
err "Failed to upload binary"
fi
ok "Binary uploaded: $ASSET_NAME"
# --- Done ---
printf "\n"
printf "${GREEN}${BOLD}Release $TAG published!${NC}\n"
printf "\n"
printf " ${BOLD}URL:${NC} %s/%s/%s/releases/tag/%s\n" "$GITEA_URL" "$REPO_OWNER" "$REPO_NAME" "$TAG"
printf " ${BOLD}Download:${NC} %s/%s/%s/releases/download/%s/lux-linux-x86_64\n" "$GITEA_URL" "$REPO_OWNER" "$REPO_NAME" "$TAG"
printf "\n"
# Cleanup
rm -f "$ARTIFACT"