Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
221 changes: 69 additions & 152 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,219 +1,136 @@
# PawGate - Release Workflow
# WHY: Automated releases on version tags ensure consistent, reproducible builds
# and eliminate manual upload errors. Creates GitHub releases with auto-generated
# changelog from commit history.
# PawGate Python - Release Workflow
# Creates GitHub releases with Windows executable on version tags
#
# Triggers:
# - Tags matching v*.*.* pattern (e.g., v1.0.0, v2.3.1-beta)
#
# Jobs:
# 1. build: Create production Windows executable
# 2. release: Create GitHub release and upload executable as asset
# - Tags: v*.*.* (e.g., v1.0.0, v2.1.0-beta)
# - Manual: workflow_dispatch
#
# Usage:
# git tag -a v1.2.3 -m "Release v1.2.3: Add awesome feature"
# git push origin v1.2.3
# git tag v1.0.0 && git push origin v1.0.0

name: Release
name: Python Release

on:
push:
tags:
- 'v*.*.*' # WHY: Semantic versioning pattern (v1.0.0, v2.1.3, etc.)
- 'v[0-9]+.[0-9]+.[0-9]+*'
workflow_dispatch:
inputs:
tag:
description: 'Tag to release (e.g., v1.0.0)'
description: 'Version tag (e.g., v1.0.0)'
required: true
type: string

permissions:
contents: write

jobs:
build:
name: Build Release Executable
name: Build Windows Executable
runs-on: windows-latest

steps:
- name: Checkout Code
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0 # WHY: Full history for changelog generation

