building from source - degoya/rDownloader GitHub Wiki

Building from source

Most people run a release archive or the container image — see Installation. This page is for contributors and for anyone who wants to build rDownloader themselves: the prerequisites, a local run, the build for every platform, the plugin components and the checks a change is expected to pass.

What you are building

One Rust binary carries the whole service:

Rust server
├── Axum REST / SSE / MCP
├── Embedded Vue web app
├── SQLite persistence and a prioritized scheduler
├── HTTP / Usenet / torrent / FTP / SFTP / WebDAV and plugin-transfer runners
├── Media / gallery / stream runners
├── LinkGrabber, subscriptions, feeds and hotfolders
├── Bandwidth, power, capacity and notification services
├── Automation and compatibility adapters
├── Post-processing pipeline
├── Wasmtime plugin host
└── Encrypted secret vault

The desktop capture agent rdownloader-capture is a second, small binary; plugins are signed WebAssembly components built separately (see Plugin components below).

Prerequisites

Tool Version Notes
Rust 1.99.0 (pinned), Edition 2024 rust-toolchain.toml installs Rustfmt, Clippy, llvm-tools and the targets wasm32-unknown-unknown (the plugins, without WASI) and x86_64-pc-windows-msvc automatically on the first cargo invocation.
Node / pnpm 24.15.0 or later / 12 Only needed for the web frontend. The floor is jsdom's, the web tests' environment, which declares ^24.15.0 for Node 24; web/package.json holds it (engines). web/package.json pins the pnpm version (packageManager), and corepack enable provides exactly that one.
C toolchain platform-specific See the platform sections below.
cargo-nextest 0.9.146 The test runner of scripts/check.sh and CI: cargo install cargo-nextest --version 0.9.146 --locked.
sqlx-cli 0.9.0 cargo sqlx prepare --check, the offline query data: cargo install sqlx-cli --version 0.9.0 --locked --no-default-features --features sqlite-unbundled.
wasm-tools 1.261.0 Makes the plugin components and reads their imports; scripts/build-plugins.sh insists on exactly this one, because it decides the components' bytes: cargo install wasm-tools --version 1.261.0 --locked.
cargo-xwin 0.23.1 The Windows cross-build and scripts/check.sh --windows: cargo install cargo-xwin --version 0.23.1 --locked.
shellcheck 0.11.0 The script lint of scripts/check.sh and CI: uv tool install shellcheck-py==0.11.0.1, or the distribution's package.
actionlint 1.7.12 The workflow lint beside it: the release binary on PATH.

Only Rust, Node and a C toolchain are needed to build and run rDownloader; the other tools are for the plugin components, the cross-build and the checks. Each version has one source the scripts read: Rust is channel in rust-toolchain.toml, wasm-tools WASM_TOOLS_VERSION in scripts/lib/plugin-stamp.sh, Node engines.node in web/package.json, the others scripts/lib/tool-versions.sh. scripts/tool-versions.sh --check holds every copy in the workflows, the actions, the SDK's CI template and the Dockerfile to those sources, and the checks run it.

Build the web frontend before the first cargo build. The interface is embedded into the binary at compile time from web/dist, so without it the build of rd-api fails:

pnpm --dir web install --frozen-lockfile
pnpm --dir web run build

Release builds use --locked, so exactly the versions from Cargo.lock are used. Results land under target/release/, or target/<TARGET>/release/ when --target is passed.

A second profile, release-test, builds much faster at the cost of slightly larger and slower binaries. It is for test packages — scripts/package-linux.sh --profile release-test, scripts/package-windows.sh --profile release-test, or RD_PACKAGE_PROFILE=release-test for both — and for the packager rd-pack. A package's VERSION.txt names the profile; a release is always built with release. Results land under target/release-test/.

Running from source

pnpm --dir web install --frozen-lockfile
pnpm --dir web run build
cargo run -p rdownloader -- serve \
  --database data/rdownloader.sqlite3 \
  --downloads downloads \
  --listen 127.0.0.1:8710

The interface is then at http://localhost:8710; the first visit sets up the administrator password (see First run). Under WSL2 on Windows 11 the same address works from the Windows browser.

