Skip to content

Latest commit

 

History

History
1728 lines (1178 loc) · 54.9 KB

DEVELOPMENT.md

File metadata and controls

1728 lines (1178 loc) · 54.9 KB

ShellStation — Development Environment Setup

This guide walks through every step needed to set up a development workstation for ShellStation, from system dependencies to a fully working build. It covers Linux, macOS, and Windows 11, plus instructions for producing release-ready production builds.

Tested on: Ubuntu 22.04+, Debian 12+, Fedora 37+, Arch Linux, macOS 13 Ventura+, Windows 11 23H2+.


Table of Contents

Linux

macOS

Windows 11

Production Builds

Maintenance

Testlab


1. System Dependencies

Tauri 2.x uses WebKitGTK 4.1 on Linux for its webview. The following packages provide the build toolchain, WebKit rendering engine, GTK bindings, and supporting libraries.

Debian / Ubuntu (22.04+)

sudo apt update
sudo apt install -y \
    build-essential \
    curl \
    wget \
    file \
    pkg-config \
    libssl-dev \
    libgtk-3-dev \
    libwebkit2gtk-4.1-dev \
    libayatana-appindicator3-dev \
    librsvg2-dev \
    libdbus-1-dev \
    libsoup-3.0-dev \
    libjavascriptcoregtk-4.1-dev

Fedora (37+)

sudo dnf check-update
sudo dnf install -y \
    webkit2gtk4.1-devel \
    openssl-devel \
    curl \
    wget \
    file \
    libappindicator-gtk3-devel \
    librsvg2-devel
sudo dnf group install "C Development Tools and Libraries"

Arch Linux

sudo pacman -Syu
sudo pacman -S --needed \
    webkit2gtk-4.1 \
    base-devel \
    curl \
    wget \
    file \
    openssl \
    appmenu-gtk-module \
    gtk3 \
    libappindicator-gtk3 \
    librsvg

2. Rust Toolchain

Install Rust via rustup (the official installer). Do not use distribution packages — they tend to lag behind and miss components like clippy and rustfmt.

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Follow the prompts and select the default installation. Then reload your shell:

source "$HOME/.cargo/env"

Verify installation

rustc --version
cargo --version
rustup --version

ShellStation needs Rust 1.94 or newer, the rust-version in src-tauri/Cargo.toml. Cargo refuses to build it with an older toolchain; rustup update fixes that.

Install required components

rustup component add clippy rustfmt
  • clippy — the Rust linter. ShellStation enforces zero clippy warnings.
  • rustfmt — the standard Rust formatter.

Install cargo utilities

cargo install cargo-watch    # auto-rebuild on file changes during development
cargo install sqlx-cli --no-default-features --features native-tls,sqlite,postgres
  • cargo-watch — runs cargo check (or any command) on every save, useful for fast feedback during development.
  • sqlx-cli — manages database migrations for both SQLite and PostgreSQL. The feature flags here enable both backends.

Keep Rust up to date

rustup update

Run this periodically (or set up a cron job) to stay on the latest stable toolchain.


3. Node.js and npm

ShellStation's frontend is built with React + TypeScript + Vite, which requires Node.js. Use Node 20 LTS or later.

Option A: Install via NodeSource (recommended)

curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs

Option B: Install via nvm (Node Version Manager)

If you prefer to manage multiple Node versions:

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
source ~/.bashrc
nvm install 20
nvm use 20

Verify installed versions

node --version    # should be v20.x or later
npm --version     # should be 10.x or later

Install global npm tools

sudo npm install -g typescript

4. Tauri CLI

The Tauri CLI handles project scaffolding, development server, and bundling. Install it via cargo:

cargo install tauri-cli --version "^2"

This installs the cargo tauri subcommand. Verify it with:

cargo tauri --version

Alternatively, you can use the npm-based CLI wrapper (useful if you prefer npx-style invocation):

sudo npm install -g @tauri-apps/cli@latest

Run the environment check

Tauri ships a built-in diagnostic command that validates your system dependencies:

cargo tauri info

Review the output. Every line should show a checkmark. If anything is marked with ✘, install the missing dependency before continuing.


5. Database Tooling

ShellStation supports SQLite (local) and PostgreSQL (shared/team). You need the development libraries for both at build time.

SQLite

SQLite is typically already available on most Linux systems. Ensure the development headers are present:

sudo apt install -y libsqlite3-dev sqlite3

Verify:

sqlite3 --version

PostgreSQL (optional for local development)

If you want to develop and test against a real PostgreSQL instance locally:

sudo apt install -y postgresql postgresql-client libpq-dev

Start the service and create a development database:

sudo systemctl start postgresql
sudo systemctl enable postgresql

# Create a development user and database
sudo -u postgres createuser --interactive --pwprompt shellstation
sudo -u postgres createdb --owner=shellstation shellstation_dev

Set the database URL as an environment variable (add this to your ~/.bashrc or ~/.zshrc):

export DATABASE_URL="postgres://shellstation:<your-password>@localhost/shellstation_dev"

Alternative: PostgreSQL via Docker

If you prefer not to install PostgreSQL system-wide:

docker run -d \
    --name shellstation-postgres \
    -e POSTGRES_USER=shellstation \
    -e POSTGRES_PASSWORD=devpassword \
    -e POSTGRES_DB=shellstation_dev \
    -p 5432:5432 \
    postgres:16-alpine

Then set:

export DATABASE_URL="postgres://shellstation:devpassword@localhost/shellstation_dev"

SQLite for development (default)

For day-to-day development without PostgreSQL, sqlx can operate against a local SQLite file. Set:

export DATABASE_URL="sqlite:///home/$USER/.config/shellstation/dev.db"

Create the directory:

mkdir -pv /home/$USER/.config/shellstation

sqlx-cli will create the file automatically when you run migrations.


6. VS Code Extensions

The following extensions provide the best development experience for a Tauri + Rust + React/TypeScript project. Install them from the VS Code extensions panel or via the command line.

