File
file manages a regular file, directory, or symlink at an absolute path.
A declared parent directory is always ordered before the files inside it, so depends_on is only needed for other prerequisites.
Which attributes apply depends on ensure:
content,source, andvalidaterequireensure = "file". Without content or source, an existing file keeps its bytes and a new one is empty.targetis required for, and only valid with,ensure = "symlink". Symlinks can’t have content or a mode.ensure = "absent"can’t manage ownership, mode, parents, or type;backupstill applies.
Optional attributes accept null as “not set”. path, ensure, and the boolean policies don’t.
Ownership, parents, and links
If you don’t set owner or group, the current ones are kept, even when the content is replaced. Owners and groups managed in the same configuration are ordered before the file. Changing only ownership or mode updates the object in place. Relative symlink targets are written exactly as given, and Rivet never follows a symlink at the managed path itself.
Existing parent directories are left as they are. Rivet refuses to work through a parent directory that is a symlink, or that someone other than root, the runner, the file’s owner, or the owning account (for paths in its home directory) can write to, unless the directory is sticky like /tmp. When a directory and files inside it are both removed, the files go first.
Candidate validation
With validate, Rivet writes the new content to a private temporary file next to the destination, then runs the validator with {candidate} replaced by that file’s path. The validator must exit 0 and must not modify the file. Only then is the file moved into place; if validation fails, the temporary file is removed and the destination is untouched. Rivet also checks that the destination didn’t change while the validator ran.
Plan and noop don’t run validators. Validators run with the runner’s authority and aren’t sandboxed, so they must only read.
Replacing a different type
Changing an existing object to another type, for example a file to a symlink, requires replace_type = true. Special files and non-empty directories are never replaced, and Rivet never deletes recursively. Files and symlinks are replaced atomically. Converting a directory involves a brief gap between removal and creation, which the journal covers if the run is interrupted.
Safety checks
Rivet verifies each change before reporting it, and leaves objects that already match alone. It refuses to:
- overwrite an object that appeared concurrently while creating a file;
- change a file whose extended attributes, ACLs, or security labels it can’t preserve;
- replace or change metadata of a regular file with more than one hard link.
ensure = "absent"removes only the managed name.
If a timeout interrupts a change after ownership or mode was already applied, the result is reported as a partial change and the journal keeps the work for recovery. There is no attribute for managing ACLs.
Backups
With backup = true, Rivet copies an existing regular file before replacing its content or type, or removing it. If the backup fails, the replacement doesn’t happen. Backups are stored in /var/lib/rivet/backups (mode 0700, files 0600) even when --state-dir points elsewhere. Each backup records the original path, ownership, permissions, and digest.
Backups are kept until you delete them and may contain secrets. Rivet never restores them automatically.
Capabilities and timeout
The default timeout is 60 seconds. Files and directories need file.native.v1. Ownership, symlinks, create_parents, replace_type, and backup need file.complete.v1; owner or group names need file.ownership.nss.v1; and validate needs file.validate.v1. See capabilities and assets.