feat: initial PSG Launcher scaffold
This commit is contained in:
198
README.md
Normal file
198
README.md
Normal file
@@ -0,0 +1,198 @@
|
||||
# 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
|
||||
```
|
||||
Reference in New Issue
Block a user