commit 8e44e3d49ed6238ddfe3dc214d2fa2015e6df797 from: xs date: Wed Jun 25 10:01:31 2025 UTC v0.2.0 commit - 622e6949f90df00caf7d792a15f19f6b56d33125 commit + 8e44e3d49ed6238ddfe3dc214d2fa2015e6df797 blob - 49df62ba5478f1493bfdc4cc476e6f55e404812c blob + 9728e84212cead97ddd44cd086e6b37ff4436204 --- Makefile +++ Makefile @@ -16,15 +16,13 @@ .POSIX: PREFIX:=/usr/local SHELLS=ksh93 ksh yash sh -UTILITY_VERSION=0.1.6 +UTILITY_VERSION=0.2.0 UTILITY=$(PWD)/r7 # To use the test suite, please edit or use environment for SSH parameters # SSH_ID=/path/to/.ssh/id_ed25519 # SSH_TEST_NODES=nodeA nodeB -.DEFAULT: all +.DEFAULT: all all: tests -all: tests - release: r7.1 make tests whoami @@ -193,8 +191,10 @@ workdir2-ssh: workdir2-init echo 'state_hello() { echo Hey! ; }' > groups/heyo && \ echo 'dummy_hello() { echo Heyo! ; }' >> groups/heyo && \ echo 'group heyo $(SSH_TEST_NODES)' >> site-config && export R7_DEBUG=yes && \ + . $(UTILITY) && \ + prefix install state doc dummy && \ digraph_host && \ - run_config && \ + run_config && \ output_dump && \ output | grep Hey! && \ output && \ @@ -257,7 +257,6 @@ workdir2-source: workdir2-init rm -fr testdir/workdir2/_run cp testdir/source testdir/workdir2/groups/ mkdir testdir/workdir2/groups/source.dir - echo : > testdir/workdir2/groups/source.dir/my-source for node in $(SSH_TEST_NODES); do \ echo myvar=3333 > "testdir/workdir2/nodes/$$node/my-source" ; \ done @@ -277,7 +276,7 @@ workdir2-importer: workdir2-init . $(UTILITY) && import "$$node"; \ done cd testdir/workdir2/ && . $(UTILITY) && import notfound || : - find testdir/workdir2/nodes | grep sshd_config + find testdir/workdir2/nodes | grep hosts find testdir/workdir2/nodes @echo _________________________________________________________ @@ -290,8 +289,14 @@ workdir3-init: test -d testdir/workdir3 cd testdir/workdir3 && $R | wc -c | grep -q 0 cd testdir/workdir3 && \ - cp "$(SSH_ID)" .ssh/id_ed25519_r7 && \ - cp "$(SSH_ID).pub" .ssh/id_ed25519_r7.pub && \ + cp .ssh/config .sshtmp && \ + echo 'Host test-haiku-b5' > .ssh/config && \ + echo ' User user' >> .ssh/config && \ + echo >>.ssh/.config && \ + cat .sshtmp >>.ssh/config && \ + sleep 1 && \ + cp -p "$(SSH_ID)" .ssh/id_ed25519_r7 && \ + cp -p "$(SSH_ID).pub" .ssh/id_ed25519_r7.pub && \ . $(UTILITY) && for node in $(SSH_TEST_NODES); do \ mkdir "nodes/$$node" && \ ssh_accept_new "$$node"; \ blob - 39b3cd51e6bd3df38183f45f8f1afda3ed2bbdcd blob + f0c090dcd606bced6e8e6289f315a43435b403f7 --- README.md +++ README.md @@ -3,8 +3,8 @@ ### Introduction R7 is a configuration management framework made to configure nodes with -configuration files and related operations versionned in a central repository. -To remain code versionning system agnostic, manual or automated versionning is +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)_. @@ -13,10 +13,10 @@ maps each host's `/etc` and to assign those hosts to c containing the directives to install the configuration and potential requirements. -_Nodes sub-directories are hostnames understood by the ssh client and +_Nodes sub-directories are host names understood by the ssh client and configuration._ -_Currently, r7 requires valid ssh configuration and keyfiles in the repository +_Currently, r7 requires valid ssh configuration and key files in the repository `.ssh/` directory._ _Repositories can be initialized with `rset init` or `rset -w @@ -53,7 +53,7 @@ output-dump summary ``` -There is an exemple of the deployment of that configuration within the r7 +There is an example of the deployment of that configuration within the r7 repository: ``` @@ -102,8 +102,8 @@ state_test() { 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 recommanded as well as it may reduce -lisibility of the interactive output._ +`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/`: @@ -122,12 +122,12 @@ This means the functions matching `^install.*()`, `^st (the default `R7_PREFIX`) in the respective declaration order they're found in the related group file will be executed. -Site confiurations are shell scripts sourced by r7 after it's environment is +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 catched happened, nothing is added to the output. +`r7 run-config`. If nothing happened, nothing is added to the output. ### Errors during remote execution of groups @@ -139,7 +139,7 @@ state_test() { } ``` -And we re-run the configuration to see what's happenning. +And we re-run the configuration to see what's happening. ``` $ r7 run-config @@ -195,10 +195,10 @@ they're are detected, the captured related output is s ### Repository initialisation -Using the `init` command, we can specify the current or a specifed +Using the `init` command, we can specify the current or a specified work directory to be created and act as an r7 repository. -_Automated versionning of outputs are currently let to the user, a recommended +_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`: @@ -215,12 +215,12 @@ Once this step is done, it is required to: - Adjust the SSH client configuration file in the repository `.ssh/config` -_Note that it is possible to import additionnal SSH configurations, like the user +_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 environement, this will +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 @@ -230,8 +230,37 @@ unlock tmux || screen ``` -_***Warning:*** Unlock will terminate any running `ssh-agent` instances._ +#### 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 @@ -261,8 +290,6 @@ automatically. ### Importing hosts configurations -_This feature currently only support OpenBSD._ - Avoiding to import the configuration manually is possible with the `import` command: @@ -275,7 +302,7 @@ Most common configuration files will be copied in a ne ### Writing group files -Group files collects fonctions related to the configuration of the operating +Group files collects functions related to the configuration of the operating system, networking, services, global and user configurations packages installations and application deployments. @@ -307,7 +334,7 @@ doc_A() { _**file**: `groups/example1`_ -With the default prefix, `install`, `state`, `doc`, the fonction that will be +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: @@ -467,7 +494,7 @@ which is the system provided default `ntpd.conf`. ### Sourcing specific files -Here's an example how to source additionnal shell from the deployed node +Here's an example how to source additional shell from the deployed node directory, or elsewhere: ```sh @@ -579,7 +606,7 @@ $ r7 output tools0 OpenBSD state_packages\* https://cdn.openbsd.org/pub/OpenBSD ``` -_Note that the `output` function arguments are using shell's case globbing patterns._ +_Note that the `output` function arguments are using shell's case glob patterns._ ### Active nodes @@ -762,7 +789,7 @@ node [filename|dirname] ### nodename ***Description:*** -Returns the small hostname of the current host +Returns the small host name of the current host ***Usage:*** ```sh @@ -837,7 +864,7 @@ destination ### groupinstalldir ***Description:*** -Change directory to the current group directoy then call installdir +Change directory to the current group directory then call installdir ***Usage:*** ```sh @@ -848,7 +875,7 @@ destination ### nodeinstalldir ***Description:*** -Change directory to the current group directoy then call installdir +Change directory to the current group directory then call installdir ***Usage:*** ```sh @@ -856,7 +883,7 @@ nodeinstalldir [-o fileowner] [-m filemode] [-O dirown destination ``` -### source +### fsource ***Description:*** Source a POSIX shell file in the current execution @@ -864,7 +891,7 @@ Source a POSIX shell file in the current execution ***Usage:*** ```sh -source filename +fsource filename ``` ### groupsource @@ -1095,7 +1122,7 @@ ssh-authorized-keys ### ssh-ids-hosts ***Description:*** -Returns SSH IDs along with matching keyfile and hostname +Returns SSH IDs along with matching key file and host name ***Usage:*** ```sh @@ -1376,18 +1403,40 @@ import HOST ## Technical notes -- Supported shells are ksh93, oksh, yash and bash. -- Supported platforms are OpenBSD, FreeBSD, NetBSD and GNU/Linux. -- Dependencies are awk, OpenSSH and column -- Optional dependencies are openrsync, rsync, column, graphviz and pandoc. -- Busybox support is planned. -- The HTML output is a work in progress. +- 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 platform: + - 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 additionnal examples WITH NO WARRANTIES of the +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. blob - 9f95a199b0e44884ab0abb31a98fb72849d7f9ff blob + 09356f5a57f7039c794727aa88d5d7f9e89b5e44 --- r7 +++ r7 @@ -20,11 +20,15 @@ # Global configuration # ==================== +: "${ZSH_VERSION:=}" +R7_DEBUG=no VERSION=0.2.0 +_init_pwd="$PWD" # shellcheck disable=SC2031 _init_env() { _debug _INIT_ENV + cd "$_init_pwd" || _fail "Unable to cd to '$_init_pwd'" # Here is the default configuration : "${R7_DEBUG:=no}" @@ -35,7 +39,7 @@ _init_env() { : "${R7_PARALLEL:=no}" : "${R7_PREFIX:=install state doc}" : "${R7_SHOWOUTPUT:=no}" - : "${R7_WORKDIR:=$PWD}" + : "${R7_WORKDIR:=$_init_pwd}" : "${R7_SITE_CONFIG:=$R7_WORKDIR/site-config}" : "${R7_OUTPUTDIR:=$R7_WORKDIR/_output}" : "${SSH_CONFIG_DIR:=$R7_WORKDIR/.ssh}" @@ -243,7 +247,7 @@ _main() { digraph_ssh_id_hosts ;; html) html "$@" ;; - '') info ;; + '') cd "$R7_WORKDIR" && info ;; *) case $0 in -) exit 33 ;; @@ -309,15 +313,6 @@ Host * StrictHostKeyChecking yes User root UserKnownHostsFile $R7_WORKDIR/.ssh/known_hosts - -Host alpha - Hostname 10.10.0.1 - -Host beta - Hostname 10.10.0.2 - -Host gamma - Hostname 10.10.0.3 eof fi touch "${SSH_IDENTITY_FILE}" "${SSH_IDENTITY_FILE}.pub" @@ -337,7 +332,7 @@ ssh() ( alias ssh-debug=ssh_debug ssh_debug() { _debug "SSH $*" - ssh -vvv "$@" : + ssh -vvv "$@" } alias ssh-known-hosts=ssh_known_hosts @@ -359,7 +354,7 @@ ssh_authorized_keys_reset() { alias ssh-accept-new=ssh_accept_new ssh_accept_new() { - ssh -o StrictHostKeyChecking=accept-new "$@" : + ssh -F "$SSH_CONFIG_FILE" -o StrictHostKeyChecking=accept-new "$@" : } alias ssh-control-master-clean=ssh_control_master_clean @@ -401,7 +396,6 @@ ssh_ids_hosts() { } unlock() { - pkill ssh-agent eval "$(ssh-agent)" ssh-add "$SSH_IDENTITY_FILE" } @@ -432,7 +426,8 @@ copy() ( _debug "COPY $1 $2 USE SCP" dir=${dir:-/tmp/r7} ssh "$node" rm -fr "$dir/$1" 2>/dev/null - /usr/bin/scp "-oConnectTimeout=$SSH_CONNECT_TIMEOUT" -qF "$SSH_CONFIG_FILE" -r "$1" "$2" + scp "-oConnectTimeout=$SSH_CONNECT_TIMEOUT" -qF "$SSH_CONFIG_FILE" -r "$1" "$2" && return 0 + _fail "Unable to copy '$1' to '$2'." ) # Deployment functions @@ -486,12 +481,12 @@ _deploy() ( nodedir="nodes/$target" test "$R7_DRYRUN" = yes || { if test -d "$stem.dir"; then - ssh "$target" 'test -d /tmp/r7 || { umask 027; mkdir -p /tmp/r7; }' + ssh "$target" /bin/sh -c 'umask 027; mkdir -p /tmp/r7' copy "$stem.dir" "$target:/tmp/r7" || return 3 fi if test -d "$nodedir"; then test -f /tmp/r7/"$trace_id.$target" || { - ssh "$target" 'test -d /tmp/r7 || { umask 027; mkdir -p /tmp/r7; }' + ssh "$target" /bin/sh -c 'umask 027; mkdir -p /tmp/r7' copy "$nodedir" "$target:/tmp/r7" || return 3 } fi @@ -592,6 +587,7 @@ _call() ( ) _payload() ( + echo "hostname=$target" echo "trace_id=$trace_id" echo "groupname=$new_name" echo "exitonerror=$R7_GROUP_EXITONERROR" @@ -624,20 +620,7 @@ _payload() ( groupdir() ( echo "/tmp/r7/$groupname.dir" ) - _presource() { - test $# -gt 0 || { - error missing source argument - return 2 - } - source "$(test -f "$1" && printf %s "$PWD/${1##"$PWD"}")" - } - nodesource() { - node "$1" && cd "$(nodedir)" && _presource "$1" - } - groupsource() { - cd "$(groupdir)" && _presource "$1" - } - source() { + _source() { if sh -n "$1"; then trace SOURCE "$1" . "$1" @@ -646,6 +629,19 @@ _payload() ( return 2 fi } + fsource() { + test $# -gt 0 || { + error missing source argument + return 2 + } + _source "$(test -f "$1" && printf %s "$PWD/${1##"$PWD"}")" + } + nodesource() { + node "$1" && cd "$(nodedir)" && fsource "$1" + } + groupsource() { + cd "$(groupdir)" && _fsource "$1" + } install() ( cd /tmp/r7 && r7_install "$@" ; ) groupinstall() ( cd "$(groupdir)" && r7_install "$@" ; ) nodeinstall() ( cd "$(nodedir)" && r7_install "$@" ; ) @@ -783,7 +779,7 @@ _payload() ( done shift $((OPTIND - 1)) set -a - test -n "$source" && sh -n "$source" && . "./$source" + test -n "$source" && sh -n "$source" && set -a && . "./$source" && set +a for file; do sh > "${file%%.tpl}" <<-. cat <<-.. @@ -801,6 +797,7 @@ _payload() ( nodedir=$(nodedir) groupdir=$(groupdir) trace CONNECTED $(date +%s) + exec 1>&2 eof cat "$1" _debug "PREFIX $R7_PREFIX" @@ -817,6 +814,7 @@ _payload() ( # shellcheck disable=SC2086,SC2030 _collect() ( + set +u tee -a "$1" | while read -r line; do set -- $line case $line in @@ -1249,15 +1247,10 @@ html() { # Importers # ========= -# TODO: WIP import() { cd "$R7_WORKDIR" || _fail "Unable to cd to '$R7_WORKDIR'" target=$1 - etc_ssh_files_to_import=' - ssh/sshd_config - ssh/ssh_config - ' if test -n "$target"; then echo >&2 "-> Importing target host: $target" case $(ssh "$target" uname) in @@ -1267,6 +1260,9 @@ import() { FreeBSD) _FreeBSD_import ;; NetBSD) _NetBSD_import ;; DragonFly) _DragonFly_import ;; + Haiku) _Haiku_import ;; + QNX) _QNX_import ;; + Minix) _Minix_import ;; *) _error "Platform unsupported or host unavailable." return 3 @@ -1294,11 +1290,12 @@ _import_copy() ( -a "$src" "$dst" && return 0 } _debug "IMPORT $1 $2 USE SCP" - scp -r "$target:$src" "$dst" && return 0 + scp "-oConnectTimeout=$SSH_CONNECT_TIMEOUT" -qF "$SSH_CONFIG_FILE" -r "$src" "$dst" && return 0 _fail "IMPORT $1 $2 FAILED" ) -_import_ssh_authorized_keys() { +_import_ssh_authorized_keys_old() ( + set +e other_users=$( ssh "$target" getent passwd | awk -F: '$3 >= 1000 { print $1 }' | grep -v nobody @@ -1311,11 +1308,29 @@ _import_ssh_authorized_keys() { ssh "$target" test -f "$authorized_keys" || continue mkdir -p "nodes/$target/ssh/" echo >&2 "--> Importing $authorized_keys" - #_import_copy "root@$target:$authorized_keys" "nodes/$target/ssh/authorized_keys_$user" - scp "root@$target:$authorized_keys" "nodes/$target/ssh/authorized_keys_$user" + _import_copy "$target:$authorized_keys" "nodes/$target/ssh/authorized_keys_$user" done -} +) +_import_ssh_authorized_keys() ( + set +e + other_users=$( + ssh "$target" cat /etc/passwd | awk -F: '$3 >= 1000 { print $1 }' | grep -v nobody + ) + for user in root $other_users; do + home=$( + ssh "$target" cat /etc/passwd | awk -F: -v user="$user" '$1 == user { print $6 }' + ) + authorized_keys="$home/.ssh/authorized_keys" + if ! ssh -n "$target" test -f "$authorized_keys"; then + continue + fi + mkdir -p "nodes/$target/ssh/" + echo >&2 "--> Importing $authorized_keys for user='$user'" + _import_copy "$target:$authorized_keys" "nodes/$target/ssh/authorized_keys_$user" + done +) + _import_etc_if_exists() { mkdir -p nodes/"$target" echo >&2 "--> Importing /etc" @@ -1326,7 +1341,11 @@ _import_etc_if_exists() { } _import_etc_ssh_if_exists() { - mkdir -p nodes/"$target" + etc_ssh_files_to_import=' + ssh/sshd_config + ssh/ssh_config + ' + mkdir -p nodes/"$target/ssh" echo >&2 "--> Importing /etc/ssh" for file in $etc_ssh_files_to_import; do ssh "$target" test -f "'/etc/$file'" && @@ -1335,54 +1354,136 @@ _import_etc_ssh_if_exists() { } _import_modprobe() { - mkdir -p nodes/"$target" - echo >&2 "--> Importing /etc/modprobe.d" - for file in $etc_modprobe_files_to_import; do - ssh "$target" test -f "'/etc/modprobe.d/$file'" && - _import_copy "$target:/etc/modprobe.d/$file" "nodes/$target/modprobe.d" - done + if ssh "$target" test -d /etc/modprobe.d; then + _content=$(ssh "$target" ls -A /etc/modprobe.d/) + test -z "$_content" && return 0 + echo >&2 "--> Importing /etc/modprobe.d" + mkdir -p nodes/"$target/modprobe.d" + _import_copy "$target:/etc/modprobe.d/*" "nodes/$target/modprobe.d" + fi } +_QNX_import() { + etc_files_to_import=' + build.date + ' + _import_etc_if_exists + echo >&2 "--> Importing /etc/settings" + mkdir -p "nodes/$target/settings" + _import_copy "$target:/etc/settings" "nodes/$target/settings" + echo >&2 "--> Importing /system/etc/ssh" + mkdir -p "nodes/$target/ssh" + _import_copy "$target:/system/etc/ssh/sshd_config" "nodes/$target/ssh" + _import_ssh_authorized_keys +} + +_Minix_import() { + etc_files_to_import=' + fstab + inet.conf + profile + resolv.conf + shells + rc.conf + ' + _import_etc_if_exists + echo >&2 "--> Importing /usr/pkg/etc/ssh" + mkdir -p nodes/"$target/ssh" + _import_copy "$target:/usr/pkg/etc/ssh/sshd_config" "nodes/$target/ssh" + _import_copy "$target:/usr/pkg/etc/ssh/ssh_config" "nodes/$target/ssh" + echo >&2 "--> Importing /usr/pkg/etc/pkgin" + mkdir -p nodes/"$target/pkgin" + _import_copy "$target:/usr/pkg/etc/pkgin/repositories.conf" "nodes/$target/pkgin" +} + +_Haiku_import() { + etc_files_to_import=' + inputrc + profile + ' + _import_etc_if_exists + _import_copy "$target:/boot/system/settings/network/hosts" "nodes/$target/" + _import_copy "$target:/boot/system/settings/network/resolv.conf" "nodes/$target/" + _import_copy "$target:/boot/system/settings/package-repositories" "nodes/$target/" + echo >&2 "--> Importing /boot/system/settings/ssh" + mkdir -p "nodes/$target/ssh" + _import_copy "$target:/boot/system/settings/ssh/sshd_config" "nodes/$target/ssh" + _import_copy "$target:/boot/system/settings/ssh/ssh_config" "nodes/$target/ssh" + _import_copy "$target:/boot/home/config/settings/ssh/authorized_keys" \ + "nodes/$target/ssh/authorized_keys_user" +} + _Linux_import() { - etc_modprobe_files_to_import=' - modprobe.d/*.conf - ' if _Linux_is_debian_based; then _Linux_import_debian elif _Linux_is_rhel_based; then _Linux_import_rhel + elif _Linux_is_suse_based; then + _Linux_import_suse elif _Linux_is_gentoo_based; then _Linux_import_gentoo + elif _Linux_is_arch_based; then + _Linux_import_arch + elif _Linux_is_artix; then + _Linux_import_artix elif ssh "$target" "grep -q 'ID=alpine' /etc/os-release"; then _Linux_import_alpine + elif ssh "$target" "grep -q 'ID=crux' /etc/os-release"; then + _Linux_import_crux + elif ssh "$target" "grep -q 'ID=\"void\"' /etc/os-release"; then + _Linux_import_void + elif ssh "$target" "grep -q 'ID=guix' /etc/os-release"; then + _Linux_import_guix else echo >&2 "Unsupported Linux distribution." fi } _Linux_is_debian_based() ( - ssh "$target" "grep -q 'ID=debian' /etc/os-release || grep -q 'ID_LIKE=.*debian' /etc/os-release || \ - grep -qiE 'Debian|Ubuntu|Linux Mint' /etc/issue || \ - test -f /etc/debian_version || test -d /etc/apt || command -v apt-get >/dev/null 2>&1" + ssh "$target" "grep -q 'ID=debian' /etc/os-release || \ + grep -q 'ID_LIKE=.*debian' /etc/os-release || \ + grep -qiE 'Debian|Ubuntu|Linux Mint' /etc/issue || \ + test -f /etc/debian_version || test -d /etc/apt || command -v apt-get >/dev/null 2>&1" 2>/dev/null ) _Linux_is_rhel_based() ( - ssh "$target" "grep -qE 'ID=\"?(rhel|fedora|centos|ol|scientific)\"?' /etc/os-release || \ - grep -qE 'ID_LIKE=\"?.*(rhel|fedora).*\"?' /etc/os-release || \ - grep -qiE 'Red Hat|CentOS|Fedora|Scientific Linux|Oracle Linux|openSUSE|SUSE' /etc/issue || \ - grep -qiE 'Red Hat|CentOS|Fedora|Scientific Linux|Oracle Linux|openSUSE|SUSE' /etc/redhat-release || \ - test -f /etc/redhat-release || test -f /etc/system-release || test -f /etc/centos-release || \ - test -f /etc/fedora-release || test -f /etc/oracle-release || \ - (test -f /etc/os-release && grep -qi 'rhel' /etc/os-release) || test -d /etc/yum.repos.d" + ssh "$target" "grep -qiE 'ID=\"?(rhel|fedora|centos|ol|scientific|rocky|eurolinux)\"?' /etc/os-release || \ + grep -qiE 'ID_LIKE=\"?.*(rhel|fedora).*\"?' /etc/os-release || \ + grep -qiE 'Red Hat|CentOS|Fedora|Scientific Linux|Oracle Linux' /etc/issue || \ + test -f /etc/redhat-release || test -f /etc/system-release || test -f /etc/centos-release || \ + test -f /etc/fedora-release || test -f /etc/oracle-release || \ + (test -f /etc/os-release && grep -qi 'rhel' /etc/os-release) || test -d /etc/yum.repos.d" 2>/dev/null ) _Linux_is_gentoo_based() ( ssh "$target" "test -f /etc/gentoo-release || \ - grep -q 'ID=gentoo' /etc/os-release || \ - grep -qi 'Gentoo' /etc/issue || \ - command -v emerge >/dev/null 2>&1" + grep -q 'ID=gentoo' /etc/os-release || \ + grep -q 'ID=funtoo' /etc/os-release || \ + grep -qi 'Gentoo' /etc/issue || \ + command -v emerge >/dev/null 2>&1" 2>/dev/null ) +_Linux_is_suse_based() ( + ssh "$target" "test -f /etc/os-release && grep -Eqi \ + 'suse|opensuse' /etc/os-release || \ + command -v zypper >/dev/null || \ + command -v yast2 >/dev/null" 2>/dev/null +) + +_Linux_is_arch_based() ( + ssh "$target" "test -f /etc/os-release && grep -Eqi \ + 'arch|manjaro|endeavouros|arcolinux|parabola' /etc/os-release || \ + command -v pacman >/dev/null || \ + command -v makepkg >/dev/null" 2>/dev/null +) + +_Linux_is_artix() ( + ssh "$target" "test -f /etc/os-release && grep -Eqi \ + 'artix|hyperbola' /etc/os-release || \ + command -v pacman >/dev/null || \ + command -v makepkg >/dev/null" 2>/dev/null +) + _Linux_import_gentoo() { etc_files_to_import=' crontab @@ -1399,7 +1500,6 @@ _Linux_import_gentoo() { nsswitch.conf pam.conf profile - resolv.conf shells sudo.conf sudoers @@ -1407,6 +1507,9 @@ _Linux_import_gentoo() { ' _import_etc_if_exists _import_etc_ssh_if_exists + echo >&2 "--> Importing /etc/portage" + mkdir -p "nodes/$target/portage" + _import_copy "$target:/etc/portage/" "nodes/$target/portage/" _import_ssh_authorized_keys _import_modprobe } @@ -1432,6 +1535,9 @@ _Linux_import_alpine() { ' _import_etc_if_exists _import_etc_ssh_if_exists + echo >&2 "--> Importing /etc/apk" + mkdir -p "nodes/$target/apk" + _import_copy "$target:/etc/apk/repositories" "nodes/$target/apk/" _import_ssh_authorized_keys _import_modprobe } @@ -1458,6 +1564,9 @@ _Linux_import_rhel() { ' _import_etc_if_exists _import_etc_ssh_if_exists + echo >&2 "--> Importing /etc/yum.repos.d" + mkdir -p "nodes/$target/yum.repos.d" + _import_copy "$target:/etc/yum.repos.d/" "nodes/$target/yum.repos.d/" _import_ssh_authorized_keys _import_modprobe } @@ -1473,7 +1582,6 @@ _Linux_import_debian() { issue locale.gen machine-id - motd nsswitch.conf pam.conf profile @@ -1483,11 +1591,122 @@ _Linux_import_debian() { sudoers ' _import_etc_if_exists + echo >&2 "--> Importing /etc/apt" + mkdir -p "nodes/$target/apt" + _import_copy "$target:/etc/apt/sources.list" "nodes/$target/apt" + _import_copy "$target:/etc/apt/sources.list.d" "nodes/$target/apt" + echo >&2 "--> Importing /etc/network" + mkdir -p "nodes/$target/network/" + _import_copy "$target:/etc/network/interfaces" "nodes/$target/network/" + _import_copy "$target:/etc/network/interfaces.d" "nodes/$target/network/" _import_etc_ssh_if_exists _import_ssh_authorized_keys _import_modprobe + } +_Linux_import_suse() { + etc_files_to_import=' + fstab + hosts + profile + sudo.conf + sudoers + sysctl.conf + environment + exports + chrony.conf + ' + _import_etc_if_exists + _import_etc_ssh_if_exists + _import_ssh_authorized_keys + _import_modprobe +} + +_Linux_import_arch() { + etc_files_to_import=' + environment + hostname + profile + arch-release + issue + hosts + pacman.conf + sudo.conf + sudoers + fstab + ' + _import_etc_if_exists + _import_etc_ssh_if_exists + _import_ssh_authorized_keys + _import_modprobe +} + +_Linux_import_artix() { + _Linux_import_arch +} + +_Linux_import_guix() { + etc_files_to_import=' + config.scm + environment + fstab + hostname + hosts + issue + nsswitch.conf + profile + resolv.conf + shells + sudoers + ' + _import_etc_if_exists + _import_etc_ssh_if_exists + _import_ssh_authorized_keys +} + +_Linux_import_void() { + etc_files_to_import='\ + fstab + dhcpcd.conf + environment + hosts + profile + rc.conf + nsswitch.conf + shells + sudoers + sudo.conf + sysctl.conf + resolv.conf + ' + _import_etc_if_exists + _import_etc_ssh_if_exists + _import_ssh_authorized_keys + _import_modprobe +} + +_Linux_import_crux() { + etc_files_to_import=' + crontab + hosts + resolv.conf + environment + fstab + locale.gen + hosts + profile + rc.conf + rc.local + syslog.conf + sysctl.conf + ' + _import_etc_if_exists + _import_etc_ssh_if_exists + _import_ssh_authorized_keys + _import_modprobe +} + _SunOS_import() { etc_files_to_import=' crontab @@ -1515,6 +1734,65 @@ _SunOS_import() { _import_ssh_authorized_keys } +_FreeBSD_import() { + etc_files_to_import=' + crontab + fbtab + fstab + hostname + hosts + login.conf + nsswitch.conf + ntp.conf + profile + resolv.conf + shells + sysctl.conf + syslog.conf + ' + _import_etc_if_exists + echo >&2 "--> Importing /etc/pkg" + _import_copy "$target:/etc/pkg" "nodes/$target/" + _import_etc_ssh_if_exists + _import_ssh_authorized_keys +} + +_DragonFly_import() { + etc_files_to_import=' + crontab + fstab + hosts + login.conf + pf.conf + resolv.conf + shells + sysctl.conf + syslog.conf + ' + _import_etc_if_exists + _import_etc_ssh_if_exists + _import_ssh_authorized_keys +} + +_NetBSD_import() { + etc_files_to_import=' + fstab + hosts + locate.conf + pf.conf + resolv.conf + shells + sysctl.conf + syslog.conf + usermgmt.conf + wscons.conf + rc.local + ' + _import_etc_if_exists + _import_etc_ssh_if_exists + _import_ssh_authorized_keys +} + _OpenBSD_import() { etc_files_to_import=' acme-client.conf @@ -1533,18 +1811,15 @@ _OpenBSD_import() { mygate myname newsyslog.conf + syslog.conf ntpd.conf pf.conf resolv.conf sysctl.conf unwind.conf usermgt.conf + rc.conf.local ' - etc_mail_files_to_import=' - mail/aliases - mail/smtpd.conf - mail/spamd.conf - ' etc_ssh_files_to_import=' ssh/sshd_config ssh/ssh_config @@ -1580,6 +1855,11 @@ _OpenBSD_import_etc_if_exists() { } _OpenBSD_import_etc_mail_if_exists() { + etc_mail_files_to_import=' + mail/aliases + mail/smtpd.conf + mail/spamd.conf + ' mkdir -p nodes/"$target" echo >&2 "--> Importing /etc/mail" for file in $etc_mail_files_to_import; do @@ -1602,7 +1882,7 @@ _OpenBSD_import_unbound_if_enabled() { _OpenBSD_import_nsd_if_enabled() { ssh "$target" rcctl check nsd >/dev/null && { - echo >&2 "--> Import /var/nsd" + echo >&2 "--> Importing /var/nsd" for file in $var_nsd_files; do prefix=${file%%/*} mkdir -p "nodes/$target/nsd/$prefix" @@ -1620,69 +1900,13 @@ _OpenBSD_import_postgresql_if_installed() { mkdir -p "nodes/$target/postgresql/$prefix" _import_copy "$target:/var/postgresql/$file" "nodes/$target/postgresql/$prefix/" done - echo >&2 "-- Importing /etc/login.conf.d/postgresql" + echo >&2 "--> Importing /etc/login.conf.d/postgresql" mkdir -p "nodes/$target/login.conf.d" _import_copy "$target:/etc/login.conf.d/postgresql" "nodes/$target/login.conf.d/" return 0 } } -_FreeBSD_import() { - etc_files_to_import=' - crontab - fbtab - fstab - hostname - hosts - login.conf - nsswitch.conf - ntp.conf - profile - resolv.conf - shells - sysctl.conf - syslog.conf - ' - _import_ssh_authorized_keys - _import_etc_if_exists - _import_etc_ssh_if_exists -} - -_NetBSD_import() { - etc_files_to_import=' - fstab - hosts - locate.conf - pf.conf - resolv.conf - shells - sysctl.conf - syslog.conf - usermgmt.conf - wscons.conf - ' - _import_ssh_authorized_keys - _import_etc_if_exists - _import_etc_ssh_if_exists -} - -_DragonFly_import() { - etc_files_to_import=' - crontab - fstab - hosts - login.conf - pf.conf - resolv.conf - shells - sysctl.conf - syslog.conf - ' - _import_ssh_authorized_keys - _import_etc_if_exists - _import_etc_ssh_if_exists -} - # Main # ==== blob - 3e6d7688489b3ea53dcb1daaad648fe01cb119c3 blob + 61ca652ae438392a6480c60875b088254b303d5c --- r7.1 +++ r7.1 @@ -1,14 +1,14 @@ '\" t .\" Automatically generated by Pandoc 3.6.2 .\" -.TH "R7" "1" "Tue Jun 3 2025" "v0.1.6" "User Manual" +.TH "R7" "1" "Tue Jun 3 2025" "v0.2.0" "User Manual" .SS Tutorial .SS Introduction R7 is a configuration management framework made to configure nodes with -configuration files and related operations versionned in a central +configuration files and related operations versioned in a central repository. -To remain code versionning system agnostic, manual or automated -versionning is left to the user, via makefiles or triggered from r7 +To remain code versioning system agnostic, manual or automated +versioning is left to the user, via makefiles or triggered from r7 \f[CR]site\-config\f[R] configuration file \f[I](See below)\f[R]. .PP The idea is to store hosts configurations in \f[CR]nodes/hostname\f[R] @@ -16,10 +16,10 @@ directories that maps each host\[cq]s \f[CR]/etc\f[R] those hosts to configuration groups containing the directives to install the configuration and potential requirements. .PP -\f[I]Nodes sub\-directories are hostnames understood by the ssh client +\f[I]Nodes sub\-directories are host names understood by the ssh client and configuration.\f[R] .PP -\f[I]Currently, r7 requires valid ssh configuration and keyfiles in the +\f[I]Currently, r7 requires valid ssh configuration and key files in the repository \f[CI].ssh/\f[I] directory.\f[R] .PP \f[I]Repositories can be initialized with \f[CI]rset init\f[I] or @@ -59,7 +59,7 @@ output\-dump summary .EE .PP -There is an exemple of the deployment of that configuration within the +There is an example of the deployment of that configuration within the r7 repository: .IP .EX @@ -110,9 +110,9 @@ Any function managed by \f[CR]r7\f[R] needs to be decl \f[CR]\[ha]PREFIX_NAME()\f[R]. .PP Group files names can\[cq]t contain spaces, tabs or newlines, as well -for nodes dirnames. -\f[I]The `:' character is not recommanded as well as it may reduce -lisibility of the interactive output.\f[R] +for nodes \f[CR]dirnames\f[R]. +\f[I]The `:' character is not recommended as well as it may reduce +readability of the interactive output.\f[R] .PP Called groups and node directories are copied to related nodes in \f[CR]hostname:/tmp/r7/\f[R]: @@ -138,7 +138,7 @@ This means the functions matching \f[CR]\[ha]install.* \f[CR]R7_PREFIX\f[R]) in the respective declaration order they\[cq]re found in the related group file will be executed. .PP -Site confiurations are shell scripts sourced by r7 after it\[cq]s +Site configurations are shell scripts sourced by r7 after it\[cq]s environment is initialized, so \f[CR]prefix\f[R], \f[CR]group\f[R], \f[CR]output\-dump\f[R], \f[CR]summary\f[R] are the functions or aliases provided by r7 itself. @@ -146,7 +146,7 @@ This allow for scripting from the \f[CR]site\-config\f Here \f[CR]output\-dump\f[R], and \f[CR]summary\f[R] are called to output potential errors, diffs and permissions changes at the end of the interactive output obtained by \f[CR]r7 run\-config\f[R]. -If nothing catched happened, nothing is added to the output. +If nothing happened, nothing is added to the output. .SS Errors during remote execution of groups In our previous example, we modify \f[CR]state_test\f[R] so the \f[CR]test\f[R] fails. @@ -157,7 +157,7 @@ state_test() \f[B]{\f[R] \f[B]}\f[R] .EE .PP -And we re\-run the configuration to see what\[cq]s happenning. +And we re\-run the configuration to see what\[cq]s happening. .IP .EX $ r7 run\-config @@ -212,9 +212,9 @@ Diffs and permissions changes are also displayed by th When they\[cq]re are detected, the captured related output is shown. .SS Repository initialisation Using the \f[CR]init\f[R] command, we can specify the current or a -specifed work directory to be created and act as an r7 repository. +specified work directory to be created and act as an r7 repository. .PP -\f[I]Automated versionning of outputs are currently let to the user, a +\f[I]Automated versioning of outputs are currently let to the user, a recommended place would be the end of the site configuration file.\f[R] .PP This command initializes a new directory called \f[CR]test.org\f[R]: @@ -233,12 +233,12 @@ repository created in \f[CR].ssh/id_ed25519\-r7\f[R] a Adjust the SSH client configuration file in the repository \f[CR].ssh/config\f[R] .PP -\f[I]Note that it is possible to import additionnal SSH configurations, +\f[I]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.\f[R] .SS SSH agent handling The function \f[CR]unlock\f[R] can be used to set the SSH agent -environement, this will be required for any automated operation later +environment, this will be required for any automated operation later done by r7: .IP .EX @@ -247,9 +247,32 @@ cd test.org unlock tmux \f[B]||\f[R] screen .EE -.PP -\f[I]\f[BI]\f[B]Warning:\f[BI]\f[I] Unlock will terminate any running -\f[CI]ssh\-agent\f[I] instances.\f[R] +.SS Tmux configuration +.IP +.EX +set \-g update\-environment \-r +.EE +.SS Optional Shell configuration +This is useful for handling multiple tmux sessions. +.IP +.EX +\f[I]# Add this to the shell\[aq]s rc\f[R] +\f[B]if\f[R] [ \-z \[dq]$TMUX\[dq] ]\f[B];\f[R] \f[B]then\f[R] + \f[B]if\f[R] [ \-n \[dq]$SSH_TTY\[dq] ]\f[B];\f[R] \f[B]then\f[R] + \f[B]if\f[R] [ \-z \[dq]$SSH_AUTH_SOCK\[dq] ]\f[B];\f[R] \f[B]then\f[R] + export SSH_AUTH_SOCK=\[dq]$HOME/.ssh/.auth_socket\[dq] + \f[B]fi\f[R] + \f[B]if\f[R] [ ! \-S \[dq]$SSH_AUTH_SOCK\[dq] ]\f[B];\f[R] \f[B]then\f[R] + eval $(ssh\-agent \-a $SSH_AUTH_SOCK) >/dev/null 2>&1 + echo $SSH_AGENT_PID >$HOME/.ssh/.auth_pid + \f[B]fi\f[R] + \f[B]if\f[R] [ \-z $SSH_AGENT_PID ]\f[B];\f[R] \f[B]then\f[R] + export SSH_AGENT_PID=$(cat $HOME/.ssh/.auth_pid) + \f[B]fi\f[R] + ssh\-add \[dq]$SSH_ID\[dq] 2>/dev/null + \f[B]fi\f[R] +\f[B]fi\f[R] +.EE .SS Adding new hosts For adding a new host, the simplest way is to have pre\-configured \f[CR]authorized_keys\f[R] for the target user, it is then required to @@ -276,8 +299,6 @@ for the target user (root, by default) and put the Once the host is accepted in the \f[CR]known_hosts\f[R] file, it\[cq]s possible to import it automatically. .SS Importing hosts configurations -\f[I]This feature currently only support OpenBSD.\f[R] -.PP Avoiding to import the configuration manually is possible with the \f[CR]import\f[R] command: .IP @@ -288,7 +309,7 @@ r7 import hostname Most common configuration files will be copied in a newly created \f[CR]nodes/hostname\f[R] sub\-directory. .SS Writing group files -Group files collects fonctions related to the configuration of the +Group files collects functions related to the configuration of the operating system, networking, services, global and user configurations packages installations and application deployments. .PP @@ -321,7 +342,7 @@ doc_A() \f[B]{\f[R] \f[I]\f[BI]file\f[I]: \f[CI]groups/example1\f[I]\f[R] .PP With the default prefix, \f[CR]install\f[R], \f[CR]state\f[R], -\f[CR]doc\f[R], the fonction that will be executed are +\f[CR]doc\f[R], the function that will be executed are \f[CR]state_A\f[R] and \f[CR]doc_A\f[R]. To test that group file with all the functions inside we can call r7 the following way: @@ -481,7 +502,7 @@ In other terms, this will update the NTP daemon config kept by default, which is the system provided default \f[CR]ntpd.conf\f[R]. .SS Sourcing specific files -Here\[cq]s an example how to source additionnal shell from the deployed +Here\[cq]s an example how to source additional shell from the deployed node directory, or elsewhere: .IP .EX @@ -590,7 +611,7 @@ $ r7 output tools0 OpenBSD state_packages\[rs]* .EE .PP \f[I]Note that the \f[CI]output\f[I] function arguments are using -shell\[cq]s case globbing patterns.\f[R] +shell\[cq]s case glob patterns.\f[R] .SS Active nodes A \f[CR]R7_WORKDIR/.active_nodes\f[R] file is automatically created when r7 tries to connect to a list of hosts via the \f[CR]group\f[R] command @@ -955,7 +976,7 @@ returns 31 node [filename\f[B]|\f[R]dirname] .EE .SS nodename -\f[B]\f[BI]Description:\f[B]\f[R] Returns the small hostname of the +\f[B]\f[BI]Description:\f[B]\f[R] Returns the small host name of the current host .PP \f[B]\f[BI]Usage:\f[B]\f[R] @@ -1021,7 +1042,7 @@ destination .EE .SS groupinstalldir \f[B]\f[BI]Description:\f[B]\f[R] Change directory to the current group -directoy then call installdir +directory then call installdir .PP \f[B]\f[BI]Usage:\f[B]\f[R] .IP @@ -1031,7 +1052,7 @@ destination .EE .SS nodeinstalldir \f[B]\f[BI]Description:\f[B]\f[R] Change directory to the current group -directoy then call installdir +directory then call installdir .PP \f[B]\f[BI]Usage:\f[B]\f[R] .IP @@ -1039,14 +1060,14 @@ directoy then call installdir nodeinstalldir [\-o fileowner] [\-m filemode] [\-O dirowner] [\-O dirmode] source destination .EE -.SS source +.SS fsource \f[B]\f[BI]Description:\f[B]\f[R] Source a POSIX shell file in the current execution .PP \f[B]\f[BI]Usage:\f[B]\f[R] .IP .EX -source filename +fsource filename .EE .SS groupsource \f[B]\f[BI]Description:\f[B]\f[R] Change directory to the current group @@ -1263,7 +1284,7 @@ ssh\-authorized\-keys .EE .SS ssh\-ids\-hosts \f[B]\f[BI]Description:\f[B]\f[R] Returns SSH IDs along with matching -keyfile and hostname +key file and host name .PP \f[B]\f[BI]Usage:\f[B]\f[R] .IP @@ -1527,21 +1548,69 @@ import HOST * * * * * .SS Technical notes .IP \[bu] 2 -Supported shells are ksh93, oksh, yash and bash. +Supported shells are ksh93, oksh, yash and bash .IP \[bu] 2 -Supported platforms are OpenBSD, FreeBSD, NetBSD and GNU/Linux. +Supported target platforms are POSIX compatible systems +.RS 2 .IP \[bu] 2 +\f[CR]/bin/sh\f[R] or compatible must be set for the user\[cq]s shell +(which is usually the case by default) +.IP \[bu] 2 +\f[CR]/tmp\f[R] must be writable by the user +.RE +.IP \[bu] 2 +Supported control node platforms are supposed to be POSIX compatible +systems but currently limited to BSD\[cq]s, Darwin and GNU/Linux +.RS 2 +.IP \[bu] 2 Dependencies are awk, OpenSSH and column .IP \[bu] 2 -Optional dependencies are openrsync, rsync, column, graphviz and pandoc. +Optional dependencies are openrsync, rsync, column, graphviz and pandoc +.RE .IP \[bu] 2 -Busybox support is planned. +The r7 importer supports the following platform: +.RS 2 .IP \[bu] 2 -The HTML output is a work in progress. +Alpine Linux +.IP \[bu] 2 +Archlinux +.IP \[bu] 2 +Crux +.IP \[bu] 2 +Debian +.IP \[bu] 2 +DragonFlyBSD +.IP \[bu] 2 +FreeBSD +.IP \[bu] 2 +Gentoo +.IP \[bu] 2 +Guix +.IP \[bu] 2 +Haiku +.IP \[bu] 2 +Minix +.IP \[bu] 2 +NetBSD +.IP \[bu] 2 +Omnios +.IP \[bu] 2 +OpenBSD +.IP \[bu] 2 +OpenSUSE +.IP \[bu] 2 +QNX +.IP \[bu] 2 +Rocky Linux +.IP \[bu] 2 +Salix +.IP \[bu] 2 +Void Linux +.RE .PP * * * * * .SS License -The provided group library is shared as additionnal examples WITH NO +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