Required

# Rust language support (rust-analyzer)
code --install-extension rust-lang.rust-analyzer

# Tauri integration
code --install-extension tauri-apps.tauri-vscode

# ESLint for TypeScript/React linting
code --install-extension dbaeumer.vscode-eslint

# Prettier for frontend formatting
code --install-extension esbenp.prettier-vscode

Recommended

# TypeScript/JavaScript language support (ships with VS Code but ensure latest)
code --install-extension ms-vscode.vscode-typescript-next

# Better TOML support (for Cargo.toml, tauri.conf.json, etc.)
code --install-extension tamasfe.even-better-toml

# Crates helper (shows latest versions inline in Cargo.toml)
code --install-extension serayuzgur.crates

# Error Lens (inline compiler/lint errors)
code --install-extension usernamehw.errorlens

# SQLite viewer (inspect local DB from VS Code)
code --install-extension alexcvzz.vscode-sqlite

# GitLens (enhanced Git integration)
code --install-extension eamodio.gitlens

VS Code settings (recommended)

Add these to your workspace .vscode/settings.json to align with ShellStation's quality standards:

{
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "[rust]": {
    "editor.defaultFormatter": "rust-lang.rust-analyzer",
    "editor.formatOnSave": true
  },
  "rust-analyzer.check.command": "clippy",
  "rust-analyzer.check.extraArgs": ["--", "-D", "warnings"],
  "eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"],
  "typescript.tsdk": "node_modules/typescript/lib",
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": "explicit"
  }
}

This configures rust-analyzer to run clippy instead of the default cargo check (catching more issues in real time) and sets up ESLint auto-fix on save.


7. Project Bootstrap

Clone and install (Linux)

git clone https://git.fiedler.live/tux/shellstation.git
cd shellstation
npm install

npm install reads package.json and pulls all frontend dependencies including the Tauri CLI wrapper. Rust crates are resolved automatically by Cargo on the first build.

Run the initial database migration

sqlx database create
sqlx migrate run

(Requires the DATABASE_URL environment variable to be set — see Database Tooling.)

First build and run

cargo tauri dev

This compiles the Rust backend, starts the Vite dev server for the frontend, and opens the application window. The first build takes several minutes as it compiles all Rust dependencies.

Verbose logging

The Log level under Application Logging in Settings applies to ShellStation's own code only; every third-party crate stays silent at any level. For more, set RUST_LOG, which replaces that filter entirely:

RUST_LOG=shellstation_lib=trace,russh=debug cargo tauri dev

On Windows PowerShell, set $env:RUST_LOG = "shellstation_lib=trace,russh=debug" first. RUST_LOG is passed through as given, so third-party targets at debug or trace write whatever they log — sqlx every SQL statement, russh protocol detail — and with application logging enabled, that lands in the rotating log file too. Unset it before collecting a log to share.


8. Verify the Environment

Run the full quality check suite to confirm everything is wired up correctly:

Rust checks

cd src-tauri

# Linter (zero warnings enforced)
cargo clippy -- -D warnings

# Formatter check (no changes = pass)
cargo fmt -- --check

# Unit tests
cargo test

Frontend checks

# ESLint (zero errors/warnings)
npx eslint src/ --ext .ts,.tsx

# Prettier format check
npx prettier --check "src/**/*.{ts,tsx,css,json}"

# TypeScript type check
npx tsc --noEmit

# Unit tests
npx vitest run

Tauri environment check

cargo tauri info

If all commands pass without errors, your development environment is ready.


9. Common Issues (Linux)

webkit2gtk-4.1 not found (Ubuntu < 22.04)

Tauri 2.x requires WebKitGTK 4.1, which is only available in Ubuntu 22.04 and later. If you are on an older release, you must upgrade your distribution or use a Docker-based build environment.

pkg-config errors during cargo build

Usually means a -dev package is missing. Read the error message — it will name the .pc file it cannot find. Search for the package that provides it:

apt-file search missing-package.pc

Install apt-file first if needed: sudo apt install apt-file && sudo apt-file update.

sqlx compile-time errors ("cannot find database")

sqlx verifies queries at compile time against a real database. If you don't have a running database, you can use sqlx's offline mode:

cargo sqlx prepare

This generates a .sqlx/ directory with cached query metadata, allowing compilation without a live database connection.

Slow first build

The initial cargo build downloads and compiles all Rust crate dependencies. This is normal and can take 5–10 minutes depending on your hardware. Subsequent builds are incremental and much faster. Using cargo-watch avoids full rebuilds during development.

Permission denied on keychain (Linux)

The keyring crate requires a running D-Bus session and a secret service (GNOME Keyring or KWallet). If you are developing in a headless environment or minimal desktop:

sudo apt install -y gnome-keyring libsecret-1-dev

Then ensure a keyring daemon is running in your session.

WebGL not available in Tauri dev mode

Some Linux environments (particularly VMs or Wayland sessions) may not expose WebGL to the webview. xterm.js will automatically fall back to the canvas renderer. This is not a bug — the application handles it gracefully.

Blank white window on Linux

The window opens with its title bar and native menu (Edit, Help), but the content area stays white. WebKitGTK's DMABUF renderer fails on some GPU drivers, and virtual GPUs are a common case (e.g. VMware SVGA with vmwgfx, seen with WebKitGTK 2.52). The app itself is fine; WebKitGTK simply draws nothing.

Disable the DMABUF renderer for one run to confirm:

WEBKIT_DISABLE_DMABUF_RENDERER=1 npm run tauri dev

If the UI appears, set it permanently for your session and log out and back in:

echo 'export WEBKIT_DISABLE_DMABUF_RENDERER=1' >> ~/.profile

Not every desktop session reads ~/.profile. If echo "$WEBKIT_DISABLE_DMABUF_RENDERER" in a new terminal prints nothing after logging back in, use a systemd user environment file instead:

