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
110 changes: 110 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
name: Release

on:
push:
tags:
- "v*.*.*"
workflow_dispatch:
inputs:
tag:
description: "Existing release tag to publish, for example v0.1.0"
required: true
type: string
draft:
description: "Create the GitHub release as a draft"
required: true
default: true
type: boolean
prerelease:
description: "Mark the GitHub release as a prerelease"
required: true
default: false
type: boolean

permissions:
contents: write

concurrency:
group: release-${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }}
cancel-in-progress: false

env:
CARGO_TERM_COLOR: always
RELEASE_TAG: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }}
RELEASE_DRAFT: ${{ github.event_name == 'workflow_dispatch' && format('{0}', inputs.draft) || 'false' }}
RELEASE_PRERELEASE: ${{ github.event_name == 'workflow_dispatch' && format('{0}', inputs.prerelease) || format('{0}', contains(github.ref_name, '-')) }}

jobs:
publish-tauri:
name: Build ${{ matrix.name }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- name: Windows
os: windows-latest
rust_targets: ""
args: ""
- name: Linux
os: ubuntu-22.04
rust_targets: ""
args: ""
- name: macOS arm64
os: macos-latest
rust_targets: aarch64-apple-darwin,x86_64-apple-darwin
args: --target aarch64-apple-darwin
- name: macOS x64
os: macos-latest
rust_targets: aarch64-apple-darwin,x86_64-apple-darwin
args: --target x86_64-apple-darwin

steps:
- name: Checkout release tag
uses: actions/checkout@v4
with:
ref: ${{ env.RELEASE_TAG }}
fetch-depth: 0

- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.rust_targets }}

- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
cache-dependency-path: inspectra-gui/package-lock.json

- name: Install Linux dependencies
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y \
libgtk-3-dev \
libwebkit2gtk-4.0-dev \
libayatana-appindicator3-dev \
librsvg2-dev \
patchelf

- name: Install frontend dependencies
working-directory: inspectra-gui
run: npm ci

- name: Build and publish desktop bundles
uses: tauri-apps/tauri-action@v0.6.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
projectPath: inspectra-gui
args: ${{ matrix.args }}
tagName: ${{ env.RELEASE_TAG }}
releaseName: Inspectra ${{ env.RELEASE_TAG }}
releaseBody: |
Automated Inspectra desktop release.

Built from `${{ github.sha }}`. See `CHANGELOG.md` for release notes.
releaseDraft: ${{ env.RELEASE_DRAFT }}
prerelease: ${{ env.RELEASE_PRERELEASE }}
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ if chrome:
- **[API Documentation](docs/API.md)**: Complete API reference
- **[Architecture](docs/ARCHITECTURE.md)**: System design and architecture
- **[Development Guide](docs/DEVELOPMENT.md)**: Contributing and development workflow
- **[Release Process](docs/RELEASE.md)**: Versioning, installers, and GitHub Releases
- **[Roadmap](docs/ROADMAP.md)**: Future plans and features

## 🏗️ Project Structure
Expand Down Expand Up @@ -144,6 +145,14 @@ pip install maturin
maturin develop
```

### Desktop Releases

Inspectra desktop releases are built automatically by GitHub Actions when a
`vX.Y.Z` tag is pushed. The release workflow builds the Tauri app for Windows,
Linux and macOS and uploads the generated bundles to GitHub Releases.

See [docs/RELEASE.md](docs/RELEASE.md) for the full release procedure.

## 🧪 Examples

### List Processes
Expand Down Expand Up @@ -251,4 +260,3 @@ If you find Inspectra useful, please consider:
For private security reports and sensitive issues: kevin.gregoire@nodasys.com

For general issues, use GitHub Issues once the repository is public.

89 changes: 89 additions & 0 deletions docs/RELEASE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Release Process

Inspectra publishes desktop builds through GitHub Actions and GitHub Releases.

The release workflow builds the Tauri desktop app on Windows, Linux and macOS,
then uploads the generated bundles to the matching GitHub Release.

## Release artifacts

The workflow is triggered by tags named `vX.Y.Z`, for example `v0.2.0`.

Expected artifacts:

- Windows installer/bundle generated by Tauri.
- Linux bundles generated by Tauri, usually AppImage and Debian packages.
- macOS bundles generated by Tauri for Apple Silicon and Intel, usually
DMG/app archives.

Windows and macOS artifacts are currently unsigned. This is acceptable for early
internal releases, but public production releases should add Authenticode
signing on Windows and Apple Developer ID signing/notarization on macOS.

## Prepare a release

Run the version preparation script from the repository root:

```powershell
node scripts/prepare-release.mjs 0.2.0
```

The script synchronizes the version in:

- `Cargo.toml`
- `inspectra-gui/src-tauri/Cargo.toml`
- `inspectra-gui/src-tauri/tauri.conf.json`
- `inspectra-gui/package.json`
- `inspectra-gui/package-lock.json`
- `bindings/python/pyproject.toml`

Review and test locally:

```powershell
cargo test -p inspectra-core
cd inspectra-gui
npm ci
npm run build
cd ..
```

Commit, tag and push:

```powershell
git add Cargo.toml inspectra-gui bindings/python/pyproject.toml
git commit -m "Release v0.2.0"
git tag v0.2.0
git push origin main v0.2.0
```

Pushing the tag starts `.github/workflows/release.yml`. For normal tags, the
release is published automatically. Tags containing a prerelease suffix such as
`v0.2.0-beta.1` are marked as prereleases.

## Manual release dispatch

If a tag already exists and the release needs to be rebuilt, run the `Release`
workflow manually from GitHub Actions and provide the existing tag. Manual runs
default to draft releases so artifacts can be inspected before publishing.

## GitHub settings

The repository must allow GitHub Actions to write releases:

1. Open repository settings.
2. Go to `Actions` > `General`.
3. Set workflow permissions to `Read and write permissions`.

## Future auto-updates

The next professional step is to enable the Tauri updater after the release
pipeline is stable:

- Generate and protect the Tauri updater signing key.
- Store the private key and password in GitHub Actions secrets.
- Add the updater config to `inspectra-gui/src-tauri/tauri.conf.json`.
- Serve update metadata from either GitHub Releases or a dedicated endpoint such
as `https://updates.nodasys.com/inspectra/{{target}}/{{current_version}}`.

