Files
PSG-Conduit/README.md

199 lines
6.1 KiB
Markdown

# 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
```powershell
.\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
```powershell
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
```powershell
npm install
```
---
## Development
```powershell
npm run tauri dev
```
The Tauri dev server starts Vite at `http://localhost:1420` and hot-reloads the UI.
---
## Building a release locally
```powershell
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:
```powershell
.\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:
```powershell
.\scripts\sign-manifest.ps1
```
5. Commit, push, and tag:
```powershell
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`
```jsonc
{
"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
```