- name: Extract Version from Tag
- name: Get Version
id: version
shell: pwsh
run: |
if ($env:GITHUB_EVENT_NAME -eq "workflow_dispatch") {
if ("${{ github.event_name }}" -eq "workflow_dispatch") {
$tag = "${{ github.event.inputs.tag }}"
} else {
$tag = $env:GITHUB_REF -replace 'refs/tags/', ''
}
$version = $tag -replace '^v', ''
$ver = $tag -replace '^v', ''
echo "tag=$tag" >> $env:GITHUB_OUTPUT
echo "version=$version" >> $env:GITHUB_OUTPUT
echo "Building version: $version"
echo "version=$ver" >> $env:GITHUB_OUTPUT

- name: Set up Python 3.12
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'
cache-dependency-path: requirements.txt

- name: Install Dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pyinstaller

- name: Build Production Executable
# WHY: --clean ensures no dev artifacts leak into production build
run: |
pyinstaller --clean PawGate.spec
- name: Build Executable
run: pyinstaller --clean PawGate.spec

- name: Verify Build and Create Checksum
- name: Create Release Package
shell: pwsh
run: |
if (-not (Test-Path "dist/PawGate.exe")) {
Write-Error "Build failed: PawGate.exe not found"
exit 1
}
$ver = "${{ steps.version.outputs.version }}"

$size = (Get-Item "dist/PawGate.exe").Length / 1MB
Write-Host "Build successful: $([math]::Round($size, 2)) MB"
# Create release directory
New-Item -ItemType Directory -Path "release" -Force

# WHY: SHA256 checksum for user verification and security
$hash = (Get-FileHash "dist/PawGate.exe" -Algorithm SHA256).Hash
$hash | Out-File -FilePath "dist/PawGate.exe.sha256" -Encoding ascii
Write-Host "SHA256: $hash"

- name: Rename Executable with Version
shell: pwsh
run: |
$version = "${{ steps.version.outputs.version }}"
Copy-Item "dist/PawGate.exe" "dist/PawGate-$version.exe"
Copy-Item "dist/PawGate.exe.sha256" "dist/PawGate-$version.exe.sha256"
# Copy executable
Copy-Item "dist/PawGate.exe" "release/PawGate.exe"

- name: Upload Build Artifacts
uses: actions/upload-artifact@v4
with:
name: release-build
path: |
dist/PawGate-${{ steps.version.outputs.version }}.exe
dist/PawGate-${{ steps.version.outputs.version }}.exe.sha256
retention-days: 90
if-no-files-found: error

release:
name: Create GitHub Release
runs-on: windows-latest
needs: build
permissions:
contents: write # WHY: Required to create releases and upload assets
# Generate SHA256 checksum
$hash = (Get-FileHash "release/PawGate.exe" -Algorithm SHA256).Hash
"$hash PawGate.exe" | Out-File "release/SHA256SUMS.txt" -Encoding ascii

steps:
- name: Checkout Code
uses: actions/checkout@v4
with:
fetch-depth: 0
# Create ZIP with version
Compress-Archive -Path "release/*" -DestinationPath "PawGate-v$ver-python-win64.zip"

- name: Extract Version from Tag
id: version
shell: pwsh
run: |
if ($env:GITHUB_EVENT_NAME -eq "workflow_dispatch") {
$tag = "${{ github.event.inputs.tag }}"
} else {
$tag = $env:GITHUB_REF -replace 'refs/tags/', ''
}
$version = $tag -replace '^v', ''
echo "tag=$tag" >> $env:GITHUB_OUTPUT
echo "version=$version" >> $env:GITHUB_OUTPUT
# Show results
Write-Host "Package contents:"
Get-ChildItem release/
Write-Host "`nZIP created: PawGate-v$ver-python-win64.zip"
Write-Host "SHA256: $hash"

- name: Download Build Artifacts
uses: actions/download-artifact@v4
- name: Create Release
uses: softprops/action-gh-release@v2
with:
name: release-build
path: dist/

- name: Generate Release Notes
id: release_notes
shell: pwsh
run: |
$tag = "${{ steps.version.outputs.tag }}"
$version = "${{ steps.version.outputs.version }}"
tag_name: ${{ steps.version.outputs.tag }}
name: "PawGate ${{ steps.version.outputs.version }} (Python)"
draft: false
prerelease: ${{ contains(steps.version.outputs.tag, '-') }}
body: |
## PawGate ${{ steps.version.outputs.version }} - Python Edition

# WHY: Get previous tag for changelog generation
$prevTag = git describe --tags --abbrev=0 "$tag^" 2>$null
if (-not $prevTag) {
$prevTag = git rev-list --max-parents=0 HEAD
}
Windows keyboard locker to protect against pets walking on your keyboard.

# Generate changelog from commits
$commits = git log "$prevTag..$tag" --pretty=format:"- %s (%h)" --no-merges
---

# Create release body
$body = @"
## What's Changed in $version
### Quick Start

$commits
1. Download `PawGate-v${{ steps.version.outputs.version }}-python-win64.zip`
2. Extract and run `PawGate.exe`
3. Look for the paw icon in your system tray

## Installation
### Default Hotkey

1. Download ``PawGate-$version.exe`` below
2. (Optional) Verify the SHA256 checksum against ``PawGate-$version.exe.sha256``
3. Run the executable - no installation required!
4. Right-click the system tray icon to configure settings
| Action | Keys |
|--------|------|
| **Lock/Unlock** | `Ctrl + B` |

## Requirements
### Features

- Windows 10/11 (64-bit)
- Webcam access permission
- .NET Framework 4.7.2+ (usually pre-installed)
- Single hotkey toggle for lock/unlock
- Semi-transparent overlay when locked
- Blocks all keys including numpad, function keys, media keys
- System tray icon with settings menu
- Configurable hotkey, opacity, and overlay color

## Security Note
### System Requirements

This is an unsigned executable. Windows SmartScreen may show a warning on first run.
This is expected for open-source projects without code signing certificates.
- Windows 10/11 (64-bit)
- No installation required - portable executable

**SHA256 Checksum:**
``````
$(Get-Content "dist/PawGate-$version.exe.sha256")
``````
### Verification

---
Verify download integrity with SHA256 checksum in `SHA256SUMS.txt`

**Full Changelog**: https://github.com/${{ github.repository }}/compare/$prevTag...$tag
"@
---

# WHY: Save to file to avoid PowerShell multi-line variable issues
$body | Out-File -FilePath "release_notes.md" -Encoding utf8
> **Note**: Windows may show a SmartScreen warning for unsigned executables.
> This is normal for open-source software without code signing certificates.

- name: Create GitHub Release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ steps.version.outputs.tag }}
name: PawGate ${{ steps.version.outputs.version }}
body_path: release_notes.md
draft: false
prerelease: ${{ contains(steps.version.outputs.tag, 'alpha') || contains(steps.version.outputs.tag, 'beta') || contains(steps.version.outputs.tag, 'rc') }}
files: |
dist/PawGate-${{ steps.version.outputs.version }}.exe
dist/PawGate-${{ steps.version.outputs.version }}.exe.sha256
PawGate-v${{ steps.version.outputs.version }}-python-win64.zip
fail_on_unmatched_files: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Release Summary
shell: pwsh
run: |
$version = "${{ steps.version.outputs.version }}"
echo "### Release Created Successfully" >> $env:GITHUB_STEP_SUMMARY
echo "" >> $env:GITHUB_STEP_SUMMARY
echo "**Version:** $version" >> $env:GITHUB_STEP_SUMMARY
echo "**Tag:** ${{ steps.version.outputs.tag }}" >> $env:GITHUB_STEP_SUMMARY
echo "" >> $env:GITHUB_STEP_SUMMARY
echo "**Assets:**" >> $env:GITHUB_STEP_SUMMARY
echo "- \`PawGate-$version.exe\`" >> $env:GITHUB_STEP_SUMMARY
echo "- \`PawGate-$version.exe.sha256\`" >> $env:GITHUB_STEP_SUMMARY
echo "" >> $env:GITHUB_STEP_SUMMARY
echo "View the release: https://github.com/${{ github.repository }}/releases/tag/${{ steps.version.outputs.tag }}" >> $env:GITHUB_STEP_SUMMARY
109 changes: 109 additions & 0 deletions .github/workflows/rust-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# CI workflow for PawGate Rust implementation
# Runs on every push and PR to verify the build

