diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b48bd0b2..a52ad3ed 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,7 +10,27 @@ env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true jobs: + sync-main: + name: Sync main branch + runs-on: ubuntu-latest + # Only runs on v* tag pushes (release trigger). Uses force-push because main + # is a release-only mirror of develop — any commits on main not in develop + # are stale snapshots that should be overwritten by the current release. + if: startsWith(github.ref, 'refs/tags/v') + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + with: + ref: develop + + - name: Push to main + run: | + git push origin HEAD:main --force + echo "✅ main synced to develop at ${{ github.sha }}" + build-release: + needs: sync-main name: Build ${{ matrix.target }} runs-on: ${{ matrix.os }} strategy: @@ -152,18 +172,16 @@ jobs: printf '| Windows (x86_64) | `cora-x86_64-pc-windows-msvc-%s.zip` |\n\n' "${{ steps.version.outputs.TAG }}" printf '### 🚀 Quick Start\n\n' printf '```bash\n' - printf '# Install via cargo\n' + printf '# Quick install (Linux / macOS)\n' + printf 'curl -fsSL https://raw.githubusercontent.com/codecoradev/cora-cli/main/install.sh | sh\n\n' + printf '# Or via cargo\n' printf 'cargo install cora\n\n' - printf '# Or download binary (extracts as '\''cora'\'')\n' - printf 'curl -fsSL https://github.com/codecoradev/cora-cli/releases/latest/download/cora-x86_64-unknown-linux-gnu-%s.tar.gz | tar xz\n' "${{ steps.version.outputs.TAG }}" - printf 'chmod +x cora\n' - printf 'sudo mv cora /usr/local/bin/\n\n' printf '# Init project\n' printf 'cora init\n\n' printf '# Review staged changes\n' printf 'CORA_API_KEY=your-key cora review --staged\n' printf '```\n\n' - printf '**Full changelog:** https://github.com/codecoradev/cora-cli/blob/develop/CHANGELOG.md\n' + printf '**Full changelog:** https://github.com/codecoradev/cora-cli/blob/main/CHANGELOG.md\n' } > release-notes.md - name: Create GitHub Release diff --git a/README.md b/README.md index 9afba1fc..da1b49a5 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,12 @@ Installs to `~/.local/bin`. Add to PATH if needed: echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc # or ~/.zshrc ``` +Pin a specific version with `CORA_VERSION`: + +```bash +CORA_VERSION=v0.4.0 curl -fsSL https://raw.githubusercontent.com/codecoradev/cora-cli/main/install.sh | sh +``` + ### Pre-built Binaries Download from [GitHub Releases](https://github.com/codecoradev/cora-cli/releases): @@ -358,11 +364,9 @@ jobs: Or install manually: ```yaml -# Manual install in CI +# Manual install in CI (using install script) - name: Install cora-cli - run: | - curl -fsSL https://github.com/codecoradev/cora-cli/releases/latest/download/cora-x86_64-unknown-linux-gnu.tar.gz | tar xz - sudo mv cora /usr/local/bin/ + run: curl -fsSL https://raw.githubusercontent.com/codecoradev/cora-cli/main/install.sh | sh ``` ### GitLab CI diff --git a/install.sh b/install.sh index d0f53d17..63f95a83 100755 --- a/install.sh +++ b/install.sh @@ -100,14 +100,35 @@ install() { TEMP_DIR=$(mktemp -d) ARCHIVE="${TEMP_DIR}/${ARCHIVE_NAME}" + CHECKSUMS_URL="https://github.com/${REPO}/releases/download/${VERSION}/checksums-sha256.txt" + CHECKSUM_FILE="${TEMP_DIR}/checksums-sha256.txt" + info "Downloading from: $DOWNLOAD_URL" if ! curl -fsSL "$DOWNLOAD_URL" -o "$ARCHIVE"; then error "Failed to download ${ARCHIVE_NAME}" fi + # Verify SHA256 checksum (prevents MITM / corrupted download). + info "Downloading checksums..." + if curl -fsSL "$CHECKSUMS_URL" -o "$CHECKSUM_FILE"; then + info "Verifying SHA256 checksum..." + EXPECTED=$(grep -F "$ARCHIVE_NAME" "$CHECKSUM_FILE" | awk '{print $1}') + if [ -n "$EXPECTED" ]; then + ACTUAL=$(sha256sum "$ARCHIVE" | awk '{print $1}') + if [ "$ACTUAL" != "$EXPECTED" ]; then + error "Checksum mismatch! Expected: ${EXPECTED}, got: ${ACTUAL}" + fi + info "Checksum verified: $EXPECTED" + else + warn "Checksum for ${ARCHIVE_NAME} not found in checksums file — skipping verification" + fi + else + warn "Failed to download checksums — skipping verification" + fi + # Verify archive contents before extraction (CWE-22 path traversal). # Reject any entry with an absolute path or a ".." component. - info "Verifying archive..." + info "Verifying archive integrity..." if tar -tzf "$ARCHIVE" | grep -qE '^/|(^|/)\.\.(/|$)'; then error "Archive contains unsafe paths (absolute or directory traversal) — refusing to extract" fi