Skip to navigation

File

file manages a regular file, directory, or symlink at an absolute path.

file "directory" {
path = "/var/lib/app"
ensure = "directory"
mode = "0755"
}
file "config" {
path = "/var/lib/app/config.json"
content = "{\"enabled\":true}\n"
owner = "root"
group = 0
mode = "0640"
backup = true
validate = ["/usr/bin/python3", "-m", "json.tool", "{candidate}"]
}

A declared parent directory is always ordered before the files inside it, so depends_on is only needed for other prerequisites.

AttributeDefaultBehavior
pathRequiredAbsolute path, without ...
ensurefilefile, directory, symlink, or absent. present means file.
contentUnmanagedFile content as a string. Can’t be combined with source.
sourceUnsetA file in the configuration directory, or an s3:// or gs:// object. See assets.
checksumUnsetsha256: followed by 64 lowercase hex digits. Requires source; required for cloud sources.
modeUnmanagedOctal string such as "0644". New files get 0644 and new directories 0755.
validateUnsetCommand, as a list of arguments, including {candidate}. See candidate validation.
targetRequired for symlinksLink target, absolute or relative.
owner, groupUnmanagedLocal account name or numeric ID.
create_parentsfalseCreate missing parent directories, owned by the runner with mode 0755.
replace_typefalseAllow replacing an existing object of a different type.
backupfalseBack up an existing file before replacing or removing it.

Which attributes apply depends on ensure:

  • content, source, and validate require ensure = "file". Without content or source, an existing file keeps its bytes and a new one is empty.
  • target is 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; backup still applies.

Optional attributes accept null as “not set”. path, ensure, and the boolean policies don’t.

file "settings" {
path = "/etc/example/settings"
content = "enabled=true\n"
owner = "root"
group = 0
mode = "0640"
create_parents = true
backup = true
}
file "current" {
path = "/opt/example/current"
ensure = "symlink"
target = "releases/current"
create_parents = true
replace_type = true
}

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.