mkdir -p ~/.config/environment.d
echo 'WEBKIT_DISABLE_DMABUF_RENDERER=1' >> ~/.config/environment.d/webkit.conf

Either way the variable applies to every WebKitGTK application in your session, not just ShellStation. To limit it to ShellStation, prefix the launch command with it as in the test above.

ShellStation deliberately does not set this variable itself: it costs every Linux user the zero-copy GPU path to fix a problem only some drivers have. If the window is still blank, try WEBKIT_DISABLE_COMPOSITING_MODE=1 instead, which turns off accelerated compositing entirely.


10. macOS Prerequisites

Tauri 2.x on macOS uses the system WebKit framework (WKWebView) — no additional webview runtime is needed. The primary build dependency is the Xcode Command Line Tools.

Install Xcode Command Line Tools

Open Terminal and run:

xcode-select --install

This installs clang, make, git, and the macOS SDK headers. You do not need the full Xcode IDE unless you plan to do code signing or work with Xcode projects directly.

Verify the installation:

xcode-select -p
# Should print: /Library/Developer/CommandLineTools or /Applications/Xcode.app/Contents/Developer

Install Homebrew

Homebrew is the standard package manager for macOS development dependencies:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

Follow the post-install instructions to add Homebrew to your PATH. Then verify:

brew --version

11. macOS Rust Toolchain

Install Rust via rustup (macOS)

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Follow the prompts and select the default installation. Then reload your shell:

source "$HOME/.cargo/env"

Verify Rust installation (macOS)

rustc --version
cargo --version
rustup --version

Install required Rust components (macOS)

rustup component add clippy rustfmt

Install cargo utilities (macOS)

cargo install cargo-watch
cargo install sqlx-cli --no-default-features --features native-tls,sqlite,postgres

Keep Rust up to date (macOS)

rustup update

12. macOS Node.js and npm

Install Node.js 20 LTS via Homebrew (recommended)

brew install node@20

Homebrew installs Node into a keg-only path. Follow the post-install output to add it to your PATH, or link it:

brew link --overwrite node@20

Alternative: Install via nvm (macOS)

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
source ~/.zshrc
nvm install 20
nvm use 20

Verify installed versions (macOS)

node --version    # should be v20.x or later
npm --version     # should be 10.x or later

Install global npm tools (macOS)

npm install -g typescript

13. macOS Tauri CLI and Database Tooling

Tauri CLI (macOS)

cargo install tauri-cli --version "^2"

Verify:

cargo tauri --version

Run the environment diagnostic:

cargo tauri info

Review the output. Every line should show a checkmark. If anything is marked with a cross, install the missing dependency before continuing.

SQLite (macOS)

macOS ships with SQLite pre-installed. Verify:

sqlite3 --version

If you need a newer version:

brew install sqlite

PostgreSQL (optional, macOS)

For local PostgreSQL development:

brew install postgresql@16
brew services start postgresql@16

Create the development database:

createuser --interactive --pwprompt shellstation
createdb --owner=shellstation shellstation_dev

Set the database URL (add to your ~/.zshrc):

export DATABASE_URL="postgres://shellstation:<your-password>@localhost/shellstation_dev"

Alternative: PostgreSQL via Docker (macOS)

docker run -d \
    --name shellstation-postgres \
    -e POSTGRES_USER=shellstation \
    -e POSTGRES_PASSWORD=devpassword \
    -e POSTGRES_DB=shellstation_dev \
    -p 5432:5432 \
    postgres:16-alpine

Then set:

export DATABASE_URL="postgres://shellstation:devpassword@localhost/shellstation_dev"

SQLite for development (default, macOS)

export DATABASE_URL="sqlite://$HOME/Library/Application Support/shellstation/dev.db"
mkdir -p "$HOME/Library/Application Support/shellstation"

14. macOS Project Bootstrap and Verify

Clone and install (macOS)

git clone https://git.fiedler.live/tux/shellstation.git
cd shellstation
npm install

Run database migrations (macOS)

sqlx database create
sqlx migrate run

First build and run (macOS)

cargo tauri dev

The first build compiles all Rust dependencies and takes several minutes. Subsequent builds are incremental.

Run the full quality check suite (macOS)

Rust checks:

cd src-tauri
cargo clippy -- -D warnings
cargo fmt -- --check
cargo test
cd ..

Frontend checks:

npx eslint src/ --ext .ts,.tsx
npx prettier --check "src/**/*.{ts,tsx,css,json}"
npx tsc --noEmit
npx vitest run

Tauri environment check:

cargo tauri info

If all commands pass without errors, your macOS development environment is ready.


15. macOS Common Issues

xcrun: error: invalid active developer path

This means Xcode Command Line Tools are not installed or need updating:

xcode-select --install

If already installed but still failing after a macOS upgrade, reset the path:

sudo xcode-select --reset

pkg-config not found (macOS)

Some Rust crates use pkg-config to locate system libraries. Install it via Homebrew:

brew install pkg-config

sqlx compile-time errors (macOS)

Same as Linux — sqlx verifies queries at compile time. Use offline mode if no database is available:

cargo sqlx prepare

Slow first build (macOS)

Normal on macOS as well. The initial cargo build can take 5-10 minutes. Subsequent builds are incremental. Using cargo-watch avoids full rebuilds during development.

Permission denied on keychain (macOS)

The keyring crate uses macOS Keychain by default. If running in a CI environment or headless context, Keychain access may be restricted. For local development, Keychain access should work without additional configuration. If prompted, allow the shellstation binary to access the keychain.

Apple Silicon (M1/M2/M3) considerations

Rust natively supports aarch64-apple-darwin. The default rustup installation on Apple Silicon installs the ARM toolchain. No Rosetta or special configuration is needed. All dependencies (including Homebrew, Node.js, and SQLite) run natively on ARM.

If you need to cross-compile for Intel Macs:

rustup target add x86_64-apple-darwin
cargo tauri build --target x86_64-apple-darwin