GitHub Releases are enough for the first phase. A dedicated update server can be
added later without changing the release tag discipline.
129 changes: 129 additions & 0 deletions scripts/prepare-release.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
#!/usr/bin/env node
import { readFileSync, writeFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";

const args = process.argv.slice(2);
const dryRun = args.includes("--dry-run");
const versionArg = args.find((arg) => !arg.startsWith("-"));

if (!versionArg) {
console.error("Usage: node scripts/prepare-release.mjs <version> [--dry-run]");
console.error("Example: node scripts/prepare-release.mjs 0.2.0");
process.exit(1);
}

const version = versionArg.replace(/^v/, "");
const tag = `v${version}`;
const semver =
/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;

if (!semver.test(version)) {
console.error(`Invalid semantic version: ${versionArg}`);
console.error("Expected a version like 0.2.0 or v0.2.0-beta.1.");
process.exit(1);
}

const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const changes = [];

function readProjectFile(path) {
return readFileSync(resolve(root, path), "utf8");
}

function writeProjectFile(path, contents) {
if (!dryRun) {
writeFileSync(resolve(root, path), contents);
}
}

function record(path, changed) {
changes.push({ path, changed });
}

function updateJson(path, mutate) {
const before = readProjectFile(path);
const data = JSON.parse(before);
const beforeData = JSON.stringify(data);
mutate(data);
const hasDataChanged = beforeData !== JSON.stringify(data);
const after = `${JSON.stringify(data, null, 2)}\n`;
record(path, hasDataChanged);
if (hasDataChanged) {
writeProjectFile(path, after);
}
}

function replaceOnce(path, pattern, replacement, label) {
const before = readProjectFile(path);
const matches = before.match(pattern);
if (!matches) {
throw new Error(`Could not find ${label} in ${path}`);
}
const after = before.replace(pattern, replacement);
record(path, before !== after);
if (before !== after) {
writeProjectFile(path, after);
}
}

replaceOnce(
"Cargo.toml",
/(\[workspace\.package\][\s\S]*?version\s*=\s*")[^"]+(")/,
`$1${version}$2`,
"workspace package version",
);

replaceOnce(
"inspectra-gui/src-tauri/Cargo.toml",
/(^version\s*=\s*")[^"]+(")/m,
`$1${version}$2`,
"Tauri package version",
);

replaceOnce(
"bindings/python/pyproject.toml",
/(\[project\][\s\S]*?version\s*=\s*")[^"]+(")/,
`$1${version}$2`,
"Python package version",
);

updateJson("inspectra-gui/src-tauri/tauri.conf.json", (data) => {
data.package.version = version;
});

updateJson("inspectra-gui/package.json", (data) => {
data.version = version;
});

updateJson("inspectra-gui/package-lock.json", (data) => {
data.version = version;
if (data.packages?.[""]) {
data.packages[""].version = version;
}
});

const changed = changes.filter((entry) => entry.changed);
const unchanged = changes.filter((entry) => !entry.changed);

console.log(`${dryRun ? "Validated" : "Prepared"} Inspectra ${tag}.`);

if (changed.length > 0) {
console.log("\nUpdated files:");
for (const entry of changed) {
console.log(`- ${entry.path}`);
}
}

if (unchanged.length > 0) {
console.log("\nAlready up to date:");
for (const entry of unchanged) {
console.log(`- ${entry.path}`);
}
}

console.log("\nNext release commands:");
console.log("git add Cargo.toml inspectra-gui bindings/python/pyproject.toml");
console.log(`git commit -m "Release ${tag}"`);
console.log(`git tag ${tag}`);
console.log(`git push origin main ${tag}`);
Loading