# Getting Started with InstallRS
This guide walks you through building your first self-contained installer
with InstallRS — from zero to a working executable that copies files,
creates directories, and ships an uninstaller.
## 1. Install the CLI
```sh
cargo install installrs --locked
```
This downloads the latest `installrs` binary to `~/.cargo/bin/installrs`.
Verify it works:
```sh
installrs --help
```
On Linux, if you want to use the GUI wizard, you'll also need GTK3 dev
headers at build time:
```sh
sudo apt-get install -y libgtk-3-dev # Debian/Ubuntu
sudo dnf install -y gtk3-devel # Fedora/RHEL
```
Windows has no extra system dependency.
## 2. Create your installer crate
Make a new library crate — **not** a binary. InstallRS generates the
binary for you from your library's `install` and `uninstall` functions.
```sh
cargo new --lib my-installer
cd my-installer
```
Edit `Cargo.toml` to depend on `installrs`:
```toml
[package]
name = "my-installer"
version = "0.1.0"
edition = "2021"
[dependencies]
installrs = "0.1"
anyhow = "1"
```
## 3. Write `install` and `uninstall` functions
Replace the generated `src/lib.rs` with something like this:
```rust
use anyhow::Result;
use installrs::{source, Installer};
pub fn install(i: &mut Installer) -> Result<()> {
// Parse CLI args (--headless, --components, --log, etc.). Required.
i.process_commandline()?;
i.set_out_dir("/opt/my-app");
i.file(source!("app"), "app").mode(0o755).install()?;
i.file(source!("config.toml"), "etc/config.toml").install()?;
i.dir(source!("assets"), "assets").install()?;
i.uninstaller("uninstall").install()?;
Ok(())
}
pub fn uninstall(i: &mut Installer) -> Result<()> {
i.process_commandline()?;
i.remove("/opt/my-app").install()?;
Ok(())
}
```
Every `source!("path")` references a file or directory the build tool
should embed. Paths are relative to your crate's root. Put the files you
want to ship alongside your `Cargo.toml`:
```text
my-installer/
├── Cargo.toml
├── src/
│ └── lib.rs
├── app # your application binary
├── config.toml
└── assets/
├── icon.png
└── logo.svg
```
## 4. Build the installer
```sh
installrs --target . --output my-installer
```
That's it. You now have a single executable `my-installer` (or
`my-installer.exe` if you pass `--target-triple x86_64-pc-windows-gnu`)
containing all the embedded files, the install logic, and a matching
uninstaller binary.
Pass `-v` for debug output or `-vv` for trace-level build logs if you
want to see what's happening.
## 5. Run the installer
```sh
./my-installer
```
By default, the installer runs in console mode, prints status to stderr,
and installs to the path you set with `set_out_dir`.
**Headless mode** (no GUI, for CI / unattended installs):
```sh
./my-installer --headless
```
**Log to a file:**
```sh
./my-installer --log install.log
```
After install, the uninstaller lives at `/opt/my-app/uninstall` — run it
to remove everything.
## 6. Add a GUI wizard
For a native wizard with welcome screen, license agreement, component
selection, directory picker, progress bar, and finish page, add this to
your installer crate's `Cargo.toml`:
```toml
[package.metadata.installrs]
gui = true
```
Then update `install()` to build a wizard:
```rust
use installrs::gui::*;
pub fn install(i: &mut Installer) -> Result<()> {
i.component("core", "Core files", "Required files", 5).required();
i.component("docs", "Documentation", "User guide + examples", 2);
i.process_commandline()?;
let mut w = InstallerGui::wizard();
w.title("My App Installer");
w.welcome("Welcome", "This wizard will install My App.");
w.license("License", include_str!("../LICENSE.txt"), "I accept");
w.components_page("Components", "Select features:");
w.directory_picker("Install Location", "Install to:", "/opt/my-app");
w.install_page(|ctx| {
let mut i = ctx.installer();
i.set_out_dir(ctx.install_dir());
i.file(source!("app"), "app").mode(0o755).install()?;
if i.is_component_selected("docs") {
i.dir(source!("docs"), "docs").install()?;
}
i.uninstaller("uninstall").install()?;
Ok(())
});
w.finish_page("Done!", "Click Finish to exit.");
w.run(i)?;
Ok(())
}
```
Rebuild and run — you'll get a native Win32 wizard on Windows, a GTK3
wizard on Linux. The same code path also handles `--headless` mode
automatically by running the `install_page` callback inline without a
window.
## 7. Polish — icons, version info, manifest
Add Windows resources (icon, VERSIONINFO, UAC manifest) via
`[package.metadata.installrs]`:
```toml
[package.metadata.installrs]
gui = true
icon = "assets/icon.png" # PNG auto-converts to ICO
execution-level = "requireAdministrator" # UAC elevation
product-name = "My App"
file-version = "1.0.0.0"
legal-copyright = "Copyright (c) 2026 Me"
```
See the full [Windows resource config reference](../README.md#windows-resource-configuration)
for every option.
## What next?
- **Full API reference:** [docs.rs/installrs](https://docs.rs/installrs)
- **Working example:** the [`example/`](../example) directory in the
repo — it demonstrates components, custom pages, translation,
cancellation, and the headless `--yes` flag.
- **Component system:** see the [Components section in the README](../README.md#components)
for details on `--with` / `--without` / `--components` flags.
- **Custom wizard pages:** text inputs, checkboxes, dropdowns — see the
[Custom pages section](../README.md#custom-pages).
## Troubleshooting
**`installrs` version mismatch error when building:**
Your installer crate's `Cargo.toml` specifies an `installrs` version
range that doesn't include the version of the CLI you're running. Either
update the version requirement in your `Cargo.toml`, or install the
matching CLI with `cargo install installrs@<version>`.
**Linux installer doesn't show a GUI:**
Check that `libgtk-3-0` is installed on the target system (runtime, not
just build-time). It's present on virtually every desktop distro by
default but may be absent on minimal server installs.
**"destination already exists" error on install:**
Use `.overwrite(OverwriteMode::Overwrite)` (or `Skip` / `Backup`) on the
`.file()` / `.dir()` builder. Default is `Overwrite`, but you may have
set something stricter.
**Build is slow:**
The generated installer crate uses `lto = true` + `codegen-units = 1` +
`opt-level = "z"` for minimal binary size — this is slow. For faster
iteration during development, override via env vars:
```sh
CARGO_PROFILE_RELEASE_LTO=false \
CARGO_PROFILE_RELEASE_CODEGEN_UNITS=16 \
CARGO_PROFILE_RELEASE_OPT_LEVEL=1 \
installrs --target .
```
## See also
- [Embedded files, builder ops, and progress](embedded-files.md) — the
full API reference for everything you call inside `install` /
`uninstall`.
- [GUI Wizard](gui-wizard.md) — beyond the quick snippet in §6, covers
custom pages, error page, dialogs, and headless mode.
- [Installer API](installer-api.md) — selectable
components plus registering your own `--flags`.
- [Building for production](building.md) — cross-compilation, size and
speed tuning, code signing, release CI.