WebGL not available in development

Some macOS configurations (particularly in VMs) may not expose WebGL to the webview. xterm.js falls back to the canvas renderer automatically. This is not a bug.


16. Windows 11 Prerequisites

Tauri 2.x on Windows uses Microsoft Edge WebView2 (pre-installed on Windows 11) for its webview. The primary build dependency is the Microsoft C++ Build Tools.

Install Visual Studio Build Tools

Download Visual Studio 2022 Build Tools from https://visualstudio.microsoft.com/visual-cpp-build-tools/.

During installation, select the following workload:

  • Desktop development with C++

Under "Individual components", ensure these are checked (they should be by default with the workload):

  • MSVC v143 — VS 2022 C++ x64/x86 build tools (Latest)
  • Windows 11 SDK (10.0.22621.0 or later)
  • C++ CMake tools for Windows

This provides cl.exe, link.exe, the Windows SDK headers, and the CRT libraries that Rust's MSVC toolchain requires. You do not need the full Visual Studio IDE — the Build Tools installer is sufficient.

Verify WebView2

WebView2 ships with Windows 11. Confirm it is present:

Get-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" -Name pv

If this returns a version string, WebView2 is installed. If not, download the Evergreen Bootstrapper from https://developer.microsoft.com/en-us/microsoft-edge/webview2/.

Install Git for Windows

If not already installed:

winget install Git.Git

After installation, open a new terminal and verify:

git --version

Install NASM (Netwide Assembler)

NASM is required because aws-lc-sys (pulled in transitively via russh → rustls → aws-lc-rs) assembles its crypto primitives from .asm sources at build time on Windows. Without nasm.exe on PATH, the Rust build panics with NASM command not found! Build cannot continue.

winget install --id NASM.NASM -e

The winget package does not add NASM to PATH automatically. Add it manually:

[Environment]::SetEnvironmentVariable(
  "Path",
  [Environment]::GetEnvironmentVariable("Path", "Machine") + ";C:\Program Files\NASM",
  "Machine"
)

Restart your terminal, then verify:

nasm --version

Optional: Windows Terminal

Windows Terminal provides a better experience than the default console. Install via:

winget install Microsoft.WindowsTerminal

17. Windows 11 Rust Toolchain

Install Rust via rustup (Windows)

Open PowerShell and run:

winget install Rustlang.Rustup

Or download the installer from https://rustup.rs/.

Follow the prompts and select the default installation (which uses the stable-x86_64-pc-windows-msvc toolchain). Close and reopen your terminal after installation.

Verify Rust installation (Windows)

rustc --version
cargo --version
rustup --version

Install required Rust components (Windows)

rustup component add clippy rustfmt

Install cargo utilities (Windows)

cargo install cargo-watch
cargo install sqlx-cli --no-default-features --features native-tls,sqlite,postgres

Keep Rust up to date (Windows)

rustup update

18. Windows 11 Node.js and npm

Install Node.js 20 LTS

winget install OpenJS.NodeJS.LTS

Close and reopen your terminal, then verify:

node --version
npm --version

Install global npm tools (Windows)

npm install -g typescript

19. Windows 11 Tauri CLI and Database Tooling

Tauri CLI (Windows)

cargo install tauri-cli --version "^2"

Verify:

cargo tauri --version

Run the environment diagnostic:

cargo tauri info

Review the output. Every line should show a checkmark. If anything is marked with a cross, install the missing dependency before continuing.

SQLite (Windows)

SQLite is compiled from source by the sqlx crate via the bundled sqlite3 feature — no separate installation is needed on Windows. However, if you want the sqlite3 CLI for inspecting databases:

winget install SQLite.SQLite

PostgreSQL (optional, Windows)

For local PostgreSQL development:

winget install PostgreSQL.PostgreSQL

Follow the installer prompts to set a superuser password and create the default cluster. Then open a new terminal and create the development database:

createuser --interactive --pwprompt shellstation
createdb --owner=shellstation shellstation_dev

Set the environment variable (add to your PowerShell profile or system environment variables):

$env:DATABASE_URL = "postgres://shellstation:<your-password>@localhost/shellstation_dev"

To make it permanent, use System Settings > Environment Variables, or add the line to your $PROFILE file.

SQLite for development (default, Windows)

$env:DATABASE_URL = "sqlite:///$env:APPDATA/shellstation/dev.db"
New-Item -ItemType Directory -Force -Path "$env:APPDATA\shellstation"

20. Windows 11 Project Bootstrap and Verify

Clone and install (Windows)

git clone https://git.fiedler.live/tux/shellstation.git
cd shellstation
npm install

Run database migrations (Windows)

sqlx database create
sqlx migrate run

First build and run (Windows)

cargo tauri dev

The first build compiles all Rust dependencies and takes several minutes. Subsequent builds are incremental.

Run the full quality check suite (Windows)

Rust checks:

cd src-tauri
cargo clippy -- -D warnings
cargo fmt -- --check
cargo test
cd ..

Frontend checks:

npx eslint src/ --ext .ts,.tsx
npx prettier --check "src/**/*.{ts,tsx,css,json}"
npx tsc --noEmit
npx vitest run

Tauri environment check:

cargo tauri info

If all commands pass without errors, your Windows development environment is ready.


21. Windows 11 Common Issues

link.exe not found or MSVC errors

The Rust MSVC toolchain requires the Visual Studio Build Tools. If you see linker errors, ensure you installed the "Desktop development with C++" workload and are using a terminal that has the MSVC environment loaded. Opening Developer PowerShell for VS 2022 from the Start menu guarantees the correct paths are set.

pkg-config not found (Windows)

Some crates probe for system libraries using pkg-config, which is not natively available on Windows. Most ShellStation dependencies use vendored/bundled C libraries (e.g., SQLite via sqlx), so this should not be an issue. If a crate does require it, install via:

winget install bloodrock.pkg-config-lite

