From d76aa17b38d49eb3c01442921b1d170a998ccc24 Mon Sep 17 00:00:00 2001 From: Brandon Lucas Date: Wed, 18 Feb 2026 19:06:33 -0500 Subject: [PATCH] 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 --- Cargo.lock | 214 +++++++++++++++++---------------------------- Cargo.toml | 2 +- flake.nix | 42 +++++++-- scripts/release.sh | 175 ++++++++++++++++++++++++++++++++++++ 4 files changed, 290 insertions(+), 143 deletions(-) create mode 100755 scripts/release.sh diff --git a/Cargo.lock b/Cargo.lock index 5777c3b..2690349 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -135,16 +135,6 @@ dependencies = [ "libc", ] -[[package]] -name = "core-foundation" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -235,7 +225,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -297,21 +287,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" version = "1.2.2" @@ -552,16 +527,17 @@ dependencies = [ ] [[package]] -name = "hyper-tls" -version = "0.5.0" +name = "hyper-rustls" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ - "bytes", + "futures-util", + "http", "hyper", - "native-tls", + "rustls", "tokio", - "tokio-native-tls", + "tokio-rustls", ] [[package]] @@ -843,23 +819,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "native-tls" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5d26952a508f321b4d3d2e80e78fc2603eaefcdf0c30783867f19586518bdc" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "nibble_vec" version = "0.1.0" @@ -905,50 +864,6 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" -[[package]] -name = "openssl" -version = "0.10.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" -dependencies = [ - "bitflags 2.10.0", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "openssl-probe" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" - -[[package]] -name = "openssl-sys" -version = "0.9.111" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "parking_lot" version = "0.12.5" @@ -1203,15 +1118,15 @@ dependencies = [ "http", "http-body", "hyper", - "hyper-tls", + "hyper-rustls", "ipnet", "js-sys", "log", "mime", - "native-tls", "once_cell", "percent-encoding", "pin-project-lite", + "rustls", "rustls-pemfile", "serde", "serde_json", @@ -1219,15 +1134,30 @@ dependencies = [ "sync_wrapper", "system-configuration", "tokio", - "tokio-native-tls", + "tokio-rustls", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", + "webpki-roots", "winreg", ] +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "rusqlite" version = "0.31.0" @@ -1252,7 +1182,19 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", ] [[package]] @@ -1264,6 +1206,16 @@ dependencies = [ "base64 0.21.7", ] +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.22" @@ -1298,15 +1250,6 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" -[[package]] -name = "schannel" -version = "0.1.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" -dependencies = [ - "windows-sys 0.61.2", -] - [[package]] name = "scopeguard" version = "1.2.0" @@ -1314,26 +1257,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] -name = "security-framework" -version = "3.6.0" +name = "sct" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d17b898a6d6948c3a8ee4372c17cb384f90d2e6e912ef00895b14fd7ab54ec38" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "bitflags 2.10.0", - "core-foundation 0.10.1", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "321c8673b092a9a42605034a9879d73cb79101ed5fd117bc9a597b89b4e9e61a" -dependencies = [ - "core-foundation-sys", - "libc", + "ring", + "untrusted", ] [[package]] @@ -1521,7 +1451,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" dependencies = [ "bitflags 1.3.2", - "core-foundation 0.9.4", + "core-foundation", "system-configuration-sys", ] @@ -1545,7 +1475,7 @@ dependencies = [ "getrandom 0.4.1", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -1619,16 +1549,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - [[package]] name = "tokio-postgres" version = "0.7.16" @@ -1655,6 +1575,16 @@ dependencies = [ "whoami", ] +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.18" @@ -1750,6 +1680,12 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.8" @@ -1941,6 +1877,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-roots" +version = "0.25.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" + [[package]] name = "whoami" version = "2.1.1" diff --git a/Cargo.toml b/Cargo.toml index ee38a1d..cf9a185 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ lsp-types = "0.94" serde = { version = "1", features = ["derive"] } serde_json = "1" rand = "0.8" -reqwest = { version = "0.11", features = ["blocking", "json"] } +reqwest = { version = "0.11", default-features = false, features = ["blocking", "json", "rustls-tls"] } tiny_http = "0.12" rusqlite = { version = "0.31", features = ["bundled"] } postgres = "0.19" diff --git a/flake.nix b/flake.nix index fb5bd5a..102c750 100644 --- a/flake.nix +++ b/flake.nix @@ -14,6 +14,7 @@ pkgs = import nixpkgs { inherit system overlays; }; rustToolchain = pkgs.rust-bin.stable.latest.default.override { extensions = [ "rust-src" "rust-analyzer" ]; + targets = [ "x86_64-unknown-linux-musl" ]; }; in { @@ -22,8 +23,8 @@ rustToolchain cargo-watch cargo-edit - pkg-config - openssl + # Static builds + pkgsStatic.stdenv.cc # Benchmark tools hyperfine poop @@ -65,14 +66,43 @@ src = ./.; cargoLock.lockFile = ./Cargo.lock; - nativeBuildInputs = [ pkgs.pkg-config ]; - buildInputs = [ pkgs.openssl ]; - doCheck = false; }; - # Benchmark scripts + packages.static = let + muslPkgs = import nixpkgs { + inherit system; + crossSystem = { + config = "x86_64-unknown-linux-musl"; + isStatic = true; + }; + }; + in muslPkgs.rustPlatform.buildRustPackage { + pname = "lux"; + version = "0.1.0"; + src = ./.; + cargoLock.lockFile = ./Cargo.lock; + + CARGO_BUILD_TARGET = "x86_64-unknown-linux-musl"; + CARGO_BUILD_RUSTFLAGS = "-C target-feature=+crt-static"; + + doCheck = false; + + postInstall = '' + $STRIP $out/bin/lux 2>/dev/null || true + ''; + }; + apps = { + # Release automation + release = { + type = "app"; + program = toString (pkgs.writeShellScript "lux-release" '' + exec ${self}/scripts/release.sh "$@" + ''); + }; + + # Benchmark scripts # Run hyperfine benchmark comparison bench = { type = "app"; diff --git a/scripts/release.sh b/scripts/release.sh new file mode 100755 index 0000000..f43fa59 --- /dev/null +++ b/scripts/release.sh @@ -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 || true) +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"