# Packaging & Release Setup Guide
This document describes how to set up secrets and external repos for automated releases.
## GitHub Actions Secrets
Configure these in **GitHub repo → Settings → Secrets and variables → Actions → Secrets**:
| `CARGO_REGISTRY_TOKEN` | crates.io → Settings → Tokens → New Token (publish-update) | crates.io publish |
| `HOMEBREW_TAP_TOKEN` | GitHub PAT with write access to `CleverCloud/homebrew-misc` | Homebrew formula update |
| `CHOCOLATEY_API_KEY` | community.chocolatey.org → My Account → API Key | Chocolatey package push |
| `SCOOP_BUCKET_TOKEN` | GitHub PAT with write access to `CleverCloud/scoop-bucket` | Scoop manifest update |
| `SNAPCRAFT_STORE_CREDENTIALS` | `snapcraft export-login --snaps mdr-markdown-renderer --acls package_push,package_update -` | Snap Store publish |
| `WINGET_TOKEN` | GitHub classic PAT with `public_repo` scope | WinGet package update |
| `AUR_SSH_PRIVATE_KEY` | SSH key registered on aur.archlinux.org | AUR package update |
### Optional Variables
Configure in **GitHub repo → Settings → Secrets and variables → Actions → Variables**:
| `HOMEBREW_TAP_ENABLED` | `true` | Enable Homebrew tap updates on release |
| `CHOCOLATEY_ENABLED` | `true` | Enable Chocolatey publishing on release |
| `SCOOP_ENABLED` | `true` | Enable Scoop bucket updates on release |
| `SNAP_ENABLED` | `true` | Enable Snap Store publishing on release |
| `WINGET_ENABLED` | `true` | Enable WinGet package updates on release |
| `AUR_ENABLED` | `true` | Enable AUR package updates on release |
Each job is skipped when its variable is not `true`, so a release still succeeds
when a channel is not configured.
## Repos to Create
### `CleverCloud/homebrew-misc`
Homebrew tap for Clever Cloud tools.
1. Create the repo `CleverCloud/homebrew-misc` on GitHub
2. Initialize with a `Formula/` directory
3. Users install with: `brew install CleverCloud/misc/mdr`
### `CleverCloud/scoop-bucket`
Scoop bucket for Clever Cloud tools.
1. Create the repo `CleverCloud/scoop-bucket` on GitHub
2. The release workflow writes `bucket/mdr.json` into it
3. Users install with:
`scoop bucket add clevercloud https://github.com/CleverCloud/scoop-bucket && scoop install mdr`
## Setting Up Homebrew Tap Token
1. Go to **GitHub Settings → Developer settings → Personal access tokens → Fine-grained tokens**
2. Click **"Generate new token"**
3. Name: `mdr-homebrew`
4. Resource owner: **CleverCloud**
5. Repository access: **Only select** `CleverCloud/homebrew-misc`
6. Permissions: **Contents: Read and write**
7. Copy token → add as `HOMEBREW_TAP_TOKEN` secret in mdr repo
## Creating a Release
```bash
# Tag the release
git tag v0.1.0
git push origin v0.1.0
```
This triggers the release workflow which:
1. Builds binaries for macOS (ARM + Intel), Linux (x86_64 + aarch64), and Windows (x86_64)
2. Builds `.deb` packages (Debian/Ubuntu) for `amd64` and `arm64`
3. Builds `.rpm` packages (Fedora/RHEL) for `x86_64` and `aarch64`
4. Publishes to crates.io
5. Creates a GitHub Release with all artifacts, using the `CHANGELOG.md` section
of the tag as release notes
6. Updates the Homebrew formula, the Scoop bucket, the Chocolatey package, the
Snap Store, the WinGet manifest and the AUR package — each one enabled by its
own `*_ENABLED` variable, and each needing its secret to succeed
### Release notes
The release body comes from `CHANGELOG.md`: the workflow extracts the
`## [x.y.z]` section matching the tag (minus the leading `v`) and appends a
"Full Changelog" compare link to the previous tag. The notes file is rebuilt
from scratch on every run, so re-running the workflow for the same tag
*replaces* the body instead of appending to it.
If no section matches the tag, the step emits a warning and falls back to
GitHub's auto-generated release notes — it never fails the release. So: add the
`## [x.y.z] - YYYY-MM-DD` section to `CHANGELOG.md` **before** pushing the tag.
### Linux aarch64
Linux ARM64 binaries and packages are built natively on the GitHub-hosted
`ubuntu-24.04-arm` runners. Cross-compiling from x86_64 was rejected: the
project links against GTK 3, WebKitGTK and OpenGL, which would require a full
aarch64 sysroot with those `-dev` packages.
Limitations:
- `ubuntu-24.04-arm` runners are free for public repositories; on a private
repo they are billed and may need to be enabled for the organisation.
- The AUR package (`mdr-bin`) is still `x86_64`-only.
### Desktop integration in .deb / .rpm
Both packages install, in addition to `/usr/bin/mdr`:
| `assets/mdr.desktop` | `/usr/share/applications/mdr.desktop` |
| `assets/logo-128.png` | `/usr/share/icons/hicolor/128x128/apps/mdr.png` |
| `assets/mdr.1` | `/usr/share/man/man1/mdr.1` |
| `README.md` | `/usr/share/doc/mdr/README.md` |
| `LICENSE` | `/usr/share/doc/mdr/copyright` (`.deb`), `/usr/share/licenses/mdr/LICENSE` (`.rpm`) |
The man page is a hand-written roff file rather than a `clap_mangen` build
script: the `Cli` struct lives in `src/main.rs` and cannot be reused from a
build script without duplicating it. **Keep `assets/mdr.1` in sync when adding
or changing a CLI flag** (check with `mdr --help`, lint with
`mandoc -T lint assets/mdr.1`).
## Installing the .deb / .rpm packages
`mdr` is a GUI application and pulls in GTK 3, WebKitGTK, libxdo and OpenGL.
**Use a package manager that resolves dependencies**, not the low-level
installer:
```bash
# Fedora / RHEL / openSUSE
sudo dnf install ./mdr-0.3.2-1.x86_64.rpm
# Debian / Ubuntu
sudo apt install ./mdr_0.3.2-1_amd64.deb
```
`sudo rpm -i mdr-*.rpm` (and likewise `sudo dpkg -i mdr_*.deb`) **only
installs** — it never fetches anything — so it aborts on the first missing
capability:
```
error: Failed dependencies:
libxdo.so.3()(64bit) is needed by mdr-0.3.2-1.x86_64
```
That message means the package is doing its job: the dependency *is* declared,
and `libxdo.so.3` is provided on Fedora by `xdotool-libs`. `dnf install ./…`
pulls it in automatically. (`rpm -i` can also be used after installing the
dependencies by hand, but there is no reason to.)
### How the dependency list is built
`cargo-deb` (`depends = "$auto"`) and `cargo-generate-rpm` (`auto-req`, left at
its default `auto`) both derive the dependency list from the shared libraries
the binary **links** against — `ldd`-style. That covers GTK 3, WebKitGTK,
JavaScriptCore, libsoup3, libxdo and glib.
It does *not* cover libraries opened with `dlopen()` at runtime, and `mdr` has
some: `x11-dl` and `khronos-egl` (through `winit`/`tao`/`wgpu`) load
`libGL.so.1` and `libEGL.so.1` dynamically. They are therefore declared
explicitly in `Cargo.toml`:
- `.deb`: `depends = "$auto, libgl1, libegl1"`
- `.rpm`: `[package.metadata.generate-rpm.requires]` with
`libGL.so.1()(64bit)` and `libEGL.so.1()(64bit)` (provided by `mesa-libGL`
and `mesa-libEGL` on Fedora)
X11 and Wayland client libraries are deliberately *not* hard requirements:
`libX11` already comes transitively with `gtk3`, and Wayland support must stay
optional.
The release workflow prints the final dependency list of every package
(`dpkg-deb -f … Depends` and `rpm -qp --requires`), so a regression is visible
in the run logs. Note the ordering trap in the `.rpm` job: `rpm` is installed
*after* `cargo generate-rpm`, because the mere presence of
`/usr/lib/rpm/find-requires` switches `cargo-generate-rpm` away from its
builtin `auto-req`.
## Nix Flake
Users can install directly with:
```bash
nix run github:CleverCloud/mdr
```
Or add to a flake:
```nix
{
inputs.mdr.url = "github:CleverCloud/mdr";
}
```
The derivation reads its `pname`, `version`, `description` and `homepage`
straight from `Cargo.toml` (`builtins.fromTOML`), so they cannot drift from the
crate. Dependencies come from the tracked `Cargo.lock` via
`cargoLock.lockFile`, so no `cargoHash` has to be updated on a bump.
## MSRV
`rust-version` in `Cargo.toml` declares the minimum supported Rust version. Two
CI jobs relate to it:
- `fmt` ("Format & manifest") fails if `rust-version` and the `MSRV` variable of
`ci.yml` disagree — a purely textual check that reports in seconds. Bump both
together.
- `msrv` pins that toolchain and runs `cargo check --all-features --all-targets`
plus `cargo test --all-features`. Both jobs are blocking.
1.95 is the highest `rust-version` declared in the dependency tree. The ranking
below it is not written down here: it moves with every dependency bump, and a
stale list is worse than none. It was confirmed by an actual build: `cargo check --all-features --all-targets` on a
1.95.0 toolchain exits 0.
Recompute the floor after a dependency bump with:
```bash
cargo metadata --format-version 1 --all-features \
```
Note that this is a *declared* floor: many resolved packages declare no
`rust-version` at all, including the direct dependencies `mermaid-rs-renderer`
and `tiny-skia`, so the `msrv` job — not this command — is what actually proves
the value. Counting them here would only date the file; the command above is
the answer at the moment it is run.
## crates.io
Automatically published on each release via `cargo publish`.
Setup:
1. Go to https://crates.io/settings/tokens
2. "New Token" → name: `mdr-ci` → scope: publish-update → crate: `mdr`
3. Add as `CARGO_REGISTRY_TOKEN` secret
## WinGet (Windows Package Manager)
Automatically updates the WinGet manifest on each release.
Setup:
1. First release: manually submit `CleverCloud.mdr` to [microsoft/winget-pkgs](https://github.com/microsoft/winget-pkgs) via PR
2. Go to **GitHub Settings → Developer settings → Personal access tokens → Tokens (classic)**
3. Generate new token with `public_repo` scope
4. Add as `WINGET_TOKEN` secret
5. Set `WINGET_ENABLED` variable to `true`
Users install with: `winget install CleverCloud.mdr`
## AUR (Arch Linux)
Automatically updates the `mdr-bin` AUR package on each release.
Setup:
1. Create an account on https://aur.archlinux.org
2. Generate an SSH key: `ssh-keygen -t ed25519 -f ~/.ssh/aur -C "mdr-aur"`
3. Add the public key to AUR: My Account → SSH Public Keys
4. Create the `mdr-bin` AUR package (first time, manually via `git clone ssh://aur@aur.archlinux.org/mdr-bin.git`)
5. Add the private key as `AUR_SSH_PRIVATE_KEY` secret
6. Set `AUR_ENABLED` variable to `true`
Users install with: `yay -S mdr-bin`