max / makenotwork
1 file changed,
+86 insertions,
-0 deletions
| @@ -13,6 +13,92 @@ Done: Phases 1-8, Git proxy A-D, UX audit (8/8), all remaining features. Deploye | |||
| 13 | 13 | ||
| 14 | 14 | --- | |
| 15 | 15 | ||
| 16 | + | ## OTA Publish Subcommand (Post-Beta) | |
| 17 | + | ||
| 18 | + | Replace `MNW/server/deploy/ota-publish.sh` with a typed Rust implementation. The bash script shells out to `curl` + `python3 -c "import json"` per artifact, fragile and not testable. Three apps (GO, BB, AF) will each publish ~9 artifacts per release; that's a lot of glue to maintain across repos. | |
| 19 | + | ||
| 20 | + | ### Goal | |
| 21 | + | ||
| 22 | + | One command: | |
| 23 | + | ``` | |
| 24 | + | mnw ota publish --app goingson --version 0.3.1 [--dist ./dist] [--notes "..."] | |
| 25 | + | ``` | |
| 26 | + | Discovers artifacts in the dist directory, reads Tauri signatures from `latest.json`, authenticates, and publishes every platform/arch artifact for the given version. | |
| 27 | + | ||
| 28 | + | ### Architecture | |
| 29 | + | ||
| 30 | + | **Layer 1 — `synckit-client::ota` module** (`MNW/shared/synckit-client/src/client/ota.rs`) | |
| 31 | + | ||
| 32 | + | Typed client wrapping the server's existing OTA endpoints. ~200 LOC + tests. | |
| 33 | + | ||
| 34 | + | ```rust | |
| 35 | + | pub struct OtaClient { /* reuses synckit auth + http */ } | |
| 36 | + | ||
| 37 | + | pub struct ReleaseManifest { | |
| 38 | + | pub app_slug: String, | |
| 39 | + | pub version: String, | |
| 40 | + | pub notes: Option<String>, | |
| 41 | + | pub artifacts: Vec<ArtifactEntry>, | |
| 42 | + | } | |
| 43 | + | ||
| 44 | + | pub struct ArtifactEntry { | |
| 45 | + | pub target: Target, // Linux | Darwin | Windows | |
| 46 | + | pub arch: Arch, // X86_64 | Aarch64 | |
| 47 | + | pub path: PathBuf, | |
| 48 | + | pub signature: Option<String>, // Tauri minisign | |
| 49 | + | } | |
| 50 | + | ||
| 51 | + | impl OtaClient { | |
| 52 | + | pub async fn create_release(&self, app_id, version, notes, signature) -> Result<ReleaseId>; | |
| 53 | + | pub async fn register_artifact(&self, release_id, target, arch, size) -> Result<UploadUrl>; | |
| 54 | + | pub async fn upload(&self, upload_url, bytes) -> Result<()>; | |
| 55 | + | pub async fn verify_updater(&self, slug, target, arch) -> Result<bool>; | |
| 56 | + | /// High-level: takes a manifest, does the full publish loop with retry/resume. | |
| 57 | + | pub async fn publish(&self, manifest: ReleaseManifest) -> Result<PublishReport>; | |
| 58 | + | } | |
| 59 | + | ``` | |
| 60 | + | ||
| 61 | + | Tests mock the HTTP layer (existing pattern in `client/sync.rs`). Cross-reference: `synckit-client/docs/todo.md`. | |
| 62 | + | ||
| 63 | + | **Layer 2 — `mnw-cli ota publish` subcommand** (`MNW/mnw-cli/src/commands.rs` + new `src/ota.rs`) | |
| 64 | + | ||
| 65 | + | ~150 LOC. Responsibilities: | |
| 66 | + | ||
| 67 | + | - Argument parsing (`--app`, `--version`, `--dist`, `--notes`, `--dry-run`) | |
| 68 | + | - **Artifact discovery** by convention: | |
| 69 | + | - `dist/{App}_{version}_x64.msi`, `_x64-setup.exe`, `_aarch64.dmg`, `_x86_64.AppImage`, `_aarch64.AppImage`, `_amd64.deb`, etc. | |
| 70 | + | - Or read from a `release.toml` manifest in the app repo (preferred for explicitness) | |
| 71 | + | - **Signature extraction** from Tauri's `latest.json` per platform (when present) | |
| 72 | + | - **Auth** via OS keyring (already used elsewhere in mnw-cli) with env-var fallback (`MNW_OTA_EMAIL/PASSWORD/API_KEY`) | |
| 73 | + | - **Progress UI** — line per artifact: `goingson 0.3.1 linux/x86_64 [====> ] 42 MB / 78 MB` | |
| 74 | + | - **Dry-run mode** — list what would be published, no network calls | |
| 75 | + | - **Idempotency** — re-running after a partial failure should skip already-uploaded artifacts (server returns 409 on duplicate artifact registration; treat as success) | |
| 76 | + | ||
| 77 | + | **Layer 3 — Per-app integration** (optional) | |
| 78 | + | ||
| 79 | + | Each app's `release.sh` (or a `cargo xtask release`) calls `mnw ota publish --app <slug> --version $(grep version Cargo.toml)`. No per-app Rust code needed. | |
| 80 | + | ||
| 81 | + | ### Cutover plan | |
| 82 | + | ||
| 83 | + | 1. Build `synckit-client::ota` with full test coverage. Server endpoints already exist — no server changes needed. | |
| 84 | + | 2. Build `mnw-cli ota publish` subcommand. | |
| 85 | + | 3. Verify against staging: publish a test release for `goingson` 0.3.1, check it appears in updater endpoint. | |
| 86 | + | 4. Delete `MNW/server/deploy/ota-publish.sh`. | |
| 87 | + | 5. Update each app's `docs/deploy.md` to reference `mnw ota publish`. | |
| 88 | + | ||
| 89 | + | ### Trigger / priority | |
| 90 | + | ||
| 91 | + | **Not blocking soft launch** — testers download from `~/Dist` directly during beta; no OTA updates planned in the first week. Build this when GO needs its first post-launch update, which is also when bugs surfaced by testers need to ship to them quickly. | |
| 92 | + | ||
| 93 | + | ### Estimate | |
| 94 | + | ||
| 95 | + | ~1 focused day total: | |
| 96 | + | - `synckit-client::ota` module + tests: ~4 hours | |
| 97 | + | - `mnw-cli ota publish` subcommand + discovery + progress UI: ~3 hours | |
| 98 | + | - Cutover + per-app docs update: ~1 hour | |
| 99 | + | ||
| 100 | + | --- | |
| 101 | + | ||
| 16 | 102 | ## Key Paths | |
| 17 | 103 | ``` | |
| 18 | 104 | mnw-cli/src/ |