diff --git a/.gitattributes b/.gitattributes
index 45c10d7b..01d20f30 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -4,5 +4,5 @@
docs/speeches export-ignore
docs/video export-ignore
docs/src/man7 export-ignore
-bin/build-helper export-ignore
+bin/cdist-build-helper export-ignore
README-maintainers export-ignore
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index e215652c..a4bc67aa 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,20 +1,23 @@
+---
+image: code.ungleich.ch:5050/ungleich-public/cdist/cdist-ci:latest
+
stages:
- test
-image: code.ungleich.ch:5050/ungleich-public/cdist/cdist-ci:latest
-
-unit_tests:
- stage: test
- script:
- - ./bin/build-helper version
- - ./bin/build-helper test
-
-pycodestyle:
- stage: test
- script:
- - ./bin/build-helper pycodestyle
+before_script:
+ - ./bin/cdist-build-helper version
shellcheck:
stage: test
script:
- - ./bin/build-helper shellcheck
+ - ./bin/cdist-build-helper shellcheck
+
+pycodestyle:
+ stage: test
+ script:
+ - ./bin/cdist-build-helper pycodestyle
+
+unit_tests:
+ stage: test
+ script:
+ - ./bin/cdist-build-helper test
diff --git a/README-maintainers b/README-maintainers
index af57f475..5766dd7d 100644
--- a/README-maintainers
+++ b/README-maintainers
@@ -1,4 +1,4 @@
-Maintainers should use ./bin/build-helper script.
+Maintainers should use ./bin/cdist-build-helper script.
Makefile is intended for end users. It can be used for non-maintaining
targets that can be run from pure source (without git repository).
diff --git a/bin/cdist b/bin/cdist
index 645020a1..ddaffa7f 100755
--- a/bin/cdist
+++ b/bin/cdist
@@ -1,7 +1,8 @@
-#!/bin/sh
+#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
-# 2012 Nico Schottelius (nico-cdist at schottelius.org)
+# 2010-2016 Nico Schottelius (nico-cdist at schottelius.org)
+# 2016 Darko Poljak (darko.poljak at gmail.com)
#
# This file is part of cdist.
#
@@ -20,14 +21,81 @@
#
#
-# Wrapper for real script to allow execution from checkout
-dir=${0%/*}
+import logging
+import os
+import sys
-# Ensure version is present - the bundled/shipped version contains a static version,
-# the git version contains a dynamic version
-"$dir/build-helper" version
+# See if this file's parent is cdist module
+# and if so add it to module search path.
+cdist_dir = os.path.realpath(
+ os.path.join(
+ os.path.dirname(os.path.realpath(__file__)),
+ os.pardir))
+cdist_init_dir = os.path.join(cdist_dir, 'cdist', '__init__.py')
+if os.path.exists(cdist_init_dir):
+ sys.path.insert(0, cdist_dir)
-libdir=$(cd "${dir}/../" && pwd -P)
-export PYTHONPATH="${libdir}"
+import cdist # noqa 402
+import cdist.argparse # noqa 402
+import cdist.banner # noqa 402
+import cdist.config # noqa 402
+import cdist.install # noqa 402
+import cdist.shell # noqa 402
+import cdist.inventory # noqa 402
-"$dir/../scripts/cdist" "$@"
+
+def commandline():
+ """Parse command line"""
+
+ # preos subcommand hack
+ if len(sys.argv) > 1 and sys.argv[1] == 'preos':
+ return cdist.preos.PreOS.commandline(sys.argv[1:])
+ parser, cfg = cdist.argparse.parse_and_configure(sys.argv[1:])
+ args = cfg.get_args()
+
+ # Work around python 3.3 bug:
+ # http://bugs.python.org/issue16308
+ # http://bugs.python.org/issue9253
+
+ # FIXME: catching AttributeError also hides
+ # real problems.. try a different way
+
+ # FIXME: we always print main help, not
+ # the help of the actual parser being used!
+ try:
+ getattr(args, "func")
+ except AttributeError:
+ parser['main'].print_help()
+ sys.exit(0)
+
+ args.func(args)
+
+
+if __name__ == "__main__":
+ if sys.version < cdist.MIN_SUPPORTED_PYTHON_VERSION:
+ print('Python >= {} is required on the source host.'.format(
+ cdist.MIN_SUPPORTED_PYTHON_VERSIO), file=sys.stderr)
+ sys.exit(1)
+
+ exit_code = 0
+
+ try:
+ import re
+ import os
+
+ if re.match("__", os.path.basename(sys.argv[0])):
+ import cdist.emulator
+ emulator = cdist.emulator.Emulator(sys.argv)
+ emulator.run()
+ else:
+ commandline()
+
+ except KeyboardInterrupt:
+ exit_code = 2
+
+ except cdist.Error as e:
+ log = logging.getLogger("cdist")
+ log.error(e)
+ exit_code = 1
+
+ sys.exit(exit_code)
diff --git a/bin/build-helper b/bin/cdist-build-helper
similarity index 93%
rename from bin/build-helper
rename to bin/cdist-build-helper
index d4d603ed..0380b3f8 100755
--- a/bin/build-helper
+++ b/bin/cdist-build-helper
@@ -45,7 +45,7 @@ usage() {
shellcheck-manifests
shellcheck-local-gencodes
shellcheck-remote-gencodes
- shellcheck-scripts
+ shellcheck-bin
shellcheck-gencodes
shellcheck-types
shellcheck
@@ -100,7 +100,7 @@ case "$option" in
if (\$0 ~ /^$end/) {
exit
} else {
- print \$0
+ print \$0
}
}
}" "$basedir/docs/changelog"
@@ -135,7 +135,7 @@ case "$option" in
version=$1; shift
- (
+ (
cat << eof
Subject: cdist $version has been released
@@ -336,7 +336,7 @@ eof
make docs-clean
make docs
- #############################################################
+ #############################################################
# Everything green, let's do the release
# Tag the current commit
@@ -405,7 +405,7 @@ eof
;;
pycodestyle|pep8)
- pycodestyle "${basedir}" "${basedir}/scripts/cdist"
+ pycodestyle "${basedir}" "${basedir}/bin/cdist"
;;
check-pycodestyle)
@@ -460,27 +460,34 @@ eof
test ! -s "${SHELLCHECKTMP}" || { cat "${SHELLCHECKTMP}"; exit 1; }
;;
- shellcheck-scripts)
+ # NOTE: shellcheck-scripts is kept for compatibility
+ shellcheck-bin|shellcheck-scripts)
# shellcheck disable=SC2086
- ${SHELLCHECKCMD} scripts/cdist-dump scripts/cdist-new-type > "${SHELLCHECKTMP}"
+ ${SHELLCHECKCMD} bin/cdist-dump bin/cdist-new-type > "${SHELLCHECKTMP}"
test ! -s "${SHELLCHECKTMP}" || { cat "${SHELLCHECKTMP}"; exit 1; }
;;
shellcheck-gencodes)
- "$0" shellcheck-local-gencodes || exit 1
- "$0" shellcheck-remote-gencodes || exit 1
+ errors=false
+ "$0" shellcheck-local-gencodes || errors=true
+ "$0" shellcheck-remote-gencodes || errors=true
+ ! $errors || exit 1
;;
shellcheck-types)
- "$0" shellcheck-type-explorers || exit 1
- "$0" shellcheck-manifests || exit 1
- "$0" shellcheck-gencodes || exit 1
+ errors=false
+ "$0" shellcheck-type-explorers || errors=true
+ "$0" shellcheck-manifests || errors=true
+ "$0" shellcheck-gencodes || errors=true
+ ! $errors || exit 1
;;
shellcheck)
- "$0" shellcheck-global-explorers || exit 1
- "$0" shellcheck-types || exit 1
- "$0" shellcheck-scripts || exit 1
+ errors=false
+ "$0" shellcheck-global-explorers || errors=true
+ "$0" shellcheck-types || errors=true
+ "$0" shellcheck-bin || errors=true
+ ! $errors || exit 1
;;
shellcheck-type-files)
@@ -490,12 +497,14 @@ eof
;;
shellcheck-with-files)
- "$0" shellcheck || exit 1
- "$0" shellcheck-type-files || exit 1
+ errors=false
+ "$0" shellcheck || errors=true
+ "$0" shellcheck-type-files || errors=true
+ ! $errors || exit 1
;;
shellcheck-build-helper)
- ${SHELLCHECKCMD} ./bin/build-helper
+ ${SHELLCHECKCMD} ./bin/cdist-build-helper
;;
check-shellcheck)
diff --git a/scripts/cdist-dump b/bin/cdist-dump
similarity index 100%
rename from scripts/cdist-dump
rename to bin/cdist-dump
diff --git a/scripts/cdist-new-type b/bin/cdist-new-type
similarity index 100%
rename from scripts/cdist-new-type
rename to bin/cdist-new-type
diff --git a/cdist/__init__.py b/cdist/__init__.py
index 5cfb2be3..3727f198 100644
--- a/cdist/__init__.py
+++ b/cdist/__init__.py
@@ -22,12 +22,27 @@
import os
import hashlib
+import subprocess
import cdist.log
-import cdist.version
-VERSION = cdist.version.VERSION
+VERSION = 'unknown version'
+
+try:
+ import cdist.version
+ VERSION = cdist.version.VERSION
+except ModuleNotFoundError:
+ cdist_dir = os.path.abspath(
+ os.path.join(os.path.dirname(__file__), os.pardir))
+ if os.path.isdir(os.path.join(cdist_dir, '.git')):
+ try:
+ VERSION = subprocess.check_output(
+ ['git', 'describe', '--always'],
+ cwd=cdist_dir,
+ universal_newlines=True)
+ except Exception:
+ pass
BANNER = """
.. . .x+=:. s
diff --git a/cdist/conf/type/__hostname/gencode-remote b/cdist/conf/type/__hostname/gencode-remote
index 02afcbfb..c1a97ac8 100755
--- a/cdist/conf/type/__hostname/gencode-remote
+++ b/cdist/conf/type/__hostname/gencode-remote
@@ -20,26 +20,27 @@
# along with cdist. If not, see .
#
-os=$(cat "$__global/explorer/os")
-name_running=$(cat "$__global/explorer/hostname")
-has_hostnamectl=$(cat "$__object/explorer/has_hostnamectl")
+os=$(cat "${__global:?}/explorer/os")
+name_running=$(cat "${__global:?}/explorer/hostname")
+has_hostnamectl=$(cat "${__object:?}/explorer/has_hostnamectl")
-if test -s "$__object/parameter/name"
+if test -s "${__object:?}/parameter/name"
then
- name_should=$(cat "$__object/parameter/name")
+ name_should=$(cat "${__object:?}/parameter/name")
else
- case $os
+ case ${os}
in
# RedHat-derivatives and BSDs
- centos|fedora|redhat|scientific|freebsd|macosx|netbsd|openbsd)
+ (centos|fedora|redhat|scientific|freebsd|macosx|netbsd|openbsd)
# Hostname is FQDN
- name_should="${__target_host}"
- ;;
- *)
+ name_should=${__target_host:?}
+ ;;
+ (*)
# Hostname is only first component of FQDN
- name_should="${__target_host%%.*}"
- ;;
+ name_should=${__target_host:?}
+ name_should=${name_should%%.*}
+ ;;
esac
fi
@@ -47,46 +48,46 @@ fi
################################################################################
# Check if the (running) hostname is already correct
#
-test "$name_running" != "$name_should" || exit 0
+test "${name_running}" != "${name_should}" || exit 0
################################################################################
# Setup hostname
#
-echo 'changed' >>"$__messages_out"
+echo 'changed' >>"${__messages_out:?}"
# Use the good old way to set the hostname.
-case $os
+case ${os}
in
- alpine|debian|devuan|ubuntu)
+ (alpine|debian|devuan|ubuntu)
echo 'hostname -F /etc/hostname'
- ;;
- archlinux)
+ ;;
+ (archlinux)
echo 'command -v hostnamectl >/dev/null 2>&1' \
- "&& hostnamectl set-hostname '$name_should'" \
- "|| hostname '$name_should'"
- ;;
- centos|fedora|redhat|scientific|freebsd|netbsd|openbsd|gentoo|void)
- echo "hostname '$name_should'"
- ;;
- openwrt)
- echo "echo '$name_should' >/proc/sys/kernel/hostname"
- ;;
- macosx)
- echo "scutil --set HostName '$name_should'"
- ;;
- solaris)
- echo "uname -S '$name_should'"
- ;;
- slackware|suse|opensuse-leap)
+ "&& hostnamectl set-hostname '${name_should}'" \
+ "|| hostname '${name_should}'"
+ ;;
+ (centos|fedora|redhat|scientific|freebsd|netbsd|openbsd|gentoo|void)
+ echo "hostname '${name_should}'"
+ ;;
+ (openwrt)
+ echo "echo '${name_should}' >/proc/sys/kernel/hostname"
+ ;;
+ (macosx)
+ echo "scutil --set HostName '${name_should}'"
+ ;;
+ (solaris)
+ echo "uname -S '${name_should}'"
+ ;;
+ (slackware|suse)
# We do not read from /etc/HOSTNAME, because the running
# hostname is the first component only while the file contains
# the FQDN.
- echo "hostname '$name_should'"
- ;;
- *)
+ echo "hostname '${name_should}'"
+ ;;
+ (*)
# Fall back to set the hostname using hostnamectl, if available.
- if test -n "$has_hostnamectl"
+ if test -n "${has_hostnamectl}"
then
# Don't use hostnamectl as the primary means to set the hostname for
# systemd systems, because it cannot be trusted to work reliably and
@@ -97,7 +98,8 @@ in
echo "test \"\$(hostname)\" = \"\$(cat /etc/hostname)\"" \
" || hostname -F /etc/hostname"
else
- printf "echo 'Unsupported OS: %s' >&2\nexit 1\n" "$os"
+ printf "echo 'Unsupported OS: %s' >&2\n" "${os}"
+ printf 'exit 1\n'
fi
- ;;
+ ;;
esac
diff --git a/cdist/conf/type/__hostname/manifest b/cdist/conf/type/__hostname/manifest
index bf8a331c..b80aa2ef 100755
--- a/cdist/conf/type/__hostname/manifest
+++ b/cdist/conf/type/__hostname/manifest
@@ -20,69 +20,49 @@
# along with cdist. If not, see .
#
-not_supported() {
- echo "Your operating system ($os) is currently not supported by this type (${__type##*/})." >&2
- echo "Please contribute an implementation for it if you can." >&2
- exit 1
-}
-
set_hostname_systemd() {
echo "$1" | __file /etc/hostname --source -
}
-os=$(cat "$__global/explorer/os")
-os_version=$(cat "$__global/explorer/os_version")
-os_major=$(echo "$os_version" | grep -o '^[0-9][0-9]*' || true)
+os=$(cat "${__global:?}/explorer/os")
-max_len=$(cat "$__object/explorer/max_len")
-has_hostnamectl=$(cat "$__object/explorer/has_hostnamectl")
+max_len=$(cat "${__object:?}/explorer/max_len")
+has_hostnamectl=$(cat "${__object:?}/explorer/has_hostnamectl")
-if test -s "$__object/parameter/name"
+if test -s "${__object:?}/parameter/name"
then
- name_should=$(cat "$__object/parameter/name")
+ name_should=$(cat "${__object:?}/parameter/name")
else
- case $os
+ case ${os}
in
# RedHat-derivatives and BSDs
- centos|fedora|redhat|scientific|freebsd|netbsd|openbsd|slackware)
+ (centos|fedora|redhat|scientific|freebsd|netbsd|openbsd|slackware|suse)
# Hostname is FQDN
- name_should="${__target_host}"
- ;;
- suse|opensuse-leap)
- # Classic SuSE stores the FQDN in /etc/HOSTNAME, while
- # systemd does not. The running hostname is the first
- # component in both cases.
- # In versions before 15.x, the FQDN is stored in /etc/hostname.
- if test -n "$has_hostnamectl" && test "$os_major" -ge 15 \
- && test "$os_major" -ne 42
- then
- name_should="${__target_host%%.*}"
- else
- name_should="${__target_host}"
- fi
- ;;
+ name_should=${__target_host:?}
+ ;;
*)
# Hostname is only first component of FQDN on all other systems.
- name_should="${__target_host%%.*}"
- ;;
+ name_should=${__target_host:?}
+ name_should=${name_should%%.*}
+ ;;
esac
fi
-if test -n "$max_len" && test "$(printf '%s' "$name_should" | wc -c)" -gt "$max_len"
+if test -n "${max_len}" && test "$(printf '%s' "${name_should}" | wc -c)" -gt "${max_len}"
then
printf "Host name too long. Up to %u characters allowed.\n" "${max_len}" >&2
exit 1
fi
-case $os
+case ${os}
in
- alpine|debian|devuan|ubuntu|void)
- echo "$name_should" | __file /etc/hostname --source -
- ;;
- archlinux)
- if test -n "$has_hostnamectl"
+ (alpine|debian|devuan|ubuntu|void)
+ echo "${name_should}" | __file /etc/hostname --source -
+ ;;
+ (archlinux)
+ if test -n "${has_hostnamectl}"
then
- set_hostname_systemd "$name_should"
+ set_hostname_systemd "${name_should}"
else
echo 'Ancient ArchLinux variants without hostnamectl are not supported.' >&2
exit 1
@@ -97,8 +77,8 @@ in
# --value "\"$name_should\""
fi
;;
- centos|fedora|redhat|scientific)
- if test -z "$has_hostnamectl"
+ (centos|fedora|redhat|scientific)
+ if test -z "${has_hostnamectl}"
then
# Only write to /etc/sysconfig/network on non-systemd versions.
# On systemd-based versions this entry is ignored.
@@ -106,63 +86,83 @@ in
--file /etc/sysconfig/network \
--delimiter '=' --exact_delimiter \
--key HOSTNAME \
- --value "\"$name_should\""
+ --value "\"${name_should}\""
else
- set_hostname_systemd "$name_should"
+ set_hostname_systemd "${name_should}"
fi
- ;;
- gentoo)
+ ;;
+ (gentoo)
# Only write to /etc/conf.d/hostname on OpenRC-based installations.
# On systemd use hostnamectl(1) in gencode-remote.
- if test -z "$has_hostnamectl"
+ if test -z "${has_hostnamectl}"
then
__key_value '/etc/conf.d/hostname:hostname' \
--file /etc/conf.d/hostname \
--delimiter '=' --exact_delimiter \
--key 'hostname' \
- --value "\"$name_should\""
+ --value "\"${name_should}\""
else
set_hostname_systemd "$name_should"
fi
- ;;
- freebsd)
+ ;;
+ (freebsd)
__key_value '/etc/rc.conf:hostname' \
--file /etc/rc.conf \
--delimiter '=' --exact_delimiter \
--key 'hostname' \
- --value "\"$name_should\""
- ;;
- macosx)
+ --value "\"${name_should}\""
+ ;;
+ (macosx)
# handled in gencode-remote
- :
- ;;
- netbsd)
+ ;;
+ (netbsd)
__key_value '/etc/rc.conf:hostname' \
--file /etc/rc.conf \
--delimiter '=' --exact_delimiter \
--key 'hostname' \
- --value "\"$name_should\""
+ --value "\"${name_should}\""
# To avoid confusion, ensure that the hostname is only stored once.
__file /etc/myname --state absent
- ;;
- openbsd)
- echo "$name_should" | __file /etc/myname --source -
- ;;
- openwrt)
- __uci system.@system[0].hostname --value "$name_should"
+ ;;
+ (openbsd)
+ echo "${name_should}" | __file /etc/myname --source -
+ ;;
+ (openwrt)
+ __uci system.@system[0].hostname --value "${name_should}"
# --transaction hostname
- ;;
- slackware)
+ ;;
+ (slackware)
# We write the FQDN into /etc/HOSTNAME. But /etc/rc.d/rc.M will only
# read the first component from this file and set it as the running
# hostname on boot.
- echo "$name_should" | __file /etc/HOSTNAME --source -
- ;;
- solaris)
- echo "$name_should" | __file /etc/nodename --source -
- ;;
- suse|opensuse-leap)
+ echo "${name_should}" | __file /etc/HOSTNAME --source -
+ ;;
+ (solaris)
+ echo "${name_should}" | __file /etc/nodename --source -
+ ;;
+ (suse)
+ if test -s "${__global:?}/explorer/os_release"
+ then
+ # shellcheck source=/dev/null
+ os_version=$(. "${__global:?}/explorer/os_release" && echo "${VERSION}")
+ else
+ os_version=$(sed -n 's/^VERSION\ *=\ *//p' "${__global:?}/explorer/os_version")
+ fi
+ os_major=$(expr "${os_version}" : '\([0-9]\{1,\}\)')
+
+ # Classic SuSE stores the FQDN in /etc/HOSTNAME, while
+ # systemd does not. The running hostname is the first
+ # component in both cases.
+ # In versions before 15.x, the FQDN is stored in /etc/hostname.
+ if test -n "${has_hostnamectl}" \
+ && test "${os_major}" -ge 15 \
+ && test "${os_major}" -ne 42
+ then
+ # strip away everything but the first part from $name_should
+ name_should=${name_should%%.*}
+ fi
+
# Modern SuSE provides /etc/HOSTNAME as a symlink for
# backwards-compatibility. Unfortunately it cannot be used
# here as __file does not follow the symlink.
@@ -171,23 +171,25 @@ in
# not work correctly on openSUSE 12.x which provides
# hostnamectl but not /etc/hostname.
- if test -n "$has_hostnamectl" -a "$os_major" -gt 12
+ if test -n "${has_hostnamectl}" -a "${os_major}" -gt 12
then
- hostname_file='/etc/hostname'
+ hostname_file=/etc/hostname
else
- hostname_file='/etc/HOSTNAME'
+ hostname_file=/etc/HOSTNAME
fi
- echo "$name_should" | __file "$hostname_file" --source -
- ;;
- *)
+ echo "${name_should}" | __file "${hostname_file}" --source -
+ ;;
+ (*)
# On other operating systems we fall back to systemd's
# hostnamectl if available…
- if test -n "$has_hostnamectl"
+ if test -n "${has_hostnamectl}"
then
- set_hostname_systemd "$name_should"
+ set_hostname_systemd "${name_should}"
else
- not_supported
+ echo "Your operating system (${os}) is currently not supported by this type (${__type##*/})." >&2
+ echo "Please contribute an implementation for it if you can." >&2
+ exit 1
fi
- ;;
+ ;;
esac
diff --git a/cdist/conf/type/__hwclock/explorer/adjtime_mode b/cdist/conf/type/__hwclock/explorer/adjtime_mode
new file mode 100755
index 00000000..2b27bedc
--- /dev/null
+++ b/cdist/conf/type/__hwclock/explorer/adjtime_mode
@@ -0,0 +1,28 @@
+#!/bin/sh -e
+#
+# 2020 Dennis Camera (dennis.camera at ssrq-sds-fds.ch)
+#
+# This file is part of cdist.
+#
+# cdist is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# cdist is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with cdist. If not, see .
+#
+# Prints the clock mode read from the /etc/adjtime file, if present.
+#
+
+# not all operating systems use an adjfile
+test -f /etc/adjtime || exit 0
+
+# 3rd line is clock mode
+# adjtime(5) https://man7.org/linux/man-pages/man5/adjtime.5.html
+sed -n 3p /etc/adjtime
diff --git a/cdist/conf/type/__hwclock/explorer/timedatectl_localrtc b/cdist/conf/type/__hwclock/explorer/timedatectl_localrtc
new file mode 100755
index 00000000..8239122e
--- /dev/null
+++ b/cdist/conf/type/__hwclock/explorer/timedatectl_localrtc
@@ -0,0 +1,27 @@
+#!/bin/sh -e
+#
+# 2020 Dennis Camera (dennis.camera at ssrq-sds-fds.ch)
+#
+# This file is part of cdist.
+#
+# cdist is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# cdist is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with cdist. If not, see .
+#
+# Prints the LocalRTC property using timedatectl on systemd-based systems.
+#
+
+command -v timedatectl >/dev/null 2>&1 || exit 0
+
+# NOTE: Older versions of timedatectl do not support `timedatectl show'
+timedatectl --no-pager status \
+| awk -F': ' '$1 ~ "RTC in local TZ$" { sub(/[ \t]*$/, "", $2); print $2 }'
diff --git a/cdist/conf/type/__hwclock/gencode-remote b/cdist/conf/type/__hwclock/gencode-remote
new file mode 100755
index 00000000..5995fb23
--- /dev/null
+++ b/cdist/conf/type/__hwclock/gencode-remote
@@ -0,0 +1,62 @@
+#!/bin/sh -e
+#
+# 2020 Dennis Camera (dennis.camera@ssrq-sds-fds.ch)
+#
+# This file is part of cdist.
+#
+# cdist is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# cdist is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with cdist. If not, see .
+#
+
+mode=$(cat "${__object:?}/parameter/mode")
+
+timedatectl_localrtc=$(cat "${__object:?}/explorer/timedatectl_localrtc")
+adjtime_mode=$(cat "${__object:?}/explorer/adjtime_mode")
+
+
+case ${mode}
+in
+ (localtime)
+ adjtime_str=LOCAL
+ local_rtc_str=yes
+ ;;
+ (UTC|utc)
+ adjtime_str=UTC
+ local_rtc_str=no
+ ;;
+ (*)
+ printf 'Invalid value for --mode: %s\n' "${mode}" >&2
+ printf 'Acceptable values are: localtime, utc.\n' >&2
+ exit 1
+esac
+
+
+if test -n "${timedatectl_localrtc}"
+then
+ # systemd
+ timedatectl_should=${local_rtc_str}
+ if test "${timedatectl_localrtc}" != "${timedatectl_should}"
+ then
+ printf 'timedatectl set-local-rtc %s\n' "${timedatectl_should}"
+ fi
+elif test -n "${adjtime_mode}"
+then
+ # others (update /etc/adjtime if present)
+ if test "${adjtime_mode}" != "${adjtime_str}"
+ then
+ # Update /etc/adjtime (3rd line is clock mode)
+ # adjtime(5) https://man7.org/linux/man-pages/man5/adjtime.5.html
+ # FIXME: Should maybe add third line if adjfile only contains two lines
+ printf "sed -i '3c\\\\\\n%s\\n' /etc/adjtime\\n" "${adjtime_str}"
+ fi
+fi
diff --git a/cdist/conf/type/__hwclock/man.rst b/cdist/conf/type/__hwclock/man.rst
new file mode 100644
index 00000000..65eb648f
--- /dev/null
+++ b/cdist/conf/type/__hwclock/man.rst
@@ -0,0 +1,63 @@
+cdist-type__hwclock(7)
+======================
+
+NAME
+----
+cdist-type__hwclock - Manage the hardware real time clock.
+
+
+DESCRIPTION
+-----------
+This type can be used to control how the hardware clock is used by the operating
+system.
+
+
+REQUIRED PARAMETERS
+-------------------
+mode
+ What mode the hardware clock is in.
+
+ Acceptable values:
+
+ localtime
+ The hardware clock is set to local time (common for systems also running
+ Windows.)
+ UTC
+ The hardware clock is set to UTC (common on UNIX systems.)
+
+
+OPTIONAL PARAMETERS
+-------------------
+None.
+
+
+BOOLEAN PARAMETERS
+------------------
+None.
+
+
+EXAMPLES
+--------
+
+.. code-block:: sh
+
+ # Make the operating system treat the time read from the hwclock as UTC.
+ __hwclock --mode UTC
+
+
+SEE ALSO
+--------
+:strong:`hwclock`\ (8)
+
+
+AUTHORS
+-------
+Dennis Camera
+
+
+COPYING
+-------
+Copyright \(C) 2020 Dennis Camera. You can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
diff --git a/cdist/conf/type/__hwclock/manifest b/cdist/conf/type/__hwclock/manifest
new file mode 100755
index 00000000..7d9ab88f
--- /dev/null
+++ b/cdist/conf/type/__hwclock/manifest
@@ -0,0 +1,222 @@
+#!/bin/sh -e
+#
+# 2020 Dennis Camera (dennis.camera@ssrq-sds-fds.ch)
+#
+# This file is part of cdist.
+#
+# cdist is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# cdist is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with cdist. If not, see .
+#
+
+# TODO: Consider supporting BADYEAR
+
+os=$(cat "${__global:?}/explorer/os")
+mode=$(cat "${__object:?}/parameter/mode")
+
+has_systemd_timedatectl=$(test -s "${__object:?}/explorer/timedatectl_localrtc" && echo true || echo false)
+
+
+case ${mode}
+in
+ (localtime)
+ local_clock=true
+ ;;
+ (UTC|utc)
+ local_clock=false
+ ;;
+ (*)
+ printf 'Invalid value for --mode: %s\n' "${mode}" >&2
+ printf 'Acceptable values are: UTC, localtime.\n' >&2
+ exit 1
+esac
+
+
+case ${os}
+in
+ (alpine|gentoo)
+ if ! $has_systemd_timedatectl
+ then
+ # NOTE: Gentoo also supports systemd, in which case /etc/conf.d is
+ # not used. So we check for systemd presence here and only
+ # update /etc/conf.d if systemd is not installed.
+ # https://wiki.gentoo.org/wiki/System_time#Hardware_clock
+
+ export CDIST_ORDER_DEPENDENCY=true
+ __file /etc/conf.d/hwclock --state present \
+ --owner root --group root --mode 0644
+ __key_value /etc/conf.d/hwclock:clock \
+ --file /etc/conf.d/hwclock \
+ --key clock \
+ --delimiter '=' --exact_delimiter \
+ --value "\"$($local_clock && echo local || echo UTC)\""
+ unset CDIST_ORDER_DEPENDENCY
+ fi
+ ;;
+ (centos|fedora|redhat|scientific)
+ os_version=$(cat "${__global:?}/explorer/os_version")
+ os_major=$(expr "${os_version}" : '.* release \([0-9]*\)')
+ case ${os}
+ in
+ (centos|scientific)
+ update_sysconfig=$(test "${os_major}" -lt 6 && echo true || echo false)
+ ;;
+ (fedora)
+ update_sysconfig=$(test "${os_major}" -lt 10 && echo true || echo false)
+ ;;
+ (redhat|*)
+ case ${os_version}
+ in
+ ('Red Hat Enterprise Linux'*)
+ update_sysconfig=$(test "${os_major}" -lt 6 && echo true || echo false)
+ ;;
+ ('Red Hat Linux'*)
+ update_sysconfig=true
+ ;;
+ (*)
+ printf 'Could not determine Red Hat distribution.\n' >&2
+ printf "Please contribute an implementation for it if you can.\n" >&2
+ exit 1
+ ;;
+ esac
+ ;;
+ esac
+
+ if ${update_sysconfig:?}
+ then
+ export CDIST_ORDER_DEPENDENCY=true
+ __file /etc/sysconfig/clock --state present \
+ --owner root --group root --mode 0644
+ __key_value /etc/sysconfig/clock:UTC \
+ --file /etc/sysconfig/clock \
+ --key UTC \
+ --delimiter '=' --exact_delimiter \
+ --value "$($local_clock && echo false || echo true)"
+ unset CDIST_ORDER_DEPENDENCY
+ fi
+ ;;
+ (debian|devuan|ubuntu)
+ os_major=$(sed 's/[^0-9].*$//' "${__global:?}/explorer/os_version")
+
+ case ${os}
+ in
+ (debian)
+ if test "${os_major}" -ge 7
+ then
+ update_rcS=false
+ elif test "${os_major}" -ge 3
+ then
+ update_rcS=true
+ else
+ # Debian 2.2 should be supportable using rcS.
+ # Debian 2.1 uses the ancient GMT key.
+ # Debian 1.3 does not have rcS.
+ printf "Your operating system (Debian %s) is currently not supported by this type (%s)\n" \
+ "$(cat "${__global:?}/explorer/os_version")" "${__type##*/}" >&2
+ printf "Please contribute an implementation for it if you can.\n" >&2
+ exit 1
+ fi
+ ;;
+ (devuan)
+ update_rcS=false
+ ;;
+ (ubuntu)
+ update_rcS=$(test "${os_major}" -lt 16 && echo true || echo false)
+ ;;
+ esac
+
+ if ${update_rcS}
+ then
+ export CDIST_ORDER_DEPENDENCY=true
+ __file /etc/default/rcS --state present \
+ --owner root --group root --mode 0644
+ __key_value /etc/default/rcS:UTC \
+ --file /etc/default/rcS \
+ --key UTC \
+ --delimiter '=' --exact_delimiter \
+ --value "$($local_clock && echo no || echo yes)"
+ unset CDIST_ORDER_DEPENDENCY
+ fi
+ ;;
+ (freebsd)
+ # cf. adjkerntz(8)
+ __file /etc/wall_cmos_clock \
+ --state "$($local_clock && echo present || echo absent)" \
+ --owner root --group wheel --mode 0444
+ ;;
+ (netbsd)
+ # https://wiki.netbsd.org/guide/boot/#index9h2
+ __key_value /etc/rc.conf:rtclocaltime \
+ --file /etc/rc.conf \
+ --key rtclocaltime \
+ --delimiter '=' --exact_delimiter \
+ --value "$($local_clock && echo YES || echo NO)"
+ ;;
+ (slackware)
+ __file /etc/hardwareclock --owner root --group root --mode 0644 \
+ --source - <<-EOF
+ # /etc/hardwareclock
+ #
+ # Tells how the hardware clock time is stored.
+ # This file is managed by cdist.
+
+ $($local_clock && echo localtime || echo UTC)
+ EOF
+ ;;
+ (suse)
+ if test -s "${__global:?}/explorer/os_release"
+ then
+ # shellcheck source=/dev/null
+ os_version=$(. "${__global:?}/explorer/os_release" && echo "${VERSION}")
+ else
+ os_version=$(sed -n 's/^VERSION\ *=\ *//p' "${__global:?}/explorer/os_version")
+ fi
+ os_major=$(expr "${os_version}" : '\([0-9]\{1,\}\)')
+
+ # TODO: Consider using `yast2 timezone set hwclock' instead
+ if expr "${os_major}" \< 12
+ then
+ # Starting with SuSE 12 (first systemd-based version)
+ # /etc/sysconfig/clock does not contain the HWCLOCK line
+ # anymore.
+ # With SuSE 13, it has been reduced to TIMEZONE configuration.
+ __key_value /etc/sysconfig/clock:HWCLOCK \
+ --file /etc/sysconfig/clock \
+ --delimiter '=' --exact_delimiter \
+ --key HWCLOCK \
+ --value "$($local_clock && echo '"--localtime"' || echo '"-u"')"
+ fi
+ ;;
+ (void)
+ export CDIST_ORDER_DEPENDENCY=true
+ __file /etc/rc.conf \
+ --owner root --group root --mode 0644 \
+ --state present
+ __key_value /etc/rc.conf:HARDWARECLOCK \
+ --file /etc/rc.conf \
+ --delimiter '=' --exact_delimiter \
+ --key HARDWARECLOCK \
+ --value "\"$($local_clock && echo localtime || echo UTC)\""
+ unset CDIST_ORDER_DEPENDENCY
+ ;;
+ (*)
+ if ! $has_systemd_timedatectl
+ then
+ printf "Your operating system (%s) is currently not supported by this type (%s)\n" "$os" "${__type##*/}" >&2
+ printf "Please contribute an implementation for it if you can.\n" >&2
+ exit 1
+ fi
+ ;;
+esac
+
+# NOTE: timedatectl set-local-rtc for systemd is in gencode-remote
+# NOTE: /etc/adjtime is also updated in gencode-remote
diff --git a/cdist/conf/type/__hwclock/parameter/required b/cdist/conf/type/__hwclock/parameter/required
new file mode 100644
index 00000000..17ab372f
--- /dev/null
+++ b/cdist/conf/type/__hwclock/parameter/required
@@ -0,0 +1 @@
+mode
diff --git a/cdist/conf/type/__hwclock/singleton b/cdist/conf/type/__hwclock/singleton
new file mode 100644
index 00000000..e69de29b
diff --git a/cdist/conf/type/__locale/deprecated b/cdist/conf/type/__locale/deprecated
new file mode 100644
index 00000000..5a06b28e
--- /dev/null
+++ b/cdist/conf/type/__locale/deprecated
@@ -0,0 +1 @@
+This type is deprecated. Please use __localedef instead.
diff --git a/cdist/conf/type/__locale/explorer/state b/cdist/conf/type/__locale/explorer/state
new file mode 100755
index 00000000..4494fcbc
--- /dev/null
+++ b/cdist/conf/type/__locale/explorer/state
@@ -0,0 +1,36 @@
+#!/bin/sh -e
+# __locale/explorer/state
+#
+# 2020 Matthias Stecher (matthiasstecher at gmx.de)
+#
+# This file is part of cdist.
+#
+# cdist is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# cdist is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with cdist. If not, see .
+#
+#
+# Check if the locale is already installed on the system.
+# Outputs 'present' or 'absent' depending if the locale exists.
+#
+
+
+# Get user-defined locale
+# locale name is echoed differently than the user propably set it (for UTF-8)
+locale="$(echo "$__object_id" | sed 's/UTF-8/utf8/')"
+
+# Check if the given locale exists on the system
+if localedef --list-archive | grep -qFx "$locale"; then
+ echo present
+else
+ echo absent
+fi
diff --git a/cdist/conf/type/__locale/gencode-remote b/cdist/conf/type/__locale/gencode-remote
index 1feb9884..4639cef8 100755
--- a/cdist/conf/type/__locale/gencode-remote
+++ b/cdist/conf/type/__locale/gencode-remote
@@ -23,6 +23,15 @@
locale="$__object_id"
+state_is=$(cat "$__object/explorer/state")
+state_should=$(cat "$__object/parameter/state")
+
+# short circuit if there is nothing to do
+if [ "$state_is" = "$state_should" ]; then
+ exit 0
+fi
+
+
# Hardcoded, create a pull request with
# branching on $os in case it is at another location
alias=/usr/share/locale/locale.alias
@@ -35,8 +44,6 @@ charmap=$(echo "$locale" | cut -d . -f 2)
# W-T-F!
locale_remove=$(echo "$locale" | sed 's/UTF-8/utf8/')
-state=$(cat "$__object/parameter/state")
-
os=$(cat "$__global/explorer/os")
# Nothing to be done on alpine
@@ -46,7 +53,7 @@ case "$os" in
;;
esac
-case "$state" in
+case "$state_should" in
present)
echo localedef -A "$alias" -f "$charmap" -i "$input" "$locale"
;;
@@ -54,7 +61,7 @@ case "$state" in
echo localedef --delete-from-archive "$locale_remove"
;;
*)
- echo "Unsupported state: $state" >&2
+ echo "Unsupported state: $state_should" >&2
exit 1
;;
esac
diff --git a/cdist/conf/type/__localedef/explorer/state b/cdist/conf/type/__localedef/explorer/state
new file mode 100755
index 00000000..3ba57661
--- /dev/null
+++ b/cdist/conf/type/__localedef/explorer/state
@@ -0,0 +1,100 @@
+#!/bin/sh -e
+#
+# 2020 Dennis Camera (dennis.camera at ssrq-sds-fds.ch)
+#
+# This file is part of cdist.
+#
+# cdist is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# cdist is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with cdist. If not, see .
+#
+# This explorer determines if the locale is defined on the target system.
+# Will print nothing on error.
+#
+# Possible output:
+# present:
+# the main locale (and possibly aliases) is present
+# absent:
+# neither the main locale nor any aliases are present
+# alias-present:
+# the main locale is absent, but at least one of its aliases is present
+#
+
+# Hardcoded, create a pull request in case it is at another location for
+# some other distro. (cf. gencode-remote)
+aliasfile='/usr/share/locale/locale.alias'
+
+command -v locale >/dev/null 2>&1 || exit 0
+
+locales=$(locale -a)
+
+parse_locale() {
+ # This function will split locales into their parts. Locale strings are
+ # usually of the form: [language[_territory][.codeset][@modifier]]
+ # For simplicity, language and territory are not separated by this function.
+ # Old Linux systems were also using "english" or "german" as locale strings.
+ # Usage: parse_locale locale_str lang_var codeset_var modifier_var
+ eval "${2:?}"="$(expr "$1" : '\([^.@]*\)')"
+ eval "${3:?}"="$(expr "$1" : '[^.]*\.\([^@]*\)')"
+ eval "${4:?}"="$(expr "$1" : '.*@\(.*\)$')"
+}
+
+format_locale() {
+ # Usage: format_locale language codeset modifier
+ printf '%s' "$1"
+ test -z "$2" || printf '.%s' "$2"
+ test -z "$3" || printf '@%s' "$3"
+ printf '\n'
+}
+
+gnu_normalize_codeset() {
+ # reimplementation of glibc/locale/programs/localedef.c normalize_codeset()
+ echo "$*" | tr '[:upper:]' '[:lower:]' | tr -cd '[:alnum:]'
+}
+
+locale_available() (
+ echo "${locales}" | grep -qxF "$1" || {
+ # glibc uses "normalized" locale names in archives.
+ # If a locale is stored in an archive, the normalized name will be
+ # printed by locale, so that needs to be checked, too.
+ localename=$(
+ parse_locale "$1" _lang _codeset _modifier \
+ && format_locale "${_lang:?}" "$(gnu_normalize_codeset "${_codeset?}")" \
+ "${_modifier?}")
+ echo "${locales}" | grep -qxF "${localename}"
+ }
+)
+
+if locale_available "${__object_id:?}"
+then
+ echo present
+else
+ # NOTE: locale.alias can be symlinked.
+ if test -e "${aliasfile}"
+ then
+ # Check if one of the aliases of the locale is defined
+ baselocale=$(
+ parse_locale "${__object_id:?}" _lang _codeset _modifiers \
+ && format_locale "${_lang}" "${_codeset}")
+ while read -r _alias _localename
+ do
+ if test "${_localename}" = "${baselocale}" \
+ && echo "${locales}" | grep -qxF "${_alias}"
+ then
+ echo alias-present
+ exit 0
+ fi
+ done <"${aliasfile}"
+ fi
+
+ echo absent
+fi
diff --git a/cdist/conf/type/__localedef/files/lib/glibc.sh b/cdist/conf/type/__localedef/files/lib/glibc.sh
new file mode 100644
index 00000000..6ace80d4
--- /dev/null
+++ b/cdist/conf/type/__localedef/files/lib/glibc.sh
@@ -0,0 +1,5 @@
+# -*- mode: sh; indent-tabs-mode: t -*-
+
+gnu_normalize_codeset() {
+ echo "$*" | tr -cd '[:alnum:]' | tr '[:upper:]' '[:lower:]'
+}
diff --git a/cdist/conf/type/__localedef/files/lib/locale.sh b/cdist/conf/type/__localedef/files/lib/locale.sh
new file mode 100644
index 00000000..b5e61374
--- /dev/null
+++ b/cdist/conf/type/__localedef/files/lib/locale.sh
@@ -0,0 +1,20 @@
+# -*- mode: sh; indent-tabs-mode:t -*-
+
+parse_locale() {
+ # This function will split locales into their parts. Locale strings are
+ # usually of the form: [language[_territory][.codeset][@modifier]]
+ # For simplicity, language and territory are not separated by this function.
+ # Old Linux systems were also using "english" or "german" as locale strings.
+ # Usage: parse_locale locale_str lang_var codeset_var modifier_var
+ eval "${2:?}"="$(expr "$1" : '\([^.@]*\)')"
+ eval "${3:?}"="$(expr "$1" : '[^.]*\.\([^@]*\)')"
+ eval "${4:?}"="$(expr "$1" : '.*@\(.*\)$')"
+}
+
+format_locale() {
+ # Usage: format_locale language codeset modifier
+ printf '%s' "$1"
+ test -z "$2" || printf '.%s' "$2"
+ test -z "$3" || printf '@%s' "$3"
+ printf '\n'
+}
diff --git a/cdist/conf/type/__localedef/gencode-remote b/cdist/conf/type/__localedef/gencode-remote
new file mode 100755
index 00000000..4538151f
--- /dev/null
+++ b/cdist/conf/type/__localedef/gencode-remote
@@ -0,0 +1,136 @@
+#!/bin/sh -e
+#
+# 2013-2019 Nico Schottelius (nico-cdist at schottelius.org)
+# 2020 Dennis Camera (dennis.camera at ssrq-sds-fds.ch)
+#
+# This file is part of cdist.
+#
+# cdist is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# cdist is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with cdist. If not, see .
+#
+# Manage system locales using localedef(1).
+#
+
+# shellcheck source=cdist/conf/type/__localedef/files/lib/locale.sh
+. "${__type:?}/files/lib/locale.sh"
+# shellcheck source=cdist/conf/type/__localedef/files/lib/glibc.sh
+. "${__type:?}/files/lib/glibc.sh"
+
+state_is=$(cat "${__object:?}/explorer/state")
+state_should=$(cat "${__object:?}/parameter/state")
+
+test "${state_should}" = 'present' -o "${state_should}" = 'absent' || {
+ printf 'Invalid state: %s\n' "${state_should}" >&2
+ exit 1
+}
+
+# NOTE: If state explorer fails (e.g. locale(1) missing), the following check
+# will always fail and let definition/removal run.
+if test "${state_is}" = "${state_should}"
+then
+ exit 0
+fi
+
+locale=${__object_id:?}
+os=$(cat "${__global:?}/explorer/os")
+
+if expr "${locale}" : '.*/' >/dev/null
+then
+ printf 'Paths as locales are not supported.\n' >&2
+ printf '__object_id is: %s\n' "${locale}" >&2
+ exit 1
+fi
+
+: "${lang=}" "${codeset=}" "${modifier=}" # declare variables for shellcheck
+parse_locale "${locale}" lang codeset modifier
+
+
+case ${os}
+in
+ (alpine|openwrt)
+ printf '%s does not support locales.\n' "${os}" >&2
+ exit 1
+ ;;
+ (archlinux|debian|devuan|ubuntu|suse|centos|fedora|redhat|scientific)
+ # FIXME: The code below only works for glibc-based installations.
+
+ # NOTE: Hardcoded, create a pull request in case it is at another
+ # location for some opther distro.
+ # NOTE: locale.alias can be symlinked (e.g. Debian)
+ aliasfile='/usr/share/locale/locale.alias'
+
+ case ${state_should}
+ in
+ (present)
+ input=$(format_locale "${lang}" '' "${modifier}")
+ cat <<-EOF
+ set --
+ if test -e '${aliasfile}'
+ then
+ set -- -A '${aliasfile}'
+ fi
+
+ localedef -i '${input}' -f '${codeset}' "\$@" '${locale}'
+ EOF
+ ;;
+ (absent)
+ main_localename=$(format_locale "${lang}" "$(gnu_normalize_codeset "${codeset}")" "${modifier}")
+
+ cat <<-EOF
+ while read -r _alias _localename
+ do
+ if test "\${_localename}" = '$(format_locale "${lang}" "${codeset}")'
+ then
+ localedef --delete-from-archive "\${_alias}"
+ fi
+ done <'${aliasfile}'
+ EOF
+
+ if test "${state_is}" = present
+ then
+ printf "localedef --delete-from-archive '%s'\n" "${main_localename}"
+ fi
+ ;;
+ esac
+ ;;
+ (freebsd)
+ case ${state_should}
+ in
+ (present)
+ if expr "$(grep -oe '^[0-9]*' "${__global:?}/explorer/os_version")" '>=' 11 >/dev/null
+ then
+ # localedef(1) is available with FreeBSD >= 11
+ printf "localedef -i '%s' -f '%s' '%s'\n" "${input}" "${codeset}" "${locale}"
+ else
+ printf 'localedef(1) was added to FreeBSD starting with version 11.\n' >&2
+ printf 'Please upgrade your FreeBSD installation to use %s.\n' "${__type##*/}" >&2
+ exit 1
+ fi
+ ;;
+ (absent)
+ printf "rm -R '/usr/share/locale/%s'\n" "${locale}"
+ ;;
+ esac
+ ;;
+ (netbsd|openbsd)
+ # NetBSD/OpenBSD are missing localedef(1).
+ # We also do not delete defined locales because they can't be recreated.
+ echo "${os} is lacking localedef(1). Locale management unavailable." >&2
+ exit 1
+ ;;
+ (*)
+ echo "Your operating system (${os}) is currently not supported by this type (${__type##*/})." >&2
+ echo "Please contribute an implementation for it if you can." >&2
+ exit 1
+ ;;
+esac
diff --git a/cdist/conf/type/__localedef/man.rst b/cdist/conf/type/__localedef/man.rst
new file mode 100644
index 00000000..454ce9d1
--- /dev/null
+++ b/cdist/conf/type/__localedef/man.rst
@@ -0,0 +1,60 @@
+cdist-type__localedef(7)
+========================
+
+NAME
+----
+cdist-type__localedef - Define and remove system locales
+
+
+DESCRIPTION
+-----------
+This cdist type allows you to define locales on the system using
+:strong:`localedef`\ (1) or remove them.
+On systems that don't support definition of new locales, the type will raise an
+error.
+
+**NB:** This type respects the glibc ``locale.alias`` file,
+i.e. it defines alias locales or deletes aliases of a locale when it is removed.
+It is not possible, however, to use alias names to define locales or only remove
+certain aliases of a locale.
+
+
+OPTIONAL PARAMETERS
+-------------------
+state
+ ``present`` or ``absent``. Defaults to ``present``.
+
+
+EXAMPLES
+--------
+
+.. code-block:: sh
+
+ # Add locale de_CH.UTF-8
+ __localedef de_CH.UTF-8
+
+ # Same as above, but more explicit
+ __localedef de_CH.UTF-8 --state present
+
+ # Remove colourful British English
+ __localedef en_GB.UTF-8 --state absent
+
+
+SEE ALSO
+--------
+:strong:`locale`\ (1),
+:strong:`localedef`\ (1),
+:strong:`cdist-type__locale_system`\ (7)
+
+
+AUTHORS
+-------
+| Dennis Camera
+| Nico Schottelius
+
+
+COPYING
+-------
+Copyright \(C) 2013-2019 Nico Schottelius, 2020 Dennis Camera. Free use of this
+software is granted under the terms of the GNU General Public License version 3
+or later (GPLv3+).
diff --git a/cdist/conf/type/__localedef/manifest b/cdist/conf/type/__localedef/manifest
new file mode 100755
index 00000000..3ab3ad8c
--- /dev/null
+++ b/cdist/conf/type/__localedef/manifest
@@ -0,0 +1,30 @@
+#!/bin/sh -e
+#
+# 2013-2019 Nico Schottelius (nico-cdist at schottelius.org)
+# 2015 David Hürlimann (david at ungleich.ch)
+# 2020 Dennis Camera (dennis.camera at ssrq-sds-fds.ch)
+#
+# This file is part of cdist.
+#
+# cdist is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# cdist is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with cdist. If not, see .
+#
+# Install required packages.
+#
+
+case $(cat "${__global:?}/explorer/os")
+in
+ (debian|devuan)
+ __package_apt locales --state present
+ ;;
+esac
diff --git a/cdist/conf/type/__localedef/parameter/default/state b/cdist/conf/type/__localedef/parameter/default/state
new file mode 100644
index 00000000..e7f6134f
--- /dev/null
+++ b/cdist/conf/type/__localedef/parameter/default/state
@@ -0,0 +1 @@
+present
diff --git a/cdist/conf/type/__localedef/parameter/optional b/cdist/conf/type/__localedef/parameter/optional
new file mode 100644
index 00000000..ff72b5c7
--- /dev/null
+++ b/cdist/conf/type/__localedef/parameter/optional
@@ -0,0 +1 @@
+state
diff --git a/cdist/conf/type/__sshd_config/explorer/state b/cdist/conf/type/__sshd_config/explorer/state
new file mode 100644
index 00000000..75c68b8a
--- /dev/null
+++ b/cdist/conf/type/__sshd_config/explorer/state
@@ -0,0 +1,121 @@
+#!/bin/sh -e
+#
+# 2020 Dennis Camera (dennis.camera at ssrq-sds-fds.ch)
+#
+# This file is part of cdist.
+#
+# cdist is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# cdist is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with cdist. If not, see .
+#
+# Determines the current state of the config option.
+# Possible output:
+# - present: "should" option present in config file
+# - default: the "should" option is the default -> don’t know if present
+# - absent: no such option present in config file
+#
+
+joinlines() { sed -n -e H -e "\${x;s/^\\n//;s/\\n/${1:?}/g;p;}"; }
+trlower() { tr '[:upper:]' '[:lower:]'; }
+tolower() { printf '%s' "$*" | trlower; }
+
+default_value() {
+ sshd -T -f /dev/null -C "$(make_conn_spec)" \
+ | sed -n -e 's/^'"$(tolower "${1:?}")"'[[:blank:]]\{1,\}//p'
+}
+
+make_conn_spec() {
+ if test -s "${__object:?}/parameter/match"
+ then
+ _match_file="${__object:?}/parameter/match"
+ else
+ _match_file='/dev/null'
+ fi
+
+ for _kw in \
+ addr=Address \
+ user=User \
+ host=Host \
+ laddr=LocalAddress \
+ lport=LocalPort \
+ rdomain=RDomain
+ do
+ _specname=${_kw%%=*}
+ _confname=$(tolower "${_kw#*=}")
+ while read -r _k _v
+ do
+ if test "$(tolower "${_k}")" = "${_confname}"
+ then
+ printf '%s=%s\n' "${_specname}" "${_v}"
+ continue 2
+ fi
+ done <"${_match_file}"
+
+ # NOTE: Print test spec even for empty keys to suppress errors like:
+ # 'Match User' in configuration but 'user' not in connection test specification.
+ # except lport:
+ # Invalid port '' in test mode specification lport=
+ test "${_specname}" = 'lport' || printf '%s=\n' "${_specname}"
+ done \
+ | joinlines ','
+ unset _match_file
+}
+
+sshd_config_file=$(cat "${__object:?}/parameter/file")
+state_should=$(cat "${__object:?}/parameter/state")
+
+if test -s "${__object:?}/parameter/option"
+then
+ option_name=$(cat "${__object:?}/parameter/option")
+else
+ option_name=${__object_id:?}
+fi
+
+value_should=$(cat "${__object:?}/parameter/value" 2>/dev/null) \
+|| test "${state_should}" = absent || exit 0 # param optional if --state absent
+
+command -v sshd >/dev/null 2>&1 || {
+ echo 'Cannot find sshd.' >&2
+ exit 1
+}
+
+test -e "${sshd_config_file}" || {
+ echo 'absent'
+ exit 0
+}
+
+value_is=$(
+ sshd -T -f "${sshd_config_file}" -C "$(make_conn_spec)" \
+ | sed -n -e 's/^'"$(tolower "${option_name}")"'[[:blank:]]\{1,\}//p')
+
+if printf '%s\n' "${value_is}" | {
+ if test -n "${value_should}"
+ then
+ grep -q -x -F "${value_should}"
+ else
+ # if no value provided, assume "any" value
+ grep -q -e .
+ fi
+ }
+then
+ if default_value "${option_name}" | grep -q -x -F "${value_is}"
+ then
+ # Might produce false positives for default values.
+ # TODO: Manual checking should be done, but for simplicity, this case is
+ # currently ignored here.
+ echo default
+ else
+ echo present
+ fi
+else
+ echo absent
+fi
diff --git a/cdist/conf/type/__sshd_config/files/update_sshd_config.awk b/cdist/conf/type/__sshd_config/files/update_sshd_config.awk
new file mode 100644
index 00000000..d0bc2b4b
--- /dev/null
+++ b/cdist/conf/type/__sshd_config/files/update_sshd_config.awk
@@ -0,0 +1,293 @@
+# -*- mode: awk; indent-tabs-mode: t -*-
+
+function usage() {
+ print_err("Usage: awk -f update_sshd_config.awk -- -o set|unset [-m 'User git'] -l 'X11Forwarding no' /etc/ssh/sshd_config")
+}
+
+function print_err(s) { print s | "cat >&2" }
+
+function alength(a, i) {
+ for (i = 0; (i + 1) in a; ++i);
+ return i
+}
+
+function join(sep, a, i, s) {
+ for (i = i ? i : 1; i in a; i++)
+ s = s sep a[i]
+ return substr(s, 2)
+}
+
+function getopt(opts, argv, target, files, i, c, lv, idx, nf) {
+ # trivial getopt(3) implementation; only basic functionality
+ if (argv[1] == "--") i++
+ for (i += 1; i in argv; i++) {
+ if (lv) { target[c] = argv[i]; lv = 0; continue }
+ if (argv[i] ~ /^-/) {
+ c = substr(argv[i], 2, 1)
+ idx = index(opts, c)
+ if (!idx) {
+ print_err(sprintf("invalid option -%c\n", c))
+ continue
+ }
+ if (substr(opts, idx + 1, 1) == ":") {
+ # option takes argument
+ if (length(argv[i]) > 2)
+ target[c] = substr(argv[i], 3)
+ else
+ lv = 1
+ } else {
+ target[c] = 1
+ }
+ } else
+ files[++nf] = argv[i]
+ }
+}
+
+# tokenise configuration line
+# this function mimics the counterpart in OpenSSH (misc.c)
+# but it returns two (next token SUBSEP rest) because I didn’t want to have to
+# simulate any pointer magic.
+function strdelim_internal(s, split_equals, old) {
+ if (!s)
+ return ""
+
+ old = s
+
+ if (!match(s, WHITESPACE "|" QUOTE "" (split_equals ? "|" EQUALS : "")))
+ return s
+
+ s = substr(s, RSTART)
+ old = substr(old, 1, RSTART - 1)
+
+ if (s ~ "^" QUOTE) {
+ old = substr(old, 2)
+
+ # Find matching quote
+ if (match(s, QUOTE)) {
+ old = substr(old, 1, RSTART)
+ # s = substr()
+ if (match(s, "^" WHITESPACE "*"))
+ s = substr(s, RLENGTH)
+ return old
+ } else {
+ # no matching quote
+ return ""
+ }
+ }
+
+ if (match(s, "^" WHITESPACE "+")) {
+ sub("^" WHITESPACE "+", "", s)
+ if (split_equals)
+ sub(EQUALS WHITESPACE "*", "", s)
+ } else if (s ~ "^" EQUALS) {
+ s = substr(s, 2)
+ }
+
+ return old SUBSEP s
+}
+function strdelim(s) { return strdelim_internal(s, 1) }
+function strdelimw(s) { return strdelim_internal(s, 0) }
+
+function singleton_option(opt) {
+ return tolower(opt) !~ /^(acceptenv|allowgroups|allowusers|authenticationmethods|authorizedkeysfile|denygroups|denyusers|hostcertificate|hostkey|listenaddress|logverbose|permitlisten|permitopen|port|setenv|subsystem)$/
+}
+
+function print_update() {
+ if (mode) {
+ if (match_only) printf "\t"
+ printf "%s\n", line_should
+ updated = 1
+ }
+}
+
+BEGIN {
+ FS = "\n" # disable field splitting
+
+ WHITESPACE = "[ \t]" # servconf.c, misc.c:strdelim_internal (without line breaks, cf. bugs)
+ QUOTE = "[\"]" # misc.c:strdelim_internal
+ EQUALS = "[=]"
+
+ split("", opts)
+ split("", files)
+ getopt("ho:l:m:", ARGV, opts, files)
+
+ if (opts["h"]) { usage(); exit (e="0") }
+
+ line_should = opts["l"]
+ match_only = opts["m"]
+ num_files = alength(files)
+
+ if (num_files != 1 || !opts["o"] || !line_should) {
+ usage()
+ exit (e=126)
+ }
+
+ if (opts["o"] == "set") {
+ mode = 1
+ } else if (opts["o"] == "unset") {
+ mode = 0
+ } else {
+ print_err(sprintf("invalid mode %s\n", mode))
+ exit (e=1)
+ }
+
+ if (mode) {
+ # loop over sshd_config twice!
+ ARGV[2] = ARGV[1] = files[1]
+ ARGC = 3
+ } else {
+ # only loop once
+ ARGV[1] = files[1]
+ ARGC = 2
+ }
+
+ split(strdelim(line_should), should, SUBSEP)
+ option_should = tolower(should[1])
+ value_should = should[2]
+}
+
+{
+ line = $0
+
+ # Strip trailing whitespace. Allow \f (form feed) at EOL only
+ sub("(" WHITESPACE "|\f)*$", "", line)
+
+ # Strip leading whitespace
+ sub("^" WHITESPACE "*", "", line)
+
+ if (match(line, "^#" WHITESPACE "*")) {
+ prefix = substr(line, RSTART, RLENGTH)
+ line = substr(line, RSTART + RLENGTH)
+ } else {
+ prefix = ""
+ }
+
+ line_type = "invalid"
+ option_is = value_is = ""
+
+ if (line) {
+ split(strdelim(line), toks, SUBSEP)
+
+ if (tolower(toks[1]) == "match") {
+ MATCH = (prefix ~ /^#/ ? "#" : "") join(" ", toks, 2)
+ line_type = "match"
+ } else if (toks[1] ~ /^[A-Za-z][A-Za-z0-9]+$/) {
+ # This could be an option line
+ line_type = "option"
+ option_is = tolower(toks[1])
+ value_is = toks[2]
+ }
+ } else {
+ line_type = "empty"
+ }
+}
+
+# mode: unset
+
+!mode {
+ # delete matching config
+ if (prefix !~ /^#/)
+ if (MATCH == match_only && option_is == option_should)
+ if (!value_should || value_should == value_is)
+ next
+
+ print
+ next
+}
+
+
+# mode: set
+
+mode && NR == FNR {
+ if (line_type == "option") {
+ if (MATCH !~ /^#/) {
+ if (prefix ~ /^#/) {
+ # comment line
+ last_occ[MATCH, "#" option_is] = FNR
+ } else {
+ # option line
+ last_occ[MATCH, option_is] = FNR
+ }
+ last_occ[MATCH] = FNR
+ }
+ } else if (line_type == "invalid" && !prefix) {
+ # INVALID LINE
+ print_err(sprintf("%s: syntax error on line %u\n", ARGV[0], FNR))
+ }
+
+ next
+}
+
+# before second pass prepare hashes containing location information to be used
+# in the second pass.
+mode && NR > FNR && FNR == 1 {
+ # First we drop the locations of commented-out options if a non-commented
+ # option is available. If a non-commented option is available, we will
+ # append new config options there to have them all at one place.
+ for (k in last_occ) {
+ if (k ~ /^#/) {
+ # delete entries of commented out match blocks
+ delete last_occ[k]
+ continue
+ }
+
+ split(k, parts, SUBSEP)
+
+ if (parts[2] ~ /^#/ && ((parts[1], substr(parts[2], 2)) in last_occ))
+ delete last_occ[k]
+ }
+
+ # Reverse the option => line mapping. The line_map allows for easier lookups
+ # in the second pass.
+ # We only keep options, not top-level keywords, because we can only have
+ # one entry per line and there are conflicts with last lines of "sections".
+ for (k in last_occ) {
+ if (!index(k, SUBSEP)) continue
+ line_map[last_occ[k]] = k
+ }
+}
+
+# Second pass
+mode && line_map[FNR] == match_only SUBSEP option_should && !updated {
+ split(line_map[FNR], parts, SUBSEP)
+
+ # If option allows multiple values, print current value
+ if (!singleton_option(parts[2])) {
+ if (value_should != value_is)
+ print
+ }
+
+ print_update()
+
+ next
+}
+
+mode { print }
+
+# Is a comment option
+mode && line_map[FNR] == match_only SUBSEP "#" option_should && !updated {
+ print_update()
+}
+
+# Last line of the should match section
+mode && last_occ[match_only] == FNR && !updated {
+ # NOTE: Inserting empty lines is only cosmetic. It is only done if
+ # different options are next to each other and not in a match block
+ # (match blocks are usually not in the default config and thus don’t
+ # contain commented blocks.)
+ if (line && option_is != option_should && !MATCH)
+ print ""
+ print_update()
+}
+
+END {
+ if (e) exit e
+
+ if (mode && !updated) {
+ if (match_only && MATCH != match_only) {
+ printf "\nMatch %s\n", match_only
+ }
+
+ print_update()
+ }
+}
diff --git a/cdist/conf/type/__sshd_config/gencode-remote b/cdist/conf/type/__sshd_config/gencode-remote
new file mode 100755
index 00000000..0b44dfa7
--- /dev/null
+++ b/cdist/conf/type/__sshd_config/gencode-remote
@@ -0,0 +1,97 @@
+#!/bin/sh -e
+#
+# 2020 Dennis Camera (dennis.camera at ssrq-sds-fds.ch)
+#
+# This file is part of cdist.
+#
+# cdist is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# cdist is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with cdist. If not, see .
+#
+
+joinlines() { sed -n -e H -e "\${x;s/^\\n//;s/\\n/${1:?}/g;p;}"; }
+
+state_is=$(cat "${__object:?}/explorer/state")
+state_should=$(cat "${__object:?}/parameter/state")
+
+if test "${state_is}" = "${state_should}" -o "${state_is}" = 'default'
+then
+ # nothing to do (if the value is the default, ignore its state)
+ exit 0
+fi
+
+case ${state_should}
+in
+ (present)
+ mode='set'
+ ;;
+ (absent)
+ mode='unset'
+ ;;
+ (*)
+ printf 'Invalid --state: %s\n' "${state_should}" >&2
+ exit 1
+ ;;
+esac
+
+sshd_config_file=$(cat "${__object:?}/parameter/file")
+
+quote() { printf "'%s'" "$(printf '%s' "$*" | sed -e "s/'/'\\\\''/g")"; }
+drop_awk_comments() { quote "$(sed '/^[[:blank:]]*#.*$/d;/^$/d' "$@")"; }
+
+# Ensure the sshd_config file is there
+cat <$(quote "${sshd_config_file}")
+ chown 0:0 $(quote "${sshd_config_file}")
+ chmod 0644 $(quote "${sshd_config_file}")
+}
+
+EOF
+
+match_only=
+if test -s "${__object:?}/parameter/match"
+then
+ match_only=$(joinlines ' ' <"${__object:?}/parameter/match")
+fi
+
+if test -s "${__object:?}/parameter/option"
+then
+ option_line=$(cat "${__object:?}/parameter/option")
+else
+ option_line=${__object_id:?}
+fi
+
+if test -s "${__object:?}/parameter/value"
+then
+ option_line="${option_line} $(cat "${__object:?}/parameter/value")"
+fi
+
+# Send message on config update
+printf '%s%s %s\n' "${mode}" "${match_only:+ [${match_only}]}" \
+ "${option_line}" >>"${__messages_out:?}"
+
+# Update sshd_config (remote code)
+cat <$(quote "${sshd_config_file}.tmp") \\
+|| exit
+
+cmp -s $(quote "${sshd_config_file}") $(quote "${sshd_config_file}.tmp") || {
+ sshd -t -f $(quote "${sshd_config_file}.tmp") \\
+ && cat $(quote "${sshd_config_file}.tmp") >$(quote "${sshd_config_file}")
+}
+rm -f $(quote "${sshd_config_file}.tmp")
+EOF
diff --git a/cdist/conf/type/__sshd_config/man.rst b/cdist/conf/type/__sshd_config/man.rst
new file mode 100644
index 00000000..8b0069ac
--- /dev/null
+++ b/cdist/conf/type/__sshd_config/man.rst
@@ -0,0 +1,94 @@
+cdist-type__sshd_config(7)
+==========================
+
+NAME
+----
+cdist-type__sshd_config - Manage options in sshd_config
+
+
+DESCRIPTION
+-----------
+This space intentionally left blank.
+
+
+REQUIRED PARAMETERS
+-------------------
+None.
+
+
+OPTIONAL PARAMETERS
+-------------------
+file
+ The path to the sshd_config file to edit.
+ Defaults to ``/etc/ssh/sshd_config``.
+match
+ Restrict this option to apply only for certain connections.
+ Allowed values are what would be allowed to be written after a ``Match``
+ keyword in ``sshd_config``, e.g. ``--match 'User anoncvs'``.
+
+ Can be used multiple times. All of the values are ANDed together.
+option
+ The name of the option to manipulate. Defaults to ``__object_id``.
+state
+ Can be:
+
+ - ``present``: ensure a matching config line is present (or the default
+ value).
+ - ``absent``: ensure no matching config line is present.
+value
+ The option's value to be assigned to the option (if ``--state present``) or
+ removed (if ``--state absent``).
+
+ This option is required if ``--state present``. If not specified and
+ ``--state absent``, all values for the given option are removed.
+
+
+BOOLEAN PARAMETERS
+------------------
+None.
+
+
+EXAMPLES
+--------
+
+.. code-block:: sh
+
+ # Disallow root logins with password
+ __sshd_config PermitRootLogin --value without-password
+
+ # Disallow password-based authentication
+ __sshd_config PasswordAuthentication --value no
+
+ # Accept the EDITOR environment variable
+ __sshd_config AcceptEnv:EDITOR --option AcceptEnv --value EDITOR
+
+ # Force command for connections as git user
+ __sshd_config git@ForceCommand --match 'User git' --option ForceCommand \
+ --value 'cd ~git && exec git-shell ${SSH_ORIGINAL_COMMAND:+-c "${SSH_ORIGINAL_COMMAND}"}'
+
+
+SEE ALSO
+--------
+:strong:`sshd_config`\ (5)
+
+
+BUGS
+----
+- This type assumes a nicely formatted config file,
+ i.e. no config options spanning multiple lines.
+- ``Include`` directives are ignored.
+- Config options are not added/removed to/from the config file if their value is
+ the default value.
+
+
+AUTHORS
+-------
+Dennis Camera
+
+
+COPYING
+-------
+Copyright \(C) 2020 Dennis Camera. You can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
diff --git a/cdist/conf/type/__sshd_config/manifest b/cdist/conf/type/__sshd_config/manifest
new file mode 100755
index 00000000..566bde90
--- /dev/null
+++ b/cdist/conf/type/__sshd_config/manifest
@@ -0,0 +1,48 @@
+#!/bin/sh -e
+#
+# 2020 Dennis Camera (dennis.camera at ssrq-sds-fds.ch)
+#
+# This file is part of cdist.
+#
+# cdist is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# cdist is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with cdist. If not, see .
+#
+
+os=$(cat "${__global:?}/explorer/os")
+
+state_should=$(cat "${__object:?}/parameter/state")
+
+case ${os}
+in
+ (alpine|centos|fedora|redhat|scientific|debian|devuan|ubuntu)
+ if test "${state_should}" != 'absent'
+ then
+ __package openssh-server --state present
+ fi
+ ;;
+ (archlinux|gentoo|slackware|suse)
+ if test "${state_should}" != 'absent'
+ then
+ __package openssh --state present
+ fi
+ ;;
+ (freebsd|netbsd|openbsd)
+ # whitelist
+ ;;
+ (*)
+ printf 'Your operating system (%s) is currently not supported by this type (%s)\n' \
+ "${os}" "${__type##*/}" >&2
+ printf 'Please contribute an implementation for it if you can.\n' >&2
+ exit 1
+ ;;
+esac
diff --git a/cdist/conf/type/__sshd_config/parameter/default/file b/cdist/conf/type/__sshd_config/parameter/default/file
new file mode 100644
index 00000000..d8ea5dfc
--- /dev/null
+++ b/cdist/conf/type/__sshd_config/parameter/default/file
@@ -0,0 +1 @@
+/etc/ssh/sshd_config
diff --git a/cdist/conf/type/__sshd_config/parameter/default/state b/cdist/conf/type/__sshd_config/parameter/default/state
new file mode 100644
index 00000000..e7f6134f
--- /dev/null
+++ b/cdist/conf/type/__sshd_config/parameter/default/state
@@ -0,0 +1 @@
+present
diff --git a/cdist/conf/type/__sshd_config/parameter/optional b/cdist/conf/type/__sshd_config/parameter/optional
new file mode 100644
index 00000000..922ab093
--- /dev/null
+++ b/cdist/conf/type/__sshd_config/parameter/optional
@@ -0,0 +1,4 @@
+file
+option
+state
+value
diff --git a/cdist/conf/type/__sshd_config/parameter/optional_multiple b/cdist/conf/type/__sshd_config/parameter/optional_multiple
new file mode 100644
index 00000000..02b1d1a9
--- /dev/null
+++ b/cdist/conf/type/__sshd_config/parameter/optional_multiple
@@ -0,0 +1 @@
+match
diff --git a/cdist/emulator.py b/cdist/emulator.py
index cf934281..05903041 100644
--- a/cdist/emulator.py
+++ b/cdist/emulator.py
@@ -143,17 +143,18 @@ class Emulator:
level = logging.WARNING
else:
level = logging.WARNING
+ self.log = logging.getLogger(self.target_host[0])
try:
logging.root.setLevel(level)
+ self.log.setLevel(level)
except (ValueError, TypeError):
# if invalid __cdist_log_level value
logging.root.setLevel(logging.WARNING)
+ self.log.setLevel(logging.WARNING)
colored_log = self.env.get('__cdist_colored_log', 'false')
cdist.log.CdistFormatter.USE_COLORS = colored_log == 'true'
- self.log = logging.getLogger(self.target_host[0])
-
def get_args_parser(self):
parser = argparse.ArgumentParser(add_help=False,
argument_default=argparse.SUPPRESS)
diff --git a/cdist/integration.py b/cdist/integration.py
index 03e4167d..17b65f09 100644
--- a/cdist/integration.py
+++ b/cdist/integration.py
@@ -54,13 +54,12 @@ _mydir = os.path.dirname(__file__)
def find_cdist_exec():
"""Search cdist executable starting from local lib directory.
- Detect if ../scripts/cdist (from local lib direcotry) exists and
+ Detect if ../bin/cdist (from local lib directory) exists and
if it is executable. If not then try to find cdist exec path in
os.get_exec_path() entries. If no cdist path is found rasie
cdist.Error.
"""
- cdist_path = os.path.abspath(os.path.join(_mydir, '..', 'scripts',
- 'cdist'))
+ cdist_path = os.path.abspath(os.path.join(_mydir, '..', 'bin', 'cdist'))
if os.access(cdist_path, os.X_OK):
return cdist_path
cdist_path = find_cdist_exec_in_path()
diff --git a/cdist/test/__init__.py b/cdist/test/__init__.py
index faa3686a..16a311e4 100644
--- a/cdist/test/__init__.py
+++ b/cdist/test/__init__.py
@@ -26,7 +26,7 @@ import tempfile
cdist_base_path = os.path.abspath(
os.path.join(os.path.dirname(os.path.realpath(__file__)), "../../"))
-cdist_exec_path = os.path.join(cdist_base_path, "scripts/cdist")
+cdist_exec_path = os.path.join(cdist_base_path, "bin/cdist")
global_fixtures_dir = os.path.abspath(os.path.join(os.path.dirname(__file__),
"fixtures"))
diff --git a/cdist/test/config/__init__.py b/cdist/test/config/__init__.py
index 0ed614b1..c405207c 100644
--- a/cdist/test/config/__init__.py
+++ b/cdist/test/config/__init__.py
@@ -202,7 +202,7 @@ class ConfigRunTestCase(test.CdistTestCase):
host_dir_name=self.hostdir,
# exec_path can not derivated from sys.argv in case of unittest
exec_path=os.path.abspath(os.path.join(
- my_dir, '../../../scripts/cdist')),
+ my_dir, '../../../bin/cdist')),
initial_manifest=os.path.join(fixtures,
'manifest/dryrun_manifest'),
add_conf_dirs=[fixtures])
@@ -219,7 +219,7 @@ class ConfigRunTestCase(test.CdistTestCase):
base_root_path=self.host_base_path,
host_dir_name=self.hostdir,
exec_path=os.path.abspath(os.path.join(
- my_dir, '../../../scripts/cdist')),
+ my_dir, '../../../bin/cdist')),
initial_manifest=os.path.join(
fixtures, 'manifest/init-deps-resolver'),
add_conf_dirs=[fixtures])
diff --git a/docs/changelog b/docs/changelog
index bf93b9fd..4aba96e9 100644
--- a/docs/changelog
+++ b/docs/changelog
@@ -5,6 +5,16 @@ next:
* Core: Add trigger functionality (Nico Schottelius, Darko Poljak)
* Core: Implement core support for python types (Darko Poljak)
+6.9.2: 2020-11-20
+ * Documentation: Fix examples in best practice (Dennis Camera)
+ * Type __locale: Add state explorer (Matthias Stecher)
+ * Core: Reorganize scripts, version generation (Ander Punnar, Dennis Camera)
+ * New type: __hwclock (Dennis Camera)
+ * Type __hostname: Fix guessing SuSE OS version (Dennis Camera)
+ * New type: __sshd_config (Dennis Camera)
+ * New type: __localedef (Dennis Camera)
+ * Type __locale: Deprecate in favor of __localedef (Dennis Camera)
+
6.9.1: 2020-11-08
* Type __file: Fix state pre-exists (Dennis Camera)
* Type __hostname: Add support for OpenWrt (Dennis Camera)
diff --git a/docs/src/cdist-best-practice.rst b/docs/src/cdist-best-practice.rst
index 39ec453e..f7677276 100644
--- a/docs/src/cdist-best-practice.rst
+++ b/docs/src/cdist-best-practice.rst
@@ -200,15 +200,15 @@ of cdist:
.. code-block:: sh
# Singleton type without parameter
- echo __ungleich_munin_server | cdist --initial-manifest - munin.panter.ch
+ echo __ungleich_munin_server | cdist config --initial-manifest - munin.panter.ch
# Singleton type with parameter
echo __ungleich_munin_node --allow 1.2.3.4 | \
- cdist --initial-manifest - rails-19.panter.ch
+ cdist config --initial-manifest - rails-19.panter.ch
# Normal type
echo __file /tmp/stdintest --mode 0644 | \
- cdist --initial-manifest - cdist-dev-01.ungleich.ch
+ cdist config --initial-manifest - cdist-dev-01.ungleich.ch
Other content in cdist repository
diff --git a/docs/src/cdist-install.rst b/docs/src/cdist-install.rst
index 6f4f14d7..18863145 100644
--- a/docs/src/cdist-install.rst
+++ b/docs/src/cdist-install.rst
@@ -49,7 +49,7 @@ create version.py:
.. code-block:: sh
- ./bin/build-helper version
+ ./bin/cdist-build-helper version
Then you install it with:
@@ -70,7 +70,7 @@ Or directly with distutils:
python setup.py install
-Note that `bin/build-helper` script is intended for cdist maintainers.
+Note that `bin/cdist-build-helper` script is intended for cdist maintainers.
Available versions in git
diff --git a/scripts/cdist b/scripts/cdist
deleted file mode 100755
index b1d782ab..00000000
--- a/scripts/cdist
+++ /dev/null
@@ -1,89 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#
-# 2010-2016 Nico Schottelius (nico-cdist at schottelius.org)
-# 2016 Darko Poljak (darko.poljak at gmail.com)
-#
-# This file is part of cdist.
-#
-# cdist is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# cdist is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with cdist. If not, see .
-#
-#
-
-import logging
-import sys
-import cdist
-import cdist.argparse
-import cdist.banner
-import cdist.config
-import cdist.install
-import cdist.shell
-import cdist.inventory
-
-
-def commandline():
- """Parse command line"""
-
- # preos subcommand hack
- if len(sys.argv) > 1 and sys.argv[1] == 'preos':
- return cdist.preos.PreOS.commandline(sys.argv[1:])
- parser, cfg = cdist.argparse.parse_and_configure(sys.argv[1:])
- args = cfg.get_args()
-
- # Work around python 3.3 bug:
- # http://bugs.python.org/issue16308
- # http://bugs.python.org/issue9253
-
- # FIXME: catching AttributeError also hides
- # real problems.. try a different way
-
- # FIXME: we always print main help, not
- # the help of the actual parser being used!
- try:
- getattr(args, "func")
- except AttributeError:
- parser['main'].print_help()
- sys.exit(0)
-
- args.func(args)
-
-
-if __name__ == "__main__":
- if sys.version < cdist.MIN_SUPPORTED_PYTHON_VERSION:
- print('Python >= {} is required on the source host.'.format(
- cdist.MIN_SUPPORTED_PYTHON_VERSIO), file=sys.stderr)
- sys.exit(1)
-
- exit_code = 0
-
- try:
- import re
- import os
-
- if re.match("__", os.path.basename(sys.argv[0])):
- import cdist.emulator
- emulator = cdist.emulator.Emulator(sys.argv)
- emulator.run()
- else:
- commandline()
-
- except KeyboardInterrupt:
- exit_code = 2
-
- except cdist.Error as e:
- log = logging.getLogger("cdist")
- log.error(e)
- exit_code = 1
-
- sys.exit(exit_code)
diff --git a/setup.py b/setup.py
index 7b000041..858c2c17 100644
--- a/setup.py
+++ b/setup.py
@@ -6,7 +6,7 @@ import subprocess
# We have it only if it is a git cloned repo.
-build_helper = os.path.join('bin', 'build-helper')
+build_helper = os.path.join('bin', 'cdist-build-helper')
# Version file path.
version_file = os.path.join('cdist', 'version.py')
# If we have build-helper we could be a git repo.
@@ -56,12 +56,11 @@ setup(
name="cdist",
packages=["cdist", "cdist.core", "cdist.exec", "cdist.util", ],
package_data={'cdist': package_data},
- scripts=["scripts/cdist", "scripts/cdist-dump", "scripts/cdist-new-type"],
+ scripts=["bin/cdist", "bin/cdist-dump", "bin/cdist-new-type"],
version=cdist.version.VERSION,
description="A Usable Configuration Management System",
- author="Nico Schottelius",
- author_email="nico-cdist-pypi@schottelius.org",
- url="https://www.cdi.st/",
+ author="cdist contributors",
+ url="https://cdi.st",
classifiers=[
"Development Status :: 6 - Mature",
"Environment :: Console",