Long path errors

Enable long path support if you encounter MAX_PATH (260 character) errors:

# Run PowerShell as Administrator
New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" -Name "LongPathsEnabled" -Value 1 -PropertyType DWORD -Force

Also configure Git:

git config --global core.longpaths true

Windows Defender slowing builds

Windows Defender real-time scanning can significantly slow Rust compilation. Add exclusions for:

  • Your project directory (e.g., C:\Users\<you>\Projects\shellstation)
  • The Cargo registry and build cache (%USERPROFILE%\.cargo)
  • The Rust toolchain directory (%USERPROFILE%\.rustup)

Open Windows Security > Virus & threat protection > Manage settings > Exclusions > Add or remove exclusions and add each path as a folder exclusion.

WebView2 runtime issues

If the application window opens but shows a blank white screen, WebView2 may need reinstalling. Download the Evergreen Standalone Installer from the Microsoft Edge WebView2 page and run it.

Application icon not showing in Task Manager

Windows aggressively caches executable icons. After rebuilding with a new or updated icon, Task Manager (and sometimes Explorer) may continue showing a generic icon even though the taskbar displays the correct one. This is a Windows icon cache issue, not a build problem.

To force a refresh, run the following in an elevated PowerShell and then reboot:

Stop-Process -Name explorer -Force
Remove-Item "$env:LOCALAPPDATA\IconCache.db" -ErrorAction SilentlyContinue
Remove-Item "$env:LOCALAPPDATA\Microsoft\Windows\Explorer\iconcache_*" -Force -ErrorAction SilentlyContinue
Start-Process explorer.exe

If the icon still does not appear after rebooting, verify it is embedded in the executable: right-click the .exe in Explorer → Properties — the custom icon should be visible on the General tab. If it shows a generic icon there as well, the issue is in the build (check that icon.ico in src-tauri/icons/ contains the expected sizes and that tauri.conf.json references it).


22. Building for Release

This section covers producing optimized, distributable binaries for all platforms.

Prerequisites

Ensure all quality checks pass before building a release:

# Rust
cd src-tauri && cargo clippy -- -D warnings && cargo fmt -- --check && cargo test && cd ..

# Frontend
npx eslint src/ --ext .ts,.tsx
npx prettier --check "src/**/*.{ts,tsx,css,json}"
npx tsc --noEmit
npx vitest run

Build the release bundle

cargo tauri build

This command:

  1. Runs npm run build (TypeScript type check + Vite production build with minification).
  2. Compiles the Rust backend in release mode (--release) with full optimizations.
  3. Produces platform-specific installers in src-tauri/target/release/bundle/.

Output artifacts by platform

Platform Artifacts Location
Windows .msi installer, .exe (NSIS installer) src-tauri/target/release/bundle/msi/ and nsis/
macOS .dmg disk image, .app bundle src-tauri/target/release/bundle/dmg/ and macos/
Linux .deb package, .AppImage src-tauri/target/release/bundle/deb/ and appimage/

Configuring bundle targets

The tauri.conf.json bundle.targets field controls which formats are built. The current setting "all" builds every format available on the host OS. To build only specific formats, pass --bundles on the command line:

cargo tauri build -- --bundles deb,appimage    # Linux
cargo tauri build -- --bundles msi,nsis        # Windows
cargo tauri build -- --bundles dmg,app         # macOS

Valid targets: msi, nsis, dmg, app, deb, appimage, rpm.

Version bumping and release process

The version-bumping checklist and end-to-end release flow live in RELEASING.md. The release profile (strip, lto, codegen-units, opt-level) is already configured in src-tauri/Cargo.toml.


23. CI/CD Release Pipeline (Forgejo Actions)

ShellStation is built and released via Forgejo Actions on a self-hosted runner setup. The pipeline lives at .forgejo/workflows/release.yml.

Pipeline overview

Job Runner label Outputs
build-linux linux .deb, .AppImage
build-windows windows .msi, .exe (NSIS)
build-macos-x64 macos .dmg (Intel)
build-macos-arm64 macos .dmg (Apple Silicon)
publish-release linux Forgejo release with all artifacts

The macOS jobs run on an Apple Silicon runner and cross-compile for Intel via --target x86_64-apple-darwin. Both architectures produce a .dmg named ShellStation-macos-{x64,arm64}-<version>.dmg.

Trigger

The pipeline fires on any tag matching v*:

git tag v0.9.0
git push origin v0.9.0

Required repository secrets

Secret name Purpose
RELEASE_TOKEN Forgejo access token with write:repository scope. Used by the publish-release job to create the release and upload artifacts.
GH_RELEASE_TOKEN GitHub personal access token with Contents: write on the mirror repo. Used to mirror the release to GitHub.
TAURI_SIGNING_PRIVATE_KEY Contents of ~/.tauri/shellstation_updater.key (the Ed25519 private key used to sign updater artifacts). See §23.x below.
TAURI_SIGNING_PRIVATE_KEY_PASSWORD Password chosen at key-generation time. Required by tauri build when the private key is encrypted (which it should always be).

Generate the Forgejo token at https://git.fiedler.live/user/settings/applications, the GitHub token at https://github.com/settings/tokens, and add them under repo Settings → Actions → Secrets.

Code signing

First-install bundles (the .deb, .dmg, .msi, NSIS .exe) are currently unsigned. Windows users see a SmartScreen "Unknown publisher" warning on first launch; macOS users must right-click the .app and choose Open (or run xattr -d com.apple.quarantine ShellStation.app). This is acceptable for early releases. If first-install code signing becomes a requirement later, the recommended path is SignPath.io's free OSS program for Windows and Apple's Developer ID for macOS (requires an Apple Developer account).

Updater artifacts are signed independently via the Tauri updater's Ed25519 keypair — this is a different signing scheme from OS-level code signing and is mandatory for the in-app update flow to work. See §23.x below for the one-time setup, and DESIGN.md §4.7 for the architectural overview.

