rod
0.1.0rod installs and removes software on UraniumOS. It uses the NixOS package repositories through nix-env, keeps one system profile in /rod/profile and takes a scram frame before every change. It also provides package sets, holds and a pinned index.
rod [-n] +OP [ARGUMENT ...] [-OP [ARGUMENT ...]] ...
An "operation" is a letter with a sign, such as +i (install). The arguments after an operation belong to it until the next operation, so one line can contain several. They run from left to right, and rod stops at the first one that fails.
The rod in the stage is a small shell script that knows +i +r +s +l. It installs the first packages, and the full rod from the uranium-tools package replaces it. The cheat sheet lists every operation.
Operations
| Operation | Effect |
+i PACKAGE|@SET... [-i NAME...] | Install. -i leaves out members of the sets. |
+r PACKAGE|@SET... [-r NAME...] | Remove. -r keeps members of the sets. |
+t PACKAGE..., -t PACKAGE... | Hold a package at its version, release the hold. |
+u [URL|REV] | Sync the index and upgrade everything that is not held. |
+h | List what +u would upgrade. |
+s [URL|REV] | Fetch the package index, or pin it to a revision. |
+p | Show the index pin. |
+f WORD | Find recipes and packages. |
+n PACKAGE... | Show the name, description and homepage of an index package. |
+l [explicit] | List installed packages, or only those you asked for. |
+y PACKAGE..., -y PACKAGE... | What a package needs, what needs it. |
+z [PACKAGE...] | Closure size of the profile or of packages. |
+w PATH... | Which package a file belongs to. |
+q PACKAGE... | List the files of a package. |
+x PACKAGE... | Why a package is installed. |
+k | Check the installed files against the store hashes. |
+c [old] | Delete unused store paths. |
+e PACKAGE... [-- COMMAND] | Temporary shell or command with packages on the path. |
+d PACKAGE... [-- COMMAND] | The same with a compiler and the headers and libraries. |
+o FILE, +a FILE | Write the installed set to a file, install from such a file. |
-n | First argument: dry run of +i, +r, +u, +h. |
To go back to an earlier state, use scram: scram flash --boot FRAME, or choose a frame in the boot menu.
How packages resolve
A package name is an attribute of the index in /rod/nixpkgs. It starts with a letter or an underscore and has only letters, digits and _ . ' + -. Any other name stops rod with an error.
For each name rod looks for /rod/recipes/NAME.nix. A recipe is installed first, one at a time. All other names go to a single nix-env call. A recipe replaces the index package of the same name. The image has recipes for avtty, doas-uranium, initramfs-uranium, linux-uranium, uranium-tools, zen and zoneinfo. See recipes.
nix-env uses the binary cache https://cache.nixos.org when it has the package, and builds from source when it does not. The sandbox is off, so a source build runs as the user that started rod, normally root. A package without a cache entry compiles on your machine and can take a long time.
The same pin gives the same package versions on every machine. The builds themselves are not identical bit for bit.
The index and the pin
Nothing that needs the index works before the first rod +s. +i, +f, +n, +h, +e, +d and +o stop and tell you to fetch the index first.
+s- Downloads the tarball from the
CHANNEL=line of/etc/rod.conf. The image sets it tohttps://channels.nixos.org/nixos-26.05/nixexprs.tar.xz, and rod uses the same URL when the line is missing. +s URL- Downloads that tarball with curl and unpacks it by its ending:
.xz,.gzor.tgz.file://URLs work. +s REV- An argument of 40 lowercase hexadecimal characters is a nixpkgs commit. rod fetches
https://github.com/NixOS/nixpkgs/archive/REV.tar.gz.
rod unpacks into /rod/nixpkgs.new and replaces /rod/nixpkgs only when the download and the unpack both worked. A failed download leaves the old index alone. At the end rod tells you the revision of the new index, or that the revision is unknown when the tarball has none.
rod writes the result to /rod/nixpkgs/.pin, and rod +p shows it, or tells you that there is no pin yet:
url https://github.com/NixOS/nixpkgs/archive/08d3a97586f6b6343b3b5e799980773629432610.tar.gz
rev 08d3a97586f6b6343b3b5e799980773629432610
date 2026-11-02
The index moves only on rod +s, rod +u and rod +a with a set that names another revision. Frames do not contain the index, so going back to a frame leaves it where it is.
Install
rod +i NAME... takes a frame labelled pre-install, releases holds on the names, installs them and adds them to /rod/installed. An error before the frame leaves the system untouched.
# rod +s
# rod +f editor
# rod +i git tmux
If a recipe fails nothing is added to /rod/installed, even when earlier names of the same command installed. rod +l shows them and rod +l explicit does not. Run the command again to record them.
Sets
An argument that starts with @ is a set: a file /etc/rod/sets/NAME with one package name per line. Empty lines and lines that start with # are skipped. A set without a file stops rod with an error.
The image ships four sets:
@xorg- xorg-server xinit xf86-input-libinput xkeyboard-config xkbcomp xauth xrandr setxkbmap xterm dbus eudev polkit fontconfig dejavu_fonts mesa xdg-user-dirs shared-mime-info desktop-file-utils adwaita-icon-theme xrdb xmodmap xset xsetroot
@xfce- xfce4-session xfwm4 xfce4-panel xfdesktop xfce4-settings xfconf garcon libxfce4ui xfce4-exo xfce4-appfinder xfce4-notifyd xfce4-terminal xfce4-power-manager thunar mousepad polkit_gnome xfce4-icon-theme
@dev- git gcc gnumake pkg-config gdb strace
@tools- htop ripgrep fd tmux vim wget curl less
Add your own by writing a file in /etc/rod/sets. /rod/installed lists the members, not the set.
Leaving members out with -i
Directly after +i, -i starts a list of names to leave out. The names can be sets too. Each name has to be one of the packages given or a member of a set given:
# rod +i @tools -i vim wget
installs htop, ripgrep, fd, tmux, curl and less, and records those six in /rod/installed:
# rod +l explicit | tail -6
htop
ripgrep
fd
tmux
curl
less
Leaving out a name that nothing named is an error, and so is leaving out everything:
# rod +i @tools -i bash
rod: -i bash: not named by +i, and a run-time dependency of a package cannot be left out
# rod +i curl -i curl
rod: +i: nothing is left after -i
A package that @tools installs may need another package to run. That one comes along and cannot be dropped. -i only chooses among the packages you or a set named.
-i works only directly after +i, and only once. The same goes for -r.
Remove
rod +r NAME... removes packages from the profile. Use the names rod +l prints without the version. They can differ from the attribute you installed with, so bashInteractive is removed with rod +r bash-interactive.
- A name that is not installed stops rod with an error.
- A set stands for those of its members that are installed. The others are skipped without a message.
- If there is nothing to remove, no frame is taken.
- Otherwise rod takes a frame labelled
pre-remove, runsnix-env -eand deletes the names from/rod/installed.
Directly after +r, -r starts a list of names to keep. Each name has to be installed:
# rod +r @tools -r tmux
removes every installed member of @tools except tmux. The removed packages stay in the store until rod +c deletes them.
To install a package again, for example to rebuild it from the current index, remove it and install it in one line:
# rod +r htop +i htop
This takes two frames, pre-remove and pre-install. If the install fails the removal stays.
Holds
rod +t NAME... adds the names to /rod/holds and rod -t NAME... removes them. A hold has one effect: +u and +h skip the package. +i of a held name releases the hold. +o writes the holds to a file and +a sets them again.
Frames before changes
When scram is installed, these operations run scram frame LABEL before they change anything:
| Operation | Label |
+i, +a | pre-install |
+r (when something is removed) | pre-remove |
+u | pre-update |
The frame id is the time plus the label, such as 20261102-101500-pre-install. scram roll lists frames. If scram is missing or the frame fails, rod goes on without one and says nothing, so run scram roll after the first change on a new system. +t, +s, +e and +o change no package and take no frame.
After a successful +i, +r, +u or +a, rod runs scram boot to refresh the boot menu.
scram flash --boot FRAME returns the system, the configuration and the home directories to a frame. A frame is a snapshot on the same disk and is not a backup. See scram and backups.
Updating
rod +h shows what an upgrade would change for every installed package that is not held. It prints nothing when everything is current.
rod +u does this:
- Takes a
pre-updateframe. - Runs the same download as
rod +swith the URL or revision given, so the index moves first. If that fails rod stops with status 1. - If
scram statusworks, rod says that it is updating in a copy of the system, and runs the upgrade inside a copy of the root withscram try. The running system is not touched. scram takes its ownpre-updateframe first, so an update leaves two. When the update works, scram says that it is pending and how to commit it. When it fails, scram throws the copy away and says that the system is untouched. If an earlier update is still pending, scram asks you to commit or discard it first. - If
scram statusfails, rod upgrades the profile in place.
Apply a pending update with scram commit --boot and reboot. It runs on trial and reverts if geiger fails, see scram. The index is already new by then, because the sync ran before the copy was made.
Dry runs
-n or --dry-run as the first argument makes +i, +r and +u show what they would do and change nothing. No frame is taken, no hold is released, and /rod/installed does not change. rod shows the lines of nix-env --dry-run that say what would be installed, removed or upgraded, or says that there is nothing to change.
Output when htop has no cache entry:
# rod -n +i htop
installing 'htop-3.3'
this derivation will be built:
When the packages come from the cache, the download size is shown too. The checks before the install still run, so a dry run of +r for a package that is not installed fails. Other operations after -n stop with an error. -n +u does what +h does.
Looking around
+l- Lists the packages of the profile, one
name-versionper line. +l explicit- Shows
/rod/installed, the names you typed or a set gave, in install order. If the file does not exist yet, rod tells you to install something first. +f WORD- Takes one word. Prints
recipe: NAMEfor each recipe whose name contains it, then the packages of the index whose name or description matches. Needs the index. +n NAME...- Shows the description, homepage and name of an index package, and says if it is installed in the profile. A name that does not exist stops rod with an error. Recipes are not looked up.
+y NAME...- What the installed package needs, as a tree.
-y NAME...- What needs it, without the package itself and the profile.
+z [NAME...]- The size of the profile with everything it needs, or of each named package.
+w PATH...- Shows the package that owns each path, with its name, version and store path. A file outside the store is reported as not in the store. A path that does not exist is skipped.
+q NAME...- Lists the files of an installed package, sorted. Symbolic links are listed as files.
+x NAME...- For a package in the profile, it says if you asked for it or if it came in as a dependency. For anything else in its closure, it shows the chain of packages that pulled it in. A name that is not in the closure stops rod with an error.
+k- Checks every store path of the profile against the hashes in the Nix database. It reports whether the profile is intact and lists every damaged path. The database is on the same disk, so this finds damage and does not tell you who caused it.
+p- Prints the pin file described above.
+y -y +z +q stop with an error for a name that is not in the profile.
Environments
rod +e NAME... opens a shell with the packages on the path and a marker in the prompt. With -- COMMAND it runs the command instead. Nothing is installed or recorded. +d does the same and adds the compiler toolchain with the headers and libraries of the packages. The first use downloads the toolchain. Both need the index and at least one name, and both take names from the index, not from seperate recipe files.
# rod +e hello -- hello
# rod +d zlib
The second line opens a shell in which a compiler finds the zlib headers and library.
Share a package set
rod +o FILE writes the installed set to a text file, and rod +a FILE installs it on another machine.
# rod set
pin 08d3a97586f6b6343b3b5e799980773629432610
pkg git
pkg curl
recipe avtty
hold git
The file holds the index revision, then every name of /rod/installed as recipe NAME or pkg NAME, then the holds. Recipe files are not written, so a recipe has to exist on the other machine too.
+a syncs the index to the revision in the file, installs all pkg and recipe lines with +i, and then sets the holds. It removes nothing. A missing file stops rod with an error. The result has the same package versions as the first machine, though not the same builds.
Cleaning up
rod +c deletes store paths that no generation uses. rod +c old deletes old generations first. Frames can still keep old files on disk, see storage.
Files and variables
/rod/profile- The nix-env profile. rod adds its
bindirectory toPATH. /rod/nixpkgs- The index, with the pin in
.pin. Frames leave it out. /rod/recipes- Recipes, one
NAME.nixper package. /rod/installed- Names you asked for, one per line. rod adds a line after a successful install and removes it after a successful remove. If the attribute and the profile name differ (
bashInteractive,bash-interactive) a stale line stays. /rod/holds- Held names, one per line.
/etc/rod/sets- Package sets.
/etc/rod.conf- Only the line
CHANNEL=URLis read. /etc/nix/nix.conf- Nix settings:
substituters,sandbox = false,experimental-features = nix-command. ROD_ROOT- Replaces
/rod. The store stays in/nix. ROD_SETS- Replaces
/etc/rod/sets. ROD_INNER- Set by
rod +ufor the command it runs in a copy. While it is set rod takes no frames and does not sync. Do not set it by hand. SCRAM_BOOT- The boot partition mount point, default
/boot. NIX_SSL_CERT_FILE- If unset, rod points it at the certificate bundle of the profile and copies it to
SSL_CERT_FILEandCURL_CA_BUNDLE.
Errors
The message says what is wrong. Errors always come before any change, so a failed command leaves the system as it was, except when the install itself fails after the frame was taken.
Limits
- Leave out a run-time dependency of a package.
- Install a chosen version. A hold keeps the installed version and the pin fixes the whole index. Pin an index revision that has the version you need (
rod +s REV). - Upgrade one package.
+uupgrades all packages that are not held. - Run
+s,+lor+cas a dry run. - Run a program that needs libraries the profile lacks. Prebuilt foreign binaries fail unless their libraries are installed, see package not in rod.