name: Rust CI

on:
push:
branches: [main, master]
paths:
- 'pawgate-rs/**'
- '.github/workflows/rust-ci.yml'
pull_request:
branches: [main, master]
paths:
- 'pawgate-rs/**'
- '.github/workflows/rust-ci.yml'

env:
CARGO_TERM_COLOR: always

jobs:
# Check formatting
fmt:
name: Format Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt

- name: Check formatting
working-directory: pawgate-rs
run: cargo fmt --all -- --check

# Run clippy lints
clippy:
name: Clippy Lints
runs-on: windows-latest
steps:
- uses: actions/checkout@v4

- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
components: clippy

- name: Cache cargo
uses: Swatinem/rust-cache@v2
with:
workspaces: pawgate-rs

- name: Run clippy
working-directory: pawgate-rs
run: cargo clippy --all-targets -- -D warnings

# Build release binary
build:
name: Build (Windows)
runs-on: windows-latest
steps:
- uses: actions/checkout@v4

- name: Install Rust
uses: dtolnay/rust-toolchain@stable

- name: Cache cargo
uses: Swatinem/rust-cache@v2
with:
workspaces: pawgate-rs

- name: Build release
working-directory: pawgate-rs
run: cargo build --release

- name: Show binary info
working-directory: pawgate-rs
run: |
dir target\release\pawgate.exe
# Show file size
(Get-Item target\release\pawgate.exe).Length / 1MB | ForEach-Object { "Binary size: {0:N2} MB" -f $_ }

- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: pawgate-windows
path: pawgate-rs/target/release/pawgate.exe
if-no-files-found: error

# Run tests (when we have them)
test:
name: Tests
runs-on: windows-latest
steps:
- uses: actions/checkout@v4

- name: Install Rust
uses: dtolnay/rust-toolchain@stable

- name: Cache cargo
uses: Swatinem/rust-cache@v2
with:
workspaces: pawgate-rs

- name: Run tests
working-directory: pawgate-rs
run: cargo test --all
Loading
Loading