# Tool catalogue

Every tool a plan step can call, grouped by namespace, with its arguments and whether a dry run can predict what it would do.

Source: https://opafra.com/docs/reference/tools

---

A step calls a tool by name and passes it arguments. This page lists every tool, what it
does, and which of them a [dry run](/docs/running/dry-run) can simulate against the host
rather than merely display.

## How to read a tool

A tool name is `namespace.verb`, and it goes in a step's `tool` field:

```yaml title="a step calling a tool"
- name: Allow HTTPS through the firewall
  tool: ufw.allow
  args:
    port: 443
    protocol: tcp
```

Every tool also receives the target host implicitly, so `server_name` is never something
you pass. Arguments listed as `none` take nothing beyond the target.

The **dry run** column says whether the tool supports a real read-only check. A `yes`
means a dry run contacts the host and reports `would create`, `would change` or `no
change`. A blank means the dry run shows you the resolved command it would execute, but
does not predict its effect. That distinction is explained in full under
[dry runs](/docs/running/dry-run).

> **Note**
>
> Nine namespaces cover most plans: `ssh`, `file`, `systemd`, `apt`, `user`, `ufw`, `cron`,
> `template` and `docker`. The rest are conveniences that wrap a command you could otherwise
> write by hand.

## Choosing a tool over a raw command

`ssh.cmd` runs any shell command, which makes it the tool that can do anything and the
tool that can tell you least. Opafra cannot preview it, cannot report whether it changed
anything, and cannot make it idempotent.

Prefer a named tool where one fits. `file.lineinfile` will not duplicate a line it already
wrote; the equivalent `echo >>` will, every time the plan runs. `apt.install` reports that
a package was already present; `ssh.cmd: apt-get install -y nginx` reports that a command
exited zero.

> **Warning**
>
> `ssh.cmd` and `ssh.script.file` run whatever text you give them, with whatever privilege
> the connecting user holds. They are the two tools where a mistake in a variable becomes a
> mistake on the host, so they are worth a dry run even though it can only show you the
> resolved text.

## Every tool

