Commit Briefs

09bfe2aaa3 xs

Minor documentation update (main, origin/main, tags/v0.2.0)


dd5b039844 xs

Update r7.1


e4821e5912 xs

Typo in technical notes


726f31543f xs

Update index.md


8e44e3d49e xs

v0.2.0




2d25d5310d xs

Updating importers WIP v0.2.0


0198b57b79 xs

Updating r7.1


8bf613ac25 xs

Updating importer section


Branches

Tags

v0.2.0

v0.1.6

v0.1.5

Tree

.gitignorecommits | blame
LICENSEcommits | blame
Makefilecommits | blame
README.mdcommits | blame
groups/
index.mdcommits | blame
r7*commits | blame
r7.1commits | blame
r7doc.htmlcommits | blame
testdir/

README.md

## Tutorial

### Introduction

R7 is a configuration management framework made to configure nodes with
configuration files and related operations versioned in a central repository.
To remain code versioning system agnostic, manual or automated versioning is
left to the user, via makefiles or triggered from r7 `site-config` configuration
file _(See below)_.

The idea is to store hosts configurations in `nodes/hostname` directories that
maps each host's `/etc` and to assign those hosts to configuration groups
containing the directives to install the configuration and potential
requirements.

_Nodes sub-directories are host names understood by the ssh client and
configuration._

_Currently, r7 requires valid ssh configuration and key files in the repository
`.ssh/` directory._

_Repositories can be initialized with `rset init` or `rset -w
path/to/new-repository init`, the user will then have to provide valid
configurations and keys for `.ssh/` in the newly created directory._

