# rlru
[](https://github.com/rlrml/rlru/actions/workflows/distributable-binaries.yml)
[](https://crates.io/crates/rlru)
[](https://docs.rs/rlru)
[](#license)
Rust-first Rocket League replay uploader.
rlru uses strict TOML configuration, explicit local state paths, testable
auth/upload boundaries, and a Dioxus client scaffold.
## Crates
This repository is a Cargo workspace. The reusable pieces are published to
crates.io as their own crates:
| [`rlru`](Cargo.toml) | CLI + library for uploading Rocket League replays | [](https://crates.io/crates/rlru) | [](https://docs.rs/rlru) |
| [`psynet`](crates/psynet) | Standalone client for Psyonix's PsyNet RPC backend (Rocket League online services) | [](https://crates.io/crates/psynet) | [](https://docs.rs/psynet) |
`crates/psynet` ([README](crates/psynet/README.md)) is independent of the rest of
rlru and can be depended on directly. The `rlru-dioxus` desktop client also lives
in this workspace but is not published. All published crates currently share a
single version number.
## Library usage
`rlru` is a library as well as a CLI — the binary is a thin wrapper over the
public API, so you can drive config, syncing, and uploads from your own Rust
code.
```toml
[dependencies]
rlru = "0.1"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
```
```rust
use rlru::Config;
use rlru::paths::AppPaths;
use rlru::sync::SyncService;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let paths = AppPaths::discover()?;
let config = Config::load_or_default(&paths.config_file())?;
// Run one sync pass: discover new replays and push them to every
// configured upload destination.
let summary = SyncService::new(paths, config).run_once().await?;
println!("uploaded {} replays", summary.uploaded);
Ok(())
}
```
Key building blocks:
- [`rlru::Config`](src/config.rs) — strict TOML config, accounts, and upload
destinations (`Config::load`, `validate`, helpers like
`UploadDestinationConfig::ballchasing()`).
- [`rlru::sync::SyncService`](src/sync.rs) — the sync engine
(`run_once`, `run_once_with_options`, `current_history`).
- [`rlru::upload`](src/upload.rs) — the upload destination abstraction.
- [`rlru::psynet`](crates/psynet) — the PsyNet client is re-exported as
`rlru::psynet`, so `rlru` users don't need a separate dependency to talk to
PsyNet directly.
Full API docs are on [docs.rs/rlru](https://docs.rs/rlru) and
[docs.rs/psynet](https://docs.rs/psynet).
## Screenshots




## Development
```bash
direnv allow
just check
just run -- --help
just dioxus-desktop
```
## Releases
Releases are driven entirely by the `vX.Y.Z` tag that matches the `rlru` Cargo
package version. All published crates (`rlru`, `psynet`) share that single
version number.
**Fully automatic (recommended):** bump the `version` in `Cargo.toml`,
`crates/psynet/Cargo.toml`, and `crates/rlru-dioxus/Cargo.toml` and merge to
`main`. The [`auto-tag-release`](.github/workflows/auto-tag-release.yml) workflow
notices the new version and pushes the matching `vX.Y.Z` tag for you, which kicks
off the release pipeline.
> For the auto-created tag to trigger the downstream publish jobs, add a
> Personal Access Token (with `contents: write`) as the `RELEASE_PAT` repository
> secret — a tag pushed with the default `GITHUB_TOKEN` will not start new
> workflow runs. Without it, the tag is still created; just re-push it manually.
**Manual:** cut the tag yourself from a clean `main` checkout:
```bash
just release-tag
```
Either way, the tagged run:
- uploads downloadable assets to the GitHub Releases page:
- `rlru-cli-linux-x86_64.tar.gz`
- `rlru-cli-windows-x86_64.zip`
- `rlru-dioxus-linux-x86_64.AppImage`
- publishes `psynet` then `rlru` to crates.io, when the `CARGO_REGISTRY_TOKEN`
repository secret is configured (GitHub release assets are still created when
that secret is absent).
## Windows Builds From Linux
The dev shell includes the Fenix `x86_64-pc-windows-gnu` Rust target and the
MinGW linker toolchain. Build Windows executables from Linux with:
```bash
just windows-cli release
just windows-dioxus release
```
The CLI executable lands under
`target/x86_64-pc-windows-gnu/release/rlru.exe`. The Dioxus desktop executable
lands under `target/x86_64-pc-windows-gnu/release/rlru-dioxus.exe`, with
`WebView2Loader.dll` copied beside it.
## Configuration
Print the effective default configuration:
```bash
rlru config defaults
```
Validate a configuration file:
```bash
rlru --config ~/.config/rlru/config.toml config validate
```
Tokens are stored separately from TOML config under the XDG config directory.
The default upload destinations include Rocky, Ballchasing, and Rocket Sense at
`https://rocket-sense.duckdns.org/api/v1`. For Rocket Sense uploads, set
`ROCKET_SENSE_TOKEN` to a Rocket Sense bearer token before running `rlru sync`,
or configure a command that prints the token to stdout:
```toml
[storage.auth]
kind = "bearer_command"
command = ["pass", "show", "rocket-sense/token"]
```
### Upload names
Replays are uploaded with a templated filename, which most destinations show as
the replay's name. The default produces names like
`2024-01-15.14.30 SaltySphinx Ranked Doubles Win`. Customize it in `[behavior]`:
```toml
[behavior]
upload_name_template = "{YEAR}-{MONTH}-{DAY}.{HOUR}.{MIN} {PLAYER} {MODE} {WINLOSS}"
```
`{PLAYER}` and `{WINLOSS}` are from the synced account's perspective. Available
placeholders:
| `{YEAR}` `{MONTH}` `{DAY}` | Match date (local time, zero-padded) |
| `{HOUR}` `{MIN}` `{SEC}` | Match time (local time, zero-padded) |
| `{PLAYER}` | Synced account's in-match name (falls back to the account name) |
| `{MODE}` | Playlist name (e.g. `Ranked Doubles`, `Tournament`) |
| `{MAP}` | Map name (e.g. `DFH Stadium`) |
| `{WINLOSS}` | `Win` / `Loss` / `Draw` for the synced account |
| `{SCORE}` | Final score as `team0-team1` |
| `{MATCH_ID}` | PsyNet match GUID |
The rendered name is sanitized for use as a filename and gets a `.replay`
extension automatically. Set `upload_name_template = ""` to keep the legacy
match-id filename.
## License
Licensed under either of [Apache License, Version 2.0](LICENSE-APACHE) or
[MIT license](LICENSE-MIT) at your option.