Plugin-related switches of serve:

  • --trusted-plugin-key KEY_ID=BASE64_PUBLIC_KEY (repeatable) trusts an additional release key.
  • --plugin-development-mode accepts unsigned packages. It is for local development only and relaxes nothing else.
  • --plugin-allow-local-targets lets installed transfer backends dial loopback and private-network addresses — a second, separate switch, likewise for local development only.

On startup the installed signatures are verified again; see Installing and trust.

Linux

Needs a C compiler and linker, for example build-essential on Debian and Ubuntu. No further system libraries: TLS uses rustls, and SQLite is bundled.

pnpm --dir web install --frozen-lockfile && pnpm --dir web run build
cargo build --locked --release -p rdownloader -p rd-capture
./target/release/rdownloader --version

A binary runs only on a glibc at least as new as the one of the system that built it; the official Linux release is built on Ubuntu 24.04 (glibc 2.39).

macOS

Needs the Xcode Command Line Tools (xcode-select --install). The release artefacts are built separately for Intel (x86_64-apple-darwin) and Apple Silicon (aarch64-apple-darwin); locally the host target is enough:

pnpm --dir web install --frozen-lockfile && pnpm --dir web run build
cargo build --locked --release -p rdownloader -p rd-capture

For the other target: rustup target add <TARGET>, then cargo build --locked --release --target <TARGET> -p rdownloader -p rd-capture.

Windows

Needs the Visual Studio Build Tools with the Desktop development with C++ workload (MSVC compiler, Windows SDK including rc.exe) and Rust through rustup with the stable-x86_64-pc-windows-msvc toolchain.

pnpm --dir web install --frozen-lockfile
pnpm --dir web run build
cargo build --locked --release --target x86_64-pc-windows-msvc -p rdownloader -p rd-capture
& target/x86_64-pc-windows-msvc/release/rdownloader.exe --version
& target/x86_64-pc-windows-msvc/release/rdownloader-capture.exe --version

The MSVC runtime is linked statically, so the executables need no Visual C++ redistributable. Both binaries embed a long-path manifest, so the Windows SDK resource compiler rc.exe must be on the PATH.

Cross-building for Windows from Linux or WSL2

Needs cargo-xwin, LLVM/Clang, LLD, NASM, CMake and Ninja. cargo-xwin downloads the MSVC CRT and the Windows SDK itself on its first run.

sudo apt-get install -y clang cmake lld llvm nasm ninja-build
cargo install cargo-xwin --version 0.23.1 --locked
rustup target add x86_64-pc-windows-msvc
pnpm --dir web install --frozen-lockfile && pnpm --dir web run build
cargo xwin build --locked --release --target x86_64-pc-windows-msvc \
  -p rdownloader -p rd-capture

scripts/package-windows.sh puts the executables into the (ignored) folder artifacts/windows/; from Windows they are reachable via \\wsl.localhost\<Distro>\home\<user>\projects\rDownloader\artifacts\windows.

The container image

docker/Dockerfile is a multi-stage build with two targets over one runtime stage in debian:trixie-slim (glibc 2.41):

Target Binary Used by
runtime (default) compiled in the image: the frontend in node:24-bookworm-slim, the binary in rust:1.99.0-bookworm scripts/docker.sh build only
prebuilt copied from dist/docker/linux-<arch>/rdownloader in the build context CI, the release workflow, a docker build of your own

Both targets take the bundled plugins from dist/plugins, which only scripts/build-plugins.sh fills: run it before either, or the image ships no bundled plugins at all. scripts/docker.sh warns before it builds, and the service logs no bundled plugin packages found at startup.

Beside rdownloader the runtime carries the external tools the service calls — ffmpeg, yt-dlp, streamlink, gallery-dl, apprise, 7-Zip with its RAR codec and par2. The desktop capture agent is not included.

scripts/build-plugins.sh     # otherwise the image ships no bundled plugins
scripts/docker.sh build      # the runtime target; caps cargo, names the build, works around the Docker Desktop credential helper

To use the prebuilt target yourself, put a Linux binary built on a system with glibc 2.41 or older where it expects one. A missing binary, one for the wrong architecture or one linked against a newer glibc fails the image build:

