Bailey 3b04cc77cb
Some checks failed
Release / build-windows (push) Has been cancelled
feat: v0.2.0 — real branding, desktop shortcut, WiX installer images
2026-05-27 10:42:26 +08:00
2026-05-27 09:01:09 +08:00
2026-05-27 09:01:09 +08:00
2026-05-27 09:01:09 +08:00

PSG Launcher

An all-in-one launcher for PSG tools. Fetches a signed manifest from your Gitea instance, displays available apps, and handles install/update/launch — with OTA updates for both the launcher itself and every app it manages.


Security model

Threat Mitigation
MITM on the wire All fetches use https_only(true) — plain HTTP is rejected at the Rust layer
Tampered manifest apps.json has a detached ed25519 signature (apps.json.sig); the launcher verifies it before parsing
Tampered package Each app package has an inline SHA-256 hash and a separate ed25519 signature; both are verified before writing a single byte to disk
Rollback / replay Tauri's self-updater rejects packages whose version ≤ the running version
Compromised CDN / server The private key never touches the server; only signed content is accepted
Supply chain (npm) Frontend is a thin React shell; all crypto lives in Rust with audited crates

Two separate keys are used:

  • manifest-private.pem — signs apps.json and each app package (your custom key)
  • Tauri signer key — signs the launcher installer for self-updates (generated by Tauri CLI)

Prerequisites

Tool Version
Rust 1.77+
Node.js 20+
OpenSSL 3.x (winget install ShiningLight.OpenSSL.Light)
WebView2 Bundled with Windows 11

First-time setup

1 — Generate the manifest signing keypair

.\scripts\keygen.ps1

This writes keys\manifest-private.pem and keys\manifest-public.pem. Copy the printed base64 value into src-tauri\src\config.rs as MANIFEST_PUBLIC_KEY_B64.

keys\ is in .gitignore. Never commit the private key.

2 — Generate the Tauri self-updater keypair

npm run tauri signer generate -- -w keys\tauri.key

Copy the printed public key into src-tauri\tauri.conf.json under plugins.updater.pubkey.

3 — Update config.rs

Open src-tauri\src\config.rs and replace the three YOURDOMAIN / OWNER placeholders with your actual Gitea instance URL and repository owner name.

4 — Update tauri.conf.json

Replace the YOURDOMAIN / OWNER placeholders in src-tauri\tauri.conf.json.

5 — Install npm dependencies

npm install

Development

npm run tauri dev

The Tauri dev server starts Vite at http://localhost:1420 and hot-reloads the UI.


Building a release locally

npm run tauri build

Installer goes to src-tauri\target\release\bundle\nsis\.


Adding an app to the manifest

  1. Build your app and note its .exe / .zip path.
  2. Sign the package and get its hash + signature:
    .\scripts\sign-package.ps1 -PackagePath .\path\to\your-app.exe
    
  3. Copy the printed values into manifests\apps.json (add a new entry or update current_version).
  4. Re-sign the manifest:
    .\scripts\sign-manifest.ps1
    
  5. Commit, push, and tag:
    git add manifests\
    git commit -m "feat: add your-app v1.0.0"
    git tag v0.2.0
    git push && git push --tags
    

The release workflow triggers automatically, builds the launcher, and pushes the updated manifests to the releases branch where the launcher can reach them.


CI secrets (Gitea repository settings → Secrets)

Secret Value
TAURI_SIGNING_PRIVATE_KEY Content of keys\tauri.key
TAURI_SIGNING_PRIVATE_KEY_PASSWORD Passphrase (or empty string)
MANIFEST_SIGNING_KEY Content of keys\manifest-private.pem
GITEA_TOKEN Personal access token with repo write scope

Manifest structure

manifests/apps.json

{
  "schema_version": 1,
  "generated_at": "ISO 8601 timestamp",
  "minimum_launcher_version": "0.1.0",
  "apps": [
    {
      "id": "my-app",           // stable, URL-safe ID
      "name": "My App",
      "description": "...",
      "category": "tools",      // tools | utilities | games | media | development | network | other
      "current_version": "1.0.0",
      "icon_url": null,         // optional HTTPS URL
      "changelog": "...",
      "tags": [],
      "platforms": {
        "windows-x86_64": {
          "download_url": "https://...",
          "hash_sha256": "lowercase hex",
          "size_bytes": 1234567,
          "install_type": "portable",  // portable | zip | installer
          "install_path": null,        // null = default (%APPDATA%\PSG\<id>)
          "signature": "base64 ed25519 sig of download bytes"
        }
      }
    }
  ]
}

manifests/apps.json.sig

Plain text file containing the base64-encoded ed25519 signature of the raw apps.json bytes.

manifests/latest.json

Tauri's self-update format — updated automatically by the release workflow.


Project layout

PSG-Conduit/
├── src/                     React + TypeScript frontend
│   ├── components/          UI components (AppCard, Sidebar, TitleBar, …)
│   ├── hooks/               useAppManifest, useLauncherUpdate
│   ├── lib/commands.ts      Type-safe Tauri invoke wrappers
│   └── types/manifest.ts    Shared TypeScript types
├── src-tauri/               Rust backend
│   ├── src/
│   │   ├── commands/        Tauri commands (manifest, apps, updater)
│   │   ├── models/          Serde types (manifest, installed)
│   │   ├── config.rs        Public key + manifest URLs
│   │   └── error.rs         Serialisable error enum
│   ├── tauri.conf.json
│   └── capabilities/
├── manifests/               Cloud manifests (committed to `releases` branch via CI)
│   ├── apps.json
│   ├── apps.json.sig        ← generated by sign-manifest.ps1
│   └── latest.json          ← generated by release workflow
├── scripts/
│   ├── keygen.ps1           Generate ed25519 keypair
│   ├── sign-manifest.ps1    Sign apps.json → apps.json.sig
│   └── sign-package.ps1     Get hash + sig for a single app package
└── .gitea/workflows/
    └── release.yml          Build + sign + publish on tag push
Description
No description provided
Readme 10 MiB
Languages
Rust 39.2%
TypeScript 24.5%
PowerShell 20%
CSS 15.7%
HTML 0.5%
Other 0.1%