R7 Documentation

Tue Jun 3 2025

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 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):

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:

install_nothing() {
    echo Nothing installed
}

state_uname() {
    uname -a
}

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

And there is the test0 group:

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.

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.

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:

r7 -w test.org init
cd test.org

Once this step is done, it is required to:

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:

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.

# 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:

. $(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:

. $(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:

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.

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:

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:

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.

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.

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.

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:

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:

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:

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.

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:

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:

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.

System shell profile

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

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:

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.

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:

r7 output

Will show the whole output of the latest groups deployments.

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

r7 output [hostname [groupname [funcname]]]

For example:

$ 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):

rm .active_nodes
r7 run-config

r7 command line

Usage:

r7 [options] [command [arguments...]]

Options

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
  2. error
  3. node
  4. nodename
  5. nodedir
  6. groupdir
  7. install
  8. groupinstall
  9. nodeinstall
  10. installdir
  11. groupinstalldir
  12. nodeinstalldir
  13. source
  14. groupsource
  15. nodesource
  16. template
  17. grouptemplate
  18. nodetemplate

trace

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

Usage:

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:

error [string...]

node

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

Usage:

node [filename|dirname]

nodename

Description: Returns the small host name of the current host

Usage:

nodename

nodedir

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

Usage:

nodename

groupdir

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

Usage:

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:

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:

groupinstall [-o owner] [-m mode] source destintaion

nodeinstall

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

Usage:

nodeinstall [-o owner] [-m mode] source destintaion

installdir

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

Usage:

installdir [-o fileowner] [-m filemode] [-O dirowner] [-O dirmode] source
destination

groupinstalldir

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

Usage:

groupinstalldir [-o fileowner] [-m filemode] [-O dirowner] [-O dirmode] source
destination

nodeinstalldir

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

Usage:

nodeinstalldir [-o fileowner] [-m filemode] [-O dirowner] [-O dirmode] source
destination

fsource

Description: Source a POSIX shell file in the current execution

Usage:

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:

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:

nodesource filename

template

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

Usage:

template [-s sourcefile] [file...]

grouptemplate

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

Usage:

grouptemplate [-s sourcefile] [file...]

nodetemplate

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

Usage:

nodetemplate [-s sourcefile] [file...]

r7 library

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

. $(command -v r7)

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

alias

Non documented functions are reserved for internal use.

Interface

  1. info
  2. usage

info

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

Usage:

info

usage

Description: Shows the command line help

Usage:

usage

SSH functions

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

unlock

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

Usage:

unlock

copy

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

Usage:

copy SRC DST

ssh

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

Usage:

ssh OPTION HOST

ssh-debug

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

Usage:

ssh-debug HOST

ssh-known-hosts

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

Usage:

ssh-known-hosts

ssh-authorized-keys-reset

Description: Resets SSH authorized_keys on the specified HOST

Usage:

ssh-authorized-keys-reset HOST

ssh-accept-new

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

Usage:

ssh-accept-new HOST

ssh-control-master-clean

Description: Cleans the SSH control master directory

Usage:

ssh-control-master-clean

ssh-users

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

Usage:

ssh-users

ssh-ids

Description: Returns sorted SSH IDs

Usage:

ssh-ids

ssh-authorized-keys

Description: Returns the list of authorized_keys from nodes

Usage:

ssh-authorized-keys

ssh-ids-hosts

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

Usage:

ssh-ids-hosts

Deployment functions

  1. nodes
  2. nodes-active
  3. run-config
  4. members
  5. group
  6. group-functions
  7. group-functions-all
  8. prefix

nodes

Description: Returns a list of available nodes

Usage:

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:

nodes-active

run-config

Description: Executes R7_SITE_CONFIG in the loaded environment

Usage:

run-config

members

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

Usage:

members REGEXES

group

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

Usage:

group GROUPNAME MEMBERS

group-functions

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

Usage:

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:

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:

prefix PREFIX

Text output functions

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

output-dump

Description: Dumps R7_RUNDIR to R7_OUTPUTDIR

Usage:

output-dump

output-latest

Description: Returns a list of outputs from the latest run

Usage:

output-latest

output-status

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

Usage:

output-status

output-duration

Description: Returns a list of durations of executed groups

Usage:

output-duration

output

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

Usage:

output HOST GROUPNAME FUNCTION

trace-id-last

Description: Returns the latest execution trace_id

Usage:

trace-id-last

output-diffs

Description: Returns the diffs from the latest execution

Usage:

output-diffs

output-permissions

Description: Returns the latest permission changes

Usage:

output-permissions

output-errors

Description: Returns the latest errors detected

Usage:

output-errors

summary

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

Usage:

summary

Digraph output

  1. digraph-host
  2. digraph-ssh-id-hosts

digraph-host

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

Usage:

digraph-host

digraph-ssh-id-hosts

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

Usage:

digraph-ssh-id-hosts

HTML output

  1. html

html

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

Usage:

html

Node importing

  1. import

import

Description: Import configuration files from a target host

Usage:

import HOST

Technical notes


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.

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.