scripts/build-plugins.sh
mkdir -p dist/docker/linux-amd64
cp target/release/rdownloader dist/docker/linux-amd64/
docker build --file docker/Dockerfile --target prebuilt --tag rdownloader:local .
scripts/docker-smoke.sh rdownloader:local

For a multi-arch image, put the arm64 binary into dist/docker/linux-arm64/ as well:

docker buildx build --file docker/Dockerfile --target prebuilt \
  --platform linux/amd64,linux/arm64 --tag rdownloader:local .

Running the image is described in Docker and NAS.

Plugin components

The plugins are WebAssembly components without WASI: cargo build links each plugin's core module and wasm-tools component new turns it into the component. scripts/build-plugins.sh does both, with exactly the wasm-tools version CI uses:

cargo install wasm-tools --version 1.261.0 --locked
scripts/build-plugins.sh --components-only ddownload

The results are under target/wasm32-unknown-unknown/release/rd_plugin_<name>.wasm. A plugin must import nothing outside rdownloader:plugin, above all no wasi: interface; scripts/check-plugin-imports.sh enforces that with wasm-tools, and the build runs it on every component.

scripts/build-plugins.sh --components-only builds every bundled component that is missing or stale, unsigned, which is what the tests need; --development packages them unsigned into dist/dev-plugins for a service started with --plugin-development-mode. Signed packages need the project's release key.

Packaging, verifying and signing do not need the service: the publishing commands — plugin package|verify|conformance|new|index — also come in the small rd-pack binary (cargo build --profile release-test -p rd-pack), with the same arguments as rdownloader's and without a web build. Writing a plugin of your own starts at Developing a plugin.

Checks

scripts/check.sh runs the checks that matter for a change and only those — it prints what it skipped and why — and scripts/check.sh --full runs everything. The individual commands:

cargo fmt --all --check
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo nextest run --workspace
SQLX_OFFLINE=true cargo sqlx prepare --check --workspace
pnpm --dir web run typecheck          # incremental; typecheck:full is the CI one, oxlint included
pnpm --dir web run lint               # oxlint alone
pnpm --dir web run test
pnpm --dir web run build              # does not type-check; see typecheck:full
scripts/build-extension.sh

The web interface is linted with oxlint, pinned as a development dependency in web/package.json and configured in web/.oxlintrc.json. It runs its default rule set over web/src with --deny-warnings as the last step of typecheck:full, so scripts/check.sh and CI fail on any finding. A rule is switched off in the configuration only with its reason; a single justified exception is an oxlint-disable-next-line <rule> -- <reason> comment on the line before. The browser extension is not linted.

What they need:

  • cargo sqlx prepare --check the SQLx CLI and cargo nextest the Nextest CLI, each at the version in Prerequisites. The supply-chain check is cargo deny check against deny.toml.
  • scripts/build-extension.sh: zip and unzip, because the two archives are the extension's release artefact.
  • cargo nextest run --workspace needs the bundled plugin components to exist, since cargo test never builds for the wasm target: build them with scripts/build-plugins.sh --components-only. A contract test whose component is missing fails and names the command. scripts/build-plugins.sh --list-missing names the missing ones. A checkout without the wasm toolchain leaves those tests out with cargo nextest run -P no-components --workspace, which counts them as skipped, not passed.

Legal test files for manual download checks are listed in testfile/README.md. How a pull request reaches the project is in CONTRIBUTING.md.

How the release artefacts are built

A v*.*.* tag runs .github/workflows/release.yml. It builds both binaries for Linux x86-64 and aarch64, Windows x86-64 and macOS x86-64 and aarch64, packs them with the portable launchers, LICENSE and README.md (the macOS archives with the NZB helper app), and publishes a GitHub release with an SPDX SBOM, SHA256SUMS signed with Sigstore and the plugin repository index rdownloader-plugin-index.json. The signed plugin packages are a second release on the same commit, plugins-vX.Y.Z (never marked latest), with its own signed SHA256SUMS; the index points into it. The multi-arch container image is built from the two Linux binaries (target prebuilt) and pushed to ghcr.io/degoya/rdownloader as v<version> and, for the newest tag only, latest, with provenance and SBOM, signed with cosign.

Related pages

⚠️ **GitHub.com Fallback** ⚠️