Quickstart

Resolve targets

DNS queries return all IPs that are found:

astu resolve -T something.com
104.21.59.206
172.67.183.168

Ports are also supported and preserved:

astu resolve -T localhost:22
127.0.0.1:22

Ping targets

astu ping -T localhost:22

CLI

At startup, astu will source information about its environment:

  • The full cmdline of the current process
  • Number of Tokio threads
  • Ulimits
  • Status of whether stdin/stdout/stderr are TTYs
$ASTU_DATA_DIR
├── astu.db
└── logs/
    ├── 019cacd7-f900-7e20-af4b-ff53ef7f8a9f.log
    └── latest.log -> 019cacd7-f900-7e20-af4b-ff53ef7f8a9f.log

The database will be created upon first use. Each run will ensure the database schema is migrated. The latest action-like run will be persisted in the database as the latest job ID (this may not always equal latest.log as such).

Each invocation will generate a unique run ID - this will be used as both the job ID as well as the name of the debug log file. IDs are UUIDv7 strings so they are time-sortable. Debug logs (via tracing-appender) will be written to this file, and a symlink latest.log will always point to the latest run debug log.

Interactive progress will be displayed with tracing-indicatif if stderr is a TTY. Outputs such as tables and JSON will be printed to stdout. Plans and prompts for confirmation will be printed on stderr.

Subcommand Groups

Astu subcommands are broken down into a few groups:

  • Action
  • Result
  • Other

Options

Flags are grouped based on scope and shared usage. Generally, each one will have a hierarchy like this:

  • Subcommand flags
  • Subcommand group flags
  • Global flags

Global Options

Astu has a few global flags that are shared by all subcommands.

--data-dir

Env: ASTU_DATA_DIR

Default: $XDG_DATA_HOME/astu (if XDG_DATA_HOME set); dirs crate default per platform (otherwise)

Astu database path.

--log-level

Env: ASTU_LOG

Default: debug

Filter directive for log file. Follows the RUST_LOG format.

-o/--output

Default: text

Possible values: text, json

Output format.

-h/--help

Prints help.

Action

Actions resolve targets and perform operations on that target set.

The general sequence goes like this:

  1. Resolve input target queries into into a set of unique targets
  2. Present an action plan and require either interactive approval or --confirm=<targets> with the exact number of targets that the action will affect
  3. Perform the sequence of actions defined by the subcommand for each target in concurrently, bounded by --concurrency, displaying progress using indicatif as tasks complete
  4. Display freq info for errors only (ie, automatically run astu freq error) and suggestions for the command to run next, ie astu freq or astu output.

If astu receives a ctrl-c interrupt during a run: currently running tasks will wait for completion, while not-yet-started tasks will be persisted as canceled. Canceled jobs may be resumed with astu resume. A second ctrl-c will forcefully kill the run without waiting for running tasks to complete.

Options

Action Options

-T/--target

Target URI or short form.

If not passed, will default to the local: target. Can be passed multiple times.

-f/--target-file

Path to a file to read target URIs from.

Can use - to read from stdin. If this is set, then --stdin is assumed to be target. Can be passed multiple times.

--stdin

Default: auto

Possible values: auto, param, target, pipe

How to interpret stdin.

auto sets the mode based on this chain of priority:

  1. If {param} is used in the command template -> param
  2. If --target-file is - or /dev/stdin -> target
  3. Else -> pipe

param splits incoming stdin into tokens based on whitespace.

target allows --target-file to read from stdin (must still be passed on its own).

pipe multiplexes stdin to each of the tasks by writing to a spool file where each task has a cursor, guaranteeing delivery.

--timeout

Default: 30s

Per-task timeout value in humantime. 0 indicates no timeout.

--confirm

Auto-accept the plan if passed target count is correct.

Required if running non-interactively to proceed with action. Skips prompt for confirmation if running interactively.

astu lookup

Alias: l, resolve

Resolves targets.

Expands a set of input targets into a set of actionable targets. Does not display a plan, and no actual actions are performed on targets.

Examples

Resolve the default target

astu lookup
Output
local:

Resolve a single target

astu lookup -T ssh://user@host
Output
ssh://user@host

Resolve multiple targets

astu lookup -T cidr://user@[::1]:22/127
Output
ip://user@[::]:22
ip://user@[::1]:22

Resolve targets from files, stdin, and flags

cat targets.txt \
| astu lookup \
    -f targets_a.txt \
    -f targets_b.txt \
    -f - \
    -T ssh://user@host
Output
dns://foo@host-from-a
dns://bar@host-from-b
dns://baz@host-from-stdin
dns://quux@host-from-stdin
ssh://user@host

astu ping

Alias: p

Pings targets.

Performs this sequence of actions on each target in the set:

  • Connect
  • Ping