App updater signing keys

The in-app updater verifies every downloaded artifact against a public key compiled into the binary. The matching private key lives only on the CI runner (as the TAURI_SIGNING_PRIVATE_KEY secret) and on whichever developer machine generated it. Without this keypair in place, the updater cannot ship — every check would fail signature verification with a confusing error. A CI preflight job rejects releases that still carry the placeholder pubkey.

One-time setup

Run these once per repo, on the maintainer's machine:

# Generate the Ed25519 keypair. You'll be prompted for a password — pick
# a strong one and store it somewhere safe; you'll need it for the CI
# secret below. The private key is written encrypted with this password.
npx tauri signer generate -w ~/.tauri/shellstation_updater.key

# Display the PUBLIC key (small, ~120 chars of base64). Copy this value.
cat ~/.tauri/shellstation_updater.key.pub

Put the public key into src-tauri/tauri.conf.json under plugins.updater.pubkey, replacing whatever was there. Commit and push:

git add src-tauri/tauri.conf.json
git commit -m "Set updater public key"

Add the matching secrets to both Forgejo Actions and (if mirroring) GitHub Actions:

Where Field Value
Forgejo: Settings → Actions → Secrets on git.fiedler.live/tux/shellstation TAURI_SIGNING_PRIVATE_KEY Full contents of ~/.tauri/shellstation_updater.key
Forgejo: same page TAURI_SIGNING_PRIVATE_KEY_PASSWORD The password you chose at generation time

Never commit the private key, and never paste it into chat with an assistant. The file is encrypted at rest with your password, but a copy outside the secret store is one breach away from full updater takeover (an attacker who holds the key can sign arbitrary "updates" that the running app will trust).

Verifying the setup

After pushing the public key change, push a throwaway tag like v0.0.0-test to confirm the pipeline produces signed artifacts:

git tag v0.0.0-test
git push origin v0.0.0-test

In the Forgejo workflow log:

  • The preflight job should pass (no placeholder pubkey).
  • Each build job should print Signing <path>.AppImage (or .exe, or .app.tar.gz) during tauri build.
  • The Generate latest.json (updater manifest) step should print a non-empty platforms: list. If the list is empty, the secrets are not reaching the build step (check spelling, runner scope, etc.).

Delete the throwaway release and tag afterwards:

git tag -d v0.0.0-test
git push origin :refs/tags/v0.0.0-test
# also delete the Forgejo + GitHub releases through the web UI

Rotation

Every install carries the embedded public key forever. To rotate, you need a transition release signed by the old private key whose binary contains the new public key. Procedure:

  1. Generate a new keypair (do not overwrite the old one yet).
  2. In a new commit, replace plugins.updater.pubkey with the new public key.
  3. Ship that commit as a release using the old TAURI_SIGNING_PRIVATE_KEY secret. Users update via the existing flow; the running binary now contains the new public key.
  4. Update the CI secret to the new private key.
  5. The next release will be signed with the new private key, which existing installs trust because they took the transition release in step 3.

Skipping the transition release leaves existing installs stranded — they can never accept any update again. Users would have to download a fresh first-install bundle manually.

Local development

cargo tauri dev does not exercise the updater unless you explicitly trigger a check. The auto-check on app launch runs in production-style builds and against the configured endpoint (the GitHub Releases URL), which always returns the latest real release manifest. If you want to test the updater UI against a fake manifest:

  1. Spin up a local HTTPS server with a self-signed cert and serve a hand-crafted latest.json plus a signed artifact.
  2. Temporarily change plugins.updater.endpoints[0] in tauri.conf.json to your local URL.
  3. Do not commit either change. Revert before pushing.

Easier alternative for UI work: stub out the dialog by setting phase to "available" directly in UpdateDialog.tsx and reloading. This skips the network/signature path entirely and lets you iterate on the dialog presentation without infrastructure.


24. Forgejo Runner Setup

The pipeline needs three self-hosted runners: Linux, macOS, and Windows. All run the forgejo-runner daemon and register against https://git.fiedler.live.

24.1. Linux runner

Any modern Debian/Ubuntu host works. Recommended: Ubuntu 22.04+ or Debian 12+.

The pipeline runs in host mode (no container), so every tool the build needs must be preinstalled on this machine — the workflow deliberately does not run apt install or setup-node at job time. Steps 1 and 2 below must be completed before the runner will build successfully.

1. Install the build prerequisites (matches what the pipeline expects to find):

sudo apt update
sudo apt install -y \
    build-essential curl wget file pkg-config \
    libssl-dev libgtk-3-dev libwebkit2gtk-4.1-dev \
    libayatana-appindicator3-dev librsvg2-dev \
    libdbus-1-dev libsoup-3.0-dev libjavascriptcoregtk-4.1-dev

2. Install Node.js 20 LTS and Rust stable as a dedicated runner user:

sudo useradd -m -s /bin/bash forgejo-runner
sudo -iu forgejo-runner bash -c '
  curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
  curl --proto "=https" --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable
'
sudo apt install -y nodejs

3. Download the Forgejo runner binary:

sudo -iu forgejo-runner bash -c '
  cd ~
  curl -L -o forgejo-runner https://code.forgejo.org/forgejo/runner/releases/download/v6.2.2/forgejo-runner-6.2.2-linux-amd64
  chmod +x forgejo-runner
  ./forgejo-runner generate-config > config.yml
'

