Developing Sevak¶
Layout¶
Cargo.toml workspace: crates/* and src-tauri
crates/sevak-core config, theme (appearance settings to CSS), theme files and gallery checks, fuzzy matcher, search engine,
usage stats, Plugin trait
crates/sevak-platform OS access: launching, icons, app scanning, clipboard, pasting into the
previous app, capturing the selection (Universal Actions), moving to the trash,
whole-disk search through the OS file index (os_search), watching typed
keywords and replacing them (snippet expansion), paths, terminal
launching, hotkey strategy, GNOME
shortcut setup, the Windows keyboard hook that takes Win+Space
(hotkey_hook), the macOS Spotlight shortcut (spotlight)
(Windows, macOS and Linux backends)
crates/sevak-extension-sdk
the SDK for writing extensions in Rust (publishable; only serde and serde_json)
crates/sevak-ext the `sevak-ext` tool: init, validate, pack, entry for extension authors
crates/sevak-plugins built-in plugins: apps, calculator (+ units, currency), files, bookmarks,
web search, system commands, automation tasks, media controls, shell,
clipboard history, snippets, emoji picker (data/emoji.tsv), uuid example;
the file buffer (collect files, act on all); the script plugin host
(external plugins); workflows (graph engine, runtime, gallery)
crates/sevak-backup settings backup and restore: the .sevakbackup archive, the allowlist of what
may go in, validation of hostile archives, merge / replace, safety snapshot
and undo, automatic backups (no Tauri code; see docs/backup-and-restore.md)
src-tauri the Tauri shell: window, hotkeys (main, Universal Actions + [[hotkey]] entries,
takeover of Win+Space / Cmd+Space / Super+Space), tray, CLI,
--query / --run handling (direct.rs), script plugin approval, IPC
commands, bundling config
ui Svelte 5 + Vite frontend (builds to ui/dist)
packaging/linux desktop-entry template used by the .deb and .rpm
scripts icon generator, emoji list generator, gallery-check.mjs (gallery hashes), WSL Linux test runner
docs plugins, install, development
examples/plugins example script plugins (Python, PowerShell, Node)
examples/workflows example workflows (also packaged for the gallery)
examples/rust-hello example native extension in Rust (a workspace member: built and tested in CI)
templates/rust-extension
the project template for `sevak-ext init` / `cargo generate`
gallery the opt-in online galleries: index.json and packages/*.zip (workflows and
script plugins), extensions/<id>/*.sevakext (native extensions),
themes.json and themes/*.toml (themes)
.github/workflows ci.yml, release.yml, promote.yml, rollback.yml, sbom.yml, attest.yml, security.yml,
codeql.yml, toolchain-canary.yml, coverage.yml, bench.yml, docs.yml, release-checks.yml,
pr-labels.yml (.github/actions/setup-rust installs the toolchain pinned in rust-toolchain.toml)
Architecture¶
ui (Svelte) <--- Tauri IPC ---> src-tauri (shell)
| owns window, tray, hotkey, CLI, single-instance
v
sevak-core::SearchEngine
| routes a query to plugins, ranks, applies usage
v
sevak-plugins (Plugin impls)
| execute -> actions
v
sevak-platform::PlatformProvider
Windows / macOS / Linux implementations
sevak-core and sevak-plugins are platform independent; only
sevak-platform contains the OS-specific backends. How a
query flows through plugins and how to add one is in plugins.md.
Running¶
Use Rust 1.95+, Node.js 22+, and the native dependencies for your platform:
| Platform | Native prerequisites |
|---|---|
| Windows | Microsoft C++ Build Tools with the Desktop development with C++ workload; WebView2 runtime |
| macOS | Xcode Command Line Tools (xcode-select --install) |
| Ubuntu / Debian | WebKitGTK 4.1, AppIndicator and the development packages below |
| Fedora | WebKitGTK 4.1, AppIndicator and the development packages below |
The Linux commands mirror this repository's CI setup. On Ubuntu / Debian:
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
build-essential curl file patchelf \
libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev \
libxdo-dev libssl-dev
On Fedora:
sudo dnf install -y \
git tar gzip xz curl file gcc gcc-c++ make patchelf pkgconf-pkg-config \
webkit2gtk4.1-devel libayatana-appindicator-gtk3-devel \
openssl-devel librsvg2-devel libxdo-devel
See Tauri's prerequisites for platform setup details. Then run from the repository root:
npm ci
npm run tauri dev # shell + Vite dev server (hot reload on the UI)
npm run check # svelte-check
npm run build # build the UI into ui/dist
npm run icons # regenerate src-tauri/icons (scripts/generate-icons.mjs)
npm run test:scripts # checks on the Windows installer sources and package manifests
A plain cargo build --release -p sevak needs --features custom-protocol to
embed ui/dist; tauri build enables it for you.
Tests and lint¶
CI runs these checks on Windows, macOS and Ubuntu 22.04. A Fedora container also builds and tests the Rust workspace:
cargo fmt --all --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace
npm run check
npm run gallery:check # gallery checksums, themes and tags
npm run test:extensions # Extension behavior and script entry points (Node.js 22+)
npm run build
npm run test:scripts
node scripts/generate-third-party-notices.mjs --check # THIRD_PARTY_NOTICES.md is current (Linux job)
Further commands (what each layer covers, and the manual release checklist, are in Testing):
npm test # UI unit tests (Vitest); npm run test:watch to keep them running
npm run test:coverage # the same with a coverage report in ui/coverage/
cargo test --profile fast-release -p sevak-plugins --test latency -- --nocapture
# search latency budget (skips itself in a debug build)
cargo bench -p sevak-plugins # search latency and startup benchmark (criterion)
Start-at-sign-in integration¶
general.launch_at_login is the persisted opt-in. The platform startup layer
owns each user's OS registration: HKCU\Software\Microsoft\Windows\CurrentVersion\Run
on Windows, ~/Library/LaunchAgents/Sevak.plist on macOS, and
$XDG_CONFIG_HOME/autostart/Sevak.desktop (normally
~/.config/autostart/Sevak.desktop) on Linux. Entries pass --background so
sign-in never opens the launcher. AppImages register their stable file path.
Settings returns both the saved preference and a startup status object
(registered, enabled, error). Do not infer successful registration from
the preference alone: OS restrictions or an external disable can prevent
startup. An explicit off-to-on change may re-enable an entry. Routine startup,
index reload and unrelated saves preserve an external disable, while repairing
a missing entry for a saved opt-in and refreshing an enabled entry's executable
path after an install moves.
Linux Hidden=true overrides are treated as disabled even when the desktop
manager leaves only a minimal [Desktop Entry] group without Exec. Windows
also recognizes legacy unquoted commands from the old autostart plugin for
owned-entry cleanup. If the old plugin registered this executable under HKLM,
status and mutation return an actionable migration error; this per-user backend
never deletes machine-wide registry values. See
legacy Windows startup.
The Windows installers use Sevak's headless helpers to keep their checkbox and Settings on the same path:
sevak --set-startup on/sevak --set-startup off: update only the startup preference and its OS registration, retaining the rest ofconfig.toml.sevak --startup-status: exit code0when enabled,1when disabled,2when status cannot be read.sevak --refresh-startup: update only an existing entry's executable path, retaining its arguments, custom config path and disabled state. It does not create a missing entry or writegeneral.launch_at_login.sevak --remove-startup: remove only an entry owned by this executable, for uninstall cleanup.
These commands do not open the launcher. Never run a per-user startup action as LocalSystem on behalf of all users. The backend snapshots registration and rolls back if the OS update or config persistence fails; Settings must surface the failure and must not display Saved. Ordinary application startup logs a registration problem without preventing the launcher from running.
Focused tests:
cargo test -p sevak-platform startup
cargo test -p sevak-platform --test startup
cargo test -p sevak cli::tests::startup
npm test -- Settings.startup.test.ts
npm run test:scripts
The UI integration tests exercise the real Settings component and IPC wrappers with only the Tauri bridge mocked. Native startup tests use isolated storage and fake OS operations; they must not change the developer's real login items. Installer source checks and these tests do not establish that a real desktop will restart Sevak after sign-in. Follow the dedicated cases in Testing on each supported OS before release.
Coverage of the Rust crates uses cargo-llvm-cov (cargo install --locked
cargo-llvm-cov, rustup component add llvm-tools-preview); the exact command and
the current numbers are in Testing. CI runs npm test and the
latency budget on Ubuntu on every push, and the Coverage workflow (pull requests,
weekly) and Benchmark workflow (weekly, manual) publish their tables in the job
summary. Neither gates a merge.
Linux code from a Windows machine¶
Type-check the Linux backend without a Linux toolchain:
rustup target add x86_64-unknown-linux-gnu
cargo clippy -p sevak-platform --target x86_64-unknown-linux-gnu
The macOS backend can be type-checked the same way (the crates it uses are pure Rust, so no Apple SDK is needed to check, only to build and run):
Run the Linux tests inside WSL (no sudo; bootstraps Rust and Zig as a linker
under $HOME):
wsl -d Ubuntu -- bash /mnt/d/PProjects/Sevak/scripts/wsl-linux-test.sh
wsl -d Ubuntu -- bash /mnt/d/PProjects/Sevak/scripts/wsl-linux-test.sh clippy -p sevak-platform --all-targets
See the header of scripts/wsl-linux-test.sh for options. The Tauri shell
itself (GTK/WebKit) cannot be built that way; CI covers it.
Packaging¶
Bundle settings are the bundle object in src-tauri/tauri.conf.json:
targetsisall, meaning every format the current OS can build. To pick:- Windows:
npx tauri build --bundles nsis,msi - macOS:
npx tauri build --bundles app,dmg(add--target universal-apple-darwinfor an Apple silicon + Intel build, afterrustup target add aarch64-apple-darwin x86_64-apple-darwin) - Linux:
npx tauri build --bundles deb,rpm,appimage - Output goes to
target/release/bundle/<format>/. - Windows: the NSIS installer is Sevak's own (a customised copy of Tauri's
template: it installs per user without administrator rights, or per PC after a
UAC prompt; see The Windows installer). The first
build downloads the NSIS and WiX toolsets from their official GitHub releases.
The MSI
upgradeCodemust never change. - Linux: the
.desktopfile comes frompackaging/linux/sevak.desktop(Categories, Keywords and a "Show or hide" action). Extra runtime dependencies (libayatana-appindicator, glib tools) are declared inbundle.linux.deb.dependsandbundle.linux.rpm.depends. - macOS: the app runs as a menu-bar (accessory) app with no Dock icon. It is
ad-hoc signed (
bundle.macOS.signingIdentity: "-"), not notarized.app.macOSPrivateApi(and Tauri'smacos-private-apifeature) give the launcher its transparent window. - macOS:
src-tauri/Info.plistis merged into the app'sInfo.plistby Tauri. It holdsNSContactsUsageDescription; macOS ends an app that reads Contacts without it, so keep it while the contacts plugin uses the Contacts framework. - Installers are not code-signed yet.
bundle.resourcesinstallsLICENSEandTHIRD_PARTY_NOTICES.mdwith the app.THIRD_PARTY_NOTICES.mdis generated: runnode scripts/generate-third-party-notices.mjsafter any dependency change (see Supply chain).- Once a stable release is published,
.github/workflows/sbom.ymlattaches the CycloneDX SBOMs and.github/workflows/attest.ymlcreates the build-provenance attestations.release.ymlandpromote.ymlcall them (a release published withGITHUB_TOKENdoes not trigger them by itself); see Supply chain. crates/sevak-plugins/data/wordnet-en.zis the bundled dictionary (about 2.7 MB), generated from Princeton WordNet 3.0 byscripts/build-dictionary.py(python scripts/build-dictionary.py <path to WordNet-3.0>, Python 3, no dependencies). Regenerate it only to change what is kept; its licence notice is inside the file and inTHIRD_PARTY_NOTICES.md.
The Windows installer¶
Everything about the Windows installers is in src-tauri/installer/, wired up in
bundle.windows of src-tauri/tauri.conf.json:
| File | What it is |
|---|---|
installer.nsi |
Sevak's copy of Tauri's NSIS template (nsis.template): pages, install scope, upgrades, uninstall |
upstream/installer.nsi |
The unmodified Tauri template it was copied from, kept to diff and merge against |
English.nsh |
Every installer string (nsis.customLanguageFiles); the only language, no selector |
upstream/English.nsh |
The unmodified Tauri strings, same purpose |
main.wxs |
Sevak's copy of Tauri's WiX template (wix.template): wires in the MSI startup UI and orders startup helpers before automatic launch |
upstream/main.wxs |
The pristine WiX template, kept as the common ancestor for Tauri upgrades |
startup.wxs |
MSI startup checkbox, existing-state lookup, explicit silent properties, path refresh and owned-entry cleanup actions |
hooks.nsh |
NSIS_HOOK_PREINSTALL and NSIS_HOOK_PREUNINSTALL (nsis.installerHooks): ask a running Sevak to quit before files change |
sidebar.bmp, header.bmp |
NSIS Welcome/Finish artwork (164x314) and page header (150x57) |
wix-banner.bmp, wix-dialog.bmp |
The same artwork for the MSI (493x58, 493x312) |
Why a template copy. Tauri's installMode: "both" runs the installer
elevated (RequestExecutionLevel highest), so administrators get a UAC prompt
even for a per-user install, silent installs and the in-app updater default to
per-machine for them, and the install-mode page is not shown to anyone else.
Sevak wants per-user to need no administrator ever, silent installs and updates
to stay in the scope they are in, and elevation only when "all users" is chosen.
No option of Tauri's NSIS config does that. The branding itself (images, icon,
text) uses only config options, the language file and the hooks.
How it behaves (what users see is in Installing Sevak):
- The installer runs as the invoking user. The scope comes from
/ALLUSERSor/CURRENTUSER, otherwise from where Sevak is already installed (HKCUandHKLMSoftware\Microsoft\Windows\CurrentVersion\Uninstall\Sevak), otherwise per user. The in-app updater passes neither switch (/P /UPDATE /R), which is why an update stays where it is. - Per-machine needs elevation. A wizard run asks on the scope page and then starts
itself again with
ShellExecute runas(RelaunchElevated; the new copy carries/ELEVATEDand skips the pages already answered). Silent, passive and/ALLUSERSruns elevate at start-up. A refused prompt exits with 1223. - The uninstaller does the same, since
uninstall.exehas no administrator manifest: for a per-machine copy,un.onInitstarts it again elevated and quits. Both register the scope inUninstallString("...\uninstall.exe" /ALLUSERSor/CURRENTUSER). - Nothing is uninstalled before an upgrade; the new files go over the old ones. A
copy in the other scope is removed only when asked (the page, or
/UNINSTALLOTHER), by running its uninstaller with/S /MOVE;/MOVEkeeps the autostart entry and all data while removing shortcuts. An earlier MSI install is removed first, as in Tauri's template. - Switch names must not start with another switch's name: Tauri finds
/Rand/Pwith a substring search. - Custom pages never show in silent or passive mode; their decisions are switches.
- Startup remains per user for either scope. The EXE offers it before UAC and
carries the original desktop user's choice across elevation; an EXE started
already elevated disables the checkbox rather than guessing the account.
The MSI offers it on Finish.
/AUTOSTART=on|off(EXE) andSEVAK_AUTOSTART=on|off(MSI) make an explicit scripted choice; omission keeps the preference. Both call the same headless startup backend as Settings. - An unchecked interactive choice with no existing Run entry persists off,
including an upgrade with an old
launch_at_login=trueconfig. An unchanged registered-but-disabled entry preserves the OS disable. Setup uses--refresh-startupto follow a changed installation path without rewriting arguments or re-enabling disabled startup. In MSI, both preference actions and refresh must run beforeLaunchApplicationin the execute sequence as well as on the Finish button.
Regenerating the images. scripts/generate-installer-images.mjs draws the
four BMPs from assets/sevak-icon.png (the flame is cut out of it by its red
channel, so the installer shows the real icon pixels). CI does not run it; the BMPs
are committed. It needs @napi-rs/canvas, which is optional like the
documentation media tooling (docs/media/README.md), and Segoe UI, so run it on
Windows:
npm install --no-save --package-lock=false @napi-rs/canvas
node scripts/generate-installer-images.mjs --preview # PNG copies in target/installer-preview/
The images use the app's palette (#2b2870 to #17152b indigo, #f59e0b amber, and
#fcfcfd/#1c1b2e for the light header and pages: MUI_BGCOLOR and
MUI_TEXTCOLOR in installer.nsi). Pages stay light on purpose: NSIS cannot
recolour the text of its check boxes, so a dark page would make them unreadable.
Edit the layout in the script. NSIS and WiX only take uncompressed 24-bit BMP, which
scripts/bmp.mjs writes.
After a Tauri upgrade, re-sync the templates. installer.nsi, English.nsh and main.wxs
are copies, so new Tauri behaviour (and fixes) reach Sevak only by merging them.
utils.nsh and FileAssociation.nsh are not copies: Tauri writes its own next to
the template at build time, so the macros the template calls (CheckIfAppIsRunning
and friends) are always the current ones.
- Find the bundler's version: Tauri's
Cargo.tomlat the tagtauri-cli-v<CLI version>(npx tauri --version) namestauri-bundler. The files are incrates/tauri-bundler/src/bundle/windows/nsis/at that tag. - Merge the upstream change into Sevak's copy (the pristine copy is the common ancestor):
V=2.13.0 # the new @tauri-apps/cli version
B=https://raw.githubusercontent.com/tauri-apps/tauri/tauri-cli-v$V/crates/tauri-bundler/src/bundle/windows/nsis
curl -fsSL $B/installer.nsi -o new-installer.nsi
curl -fsSL $B/languages/English.nsh -o new-English.nsh
git merge-file src-tauri/installer/installer.nsi src-tauri/installer/upstream/installer.nsi new-installer.nsi
git merge-file src-tauri/installer/English.nsh src-tauri/installer/upstream/English.nsh new-English.nsh
mv new-installer.nsi src-tauri/installer/upstream/installer.nsi
mv new-English.nsh src-tauri/installer/upstream/English.nsh
Resolve any conflict markers. Every Sevak change sits between SEVAK: comments
(diff src-tauri/installer/upstream/installer.nsi src-tauri/installer/installer.nsi
lists them all). Update the version in the header comment of installer.nsi.
Also obtain the same tag's crates/tauri-bundler/src/bundle/windows/msi/main.wxs,
merge it into src-tauri/installer/main.wxs using upstream/main.wxs as the
ancestor, and replace that pristine copy. Keep SevakStartupUI and the
startup-before-launch ordering when resolving conflicts; startup.wxs is
Sevak's own fragment, not a pristine upstream file.
3. Build (npx tauri build --bundles nsis,msi; makensis must report no warning) and
run npm run test:scripts, which checks that every string the template uses is
defined and that every upstream string is still in English.nsh. With the
Windows toolchain available, it also compiles and runs the production NSIS
startup functions against isolated fixtures: choices, disabled states, path
changes, elevation arguments and helper receipts.
On Windows also run pwsh -NoProfile -File scripts/test-windows-installer.ps1
against the compiled MSI. It inspects app registration and evaluates real
installer conditions and sequencing without installing or changing startup;
the Windows bundle and release jobs run both installer test suites after
building, when the required NSIS toolchain is available.
4. Test the actual installers by hand, on a machine that is not your
daily one (or in a VM): a fresh per-user install; a fresh all-users install (UAC);
upgrading a per-user and an all-users install with Sevak running, from the wizard
and with /S; the in-app update path (/P /UPDATE /R); per-user over all-users
and the reverse (the move page, and /UNINSTALLOTHER); uninstalling from
Settings > Apps for both scopes; silent /S and /S /ALLUSERS.
CI¶
.github/workflows/ci.yml runs on pushes and pull requests:
lint-test: fmt, clippy, tests and the UI checks onwindows-latest,macos-latestandubuntu-22.04.ui-testsandlatency-budget(Ubuntu): the Vitest suite, and the search latency budget in an optimized build.fedora: builds and tests in afedoracontainer to prove the Fedora toolchain and libraries work. Bump the image tag when the release goes EOL.bundle(afterlint-test): builds the installers (Windows: nsis+msi; macOS: app+dmg; ubuntu-22.04: deb+rpm+appimage) and uploads them as workflow artifacts. Ubuntu 22.04 is the oldest supported base, so the.deband AppImage run on 22.04+.msrv:cargo check --workspace --lockedon therust-versiondeclared inCargo.toml, so the stated minimum is really buildable.
The Rust toolchain every job uses comes from rust-toolchain.toml (see below).
The security, CodeQL and canary workflows are described in the next section.
Security and supply chain¶
| Workflow | Runs | What it does |
|---|---|---|
security.yml |
PRs that touch a manifest or lockfile, pushes to main, weekly |
cargo deny (advisories; licences, bans and sources) and npm audit --omit=dev --audit-level=high. The weekly run also writes a cargo audit report and keeps one security issue open while something fails. |
codeql.yml |
PRs to main, pushes to main, weekly |
CodeQL for javascript-typescript and for the actions workflows. Rust is not scanned by CodeQL; clippy (-D warnings) and cargo-deny cover it. |
toolchain-canary.yml |
weekly, manually | clippy and tests on the newest stable and beta Rust (Linux only). A failure opens one toolchain issue, "New Rust release breaks the build". |
Dependency scanning locally¶
cargo install --locked cargo-deny cargo-audit
cargo deny check # advisories, licenses, bans, sources (deny.toml)
cargo deny check licenses # or a single check
cargo audit # wider RustSec report, including unmaintained crates
npm audit --omit=dev --audit-level=high
deny.toml is the policy: crates.io only, permissive licences only (MIT,
Apache-2.0, BSD, ISC, Zlib, Unicode and similar; MPL-2.0 per crate), duplicate
crate versions warn. Advisories fail the check because cargo-deny cannot filter
by severity; yanked crates and unmaintained transitive crates only warn (the
latter appear in cargo audit). The advisory database is fetched on every run,
so a clean result today can fail next week without any change here; that is what
the weekly run is for.
Handling a finding¶
- Prefer fixing it:
cargo update -p <crate>(or bump the requirement), then re-runcargo deny checkand the tests. - If no fix exists yet, add a time-boxed exception to
deny.toml:
[advisories]
ignore = [
{ id = "RUSTSEC-2099-0001", reason = "only reachable via X, which Sevak never calls; fix tracked in #123; review-by: 2027-01-31" },
]
Always give a reason and a review-by: YYYY-MM-DD date (at most three months
out): the Security workflow fails once that date has passed, so the exception
gets reviewed instead of forgotten. Remove it once the fix lands
(unused-ignored-advisory warns when it is no longer needed).
3. A new licence goes in [licenses] allow only if it is permissive and
compatible with Apache-2.0, with a comment naming the crate. Copyleft that is
file-level only (MPL-2.0) is allowed per crate under [licenses] exceptions;
anything stronger (GPL, AGPL, LGPL-only) needs a maintainer decision, and the
dependency is usually better replaced.
4. A new THIRD_PARTY_NOTICES.md entry may be needed when a dependency with
a notice requirement is added.
Known, accepted findings (no exception needed, because cargo-deny only gates
direct dependencies for these classes): cargo audit reports
RUSTSEC-2024-0370 (proc-macro-error, unmaintained) and RUSTSEC-2024-0429
(glib 0.18, unsound VariantStrIter iterators). Both come from the GTK3
bindings that Tauri 2 and wry use on Linux only, Sevak does not call the
affected API, and the fix is Tauri moving to newer bindings, so they clear
with a Tauri update.
Dependabot (.github/dependabot.yml) opens one grouped pull request per month
for minor and patch updates of Cargo, npm and GitHub Actions, and one pull
request per major update. Third-party GitHub Actions are pinned to a commit SHA
(uses: owner/repo@<sha> # v1.2.3; Dependabot keeps both in step). Actions owned
by GitHub (actions/*, github/*) may use version tags.
The pinned toolchain¶
rust-toolchain.toml pins an exact Rust version (currently 1.99.0) with the
clippy and rustfmt components. It is the single source of truth: rustup
picks it up in any checkout, and CI installs it through the
.github/actions/setup-rust composite action (which runs rustup toolchain
install against that file). A new Rust release therefore never changes CI, or a
release build, by itself; new lints cannot block an unrelated pull request.
To bump it, change channel in rust-toolchain.toml in a pull request of its
own (Dependabot opens one monthly), fix any new clippy findings, and run the
checks in "Tests and lint". Nothing else needs editing. The rust-version in
Cargo.toml is the separate, lower minimum supported Rust version; the msrv CI
job checks it. Raise it only when the code or a dependency really needs a newer
compiler.
The canary workflow tells you in advance when a bump will need work: if the latest stable or beta fails clippy or tests, it files (or updates) the "New Rust release breaks the build" issue, and closes it again when they pass.
Repository settings (maintainers)¶
Workflows cannot change repository settings. Enable these under the repository's
Settings > Advanced Security (Code security): Dependency graph, Dependabot
alerts, and Dependabot security updates (without the last one, only the monthly
version PRs arrive). For CodeQL, leave "Code scanning" on "Advanced" or off, not
"Default setup", or GitHub rejects the results from codeql.yml. Do not make the
Security workflow's jobs required status checks of main: they run only when
a manifest or lockfile changes, and a required check that never starts leaves the
pull request waiting forever.
Releasing¶
The release flow, the pre-release checklist, the opt-in staged flow (beta, then promote), rollback and hotfixes are in Releasing. What follows is how the default pipeline is built.
Releases are automatic: every push to main that changes more than docs
(in practice, every merged pull request) publishes a new version.
.github/workflows/release.yml:
- plan computes the next version with
scripts/release-version.mjs nextfrom the latestvX.Y.Ztag and the Conventional Commit subjects since it, and opens a draft GitHub Release with generated notes:
| Commits since the last tag | Bump |
|---|---|
any feat!: / fix!: or a BREAKING CHANGE: footer |
major (minor while on 0.x) |
any feat: / feat(scope): |
minor |
| anything else | patch |
- build stamps that version into
Cargo.toml,tauri.conf.json,package.jsonandpackage-lock.json(release-version.mjs set, in the runner only; nothing is committed), then builds and uploads the installers: Windows (nsis, msi), macOS universal (app, dmg) and Linux (deb, rpm, AppImage).src-tauri/tauri.release.conf.jsonturns oncreateUpdaterArtifacts, so each installer is signed for the updater (.signext to it). - publish writes
latest.jsonfrom those signatures (scripts/updater-manifest.mjs), addsSHA256SUMS.txtand publishes the release, which creates thevX.Y.Ztag on the released commit.
Package managers¶
After publishing, the distribute job renders the manifests in packaging/
for the new version (scripts/package-manifests.mjs, which fills in versions,
file names and SHA-256 hashes from SHA256SUMS.txt) and publishes them. Each
channel runs only when its secret is set, and a failure there never fails the
release:
| Channel | Where | Secret |
|---|---|---|
| Homebrew | Casks/sevak.rb in ninad-k/homebrew-tap |
PACKAGING_TOKEN |
| Scoop | bucket/sevak.json in ninad-k/scoop-bucket |
PACKAGING_TOKEN |
| AUR | sevak-bin, from packaging/aur/PKGBUILD |
AUR_SSH_PRIVATE_KEY |
| winget | a pull request to microsoft/winget-pkgs for NinadKulkarni.Sevak |
WINGET_TOKEN |
PACKAGING_TOKEN: a fine-grained personal access token with Contents: read and write onninad-k/homebrew-tapandninad-k/scoop-bucketonly.AUR_SSH_PRIVATE_KEY: the private half of an SSH key whose public half is added to the AUR account that ownssevak-bin.WINGET_TOKEN: a classic personal access token with thepublic_reposcope, from an account with a fork ofmicrosoft/winget-pkgs. winget only accepts automated updates for packages that already exist, so the first version is submitted once by hand from the renderedpackaging/wingetmanifests.
Scoop and AUR packages write a package-manager marker (next to sevak.exe,
or /usr/share/sevak/package-manager) that turns Sevak's self-update off; see
ManagedBy in src-tauri/src/updater.rs. Preview the manifests for any
release with node scripts/package-manifests.mjs <version> SHA256SUMS.txt out.
Auto-update and the signing key¶
Installed copies poll
https://github.com/ninad-k/Sevak/releases/latest/download/latest.json
(plugins.updater in tauri.conf.json; code in src-tauri/src/updater.rs; copies
on the beta channel also read latest-beta.json, see Releasing) at
startup, every six hours and when the launcher opens, and from the tray's "Check for updates". They install an
update only after the user agrees, and only if its signature matches the
pubkey in tauri.conf.json.
The matching private key is the repository secret TAURI_SIGNING_PRIVATE_KEY
(plus TAURI_SIGNING_PRIVATE_KEY_PASSWORD if the key has a password). The
release workflow stops early if it is missing. Keep an offline backup of the
private key: if it is lost, existing installs can no longer verify updates
and users must reinstall by hand after you rotate keys. To rotate, generate a
pair with npx tauri signer generate -w ~/.tauri/sevak-updater.key, put the
new public key in tauri.conf.json and the private key in the secret; the
release that ships the new public key must still be signed with the old key.
Local and CI builds don't sign (no --config src-tauri/tauri.release.conf.json),
so they don't need the key. Debug builds never check for updates automatically.
If a build fails, nothing is tagged; the draft is replaced on the next run.
The version in the repository files is only the floor for the first release
and for forced bumps: to jump to a specific version (say 1.0.0), set it in
src-tauri/tauri.conf.json and Cargo.toml in a PR. To merge without
releasing, put [skip release] in the merge commit message; pushes that only
touch Markdown, docs/ or LICENSE never release.
Preview the next version locally with node scripts/release-version.mjs next.
Documentation site¶
The user and developer guide is built with MkDocs Material and published to GitHub Pages.
Editing:
- Documentation source:
docs/(Markdown files) - Site configuration:
mkdocs.yml - Extra CSS:
docs/stylesheets/extra.css - Media (screenshots, images):
docs/media/
Preview locally:
Strict build (no warnings):
PDF manual:
The documentation is also exported to PDF (Sevak-User-Guide.pdf) for download:
mkdocs build # Generates the site to site/
npm run docs:pdf # Builds the PDF (see scripts/docs-pdf.mjs)
# Output: docs/pdf/Sevak-User-Guide.pdf
The PDF script requires Chrome or Chromium; set CHROME_PATH if it's not in the default location.
Publishing:
The site is built and deployed to GitHub Pages by .github/workflows/docs.yml on every push to main. The same workflow publishes the product page from landing/ at the site root, ninad-k.github.io/Sevak, with the documentation under /docs/.
Linux testing in WSL:
For contributors on Windows, test the Linux code without a Linux machine:
# From the Windows project root, type these in PowerShell:
# The scripts bootstrap Rust and Zig inside WSL, no sudo needed.
wsl -d Ubuntu -- bash /mnt/d/PProjects/Sevak/scripts/wsl-setup.sh # First-time setup
wsl -d Ubuntu -- bash /mnt/d/PProjects/Sevak/scripts/wsl-dev.sh # Build and test
# Or run specific cargo commands:
wsl -d Ubuntu -- bash /mnt/d/PProjects/Sevak/scripts/wsl-linux-test.sh test -p sevak-platform
wsl -d Ubuntu -- bash /mnt/d/PProjects/Sevak/scripts/wsl-linux-test.sh clippy -p sevak-platform --all-targets
See the header of scripts/wsl-linux-test.sh for more options.