_See [Technical Notes](#technical-notes) for dependencies and supported
platforms._

There is an example of r7 repository:

    ./nodes/
    ./nodes/hostA
    ./nodes/hostB
    ./nodes/hostC
    ./groups/
    ./groups/common
    ./groups/test0
    ./groups/common.dir
    ./groups/test0.dir
    ./.ssh/
    ./.ssh/config
    ./.ssh/id_ed25519_r7
    ./.ssh/id_ed25519_r7.pub
    ./site-config

There is an example of r7 site configuration (`site-config` file):

```sh
prefix install state doc
group common '.*'
group test0 hostB hostC
output-dump
summary
```

There is an example of the deployment of that configuration within the r7
repository:

```
$ r7 run-config
- (ok) hostA:common::install_nothing
- (ok) hostA:common::state_uname
- (ok) hostA:common::doc_tutorial
- (ok) hostB:common::install_nothing
- (ok) hostB:common::state_uname
- (ok) hostB:common::doc_tutorial
- (ok) hostC:common::install_nothing
- (ok) hostC:common::state_uname
- (ok) hostC:common::doc_tutorial
- (ok) hostB:test0::state_test
- (ok) hostC:test0::state_test
```

In this example, everything goes fine, groups are executed on each related
nodes, note the `'.*'` matching all hosts present in `./nodes`. Groups are POSIX
shell scripts that are a collection of functions.

There is the `common` group:

```sh
install_nothing() {
    echo Nothing installed
}

state_uname() {
    uname -a
}

doc_tutorial() {
    echo "## Welcome to the R7 tutorial!"
}
```

And there is the `test0` group:

```sh
state_test() {
    test 1 -eq 1
}
```

Any function managed by `r7` needs to be declared in the format `^PREFIX_NAME()`.

Group files names can't contain spaces, tabs or newlines, as well for nodes
`dirnames`. _The ':' character is not recommended as well as it may reduce
readability of the interactive output._

Called groups and node directories are copied to related nodes in `hostname:/tmp/r7/`:

    /tmp/r7/hostA/ # Contents of test.org/nodes/hostA/
    /tmp/r7/common.dir # Contents of test.org/groups/common.dir/
    /tmp/r7/test.dir # Contents of test.org/groups/test.dir

Groups may have no `.dir` directory, in this case, nothing is copied.

Once the group copy is done, functions are executed in the order of the defined
prefix (part of the `site-config` file shown before):

    prefix install state doc

This means the functions matching `^install.*()`, `^state.*()` and `^doc.*()`
(the default `R7_PREFIX`) in the respective declaration order they're found in the
related group file will be executed.

Site configurations are shell scripts sourced by r7 after it's environment is
initialized, so `prefix`, `group`, `output-dump`, `summary` are the functions or
aliases provided by r7 itself. This allow for scripting from the `site-config`
file. Here `output-dump`, and `summary` are called to output potential errors,
diffs and permissions changes at the end of the interactive output obtained by
`r7 run-config`. If nothing happened, nothing is added to the output.

### Errors during remote execution of groups

In our previous example, we modify `state_test` so the `test` fails.

```sh
state_test() {
    test 1 -eq 2
}
```

And we re-run the configuration to see what's happening.

```
$ r7 run-config
- (ok) hostA:common::install_nothing
- (ok) hostA:common::state_uname
- (ok) hostA:common::doc_tutorial
- (ok) hostB:common::install_nothing
- (ok) hostB:common::state_uname
- (ok) hostB:common::doc_tutorial
- (ok) hostC:common::install_nothing
- (ok) hostC:common::state_uname
- (ok) hostC:common::doc_tutorial
- (error 1) hostB:test0::state_test
- (error 1) hostC:test0::state_test
- (error 1) - in hostB test0 state_test (1748622980)
- (error 1) - in hostC test0 state_test (1748622980)
```


The last two lines are part of the `summary` output.

Let's modify the `state_test` function again to add some verbosity to it's
output.

_Note that we also change the return value._

```sh
state_test() {
    echo something
    false || {
        echo >&2 failed
        return 22
    }
}
```

This will produce the following summary:

```
- (error 22) - in hostB test0 state_test (1748622980)

    something
    failed

- (error 22) - in hostC test0 state_test (1748622980)

    something
    failed
```

Diffs and permissions changes are also displayed by the `summary` command. When
they're are detected, the captured related output is shown.

### Repository initialisation

Using the `init` command, we can specify the current or a specified
work directory to be created and act as an r7 repository.

_Automated versioning of outputs are currently let to the user, a recommended
place would be the end of the site configuration file._

This command initializes a new directory called `test.org`:

```sh
r7 -w test.org init
cd test.org
```

Once this step is done, it is required to:

- Copy or initialize the ssh key to the `.ssh` folder in the repository created
  in `.ssh/id_ed25519-r7` and `./ssh/id_ed25519-r7.pub`

- Adjust the SSH client configuration file in the repository `.ssh/config`

_Note that it is possible to import additional SSH configurations, like the user
one from the SSH configuration, but it is advised to keep them separate._

### SSH agent handling

The function `unlock` can be used to set the SSH agent environment, this will
be required for any automated operation later done by r7:

```sh
cd test.org
. $(command -v r7) # Sourcing r7
unlock
tmux || screen
```


#### Tmux configuration

```
set -g update-environment -r
```

#### Optional Shell configuration

This is useful for handling multiple tmux sessions.

```sh
# Add this to the shell's rc
if [ -z "$TMUX" ]; then
    if [ -n "$SSH_TTY" ]; then
	if [ -z "$SSH_AUTH_SOCK" ]; then
	    export SSH_AUTH_SOCK="$HOME/.ssh/.auth_socket"
	fi
	if [ ! -S "$SSH_AUTH_SOCK" ]; then
	    eval $(ssh-agent -a $SSH_AUTH_SOCK) >/dev/null 2>&1
	    echo $SSH_AGENT_PID >$HOME/.ssh/.auth_pid
	fi
	if [ -z $SSH_AGENT_PID ]; then
	    export SSH_AGENT_PID=$(cat $HOME/.ssh/.auth_pid)
	fi
	ssh-add "$SSH_ID" 2>/dev/null
    fi
fi
```


### Adding new hosts

For adding a new host, the simplest way is to have pre-configured
`authorized_keys` for the target user, it is then required to add it to the
`known_hosts` file. For that, it is possible to use r7 SSH functions:

```sh
. $(command -v r7) # Sourcing r7
ssh-accept-new hostname

```

If resetting the target's user `authorized_keys` file is needed, the following
function can be used:

```sh
. $(command -v r7) # Sourcing r7
ssh-authorized-keys-reset hostname
```

_**Warning:** This will resets the SSH authorized_keys file for the target user
(root, by default) and put the `.ssh/id_ed25519-r7.pub` in place._


Once the host is accepted in the `known_hosts` file, it's possible to import it
automatically.

### Importing hosts configurations

Avoiding to import the configuration manually is possible with the `import`
command:

```sh
r7 import hostname
```

Most common configuration files will be copied in a newly created
`nodes/hostname` sub-directory.

### Writing group files

Group files collects functions related to the configuration of the operating
system, networking, services, global and user configurations packages
installations and application deployments.

By specifying the execution prefix, the user determines the order of execution
of the functions declared in the group file.

```sh
service_A() {
    service A enable
    service A restart
}

service_B() {
    service B enable
    service B restart
}

state_A() {
    service A status
}

doc_A() {
    echo "## Service A"
    echo
    echo "- [url](http://test.org/)"
    echo
}
```

_**file**: `groups/example1`_

With the default prefix, `install`, `state`, `doc`, the function that will be
executed are `state_A` and `doc_A`. To test that group file with all the
functions inside we can call r7 the following way:

```sh
r7 -P 'service state doc' group example1 hostname
```

Or using the `-a` flag which will create a prefix automatically with all
functions present in the group file:

```sh
r7 -a group example1 hostname
```

In a real situation, this example will have failing functions because the
`service` command may not be found or the services named `A` or `B` does not
exist.

```
! (error 127) tools0:example1::service_A
! (error 127) tools0:example1::service_B
! (error 127) tools0:example1::state_A
- (ok) tools0:example1::doc_A
```

We could force the group execution to stop using a combination of the `-e` flag
and the `error` group function.

```sh
service_A() {
    service A enable &&
    service A restart ||
        error unable to setup service A
}

service_B() {
    service B enable &&
    service B restart ||
        error unable to setup service B
}

state_A() {
    service A status
}

doc_A() {
    echo "## Service A"
    echo
    echo "- [url](http://test.org/)"
    echo
}
```

_**file:** `groups/example2`_

Let's run the updated example with the `-s` flag to trigger the summary output.

```sh
rm -fr _output
r7 -sea group example2 hostname
```

This will produce something like the following:

```
! (error) tools0:example2::service_A
! (exit) tools0:example2::service_A
- (error 127) - in tools0 example2 service_A (1748791986)

    /bin/sh: <stdin>[155]: service: not found
    :: 1748791986 tools0 ERROR unable to setup service A

```

### Cleaning functions

To remove a function from a group, we need to ensure that this function returned
0 for the last execution.

```sh
state_function_to_remove() {
    # old code
    # not used anymore
    :
}
```

This would prevent for seeing potential past errors caused by this function in
the summary output.

### Using the r7 install functions

The group library comes with the following functions that will allow
installation of files and triggering actions if necessary: `install`,
`groupinstall`, `nodeinstall`. They are all shortcuts to an internal
`r7_install` function, it's just the wrappers changes directory to respectively
`/tmp/r7`, `/tmp/r7/groupname.dir/` and `/tmp/r7/hostname/`.

If we want to install files from the group directory we will use something like:


```sh
install_program() {
    groupinstall -m 755 -o root:bin my_program /usr/local/bin
}
```

_We are supposing to have the file `groups/example3.dir/my_program`_

And if it's something from node, we should use:

```sh
install_program_config() {
    nodeinstall -m 644 -o root:wheel my_config /etc
}
```

We can now chain the `install` functions to configuration checking and
initialization of programs, services or network features like in the following
example, in which we install `ntp.conf` from the deployed node directory to
`/etc`:

```sh
service_ntpd() {
    nodeinstall -m 644 -o root:wheel ntpd.conf /etc &&
        ntpd -n &&
        rcctl enable ntpd  &&
        rcctl restart ntpd
}
```

### Using the r7 node function

We may want to avoid executing a function or parts of it if the node has no
related files before executing the installation and actions handling parts. This
can be done with the help of the `node` function.

```sh
service_ntpd() {
    node ntpd.conf &&
        nodeinstall -m 644 -o root:wheel ntpd.conf /etc &&
            ntpd -n &&
            rcctl enable ntpd  &&
            rcctl restart ntpd
}
```

Here, `node` will return `31` if there is no file or directory called
`ntpd.conf` inside the deployed node directory (`/tmp/r7/hostname`).

`0`, `30` and `31` exit codes are ignored when r7 will search for execution
errors. This means `30` and `31` exit code are reserved to r7.

In other terms, this will update the NTP daemon configuration only if
`ntpd.conf` is present in the node directory, otherwise it is kept by default,
which is the system provided default `ntpd.conf`.

### Sourcing specific files

Here's an example how to source additional shell from the deployed node
directory, or elsewhere:

```sh
service_sysrc() {
    sysrc dumpdev=NO
    sysrc kld_list="vmm if_tuntap if_bridge nmdm"
    sysrc microcode_update_enable=YES
    sysrc mountd_enable=YES
    sysrc nfs_server_enable=YES
    sysrc ntpdate_enable=YES
    sysrc powerd_enable=YES
    sysrc rcshutdown_timeout=900
    sysrc rpcbind_enable=YES
    sysrc sshd_enable=YES
    sysrc zfs_enable=YES
    nodesource sysrc.local
}

```

In this FreeBSD example, we configure the system RC globally with
`service_sysrc` which apply, if `sysrc.local` is found in the root of the node
directory, it's sourcing in the current scope via the help of `nodesource
sysrc.local`.

The file `nodes/hostname/sysrcl.local` contains the following:

```sh
sysrc ifconfig_igb0=DHCP
sysrc ifconfig_igb1=up
```

### Other examples

Inside the r7 release, a small example groups library can be found in the
`groups` directory, that can also be browsed in the
[repository](https://got.inda.re/?action=summary&path=r7.git).

#### System shell profile

Here we install the `profile` file provided in the `groupname.dir` directory or
the profile provided by the node directory:

```sh
setup_profile() {
    groupinstall -m 644 -o root:wheel profile /etc
    node profile &&
        nodeinstall -m 644 -o root:wheel profile /etc
}
```

#### Users

Here we add new users:

```sh
setup_users() {
    useradd user1 2>/dev/null || :
    useradd user2 2>/dev/null || :
}
```

#### Crontab

In the following example, we install a script and call it from a dedicated user crontab.

```sh
mail_admins=admins@test.org

install_myscript() {
    groupinstall -o root:bin -m 755 myscript /usr/local/bin &&
        useradd -d /var/empty -s /sbin/nologin _myscript
    crontab -u _myscript - <<-/
        MAILTO=$mail_admins
        */5 * * * *     myscript
    /
}
```

### Consulting the output

After the execution is done, the result are saved in the `_run` directory,
`output-dump` needs to be called to produce the current `_output` structure.

The r7 `output` command can then be used to show specific output groups or functions:

```sh
r7 output
```

Will show the whole output of the latest groups deployments.

This format can be used to match specific host, group or function:

```sh
r7 output [hostname [groupname [funcname]]]
```

For example:

```sh
$ r7 output tools0 OpenBSD state_packages\*
- tools0 OpenBSD state_packages_upgrades

	quirks-7.103 signed on 2025-05-30T01:12:03Z

- tools0 OpenBSD state_packages_repository

	https://cdn.openbsd.org/pub/OpenBSD
```

_Note that the `output` function arguments are using shell's case glob patterns._

### Active nodes

A `R7_WORKDIR/.active_nodes` file is automatically created when r7 tries to
connect to a list of hosts via the `group` command or function. This serves as a
cache of available hosts reachable via SSH login (the username is specified in
the SSH configuration file).

To add a previously non-available host, simply remove the file and use `group`
(here via `run-config`):

```sh
rm .active_nodes
r7 run-config
```

## r7 command line

***Usage:***
```sh
r7 [options] [command [arguments...]]
```

### Options

- **-h**
  Show usage and exit

- **-d**
  Enable debug mode

- **-i**
  Specify the SSH identity file

- **-F**
  Specify the SSH client configuration file

- **-w**
  Set the work directory where `./nodes` and `./groups` can be found

- **-r**
  Set the run directory (default: `./_run`)

- **-o**
  Set the output directory (default: `./_output`)

- **-c**
  Specify the site configuration file (default: `./site-config`)

- **-n**
  Enable dry run mode; prevents any modification on hosts

- **-e**
  Exit the group execution on the error function

- **-p**
  Run each host in parallel per group call

- **-O**
  Show output interactively

- **-P**
  Specify a prefix, can contain whole function names

- **-a**
  Select all available prefixes for a group command

- **-s**
  Apply output-dump and summary after a group command

### Commands

| Name | Description |
| ---- | ----------- |
| **run-config** | Run the site configuration |
| **group** | Deploy specified group to hosts |
| **output-dump** | Dump the run directory to the output directory |
| **output** | Consult the output |
| **summary** | Produce a summary of errors and changes |
| **digraph-host** | Produce a dot digraph for hosts |
| **digraph-ssh** | Produce a dot digraph of SSH ID's, users and nodes relations |
| **import** | Import TARGET host in the current nodes directory |
| **info** | Default command showing active nodes and summary |
| **init** | Initialize a new work directory |

## Environment

| Name | Default value | Description |
| ---- | ------------- | ------------|
| R7_DEBUG | no | Enable debug mode |
| R7_DRYRUN | no | Enable dry-run mode |
| R7_GROUP_ALLPREFIX | no | Group selects all available prefix |
| R7_GROUP_SUMMARY | no | Enable output-dump and summary after group execution |
| R7_GROUP_EXITONERROR | no | Enable exit on error function |
| R7_PARALLEL | no | Enable group nodes parallel execution |
| R7_PREFIX | install state doc | Set the r7 group execution prefix |
| R7_SHOWOUTPUT | no | Show interactive output |
| R7_WORKDIR | PWD | Set the r7 repository path |
| R7_SITE_CONFIG | R7_WORKDIR/site-config | Set the r7 `site-config` path |
| R7_OUTPUTDIR | R7_WORKDIR/_output | Set the r7 output directory path |
| SSH_CONFIG_DIR | R7_WORKDIR/.ssh | Set the SSH configuration directory path  |
| SSH_CONFIG_FILE | R7_WORKDIR/.ssh/config | Set the SSH configuration file path |
| SSH_CONTROL_DIR | SSH_CONFIG_DIR/control | Set the SSH control dir path |
| SSH_IDENTITY_FILE | R7_WORKDIR/.ssh/id_ed25519_r7 | Set the SSH identity file |
| SSH_KNOWN_HOSTS | R7_WORKDIR/.ssh/known_hosts | Set the SSH known hosts file |
| SSH_CONNECT_TIMEOUT | 3 | Set the SSH connect timeout |


## Group variables

Those variables are available during the execution of th group file's functions.

| Name | Description |
| ---- | ----------- |
| trace_id | Current run trace ID |
| groupname | Current group name being executed |
| exitonerror | Cause the error function to exit if equals `yes` |
| nodename | Contains the output of `nodename` |
| nodedir | Contains the output of `nodedir` |
| groupdir | Contains the output of `groupdir` |


## Group functions

Those functions are accessible during the execution of the group file's functions.
Non documented functions are reserved for internal use.

1. [trace](#trace)
2. [error](#error)
3. [node](#node)
4. [nodename](#nodename)
5. [nodedir](#nodedir)
6. [groupdir](#groupdir)
7. [install](#install)
8. [groupinstall](#groupinstall)
9. [nodeinstall](#nodeinstall)
10. [installdir](#installdir)
11. [groupinstalldir](#groupinstalldir)
12. [nodeinstalldir](#nodeinstalldir)
13. [source](#source)
14. [groupsource](#groupsource)
15. [nodesource](#groupsource)
16. [template](#template)
17. [grouptemplate](#grouptemplate)
18. [nodetemplate](#nodetemplate)

---

### trace

***Description:***
Append a trace containing the current node name and `trace_id`

**Usage:**
```sh
trace [string...]
```

### error

***Description:***
Trace an error message that will inform the user of an event in the summary
output, exit the group execution if `-e` is set

**Usage:**
```sh
error [string...]
```

### node

***Description:***
Returns true if the argument is an existing file or directory inside `/tmp/r7/$(nodename)`, else, returns 31

***Usage:***
```sh
node [filename|dirname]
```

### nodename

***Description:***
Returns the small host name of the current host

***Usage:***
```sh
nodename
```

### nodedir

***Description:***
Returns `/tmp/r7/$(nodename)`

***Usage:***
```sh
nodename
```

### groupdir

***Description:***
Returns `/tmp/r7/$groupname.dir`

***Usage:***
```sh
groupdir
```

### install

***Description:***
Change directory to `/tmp/r7` and install files if changed and sets mode and
owner, returns true if the file changed, otherwise returns 30

***Usage:***
```sh
install [-o owner] [-m mode] source destintaion
```

### groupinstall

***Description:***
Change directory to the current group `.dir` directory in `/tmp/r7` then
performs like `install`

***Usage:***
```sh
groupinstall [-o owner] [-m mode] source destintaion
```

### nodeinstall

***Description:***
Change directory to the current node directory in `/tmp/r7` then
performs like `install`

***Usage:***
```sh
nodeinstall [-o owner] [-m mode] source destintaion
```

### installdir

***Description:***
Install directory calling `install` for each file and checking for permissions
changes.

***Usage:***
```sh
installdir [-o fileowner] [-m filemode] [-O dirowner] [-O dirmode] source
destination
```

### groupinstalldir

***Description:***
Change directory to the current group directory then call installdir

***Usage:***
```sh
groupinstalldir [-o fileowner] [-m filemode] [-O dirowner] [-O dirmode] source
destination
```

### nodeinstalldir

***Description:***
Change directory to the current group directory then call installdir

***Usage:***
```sh
nodeinstalldir [-o fileowner] [-m filemode] [-O dirowner] [-O dirmode] source
destination
```

### fsource

***Description:***
Source a POSIX shell file in the current execution

***Usage:***

```sh
fsource filename
```

### groupsource

***Description:***
Change directory to the current group directory then call fsource with the file
name as argument, produces an error if filename is not found from the group
directory

***Usage:***
```sh
groupsource filename
```

### nodesource

***Description:***
Change directory to the current group directory then call node and fsource with the file
name as argument without producing an error if the filename is not found

***Usage:***
```sh
nodesource filename
```

### template

***Description:***
Template the files passed as arguments with the current environment or by
sourcing a specified file

***Usage:***
```sh
template [-s sourcefile] [file...]
```

### grouptemplate

***Description:***
Change directory to the current group directory before calling template

***Usage:***
```sh
grouptemplate [-s sourcefile] [file...]
```

### nodetemplate

***Description:***
Change directory to the current node directory before calling template

***Usage:***
```sh
nodetemplate [-s sourcefile] [file...]
```

# r7 library

Usable from the r7 `site-config` file or sourced in the current shell using:

```sh
. $(command -v r7)
```

Each functions are declared with underscore names and aliased with dashes.
Shorter aliases are also available, list all declared aliases with:

```sh
alias
```

Non documented functions are reserved for internal use.

## Interface

1. [info](#info)
2. [usage](#usage)

---

### info

***Description:***
Default command showing active nodes and summary, ran when sourced

***Usage:***
```sh
info
```

### usage

***Description:***
Shows the command line help

***Usage:***
```sh
usage
```

## SSH functions

1. [unlock](#unlock)
2. [copy](#copy)
3. [ssh](#ssh)
4. [ssh-debug](#ssh-debug)
5. [ssh-known-hosts](#ssh-known-hosts)
6. [ssh-authorized-keys-reset](#ssh-authorized-keys-reset)
7. [ssh-accept-new](#ssh-accept-new)
8. [ssh-control-master-clean](#ssh-control-master-clean)
9. [ssh-users](#ssh-users)
10. [ssh-ids](#ssh-ids)
11. [ssh-authorized-keys](#ssh-authorized-keys)
12. [ssh-ids-hosts](#ssh-ids-hosts)

---

### unlock

***Description:***
Resets the SSH agent environment and issues ssh-add for SSH_IDENTITY_FILE

***Usage:***
```sh
unlock
```

### copy

***Description:***
r7 openrsync, rsync, scp wrapper; selected in order of availability

***Usage:***
```sh
copy SRC DST
```

### ssh

***Description:***
r7 SSH wrapper; behaves like the standard OpenSSH command

***Usage:***
```sh
ssh OPTION HOST
```

### ssh-debug

***Description:***
r7 SSH with debug connection mode for a specific HOST

***Usage:***
```sh
ssh-debug HOST
```

### ssh-known-hosts

***Description:***
Calls ssh-keygen to dump the SSH known_hosts file

***Usage:***
```sh
ssh-known-hosts
```

### ssh-authorized-keys-reset

***Description:***
Resets SSH authorized_keys on the specified HOST

***Usage:***
```sh
ssh-authorized-keys-reset HOST
```

### ssh-accept-new

***Description:***
Adds a new host entry in the SSH known_hosts file

***Usage:***
```sh
ssh-accept-new HOST
```

### ssh-control-master-clean

***Description:***
Cleans the SSH control master directory

***Usage:***
```sh
ssh-control-master-clean
```

### ssh-users

***Description:***
Searches nodes for authorized_keys_* and returns a list of users

***Usage:***
```sh
ssh-users
```

### ssh-ids

***Description:***
Returns sorted SSH IDs

***Usage:***
```sh
ssh-ids
```

### ssh-authorized-keys

***Description:***
Returns the list of authorized_keys from nodes

***Usage:***
```sh
ssh-authorized-keys
```

### ssh-ids-hosts

***Description:***
Returns SSH IDs along with matching key file and host name

***Usage:***
```sh
ssh-ids-hosts
```

## Deployment functions

1. [nodes](#nodes)
2. [nodes-active](#nodes-active)
3. [run-config](#run-config)
4. [members](#members)
5. [group](#group)
6. [group-functions](#group-functions)
7. [group-functions-all](#group-functions-all)
8. [prefix](#prefix)

---

### nodes

***Description:***
Returns a list of available nodes

***Usage:***
```sh
nodes
```

### nodes-active

***Description:***
Returns a list of active nodes by detecting SSH availability (a login is done), writes
`R7_WORKDIR/.active_nodes` containing the available hosts

***Usage:***
```sh
nodes-active
```

### run-config

***Description:***
Executes `R7_SITE_CONFIG` in the loaded environment

***Usage:***
```sh
run-config
```

### members

***Description:***
Returns a list of active nodes matching a list of grep REGEXES

***Usage:***
```sh
members REGEXES
```

### group

***Description:***
Applies GROUPNAME functions (in prefix order) to the provided MEMBERS list

***Usage:***
```sh
group GROUPNAME MEMBERS
```

### group-functions

***Description:***
Returns a list of functions found in the specified GROUPNAME

***Usage:***
```sh
group-functions GROUPNAME
```

### group-functions-all

***Description:***
Returns a list of all group functions found in the current `R7_WORKDIR/groups`
directory with the `group_name::function_name` format

***Usage:***

```sh
group-functions-all
```


### prefix

***Description:***
Sets the current PREFIX for matching function names, can match for the whole
unique name in the group, for executing one specified function only

***Usage:***
```sh
prefix PREFIX
```

## Text output functions

1. [output-dump](#output-dump)
2. [output-latest](#output-latest)
3. [output-status](#output-status)
4. [output-duration](#output-duration)
5. [output](#output)
6. [output-diffs](#output-diffs)
7. [output-permissions](#output-permissions)
8. [output-errors](#output-errors)
9. [summary](#summary)
10. [trace-id-last](#trace-id-last)

---

### output-dump

***Description:***
Dumps R7_RUNDIR to R7_OUTPUTDIR

***Usage:***
```sh
output-dump
```

### output-latest

***Description:***
Returns a list of outputs from the latest run

***Usage:***
```sh
output-latest
```

### output-status

***Description:***
Returns a list of current dumped functions exit status codes

***Usage:***
```sh
output-status
```

### output-duration

***Description:***
Returns a list of durations of executed groups

***Usage:***
```sh
output-duration
```

### output

***Description:***
Shows the dumped output matching HOST, GROUPNAME, and FUNCTION (or any if unspecified)

***Usage:***
```sh
output HOST GROUPNAME FUNCTION
```

### trace-id-last

***Description:***
Returns the latest execution trace_id

***Usage:***
```sh
trace-id-last
```

### output-diffs

***Description:***
Returns the diffs from the latest execution

***Usage:***
```sh
output-diffs
```

### output-permissions

***Description:***
Returns the latest permission changes

***Usage:***
```sh
output-permissions
```

### output-errors

***Description:***
Returns the latest errors detected

***Usage:***
```sh
output-errors
```

### summary

***Description:***
Produces a summary of errors, diffs, and permission outputs

***Usage:***
```sh
summary
```

## Digraph output

1. [digraph-host](#digraph-host)
2. [digraph-ssh-id-hosts](#digraph-ssh-id-hosts)

---

### digraph-host

***Description:***
Returns a directed graph (in DOT format) showing relations among groups, functions, and nodes

***Usage:***
```sh
digraph-host
```

### digraph-ssh-id-hosts

***Description:***
Returns a directed graph (in DOT format) of relations between SSH IDs and hosts

***Usage:***
```sh
digraph-ssh-id-hosts
```

## HTML output

1. [html](#html)

### html

***Description:***
Retunrs a report page with the content of `README.md` in HTML format

***Usage:***
```sh
html
```

---

## Node importing

1. [import](#import)

### import

***Description:***
Import configuration files from a target host

***Usage:***
```sh
import HOST
```

---

## Technical notes

- Supported shells are ksh93, oksh, yash and bash
- Supported target platforms are POSIX compatible systems
    - `/bin/sh` or compatible must be set for the user's shell (which is
      usually the case by default)
    - `/tmp` must be writable by the user
- Supported control node platforms are supposed to be POSIX compatible systems
  but currently limited to BSD's, Darwin and GNU/Linux
    - Dependencies are awk, OpenSSH and column
    - Optional dependencies are openrsync, rsync, column, graphviz and pandoc
- The r7 importer supports the following platforms:
    - Alpine Linux
    - Archlinux
    - Crux
    - Debian
    - DragonFlyBSD
    - FreeBSD
    - Gentoo
    - Guix
    - Haiku
    - Minix
    - NetBSD
    - Omnios
    - OpenBSD
    - OpenSUSE
    - QNX
    - Rocky Linux
    - Salix
    - Void Linux

---

## License

The provided group library is shared as additional examples WITH NO WARRANTIES of the
documentation and contain the following third-party files under the ISC License.
Copyright information are indicated in the LICENSE section of related files.

- `pf-badhost.sh`
- `unbound-adblock.sh`
- `http-ban.sh`

The project is shared under the terms of the ISC license. Any version previous
`0.1.0` needs to be considered obsolete and should not be redistributed. History
in the GIT repository is kept for transparency purpose only.

Below is a copy of the applicable license:

    ISC License

    Copyright 2025 xs <xs@inda.re>

    Permission to use, copy, modify, and/or distribute this software for any
    purpose with or without fee is hereby granted, provided that the above
    copyright notice and this permission notice appear in all copies.

    THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
    REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
    FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
    INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
    LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
    OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
    PERFORMANCE OF THIS SOFTWARE.