(Check https://code.forgejo.org/forgejo/runner/releases for the latest version.)

4. Register against Forgejo. Get a registration token from https://git.fiedler.live/-/admin/actions/runners (instance-wide) or your repo's Settings → Actions → Runners (repo-scoped):

sudo -iu forgejo-runner bash -c '
  cd ~
  ./forgejo-runner register --no-interactive \
    --instance https://git.fiedler.live \
    --token <REGISTRATION_TOKEN> \
    --name linux-builder \
    --labels linux:host
'

The linux label matches runs-on: linux in the workflow. The :host suffix is critical — it tells the runner to execute jobs directly on the host OS instead of inside a Docker container. Without it, the runner will fail to start with daemon Docker Engine socket not found unless Docker is installed. Host mode is what you want for a Tauri build runner: the host already has Rust, Node, and the GTK dev libs installed, so there's no reason to add a container layer.

5. Run as a systemd service:

sudo tee /etc/systemd/system/forgejo-runner.service >/dev/null <<'EOF'
[Unit]
Description=Forgejo Actions Runner
After=network.target

[Service]
Type=simple
User=forgejo-runner
WorkingDirectory=/home/forgejo-runner
Environment="PATH=/home/forgejo-runner/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
ExecStart=/home/forgejo-runner/forgejo-runner daemon
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now forgejo-runner
sudo systemctl status forgejo-runner

Verify the runner is online at https://git.fiedler.live/-/admin/actions/runners.

24.2. macOS runner

Recommended: Apple Silicon Mac (M1 or later) running macOS 13 Ventura+. The runner cross-compiles for Intel (x86_64-apple-darwin) via Rosetta-capable toolchain — no Intel hardware needed.

1. Install build prerequisites (if not already present from development setup):

xcode-select --install
brew install node@20
brew link --overwrite node@20
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable
source "$HOME/.cargo/env"
rustup target add x86_64-apple-darwin aarch64-apple-darwin

2. Download the Forgejo runner binary:

cd ~
curl -L -o forgejo-runner https://code.forgejo.org/forgejo/runner/releases/download/v6.2.2/forgejo-runner-6.2.2-darwin-arm64
chmod +x forgejo-runner
./forgejo-runner generate-config > config.yml

(Check https://code.forgejo.org/forgejo/runner/releases for the latest version. Use darwin-amd64 on Intel Macs.)

3. Register against Forgejo:

./forgejo-runner register --no-interactive \
  --instance https://git.fiedler.live \
  --token <REGISTRATION_TOKEN> \
  --name macos-builder \
  --labels macos:host

The macos label matches runs-on: macos in the workflow. The :host suffix is critical — it tells the runner to execute jobs directly on the host instead of inside a container.

4. Run as a launchd service:

cat > ~/Library/LaunchAgents/com.forgejo.runner.plist <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>com.forgejo.runner</string>
  <key>ProgramArguments</key>
  <array>
    <string>/Users/YOUR_USER/forgejo-runner</string>
    <string>daemon</string>
  </array>
  <key>WorkingDirectory</key>
  <string>/Users/YOUR_USER</string>
  <key>RunAtLoad</key>
  <true/>
  <key>KeepAlive</key>
  <true/>
  <key>EnvironmentVariables</key>
  <dict>
    <key>PATH</key>
    <string>/Users/YOUR_USER/.cargo/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
  </dict>
  <key>StandardOutPath</key>
  <string>/Users/YOUR_USER/forgejo-runner.log</string>
  <key>StandardErrorPath</key>
  <string>/Users/YOUR_USER/forgejo-runner.err</string>
</dict>
</plist>
EOF

# Replace YOUR_USER with the actual username, then load:
launchctl load ~/Library/LaunchAgents/com.forgejo.runner.plist

Verify the runner appears as macos-builder at https://git.fiedler.live/-/admin/actions/runners.

5. Prevent sleep so the runner stays online when the display is off:

sudo pmset -a sleep 0 disablesleep 1

24.3. Windows runner

Recommended OS: Windows 11 Pro 23H2 or 24H2 (x64). Reasons:

  • Matches what end users have, so platform-specific bugs surface during the build
  • WebView2 Runtime is preinstalled (Tauri requires it)
  • Modern PowerShell, winget, and proper symlink support
  • Pro (not Home) gives Remote Desktop, Group Policy, Hyper-V

Avoid Windows Server (different WebView2 story), Windows 10 (EOL October 2025), and Windows 11 LTSC (no WebView2 by default).

1. Base OS prep:

  • Install Windows 11 Pro, fully patch it, set a static hostname

  • Create a dedicated local account for the runner service (e.g. forgejo-svc) with a real password — not a Microsoft account and not PIN-only login. Service logon requires an NTLM password blob that MS-account/PIN accounts do not have, and attempting to use one produces RUNAS ERROR: Unable to acquire user password and service start failures with event ID 7000 "Access is denied". Create it via New-LocalUser in an elevated PowerShell:

    $pw = Read-Host -AsSecureString "Password"
    New-LocalUser -Name "forgejo-svc" -Password $pw -PasswordNeverExpires -AccountNeverExpires -UserMayNotChangePassword
    Add-LocalGroupMember -Group "Administrators" -Member "forgejo-svc"
  • Install the build toolchain (winget steps below) while logged in as this service account, so Rust and Node land in that account's profile where the service will later find them.

  • Disable sleep and hibernate so the runner stays online: powercfg /change standby-timeout-ac 0

2. Install build prerequisites (elevated PowerShell):

winget install --id Git.Git -e
winget install --id OpenJS.NodeJS.LTS -e
winget install --id Rustlang.Rustup -e
winget install --id Microsoft.VisualStudio.2022.BuildTools -e --override "--quiet --wait --add Microsoft.VisualStudio.Workload.VCTools --add Microsoft.VisualStudio.Component.Windows11SDK.22621 --includeRecommended"
winget install --id Microsoft.EdgeWebView2Runtime -e
winget install --id NASM.NASM -e

After installing NASM, add its install directory to the Machine PATH so the runner service picks it up (the winget package does not do this automatically):

[Environment]::SetEnvironmentVariable(
  "Path",
  [Environment]::GetEnvironmentVariable("Path", "Machine") + ";C:\Program Files\NASM",
  "Machine"
)

NASM is required because aws-lc-sys (pulled in transitively via russh → rustls → aws-lc-rs) assembles its crypto primitives from .asm sources at build time on Windows. Without nasm.exe on PATH, the Rust build panics with NASM command not found! Build cannot continue. Restart the runner service (or reboot) after changing the Machine PATH so the new value is inherited.

Open a fresh terminal and verify:

git --version
node --version
rustup default stable
rustc --version
cargo --version

3. Install the Forgejo runner binary:

cd C:\forgejo-runner
.\forgejo-runner.exe generate-config > config.yml

4. Register against Forgejo (same token source as the Linux runner):

.\forgejo-runner.exe register --no-interactive `
  --instance https://git.fiedler.live `
  --token <REGISTRATION_TOKEN> `
  --name windows-builder `
  --labels windows:host

The windows label matches runs-on: windows in the workflow. The :host suffix is critical — it tells the runner to execute jobs directly on Windows instead of inside a Docker container. Without it, the service will fail to start with daemon Docker Engine socket not found. If you already registered without :host and hit that error, stop the service, delete C:\forgejo-runner\.runner, grab a fresh registration token from the Forgejo admin UI, and re-run the register command with the corrected label.

5. Run as a Windows service using NSSM so it survives reboots:

winget install --id NSSM.NSSM -e
nssm install ForgejoRunner "C:\forgejo-runner\forgejo-runner.exe" "daemon"
nssm set ForgejoRunner AppDirectory "C:\forgejo-runner"
nssm set ForgejoRunner Start SERVICE_AUTO_START
nssm set ForgejoRunner ObjectName ".\<your-username>" "<password>"
nssm start ForgejoRunner

The ObjectName line is critical. NSSM defaults to running services as LocalSystem, which has its own empty profile and cannot see rustup, cargo, or node installed into C:\Users\<you>\.cargo\bin and %APPDATA%\npm. Running the service as the user that installed the toolchain inherits that user's PATH and profile, so the build just works. The alternative — installing Rust machine-wide under C:\Rust with RUSTUP_HOME and CARGO_HOME set in the Machine environment — also works but is more ceremony.

Note the .\ prefix on ObjectName. It tells Windows the account is local to this machine rather than a domain account; without it, the logon will fail even for a valid local user.

Before starting the service, the account also needs the Log on as a service user right. By default even local admins do not have it. Grant it via secpol.msc → Local Policies → User Rights Assignment → Log on as a service → Add User or Group. Without this right, nssm start ForgejoRunner returns START: Access is denied.

Verify the runner appears as windows-builder at https://git.fiedler.live/-/admin/actions/runners.

6. Smoke test: push a throwaway tag (git tag v0.9.0-test1 && git push origin v0.9.0-test1) and watch both runners pick up their jobs in the Actions tab. Delete the tag after the test runs.

25. Updating Dependencies

Keep dependencies up-to-date periodically (at minimum before each release) to pick up security patches and bug fixes.

Rust crates

# Check for outdated crates (install cargo-outdated if not present)
cargo install cargo-outdated
cd src-tauri && cargo outdated

# Update all crates to the latest version allowed by Cargo.toml constraints
cd src-tauri && cargo update

# To upgrade past semver-pinned versions, edit src-tauri/Cargo.toml
# then run cargo update again. Review changelogs for breaking changes.

# Audit for known security vulnerabilities (install cargo-audit if not present)
cargo install cargo-audit
cd src-tauri && cargo audit

Handling cargo audit findings

cargo audit reports the vulnerable crate name, its version, a RUSTSEC advisory ID, and a link to the full advisory description.

Step 1 — Find why the crate is in your tree:

# Shows which of your dependencies pulls in the vulnerable crate
cd src-tauri && cargo tree -i <vulnerable-crate>

Step 2 — Fix it:

  • Direct dependency (listed in Cargo.toml): bump the version in Cargo.toml to one that resolves the advisory, then run cargo update.
  • Transitive dependency (pulled in by another crate): run cargo update -p <vulnerable-crate> to pull a patched version within the existing semver range. If no patched version exists, update the parent crate that depends on it.
  • No fix available yet: check the advisory details. If the vulnerability does not apply to your usage (e.g., an unused feature or code path), you can temporarily suppress it with cargo audit --ignore RUSTSEC-XXXX-XXXX and track the upstream issue until a fix is released.

After updating, verify the build and lint pass:

cd src-tauri && cargo build && cargo clippy -- -D warnings && cargo test

npm packages

# Check for outdated packages
npm outdated

# Update all packages within the version ranges in package.json
npm update

# To upgrade to new major versions beyond the pinned ranges, use:
npx npm-check-updates -u   # rewrites package.json to latest versions
npm install                 # install the updated versions

# Audit for known security vulnerabilities
npm audit

# Auto-fix vulnerabilities where possible
npm audit fix

After updating, verify the frontend build and lint pass:

npx tsc --noEmit && npx vite build
npx eslint src/ --ext .ts,.tsx
npx prettier --check "src/**/*.{ts,tsx,css,json}"

Tauri CLI

The Tauri CLI is listed as a devDependency in package.json (@tauri-apps/cli) and is updated along with npm packages. Ensure the CLI version stays compatible with the tauri and tauri-build crate versions in Cargo.toml — they should track the same major.minor release line.

Recommended workflow

  1. Create a dedicated branch: git checkout -b deps/update-YYYY-MM-DD
  2. Update Rust crates (cargo update) and npm packages (npm update or npx npm-check-updates -u && npm install).
  3. Run cargo audit and npm audit to check for remaining vulnerabilities.
  4. Run the full lint and test suite (see Verify the Environment).
  5. Test the application manually (connect via SSH/Telnet, open local terminal).
  6. Commit and open a PR for review.

26. Docker SSH/Telnet Testlab

To test the functionality of connecting to a remote system via SSH or Telnet, including with the use of a jumphost, see the Readme in the directory ssh-testlab. The setup allows tests on a small footprint docker container construct, providing an SSH jumphost, an SSH target, and a Telnet target.