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
9 changes: 9 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# CODEOWNERS only takes effect when branch or ruleset settings require code-owner review.
/.github/workflows/ @Knownassa
/src-tauri/tauri.conf.json @Knownassa
/src-tauri/capabilities/ @Knownassa
/src-tauri/Cargo.toml @Knownassa
/src-tauri/Cargo.lock @Knownassa
/package.json @Knownassa
/bun.lock @Knownassa
/src/features/updater/ @Knownassa
1 change: 1 addition & 0 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
github: Knownassa
30 changes: 0 additions & 30 deletions .github/workflows/build.yml

This file was deleted.

10 changes: 7 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ name: CI

on:
push:
branches: [main]
pull_request:

permissions:
contents: read

jobs:
test:
strategy:
Expand All @@ -12,19 +16,19 @@ jobs:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
# Pin actions to full commit SHAs before merging this workflow.
- uses: actions/checkout@v7
- uses: oven-sh/setup-bun@v2
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- run: bun install --frozen-lockfile
- run: bun run typecheck
- run: bun run lint
- run: bun run test
- run: bun run build
- working-directory: src-tauri
run: cargo fmt --check
- working-directory: src-tauri
run: cargo test
- working-directory: src-tauri
run: cargo clippy -- -D warnings
run: cargo clippy --all-targets
105 changes: 105 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
name: Draft signed release

on:
push:
tags:
- "v*.*.*"

permissions:
contents: read

concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false

jobs:
publish-tauri:
name: ${{ matrix.name }}
runs-on: ${{ matrix.os }}
environment: production-release
permissions:
contents: write
strategy:
fail-fast: false
matrix:
include:
- name: Linux x86_64
os: ubuntu-22.04
rust_target: ""
args: ""
- name: Windows x86_64
os: windows-latest
rust_target: ""
args: ""
- name: macOS x86_64
os: macos-latest
rust_target: x86_64-apple-darwin
args: --target x86_64-apple-darwin
- name: macOS aarch64
os: macos-latest
rust_target: aarch64-apple-darwin
args: --target aarch64-apple-darwin
steps:
# Pin every third-party action below to a reviewed full commit SHA before merging.
- uses: actions/checkout@v7
- uses: oven-sh/setup-bun@v2
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.rust_target }}
- uses: swatinem/rust-cache@v2
with:
workspaces: ./src-tauri -> target
- name: Install Linux dependencies
if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
- run: bun install --frozen-lockfile
- name: Build, sign, and upload draft assets
uses: tauri-apps/tauri-action@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
with:
tagName: ${{ github.ref_name }}
releaseName: Fontsequal ${{ github.ref_name }}
releaseDraft: true
prerelease: false
args: ${{ matrix.args }}
- name: Verify generated updater signatures
shell: bash
run: |
set -euo pipefail
count=0
while IFS= read -r -d '' artifact; do
count=$((count + 1))
test -s "${artifact}.sig"
done < <(find src-tauri/target -type f \( -name '*.AppImage' -o -name '*.app.tar.gz' -o -name '*.exe' -o -name '*.msi' \) -print0)
test "$count" -gt 0

verify-release-metadata:
needs: publish-tauri
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Validate the draft updater metadata
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ github.ref_name }}
run: |
set -euo pipefail
mkdir release-metadata
gh release download "$TAG" --pattern latest.json --dir release-metadata --clobber
latest_json=release-metadata/latest.json
test -s "$latest_json"
version="${TAG#v}"
jq -e --arg version "$version" '.version == $version' "$latest_json"
prefix="https://github.com/Knownassa/Fontsequal/releases/download/${TAG}/"
for platform in linux-x86_64 windows-x86_64 darwin-x86_64 darwin-aarch64; do
jq -e --arg platform "$platform" '
.platforms[$platform].signature | type == "string" and length > 0
' "$latest_json"
jq -e --arg platform "$platform" --arg prefix "$prefix" '
.platforms[$platform].url | type == "string" and startswith($prefix)
' "$latest_json"
done
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ node_modules
dist
dist-ssr
*.local
*.key
*.pem
.env
.env.*
.tmp

