From a969692d048468e808cfd45f77f2dbf2c7d4150b Mon Sep 17 00:00:00 2001 From: manbo Date: Fri, 10 Apr 2026 16:45:55 +0800 Subject: [PATCH] Upload files to "designs/grel-rs" --- designs/grel-rs/TECHNICAL_DESIGN.md | 310 ++++++++++++++++++++++++++++ 1 file changed, 310 insertions(+) create mode 100644 designs/grel-rs/TECHNICAL_DESIGN.md diff --git a/designs/grel-rs/TECHNICAL_DESIGN.md b/designs/grel-rs/TECHNICAL_DESIGN.md new file mode 100644 index 0000000..ccaef03 --- /dev/null +++ b/designs/grel-rs/TECHNICAL_DESIGN.md @@ -0,0 +1,310 @@ +# 📘 `grel-rs` Technical Design Document +**Binary:** `grel` | **Repository:** `grel-rs` +**Target Platforms:** Linux, Windows (macOS optional) +**Core Philosophy:** Forge-agnostic, pacman-familiar, performance-first, secure, extensible via traits. + +--- + +## 1. Overview & Goals +`grel` (Global/General Release) is a terminal-native, high-performance package manager for downloading and managing binary releases from Git forges. It abstracts provider-specific APIs into a unified resolution/download pipeline, supports HTTP/SOCKS5 proxies, caches optimal DNS endpoints, downloads assets in parallel, and presents a polished, pacman-compatible UX. + +**Key Requirements Met:** +- ✅ Pacman-style CLI (`-S`, `-Syu`, `-Ss`, `--noconfirm`) +- ✅ Proxy support (HTTP/HTTPS/SOCKS5) with env/config fallback +- ✅ DNS/IP latency caching + RTT probing for CDN optimization +- ✅ Parallel asset downloads with multi-progress UI +- ✅ Interactive asset selection (pacman/artix style) with TTY/CI fallback +- ✅ Cross-platform (Linux/Windows) with safe extraction & PATH integration +- ✅ Extensible provider architecture (GitHub, GitLab, Codeberg, Gitea, self-hosted) + +--- + +## 2. Workspace Architecture +``` +grel-rs/ +├── Cargo.toml # Workspace root (resolver = "2") +├── crates/ +│ ├── grel-cli/ # CLI parsing, TUI orchestration, prompts, progress +│ ├── grel-core/ # Resolution, versioning, asset matching, upgrade state +│ ├── grel-providers/ # Forge trait, registry, API implementations (modules) +│ ├── grel-network/ # HTTP client, proxy routing, IP cache resolver, downloader +│ ├── grel-cache/ # SQLite state, IP cache, artifact storage, TTL eviction +│ └── grel-config/ # Layered config (CLI > env > TOML > defaults), migrations +├── tests/ # Integration, mock servers, e2e fixtures +└── scripts/ # CI, release, benchmark helpers +``` + +**Crate Boundaries:** +| Crate | Owns | Depends On | +|-------|------|------------| +| `grel-cli` | CLI, UI, command routing, prompts | `grel-core`, `grel-config`, `dialoguer`, `indicatif` | +| `grel-core` | Package resolution, asset matching, upgrade logic | `grel-providers`, `grel-cache`, `semver` | +| `grel-providers` | API clients, pagination, rate-limit handling | `reqwest`, `async-trait`, `chrono` | +| `grel-network` | Proxy, IP resolver, parallel downloads, streaming | `reqwest`, `hickory-resolver`, `dashmap`, `tokio` | +| `grel-cache` | SQLite state, IP cache, artifact storage, LRU | `sqlx`, `sha2`, `tar`/`zip`, `directories` | +| `grel-config` | Settings loading, validation, defaults, migrations | `figment`, `serde`, `toml` | + +--- + +## 3. Core Abstractions & Data Flow +All forges implement a single normalized contract. `grel-core` parses user input → providers return forge-agnostic structs → `grel-network` downloads blindly. + +```rust +pub enum ForgeType { GitHub, GitLab, Gitea, Codeberg, SelfHosted(String) } + +pub struct PackageRef { + pub forge: ForgeType, + pub owner: String, pub repo: String, + pub version: Option, + pub prerelease: bool, +} + +pub struct ResolvedRelease { + pub version: semver::Version, + pub published_at: chrono::DateTime, + pub is_prerelease: bool, + pub assets: Vec, + pub release_notes: Option, +} + +pub struct DownloadAsset { + pub filename: String, + pub download_url: url::Url, + pub size_bytes: u64, + pub sha256: Option, + pub asset_type: AssetKind, // Binary, Archive, Source, Other +} +``` + +**Flow:** +``` +CLI → PackageRef → ProviderRegistry → Provider.resolve() → ResolvedRelease + → AssetResolver (tiered match + checksum pairing) → SelectedRelease + → Network (IP cache → proxy client → parallel download) + → Cache (verify sha256 → extract → atomic rename → update state.sqlite) + → CLI (progress summary → exit) +``` + +--- + +## 4. CLI Specification & UX Design +`clap 4` with subcommands. Pacman short flags supported via aliases. + +| Command | Aliases | Description | +|---------|---------|-------------| +| `grel sync foo/bar` | `S`, `-S` | Download & install latest | +| `grel sync foo/bar@1.2.3` | `-S` | Pin version | +| `grel search keyword` | `Ss`, `-Ss` | Search across registered forges | +| `grel upgrade` | `Syu`, `-Syu` | Refresh + upgrade installed | +| `grel info foo/bar` | `Si`, `-Si` | Show release metadata | +| `grel list` | `Q`, `-Q` | List installed packages | +| `grel remove foo/bar` | `R`, `-R` | Uninstall | +| `grel clean` | `Sc`, `-Sc` | Purge artifact/metadata cache | +| `grel path add` | - | Inject `bin/` dir into shell/Windows PATH | + +**UX Features:** +- `tabled` for search/list output +- `indicatif::MultiProgress` for parallel downloads +- `owo-colors` for status: đŸŸĸ ✅, 🟡 âš ī¸, 🔴 ❌, đŸ”ĩ â„šī¸ +- `--json` for machine-readable output +- `--noconfirm` / `-y` skips all prompts + +--- + +## 5. PATH Integration & Post-Install Behavior +**Problem:** User-space installs aren't in `$PATH` by default. Pacman installs globally (`/usr/bin`). + +**Solution:** Dedicated `bin/` directory + explicit shell integration. +``` +~/.local/share/grel/ (Linux) → bin/ +%LOCALAPPDATA%\grel\ (Windows) → bin\ +``` + +**Implementation:** +1. `grel sync` extracts binaries to `~/.local/share/grel/bin/` (or equivalent) +2. `grel path add` detects shell (`$SHELL`, `$PROFILE`) and appends: + ```bash + export PATH="$HOME/.local/share/grel/bin:$PATH" # bash/zsh + $env:Path = "$env:LOCALAPPDATA\grel\bin;" + $env:Path # PowerShell + ``` +3. Windows: Uses registry `HKCU\Environment\Path` + broadcasts `WM_SETTINGCHANGE` +4. First-run hint: `💡 Run grel path add to enable global command access` +5. **Why not `/usr/local/bin`?** Avoids `sudo`, respects XDG, prevents system package conflicts. + +--- + +## 6. Configuration vs State Management +**Strict Separation:** +| Layer | Format | Location | Purpose | +|-------|--------|----------|---------| +| **User Config** | `config.toml` | `~/.config/grel/` (Linux) / `%APPDATA%\grel\` (Win) | Proxy, tokens, concurrency, asset preferences | +| **Program State** | `state.sqlite` | `~/.local/share/grel/db/` | Installed packages, IP cache, ETags, last-checked | +| **Artifact Cache** | Files | `~/.cache/grel/artifacts/` | Downloaded archives (LRU evicted, 2GB default) | + +**`config.toml` Example:** +```toml +[general] +version = 1 +max_concurrent = 4 +proxy = "http://127.0.0.1:7890" + +[assets] +default_selection_policy = "largest" # first | largest | preferred_format +prefer_formats = ["*.tar.gz", "*.zip", "*.exe"] +ignore_patterns = ["*source*", "*.deb", "*.rpm"] + +[upgrade] +check_interval_hours = 6 +max_parallel_checks = 10 +``` + +**Migrations:** +- Config: `version` field in TOML. On load, `grel-config` runs migration functions (rename keys, add defaults, warn on breaking changes). +- SQLite: `sqlx migrate` with timestamped files. Applied automatically on startup. Zero-downtime schema upgrades. + +--- + +## 7. Asset Resolution & Selection Pipeline +Releases contain multiple formats. `grel` uses **deterministic tiered matching** + **interactive fallback**. + +### Tiered Matching (Order Matters) +1. **Exact:** `{repo}-{version}-{os}-{arch}.{fmt}` +2. **OS+Arch:** `*{os}*{arch}*.{fmt}` +3. **OS Only:** `*{os}*.{fmt}` +4. **Universal Archive:** `*.{tar.gz,tar.xz,zip}` +5. **Universal Binary:** `*.{exe,elf,dll,dylib}` + +### Checksum & Signature Auto-Pairing +If main asset is `foo-1.0.0-linux-amd64.tar.gz`, `grel` searches for: +- `foo-1.0.0-linux-amd64.tar.gz.sha256` +- `sha256sums.txt` (parses line matching filename) +Automatically downloads & verifies before extraction. + +### Interactive Selection (Pacman/Artix Style) +- Uses `dialoguer::Select` with numbered list +- Default marker: `(default)` shown next to highest-priority match +- `--noconfirm` / `-y` skips prompt, uses default +- **TTY Detection:** If not interactive (CI/pipes), auto-selects default + prints `â„šī¸ Non-interactive mode. Using default: ` to stderr +- Timeout fallback (configurable): auto-selects default if no input within `prompt_timeout_secs` + +**CLI Overrides:** +```bash +grel sync foo/bar --asset custom.exe +grel sync foo/bar --platform linux/aarch64 +grel sync foo/bar --list-assets # Dry-run table +``` + +--- + +## 8. Networking, Proxy & DNS/IP Caching +### Proxy Support +- Native via `reqwest::Proxy::all()`, `http()`, `https()` +- Priority: `--proxy` > `GREL_PROXY` > `http_proxy`/`all_proxy` > `config.toml` +- Supports `http://user:pass@host`, `socks5://...`, `socks5h://...` + +### DNS/IP Caching +âš ī¸ **No hardcoded IPs.** CDNs rotate frequently. Instead: +1. Resolve `A/AAAA` via `hickory-resolver` +2. Parallel `TcpStream::connect` probes to all IPs on `443` +3. Store fastest IP + RTT in SQLite with TTL (default `300s`) +4. Use `reqwest::ClientBuilder::resolve(host, ip)` for routing +5. Background refresh thread evicts stale entries, pre-warms hot domains + +--- + +## 9. Rate Limiting & `-Syu` Optimization +GitHub allows 60 req/hr unauthenticated, 5000/hr authenticated. `grel` handles this gracefully: + +1. **ETag Caching:** `If-None-Match` → `304 Not Modified` **does not count** against rate limits. Cached ETags stored in `state.sqlite`. +2. **Smart Polling:** Only recheck packages where `last_checked < now() - check_interval_hours`. +3. **Rate Limit Awareness:** Parse `X-RateLimit-Remaining` & `X-RateLimit-Reset`. If `< 50`, sleep until reset or queue remaining checks. +4. **Auth Token:** Strongly recommended. `grel` prompts on first run: `🔑 Set GREL_GITHUB_TOKEN for 5000 req/hr`. +5. **`--no-api` Fallback:** Skips remote checks, uses local manifest + cache timestamps. +6. **Parallel Batch Checks:** `max_parallel_checks` limits concurrent API calls to avoid hitting burst limits. + +**`-Syu` Flow:** +``` +Load state → Filter stale → Batch API (ETag) → Parse 200/304 → Build diff → Prompt → Download → Verify → Update +``` + +--- + +## 10. Cross-Platform Implementation Notes +| Aspect | Linux | Windows | Implementation | +|--------|-------|---------|----------------| +| Paths | `~/.local/share/grel` | `%LOCALAPPDATA%\grel` | `directories::ProjectDirs` | +| Archives | `tar` + `zstd`/`xz`/`gz` | `zip` + `tar` | Feature-gated extraction | +| Exec Perms | `0o755` | `FILE_ATTRIBUTE_ARCHIVE` | `cfg(unix)` vs `cfg(windows)` | +| Path Safety | Reject `..`, absolute paths | Same | `sanitize-filename` + strict validation | +| Executable Suffix | None | `.exe` | Auto-append if `cfg(windows)` & missing | +| Shell Integration | `.bashrc`, `.zshrc`, fish | `$PROFILE`, Registry | `grel path add` | + +--- + +## 11. Security & Reliability Guarantees +- ✅ **TLS:** `rustls` only (no native OpenSSL) +- ✅ **Checksums:** Verify `sha256` before extraction +- ✅ **Archive Safety:** Reject `..`, absolute paths, symlinks to outside dest +- ✅ **Atomic Installs:** Download to temp dir → verify → `rename` → register in DB +- ✅ **No Auto-Execute:** Binaries installed but not run unless invoked by user +- ✅ **Token Security:** Loaded from env/config, never logged or serialized to stdout +- ✅ **Graceful Degradation:** Network errors → retry with backoff, rate limits → sleep/queue, missing assets → clear error + suggestions + +--- + +## 12. Dependency Matrix +```toml +# Core & Async +tokio = { version = "1", features = ["full"] } +futures = "0.3" +async-trait = "0.1" + +# CLI & UX +clap = { version = "4", features = ["derive", "wrap_help", "string"] } +indicatif = { version = "0.17", features = ["tokio"] } +tracing-indicatif = "0.3" +dialoguer = "0.11" +tabled = "0.16" +owo-colors = "4" + +# Network & Proxy +reqwest = { version = "0.12", features = ["rustls-tls", "json", "socks", "stream"] } +hickory-resolver = "0.24" +dashmap = "6" + +# Cache & Storage +sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "sqlite"] } +sha2 = "0.10" +directories = "5" +tar = "0.4" +zip = "2" +zstd = "0.13" +xz2 = "0.1" +bzip2 = "0.5" +sanitize-filename = "0.5" + +# Config & Utils +figment = { version = "0.10", features = ["toml", "env"] } +semver = "1" +url = "2" +chrono = { version = "0.4", features = ["serde"] } +serde = { version = "1", features = ["derive"] } +anyhow = "1" +thiserror = "2" +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] } +``` +*Note: Rust 1.70+ required for `std::io::IsTerminal` (replaces `atty`).* + +--- + +## 13. Testing & CI Strategy +- **Unit:** Parsing, version comparison, cache TTL math, asset matching tiers +- **Mocked Providers:** `wiremock` for API responses (200, 304, 403, 429, 500) +- **Integration:** Download test fixtures → extract → verify structure/checksums +- **Cross-Platform CI:** `ubuntu-latest`, `windows-latest` (GitHub Actions) +- **Performance:** `criterion` for DNS resolution + parallel download throughput +- **Fuzzing:** `cargo-fuzz` on `PackageRef` parser + archive path validation +- **Non-Interactive Tests:** `echo "" | grel sync foo/bar` must not hang + +---