Persists the output of ping (if it exists) as stdout, as well as the timing of each phase. Exitcode and stderr will never exist.

Examples

Ping a target with no errors

astu ping -T ssh://user@host
Output
error-freq
(no rows)

Ping targets with some errors

astu ping -f targets-total-10.txt
Output
error-freq
| value     | count | pct |
|-----------|-------|-----|
| foo error | 3     | 30% |
| bar error | 2     | 20% |
| baz error | 1     | 10% |

Use `astu output` or `astu freq` for result analysis

astu run

Alias: r, exec

Runs a command on targets.

Performs this sequence of actions on each target in the set:

  • Connect
  • Auth (if required) (until authenticated)
  • Exec

Persists stdout/stderr/exitcode/error, as well as the timing of each phase.

Templates

Template strings that will be substituted with per-task context. Can also be reverse substituted with --dedupe to reduce output cardinality. Not all templates are guaranteed to exist; if a template cannot be substituted, the job will fail fast.

  • {param}: Split param from --stdin=param mode
  • {host}: Target hostname
  • {user}: Target login username
  • {ip}: Target IP address

Options

Arguments

<COMMAND>

Command template.

Options

--live

Stream task stdout and stderr the terminal.

Useful for things produce live tails of information such as bpftrace. Not to be used with fullscreen programs like Vim. Output will still be captured to the database.

--dedupe

Default: param, host, user, ip

Possible values: See TEMPLATES.

Deduplicators for line normalization.

These values will be substituted for their template tokens when seen, so that aggregations like astu freq can more usefully display values that differ predictably. Also helps with db size.

Examples

Execute a command on an SSH target

astu run -T ssh://user@host 'whoami'
Output
TODO

Result

Display results/output from prior runs.

Options

Result Options

-j/--job

Default: Last run job ID

Job ID to display results for.

If not set, will use the last action job ID persisted in the DB.

astu output

Alias: o, out

Displays tables of captured stdout/stderr/exitcode/error per task in a job.

Examples

Display all fields for all tasks in the last job

astu output
Output
TODO

Display all fields for an explicit target in the last job

astu output -T ssh://user@host
Output
TODO

Display all fields for all tasks where that field contains a string in the last job

astu output --contains=needle
Output
TODO

Display only stdout for all tasks in an explicit job

astu output stdout --job=746677e7-b6f9-458b-857e-aa6a8638e101
Output
TODO

astu freq

Alias: f

Displays tables of captured stdout/stderr/exitcode/error aggregated by count of appearance in a job.

Empty strings in stdout/stderr will be displayed as such. Exitcodes that do not exist (such as the task erroring before a real exitcode is received) will be displayed as -1. Tasks that did not error at all will not be displayed in the error table; thus it could potentially not sum to 100%. All other tables will sum to 100%.

Examples

Display all fields aggregated in the last job

astu freq
Output
stdout
| value    | count | pct |
|----------|-------|-----|
| foo      | 6     | 60% |
| bar      | 3     | 30% |
| baz      | 1     | 10% |

stderr
(no rows)

exitcode
| value | count | pct  |
|-------|-------|------|
| 0     | 2     | 100% |

error-freq
| value     | count | pct |
|-----------|-------|-----|
| foo error | 3     | 30% |
| bar error | 2     | 20% |
| baz error | 1     | 10% |

Display all fields aggregated where that field contains a string in the last job

astu freq --contains=foo
Output
stdout
| value    | count | pct |
|----------|-------|-----|
| foo      | 6     | 60% |

stderr
(no rows)

exitcode
(no rows)

error-freq
| value     | count | pct |
|-----------|-------|-----|
| foo error | 3     | 30% |

Display only stdout aggregated for all tasks in an explicit job

astu freq stdout --job=746677e7-b6f9-458b-857e-aa6a8638e101
Output
stdout
| value    | count | pct |
|----------|-------|-----|
| foo      | 6     | 60% |
| bar      | 3     | 30% |
| baz      | 1     | 10% |

astu trace

Displays a diagnostic trace of timings for the sequence of actions and observed errors for tasks in a job.

Examples

Trace an SSH target

astu trace -T ssh://user@host
Output
TODO

Other

Other subcommands that do not fit into the action or result groups.

astu jobs

Alias: j, job

Displays a table of jobs and their metadata.

Examples

Display all jobs

astu jobs
Output
| job_id                               | started_at                 | command                           | task_count |
|--------------------------------------|----------------------------|-----------------------------------|------------|
| 019ca7da-534f-7fa0-874a-7af651acbd65 | 2026-03-01 05:23:49.199082 | /usr/bin/printf 'x=%s\n' '{param} | 2          |

astu tasks

Alias: t, task

Displays a table of tasks and their metadata within a job. The last job will automatically be used if job is not explicitly passed.

Options

Options