| Tool | What it does | Arguments | Dry run |
|---|---|---|---|
| `apt.install` | Install packages via apt-get (non-interactive) | `packages` | yes |
| `apt.update_upgrade` | Update apt cache and upgrade all installed packages | none |  |
| `archive.extract` | Extract a gzipped tar archive to a directory | `archive_path`, `dest_dir` |  |
| `backup.create_tar` | Create a timestamped tar.gz backup of a directory | `backup_path`, `source_path` |  |
| `backup.rotate` | Delete backup files older than N days from a directory | `backup_dir`, `days` |  |
| `certbot.install` | Install Certbot and the nginx plugin via apt-get | none |  |
| `certbot.obtain` | Obtain an SSL certificate for a domain using Certbot + nginx | `domain`, `email` |  |
| `certbot.renew` | Renew all Certbot certificates quietly | none |  |
| `cron.add` | Add a cron entry (idempotent - will not duplicate an identical line) | `schedule`, `command` | yes |
| `cron.list` | List all cron jobs in the current crontab | none |  |
| `disk.usage` | Show disk usage for a given path | `path` |  |
| `disk.cleanup` | Delete files older than N days under a path | `path`, `days` |  |
| `docker.run` | Run a Docker container in detached mode | `args`, `image` |  |
| `docker.compose_up` | Start services defined in a Docker Compose file | `compose_path` |  |
| `docker.compose_down` | Stop and remove services defined in a Docker Compose file | `compose_path` |  |
| `file.template` | Write text content directly to a file on the remote server | `remote_path`, `content` |  |
| `file.lineinfile` | Ensure a line is present in or absent from a config file (idempotent, like Ansible lineinfile) | `path`, `regexp`, `line`, `state` | yes |
| `file.backup` | Copy a remote file to a backup path | `remote_path`, `backup_path` | yes |
| `git.clone` | Clone a git repository to a destination path | `repo`, `dest` |  |
| `git.pull` | Pull latest changes in an existing git repository | `repo_path` |  |
| `helm.upgrade` | Install or upgrade a Helm release with retry on lock conflicts | `release`, `chart`, `helm_args` |  |
| `helm.status` | Show the status of a Helm release | `namespace`, `release` |  |
| `helm.install` | Install a Helm chart from a repository | `chart_name`, `chart_repo`, `helm_args` |  |
| `kube.apply` | Apply a Kubernetes manifest to a namespace | `manifest`, `namespace` |  |
| `kube.wait_rollout` | Wait for a Kubernetes rollout to complete | `kind`, `name`, `namespace`, `timeout` |  |
| `log.tail` | Show the last N lines of a log file | `lines`, `log_path` |  |
| `logs.grep` | Search a log file for a pattern and return last N matches | `pattern`, `log_file`, `lines` |  |
| `monitoring.disk_alert` | Alert if any filesystem is above a usage threshold | `threshold` |  |
| `monitoring.cpu_load` | Show 1/5/15 minute CPU load averages | none |  |
| `monitoring.swap_usage` | Show current swap memory usage as a percentage | none |  |
| `monitoring.memory_check` | Show current RAM usage as a percentage | none |  |
| `mysql.dump` | Dump a MySQL database to a SQL file using mysqldump | `host`, `user`, `password`, `database`, `output_file` |  |
| `network.ping` | Ping a host N times and report packet loss | `count`, `host` |  |
| `network.port_check` | Check if a TCP port is open on a remote host | `host`, `port` |  |
| `network.curl_health` | Make an HTTP health-check request and return status code | `url` |  |
| `nginx.install` | Install nginx via apt-get | none | yes |
| `nginx.config` | Write an nginx configuration file to the server | `config_content`, `config_path` |  |
| `nginx.reload` | Test nginx config and reload the service | none | yes |
| `nginx.test_config` | Test the nginx configuration syntax (read-only; falls back to sudo if the config is unreadable) | none |  |
| `nodejs.install` | Install a specific Node.js version from NodeSource | `version` |  |
| `nodejs.install_pm2` | Install PM2 globally and configure it to start on boot | none |  |
| `npm.install` | Install an npm package inside an application directory | `app_path`, `package` |  |
| `pm2.start` | Start a Node.js application with PM2 and save the process list | `app_path`, `entry_file`, `app_name` |  |
| `pm2.restart` | Restart a PM2-managed application | `app_name` |  |
| `pm2.logs` | Show recent PM2 logs for an application | `app_name`, `lines` |  |
| `postgres.dump` | Dump a PostgreSQL database to a SQL file using pg_dump | `password`, `host`, `user`, `database`, `output_file` |  |
| `process.list` | List running processes matching a pattern | `pattern` |  |
| `rsync.sync` | Sync files between two paths using rsync | `source`, `destination` |  |
| `security.disable_root_ssh` | Disable root SSH login (matches commented, prohibit-password and spaced variants) | none |  |
| `service.health` | Check whether a systemd service is active | `service` |  |
| `ssh.cmd` | Run any shell command on the target server over SSH | `command` |  |
| `ssh.ping` | Check SSH reachability of the target server | none |  |
| `ssh.script.file` | Upload and execute a script file on the target server | `script_name`, `script_args`, `cleanup` |  |
| `ssl.check_expiry` | Check SSL certificate expiry dates for a domain | `domain` |  |
| `system.uptime` | Show how long the server has been running | none |  |
| `systemd.status` | Show the status of a systemd service | `service` |  |
| `systemd.start` | Start a systemd service | `service` | yes |
| `systemd.restart` | Restart a systemd service | `service` | yes |
| `systemd.enable` | Enable a systemd service to start on boot | `service` | yes |
| `template.deploy` | Deploy a config template to a target server path | `template_id`, `template_name`, `target_path`, `variables`, `environment_scope`, `owner`, `mode`, `backup` | yes |
| `template.verify` | Verify a deployed config template matches the expected hash | `target_path`, `deployment_id` |  |
| `ufw.enable` | Enable the UFW firewall | none | yes |
| `ufw.allow` | Allow a port/protocol through UFW | `port`, `protocol` | yes |
| `ufw.status` | Show UFW firewall rules and status | none |  |
| `user.create` | Create a new Linux user with a home directory | `username` | yes |
| `user.exists` | Check whether a Linux user account exists | `username` |  |
| `user.delete` | Delete a Linux user account | `userdel_flags`, `username` | yes |
| `user.set_shell` | Change the default shell for a Linux user | `shell`, `username` |  |
| `user.ensure_group` | Create a Linux group if it does not already exist | `group` |  |
| `user.add_to_group` | Add a Linux user to a supplementary group | `username`, `group` |  |
| `user.set_groups` | Set the full list of supplementary groups for a Linux user | `groups`, `username` |  |
| `user.set_password` | Set the password for a Linux user account | `username`, `password` |  |
| `user.add_ssh_key` | Add an SSH public key to a user's authorized_keys | `username`, `ssh_key` |  |
| `yum.install` | Install packages via yum (RPM-based systems) | `packages` | yes |

## Namespaces at a glance

| Namespace | Tools | Covers |
|---|---|---|
| `apt`, `yum`, `npm` | 4 | Package installation |
| `archive`, `backup`, `rsync` | 4 | Files moved, packed and rotated |
| `certbot`, `ssl` | 4 | Certificates and expiry |
| `cron` | 2 | Scheduled entries on the host |
| `disk`, `system`, `process`, `monitoring` | 8 | Reading host state: usage, load, memory, processes |
| `docker`, `helm`, `kube` | 8 | Containers and clusters |
| `file`, `template` | 5 | Config files, written and verified |
| `git` | 2 | Repositories on the host |
| `log`, `logs` | 2 | Reading and searching logs |
| `mysql`, `postgres` | 2 | Database dumps |
| `network` | 3 | Reachability and health checks |
| `nginx` | 4 | Install, configure, test, reload |
| `nodejs`, `pm2` | 5 | Node runtimes and process management |
| `security`, `ufw` | 4 | Hardening and firewall rules |
| `service`, `systemd` | 5 | Service state |
| `ssh` | 3 | Raw commands and uploaded scripts |
| `user` | 9 | Accounts, groups, shells and keys |

## Arguments and variables

Any argument can be a literal or a `{{ vars.x }}` reference resolved at run time from plan
variables, environment variables, or values supplied when the run starts.

```yaml title="the same step, parameterised"
- name: Allow the app port
  tool: ufw.allow
  args:
    port: "{{ vars.app_port }}"
    protocol: tcp
```

A variable that stays unresolved is empty at run time, which is rarely what was meant. A
dry run lists every unresolved variable it found, which is the cheapest place to catch it.

Arguments naming a credential should be a `secret://` reference rather than a literal.
References are resolved on the host at the moment of use and are redacted everywhere a
command is displayed or logged.

## Next steps

- [Dry runs](/docs/running/dry-run) for what the dry run column actually means
- [Core concepts](/docs/concepts) for how steps, tools and plans fit together