src-tauri/target
Expand Down
22 changes: 22 additions & 0 deletions SECURITY_RELEASE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Secure release operations

Only `@Knownassa`, or a maintainer explicitly delegated by that account, may approve the `production-release` environment and publish a Fontsequal release.

## Updater signing key

- Generate the Tauri updater key outside this repository and back up the private key in two separately protected, encrypted locations.
- Store `TAURI_SIGNING_PRIVATE_KEY` and its password only as `production-release` environment secrets. Never commit, log, email, chat, or provide the private key to an AI tool.
- The public key is allowed only in `src-tauri/tauri.conf.json`; do not fetch it at runtime.
- If the private key is suspected compromised, stop releases, revoke environment access, investigate affected draft releases, generate a replacement key, and plan a signed migration for already-installed versions. Do not publish a replacement casually: existing clients trust the current public key.

## GitHub protections

Configure GitHub rulesets to require pull requests, passing CI, and code-owner review for `main` and the files listed in `.github/CODEOWNERS`. Block force-pushes and deletion.

Restrict creation, updates, and deletion of `v*` tags to release maintainers. Configure `production-release` to permit protected tags only, require a maintainer review, and prevent bypass where the GitHub plan supports it.

## Draft verification

The release workflow creates drafts only. Before publishing, verify every platform entry in `latest.json`, the release version and notes, non-empty signatures, and the matching uploaded files. Treat release assets as immutable once published.

All workflow actions are currently referenced by maintained version tags. Before merging a release workflow change, replace every third-party action reference with a reviewed full commit SHA and document the reviewed source.
13 changes: 9 additions & 4 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions docs/UPDATER_TESTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Updater test checklist

Run these checks against a private draft release or a dedicated test endpoint, never the stable public release.

| Scenario | Expected result |
| --- | --- |
| Valid signed update | The app shows the version and notes, verifies the package, and updates only after the user chooses to do so. |
| Invalid signature | The update is rejected, no installer runs, and the app stays usable. |
| Modified artifact | Signature verification rejects the artifact. |
| Malformed `latest.json` | A safe metadata error is shown; nothing installs. |
| Missing platform entry | The static metadata is rejected; nothing installs. |
| GitHub unavailable | A safe connection error is shown and the app remains usable. |
| Interrupted download | The update fails safely and can be retried. |
| Same or older version | No downgrade or redundant update is offered. |
| Unsupported architecture | No update is offered for that target. |
| Corrupted installer | Verification or installation fails safely. |
| Relaunch failure | The installed update remains pending and a safe error is shown. |

Perform the valid-update and signature-rejection tests separately on Windows x86_64, Linux x86_64 AppImage, macOS Intel, and macOS Apple Silicon. Also verify that fonts, collections, favorites, and SQLite data survive the update.

The Bun tests in `src/features/updater/updater.service.test.ts` cover progress calculation, release-note normalization, and safe error handling. End-to-end installer and signature tests require real signed artifacts and remain manual.
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"build": "tsc --noEmit && vite build",
"typecheck": "tsc --noEmit",
"lint": "tsc --noEmit",
"test": "tsc --noEmit"
"test": "bun test"
},
"dependencies": {
"@hugeicons/core-free-icons": "^4.2.2",
Expand All @@ -30,6 +30,8 @@
"@tanstack/react-query": "^5.101.2",
"@tanstack/react-router": "^1.170.17",
"@tauri-apps/api": "^2.0.0",
"@tauri-apps/plugin-process": "~2",
"@tauri-apps/plugin-updater": "~2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.468.0",
Expand All @@ -46,6 +48,7 @@
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "^10.4.20",
"bun-types": "^1.3.14",
"postcss": "^8.4.49",
"tailwindcss": "^3.4.17",
"tailwindcss-animate": "^1.0.7",
Expand Down
Loading
Loading