-j/--job

Default: Last run job ID

Job ID to display results for.

If not set, will use the last action job ID persisted in the DB.

Examples

Display all tasks in the last job

astu tasks
Output
| task_id                              | target            | status   |
|--------------------------------------|-------------------|----------|
| 019ca7da-534f-7fa0-874a-7b17b297d9e1 | local://localhost | complete |
| 019ca7da-534f-7fa0-874a-7b067d6636f7 | local://localhost | failed   |

astu gc

Cleans the database of jobs and their associated data.

Examples

Delete data that was collected 30 days ago and older

astu gc --before=30d
Output
TODO

Architecture

Targets

A target is the basic unit of operation in Astu - it represents an object on which an action will be performed.

Targets are usually parsed from URIs, but they also support convenient short forms for common types. Not all target types support short forms; those that do will state so.

Here is an example long form for an IP target:

ip://127.0.0.1

While here is the equivalent short form for the same target:

127.0.0.1

Targets can be dynamically expanded and aggregated into other targets using resolvers. The core Astu workflow revolves around dynamic target discovery using resolvers. Generally, one provides at least one seed target which is iteratively expanded using resolver chains.

Clients are drivers that perform actions on targets.

Target Graph

A target graph is special target-centric data structure representing targets as a directed graph. Resolvers generally support resolving directly into a caller-provided target graph - this is useful for building a topological action plan.

Here's a simple example of the target graph for a DNS target that resolves to multiple different IP targets:

digraph {
    rankdir=LR;
    0 [ label="dns://something.com"]
    1 [ label="ip://104.21.59.206"]
    2 [ label="ip://172.67.183.168"]
    0 -> 1 [ ]
    0 -> 2 [ ]
}

While here's a more complex example where targets have multiple parents. In this example, both the CIDR target 10.0.0.0/31 and the DNS target myrouter.lan point to the IP target 10.0.0.1.

digraph {
    rankdir=LR;
    0 [ label="cidr://10.0.0.0/31"]
    1 [ label="ip://10.0.0.0"]
    2 [ label="ip://10.0.0.1"]
    3 [ label="dns://myrouter.lan"]
    0 -> 1 [ ]
    0 -> 2 [ ]
    3 -> 2 [ ]
}

Target Types

For each target type, the URI and short forms will be given along with some examples.

IP

Internet Protocol (IP) address.

  • URI form: ip://[user@]<ip>[:port]
    • ip://127.0.0.1
    • ip://root@127.0.0.1:22
    • ip://[::1]
    • ip://root@[::1]:22
  • Short form: <ip>[:port]
    • 127.0.0.1
    • 127.0.0.1:22
    • ::1
    • [::1]:22

TCP

Essentially an alias for IP.

CIDR

Classless Inter-Domain Routing (CIDR) block.

  • URI form: cidr://[user@]<ip>[:port]/<prefix>
    • cidr://127.0.0.0/32
    • cidr://root@127.0.0.0:22/32
    • cidr://[::1]/128
    • cidr://root@[::1]:22/128
  • Short form: <ip>/<prefix>
    • 127.0.0.0/24
    • ::1/128

DNS

Domain Name System (DNS) record.

  • URI form: dns://[user@]<name>[:port]
    • dns://localhost
    • dns://root@localhost:22
  • Short form: n/a

SSH

Secure Shell (SSH) address.

  • URI form: ssh://[user@]<host>[:port]
    • ssh://127.0.0.1
    • ssh://localhost
    • ssh://root@localhost:2222
  • Short form: n/a

File

Local file.

  • URI form: file:[//]<path>
    • file:///absolute/file.txt
    • file://relative/file.txt
    • file:relative/file.txt
  • Short form: <path> (if path exists locally)
    • /absolute/file.txt
    • relative/file.txt

Kubernetes

Kubernetes pod.

  • URI form: k8s:[//][user@][cluster][/namespace]/<name>[#container][?kind]
    • k8s:coredns-ff8999cc5-x56jw
    • k8s:kube-system/coredns#coredns?deployment
    • k8s://user@cluster/kube-system/coredns#coredns?deployment
  • Short form: n/a

Developing

Project Structure

CLI

The astu command line interface. Parses flags and loads configuration.

Library

Core logic: types and drivers for target resolution and action execution.

Coding Style

Commit Messages

Uses pre-commit for enforcing Conventional Commits. This is used for automating the release process.

Initialize pre-commit's Git hooks in this repo after first clone:

pre-commit install

Release

Inspired by this post. Uses conventional commits for automatically bumping versions.

TODOs

  • Eliminate all usages of dyn in favor of enum_dispatch
  • Eliminate all usages of async_trait for more readable documentation
  • Investigate if internment is really necessary for the target graph
  • Investigate why release PRs are being made even though no Rust changes have been made