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 1cc17995..a4bc67aa 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,18 +1,23 @@
+---
+image: code.ungleich.ch:5050/ungleich-public/cdist/cdist-ci:latest
+
stages:
- test
-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/Makefile b/Makefile
index f89ac1e7..3712511c 100644
--- a/Makefile
+++ b/Makefile
@@ -81,7 +81,7 @@ version:
}
# Manpages #3: generic part
-man: version $(MANTYPES) $(DOCSREF)
+man: version configskel $(MANTYPES) $(DOCSREF) $(DOCSTYPESREF)
$(SPHINXM)
html: version configskel $(MANTYPES) $(DOCSREF) $(DOCSTYPESREF)
@@ -104,7 +104,7 @@ DOTMANTYPES=$(subst /man.rst,.rst,$(DOTMANTYPEPREFIX))
$(DOTMAN7DSTDIR)/cdist-type%.rst: $(DOTTYPEDIR)/%/man.rst
ln -sf "$^" $@
-dotman: version $(DOTMANTYPES)
+dotman: version configskel $(DOTMANTYPES) $(DOCSREF) $(DOCSTYPESREF)
$(SPHINXM)
################################################################################
diff --git a/README b/README
deleted file mode 100644
index caf2dac8..00000000
--- a/README
+++ /dev/null
@@ -1,7 +0,0 @@
-cdist
------
-
-cdist is a usable configuration management system.
-
-For the web documentation have a look at https://www.cdi.st/
-or at docs/src for reStructuredText manual.
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/README.md b/README.md
new file mode 100644
index 00000000..de6901c7
--- /dev/null
+++ b/README.md
@@ -0,0 +1,31 @@
+# cdist
+
+**cdist** is a usable configuration management system.
+
+It adheres to the [**KISS principle**](https://en.wikipedia.org/wiki/KISS_principle)
+and is being used in small up to enterprise grade environments.
+
+For more information have a look at [**homepage**](https://cdi.st)
+or at **``docs/src``** for manual in **reStructuredText** format.
+
+## Contributing
+
+Merge/Pull requests can be made in both
+[upstream **GitLab**](https://code.ungleich.ch/ungleich-public/cdist/merge_requests)
+(managed by [**ungleich**](https://ungleich.ch))
+and [**GitHub** project](https://github.com/ungleich/cdist/pulls).
+
+Issues can be made and other project management activites happen
+[**only in GitLab**](https://code.ungleich.ch/ungleich-public/cdist)
+(needs [**ungleich** account](https://account.ungleich.ch)).
+
+For community-maintained types there is
+[**cdist-contrib** project](https://code.ungleich.ch/ungleich-public/cdist-contrib).
+
+## Participating
+
+IRC: ``#cdist`` @ freenode
+
+Matrix: ``#cdist:ungleich.ch``
+
+Mattermost: https://chat.ungleich.ch/ungleich/channels/cdist
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 ed41e438..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
@@ -371,7 +371,6 @@ eof
Manual steps post release:
- cdist-web
- send generated mailinglist.tmp mail
- - twitter
eof
;;
@@ -406,7 +405,7 @@ eof
;;
pycodestyle|pep8)
- pycodestyle "${basedir}" "${basedir}/scripts/cdist"
+ pycodestyle "${basedir}" "${basedir}/bin/cdist"
;;
check-pycodestyle)
@@ -461,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)
@@ -491,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 99%
rename from scripts/cdist-dump
rename to bin/cdist-dump
index 83b09eb8..d29e5985 100755
--- a/scripts/cdist-dump
+++ b/bin/cdist-dump
@@ -224,6 +224,7 @@ hor_line()
if [ "${do_global_explorer}" ]
then
print_verbose 2 "Dumping global explorers"
+ # shellcheck disable=SC2086
set -- "$@" ${or} \( \
-path "*/explorer/*" -a \
! -path "*/conf/*" -a \
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 c673b3ba..44366cd0 100644
--- a/cdist/__init__.py
+++ b/cdist/__init__.py
@@ -22,11 +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
@@ -48,6 +64,9 @@ REMOTE_EXEC = "ssh -o User=root"
REMOTE_CMDS_CLEANUP_PATTERN = "ssh -o User=root -O exit -S {}"
+MIN_SUPPORTED_PYTHON_VERSION = '3.5'
+
+
class Error(Exception):
"""Base exception class for this project"""
pass
diff --git a/cdist/argparse.py b/cdist/argparse.py
index 611c484a..88759d7b 100644
--- a/cdist/argparse.py
+++ b/cdist/argparse.py
@@ -5,12 +5,14 @@ import logging
import collections
import functools
import cdist.configuration
+import cdist.log
import cdist.preos
import cdist.info
+import cdist.scan.commandline
# set of beta sub-commands
-BETA_COMMANDS = set(('install', 'inventory', ))
+BETA_COMMANDS = set(('install', 'inventory', 'scan', ))
# set of beta arguments for sub-commands
BETA_ARGS = {
'config': set(('tag', 'all_tagged_hosts', 'use_archiving', )),
@@ -125,6 +127,14 @@ def get_parsers():
'value.'),
action='count', default=None)
+ parser['colored_output'] = argparse.ArgumentParser(add_help=False)
+ parser['colored_output'].add_argument(
+ '--colors', metavar='WHEN',
+ help="Colorize cdist's output based on log level; "
+ "WHEN is 'always', 'never', or 'auto'.",
+ action='store', dest='colored_output', required=False,
+ choices=cdist.configuration.ColoredOutputOption.CHOICES)
+
parser['beta'] = argparse.ArgumentParser(add_help=False)
parser['beta'].add_argument(
'-b', '--beta',
@@ -197,6 +207,13 @@ def get_parsers():
'supported. Without argument CPU count is used by default. '),
action='store', dest='jobs',
const=multiprocessing.cpu_count())
+ parser['config_main'].add_argument(
+ '--log-server',
+ action='store_true',
+ help=('Start a log server for sub processes to use. '
+ 'This is mainly useful when running cdist nested '
+ 'from a code-local script. Log server is alwasy '
+ 'implicitly started for \'install\' command.'))
parser['config_main'].add_argument(
'-n', '--dry-run',
help='Do not execute code.', action='store_true')
@@ -257,8 +274,7 @@ def get_parsers():
'-f', '--file',
help=('Read specified file for a list of additional hosts to '
'operate on or if \'-\' is given, read stdin (one host per '
- 'line). If no host or host file is specified then, by '
- 'default, read hosts from stdin.'),
+ 'line).'),
dest='hostfile', required=False)
parser['config_args'].add_argument(
'-p', '--parallel', nargs='?', metavar='HOST_MAX',
@@ -283,6 +299,7 @@ def get_parsers():
'host', nargs='*', help='Host(s) to operate on.')
parser['config'] = parser['sub'].add_parser(
'config', parents=[parser['loglevel'], parser['beta'],
+ parser['colored_output'],
parser['common'],
parser['config_main'],
parser['inventory_common'],
@@ -301,6 +318,7 @@ def get_parsers():
parser['add-host'] = parser['invsub'].add_parser(
'add-host', parents=[parser['loglevel'], parser['beta'],
+ parser['colored_output'],
parser['common'],
parser['inventory_common']])
parser['add-host'].add_argument(
@@ -308,13 +326,12 @@ def get_parsers():
parser['add-host'].add_argument(
'-f', '--file',
help=('Read additional hosts to add from specified file '
- 'or from stdin if \'-\' (each host on separate line). '
- 'If no host or host file is specified then, by default, '
- 'read from stdin.'),
+ 'or from stdin if \'-\' (each host on separate line). '),
dest='hostfile', required=False)
parser['add-tag'] = parser['invsub'].add_parser(
'add-tag', parents=[parser['loglevel'], parser['beta'],
+ parser['colored_output'],
parser['common'],
parser['inventory_common']])
parser['add-tag'].add_argument(
@@ -323,20 +340,12 @@ def get_parsers():
parser['add-tag'].add_argument(
'-f', '--file',
help=('Read additional hosts to add tags from specified file '
- 'or from stdin if \'-\' (each host on separate line). '
- 'If no host or host file is specified then, by default, '
- 'read from stdin. If no tags/tagfile nor hosts/hostfile'
- ' are specified then tags are read from stdin and are'
- ' added to all hosts.'),
+ 'or from stdin if \'-\' (each host on separate line). '),
dest='hostfile', required=False)
parser['add-tag'].add_argument(
'-T', '--tag-file',
help=('Read additional tags to add from specified file '
- 'or from stdin if \'-\' (each tag on separate line). '
- 'If no tag or tag file is specified then, by default, '
- 'read from stdin. If no tags/tagfile nor hosts/hostfile'
- ' are specified then tags are read from stdin and are'
- ' added to all hosts.'),
+ 'or from stdin if \'-\' (each tag on separate line). '),
dest='tagfile', required=False)
parser['add-tag'].add_argument(
'-t', '--taglist',
@@ -346,6 +355,7 @@ def get_parsers():
parser['del-host'] = parser['invsub'].add_parser(
'del-host', parents=[parser['loglevel'], parser['beta'],
+ parser['colored_output'],
parser['common'],
parser['inventory_common']])
parser['del-host'].add_argument(
@@ -356,13 +366,12 @@ def get_parsers():
parser['del-host'].add_argument(
'-f', '--file',
help=('Read additional hosts to delete from specified file '
- 'or from stdin if \'-\' (each host on separate line). '
- 'If no host or host file is specified then, by default, '
- 'read from stdin.'),
+ 'or from stdin if \'-\' (each host on separate line). '),
dest='hostfile', required=False)
parser['del-tag'] = parser['invsub'].add_parser(
'del-tag', parents=[parser['loglevel'], parser['beta'],
+ parser['colored_output'],
parser['common'],
parser['inventory_common']])
parser['del-tag'].add_argument(
@@ -375,20 +384,13 @@ def get_parsers():
parser['del-tag'].add_argument(
'-f', '--file',
help=('Read additional hosts to delete tags for from specified '
- 'file or from stdin if \'-\' (each host on separate line). '
- 'If no host or host file is specified then, by default, '
- 'read from stdin. If no tags/tagfile nor hosts/hostfile'
- ' are specified then tags are read from stdin and are'
- ' deleted from all hosts.'),
+ 'file or from stdin if \'-\' (each host on separate '
+ 'line). '),
dest='hostfile', required=False)
parser['del-tag'].add_argument(
'-T', '--tag-file',
help=('Read additional tags from specified file '
- 'or from stdin if \'-\' (each tag on separate line). '
- 'If no tag or tag file is specified then, by default, '
- 'read from stdin. If no tags/tagfile nor'
- ' hosts/hostfile are specified then tags are read from'
- ' stdin and are added to all hosts.'),
+ 'or from stdin if \'-\' (each tag on separate line). '),
dest='tagfile', required=False)
parser['del-tag'].add_argument(
'-t', '--taglist',
@@ -398,6 +400,7 @@ def get_parsers():
parser['list'] = parser['invsub'].add_parser(
'list', parents=[parser['loglevel'], parser['beta'],
+ parser['colored_output'],
parser['common'],
parser['inventory_common']])
parser['list'].add_argument(
@@ -430,7 +433,7 @@ def get_parsers():
# Shell
parser['shell'] = parser['sub'].add_parser(
- 'shell', parents=[parser['loglevel']])
+ 'shell', parents=[parser['loglevel'], parser['colored_output']])
parser['shell'].add_argument(
'-s', '--shell',
help=('Select shell to use, defaults to current shell. Used shell'
@@ -468,6 +471,35 @@ def get_parsers():
'pattern', nargs='?', help='Glob pattern.')
parser['info'].set_defaults(func=cdist.info.Info.commandline)
+ # Scan = config + further
+ parser['scan'] = parser['sub'].add_parser('scan', add_help=False,
+ parents=[parser['config']])
+
+ parser['scan'] = parser['sub'].add_parser(
+ 'scan', parents=[parser['loglevel'],
+ parser['beta'],
+ parser['colored_output'],
+ parser['common'],
+ parser['config_main']])
+
+ parser['scan'].add_argument(
+ '-m', '--mode', help='Which modes should run',
+ action='append', default=[],
+ choices=['scan', 'trigger'])
+ parser['scan'].add_argument(
+ '--config',
+ action='store_true',
+ help='Try to configure detected hosts')
+ parser['scan'].add_argument(
+ '-I', '--interfaces',
+ action='append', default=[],
+ help='On which interfaces to scan/trigger')
+ parser['scan'].add_argument(
+ '-d', '--delay',
+ action='store', default=3600,
+ help='How long to wait before reconfiguring after last try')
+ parser['scan'].set_defaults(func=cdist.scan.commandline.commandline)
+
for p in parser:
parser[p].epilog = EPILOG
@@ -478,7 +510,12 @@ def handle_loglevel(args):
if hasattr(args, 'quiet') and args.quiet:
args.verbose = _verbosity_level_off
- logging.root.setLevel(_verbosity_level[args.verbose])
+ logging.getLogger().setLevel(_verbosity_level[args.verbose])
+
+
+def handle_log_colors(args):
+ if cdist.configuration.ColoredOutputOption.translate(args.colored_output):
+ cdist.log.CdistFormatter.USE_COLORS = True
def parse_and_configure(argv, singleton=True):
@@ -492,6 +529,7 @@ def parse_and_configure(argv, singleton=True):
raise cdist.Error(str(e))
# Loglevels are handled globally in here
handle_loglevel(args)
+ handle_log_colors(args)
log = logging.getLogger("cdist")
diff --git a/cdist/conf/explorer/cpu_cores b/cdist/conf/explorer/cpu_cores
index a52bddac..81e5294e 100755
--- a/cdist/conf/explorer/cpu_cores
+++ b/cdist/conf/explorer/cpu_cores
@@ -32,6 +32,11 @@ case "$os" in
sysctl -n hw.ncpuonline
;;
+ "freebsd"|"netbsd")
+ PATH=$(getconf PATH)
+ sysctl -n hw.ncpu
+ ;;
+
*)
if [ -r /proc/cpuinfo ]; then
cores="$(grep "core id" /proc/cpuinfo | sort | uniq | wc -l)"
diff --git a/cdist/conf/explorer/disks b/cdist/conf/explorer/disks
index 24540601..56d62d10 100755
--- a/cdist/conf/explorer/disks
+++ b/cdist/conf/explorer/disks
@@ -30,9 +30,8 @@ case $uname_s in
sysctl -n hw.disknames | grep -Eo '[lsw]d[0-9]+'
;;
NetBSD)
- PATH="${PATH}:/usr/local/sbin:/usr/sbin:/sbin"
- sysctl -n hw.disknames \
- | awk 'BEGIN { RS = " " } /^[lsw]d[0-9]+/'
+ PATH=$(getconf PATH)
+ sysctl -n hw.disknames | awk -v RS=' ' '/^[lsw]d[0-9]+/'
;;
Linux)
# list of major device numbers toexclude:
diff --git a/cdist/conf/explorer/init b/cdist/conf/explorer/init
index 1b921c68..f27c77ef 100755
--- a/cdist/conf/explorer/init
+++ b/cdist/conf/explorer/init
@@ -221,6 +221,7 @@ check_systemstarter() {
check_sysvinit() (
init_path=${1:-/sbin/init}
+ test -x "${init_path}" || return 1
grep -q 'INIT_VERSION=sysvinit-[0-9.]*' "${init_path}" || return 1
# It is quite common to use SysVinit to stack other init systemd
diff --git a/cdist/conf/explorer/machine_type b/cdist/conf/explorer/machine_type
index bb21f69c..1c84f4d7 100755
--- a/cdist/conf/explorer/machine_type
+++ b/cdist/conf/explorer/machine_type
@@ -2,6 +2,7 @@
#
# 2014 Daniel Heule (hda at sfs.biz)
# 2014 Thomas Oettli (otho at sfs.biz)
+# 2020 Evilham (contact at evilham.com)
#
# This file is part of cdist.
#
@@ -18,63 +19,91 @@
# You should have received a copy of the GNU General Public License
# along with cdist. If not, see .
#
-#
-# FIXME: other system types (not linux ...)
+os=$("$__explorer/os")
-if [ -d "/proc/vz" ] && [ ! -d "/proc/bc" ]; then
- echo openvz
- exit
-fi
-
-if [ -e "/proc/1/environ" ] &&
- tr '\000' '\n' < "/proc/1/environ" | grep -Eiq '^container='; then
- echo lxc
- exit
-fi
-
-if [ -r /proc/cpuinfo ]; then
- # this should only exist on virtual guest machines,
- # tested on vmware, xen, kvm
- if grep -q "hypervisor" /proc/cpuinfo; then
- # this file is aviable in xen guest systems
- if [ -r /sys/hypervisor/type ]; then
- if grep -q -i "xen" /sys/hypervisor/type; then
- echo virtual_by_xen
- exit
+vendor_string_to_machine_type() {
+ for vendor in vmware bochs kvm qemu virtualbox bhyve; do
+ if echo "${1}" | grep -q -i "${vendor}"; then
+ if [ "${vendor}" = "bochs" ] || [ "${vendor}" = "qemu" ]; then
+ vendor="kvm"
fi
- else
- if [ -r /sys/class/dmi/id/product_name ]; then
- if grep -q -i 'vmware' /sys/class/dmi/id/product_name; then
- echo "virtual_by_vmware"
- exit
- elif grep -q -i 'bochs' /sys/class/dmi/id/product_name; then
- echo "virtual_by_kvm"
- exit
- elif grep -q -i 'virtualbox' /sys/class/dmi/id/product_name; then
- echo "virtual_by_virtualbox"
- exit
- fi
- fi
-
- if [ -r /sys/class/dmi/id/sys_vendor ]; then
- if grep -q -i 'qemu' /sys/class/dmi/id/sys_vendor; then
- echo "virtual_by_kvm"
- exit
- fi
- fi
-
- if [ -r /sys/class/dmi/id/chassis_vendor ]; then
- if grep -q -i 'qemu' /sys/class/dmi/id/chassis_vendor; then
- echo "virtual_by_kvm"
- exit
- fi
- fi
+ echo "virtual_by_${vendor}"
+ exit
fi
- echo "virtual_by_unknown"
- else
- echo "physical"
- fi
-else
- echo "unknown"
-fi
+ done
+}
+
+case "$os" in
+ "freebsd")
+ # FreeBSD does not have /proc/cpuinfo even when procfs is used.
+ # Instead there is a sysctl kern.vm_guest.
+ # Which is 'none' if physical, else the virtualisation.
+ vm_guest="$(sysctl -n kern.vm_guest 2>/dev/null || true)"
+ if [ -n "${vm_guest}" ]; then
+ if [ "${vm_guest}" = "none" ]; then
+ echo "physical"
+ exit
+ fi
+ echo "virtual_by_${vm_guest}"
+ exit
+ fi
+ ;;
+
+ "openbsd")
+ # OpenBSD can also use the sysctl's: hw.vendor or hw.product.
+ # Note we can be reasonably sure about a machine being virtualised
+ # as long as we can identify the virtualisation technology.
+ # But not so much about it being physical...
+ # Patches are welcome / reach out if you have better ideas.
+ for sysctl in hw.vendor hw.product; do
+ # This exits if we can make a reasonable judgement
+ vendor_string_to_machine_type "$(sysctl -n "${sysctl}")"
+ done
+ ;;
+
+ *)
+ # Defaulting to linux for compatibility with previous cdist behaviour
+
+ if [ -d "/proc/vz" ] && [ ! -d "/proc/bc" ]; then
+ echo openvz
+ exit
+ fi
+
+ if [ -e "/proc/1/environ" ] &&
+ tr '\000' '\n' < "/proc/1/environ" | grep -Eiq '^container='; then
+ echo lxc
+ exit
+ fi
+
+ if [ -r /proc/cpuinfo ]; then
+ # this should only exist on virtual guest machines,
+ # tested on vmware, xen, kvm, bhyve
+ if grep -q "hypervisor" /proc/cpuinfo; then
+ # this file is aviable in xen guest systems
+ if [ -r /sys/hypervisor/type ]; then
+ if grep -q -i "xen" /sys/hypervisor/type; then
+ echo virtual_by_xen
+ exit
+ fi
+ else
+ for vendor_file in /sys/class/dmi/id/product_name \
+ /sys/class/dmi/id/sys_vendor \
+ /sys/class/dmi/id/chasis_vendor; do
+ if [ -r ${vendor_file} ]; then
+ # This exits if we can make a reasonable judgement
+ vendor_string_to_machine_type "$(cat "${vendor_file}")"
+ fi
+ done
+ fi
+ echo "virtual_by_unknown"
+ exit
+ else
+ echo "physical"
+ exit
+ fi
+ fi
+ ;;
+esac
+
+echo "unknown"
diff --git a/cdist/conf/explorer/memory b/cdist/conf/explorer/memory
index 4e3efff8..5ea15ada 100755
--- a/cdist/conf/explorer/memory
+++ b/cdist/conf/explorer/memory
@@ -29,7 +29,8 @@ case "$os" in
echo "$(sysctl -n hw.memsize)/1024" | bc
;;
- "openbsd")
+ *"bsd")
+ PATH=$(getconf PATH)
echo "$(sysctl -n hw.physmem) / 1048576" | bc
;;
diff --git a/cdist/conf/explorer/os b/cdist/conf/explorer/os
index 563fa4cf..46d87f3e 100755
--- a/cdist/conf/explorer/os
+++ b/cdist/conf/explorer/os
@@ -143,6 +143,13 @@ case "$uname_s" in
esac
if [ -f /etc/os-release ]; then
+ # after sles15, suse don't provide an /etc/SuSE-release anymore, but there is almost no difference between sles and opensuse leap, so call it suse
+ # shellcheck disable=SC1091
+ if (. /etc/os-release && echo "${ID_LIKE}" | grep -q '\(^\|\ \)suse\($\|\ \)')
+ then
+ echo suse
+ exit 0
+ fi
# already lowercase, according to:
# https://www.freedesktop.org/software/systemd/man/os-release.html
awk -F= '/^ID=/ { if ($2 ~ /^'"'"'(.*)'"'"'$/ || $2 ~ /^"(.*)"$/) { print substr($2, 2, length($2) - 2) } else { print $2 } }' /etc/os-release
diff --git a/cdist/conf/explorer/os_version b/cdist/conf/explorer/os_version
index 1d54ea60..3b02dedd 100755
--- a/cdist/conf/explorer/os_version
+++ b/cdist/conf/explorer/os_version
@@ -31,7 +31,32 @@ case "$("$__explorer/os")" in
cat /etc/arch-release
;;
debian)
- cat /etc/debian_version
+ debian_version=$(cat /etc/debian_version)
+ case $debian_version
+ in
+ testing/unstable)
+ # previous to Debian 4.0 testing/unstable was used
+ # cf. https://metadata.ftp-master.debian.org/changelogs/main/b/base-files/base-files_11_changelog
+ echo 3.99
+ ;;
+ */sid)
+ # sid versions don't have a number, so we decode by codename:
+ case $(expr "$debian_version" : '\([a-z]\{1,\}\)/')
+ in
+ bullseye) echo 10.99 ;;
+ buster) echo 9.99 ;;
+ stretch) echo 8.99 ;;
+ jessie) echo 7.99 ;;
+ wheezy) echo 6.99 ;;
+ squeeze) echo 5.99 ;;
+ lenny) echo 4.99 ;;
+ *) exit 1
+ esac
+ ;;
+ *)
+ echo "$debian_version"
+ ;;
+ esac
;;
devuan)
cat /etc/devuan_version
@@ -45,6 +70,11 @@ case "$("$__explorer/os")" in
macosx)
sw_vers -productVersion
;;
+ freebsd)
+ # Apparently uname -r is not a reliable way to get the patch level.
+ # See: https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=251743
+ freebsd-version
+ ;;
*bsd|solaris)
uname -r
;;
@@ -73,4 +103,4 @@ case "$("$__explorer/os")" in
alpine)
cat /etc/alpine-release
;;
-esac
\ No newline at end of file
+esac
diff --git a/cdist/conf/type/__acl/explorer/getent b/cdist/conf/type/__acl/explorer/getent
new file mode 100755
index 00000000..7e6c2c30
--- /dev/null
+++ b/cdist/conf/type/__acl/explorer/getent
@@ -0,0 +1,4 @@
+#!/bin/sh -e
+
+getent passwd | awk -F: '{print "user:"$1}'
+getent group | awk -F: '{print "group:"$1}'
diff --git a/cdist/conf/type/__acl/gencode-remote b/cdist/conf/type/__acl/gencode-remote
index e5404a9d..32318e91 100755
--- a/cdist/conf/type/__acl/gencode-remote
+++ b/cdist/conf/type/__acl/gencode-remote
@@ -22,8 +22,8 @@ file_is="$( cat "$__object/explorer/file_is" )"
if [ "$file_is" = 'missing' ] \
&& [ -z "$__cdist_dry_run" ] \
- && \( [ ! -f "$__object/parameter/file" ] \
- || [ ! -f "$__object/parameter/directory" ] \)
+ && [ ! -f "$__object/parameter/file" ] \
+ && [ ! -f "$__object/parameter/directory" ]
then
exit 0
fi
@@ -47,28 +47,26 @@ then
elif [ -f "$__object/parameter/entry" ]
then
acl_should="$( cat "$__object/parameter/entry" )"
-elif [ -f "$__object/parameter/acl" ]
-then
- acl_should="$( cat "$__object/parameter/acl" )"
-elif
- [ -f "$__object/parameter/user" ] \
- || [ -f "$__object/parameter/group" ] \
- || [ -f "$__object/parameter/mask" ] \
- || [ -f "$__object/parameter/other" ]
-then
- acl_should="$( for param in user group mask other
- do
- [ ! -f "$__object/parameter/$param" ] && continue
-
- echo "$param" | grep -Eq 'mask|other' && sep=:: || sep=:
-
- echo "$param$sep$( cat "$__object/parameter/$param" )"
- done )"
else
echo 'no parameters set' >&2
exit 1
fi
+# instead of setfacl's non-helpful message "Option -m: Invalid argument near character X"
+# let's check if target has necessary users and groups, since mistyped or missing
+# users/groups in target is most common reason.
+echo "$acl_should" \
+ | grep -Po '(user|group):[^:]+' \
+ | sort -u \
+ | while read -r l
+ do
+ if ! grep "$l" -Fxq "$__object/explorer/getent"
+ then
+ echo "no $l' in target" | sed "s/:/ '/" >&2
+ exit 1
+ fi
+ done
+
if [ -f "$__object/parameter/default" ]
then
acl_should="$( echo "$acl_should" \
diff --git a/cdist/conf/type/__acl/man.rst b/cdist/conf/type/__acl/man.rst
index 28412871..307be72b 100644
--- a/cdist/conf/type/__acl/man.rst
+++ b/cdist/conf/type/__acl/man.rst
@@ -12,11 +12,14 @@ Fully supported and tested on Linux (ext4 filesystem), partial support for FreeB
See ``setfacl`` and ``acl`` manpages for more details.
+One of ``--entry`` or ``--source`` must be used.
-REQUIRED MULTIPLE PARAMETERS
+
+OPTIONAL MULTIPLE PARAMETERS
----------------------------
entry
Set ACL entry following ``getfacl`` output syntax.
+ Must be used if ``--source`` is not used.
OPTIONAL PARAMETERS
@@ -25,6 +28,7 @@ source
Read ACL entries from stdin or file.
Ordering of entries is not important.
When reading from file, comments and empty lines are ignored.
+ Must be used if ``--entry`` is not used.
file
Create/change file with ``__file`` using ``user:group:mode`` pattern.
@@ -48,12 +52,6 @@ remove
``mask`` and ``other`` entries can't be removed, but only changed.
-DEPRECATED PARAMETERS
----------------------
-Parameters ``acl``, ``user``, ``group``, ``mask`` and ``other`` are deprecated and they
-will be removed in future versions. Please use ``entry`` parameter instead.
-
-
EXAMPLES
--------
diff --git a/cdist/conf/type/__acl/parameter/deprecated/acl b/cdist/conf/type/__acl/parameter/deprecated/acl
deleted file mode 100644
index 94e14159..00000000
--- a/cdist/conf/type/__acl/parameter/deprecated/acl
+++ /dev/null
@@ -1 +0,0 @@
-see manual for details
diff --git a/cdist/conf/type/__acl/parameter/deprecated/group b/cdist/conf/type/__acl/parameter/deprecated/group
deleted file mode 100644
index 94e14159..00000000
--- a/cdist/conf/type/__acl/parameter/deprecated/group
+++ /dev/null
@@ -1 +0,0 @@
-see manual for details
diff --git a/cdist/conf/type/__acl/parameter/deprecated/mask b/cdist/conf/type/__acl/parameter/deprecated/mask
deleted file mode 100644
index 94e14159..00000000
--- a/cdist/conf/type/__acl/parameter/deprecated/mask
+++ /dev/null
@@ -1 +0,0 @@
-see manual for details
diff --git a/cdist/conf/type/__acl/parameter/deprecated/other b/cdist/conf/type/__acl/parameter/deprecated/other
deleted file mode 100644
index 94e14159..00000000
--- a/cdist/conf/type/__acl/parameter/deprecated/other
+++ /dev/null
@@ -1 +0,0 @@
-see manual for details
diff --git a/cdist/conf/type/__acl/parameter/deprecated/user b/cdist/conf/type/__acl/parameter/deprecated/user
deleted file mode 100644
index 94e14159..00000000
--- a/cdist/conf/type/__acl/parameter/deprecated/user
+++ /dev/null
@@ -1 +0,0 @@
-see manual for details
diff --git a/cdist/conf/type/__acl/parameter/optional b/cdist/conf/type/__acl/parameter/optional
index cdcbc0b8..5a0c29a3 100644
--- a/cdist/conf/type/__acl/parameter/optional
+++ b/cdist/conf/type/__acl/parameter/optional
@@ -1,5 +1,3 @@
-mask
-other
source
file
directory
diff --git a/cdist/conf/type/__acl/parameter/optional_multiple b/cdist/conf/type/__acl/parameter/optional_multiple
index c615d507..4c884f03 100644
--- a/cdist/conf/type/__acl/parameter/optional_multiple
+++ b/cdist/conf/type/__acl/parameter/optional_multiple
@@ -1,4 +1 @@
entry
-acl
-user
-group
diff --git a/cdist/conf/type/__apt_backports/man.rst b/cdist/conf/type/__apt_backports/man.rst
new file mode 100644
index 00000000..7036fb84
--- /dev/null
+++ b/cdist/conf/type/__apt_backports/man.rst
@@ -0,0 +1,104 @@
+cdist-type__debian_backports(7)
+===============================
+
+NAME
+----
+cdist-type__apt_backports - Install backports
+
+
+DESCRIPTION
+-----------
+This singleton type installs backports for the current OS release.
+It aborts if backports are not supported for the specified OS or
+no version codename could be fetched (like Debian unstable).
+
+The package index will be automatically updated if required.
+
+It supports backports from following OSes:
+
+- Debian
+- Devuan
+- Ubuntu
+
+
+REQUIRED PARAMETERS
+-------------------
+None.
+
+
+OPTIONAL PARAMETERS
+-------------------
+state
+ Represents the state of the backports repository. ``present`` or
+ ``absent``, defaults to ``present``.
+
+ Will be directly passed to :strong:`cdist-type__apt_source`\ (7).
+
+mirror
+ The mirror to fetch the backports from. Will defaults to the generic
+ mirror of the current OS.
+
+ Will be directly passed to :strong:`cdist-type__apt_source`\ (7).
+
+
+BOOLEAN PARAMETERS
+------------------
+None.
+
+
+MESSAGES
+--------
+None.
+
+
+EXAMPLES
+--------
+
+.. code-block:: sh
+
+ # setup the backports
+ __apt_backports
+ __apt_backports --state absent
+ __apt_backports --state present --mirror "http://ftp.de.debian.org/debian/"
+
+ # install a backports package
+ # currently for the buster release backports
+ require="__apt_backports" __package_apt wireguard \
+ --target-release buster-backports
+
+
+ABORTS
+------
+Aborts if the detected os is not Debian.
+
+Aborts if no distribuition codename could be detected. This is common for the
+unstable distribution, but there is no backports repository for it already.
+
+
+CAVEATS
+-------
+For Ubuntu, it setup all componenents for the backports repository: ``main``,
+``restricted``, ``universe`` and ``multiverse``. The user may not want to
+install proprietary packages, which will only be installed if the user
+explicitly uses the backports target-release. The user may change this behavior
+to install backports packages without the need of explicitly select it.
+
+
+SEE ALSO
+--------
+`Official Debian Backports site `_
+
+:strong:`cdist-type__apt_source`\ (7)
+
+
+AUTHORS
+-------
+Matthias Stecher
+
+
+COPYING
+-------
+Copyright \(C) 2020 Matthias Stecher. 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/__apt_backports/manifest b/cdist/conf/type/__apt_backports/manifest
new file mode 100755
index 00000000..bc47d8de
--- /dev/null
+++ b/cdist/conf/type/__apt_backports/manifest
@@ -0,0 +1,81 @@
+#!/bin/sh -e
+# __apt_backports/manifest
+#
+# 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 .
+#
+#
+# Enables/disables backports repository. Utilises __apt_source for it.
+#
+
+
+# Get the distribution codename by /etc/os-release.
+# is already executed in a subshell by string substitution
+# lsb_release may not be given in all installations
+codename_os_release() {
+ # shellcheck disable=SC1090
+ . "$__global/explorer/os_release"
+ printf "%s" "$VERSION_CODENAME"
+}
+
+# detect backport distribution
+os="$(cat "$__global/explorer/os")"
+case "$os" in
+ debian)
+ dist="$( codename_os_release )"
+ components="main"
+ mirror="http://deb.debian.org/debian/"
+ ;;
+ devuan)
+ dist="$( codename_os_release )"
+ components="main"
+ mirror="http://deb.devuan.org/merged"
+ ;;
+ ubuntu)
+ dist="$( codename_os_release )"
+ components="main restricted universe multiverse"
+ mirror="http://archive.ubuntu.com/ubuntu"
+ ;;
+
+ *)
+ printf "Backports for %s are not supported!\n" "$os" >&2
+ exit 1
+ ;;
+esac
+
+# error if no codename given (e.g. on Debian unstable)
+if [ -z "$dist" ]; then
+ printf "No backports for unkown version of distribution %s!\n" "$os" >&2
+ exit 1
+fi
+
+
+# parameters
+state="$(cat "$__object/parameter/state")"
+
+# mirror already set for the os, only override user-values
+if [ -f "$__object/parameter/mirror" ]; then
+ mirror="$(cat "$__object/parameter/mirror")"
+fi
+
+
+# install the given backports repository
+__apt_source "${dist}-backports" \
+ --state "$state" \
+ --distribution "${dist}-backports" \
+ --component "$components" \
+ --uri "$mirror"
diff --git a/cdist/conf/type/__apt_backports/parameter/default/state b/cdist/conf/type/__apt_backports/parameter/default/state
new file mode 100644
index 00000000..e7f6134f
--- /dev/null
+++ b/cdist/conf/type/__apt_backports/parameter/default/state
@@ -0,0 +1 @@
+present
diff --git a/cdist/conf/type/__apt_backports/parameter/optional b/cdist/conf/type/__apt_backports/parameter/optional
new file mode 100644
index 00000000..4b05c235
--- /dev/null
+++ b/cdist/conf/type/__apt_backports/parameter/optional
@@ -0,0 +1,2 @@
+state
+mirror
diff --git a/cdist/conf/type/__matterbridge/singleton b/cdist/conf/type/__apt_backports/singleton
similarity index 100%
rename from cdist/conf/type/__matterbridge/singleton
rename to cdist/conf/type/__apt_backports/singleton
diff --git a/cdist/conf/type/__apt_norecommends/man.rst b/cdist/conf/type/__apt_norecommends/man.rst
index 001fffe4..9297b518 100644
--- a/cdist/conf/type/__apt_norecommends/man.rst
+++ b/cdist/conf/type/__apt_norecommends/man.rst
@@ -32,11 +32,12 @@ EXAMPLES
AUTHORS
-------
Steven Armstrong
+Dennis Camera
COPYING
-------
-Copyright \(C) 2014 Steven Armstrong. 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.
+Copyright \(C) 2014 Steven Armstrong, 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/__apt_norecommends/manifest b/cdist/conf/type/__apt_norecommends/manifest
index e737df89..fc187784 100755
--- a/cdist/conf/type/__apt_norecommends/manifest
+++ b/cdist/conf/type/__apt_norecommends/manifest
@@ -1,6 +1,7 @@
#!/bin/sh -e
#
# 2014 Steven Armstrong (steven-cdist at armstrong.cc)
+# 2020 Dennis Camera (dennis.camera at ssrq-sds-fds.ch)
#
# This file is part of cdist.
#
@@ -19,26 +20,28 @@
#
-os=$(cat "$__global/explorer/os")
+os=$(cat "${__global:?}/explorer/os")
-case "$os" in
- ubuntu|debian|devuan)
- # No stinking recommends thank you very much.
- # If I want something installed I will do so myself.
- __file /etc/apt/apt.conf.d/99-no-recommends \
- --owner root --group root --mode 644 \
- --source - << DONE
-APT::Install-Recommends "0";
-APT::Install-Suggests "0";
-APT::AutoRemove::RecommendsImportant "0";
-APT::AutoRemove::SuggestsImportant "0";
-DONE
- ;;
- *)
- cat >&2 << DONE
+case ${os}
+in
+ (ubuntu|debian|devuan)
+ __file /etc/apt/apt.conf.d/00InstallRecommends --state present \
+ --owner root --group root --mode 0644 --source - <<-'EOF'
+ APT::Install-Recommends "false";
+ APT::Install-Suggests "false";
+ APT::AutoRemove::RecommendsImportant "false";
+ APT::AutoRemove::SuggestsImportant "false";
+ EOF
+
+ # TODO: Remove the following object after some time
+ require=__file/etc/apt/apt.conf.d/00InstallRecommends \
+ __file /etc/apt/apt.conf.d/99-no-recommends --state absent
+ ;;
+ (*)
+ cat >&2 < "\$tmpfile"
-mv -f "\$tmpfile" "$file"
+}' $quoted_file > "\$tmpfile"
+mv -f "\$tmpfile" $quoted_file
DONE
}
+quoted_file="$(quote "$file")"
case "$state_should" in
present)
if [ "$state_is" = "changed" ]; then
@@ -77,7 +78,7 @@ case "$state_should" in
echo add >> "$__messages_out"
fi
cat << DONE
-cat >> "$file" << ${__type##*/}_DONE
+cat >> $quoted_file << '${__type##*/}_DONE'
$(cat "$block")
${__type##*/}_DONE
DONE
diff --git a/cdist/conf/type/__cdist/manifest b/cdist/conf/type/__cdist/manifest
index a97cf288..0b0f1263 100755
--- a/cdist/conf/type/__cdist/manifest
+++ b/cdist/conf/type/__cdist/manifest
@@ -37,6 +37,7 @@ source="$(cat "$__object/parameter/source")"
# out of it
home=/home/$username
+# shellcheck disable=SC2086
__user "$username" --home "$home" $shell
require="__user/$username" __directory "$home" \
diff --git a/cdist/conf/type/__clean_path/explorer/list b/cdist/conf/type/__clean_path/explorer/list
index 07d38127..2bdc63a5 100755
--- a/cdist/conf/type/__clean_path/explorer/list
+++ b/cdist/conf/type/__clean_path/explorer/list
@@ -18,7 +18,12 @@
# along with cdist. If not, see .
#
-path="/$__object_id"
+if [ -f "$__object/parameter/path" ]
+then
+ path="$( cat "$__object/parameter/path" )"
+else
+ path="/$__object_id"
+fi
[ ! -d "$path" ] && exit 0
diff --git a/cdist/conf/type/__clean_path/gencode-remote b/cdist/conf/type/__clean_path/gencode-remote
index 998a70d8..2899c4a5 100755
--- a/cdist/conf/type/__clean_path/gencode-remote
+++ b/cdist/conf/type/__clean_path/gencode-remote
@@ -20,7 +20,12 @@
[ ! -s "$__object/explorer/list" ] && exit 0
-path="/$__object_id"
+if [ -f "$__object/parameter/path" ]
+then
+ path="$( cat "$__object/parameter/path" )"
+else
+ path="/$__object_id"
+fi
pattern="$( cat "$__object/parameter/pattern" )"
diff --git a/cdist/conf/type/__clean_path/man.rst b/cdist/conf/type/__clean_path/man.rst
index 826f4589..31d90701 100644
--- a/cdist/conf/type/__clean_path/man.rst
+++ b/cdist/conf/type/__clean_path/man.rst
@@ -10,7 +10,7 @@ DESCRIPTION
-----------
Remove files and directories which match the pattern.
-Provided path (as __object_id) must be a directory.
+Provided path must be a directory.
Patterns are passed to ``find``'s ``-regex`` - see ``find(1)`` for more details.
@@ -29,6 +29,9 @@ pattern
OPTIONAL PARAMETERS
-------------------
+path
+ Path which will be cleaned. Defaults to ``$__object_id``.
+
exclude
Pattern of files which are excluded from removal.
@@ -46,6 +49,11 @@ EXAMPLES
--exclude '.+\(charset\.conf\|security\.conf\)' \
--onchange 'service apache2 restart'
+ __clean_path apache2-conf-enabled \
+ --path /etc/apache2/conf-enabled \
+ --pattern '.+' \
+ --exclude '.+\(charset\.conf\|security\.conf\)' \
+ --onchange 'service apache2 restart'
AUTHORS
-------
diff --git a/cdist/conf/type/__clean_path/parameter/optional b/cdist/conf/type/__clean_path/parameter/optional
index 6f313474..3b97f71c 100644
--- a/cdist/conf/type/__clean_path/parameter/optional
+++ b/cdist/conf/type/__clean_path/parameter/optional
@@ -1,2 +1,3 @@
exclude
onchange
+path
diff --git a/cdist/conf/type/__cron/man.rst b/cdist/conf/type/__cron/man.rst
index d0694738..e39bfb5c 100644
--- a/cdist/conf/type/__cron/man.rst
+++ b/cdist/conf/type/__cron/man.rst
@@ -21,6 +21,11 @@ command
OPTIONAL PARAMETERS
-------------------
+**NOTE**: All time-related parameters (``--minute``, ``--hour``, ``--day_of_month``
+``--month`` and ``--day_of_week``) defaults to ``*``, which means to execute it
+**always**. If you set ``--hour 0`` to execute the cronjob only at midnight, it
+will execute **every** minute in the first hour of the morning all days.
+
state
Either present or absent. Defaults to present.
minute
diff --git a/cdist/conf/type/__directory/explorer/stat b/cdist/conf/type/__directory/explorer/stat
index 105d894f..f817cb02 100755
--- a/cdist/conf/type/__directory/explorer/stat
+++ b/cdist/conf/type/__directory/explorer/stat
@@ -30,10 +30,10 @@ fallback() {
gid=$(echo "$ls_line" | awk '{ print $4 }')
owner=$(awk -F: -v uid="$uid" '$3 == uid { print $1; f=1 } END { if (!f) print "UNKNOWN" }' /etc/passwd)
- group=$(awk -F: -v uid="$uid" '$3 == uid { print $1; f=1 } END { if (!f) print "UNKNOWN" }' /etc/group)
+ group=$(awk -F: -v gid="$gid" '$3 == gid { print $1; f=1 } END { if (!f) print "UNKNOWN" }' /etc/group)
mode_text=$(echo "$ls_line" | awk '{ print $1 }')
- mode=$(echo "$mode_text" | awk '{ k=0; for (i=0; i<=8; i++) k += ((substr($1, i+2, 1) ~ /[rwx]/) * 2^(8-i)); printf("%0o", k) }')
+ mode=$(echo "$mode_text" | awk '{for(i=8;i>=0;--i){c=substr($1,10-i,1);k+=((c~/[rwxst]/)*2^i);if(!(i%3))k+=(tolower(c)~/[lst]/)*2^(9+i/3)}printf("%04o",k)}')
printf 'type: %s\nowner: %d %s\ngroup: %d %s\nmode: %s %s\n' \
"$("$__type_explorer/type")" \
@@ -45,56 +45,27 @@ fallback() {
# nothing to work with, nothing we could do
[ -e "$destination" ] || exit 0
-if ! command -v stat >/dev/null
-then
+command -v stat >/dev/null 2>&1 || {
fallback
exit
-fi
+}
-case $("$__explorer/os") in
- "freebsd"|"netbsd"|"openbsd"|"macosx")
- stat -f "type: %HT
+case $("$__explorer/os")
+in
+ freebsd|netbsd|openbsd|macosx)
+ stat -f 'type: %HT
owner: %Du %Su
group: %Dg %Sg
-mode: %Lp %Sp
-" "$destination" | awk '/^type/ { print tolower($0); next } { print }'
+mode: %Mp%03Lp %Sp
+' "$destination" | awk '/^type/ { print tolower($0); next } { print }'
;;
- solaris)
- ls1="$( ls -ld "$destination" )"
- ls2="$( ls -ldn "$destination" )"
-
- if [ -f "$__object/parameter/mode" ]
- then mode_should="$( cat "$__object/parameter/mode" )"
- fi
-
- # yes, it is ugly hack, but if you know better way...
- if [ -z "$( find "$destination" -perm "$mode_should" )" ]
- then octets=888
- else octets="$( echo "$mode_should" | sed 's/^0//' )"
- fi
-
- case "$( echo "$ls1" | cut -c1-1 )" in
- -) echo 'type: regular file' ;;
- d) echo 'type: directory' ;;
- esac
-
- echo "owner: $( echo "$ls2" \
- | awk '{print $3}' ) $( echo "$ls1" \
- | awk '{print $3}' )"
-
- echo "group: $( echo "$ls2" \
- | awk '{print $4}' ) $( echo "$ls1" \
- | awk '{print $4}' )"
-
- echo "mode: $octets $( echo "$ls1" | awk '{print $1}' )"
- ;;
*)
# NOTE: Do not use --printf here as it is not supported by BusyBox stat.
# NOTE: BusyBox's stat might not support the "-c" option, in which case
# we fall through to the shell fallback.
- stat -c "type: %F
+ stat -c 'type: %F
owner: %u %U
group: %g %G
-mode: %a %A" "$destination" 2>/dev/null || fallback
- ;;
+mode: %04a %A' "$destination" 2>/dev/null || fallback
+ ;;
esac
diff --git a/cdist/conf/type/__directory/gencode-remote b/cdist/conf/type/__directory/gencode-remote
index a1a32ea2..d9c00b56 100755
--- a/cdist/conf/type/__directory/gencode-remote
+++ b/cdist/conf/type/__directory/gencode-remote
@@ -97,9 +97,11 @@ case "$state_should" in
value_should="$(cat "$__object/parameter/$attribute")"
value_is="$(get_current_value "$attribute" "$value_should")"
- # change 0xxx format to xxx format => same as stat returns
+ # format mode in four digits => same as stat returns
if [ "$attribute" = mode ]; then
- value_should="$(echo "$value_should" | sed 's/^0\(...\)/\1/')"
+ # Convert to four-digit octal number (printf interprets
+ # strings with leading 0s as octal!)
+ value_should=$(printf '%04o' "0${value_should}")
fi
if [ "$set_attributes" = 1 ] || [ "$value_should" != "$value_is" ]; then
diff --git a/cdist/conf/type/__dot_file/man.rst b/cdist/conf/type/__dot_file/man.rst
index ae65eb95..ba7621a1 100644
--- a/cdist/conf/type/__dot_file/man.rst
+++ b/cdist/conf/type/__dot_file/man.rst
@@ -25,6 +25,9 @@ user
OPTIONAL PARAMETERS
-------------------
+dirmode
+ forwarded to :strong:`__directory` type as mode
+
mode
forwarded to :strong:`__file` type
diff --git a/cdist/conf/type/__dot_file/manifest b/cdist/conf/type/__dot_file/manifest
index 5e4957e5..02dadf05 100755
--- a/cdist/conf/type/__dot_file/manifest
+++ b/cdist/conf/type/__dot_file/manifest
@@ -19,6 +19,7 @@ set -eu
user="$(cat "${__object}/parameter/user")"
home="$(cat "${__object}/explorer/home")"
primary_group="$(cat "${__object}/explorer/primary_group")"
+dirmode="$(cat "${__object}/parameter/dirmode")"
# Create parent directory. Type __directory has flag 'parents', but it
# will leave us with root-owned directory in user home, which is not
@@ -36,6 +37,7 @@ export CDIST_ORDER_DEPENDENCY
for dir ; do
__directory "${home}/${dir}" \
--group "${primary_group}" \
+ --mode "${dirmode}" \
--owner "${user}"
done
diff --git a/cdist/conf/type/__dot_file/parameter/default/dirmode b/cdist/conf/type/__dot_file/parameter/default/dirmode
new file mode 100644
index 00000000..e9745d1f
--- /dev/null
+++ b/cdist/conf/type/__dot_file/parameter/default/dirmode
@@ -0,0 +1 @@
+0700
diff --git a/cdist/conf/type/__dot_file/parameter/optional b/cdist/conf/type/__dot_file/parameter/optional
index ccab9fa6..9f7f83fb 100644
--- a/cdist/conf/type/__dot_file/parameter/optional
+++ b/cdist/conf/type/__dot_file/parameter/optional
@@ -1,3 +1,4 @@
state
mode
source
+dirmode
diff --git a/cdist/conf/type/__download/explorer/remote_cmd b/cdist/conf/type/__download/explorer/remote_cmd
new file mode 100755
index 00000000..e3e35b45
--- /dev/null
+++ b/cdist/conf/type/__download/explorer/remote_cmd
@@ -0,0 +1,19 @@
+#!/bin/sh -e
+
+if [ -f "$__object/parameter/cmd-get" ]
+then
+ cmd="$( cat "$__object/parameter/cmd-get" )"
+
+elif command -v curl > /dev/null
+then
+ cmd="curl -L -o - '%s'"
+
+elif command -v fetch > /dev/null
+then
+ cmd="fetch -o - '%s'"
+
+else
+ cmd="wget -O - '%s'"
+fi
+
+echo "$cmd"
diff --git a/cdist/conf/type/__download/explorer/state b/cdist/conf/type/__download/explorer/state
new file mode 100755
index 00000000..00362545
--- /dev/null
+++ b/cdist/conf/type/__download/explorer/state
@@ -0,0 +1,72 @@
+#!/bin/sh -e
+
+dst="/$__object_id"
+
+if [ ! -f "$dst" ]
+then
+ echo 'absent'
+ exit 0
+fi
+
+sum_should="$( cat "$__object/parameter/sum" )"
+
+if [ -f "$__object/parameter/cmd-sum" ]
+then
+ # shellcheck disable=SC2059
+ sum_is="$( eval "$( printf \
+ "$( cat "$__object/parameter/cmd-sum" )" \
+ "$dst" )" )"
+else
+ os="$( "$__explorer/os" )"
+
+ if echo "$sum_should" | grep -Eq '^[0-9]+\s[0-9]+$'
+ then
+ sum_is="$( cksum "$dst" | awk '{print $1" "$2}' )"
+
+ elif echo "$sum_should" | grep -Eiq '^md5:[a-f0-9]{32}$'
+ then
+ case "$os" in
+ freebsd)
+ sum_is="md5:$( md5 -q "$dst" )"
+ ;;
+ *)
+ sum_is="md5:$( md5sum "$dst" | awk '{print $1}' )"
+ ;;
+ esac
+
+ elif echo "$sum_should" | grep -Eiq '^sha1:[a-f0-9]{40}$'
+ then
+ case "$os" in
+ freebsd)
+ sum_is="sha1:$( sha1 -q "$dst" )"
+ ;;
+ *)
+ sum_is="sha1:$( sha1sum "$dst" | awk '{print $1}' )"
+ ;;
+ esac
+
+ elif echo "$sum_should" | grep -Eiq '^sha256:[a-f0-9]{64}$'
+ then
+ case "$os" in
+ freebsd)
+ sum_is="sha256:$( sha256 -q "$dst" )"
+ ;;
+ *)
+ sum_is="sha256:$( sha256sum "$dst" | awk '{print $1}' )"
+ ;;
+ esac
+ fi
+fi
+
+if [ -z "$sum_is" ]
+then
+ echo 'no checksum from target' >&2
+ exit 1
+fi
+
+if [ "$sum_is" = "$sum_should" ]
+then
+ echo 'present'
+else
+ echo 'mismatch'
+fi
diff --git a/cdist/conf/type/__download/gencode-local b/cdist/conf/type/__download/gencode-local
new file mode 100755
index 00000000..571d2c3c
--- /dev/null
+++ b/cdist/conf/type/__download/gencode-local
@@ -0,0 +1,58 @@
+#!/bin/sh -e
+
+download="$( cat "$__object/parameter/download" )"
+
+state_is="$( cat "$__object/explorer/state" )"
+
+if [ "$download" != 'local' ] || [ "$state_is" = 'present' ]
+then
+ exit 0
+fi
+
+url="$( cat "$__object/parameter/url" )"
+
+tmp="$( mktemp )"
+
+dst="/$__object_id"
+
+if [ -f "$__object/parameter/cmd-get" ]
+then
+ cmd="$( cat "$__object/parameter/cmd-get" )"
+
+elif command -v wget > /dev/null
+then
+ cmd="wget -O - '%s'"
+
+elif command -v curl > /dev/null
+then
+ cmd="curl -L -o - '%s'"
+
+elif command -v fetch > /dev/null
+then
+ cmd="fetch -o - '%s'"
+
+else
+ echo 'no usable locally installed utility for downloading' >&2
+ exit 1
+fi
+
+printf "$cmd > %s\n" \
+ "$url" \
+ "$tmp"
+
+if echo "$__target_host" | grep -Eq '^[0-9a-fA-F:]+$'
+then
+ target_host="[$__target_host]"
+else
+ target_host="$__target_host"
+fi
+
+printf '%s %s %s:%s\n' \
+ "$__remote_copy" \
+ "$tmp" \
+ "$target_host" \
+ "$dst"
+
+echo "rm -f '$tmp'"
+
+echo 'downloaded' > "$__messages_out"
diff --git a/cdist/conf/type/__download/gencode-remote b/cdist/conf/type/__download/gencode-remote
new file mode 100755
index 00000000..029a0801
--- /dev/null
+++ b/cdist/conf/type/__download/gencode-remote
@@ -0,0 +1,25 @@
+#!/bin/sh -e
+
+download="$( cat "$__object/parameter/download" )"
+
+state_is="$( cat "$__object/explorer/state" )"
+
+if [ "$download" = 'remote' ] && [ "$state_is" != 'present' ]
+then
+ cmd="$( cat "$__object/explorer/remote_cmd" )"
+
+ url="$( cat "$__object/parameter/url" )"
+
+ dst="/$__object_id"
+
+ printf "$cmd > %s\n" \
+ "$url" \
+ "$dst"
+
+ echo 'downloaded' > "$__messages_out"
+fi
+
+if [ -f "$__object/parameter/onchange" ] && [ "$state_is" != "present" ]
+then
+ cat "$__object/parameter/onchange"
+fi
diff --git a/cdist/conf/type/__download/man.rst b/cdist/conf/type/__download/man.rst
new file mode 100644
index 00000000..54503470
--- /dev/null
+++ b/cdist/conf/type/__download/man.rst
@@ -0,0 +1,87 @@
+cdist-type__download(7)
+=======================
+
+NAME
+----
+cdist-type__download - Download a file
+
+
+DESCRIPTION
+-----------
+Destination (``$__object_id``) in target host must be persistent storage
+in order to calculate checksum and decide if file must be (re-)downloaded.
+
+By default type will try to use ``wget``, ``curl`` or ``fetch``.
+If download happens in target (see ``--download``) then type will
+fallback to (and install) ``wget``.
+
+If download happens in local machine, then environment variables like
+``{http,https,ftp}_proxy`` etc can be used on cdist execution
+(``http_proxy=foo cdist config ...``).
+
+
+REQUIRED PARAMETERS
+-------------------
+url
+ File's URL.
+
+sum
+ Checksum of file going to be downloaded.
+ By default output of ``cksum`` without filename is expected.
+ Other hash formats supported with prefixes: ``md5:``, ``sha1:`` and ``sha256:``.
+
+
+OPTIONAL PARAMETERS
+-------------------
+download
+ If ``local`` (default), then download file to local storage and copy
+ it to target host. If ``remote``, then download happens in target.
+
+cmd-get
+ Command used for downloading.
+ Command must output to ``stdout``.
+ Parameter will be used for ``printf`` and must include only one
+ format specification ``%s`` which will become URL.
+ For example: ``wget -O - '%s'``.
+
+cmd-sum
+ Command used for checksum calculation.
+ Command output and ``--sum`` parameter must match.
+ Parameter will be used for ``printf`` and must include only one
+ format specification ``%s`` which will become destination.
+ For example: ``md5sum '%s' | awk '{print $1}'``.
+
+onchange
+ Execute this command after download.
+
+
+EXAMPLES
+--------
+
+.. code-block:: sh
+
+ __directory /opt/cpma
+
+ require='__directory/opt/cpma' \
+ __download /opt/cpma/cnq3.zip \
+ --url https://cdn.playmorepromode.com/files/cnq3/cnq3-1.51.zip \
+ --sum md5:46da3021ca9eace277115ec9106c5b46
+
+ require='__download/opt/cpma/cnq3.zip' \
+ __unpack /opt/cpma/cnq3.zip \
+ --backup-destination \
+ --preserve-archive \
+ --destination /opt/cpma/server
+
+
+AUTHORS
+-------
+Ander Punnar
+
+
+COPYING
+-------
+Copyright \(C) 2020 Ander Punnar. 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/__download/manifest b/cdist/conf/type/__download/manifest
new file mode 100755
index 00000000..7ec8d86d
--- /dev/null
+++ b/cdist/conf/type/__download/manifest
@@ -0,0 +1,6 @@
+#!/bin/sh -e
+
+if grep -Eq '^wget' "$__object/explorer/remote_cmd"
+then
+ __package wget
+fi
diff --git a/cdist/conf/type/__download/parameter/default/download b/cdist/conf/type/__download/parameter/default/download
new file mode 100644
index 00000000..40830374
--- /dev/null
+++ b/cdist/conf/type/__download/parameter/default/download
@@ -0,0 +1 @@
+local
diff --git a/cdist/conf/type/__download/parameter/optional b/cdist/conf/type/__download/parameter/optional
new file mode 100644
index 00000000..838e2fbf
--- /dev/null
+++ b/cdist/conf/type/__download/parameter/optional
@@ -0,0 +1,4 @@
+cmd-get
+cmd-sum
+download
+onchange
diff --git a/cdist/conf/type/__download/parameter/required b/cdist/conf/type/__download/parameter/required
new file mode 100644
index 00000000..6ea4c38f
--- /dev/null
+++ b/cdist/conf/type/__download/parameter/required
@@ -0,0 +1,2 @@
+url
+sum
diff --git a/cdist/conf/type/__dpkg_architecture/explorer/architecture b/cdist/conf/type/__dpkg_architecture/explorer/architecture
new file mode 100755
index 00000000..03e7e386
--- /dev/null
+++ b/cdist/conf/type/__dpkg_architecture/explorer/architecture
@@ -0,0 +1,26 @@
+#!/bin/sh -e
+# __dpkg_architecture/explorer/architecture
+#
+# 2020 Matthias Stecher
+#
+# 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 .
+#
+
+# Get the main architecture of this machine
+
+
+# print or die in the gencode-remote
+dpkg --print-architecture || true
diff --git a/cdist/conf/type/__dpkg_architecture/explorer/foreign-architectures b/cdist/conf/type/__dpkg_architecture/explorer/foreign-architectures
new file mode 100755
index 00000000..a150d307
--- /dev/null
+++ b/cdist/conf/type/__dpkg_architecture/explorer/foreign-architectures
@@ -0,0 +1,26 @@
+#!/bin/sh -e
+# __dpkg_architecture/explorer/foreign-architectures
+#
+# 2020 Matthias Stecher
+#
+# 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 .
+#
+
+# Print all additional architectures
+
+
+# print or die in the gencode-remote
+dpkg --print-foreign-architectures || true
diff --git a/cdist/conf/type/__dpkg_architecture/gencode-remote b/cdist/conf/type/__dpkg_architecture/gencode-remote
new file mode 100755
index 00000000..47fb24e7
--- /dev/null
+++ b/cdist/conf/type/__dpkg_architecture/gencode-remote
@@ -0,0 +1,82 @@
+#!/bin/sh -e
+# __dpkg_architecture/gencode-remote
+#
+# 2020 Matthias Stecher
+#
+# 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 .
+#
+
+
+# Get parameter and explorer
+state_should="$(cat "$__object/parameter/state")"
+arch_wanted="$__object_id"
+main_arch="$(cat "$__object/explorer/architecture")"
+
+# Exit here if dpkg do not work (empty explorer)
+if [ -z "$main_arch" ]; then
+ echo "dpkg is not available or unable to detect a architecture!" >&2
+ exit 1
+fi
+
+
+# Check if requested architecture is the main one
+if [ "$arch_wanted" = "$main_arch" ]; then
+ # higher than present; we can not remove it
+ state_is="present"
+ caution="yes"
+
+# Check if the architecture not already used
+elif grep -qFx "$arch_wanted" "$__object/explorer/foreign-architectures"; then
+ state_is="present"
+
+# arch does not exist
+else
+ state_is="absent"
+fi
+
+
+# Check what to do
+if [ "$state_is" != "$state_should" ]; then
+ case "$state_should" in
+ present)
+ # print add code
+ printf "dpkg --add-architecture '%s'\n" "$arch_wanted"
+ # updating the index to make the new architecture available
+ echo "apt update"
+
+ echo added >> "$__messages_out"
+ ;;
+
+ absent)
+ if [ "$caution" ]; then
+ printf "can not remove the main arch '%s' of the system!\n" "$main_arch" >&2
+ exit 1
+ fi
+
+ # removing all existing packages for the architecture
+ printf "apt purge '.*:%s'\n" "$arch_wanted"
+ # print remove code
+ printf "dpkg --remove-architecture '%s'\n" "$arch_wanted"
+
+ echo removed >> "$__messages_out"
+ ;;
+
+ *)
+ printf "state '%s' is unknown!\n" "$state_should" >&2
+ exit 1
+ ;;
+ esac
+fi
diff --git a/cdist/conf/type/__dpkg_architecture/man.rst b/cdist/conf/type/__dpkg_architecture/man.rst
new file mode 100644
index 00000000..fa196229
--- /dev/null
+++ b/cdist/conf/type/__dpkg_architecture/man.rst
@@ -0,0 +1,103 @@
+cdist-type__dpkg_architecture(7)
+================================
+
+NAME
+----
+cdist-type__dpkg_architecture - Handles foreign architectures on debian-like
+systems managed by `dpkg`
+
+
+DESCRIPTION
+-----------
+This type handles foreign architectures on systems managed by
+:strong:`dpkg`\ (1). The object id is the name of the architecture accepted by
+`dpkg`, which should be added or removed.
+
+If the architecture is not setup on the system, it adds a new architecture as a
+new foreign architecture in `dpkg`. Then, it updates the apt package index to
+make packages from the new architecture available.
+
+If the architecture should be removed, it will remove it if it is not the base
+architecture on where the system was installed on. Before it, it will purge
+every package based on the "to be removed" architecture via `apt` to be able to
+remove the selected architecture.
+
+
+REQUIRED PARAMETERS
+-------------------
+None.
+
+
+OPTIONAL PARAMETERS
+-------------------
+state
+ ``present`` or ``absent``. Defaults to ``present``.
+
+
+MESSAGES
+--------
+added
+ Added the specified architecture
+
+removed
+ Removed the specified architecture
+
+
+ABORTS
+------
+Aborts in the following cases:
+
+If :strong:`dpkg`\ (1) is not available. It will abort with a proper error
+message.
+
+If the architecture is the same as the base architecture the system is build
+upon it (returned by ``dpkg --print-architecture``) and it should be removed.
+
+It will fail if it can not execute :strong:`apt`\ (8). It is assumed that it is
+already installed.
+
+
+EXAMPLES
+--------
+
+.. code-block:: sh
+
+ # add i386 (32 bit) architecture
+ __dpkg_architecture i386
+
+ # remove it again :)
+ __dpkg_architecture i386 --state absent
+
+
+SEE ALSO
+--------
+`Multiarch on Debian systems `_
+
+`How to setup multiarch on Debian `_
+
+:strong:`dpkg`\ (1)
+:strong:`cdist-type__package_dpkg`\ (7)
+:strong:`cdist-type__package_apt`\ (7)
+
+Useful commands:
+
+.. code-block:: sh
+
+ # base architecture installed on this system
+ dpkg --print-architecture
+
+ # extra architectures added
+ dpkg --print-foreign-architectures
+
+
+AUTHORS
+-------
+Matthias Stecher
+
+
+COPYING
+-------
+Copyright \(C) 2020 Matthias Stecher. You can redistribute it
+and/or modify it under the terms of the GNU General Public License as
+ublished by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
diff --git a/cdist/conf/type/__pf_apply/singleton b/cdist/conf/type/__dpkg_architecture/nonparallel
similarity index 100%
rename from cdist/conf/type/__pf_apply/singleton
rename to cdist/conf/type/__dpkg_architecture/nonparallel
diff --git a/cdist/conf/type/__dpkg_architecture/parameter/default/state b/cdist/conf/type/__dpkg_architecture/parameter/default/state
new file mode 100644
index 00000000..e7f6134f
--- /dev/null
+++ b/cdist/conf/type/__dpkg_architecture/parameter/default/state
@@ -0,0 +1 @@
+present
diff --git a/cdist/conf/type/__dpkg_architecture/parameter/optional b/cdist/conf/type/__dpkg_architecture/parameter/optional
new file mode 100644
index 00000000..ff72b5c7
--- /dev/null
+++ b/cdist/conf/type/__dpkg_architecture/parameter/optional
@@ -0,0 +1 @@
+state
diff --git a/cdist/conf/type/__file/explorer/stat b/cdist/conf/type/__file/explorer/stat
index 91c8cc84..29b3c8a3 100755
--- a/cdist/conf/type/__file/explorer/stat
+++ b/cdist/conf/type/__file/explorer/stat
@@ -31,10 +31,10 @@ fallback() {
gid=$(echo "$ls_line" | awk '{ print $4 }')
owner=$(awk -F: -v uid="$uid" '$3 == uid { print $1; f=1 } END { if (!f) print "UNKNOWN" }' /etc/passwd)
- group=$(awk -F: -v uid="$uid" '$3 == uid { print $1; f=1 } END { if (!f) print "UNKNOWN" }' /etc/group)
+ group=$(awk -F: -v gid="$gid" '$3 == gid { print $1; f=1 } END { if (!f) print "UNKNOWN" }' /etc/group)
mode_text=$(echo "$ls_line" | awk '{ print $1 }')
- mode=$(echo "$mode_text" | awk '{ k=0; for (i=0; i<=8; i++) k += ((substr($1, i+2, 1) ~ /[rwx]/) * 2^(8-i)); printf("%0o", k) }')
+ mode=$(echo "$mode_text" | awk '{for(i=8;i>=0;--i){c=substr($1,10-i,1);k+=((c~/[rwxst]/)*2^i);if(!(i%3))k+=(tolower(c)~/[lst]/)*2^(9+i/3)}printf("%04o",k)}')
size=$(echo "$ls_line" | awk '{ print $5 }')
links=$(echo "$ls_line" | awk '{ print $2 }')
@@ -53,64 +53,32 @@ fallback() {
[ -e "$destination" ] || exit 0
-if ! command -v stat >/dev/null
-then
+command -v stat >/dev/null 2>&1 || {
fallback
exit
-fi
+}
case $("$__explorer/os")
in
freebsd|netbsd|openbsd|macosx)
- stat -f "type: %HT
+ stat -f 'type: %HT
owner: %Du %Su
group: %Dg %Sg
-mode: %Lp %Sp
+mode: %Mp%03Lp %Sp
size: %Dz
links: %Dl
-" "$destination" | awk '/^type/ { print tolower($0); next } { print }'
+' "$destination" | awk '/^type/ { print tolower($0); next } { print }'
;;
- solaris)
- ls1="$( ls -ld "$destination" )"
- ls2="$( ls -ldn "$destination" )"
-
- if [ -f "$__object/parameter/mode" ]
- then mode_should="$( cat "$__object/parameter/mode" )"
- fi
-
- # yes, it is ugly hack, but if you know better way...
- if [ -z "$( find "$destination" -perm "$mode_should" )" ]
- then octets=888
- else octets="$( echo "$mode_should" | sed 's/^0//' )"
- fi
-
- case "$( echo "$ls1" | cut -c1-1 )" in
- -) echo 'type: regular file' ;;
- d) echo 'type: directory' ;;
- esac
-
- echo "owner: $( echo "$ls2" \
- | awk '{print $3}' ) $( echo "$ls1" \
- | awk '{print $3}' )"
-
- echo "group: $( echo "$ls2" \
- | awk '{print $4}' ) $( echo "$ls1" \
- | awk '{print $4}' )"
-
- echo "mode: $octets $( echo "$ls1" | awk '{print $1}' )"
- echo "size: $( echo "$ls1" | awk '{print $5}' )"
- echo "links: $( echo "$ls1" | awk '{print $2}' )"
- ;;
*)
# NOTE: Do not use --printf here as it is not supported by BusyBox stat.
# NOTE: BusyBox's stat might not support the "-c" option, in which case
# we fall through to the shell fallback.
- stat -c "type: %F
+ stat -c 'type: %F
owner: %u %U
group: %g %G
-mode: %a %A
+mode: %04a %A
size: %s
-links: %h" "$destination" 2>/dev/null || fallback
- ;;
+links: %h' "$destination" 2>/dev/null || fallback
+ ;;
esac
diff --git a/cdist/conf/type/__file/gencode-remote b/cdist/conf/type/__file/gencode-remote
index 815593bd..f7a528fd 100755
--- a/cdist/conf/type/__file/gencode-remote
+++ b/cdist/conf/type/__file/gencode-remote
@@ -68,9 +68,11 @@ case "$state_should" in
if [ -f "$__object/parameter/$attribute" ]; then
value_should="$(cat "$__object/parameter/$attribute")"
- # change 0xxx format to xxx format => same as stat returns
+ # format mode in four digits => same as stat returns
if [ "$attribute" = mode ]; then
- value_should="$(echo "$value_should" | sed 's/^0\(...\)/\1/')"
+ # Convert to four-digit octal number (printf interprets
+ # strings with leading 0s as octal!)
+ value_should=$(printf '%04o' "0${value_should}")
fi
value_is="$(get_current_value "$attribute" "$value_should")"
@@ -85,11 +87,6 @@ case "$state_should" in
fi
;;
- pre-exists)
- # pre-exists should never reach gencode-remote…
- exit 1
- ;;
-
absent)
if [ "$type" = "file" ]; then
echo "rm -f '$destination'"
@@ -98,6 +95,10 @@ case "$state_should" in
fi
;;
+ pre-exists)
+ :
+ ;;
+
*)
echo "Unknown state: $state_should" >&2
exit 1
diff --git a/cdist/conf/type/__file/man.rst b/cdist/conf/type/__file/man.rst
index 7a0603bb..2f3b9e69 100644
--- a/cdist/conf/type/__file/man.rst
+++ b/cdist/conf/type/__file/man.rst
@@ -50,13 +50,13 @@ state
create or modify it
group
- Group to chgrp to.
+ Group to chgrp to. Defaults to ``root``.
mode
- Unix permissions, suitable for chmod.
+ Unix permissions, suitable for chmod. Defaults to a very secure ``0600``.
owner
- User to chown to.
+ User to chown to. Defaults to ``root``.
source
If supplied, copy this file from the host running cdist to the target.
diff --git a/cdist/conf/type/__filesystem/explorer/lsblk b/cdist/conf/type/__filesystem/explorer/lsblk
index 9ae544ac..9be3c575 100644
--- a/cdist/conf/type/__filesystem/explorer/lsblk
+++ b/cdist/conf/type/__filesystem/explorer/lsblk
@@ -18,16 +18,16 @@
# along with cdist. If not, see .
#
-os=$("$__explorer/os")
+os=$("${__explorer:?}/os")
-if [ -f "$__object/parameter/device" ]; then
+if [ -f "${__object:?}/parameter/device" ]; then
blkdev="$(cat "$__object/parameter/device")"
else
- blkdev="$__object_id"
+ blkdev="${__object_id:?}"
fi
case "$os" in
- centos|fedora|redhat|suse|gentoo)
+ alpine|centos|fedora|redhat|suse|gentoo)
if [ ! -x "$(command -v lsblk)" ]; then
echo "lsblk is required for __filesystem type" >&2
exit 1
diff --git a/cdist/conf/type/__group/gencode-remote b/cdist/conf/type/__group/gencode-remote
index 6091c548..ff63e218 100755
--- a/cdist/conf/type/__group/gencode-remote
+++ b/cdist/conf/type/__group/gencode-remote
@@ -88,7 +88,7 @@ if [ "$state" = "present" ]; then
fi
done
if [ "$os" = "freebsd" ]; then
- echo pw groupadd "$@" "$name"
+ echo pw groupadd "$name" "$@"
else
echo groupadd "$@" "$name"
fi
diff --git a/cdist/conf/type/__hostname/gencode-remote b/cdist/conf/type/__hostname/gencode-remote
index ae224611..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,43 +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'"
- ;;
- 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
@@ -94,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 e1e356a0..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,59 +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 -
- ;;
- slackware)
+ ;;
+ (openbsd)
+ echo "${name_should}" | __file /etc/myname --source -
+ ;;
+ (openwrt)
+ __uci system.@system[0].hostname --value "${name_should}"
+ # --transaction hostname
+ ;;
+ (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.
@@ -167,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/__hosts/man.rst b/cdist/conf/type/__hosts/man.rst
index bece7967..1ac706cb 100644
--- a/cdist/conf/type/__hosts/man.rst
+++ b/cdist/conf/type/__hosts/man.rst
@@ -25,6 +25,10 @@ ip
state is ``present``, this parameter is mandatory, if state is
``absent``, this parameter is silently ignored.
+alias
+ An alias for the hostname.
+ This parameter can be specified multiple times (once per alias).
+
EXAMPLES
--------
@@ -36,6 +40,8 @@ EXAMPLES
# previously configured via __hosts.
__hosts happy --state absent
+ __hosts srv1.example.com --ip 192.168.0.42 --alias srv1
+
SEE ALSO
--------
@@ -43,13 +49,14 @@ SEE ALSO
AUTHORS
-------
-
-Dmitry Bogatov
+| Dmitry Bogatov
+| Dennis Camera
COPYING
-------
-Copyright (C) 2015,2016 Dmitry Bogatov. Free use of this software is granted
-under the terms of the GNU General Public License version 3 or later
-(GPLv3+).
+Copyright \(C) 2015-2016 Dmitry Bogatov, 2019 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/__hosts/manifest b/cdist/conf/type/__hosts/manifest
index c536b83b..8103ebd5 100755
--- a/cdist/conf/type/__hosts/manifest
+++ b/cdist/conf/type/__hosts/manifest
@@ -1,29 +1,42 @@
#!/bin/sh -e
-# Copyright (C) 2015 Bogatov Dmitry
#
-# This program is free software: you can redistribute it and/or modify
+# Copyright (C) 2015 Bogatov Dmitry
+# 2019 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.
#
-# This program is distributed in the hope that it will be useful,
+# 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 this program. If not, see .
-set -ue
+#
-hostname="$__object_id"
-state="$(cat "$__object/parameter/state")"
-marker="# __hosts/$hostname"
+set -e
-set -- "__hosts/$hostname" --file /etc/hosts --state "$state"
+hostname=$__object_id
+state=$(cat "${__object}/parameter/state")
+marker="# __hosts/${hostname}"
-if [ "$state" = absent ] ; then
- __line "$@" --regex "$marker"
+if test "${state}" != 'absent'
+then
+ ip=$(cat "${__object}/parameter/ip")
+ if test -s "${__object}/parameter/alias"
+ then
+ aliases=$(while read -r a; do printf '\t%s' "$a"; done <"$__object/parameter/alias")
+ fi
+
+ set -- --line "$(printf '%s\t%s%s %s' \
+ "${ip}" "${hostname}" "${aliases}" "${marker}")"
else
- ip="$(cat "$__object/parameter/ip")"
- __line "$@" --line "$ip $hostname $marker"
+ set -- --regex "$(echo "${marker}" | sed -e 's/\./\\./')$"
fi
+
+__line "/etc/hosts:${hostname}" --file /etc/hosts --state "${state}" "$@"
diff --git a/cdist/conf/type/__hosts/parameter/optional_multiple b/cdist/conf/type/__hosts/parameter/optional_multiple
new file mode 100644
index 00000000..d077ed80
--- /dev/null
+++ b/cdist/conf/type/__hosts/parameter/optional_multiple
@@ -0,0 +1 @@
+alias
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/__pf_apply/explorer/rcvar b/cdist/conf/type/__ipset/explorer/content
similarity index 65%
rename from cdist/conf/type/__pf_apply/explorer/rcvar
rename to cdist/conf/type/__ipset/explorer/content
index 7c8d535f..87f6b517 100755
--- a/cdist/conf/type/__pf_apply/explorer/rcvar
+++ b/cdist/conf/type/__ipset/explorer/content
@@ -1,6 +1,6 @@
#!/bin/sh
#
-# 2012 Jake Guffey (jake.guffey at eprotex.com)
+# 2021 Mesar Hameed (mesar.hameed at gmail.com)
#
# This file is part of cdist.
#
@@ -17,20 +17,10 @@
# You should have received a copy of the GNU General Public License
# along with cdist. If not, see .
#
-#
-# Get the location of the pf ruleset on the target host.
-#
-
-# Debug
-#exec >&2
-#set -x
-
-# Check /etc/rc.conf for pf's configuration file name. Default to /etc/pf.conf
-
-RC="/etc/rc.conf"
-PFCONF="$(grep '^pf_rules=' ${RC} | cut -d= -f2 | sed 's/"//g')"
-echo "${PFCONF:-"/etc/pf.conf"}"
-
-# Debug
-#set +x
+name="$__object_id"
+if ipset -t list | grep -qFx "Name: $name"; then
+ ipset list "$name" | sed '0,/^Members:/d'
+else
+ echo "x_missing_x"
+fi
diff --git a/cdist/conf/type/__pf_ruleset/explorer/cksum b/cdist/conf/type/__ipset/explorer/state
similarity index 55%
rename from cdist/conf/type/__pf_ruleset/explorer/cksum
rename to cdist/conf/type/__ipset/explorer/state
index 9be6c901..9ece28df 100755
--- a/cdist/conf/type/__pf_ruleset/explorer/cksum
+++ b/cdist/conf/type/__ipset/explorer/state
@@ -1,6 +1,6 @@
#!/bin/sh
#
-# 2012 Jake Guffey (jake.guffey at eprotex.com)
+# 2021 Mesar Hameed (mesar.hameed at gmail.com)
#
# This file is part of cdist.
#
@@ -17,25 +17,10 @@
# You should have received a copy of the GNU General Public License
# along with cdist. If not, see .
#
-#
-# Get the 256 bit SHA2 checksum of the pf ruleset on the target host.
-#
-# Debug
-#exec >&2
-#set -x
-
-# Check /etc/rc.conf for pf's configuration file name. Default to /etc/pf.conf
-# See if file exists and if so, get checksum
-
-RC="/etc/rc.conf"
-TMP="$(grep '^pf_rules=' ${RC} | cut -d= -f2 | sed 's/"//g')"
-PFCONF="${TMP:-"/etc/pf.conf"}"
-
-if [ -f "${PFCONF}" ]; then # The pf config file exists, find its cksum.
- cksum -o 1 "${PFCONF}" | cut -d= -f2 | awk '{print $1}'
+name="$__object_id"
+if ipset -t list "$name" >/dev/null; then
+ echo "present"
+else
+ echo "absent"
fi
-
-# Debug
-#set +x
-
diff --git a/cdist/conf/type/__ipset/explorer/type b/cdist/conf/type/__ipset/explorer/type
new file mode 100755
index 00000000..9413cdad
--- /dev/null
+++ b/cdist/conf/type/__ipset/explorer/type
@@ -0,0 +1,26 @@
+#!/bin/sh
+#
+# 2021 Mesar Hameed (mesar.hameed 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 .
+#
+
+name="$__object_id"
+if ipset -t list | grep -qFx "Name: $name"; then
+ ipset -t list "$name" | grep "^Type: " | awk '{print $2}'
+else
+ echo "x_missing_x"
+fi
diff --git a/cdist/conf/type/__ipset/files/ipset-persistent b/cdist/conf/type/__ipset/files/ipset-persistent
new file mode 100755
index 00000000..e812c30f
--- /dev/null
+++ b/cdist/conf/type/__ipset/files/ipset-persistent
@@ -0,0 +1,48 @@
+#!/bin/sh
+#
+# 2021 Mesar Hameed (mesar.hameed 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 .
+#
+#
+### BEGIN INIT INFO
+# Provides: ipset
+# Required-Start: $local_fs $remote_fs
+# Required-Stop: $local_fs $remote_fs
+# X-Start-Before: iptables
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: Create ipset lists before iptables rules require them
+# Description: Applies lists found in /etc/ipset.d/*.saved
+# and saves/restores previous status
+### END INIT INFO
+
+case $1 in
+ start)
+ # Restore previous state:
+ /usr/local/bin/ipsets-restore
+ ;;
+ stop)
+ # Save current state before exiting:
+ /usr/local/bin/ipsets-save
+ ;;
+ restart)
+ "$0" stop && "$0" start
+ ;;
+ reset)
+ ipset flush
+ ;;
+esac
diff --git a/cdist/conf/type/__ipset/files/ipsets-restore b/cdist/conf/type/__ipset/files/ipsets-restore
new file mode 100755
index 00000000..30df3a13
--- /dev/null
+++ b/cdist/conf/type/__ipset/files/ipsets-restore
@@ -0,0 +1,28 @@
+#!/bin/sh
+#
+# 2021 Mesar Hameed (mesar.hameed 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 .
+#
+
+mkdir -p /etc/ipset.d/
+if [ -n "$1" ]; then
+ ipset -! restore < "/etc/ipset.d/$1"
+else
+find /etc/ipset.d/ -iname "*.saved" | while read s; do
+ ipset -! restore <$s
+done
+fi
diff --git a/cdist/conf/type/__ipset/files/ipsets-save b/cdist/conf/type/__ipset/files/ipsets-save
new file mode 100755
index 00000000..9f5a9f3a
--- /dev/null
+++ b/cdist/conf/type/__ipset/files/ipsets-save
@@ -0,0 +1,28 @@
+#!/bin/sh
+#
+# 2021 Mesar Hameed (mesar.hameed 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 .
+#
+
+mkdir -p /etc/ipset.d/
+if [ -n "$1" ]; then
+ ipset save "$1" > "/etc/ipset.d/${1}.saved"
+else
+ipset -t list | grep "^Name:" | awk '{print $2}' | while read s; do
+ ipset save $s > /etc/ipset.d/$s.saved
+done
+fi
diff --git a/cdist/conf/type/__ipset/gencode-remote b/cdist/conf/type/__ipset/gencode-remote
new file mode 100755
index 00000000..38437a6c
--- /dev/null
+++ b/cdist/conf/type/__ipset/gencode-remote
@@ -0,0 +1,79 @@
+#!/bin/sh
+#
+# 2021 Mesar Hameed (mesar.hameed 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 .
+#
+
+e="$__object/explorer"
+p="$__object/parameter"
+name="$__object_id"
+type_is="$(cat "$e/type")"
+type_should="$(cat "$p/type")"
+state_is="$(cat "$e/state")"
+state_should="$(cat "$p/state")"
+needToSave=0
+
+case $state_should in
+ present)
+ if [ "$state_is" = "absent" ]; then
+ echo ipset create "$name" "$type_should"
+ needToSave=1
+ elif [ "$state_is" = "present" ] && [ "$type_is" != "$type_should" ]; then
+ echo ipset destroy "$name"
+ echo "rm \"/etc/ipset.d/${name}.saved\" || true"
+ echo ipset create "$name" "$type_should"
+ needToSave=1
+ fi
+ ;;
+ absent)
+ if [ "$state_is" = "present" ]; then
+ echo ipset destroy "$name"
+ echo "rm \"/etc/ipset.d/${name}.saved\" || true"
+ fi
+ ;;
+ *)
+ echo "Unknown state: $state_should" >&2
+ exit 1
+ ;;
+esac
+
+if [ "$state_should" = "present" ]; then
+ if [ -f "$p/add" ]; then
+ while read -r value; do
+ if ! grep -qFx "$value" "$e/content"; then
+ echo "ipset -! add $name $value"
+ needToSave=1
+ fi
+ done < "$p/add"
+ fi
+
+ if [ -f "$p/del" ]; then
+ while read -r value; do
+ if grep -qFx "$value" "$e/content"; then
+ echo "ipset -! del $name $value"
+ needToSave=1
+ fi
+ done < "$p/del"
+ fi
+elif [ "$state_should" = "absent" ] && \( [ -f "$p/add" ] || [ -f "$p/del" ] \); then
+ echo "Error: ipset state absent is incompatible with --add or --del" >&2
+ exit 1
+fi
+
+if [ $needToSave -ne 0 ]; then
+ echo /usr/local/bin/ipsets-save "$name"
+fi
diff --git a/cdist/conf/type/__ipset/man.rst b/cdist/conf/type/__ipset/man.rst
new file mode 100644
index 00000000..f376470e
--- /dev/null
+++ b/cdist/conf/type/__ipset/man.rst
@@ -0,0 +1,69 @@
+cdist-type__ipset(7)
+====================
+
+NAME
+----
+cdist-type__ipset - Manage ipset sets
+
+DESCRIPTION
+-----------
+Making use of ipset sets in iptable rules can make your rules more expressive, maintainable and efficient.
+
+REQUIRED PARAMETERS
+-------------------
+type
+ One of the supported ipset set types, for a full list see:
+
+ ``ipset help``
+
+OPTIONAL PARAMETERS
+-------------------
+add
+ The entry that must exist in the given set.
+
+ Can be used multiple times.
+del
+ The entry that must not exist in the given set.
+
+ Can be used multiple times.
+state
+ Can be:
+
+ - ``present``: ensure that the given set exists.
+ - ``absent``: ensure the given set doesn't exist.
+
+BOOLEAN PARAMETERS
+------------------
+None.
+
+EXAMPLES
+--------
+
+.. code-block:: sh
+
+ # Make sure a set with the given name/type exists:
+ __ipset testset1 --type hash:ip
+
+ # Ensure allowed_ssh_clients contains private range:
+ __ipset allowed_ssh_hosts --type hash:net \
+ --add 192.168.0.0/24 --add 10.0.0.0/8
+
+ # Make sure host is not on the blocked list:
+ __ipset blocked_hosts --type hash:ip \
+ --del 1.2.3.4
+
+
+SEE ALSO
+--------
+:strong:`cdist-type__iptables_rule`\ (7), :strong:`iptables`\ (8)
+
+AUTHORS
+-------
+Mesar Hameed
+
+COPYING
+-------
+Copyright \(C) 2021 Mesar Hameed. 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/__ipset/manifest b/cdist/conf/type/__ipset/manifest
new file mode 100755
index 00000000..769a50b8
--- /dev/null
+++ b/cdist/conf/type/__ipset/manifest
@@ -0,0 +1,45 @@
+#!/bin/sh -e
+#
+# 2021 Mesar Hameed (mesar.hameed 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 .
+#
+
+os=$(cat "$__global/explorer/os")
+case "$os" in
+ debian)
+ :
+ ;;
+ ubuntu)
+ :
+ ;;
+ *)
+ echo "OS $os currently not supported" >&2
+ exit 1
+ ;;
+esac
+
+export CDIST_ORDER_DEPENDENCY=on
+
+# install packages
+__package ipset
+
+__file /etc/init.d/ipset-persistent --mode 0755 --source "${__type}/files/ipset-persistent"
+__file /usr/local/bin/ipsets-restore --mode 0755 --source "${__type}/files/ipsets-restore"
+__file /usr/local/bin/ipsets-save --mode 0755 --source "${__type}/files/ipsets-save"
+__systemd_unit ipset-persistent --enablement-state enabled --restart
+
+unset CDIST_ORDER_DEPENDENCY
diff --git a/cdist/conf/type/__ipset/parameter/default/state b/cdist/conf/type/__ipset/parameter/default/state
new file mode 100644
index 00000000..e7f6134f
--- /dev/null
+++ b/cdist/conf/type/__ipset/parameter/default/state
@@ -0,0 +1 @@
+present
diff --git a/cdist/conf/type/__ipset/parameter/optional b/cdist/conf/type/__ipset/parameter/optional
new file mode 100644
index 00000000..ff72b5c7
--- /dev/null
+++ b/cdist/conf/type/__ipset/parameter/optional
@@ -0,0 +1 @@
+state
diff --git a/cdist/conf/type/__ipset/parameter/optional_multiple b/cdist/conf/type/__ipset/parameter/optional_multiple
new file mode 100644
index 00000000..4f890061
--- /dev/null
+++ b/cdist/conf/type/__ipset/parameter/optional_multiple
@@ -0,0 +1,2 @@
+add
+del
diff --git a/cdist/conf/type/__ipset/parameter/required b/cdist/conf/type/__ipset/parameter/required
new file mode 100644
index 00000000..aa80e646
--- /dev/null
+++ b/cdist/conf/type/__ipset/parameter/required
@@ -0,0 +1 @@
+type
diff --git a/cdist/conf/type/__iptables_apply/files/init-script b/cdist/conf/type/__iptables_apply/files/init-script
index d9c79ef7..e42017ae 100644
--- a/cdist/conf/type/__iptables_apply/files/init-script
+++ b/cdist/conf/type/__iptables_apply/files/init-script
@@ -1,7 +1,4 @@
#!/bin/sh
-# Nico Schottelius
-# Zürisee, Mon Sep 2 18:38:27 CEST 2013
-#
### BEGIN INIT INFO
# Provides: iptables
# Required-Start: $local_fs $remote_fs
@@ -14,34 +11,72 @@
# and saves/restores previous status
### END INIT INFO
+# Originally written by:
+# Nico Schottelius
+# Zürisee, Mon Sep 2 18:38:27 CEST 2013
+#
+# 2013 Nico Schottelius (nico-cdist at schottelius.org)
+# 2020 Matthias Stecher (matthiasstecher at gmx.de)
+#
+# This file is distributed with cdist and licenced under the
+# GNU GPLv3+ WITHOUT ANY WARRANTY.
+
+
+# Read files and execute the content with the given commands
+#
+# Arguments:
+# 1: Directory
+# 2..n: Commands which should be used to execute the file content
+gothrough() {
+ cd "$1" || return
+ shift
+
+ # iterate through all rules and continue if it's not a file
+ for rule in *; do
+ [ -f "$rule" ] || continue
+ echo "Appling iptables rule $rule ..."
+
+ # execute it with all commands specificed
+ ruleparam="$(cat "$rule")"
+ for cmd in "$@"; do
+ # Command and Rule should be split.
+ # shellcheck disable=SC2046
+ command $cmd $ruleparam
+ done
+ done
+}
+
+# Shortcut for iptables command to do IPv4 and v6
+# only applies to the "reset" target
+iptables() {
+ command iptables "$@"
+ command ip6tables "$@"
+}
basedir=/etc/iptables.d
-status="${basedir}/.pre-start"
+status4="${basedir}/.pre-start"
+status6="${basedir}/.pre-start6"
case $1 in
start)
# Save status
- iptables-save > "$status"
+ iptables-save > "$status4"
+ ip6tables-save > "$status6"
# Apply our ruleset
- cd "$basedir" || exit
- count="$(find . ! -name . -prune | wc -l)"
-
- # Only do something if there are rules
- if [ "$count" -ge 1 ]; then
- for rule in *; do
- echo "Applying iptables rule $rule ..."
- # Rule should be split.
- # shellcheck disable=SC2046
- iptables $(cat "$rule")
- done
- fi
+ gothrough "$basedir" iptables
+ #gothrough "$basedir/v4" iptables # conflicts with $basedir
+ gothrough "$basedir/v6" ip6tables
+ gothrough "$basedir/all" iptables ip6tables
;;
stop)
# Restore from status before, if there is something to restore
- if [ -f "$status" ]; then
- iptables-restore < "$status"
+ if [ -f "$status4" ]; then
+ iptables-restore < "$status4"
+ fi
+ if [ -f "$status6" ]; then
+ ip6tables-restore < "$status6"
fi
;;
restart)
diff --git a/cdist/conf/type/__iptables_apply/man.rst b/cdist/conf/type/__iptables_apply/man.rst
index 76e1f6bf..3bef92cc 100644
--- a/cdist/conf/type/__iptables_apply/man.rst
+++ b/cdist/conf/type/__iptables_apply/man.rst
@@ -10,7 +10,24 @@ DESCRIPTION
-----------
This cdist type deploys an init script that triggers
the configured rules and also re-applies them on
-configuration.
+configuration. Rules are written from __iptables_rule
+into the folder ``/etc/iptables.d/``.
+
+It reads all rules from the base folder as rules for IPv4.
+Rules in the subfolder ``v6/`` are IPv6 rules. Rules in
+the subfolder ``all/`` are applied to both rule tables. All
+files contain the arguments for a single ``iptables`` and/or
+``ip6tables`` command.
+
+Rules are applied in the following order:
+1. All IPv4 rules
+2. All IPv6 rules
+2. All rules that should be applied to both tables
+
+The order of the rules that will be applied are definite
+from the result the shell glob returns, which should be
+alphabetical. If rules must be applied in a special order,
+prefix them with a number like ``02-some-rule``.
REQUIRED PARAMETERS
@@ -24,7 +41,7 @@ None
EXAMPLES
--------
-None (__iptables_apply is used by __iptables_rule)
+None (__iptables_apply is used by __iptables_rule automatically)
SEE ALSO
@@ -35,11 +52,13 @@ SEE ALSO
AUTHORS
-------
Nico Schottelius
+Matthias Stecher
COPYING
-------
-Copyright \(C) 2013 Nico Schottelius. 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.
+Copyright \(C) 2013 Nico Schottelius.
+Copyright \(C) 2020 Matthias Stecher.
+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/__iptables_rule/man.rst b/cdist/conf/type/__iptables_rule/man.rst
index 92d8859f..afb71e01 100644
--- a/cdist/conf/type/__iptables_rule/man.rst
+++ b/cdist/conf/type/__iptables_rule/man.rst
@@ -11,6 +11,10 @@ DESCRIPTION
This cdist type allows you to manage iptable rules
in a distribution independent manner.
+See :strong:`cdist-type__iptables_apply`\ (7) for the
+execution order of these rules. It will be executed
+automaticly to apply all rules non-volaite.
+
REQUIRED PARAMETERS
-------------------
@@ -25,6 +29,24 @@ state
'present' or 'absent', defaults to 'present'
+BOOLEAN PARAMETERS
+------------------
+All rules without any of these parameters will be treated like ``--v4`` because
+of backward compatibility.
+
+v4
+ Explicitly set it as rule for IPv4. If IPv6 is set, too, it will be
+ threaten like ``--all``. Will be the default if nothing else is set.
+
+v6
+ Explicitly set it as rule for IPv6. If IPv4 is set, too, it will be
+ threaten like ``--all``.
+
+all
+ Set the rule for both IPv4 and IPv6. It will be saved separately from the
+ other rules.
+
+
EXAMPLES
--------
@@ -48,6 +70,16 @@ EXAMPLES
--state absent
+ # IPv4-only rule for ICMPv4
+ __iptables_rule icmp-v4 --v4 --rule "-A INPUT -p icmp -j ACCEPT"
+ # IPv6-only rule for ICMPv6
+ __iptables_rule icmp-v6 --v6 --rule "-A INPUT -p icmpv6 -j ACCEPT"
+
+ # doing something for the dual stack
+ __iptables_rule fwd-eth0-eth1 --v4 --v6 --rule "-A INPUT -i eth0 -o eth1 -j ACCEPT"
+ __iptables_rule fwd-eth1-eth0 --all --rule "-A -o eth1 -i eth0 -m state --state RELATED,ESTABLISHED -j ACCEPT"
+
+
SEE ALSO
--------
:strong:`cdist-type__iptables_apply`\ (7), :strong:`iptables`\ (8)
@@ -56,11 +88,13 @@ SEE ALSO
AUTHORS
-------
Nico Schottelius
+Matthias Stecher
COPYING
-------
-Copyright \(C) 2013 Nico Schottelius. 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.
+Copyright \(C) 2013 Nico Schottelius.
+Copyright \(C) 2020 Matthias Stecher.
+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/__iptables_rule/manifest b/cdist/conf/type/__iptables_rule/manifest
index ed78787f..d4394c25 100755
--- a/cdist/conf/type/__iptables_rule/manifest
+++ b/cdist/conf/type/__iptables_rule/manifest
@@ -1,6 +1,7 @@
#!/bin/sh -e
#
# 2013 Nico Schottelius (nico-cdist at schottelius.org)
+# 2020 Matthias Stecher (matthiasstecher at gmx.de)
#
# This file is part of cdist.
#
@@ -24,12 +25,36 @@ base_dir=/etc/iptables.d
name="$__object_id"
state="$(cat "$__object/parameter/state")"
+if [ -f "$__object/parameter/v4" ]; then
+ only_v4="yes"
+ # $specific_dir is $base_dir
+fi
+if [ -f "$__object/parameter/v6" ]; then
+ only_v6="yes"
+ specific_dir="$base_dir/v6"
+fi
+# If rules should be set for both protocols
+if { [ "$only_v4" = "yes" ] && [ "$only_v6" = "yes" ]; } ||
+ [ -f "$__object/parameter/all" ]; then
+
+ # all to a specific directory
+ specific_dir="$base_dir/all"
+fi
+
+# set rule directory based on if it's the base or subdirectory
+rule_dir="${specific_dir:-$base_dir}"
+
################################################################################
# Basic setup
#
__directory "$base_dir" --state present
+# sub-directory if required
+if [ "$specific_dir" ]; then
+ require="__directory/$base_dir" __directory "$specific_dir" --state present
+fi
+
# Have apply do the real job
require="$__object_name" __iptables_apply
@@ -37,6 +62,15 @@ require="$__object_name" __iptables_apply
# The rule
#
-require="__directory/$base_dir" __file "$base_dir/${name}" \
- --source "$__object/parameter/rule" \
- --state "$state"
+for dir in "$base_dir" "$base_dir/v6" "$base_dir/all"; do
+ # defaults to absent except the directory that should contain the file
+ if [ "$rule_dir" = "$dir" ]; then
+ curr_state="$state"
+ else
+ curr_state="absent"
+ fi
+
+ require="__directory/$rule_dir" __file "$dir/$name" \
+ --source "$__object/parameter/rule" \
+ --state "$curr_state"
+done
diff --git a/cdist/conf/type/__iptables_rule/parameter/boolean b/cdist/conf/type/__iptables_rule/parameter/boolean
new file mode 100644
index 00000000..76882272
--- /dev/null
+++ b/cdist/conf/type/__iptables_rule/parameter/boolean
@@ -0,0 +1,3 @@
+all
+v4
+v6
diff --git a/cdist/conf/type/__key_value/explorer/state b/cdist/conf/type/__key_value/explorer/state
index 7b2de1df..d24600af 100755
--- a/cdist/conf/type/__key_value/explorer/state
+++ b/cdist/conf/type/__key_value/explorer/state
@@ -40,7 +40,9 @@ else
fi
export key state delimiter value exact_delimiter
-awk -f - "$file" <<"AWK_EOF"
+awk_bin=$(PATH=$(getconf PATH 2>/dev/null) && command -v awk || echo awk)
+
+"${awk_bin}" -f - "$file" <<"AWK_EOF"
BEGIN {
state=ENVIRON["state"]
key=ENVIRON["key"]
diff --git a/cdist/conf/type/__key_value/files/remote_script.sh b/cdist/conf/type/__key_value/files/remote_script.sh
index f7a1add5..faf080cb 100644
--- a/cdist/conf/type/__key_value/files/remote_script.sh
+++ b/cdist/conf/type/__key_value/files/remote_script.sh
@@ -24,7 +24,10 @@ if [ -f "$file" ]; then
else
touch "$file"
fi
-awk -f - "$file" >"$tmpfile" <<"AWK_EOF"
+
+awk_bin=$(PATH=$(getconf PATH 2>/dev/null) && command -v awk || echo awk)
+
+"${awk_bin}" -f - "$file" >"$tmpfile" <<"AWK_EOF"
BEGIN {
# import variables in a secure way ..
state=ENVIRON["state"]
diff --git a/cdist/conf/type/__key_value/gencode-remote b/cdist/conf/type/__key_value/gencode-remote
index 13cc27c7..1174400e 100755
--- a/cdist/conf/type/__key_value/gencode-remote
+++ b/cdist/conf/type/__key_value/gencode-remote
@@ -25,7 +25,7 @@ state_should="$(cat "$__object/parameter/state")"
state_is="$(cat "$__object/explorer/state")"
fire_onchange=''
-if [ "$state_is" = "$state_should" ]; then
+if [ "$state_is" = "$state_should" ]; then
exit 0
fi
diff --git a/cdist/conf/type/__letsencrypt_cert/manifest b/cdist/conf/type/__letsencrypt_cert/manifest
index 68ecf9d4..b4464366 100755
--- a/cdist/conf/type/__letsencrypt_cert/manifest
+++ b/cdist/conf/type/__letsencrypt_cert/manifest
@@ -91,6 +91,9 @@ if [ -z "${certbot_fullpath}" ]; then
certbot_fullpath=/usr/local/bin/certbot
;;
+ ubuntu)
+ __package certbot
+ ;;
*)
echo "Unsupported os: $os" >&2
exit 1
diff --git a/cdist/conf/type/__line/explorer/state b/cdist/conf/type/__line/explorer/state
index e8fc3630..9d480b19 100755
--- a/cdist/conf/type/__line/explorer/state
+++ b/cdist/conf/type/__line/explorer/state
@@ -53,8 +53,10 @@ function _find(_text, _pattern) {
BEGIN {
getline anchor < (ENVIRON["__object"] "/parameter/" position)
getline pattern < (ENVIRON["__object"] "/parameter/" needle)
+ getline line < (ENVIRON["__object"] "/parameter/line")
found_line = 0
+ correct_line = 0
correct_pos = (position != "after" && position != "before")
}
{
@@ -63,15 +65,18 @@ BEGIN {
getline
if (_find($0, pattern)) {
found_line++
+ if (index($0, line) == 1) { correct_line++ }
correct_pos = 1
exit 0
}
} else if (_find($0, pattern)) {
found_line++
+ if (index($0, line) == 1) { correct_line++ }
}
} else if (position == "before") {
if (_find($0, pattern)) {
found_line++
+ if (index($0, line) == 1) { correct_line++ }
getline
if (match($0, anchor)) {
correct_pos = 1
@@ -81,13 +86,18 @@ BEGIN {
} else {
if (_find($0, pattern)) {
found_line++
+ if (index($0, line) == 1) { correct_line++ }
exit 0
}
}
}
END {
if (found_line && correct_pos) {
- print "present"
+ if (correct_line) {
+ print "present"
+ } else {
+ print "matching"
+ }
} else if (found_line) {
print "wrongposition"
} else {
diff --git a/cdist/conf/type/__line/gencode-remote b/cdist/conf/type/__line/gencode-remote
index 88cae68b..a89886da 100755
--- a/cdist/conf/type/__line/gencode-remote
+++ b/cdist/conf/type/__line/gencode-remote
@@ -38,7 +38,11 @@ if [ -z "$state_is" ]; then
exit 1
fi
-if [ "$state_should" = "$state_is" ]; then
+if [ "$state_should" = "$state_is" ] || \
+ { [ "$state_should" = "present" ] && [ "$state_is" = "matching" ] ;} || \
+ { [ "$state_should" = "replace" ] && [ "$state_is" = "present" ] ;} ; then
+ # If state matches already, or 'present' is used and regex matches
+ # or 'replace' is used and the exact line is present, then there is
# nothing to do
exit 0
fi
@@ -61,8 +65,8 @@ fi
add=0
remove=0
case "$state_should" in
- present)
- if [ "$state_is" = "wrongposition" ]; then
+ present|replace)
+ if [ "$state_is" = "wrongposition" ] || [ "$state_is" = "matching" ]; then
echo updated >> "$__messages_out"
remove=1
else
diff --git a/cdist/conf/type/__line/man.rst b/cdist/conf/type/__line/man.rst
index f76cab64..70490f68 100644
--- a/cdist/conf/type/__line/man.rst
+++ b/cdist/conf/type/__line/man.rst
@@ -31,7 +31,7 @@ file
line
Specifies the line which should be absent or present.
- Must be present, if state is 'present'.
+ Must be present, if state is 'present' or 'replace'.
Ignored if regex is given and state is 'absent'.
regex
@@ -41,10 +41,13 @@ regex
If state is 'absent', ensure all lines matching the regular expression
are absent.
+ If state is 'replace', ensure all lines matching the regular expression
+ are exactly 'line'.
+
The regular expression is interpreted by awk's match function.
state
- 'present' or 'absent', defaults to 'present'
+ 'present', 'absent' or 'replace', defaults to 'present'.
onchange
The code to run if line is added, removed or updated.
@@ -99,6 +102,12 @@ EXAMPLES
--line '-session required pam_exec.so debug log=/tmp/classify.log /usr/local/libexec/classify' \
--after '^session[[:space:]]+include[[:space:]]+password-auth-ac$'
+ # Uncomment as needed and set a value in a configuration file.
+ __line /etc/example.conf \
+ --line 'SomeSetting SomeValue' \
+ --regex '^(#[[:space:]]*)?SomeSetting[[:space:]]' \
+ --state replace
+
SEE ALSO
--------
diff --git a/cdist/conf/type/__link/man.rst b/cdist/conf/type/__link/man.rst
index fe0ce425..2e81aea9 100644
--- a/cdist/conf/type/__link/man.rst
+++ b/cdist/conf/type/__link/man.rst
@@ -18,7 +18,7 @@ source
Specifies the link source.
type
- Specifies the link type: Either hard or symoblic.
+ Specifies the link type: Either hard or symbolic.
OPTIONAL PARAMETERS
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/__acl/explorer/checks b/cdist/conf/type/__locale/explorer/state
similarity index 54%
rename from cdist/conf/type/__acl/explorer/checks
rename to cdist/conf/type/__locale/explorer/state
index 70bb0412..4494fcbc 100755
--- a/cdist/conf/type/__acl/explorer/checks
+++ b/cdist/conf/type/__locale/explorer/state
@@ -1,6 +1,7 @@
#!/bin/sh -e
+# __locale/explorer/state
#
-# 2019 Ander Punnar (ander-at-kvlt-dot-ee)
+# 2020 Matthias Stecher (matthiasstecher at gmx.de)
#
# This file is part of cdist.
#
@@ -17,23 +18,19 @@
# 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.
+#
-# TODO check if filesystem has ACL turned on etc
-if [ -f "$__object/parameter/acl" ]
-then
- grep -E '^(default:)?(user|group):' "$__object/parameter/acl" \
- | while read -r acl
- do
- param="$( echo "$acl" | awk -F: '{print $(NF-2)}' )"
- check="$( echo "$acl" | awk -F: '{print $(NF-1)}' )"
+# 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/')"
- [ "$param" = 'user' ] && db=passwd || db="$param"
-
- if ! getent "$db" "$check" > /dev/null
- then
- echo "missing $param '$check'" >&2
- exit 1
- fi
- done
+# 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/__locale_system/manifest b/cdist/conf/type/__locale_system/manifest
index 80f7401b..4b996ebc 100755
--- a/cdist/conf/type/__locale_system/manifest
+++ b/cdist/conf/type/__locale_system/manifest
@@ -3,6 +3,7 @@
# 2012-2016 Steven Armstrong (steven-cdist at armstrong.cc)
# 2016 Carlos Ortigoza (carlos.ortigoza at ungleich.ch)
# 2016 Nico Schottelius (nico.schottelius at ungleich.ch)
+# 2020 Dennis Camera (dennis.camera at ssrq-sds-fds.ch)
#
# This file is part of cdist.
#
@@ -23,17 +24,171 @@
# Configure system-wide locale by modifying i18n file.
#
+version_ge() {
+ awk -F '[^0-9.]' -v target="${1:?}" '
+ function max(x, y) { return x > y ? x : y }
+ BEGIN {
+ getline
+ nx = split($1, x, ".")
+ ny = split(target, y, ".")
+ for (i = 1; i <= max(nx, ny); ++i) {
+ diff = int(x[i]) - int(y[i])
+ if (diff == 0) continue
+ exit (diff < 0)
+ }
+ }'
+}
+
+
+key=$__object_id
+onchange_cmd= # none, by default
+quote_value=false
+
+catval() {
+ # shellcheck disable=SC2059
+ printf "$($quote_value && echo '"%s"' || echo '%s')" "$(cat "$1")"
+}
+
+state_should=$(cat "${__object}/parameter/state")
+
os=$(cat "$__global/explorer/os")
-case "$os" in
- debian|ubuntu)
+case $os
+in
+ debian)
+ if version_ge 4 <"${__global}/explorer/os_version"
+ then
+ # Debian 4 (etch) and later
+ locale_conf="/etc/default/locale"
+ else
+ locale_conf="/etc/environment"
+ fi
+ ;;
+ devuan)
locale_conf="/etc/default/locale"
;;
+ ubuntu)
+ if version_ge 6.10 <"${__global}/explorer/os_version"
+ then
+ # Ubuntu 6.10 (edgy) and later
+ locale_conf="/etc/default/locale"
+ else
+ locale_conf="/etc/environment"
+ fi
+ ;;
archlinux)
locale_conf="/etc/locale.conf"
;;
- redhat|centos)
- locale_conf="/etc/sysconfig/i18n"
+ centos|redhat|scientific)
+ # shellcheck source=/dev/null
+ version_id=$(. "${__global}/explorer/os_release" && echo "${VERSION_ID:-0}")
+ if echo "${version_id}" | version_ge 7
+ then
+ locale_conf="/etc/locale.conf"
+ else
+ locale_conf="/etc/sysconfig/i18n"
+ fi
+ ;;
+ fedora)
+ # shellcheck source=/dev/null
+ version_id=$(. "${__global}/explorer/os_release" && echo "${VERSION_ID:-0}")
+ if echo "${version_id}" | version_ge 18
+ then
+ locale_conf="/etc/locale.conf"
+ quote_value=false
+ else
+ locale_conf="/etc/sysconfig/i18n"
+ fi
+ ;;
+ gentoo)
+ case $(cat "${__global}/explorer/init")
+ in
+ (*openrc*)
+ locale_conf="/etc/env.d/02locale"
+ onchange_cmd="env-update --no-ldconfig"
+ quote_value=true
+ ;;
+ (systemd)
+ locale_conf="/etc/locale.conf"
+ ;;
+ esac
+ ;;
+ freebsd|netbsd)
+ # NetBSD doesn't have a separate configuration file to set locales.
+ # In FreeBSD locales could be configured via /etc/login.conf but parsing
+ # that would be annoying, so the shell login file will have to do.
+ # "Non-POSIX" shells like csh will not be updated here.
+
+ locale_conf="/etc/profile"
+ quote_value=true
+ value="$(catval "${__object}/parameter/value"); export ${key}"
+ ;;
+ solaris)
+ locale_conf="/etc/default/init"
+ locale_conf_group="sys"
+
+ if version_ge 5.11 <"${__global}/explorer/os_version"
+ then
+ # mode on Oracle Solaris 11 is actually 0444,
+ # but the write bit makes sense, IMO
+ locale_conf_mode=0644
+
+ # Oracle Solaris 11.2 and later uses SMF to store environment info.
+ # This is a hack, but I didn't feel like modifying the whole type
+ # just for some Oracle nonsense.
+ # 11.3 apparently added nlsadm(1m), but it is missing from 11.2.
+ # Illumos continues to use /etc/default/init
+ # NOTE: Remember not to use "cool" POSIX features like -q or -e with
+ # Solaris grep.
+ release_regex='Oracle Solaris 11.[2-9][0-9]*'
+ case $state_should
+ in
+ (present)
+ svccfg_cmd="svccfg -s svc:/system/environment:init setprop environment/${key} = astring: '$(cat "${__object}/parameter/value")'"
+ ;;
+ (absent)
+ svccfg_cmd="svccfg -s svc:/system/environment:init delprop environment/${key}"
+ ;;
+ esac
+ refresh_cmd='svcadm refresh svc:/system/environment'
+ onchange_cmd="grep '${release_regex}' /etc/release >&- || exit 0; ${svccfg_cmd:-:} && ${refresh_cmd}"
+ else
+ locale_conf_mode=0555
+ fi
+ ;;
+ slackware)
+ # NOTE: lang.csh (csh config) is ignored here.
+ locale_conf="/etc/profile.d/lang.sh"
+ locale_conf_mode=0755
+ key="export ${__object_id}"
+ ;;
+ 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,\}\)')
+
+ # https://documentation.suse.com/sles/15-SP2/html/SLES-all/cha-suse.html#sec-suse-l10n
+ if expr "${os_major}" '>=' 15 \& "${os_major}" != 42
+ then
+ # It seems that starting with SuSE 15 the systemd /etc/locale.conf
+ # is the preferred way to set locales, although
+ # /etc/sysconfig/language is still available.
+ # Older documentation doesn't mention /etc/locale.conf, even though
+ # is it created when localectl is used.
+ locale_conf="/etc/locale.conf"
+ else
+ locale_conf="/etc/sysconfig/language"
+ quote_value=true
+ key="RC_${__object_id}"
+ fi
+ ;;
+ voidlinux)
+ locale_conf="/etc/locale.conf"
;;
*)
echo "Your operating system ($os) is currently not supported by this type (${__type##*/})." >&2
@@ -42,14 +197,16 @@ case "$os" in
;;
esac
-__file "$locale_conf" \
- --owner root --group root --mode 644 \
- --state exists
+__file "${locale_conf}" --state exists \
+ --owner "${locale_conf_owner:-0}" \
+ --group "${locale_conf_group:-0}" \
+ --mode "${locale_conf_mode:-0644}"
-require="__file/$locale_conf" \
- __key_value "$locale_conf:$__object_id" \
- --file "$locale_conf" \
- --key "$__object_id" \
- --delimiter = \
- --state "$(cat "$__object/parameter/state")" \
- --value "$(cat "$__object/parameter/value")"
+require="__file/${locale_conf}" \
+__key_value "${locale_conf}:${key#export }" \
+ --file "${locale_conf}" \
+ --key "${key}" \
+ --delimiter '=' --exact_delimiter \
+ --state "${state_should}" \
+ --value "${value:-$(catval "${__object}/parameter/value")}" \
+ --onchange "${onchange_cmd}"
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/__matterbridge/files/matterbridge.service.sh b/cdist/conf/type/__matterbridge/files/matterbridge.service.sh
deleted file mode 100755
index 9dbd1cb6..00000000
--- a/cdist/conf/type/__matterbridge/files/matterbridge.service.sh
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/sh
-
-cat <`_
-
-
-AUTHORS
--------
-Timothée Floure
-
-
-COPYING
--------
-Copyright \(C) 2020 Timothée Floure. 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/__matterbridge/manifest b/cdist/conf/type/__matterbridge/manifest
deleted file mode 100755
index 56f470a0..00000000
--- a/cdist/conf/type/__matterbridge/manifest
+++ /dev/null
@@ -1,101 +0,0 @@
-#!/bin/sh -e
-#
-# 2020 Timothée Floure (timothee.floure@ungleich.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")
-case "$os" in
- debian)
- # This type assume systemd for service installation.
- ;;
- *)
- 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
-
-# Required parameters.
-VERSION=$(cat "$__object/parameter/version")
-if [ -f "$__object/parameter/config" ]; then
- CONFIG="$(cat "$__object/parameter/config")"
- if [ "$CONFIG" = "-" ]; then
- CONFIG=$(cat "$__object/stdin")
- fi
-fi
-
-# Hardcoded values used in templates.
-export BINARY_PATH=/usr/local/bin/matterbridge
-export CONFIG_PATH=/etc/matterbridge/matterbridge.toml
-export USER=matterbridge
-export GROUP=$USER
-
-# Internal variables.
-artefact="matterbridge-$VERSION-linux-64bit"
-checksum_file="checksums.txt"
-release_download_url=https://github.com/42wim/matterbridge/releases/download
-binary_url="$release_download_url/v$VERSION/$artefact"
-checksum_file_url="$release_download_url/v$VERSION/$checksum_file"
-config_dir=$(dirname $CONFIG_PATH)
-systemd_unit_path='/etc/systemd/system/matterbridge.service'
-
-# Check if curl is available.
-if [ ! -x "$(which curl)" ]; then
- echo "curl is required for this type, but could not be found. Exiting." &>2
- exit 1
-fi
-
-# Initialize working directory.
-mkdir -p "$__object/files"
-
-# Download and check matterbridge binary.
-curl -L $binary_url -o "$__object/files/$artefact"
-curl -Ls $checksum_file_url | grep $artefact > "$__object/files/$checksum_file"
-ls $__object/files/ >&2
-cat $__object/files/checksums.txt >&2
-(cd "$__object/files"; sha256sum --check $checksum_file)
-if [ $? -ne 0 ]; then
- echo "Matterbridge binary checksum failed." >&2
- exit 1
-fi
-
-# Create service user.
-__user $USER --home "/var/lib/$USER"
-
-# Deploy matterbridge binary.
-require="__user/$USER" __file "$BINARY_PATH" \
- --source "$__object/files/$artefact" \
- --owner "$USER" --mode 755
-
-# Generate and deploy configuration file.
-"$__type/files/matterbridge.service.sh" > "$__object/files/matterbridge.service"
-
-require="__user/$USER" __directory "$config_dir" \
- --owner "$USER" --mode 0755 --parents \
-
-require="__directory/$config_dir" __file "$CONFIG_PATH" \
- --owner "$USER" \
- --mode 0640 \
- --source "$CONFIG"
-
-__file "$systemd_unit_path" \
- --source "$__object/files/matterbridge.service"
-
-# Deal with init system.
-require="__file/$systemd_unit_path" __start_on_boot matterbridge
-require="__file/$BINARY_PATH __file/$CONFIG_PATH __file/$systemd_unit_path" __service matterbridge --action restart
diff --git a/cdist/conf/type/__matterbridge/parameter/required b/cdist/conf/type/__matterbridge/parameter/required
deleted file mode 100644
index ed5d8b33..00000000
--- a/cdist/conf/type/__matterbridge/parameter/required
+++ /dev/null
@@ -1,2 +0,0 @@
-version
-config
diff --git a/cdist/conf/type/__motd/gencode-remote b/cdist/conf/type/__motd/gencode-remote
index bc842cc8..cb7bfc84 100755
--- a/cdist/conf/type/__motd/gencode-remote
+++ b/cdist/conf/type/__motd/gencode-remote
@@ -22,14 +22,18 @@
os=$(cat "$__global/explorer/os")
case "$os" in
- debian|ubuntu|devuan)
-
- # Debian and Ubuntu need to be updated,
- # as seen in /etc/init.d/bootlogs
- echo "uname -snrvm > /var/run/motd"
- echo "cat /etc/motd.tail >> /var/run/motd"
+ freebsd)
+ # FreeBSD only updates /etc/motd on boot,
+ # as seen in /etc/rc.d/motd
+ echo "uname -sri > /etc/motd"
+ echo "cat /etc/motd.template >> /etc/motd"
+ # FreeBSD 13 starts treating motd slightly different from previous
+ # versions this ensures hosts have the expected config.
+ echo "rm /etc/motd.template || true"
+ echo "service motd start"
;;
*)
+ # Other OS tend to treat /etc/motd statically
exit 0
;;
esac
diff --git a/cdist/conf/type/__motd/man.rst b/cdist/conf/type/__motd/man.rst
index 17369684..a567dc80 100644
--- a/cdist/conf/type/__motd/man.rst
+++ b/cdist/conf/type/__motd/man.rst
@@ -10,6 +10,13 @@ DESCRIPTION
-----------
This cdist type allows you to easily setup /etc/motd.
+.. note::
+ In some OS, motd is a bit special, check `motd(5)`.
+ Currently Debian, Devuan, Ubuntu and FreeBSD are taken into account.
+ If your OS of choice does something besides /etc/motd, check the source
+ and contribute support for it.
+ Otherwise it will likely just work.
+
REQUIRED PARAMETERS
-------------------
@@ -20,6 +27,7 @@ OPTIONAL PARAMETERS
-------------------
source
If supplied, copy this file from the host running cdist to the target.
+ If source is '-' (dash), take what was written to stdin as the file content.
If not supplied, a default message will be placed onto the target.
@@ -34,6 +42,15 @@ EXAMPLES
# Supply source file from a different type
__motd --source "$__type/files/my-motd"
+ # Supply source from stdin
+ __motd --source "-" <
COPYING
-------
-Copyright \(C) 2011 Nico Schottelius. You can redistribute it
+Copyright \(C) 2020 Nico Schottelius. 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/__motd/manifest b/cdist/conf/type/__motd/manifest
index cd741cf4..b8f74ebf 100755
--- a/cdist/conf/type/__motd/manifest
+++ b/cdist/conf/type/__motd/manifest
@@ -33,10 +33,14 @@ os=$(cat "$__global/explorer/os")
case "$os" in
- debian|ubuntu|devuan)
- destination=/etc/motd.tail
+ freebsd)
+ # FreeBSD uses motd.template to prepend system information on boot
+ # (this actually only applies starting with version 13,
+ # but we fix that for whatever version in gencode-remote)
+ destination=/etc/motd.template
;;
*)
+ # Most UNIX systems, including other Linux and OpenBSD just use /etc/motd
destination=/etc/motd
;;
esac
diff --git a/cdist/conf/type/__openldap_server/man.rst b/cdist/conf/type/__openldap_server/man.rst
index d20101d1..fa714ec0 100644
--- a/cdist/conf/type/__openldap_server/man.rst
+++ b/cdist/conf/type/__openldap_server/man.rst
@@ -31,8 +31,8 @@ manager-password-hash
Generate e.g. with: `slappasswd -s weneedgoodsecurity`.
See `slappasswd(8C)`, `slapd.conf(5)`.
TODO: implement this: http://blog.adamsbros.org/2015/06/09/openldap-ssha-salted-hashes-by-hand/
- to derive from the manager-password parameter and ensure idempotency (care with salts).
- At that point, manager-password-hash should be deprecated and ignored.
+ to derive from the manager-password parameter and ensure idempotency (care with salts).
+ At that point, manager-password-hash should be deprecated and ignored.
serverid
The server for the directory.
@@ -92,6 +92,9 @@ tls-ca
Required if `tls-cert` is defined.
Path in the remote hosts to the PEM-encoded CA certificate file.
+extra-config
+ Custom settings to be added in `slapd.conf(5)`.
+
OPTIONAL MULTIPLE PARAMETERS
----------------------------
@@ -100,8 +103,8 @@ syncrepl-host
Set once per host that will replicate the directory.
module
- LDAP module to load. See `slapd.conf(5)`.
- Default value is OS-dependent, see manifest.
+ LDAP module to load. See `slapd.conf(5)`. Some dependencies might have to
+ be installed beforehand. Default value is OS-dependent, see manifest.
schema
Name of LDAP schema to load. Must be the name without extension of a
diff --git a/cdist/conf/type/__openldap_server/manifest b/cdist/conf/type/__openldap_server/manifest
index dadc9f20..2aeece26 100644
--- a/cdist/conf/type/__openldap_server/manifest
+++ b/cdist/conf/type/__openldap_server/manifest
@@ -9,6 +9,7 @@ slapd_modules=$(cat "${__object}/parameter/module" 2>/dev/null || true)
schemas=$(cat "${__object}/parameter/schema")
slapd_urls=$(tr '\n' ' ' < "${__object}/parameter/slapd-url")
tls_cipher_suite=$(cat "${__object}/parameter/tls-cipher-suite" 2>/dev/null || true)
+extra_config=$(cat "${__object}/parameter/extra-config" || true)
os="$(cat "${__global}/explorer/os")"
@@ -24,6 +25,7 @@ case "${os}" in
SLAPD_DATA_DIR="/var/db/openldap-data"
SLAPD_RUN_DIR="/var/run/openldap"
SLAPD_MODULE_PATH="/usr/local/libexec/openldap"
+ SLAPD_MODULE_TYPE="la"
if [ -z "${slapd_modules}" ]; then
# It looks like ppolicy and syncprov must be compiled
slapd_modules="back_mdb back_monitor"
@@ -42,13 +44,34 @@ case "${os}" in
SLAPD_DATA_DIR="/var/lib/ldap"
SLAPD_RUN_DIR="/var/run/slapd"
SLAPD_MODULE_PATH="/usr/lib/ldap"
+ SLAPD_MODULE_TYPE="la"
if [ -z "${slapd_modules}" ]; then
slapd_modules="back_mdb ppolicy syncprov back_monitor"
fi
+ CONF_OWNER="openldap"
+ CONF_GROUP="openldap"
if [ -z "${tls_cipher_suite}" ]; then
tls_cipher_suite="NORMAL"
fi
;;
+ alpine)
+ PKGS="openldap openldap-clients"
+ ETC="/etc"
+ SLAPD_DIR="/etc/openldap"
+ SLAPD_DATA_DIR="/var/lib/openldap"
+ SLAPD_RUN_DIR="/var/run/openldap"
+ SLAPD_MODULE_PATH="/usr/lib/openldap"
+ SLAPD_MODULE_TYPE="so"
+ if [ -z "${slapd_modules}" ]; then
+ slapd_modules="back_mdb ppolicy syncprov back_monitor"
+ PKGS="$PKGS openldap-back-mdb openldap-back-monitor openldap-overlay-all"
+ fi
+ CONF_OWNER="ldap"
+ CONF_GROUP="$SLAPD_USER"
+ if [ -z "${tls_cipher_suite}" ]; then
+ tls_cipher_suite="DEFAULT"
+ fi
+ ;;
*)
echo "Don't know the openldap defaults for: $os" >&2
exit 1
@@ -155,6 +178,12 @@ case "${os}" in
--line "SLAPD_SERVICES=\"${slapd_urls}\"" \
--state present
;;
+ alpine)
+ require="__package/${PKG_MAIN}" __line add_slapd_services \
+ --file ${ETC}/conf.d/slapd \
+ --line "command_args=\"-h '${slapd_urls}'\"" \
+ --state present
+ ;;
*)
# Nothing to do here, move on.
;;
@@ -168,20 +197,23 @@ if [ -z "${_skip_letsencrypt_cert}" ]; then
staging=""
fi
- __letsencrypt_cert "${name}" --admin-email "${admin_email}" \
- --renew-hook "cp ${ETC}/letsencrypt/live/${name}/*.pem ${SLAPD_DIR}/sasl2 && chown -R openldap:openldap ${SLAPD_DIR}/sasl2 && service slapd restart" \
- --automatic-renewal ${staging}
+ # shellcheck disable=SC2086
+ __directory ${SLAPD_DIR}/sasl2
+ require="__directory/${SLAPD_DIR}/sasl2" __letsencrypt_cert "${name}" \
+ --admin-email "${admin_email}" \
+ --renew-hook "cp ${ETC}/letsencrypt/live/${name}/*.pem ${SLAPD_DIR}/sasl2 && chown -R ${CONF_OWNER}:${CONF_GROUP} ${SLAPD_DIR}/sasl2 && service slapd restart" \
+ --automatic-renewal "${staging}"
fi
require="__package/${PKG_MAIN}" __directory ${SLAPD_DIR}/slapd.d --state absent
if [ -z "${_skip_letsencrypt_cert}" ]; then
require="__package/${PKG_MAIN} __letsencrypt_cert/${name}" \
- __file ${SLAPD_DIR}/slapd.conf --owner ${CONF_OWNER} --group ${CONF_GROUP} --mode 644 \
+ __file "${SLAPD_DIR}/slapd.conf" --owner "${CONF_OWNER}" --group "${CONF_GROUP}" --mode 644 \
--source "${ldapconf}"
else
require="__package/${PKG_MAIN}" \
- __file ${SLAPD_DIR}/slapd.conf --owner ${CONF_OWNER} --group ${CONF_GROUP} --mode 644 \
+ __file "${SLAPD_DIR}/slapd.conf" --owner "${CONF_OWNER}" --group "${CONF_GROUP}" --mode 644 \
--source "${ldapconf}"
fi
@@ -208,7 +240,7 @@ done
# Add specified modules
echo "modulepath ${SLAPD_MODULE_PATH}" >> "${ldapconf}"
for module in ${slapd_modules}; do
- echo "moduleload ${module}.la" >> "${ldapconf}"
+ echo "moduleload ${module}.${SLAPD_MODULE_TYPE}" >> "${ldapconf}"
done
# Rest of the config
@@ -230,6 +262,8 @@ index uid,memberUid eq,pres,sub
index nisMapName,nisMapEntry eq,pres,sub
index entryCSN,entryUUID eq
+${extra_config}
+
serverid ${serverid}
EOF
diff --git a/cdist/conf/type/__openldap_server/parameter/optional b/cdist/conf/type/__openldap_server/parameter/optional
index a92b9c6e..71c64659 100644
--- a/cdist/conf/type/__openldap_server/parameter/optional
+++ b/cdist/conf/type/__openldap_server/parameter/optional
@@ -5,4 +5,5 @@ admin-email
tls-cipher-suite
tls-cert
tls-privkey
-tls-ca
\ No newline at end of file
+tls-ca
+extra-config
diff --git a/cdist/conf/type/__package_apt/gencode-remote b/cdist/conf/type/__package_apt/gencode-remote
index e02564a2..fbfca330 100755
--- a/cdist/conf/type/__package_apt/gencode-remote
+++ b/cdist/conf/type/__package_apt/gencode-remote
@@ -42,6 +42,13 @@ else
target_release=""
fi
+if [ -f "$__object/parameter/install-recommends" ]; then
+ # required if __apt_norecommends is used
+ recommendsparam="-o APT::Install-Recommends=1"
+else
+ recommendsparam="-o APT::Install-Recommends=0"
+fi
+
if [ -f "$__object/parameter/purge-if-absent" ]; then
purgeparam="--purge"
else
@@ -62,16 +69,16 @@ case "$state_is" in
;;
esac
-# Hint if we need to avoid questions at some point:
-# DEBIAN_PRIORITY=critical can reduce the number of questions
-aptget="DEBIAN_FRONTEND=noninteractive apt-get --quiet --yes --no-install-recommends -o Dpkg::Options::=\"--force-confdef\" -o Dpkg::Options::=\"--force-confold\""
-
if [ "$state_is" = "$state_should" ]; then
if [ -z "$version" ] || [ "$version" = "$version_is" ]; then
exit 0;
fi
fi
+# Hint if we need to avoid questions at some point:
+# DEBIAN_PRIORITY=critical can reduce the number of questions
+aptget="DEBIAN_FRONTEND=noninteractive apt-get --quiet --yes -o Dpkg::Options::=\"--force-confdef\" -o Dpkg::Options::=\"--force-confold\""
+
case "$state_should" in
present)
# following is bit ugly, but important hack.
@@ -85,7 +92,7 @@ EOF
if [ -n "$version" ]; then
name="${name}=${version}"
fi
- echo "$aptget install $target_release '$name'"
+ echo "$aptget $recommendsparam install $target_release '$name'"
echo "installed" >> "$__messages_out"
;;
absent)
diff --git a/cdist/conf/type/__package_apt/man.rst b/cdist/conf/type/__package_apt/man.rst
index a1691eac..4e6101a5 100644
--- a/cdist/conf/type/__package_apt/man.rst
+++ b/cdist/conf/type/__package_apt/man.rst
@@ -9,7 +9,9 @@ cdist-type__package_apt - Manage packages with apt-get
DESCRIPTION
-----------
apt-get is usually used on Debian and variants (like Ubuntu) to
-manage packages.
+manage packages. The package will be installed without recommended
+or suggested packages. If such packages are required, install them
+separatly or use the parameter ``--install-recommends``.
This type will also update package index, if it is older
than one day, to avoid missing package error messages.
@@ -23,7 +25,7 @@ None
OPTIONAL PARAMETERS
-------------------
name
- If supplied, use the name and not the object id as the package name.
+ If supplied, use the name and not the object id as the package name.
state
Either "present" or "absent", defaults to "present"
@@ -39,6 +41,15 @@ version
BOOLEAN PARAMETERS
------------------
+install-recommends
+ If the package will be installed, it also installs recommended packages
+ with it. It will not install recommended packages if the original package
+ is already installed.
+
+ In most cases, it is recommended to install recommended packages separatly
+ to control which additional packages will be installed to avoid useless
+ installed packages.
+
purge-if-absent
If this parameter is given when state is `absent`, the package is
purged from the system (using `--purge`).
diff --git a/cdist/conf/type/__package_apt/parameter/boolean b/cdist/conf/type/__package_apt/parameter/boolean
index f9a0f6b0..a2e433f3 100644
--- a/cdist/conf/type/__package_apt/parameter/boolean
+++ b/cdist/conf/type/__package_apt/parameter/boolean
@@ -1 +1,2 @@
+install-recommends
purge-if-absent
diff --git a/cdist/conf/type/__package_opkg/explorer/pkg_status b/cdist/conf/type/__package_opkg/explorer/pkg_status
index 5da4f742..de7b896b 100755
--- a/cdist/conf/type/__package_opkg/explorer/pkg_status
+++ b/cdist/conf/type/__package_opkg/explorer/pkg_status
@@ -1,7 +1,8 @@
-#!/bin/sh
+#!/bin/sh -e
#
# 2011 Nico Schottelius (nico-cdist at schottelius.org)
# 2012 Giel van Schijndel (giel plus cdist at mortis dot eu)
+# 2020 Dennis Camera (dennis.camera at ssrq-sds-fds.ch)
#
# This file is part of cdist.
#
@@ -19,21 +20,78 @@
# along with cdist. If not, see .
#
#
-# Retrieve the status of a package - parsed opkg output
+# Retrieve the status of a package - parses opkg output
#
-if [ -f "$__object/parameter/name" ]; then
- name="$(cat "$__object/parameter/name")"
+readonly __type_path=${__object%%${__object_id}*}
+test -d "${__type_path}" || { echo 'Cannot determine __type_path' >&2; exit 1; }
+readonly LOCKFILE="${__type_path:?}/.cdist_opkg.lock"
+
+if command -v flock >/dev/null 2>&1
+then
+ # use flock (if available) on FD 9
+ _lock() {
+ exec 9<>"${LOCKFILE:?}"
+ flock -x 9
+ echo $$>&9
+ }
+ _unlock() {
+ :>"${LOCKFILE:?}"
+ flock -u 9
+ exec 9<&-
+ }
else
- name="$__object_id"
+ # fallback to mkdir if flock is missing
+ _lock() {
+ until mkdir "${LOCKFILE:?}.dir" 2>/dev/null
+ do
+ while test -d "${LOCKFILE}.dir"
+ do
+ # DEBUG:
+ # printf 'Locked by PID: %u\n' "$(cat "${LOCKFILE}.dir/pid")"
+ sleep 1
+ done
+ done
+ echo $$ >"${LOCKFILE:?}.dir/pid"
+ }
+ _unlock() {
+ test -d "${LOCKFILE}.dir" || return 0
+ if test -s "${LOCKFILE}.dir/pid"
+ then
+ test "$(cat "${LOCKFILE}.dir/pid")" = $$ || return 1
+ rm "${LOCKFILE:?}.dir/pid"
+ fi
+ rmdir "${LOCKFILE:?}.dir"
+ }
fi
-# Except dpkg failing, if package is not known / installed
-if opkg status "$name" 2>/dev/null | grep -q "^Status: install user installed$"; then
- echo "present"
- exit 0
-elif [ "$(opkg info "$name" 2> /dev/null | wc -l)" -eq 0 ]; then
- echo "absent notpresent"
- exit 0
+
+if test -f "${__object}/parameter/name"
+then
+ pkg_name=$(cat "${__object}/parameter/name")
+else
+ pkg_name=$__object_id
+fi
+
+
+# NOTE: We need to lock parallel execution of type explorers and code-remote
+# because opkg will try to acquire the OPKG lock (usually /var/lock/opkg.lock)
+# using lockf(2) for every operation.
+# It will not wait for the lock but terminate with an error.
+# This leads to incorrect 'absent notpresent' statuses when parallel execution
+# is enabled.
+trap _unlock EXIT
+_lock
+
+
+# Except opkg failing, if package is not known / installed
+if opkg status "${pkg_name}" 2>/dev/null \
+ | grep -q -e '^Status: [^ ][^ ]* [^ ][^ ]* installed$'
+then
+ echo 'present'
+elif opkg info "${pkg_name}" 2>/dev/null | grep -q .
+then
+ echo 'absent notpresent'
+else
+ echo 'absent'
fi
-echo "absent"
diff --git a/cdist/conf/type/__package_opkg/gencode-remote b/cdist/conf/type/__package_opkg/gencode-remote
index 269d5f49..28caff71 100755
--- a/cdist/conf/type/__package_opkg/gencode-remote
+++ b/cdist/conf/type/__package_opkg/gencode-remote
@@ -2,6 +2,7 @@
#
# 2011,2013 Nico Schottelius (nico-cdist at schottelius.org)
# 2012 Giel van Schijndel (giel plus cdist at mortis dot eu)
+# 2020 Dennis Camera (dennis.camera at ssrq-sds-fds.ch)
#
# This file is part of cdist.
#
@@ -19,41 +20,50 @@
# along with cdist. If not, see .
#
#
-# Manage packages on OpenWRT and co.
+# Manage packages on OpenWrt, optware, and co.
#
-if [ -f "$__object/parameter/name" ]; then
- name="$(cat "$__object/parameter/name")"
+if test -f "${__object}/parameter/name"
+then
+ name=$(cat "${__object}/parameter/name")
else
- name="$__object_id"
+ name=$__object_id
fi
-state_should="$(cat "$__object/parameter/state")"
+state_should=$(cat "${__object}/parameter/state")
+state_is=$(cat "${__object}/explorer/pkg_status")
-state_is="$(cat "$__object/explorer/pkg_status")"
-case "$state_is" in
- absent*)
- present="$(echo "$state_is" | cut -d ' ' -f 2)"
- state_is="absent"
- ;;
+case $state_is
+in
+ (absent*)
+ presence=$(echo "${state_is}" | cut -d ' ' -f 2)
+ state_is='absent'
+ ;;
esac
-[ "$state_is" = "$state_should" ] && exit 0
+if test "${state_is}" = "${state_should}"
+then
+ exit 0
+fi
-case "$state_should" in
- present)
- if [ "$present" = "notpresent" ]; then
- echo "opkg --verbosity=0 update"
- fi
- echo "opkg --verbosity=0 install '$name'"
- echo "installed" >> "$__messages_out"
- ;;
- absent)
- echo "opkg --verbosity=0 remove '$name'"
- echo "removed" >> "$__messages_out"
- ;;
- *)
- echo "Unknown state: ${state_should}" >&2
- exit 1
- ;;
+
+case $state_should
+in
+ (present)
+ if test "${presence}" = 'notpresent'
+ then
+ echo 'opkg --verbosity=0 update'
+ fi
+
+ printf "opkg --verbosity=0 install '%s'\n" "${name}"
+ echo 'installed' >>"${__messages_out}"
+ ;;
+ (absent)
+ printf "opkg --verbosity=0 remove '%s'" "${name}"
+ echo 'removed' >>"${__messages_out}"
+ ;;
+ (*)
+ printf 'Unknown state: %s\n' "${state_should}" >&2
+ exit 1
+ ;;
esac
diff --git a/cdist/conf/type/__package_pip/explorer/pip b/cdist/conf/type/__package_pip/explorer/pip
new file mode 100755
index 00000000..cf9fae89
--- /dev/null
+++ b/cdist/conf/type/__package_pip/explorer/pip
@@ -0,0 +1,10 @@
+#!/bin/sh -e
+
+for bin in pip3 pip
+do
+ if check="$( command -v "$bin" )"
+ then
+ echo "$check"
+ break
+ fi
+done
diff --git a/cdist/conf/type/__package_pip/explorer/state b/cdist/conf/type/__package_pip/explorer/state
index 5be07280..3cc98ab9 100644
--- a/cdist/conf/type/__package_pip/explorer/state
+++ b/cdist/conf/type/__package_pip/explorer/state
@@ -32,7 +32,7 @@ pipparam="$__object/parameter/pip"
if [ -f "$pipparam" ]; then
pip=$(cat "$pipparam")
else
- pip="pip"
+ pip="$( "$__type_explorer/pip" )"
fi
# If there is no pip, it may get created from somebody else.
diff --git a/cdist/conf/type/__package_pip/gencode-remote b/cdist/conf/type/__package_pip/gencode-remote
index dcc4fdf9..a1375c2d 100755
--- a/cdist/conf/type/__package_pip/gencode-remote
+++ b/cdist/conf/type/__package_pip/gencode-remote
@@ -38,7 +38,12 @@ pipparam="$__object/parameter/pip"
if [ -f "$pipparam" ]; then
pip=$(cat "$pipparam")
else
- pip="pip"
+ pip="$( cat "$__object/explorer/pip" )"
+ if [ -z "$pip" ]
+ then
+ echo 'pip not found in path' >&2
+ exit 1
+ fi
fi
runasparam="$__object/parameter/runas"
@@ -55,7 +60,7 @@ case "$state_should" in
then
echo "su -c '$pip install -q $name' $runas"
else
- echo $pip install -q "$name"
+ echo "$pip" install -q "$name"
fi
echo "installed" >> "$__messages_out"
;;
@@ -64,7 +69,7 @@ case "$state_should" in
then
echo "su -c '$pip uninstall -q -y $name' $runas"
else
- echo $pip uninstall -q -y "$name"
+ echo "$pip" uninstall -q -y "$name"
fi
echo "removed" >> "$__messages_out"
;;
diff --git a/cdist/conf/type/__package_pkgng_freebsd/explorer/pkg_bootstrapped b/cdist/conf/type/__package_pkgng_freebsd/explorer/pkg_bootstrapped
new file mode 100755
index 00000000..429f15d3
--- /dev/null
+++ b/cdist/conf/type/__package_pkgng_freebsd/explorer/pkg_bootstrapped
@@ -0,0 +1,4 @@
+#!/bin/sh -e
+if pkg -N >/dev/null 2>&1; then
+ echo "YES"
+fi
diff --git a/cdist/conf/type/__package_pkgng_freebsd/explorer/pkg_version b/cdist/conf/type/__package_pkgng_freebsd/explorer/pkg_version
index 92ce0623..1c6ba5e5 100755
--- a/cdist/conf/type/__package_pkgng_freebsd/explorer/pkg_version
+++ b/cdist/conf/type/__package_pkgng_freebsd/explorer/pkg_version
@@ -18,9 +18,14 @@
# along with cdist. If not, see .
#
#
-# Retrieve the status of a package - parsed dpkg output
+# Retrieve the status of a package - parsed pkgng output
#
+if ! pkg -N >/dev/null 2>&1; then
+ # Nothing to do if pkg is not bootstrapped
+ exit
+fi
+
if [ -f "$__object/parameter/name" ]; then
name="$(cat "$__object/parameter/name")"
else
diff --git a/cdist/conf/type/__package_pkgng_freebsd/gencode-remote b/cdist/conf/type/__package_pkgng_freebsd/gencode-remote
index dd36efda..05ba4cb2 100755
--- a/cdist/conf/type/__package_pkgng_freebsd/gencode-remote
+++ b/cdist/conf/type/__package_pkgng_freebsd/gencode-remote
@@ -43,6 +43,7 @@ fi
repo="$(cat "$__object/parameter/repo")"
state="$(cat "$__object/parameter/state")"
curr_version="$(cat "$__object/explorer/pkg_version")"
+pkg_bootstrapped="$(cat "$__object/explorer/pkg_bootstrapped")"
add_cmd="pkg install -y"
rm_cmd="pkg delete -y"
upg_cmd="pkg upgrade -y"
@@ -73,6 +74,10 @@ execcmd(){
;;
esac
+ if [ -z "${pkg_bootstrapped}" ]; then
+ echo "ASSUME_ALWAYS_YES=yes pkg bootstrap >/dev/null 2>&1"
+ fi
+
echo "$_cmd >/dev/null 2>&1" # Silence the output of the command
echo "status=\$?"
echo "if [ \"\$status\" -ne \"0\" ]; then"
diff --git a/cdist/conf/type/__pf_apply/gencode-remote b/cdist/conf/type/__pf_apply/gencode-remote
deleted file mode 100755
index c8f7a25a..00000000
--- a/cdist/conf/type/__pf_apply/gencode-remote
+++ /dev/null
@@ -1,51 +0,0 @@
-#!/bin/sh -e
-#
-# 2012 Jake Guffey (jake.guffey at eprotex.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 .
-#
-#
-# Apply pf(4) ruleset on *BSD
-#
-
-# Debug
-#exec >&2
-#set -x
-
-rcvar=$(cat "$__object/explorer/rcvar")
-
-cat <&2
- fi
-fi
-EOF
-
-# Debug
-#set +x
-
diff --git a/cdist/conf/type/__pf_apply/man.rst b/cdist/conf/type/__pf_apply/man.rst
deleted file mode 100644
index eee345e7..00000000
--- a/cdist/conf/type/__pf_apply/man.rst
+++ /dev/null
@@ -1,55 +0,0 @@
-cdist-type__pf_apply(7)
-=======================
-
-NAME
-----
-cdist-type__pf_apply - Apply pf(4) ruleset on \*BSD
-
-
-DESCRIPTION
------------
-This type is used on \*BSD systems to manage the pf firewall's active ruleset.
-
-
-REQUIRED PARAMETERS
--------------------
-NONE
-
-
-OPTIONAL PARAMETERS
--------------------
-NONE
-
-
-EXAMPLES
---------
-
-.. code-block:: sh
-
- # Modify the ruleset on $__target_host:
- __pf_ruleset --state present --source /my/pf/ruleset.conf
- require="__pf_ruleset" \
- __pf_apply
-
- # Remove the ruleset on $__target_host (implies disabling pf(4):
- __pf_ruleset --state absent
- require="__pf_ruleset" \
- __pf_apply
-
-
-SEE ALSO
---------
-:strong:`pf`\ (4), :strong:`cdist-type__pf_ruleset`\ (7)
-
-
-AUTHORS
--------
-Jake Guffey
-
-
-COPYING
--------
-Copyright \(C) 2012 Jake Guffey. 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/__pf_apply_anchor/gencode-remote b/cdist/conf/type/__pf_apply_anchor/gencode-remote
new file mode 100755
index 00000000..36c26521
--- /dev/null
+++ b/cdist/conf/type/__pf_apply_anchor/gencode-remote
@@ -0,0 +1,33 @@
+#!/bin/sh -e
+#
+# 2016 Kamila Součková (coding at kamila.is)
+#
+# 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 .
+#
+#
+# Apply pf(4) ruleset on *BSD
+#
+
+ANCHORS_DIR="/etc/pf.d"
+
+if [ -f "${__object}/parameter/anchor_name" ]; then
+ anchor_name="$(cat "${__object}/parameter/anchor_name")"
+else
+ anchor_name="${__object_id}"
+fi
+anchor_file="${ANCHORS_DIR}/${anchor_name}"
+
+echo "pfctl -a \"${anchor_name}\" -f \"${anchor_file}\""
diff --git a/cdist/conf/type/__pf_apply_anchor/man.rst b/cdist/conf/type/__pf_apply_anchor/man.rst
new file mode 100644
index 00000000..aef6cdf4
--- /dev/null
+++ b/cdist/conf/type/__pf_apply_anchor/man.rst
@@ -0,0 +1,62 @@
+cdist-type__pf_apply_anchor(7)
+==============================
+
+NAME
+----
+cdist-type__pf_apply_anchor - Apply a pf(4) anchor on $__target_host
+
+
+DESCRIPTION
+-----------
+This type is used on \*BSD systems to manage anchors for the pf firewall.
+
+Notice this type does not take care of copying the ruleset, that must be
+done by the user with, e.g. `__file`.
+
+
+OPTIONAL PARAMETERS
+-------------------
+anchor_name
+ The name of the anchor to apply. If not set, `${__object_id}` is used.
+ This type requires `/etc/pf.d/${anchor_name}` to exist on
+ `$__target_host`.
+
+
+EXAMPLES
+--------
+
+.. code-block:: sh
+
+ # Copy anchor file to ${__target_host}
+ __file "/etc/pf.d/80_dns" --source - <
+Kamila Součková
+Jake Guffey
+
+
+COPYING
+-------
+Copyright \(C) 2020 Evilham.
+Copyright \(C) 2016 Kamila Součková.
+Copyright \(C) 2012 Jake Guffey. 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/__pf_apply_anchor/parameter/optional b/cdist/conf/type/__pf_apply_anchor/parameter/optional
new file mode 100644
index 00000000..b9f61e28
--- /dev/null
+++ b/cdist/conf/type/__pf_apply_anchor/parameter/optional
@@ -0,0 +1 @@
+anchor_name
diff --git a/cdist/conf/type/__pf_ruleset/gencode-local b/cdist/conf/type/__pf_ruleset/gencode-local
deleted file mode 100755
index 11bfb0b1..00000000
--- a/cdist/conf/type/__pf_ruleset/gencode-local
+++ /dev/null
@@ -1,81 +0,0 @@
-#!/bin/sh -e
-#
-# 2012 Jake Guffey (jake.guffey at eprotex.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 .
-#
-#
-# Manage pf(4) on *BSD
-#
-
-# Debug
-#exec >&2
-#set -x
-
-# Send files to $__target_host via $__remote_copy
-
-uname=$(uname) # Need to know what the cdist host is running so we know how to compute the ruleset's checksum
-state=$(cat "$__object/parameter/state")
-
-if [ "$state" = "absent" ]; then # There is nothing more for a *local* script to do
- exit 0
-fi
-
-if [ -f "$__object/parameter/source" ]; then
- source=$(cat "$__object/parameter/source")
-fi
-
-rcvar=$(cat "$__object/explorer/rcvar")
-cksum=$(cat "$__object/explorer/cksum")
-
-
-cat <&2
- exit 1
- ;;
-esac
-
-# IPv6 fix
-if $(echo "${__target_host}" | grep -q -E '^[0-9a-fA-F:]+$')
-then
- my_target_host="[${__target_host}]"
-else
- my_target_host="${__target_host}"
-fi
-
-if [ -n "${cksum}" ]; then
- if [ ! "\${currentSum}" = "${cksum}" ]; then
- $__remote_copy "${source}" "\${my_target_host}:${rcvar}.new"
- fi
-else # File just doesn't exist yet
- $__remote_copy "${source}" "\${my_target_host}:${rcvar}.new"
-fi
-EOF
-
-# Debug
-#exec +x
-
diff --git a/cdist/conf/type/__pf_ruleset/man.rst b/cdist/conf/type/__pf_ruleset/man.rst
index 5719e94e..db8873ac 100644
--- a/cdist/conf/type/__pf_ruleset/man.rst
+++ b/cdist/conf/type/__pf_ruleset/man.rst
@@ -10,6 +10,9 @@ DESCRIPTION
-----------
This type is used on \*BSD systems to manage the pf firewall's ruleset.
+It will also enable and disable the pf firewall as requested in the `state`
+parameter.
+
REQUIRED PARAMETERS
-------------------
@@ -20,9 +23,8 @@ state
OPTIONAL PARAMETERS
-------------------
source
- If supplied, use to define the ruleset to load onto the $__target_host for pf(4).
- Note that this type is almost useless without a ruleset defined, but it's technically not
- needed, e.g. for the case of disabling the firewall temporarily.
+ Required when state is "present".
+ Defines the ruleset to load onto the $__target_host for `pf(4)`.
EXAMPLES
@@ -30,10 +32,10 @@ EXAMPLES
.. code-block:: sh
- # Remove the current ruleset in place
+ # Remove the current ruleset in place and disable pf
__pf_ruleset --state absent
- # Enable the firewall with the ruleset defined in $__manifest/files/pf.conf
+ # Enable pf with the ruleset defined in $__manifest/files/pf.conf
__pf_ruleset --state present --source $__manifest/files/pf.conf
@@ -44,11 +46,13 @@ SEE ALSO
AUTHORS
-------
+Kamila Součková
Jake Guffey
COPYING
-------
+Copyright \(C) 2016 Kamila Součková.
Copyright \(C) 2012 Jake Guffey. 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
diff --git a/cdist/conf/type/__pf_ruleset/gencode-remote b/cdist/conf/type/__pf_ruleset/manifest
similarity index 51%
rename from cdist/conf/type/__pf_ruleset/gencode-remote
rename to cdist/conf/type/__pf_ruleset/manifest
index 12760fdf..27b35328 100755
--- a/cdist/conf/type/__pf_ruleset/gencode-remote
+++ b/cdist/conf/type/__pf_ruleset/manifest
@@ -1,6 +1,6 @@
#!/bin/sh -e
#
-# 2012 Jake Guffey (jake.guffey at eprotex.com)
+# 2016 Kamila Součková (coding at kamila.is)
#
# This file is part of cdist.
#
@@ -21,29 +21,26 @@
# Manage pf(4) on *BSD
#
-# Debug
-#exec >&2
-#set -x
-
-# Remove ${rcvar} in the case of --state absent
-
-state=$(cat "$__object/parameter/state")
-rcvar=$(cat "$__object/explorer/rcvar")
-
-if [ "$state" = "present" ]; then # There is nothing more for a *remote* script to do
- exit 0
-elif [ "$state" = "absent" ]; then
- # --state absent, so ensure that .new doesn't exist and that conf is renamed to .old
- cat <&2
- exit 1
+rcvar="$(cat "${__object}/explorer/rcvar")"
+state="$(cat "${__object}/parameter/state")"
+if [ -f "${__object}/parameter/source" ]; then
+ source="$(cat "${__object}/parameter/source")"
fi
+if [ "${state}" = "absent" ]; then
+ action="/etc/rc.d/pf stop"
+else
+ action="/etc/rc.d/pf reload || /etc/rc.d/pf start"
+fi
+
+__key_value __pf_ruleset/rcvar \
+ --state "${state}" \
+ --file /etc/rc.conf \
+ --delimiter "=" \
+ --key "pf_enable" \
+ --value "YES"
+
+require="__key_value/__pf_ruleset/rcvar" __config_file "${rcvar}" \
+ --source "${source}" \
+ --state "${state}" \
+ --onchange "${action}"
diff --git a/cdist/conf/type/__postfix/manifest b/cdist/conf/type/__postfix/manifest
index f3616979..121bba96 100755
--- a/cdist/conf/type/__postfix/manifest
+++ b/cdist/conf/type/__postfix/manifest
@@ -19,16 +19,4 @@
# along with cdist. If not, see .
#
-
-os=$(cat "$__global/explorer/os")
-
-case "$os" in
- alpine|ubuntu|debian|archlinux|suse|scientific|centos|devuan)
- __package postfix --state present
- ;;
- *)
- 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
+__package postfix --state present
diff --git a/cdist/conf/type/__postfix_master/gencode-remote b/cdist/conf/type/__postfix_master/gencode-remote
index 7c109a69..73de1088 100755
--- a/cdist/conf/type/__postfix_master/gencode-remote
+++ b/cdist/conf/type/__postfix_master/gencode-remote
@@ -67,7 +67,7 @@ case "$state_should" in
remove_entry
fi
cat << DONE
-cat >> "$config" << ${__type##*/}_DONE
+cat >> "$config" << "${__type##*/}_DONE"
$(cat "$entry")
${__type##*/}_DONE
DONE
diff --git a/cdist/conf/type/__postfix_master/parameter/optional b/cdist/conf/type/__postfix_master/parameter/optional
index 792b42c5..410482b8 100644
--- a/cdist/conf/type/__postfix_master/parameter/optional
+++ b/cdist/conf/type/__postfix_master/parameter/optional
@@ -4,6 +4,5 @@ unpriv
chroot
wakeup
maxproc
-option
comment
state
diff --git a/cdist/conf/type/__postfix_master/parameter/optional_multiple b/cdist/conf/type/__postfix_master/parameter/optional_multiple
new file mode 100644
index 00000000..01925a15
--- /dev/null
+++ b/cdist/conf/type/__postfix_master/parameter/optional_multiple
@@ -0,0 +1 @@
+option
diff --git a/cdist/conf/type/__postgres_database/gencode-remote b/cdist/conf/type/__postgres_database/gencode-remote
index 47e6b97c..0f11cff4 100755
--- a/cdist/conf/type/__postgres_database/gencode-remote
+++ b/cdist/conf/type/__postgres_database/gencode-remote
@@ -43,8 +43,29 @@ if [ "$state_should" != "$state_is" ]; then
if [ -f "$__object/parameter/owner" ]; then
owner="-O \"$(cat "$__object/parameter/owner")\""
fi
+
+ template=""
+ if [ -f "$__object/parameter/template" ]; then
+ template="--template \"$(cat "$__object/parameter/template")\""
+ fi
+
+ encoding=""
+ if [ -f "$__object/parameter/encoding" ]; then
+ encoding="--encoding \"$(cat "$__object/parameter/encoding")\""
+ fi
+
+ lc_collate=""
+ if [ -f "$__object/parameter/lc-collate" ]; then
+ lc_collate="--lc-collate \"$(cat "$__object/parameter/lc-collate")\""
+ fi
+
+ lc_ctype=""
+ if [ -f "$__object/parameter/lc-ctype" ]; then
+ lc_ctype="--lc-ctype \"$(cat "$__object/parameter/lc-ctype")\""
+ fi
+
cat << EOF
-su - '$postgres_user' -c "createdb $owner \"$name\""
+su - '$postgres_user' -c "createdb $owner \"$name\" $template $encoding $lc_collate $lc_ctype"
EOF
;;
absent)
diff --git a/cdist/conf/type/__postgres_database/man.rst b/cdist/conf/type/__postgres_database/man.rst
index acceec9b..870b4917 100644
--- a/cdist/conf/type/__postgres_database/man.rst
+++ b/cdist/conf/type/__postgres_database/man.rst
@@ -14,10 +14,22 @@ This cdist type allows you to create or drop postgres databases.
OPTIONAL PARAMETERS
-------------------
state
- either 'present' or 'absent', defaults to 'present'.
+ Either 'present' or 'absent', defaults to 'present'.
owner
- the role owning this database
+ Specifies the database user who will own the new database.
+
+encoding
+ Specifies the character encoding scheme to be used in this database.
+
+lc-collate
+ Specifies the LC_COLLATE setting to be used in this database.
+
+lc-ctype
+ Specifies the LC_CTYPE setting to be used in this database.
+
+template
+ Specifies the template database from which to build this database.
EXAMPLES
diff --git a/cdist/conf/type/__postgres_database/parameter/optional b/cdist/conf/type/__postgres_database/parameter/optional
index d86b6469..877fbf32 100644
--- a/cdist/conf/type/__postgres_database/parameter/optional
+++ b/cdist/conf/type/__postgres_database/parameter/optional
@@ -1,2 +1,6 @@
state
owner
+encoding
+lc-collate
+lc-ctype
+template
diff --git a/cdist/conf/type/__pyvenv/gencode-remote b/cdist/conf/type/__pyvenv/gencode-remote
index 9c7b7fab..c5b64eff 100755
--- a/cdist/conf/type/__pyvenv/gencode-remote
+++ b/cdist/conf/type/__pyvenv/gencode-remote
@@ -1,6 +1,7 @@
#!/bin/sh -e
#
# 2016 Darko Poljak (darko.poljak at gmail.com)
+# 2020 Nico Schotetlius (nico.schottelius at ungleich.ch)
#
# This file is part of cdist.
#
@@ -45,7 +46,7 @@ then
pyvenv=$(cat "$pyvenvparam")
else
case "$os" in
- alpine) # no pyvenv on alpine - I assume others will follow
+ alpine|ubuntu) # no pyvenv on alpine - I assume others will follow
pyvenv="python3 -m venv"
;;
*)
diff --git a/cdist/conf/type/__pyvenv/man.rst b/cdist/conf/type/__pyvenv/man.rst
index d7de92fa..8085ff12 100644
--- a/cdist/conf/type/__pyvenv/man.rst
+++ b/cdist/conf/type/__pyvenv/man.rst
@@ -9,7 +9,7 @@ cdist-type__pyvenv - Create or remove python virtual environment
DESCRIPTION
-----------
This cdist type allows you to create or remove python virtual
-environment using pyvenv.
+environment using pyvenv on python3 -m venv.
It assumes pyvenv is already installed. Concrete package depends
on concrete OS and/or OS version/distribution.
Ensure this for e.g. in your init manifest as in the following example:
@@ -57,7 +57,7 @@ EXAMPLES
__pyvenv /home/services/djangoenv
- # Use specific pyvenv
+ # Use specific pyvenv
__pyvenv /home/foo/fooenv --pyvenv /usr/local/bin/pyvenv-3.4
# Create python virtualenv for user foo.
@@ -76,4 +76,3 @@ COPYING
-------
Copyright \(C) 2016 Darko Poljak. Free use of this software is
granted under the terms of the GNU General Public License v3 or later (GPLv3+).
-
diff --git a/cdist/conf/type/__service/manifest b/cdist/conf/type/__service/manifest
index cb5af234..beb0713c 100644
--- a/cdist/conf/type/__service/manifest
+++ b/cdist/conf/type/__service/manifest
@@ -7,7 +7,9 @@ action="$(cat "$__object/parameter/action")"
case "$manager" in
systemd)
- __systemd_service "$name" --action "$action"
+ test "$action" = "start" && action="running"
+ test "$action" = "stop" && action="stopped"
+ __systemd_service "$name" --state "$action"
;;
*)
# Unknown: handled by `service $NAME $action` in gencode-remote.
diff --git a/cdist/conf/type/__ssh_authorized_key/man.rst b/cdist/conf/type/__ssh_authorized_key/man.rst
index 087a3dae..5bae02aa 100644
--- a/cdist/conf/type/__ssh_authorized_key/man.rst
+++ b/cdist/conf/type/__ssh_authorized_key/man.rst
@@ -15,25 +15,27 @@ This type was created to be used by the __ssh_authorized_keys type.
REQUIRED PARAMETERS
-------------------
file
- the authorized_keys file to which the given key should be added
+ The authorized_keys file where the given key should be managed.
key
- a string containing the ssh keytype, base 64 encoded key and optional
- trailing comment which shall be added to the given authorized_keys file.
+ The ssh key which shall be managed in this authorized_keys file.
+ Must be a string containing the ssh keytype, base 64 encoded key and
+ optional trailing comment which shall be added to the given
+ authorized_keys file.
OPTIONAL PARAMETERS
-------------------
comment
- explicit comment instead of the one which may be trailing the given key
+ Use this comment instead of the one which may be trailing in the key.
option
- an option to set for this authorized_key entry.
+ An option to set for this authorized_key entry.
Can be specified multiple times.
See sshd(8) for available options.
state
- if the given keys should be 'present' or 'absent', defaults to 'present'.
+ If the managed key should be 'present' or 'absent', defaults to 'present'.
MESSAGES
@@ -64,7 +66,7 @@ EXAMPLES
SEE ALSO
--------
-:strong:`cdist__ssh_authorized_keys`\ (7), :strong:`sshd`\ (8)
+:strong:`cdist-type__ssh_authorized_keys`\ (7), :strong:`sshd`\ (8)
AUTHORS
diff --git a/cdist/conf/type/__ssh_authorized_keys/explorer/keys b/cdist/conf/type/__ssh_authorized_keys/explorer/keys
new file mode 100755
index 00000000..cec25746
--- /dev/null
+++ b/cdist/conf/type/__ssh_authorized_keys/explorer/keys
@@ -0,0 +1,9 @@
+#!/bin/sh -e
+
+# shellcheck disable=SC1090
+file="$( . "$__type_explorer/file" )"
+
+if [ -f "$file" ]
+then
+ cat "$file"
+fi
diff --git a/cdist/conf/type/__ssh_authorized_keys/man.rst b/cdist/conf/type/__ssh_authorized_keys/man.rst
index ba310ff9..dac6adeb 100644
--- a/cdist/conf/type/__ssh_authorized_keys/man.rst
+++ b/cdist/conf/type/__ssh_authorized_keys/man.rst
@@ -20,42 +20,48 @@ then left to the user to ensure that the file exists and that ownership and
permissions work with ssh.
-REQUIRED PARAMETERS
--------------------
+REQUIRED MULTIPLE PARAMETERS
+----------------------------
key
- the ssh key which shall be added to this authorized_keys file.
- Must be a string and can be specified multiple times.
+ An ssh key which shall be managed in this authorized_keys file.
+ Must be a string containing the ssh keytype, base 64 encoded key and
+ optional trailing comment which shall be added to the given
+ authorized_keys file.
+ Can be specified multiple times.
OPTIONAL PARAMETERS
-------------------
comment
- explicit comment instead of the one which may be trailing the given key
+ Use this comment instead of the one which may be trailing in each key.
file
- an alternative destination file, defaults to ~$owner/.ssh/authorized_keys
+ An alternative destination file, defaults to ~$owner/.ssh/authorized_keys.
option
- an option to set for all created authorized_key entries.
+ An option to set for all authorized_key entries in the key parameter.
Can be specified multiple times.
See sshd(8) for available options.
owner
- the user owning the authorized_keys file, defaults to object_id.
+ The user owning the authorized_keys file, defaults to object_id.
state
- if the given keys should be 'present' or 'absent', defaults to 'present'.
+ If the given keys should be 'present' or 'absent', defaults to 'present'.
BOOLEAN PARAMETERS
------------------
noparent
- don't create or change ownership and permissions of the directory containing
- the authorized_keys file
+ Don't create or change ownership and permissions of the directory containing
+ the authorized_keys file.
nofile
- don't manage existence, ownership and permissions of the the authorized_keys
- file
+ Don't manage existence, ownership and permissions of the the authorized_keys
+ file.
+
+remove-unknown
+ Remove undefined keys.
EXAMPLES
@@ -67,6 +73,12 @@ EXAMPLES
__ssh_authorized_keys root \
--key "$(cat ~/.ssh/id_rsa.pub)"
+ # same as above, but make sure your key is only key in
+ # root's authorized_keys file
+ __ssh_authorized_keys root \
+ --key "$(cat ~/.ssh/id_rsa.pub)" \
+ --remove-unknown
+
# allow key to login as user-name
__ssh_authorized_keys user-name \
--key "ssh-rsa AXYZAAB3NzaC1yc2..."
diff --git a/cdist/conf/type/__ssh_authorized_keys/manifest b/cdist/conf/type/__ssh_authorized_keys/manifest
index b9f0582e..b319316b 100755
--- a/cdist/conf/type/__ssh_authorized_keys/manifest
+++ b/cdist/conf/type/__ssh_authorized_keys/manifest
@@ -55,8 +55,12 @@ _cksum() {
echo "$1" | cksum | cut -d' ' -f 1
}
+_type_and_key() {
+ echo "$1" | tr ' ' '\n' | awk '/^(ssh|ecdsa)-[^ ]+/ { printf $1" "; getline; printf $1 }'
+}
+
while read -r key; do
- type_and_key="$(echo "$key" | tr ' ' '\n' | awk '/^(ssh|ecdsa)-[^ ]+/ { printf $1" "; getline; printf $1 }')"
+ type_and_key="$( _type_and_key "$key" )"
object_id="$(_cksum "$file")-$(_cksum "$type_and_key")"
set -- "$object_id"
set -- "$@" --file "$file"
@@ -72,3 +76,24 @@ while read -r key; do
# Ensure __ssh_authorized_key does not read stdin
__ssh_authorized_key "$@" < /dev/null
done < "$__object/parameter/key"
+
+if [ -f "$__object/parameter/remove-unknown" ] &&
+ [ -s "$__object/explorer/keys" ]
+then
+ while read -r key
+ do
+ type_and_key="$( _type_and_key "$key" )"
+
+ if grep -Fq "$type_and_key" "$__object/parameter/key"
+ then
+ continue
+ fi
+
+ __ssh_authorized_key "remove-$( _cksum "$file$key" )" \
+ --file "$file" \
+ --key "$key" \
+ --state absent \
+ < /dev/null
+ done \
+ < "$__object/explorer/keys"
+fi
diff --git a/cdist/conf/type/__ssh_authorized_keys/parameter/boolean b/cdist/conf/type/__ssh_authorized_keys/parameter/boolean
index 4bb126fe..7388fed5 100644
--- a/cdist/conf/type/__ssh_authorized_keys/parameter/boolean
+++ b/cdist/conf/type/__ssh_authorized_keys/parameter/boolean
@@ -1,2 +1,3 @@
noparent
nofile
+remove-unknown
diff --git a/cdist/conf/type/__ssh_authorized_keys/parameter/optional b/cdist/conf/type/__ssh_authorized_keys/parameter/optional
index 21f9bc29..fa64fc43 100644
--- a/cdist/conf/type/__ssh_authorized_keys/parameter/optional
+++ b/cdist/conf/type/__ssh_authorized_keys/parameter/optional
@@ -1,5 +1,4 @@
comment
file
-option
owner
state
diff --git a/cdist/conf/type/__ssh_authorized_keys/parameter/optional_multiple b/cdist/conf/type/__ssh_authorized_keys/parameter/optional_multiple
new file mode 100644
index 00000000..01925a15
--- /dev/null
+++ b/cdist/conf/type/__ssh_authorized_keys/parameter/optional_multiple
@@ -0,0 +1 @@
+option
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/conf/type/__sysctl/explorer/value b/cdist/conf/type/__sysctl/explorer/value
index fc85b3d8..3e93c151 100755
--- a/cdist/conf/type/__sysctl/explorer/value
+++ b/cdist/conf/type/__sysctl/explorer/value
@@ -1,4 +1,4 @@
-#!/bin/sh
+#!/bin/sh -e
#
# 2014 Steven Armstrong (steven-cdist at armstrong.cc)
#
@@ -18,5 +18,10 @@
# along with cdist. If not, see .
#
+if test "$(uname -s)" = NetBSD
+then
+ PATH=$(getconf PATH)
+fi
+
# get the current runtime value
-sysctl -n "$__object_id" || true
+sysctl -n "${__object_id}" || true
diff --git a/cdist/conf/type/__sysctl/gencode-remote b/cdist/conf/type/__sysctl/gencode-remote
index 711d54e5..f0f6deef 100755
--- a/cdist/conf/type/__sysctl/gencode-remote
+++ b/cdist/conf/type/__sysctl/gencode-remote
@@ -44,6 +44,8 @@ case "$os" in
flag='-w'
;;
netbsd)
+ # shellcheck disable=SC2016
+ echo 'PATH=$(getconf PATH)'
flag='-w'
;;
freebsd|openbsd)
diff --git a/cdist/conf/type/__sysctl/man.rst b/cdist/conf/type/__sysctl/man.rst
index 6873003e..dbb9a1ac 100644
--- a/cdist/conf/type/__sysctl/man.rst
+++ b/cdist/conf/type/__sysctl/man.rst
@@ -26,6 +26,13 @@ EXAMPLES
__sysctl net.ipv4.ip_forward --value 1
+ # On some operating systems, e.g. NetBSD, to prevent an error if the
+ # MIB style name does not exist (e.g. optional kernel components),
+ # name and value can be separated by `?=`. The same effect can be achieved
+ # in cdist by appending a `?` to the key:
+
+ __sysctl ddb.onpanic? --value -1
+
AUTHORS
-------
diff --git a/cdist/conf/type/__systemd_service/man.rst b/cdist/conf/type/__systemd_service/man.rst
index 7eca398b..cd14c985 100644
--- a/cdist/conf/type/__systemd_service/man.rst
+++ b/cdist/conf/type/__systemd_service/man.rst
@@ -1,9 +1,10 @@
-cdist-type__systemd-service(7)
+cdist-type__systemd_service(7)
==============================
NAME
----
-cdist-type__systemd-service - Controls a systemd service state
+cdist-type__systemd_service - Controls a systemd service state
+
DESCRIPTION
-----------
@@ -14,11 +15,12 @@ service after configuration applied or shutdown one service.
The activation or deactivation is out of scope. Look for the
:strong:`cdist-type__systemd_util`\ (7) type instead.
+
REQUIRED PARAMETERS
-------------------
-
None.
+
OPTIONAL PARAMETERS
-------------------
@@ -31,12 +33,12 @@ state
running
Service should run (default)
- stoppend
- Service should stopped
+ stopped
+ Service should be stopped
action
Executes an action on on the service. It will only execute it if the
- service keeps the state **running**. There are following actions, where:
+ service keeps the state ``running``. There are following actions, where:
reload
Reloads the service
@@ -48,11 +50,12 @@ BOOLEAN PARAMETERS
------------------
if-required
- Only execute the action if minimum one required type outputs a message to
- **$__messages_out**. Through this, the action should only executed if a
+ Only execute the action if at minimum one required type outputs a message
+ to ``$__messages_out``. Through this, the action should only executed if a
dependency did something. The action will not executed if no dependencies
given.
+
MESSAGES
--------
@@ -68,12 +71,14 @@ restart
reload
Reloaded the service
+
ABORTS
------
Aborts in following cases:
systemd or the service does not exist
+
EXAMPLES
--------
.. code-block:: sh
@@ -95,13 +100,15 @@ EXAMPLES
# reload the service for a modified configuration file
# only reloads the service if the file really changed
- require="__config_file/etc/foo.conf" __systemd_service foo \
+ require="__file/etc/foo.conf" __systemd_service foo \
--action reload --if-required
+
AUTHORS
-------
Matthias Stecher
+
COPYRIGHT
---------
Copyright \(C) 2020 Matthias Stecher. You can redistribute it
diff --git a/cdist/conf/type/__timezone/gencode-remote b/cdist/conf/type/__timezone/gencode-remote
index 5299f548..b685c990 100755
--- a/cdist/conf/type/__timezone/gencode-remote
+++ b/cdist/conf/type/__timezone/gencode-remote
@@ -22,7 +22,7 @@
# This type allows to configure the desired localtime timezone.
timezone_is=$(cat "$__object/explorer/timezone_is")
-timezone_should="$__object_id"
+timezone_should=$(cat "$__object/parameter/tz")
os=$(cat "$__global/explorer/os")
if [ "$timezone_is" = "$timezone_should" ]; then
diff --git a/cdist/conf/type/__timezone/man.rst b/cdist/conf/type/__timezone/man.rst
index 8a945c16..6012c552 100644
--- a/cdist/conf/type/__timezone/man.rst
+++ b/cdist/conf/type/__timezone/man.rst
@@ -14,7 +14,8 @@ This type creates a symlink (/etc/localtime) to the selected timezone
REQUIRED PARAMETERS
-------------------
-None.
+tz
+ The name of timezone to set.
OPTIONAL PARAMETERS
@@ -27,19 +28,24 @@ EXAMPLES
.. code-block:: sh
- #Set up Europe/Andorra as our timezone.
- __timezone Europe/Andorra
+ # Set up Europe/Andorra as our timezone.
+ __timezone --tz Europe/Andorra
- #Set up US/Central as our timezone.
- __timezone US/Central
+ # Set up US/Central as our timezone.
+ __timezone --tz US/Central
AUTHORS
-------
-Ramon Salvadó
+| Steven Armstrong
+| Nico Schottelius
+| Ramon Salvadó
+| Dennis Camera
COPYING
-------
-Free use of this software is
-granted under the terms of the GNU General Public License version 3 (GPLv3).
+Copyright \(C) 2012-2020 the `AUTHORS`_. 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/__timezone/manifest b/cdist/conf/type/__timezone/manifest
index 3d28ccba..0eb7fb9c 100755
--- a/cdist/conf/type/__timezone/manifest
+++ b/cdist/conf/type/__timezone/manifest
@@ -22,7 +22,7 @@
#
# This type allows to configure the desired localtime timezone.
-timezone="$__object_id"
+timezone=$(cat "$__object/parameter/tz")
os=$(cat "$__global/explorer/os")
case "$os" in
diff --git a/cdist/conf/type/__timezone/parameter/required b/cdist/conf/type/__timezone/parameter/required
new file mode 100644
index 00000000..975445e4
--- /dev/null
+++ b/cdist/conf/type/__timezone/parameter/required
@@ -0,0 +1 @@
+tz
diff --git a/cdist/conf/type/__timezone/singleton b/cdist/conf/type/__timezone/singleton
new file mode 100644
index 00000000..e69de29b
diff --git a/cdist/conf/type/__uci/explorer/state b/cdist/conf/type/__uci/explorer/state
new file mode 100644
index 00000000..d7363dbf
--- /dev/null
+++ b/cdist/conf/type/__uci/explorer/state
@@ -0,0 +1,110 @@
+#!/bin/sh
+#
+# 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 retrieves the current state of the configuration option
+# The output of this explorer is one of these values:
+# present
+# The configuration option is present and has the value of the
+# parameter --value.
+# absent
+# The configuration option is not defined.
+# different
+# The configuration option is present but has a different value than the
+# parameter --value.
+# rearranged
+# The configuration option is present (a list) and has the same values as
+# the parameter --value, but in a different order.
+
+RS=$(printf '\036')
+
+option=${__object_id:?}
+
+values_is=$(uci -s -N -d "${RS}" get "${option}" 2>/dev/null) || {
+ echo absent
+ exit 0
+}
+
+if test -f "${__object:?}/parameter/value"
+then
+ should_file="${__object:?}/parameter/value"
+else
+ should_file='/dev/null'
+fi
+
+
+# strip off trailing newline
+printf '%s' "${values_is}" \
+| awk '
+function unquote(s) {
+ # simplified dequoting of single quoted strings
+ if (s ~ /^'\''.*'\''$/) {
+ s = substr(s, 2, length(s) - 2)
+ sub(/'"'\\\\''"'/, "'\''", s)
+ }
+ return s
+}
+
+BEGIN {
+ state = "present" # assume all is fine
+}
+NR == FNR {
+ # memoize "should" state
+ should[FNR] = $0
+ should_count++
+
+ # go to next line (important!)
+ next
+}
+
+# compare "is" state
+
+{ $0 = unquote($0) }
+
+$0 == should[FNR] { next }
+
+FNR > should_count {
+ # there are more "is" records than "should" -> definitely different
+ state = "different"
+ exit
+}
+
+{
+ # see if we can find the value somewhere in should
+ for (i in should) {
+ if ($0 == should[i]) {
+ # ... value found -> rearranged
+ # FIXME: Duplicate values are not properly handled here. Do they matter?
+ state = "rearranged"
+ next
+ }
+ }
+
+ state = "different"
+ exit
+}
+
+END {
+ if (FNR < should_count) {
+ # "is" was shorter than "should" -> different
+ state = "different"
+ }
+
+ print state
+}
+' "${should_file}" RS="${RS}" -
diff --git a/cdist/conf/type/__uci/files/functions.sh b/cdist/conf/type/__uci/files/functions.sh
new file mode 100644
index 00000000..277f648c
--- /dev/null
+++ b/cdist/conf/type/__uci/files/functions.sh
@@ -0,0 +1,73 @@
+# -*- mode: sh; indent-tabs-mode: t -*-
+
+in_list() {
+ printf '%s\n' "$@" | { grep -qxF "$(read -r ndl; echo "${ndl}")"; }
+}
+
+quote() {
+ for _arg
+ do
+ shift
+ if test -n "$(printf %s "${_arg}" | tr -d -c '\t\n \042-\047\050-\052\073-\077\133\\`|~' | tr -c '' '.')"
+ then
+ # needs quoting
+ set -- "$@" "$(printf "'%s'" "$(printf %s "${_arg}" | sed -e "s/'/'\\\\''/g")")"
+ else
+ set -- "$@" "${_arg}"
+ fi
+ done
+ unset _arg
+
+ # NOTE: Use printf because POSIX echo interprets escape sequences
+ printf '%s' "$*"
+}
+
+uci_cmd() {
+ # Usage: uci_cmd [UCI ARGUMENTS]...
+ mkdir -p "${__object:?}/files"
+ printf '%s\n' "$(quote "$@")" >>"${__object:?}/files/uci_batch.txt"
+}
+
+uci_validate_name() {
+ # like util.c uci_validate_name()
+ test -n "$*" && test -z "$(echo "$*" | tr -d '[:alnum:]_')"
+}
+
+uci_validate_tuple() (
+ tok=${1:?}
+ case $tok
+ in
+ (*.*.*)
+ # check option
+ option=${tok##*.}
+ uci_validate_name "${option}" || {
+ printf 'Invalid option: %s\n' "${option}" >&2
+ return 1
+ }
+ tok=${tok%.*}
+ ;;
+ (*.*)
+ # no option (section definition)
+ ;;
+ (*)
+ printf 'Invalid tuple: %s\n' "$1" >&2
+ return 1
+ ;;
+ esac
+
+ case ${tok#*.}
+ in
+ (@*) section=$(expr "${tok#*.}" : '@\(.*\)\[-*[0-9]*\]$') ;;
+ (*) section=${tok#*.} ;;
+ esac
+ uci_validate_name "${section}" || {
+ printf 'Invalid section: %s\n' "${1#*.}" >&2
+ return 1
+ }
+
+ config=${tok%%.*}
+ uci_validate_name "${config}" || {
+ printf 'Invalid config: %s\n' "${config}" >&2
+ return 1
+ }
+)
diff --git a/cdist/conf/type/__uci/files/uci_apply.sh b/cdist/conf/type/__uci/files/uci_apply.sh
new file mode 100644
index 00000000..63f94290
--- /dev/null
+++ b/cdist/conf/type/__uci/files/uci_apply.sh
@@ -0,0 +1,43 @@
+changes=$(uci changes)
+
+if test -n "${changes}"
+then
+ echo 'Uncommited UCI changes were found on the target:'
+ printf '%s\n\n' "${changes}"
+ echo 'This can be caused by manual changes or due to a previous failed run.'
+ echo 'Please investigate the situation, revert or commit the changes, and try again.'
+ exit 1
+fi >&2
+
+check_errors() {
+ # reads stdin and forwards non-empty lines to stderr.
+ # returns 0 if stdin is empty, else 1.
+ ! grep -e . >&2
+}
+
+commit() {
+ uci commit
+}
+
+rollback() {
+ printf '\nAn error occurred when trying to commit UCI transaction!\n' >&2
+
+ uci changes \
+ | sed -e 's/^-//' -e 's/\..*\$//' \
+ | sort -u \
+ | while read -r _package
+ do
+ uci revert "${_package}"
+ echo "${_package}" # for logging
+ done \
+ | awk '
+ BEGIN { printf "Reverted changes in: " }
+ { printf "%s%s", (FNR > 1 ? ", " : ""), $0 }
+ END { printf "\n" }' >&2
+
+ return 1
+}
+
+uci_apply() {
+ uci batch 2>&1 | check_errors && commit || rollback
+}
diff --git a/cdist/conf/type/__uci/gencode-remote b/cdist/conf/type/__uci/gencode-remote
new file mode 100755
index 00000000..70a3d3e0
--- /dev/null
+++ b/cdist/conf/type/__uci/gencode-remote
@@ -0,0 +1,101 @@
+#!/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 .
+#
+
+# shellcheck source=cdist/conf/type/__uci/files/functions.sh
+. "${__type:?}/files/functions.sh"
+
+state_is=$(cat "${__object:?}/explorer/state")
+state_should=$(cat "${__object:?}/parameter/state")
+
+config=${__object_id:?}
+uci_validate_tuple "${config}"
+
+
+case ${state_should}
+in
+ (present)
+ if in_list "${state_is}" 'present' 'rearranged'
+ then
+ # NOTE: order is ignored so rearranged is also fine.
+ exit 0
+ fi
+
+ # Determine type
+ type=$(cat "${__object:?}/parameter/type" 2>/dev/null || true)
+ case ${type}
+ in
+ (option|list) ;;
+ ('')
+ # Guess type by the number of values
+ test "$(wc -l "${__object:?}/parameter/value")" -gt 1 \
+ && type=list \
+ || type=option
+ ;;
+ (*)
+ printf 'Invalid --type: %s\n' "${type}" >&2
+ exit 1
+ ;;
+ esac
+
+ case ${type}
+ in
+ (list)
+ printf 'set_list %s\n' "${config}" >>"${__messages_out:?}"
+
+ if test "${state_is}" != 'absent'
+ then
+ uci_cmd delete "${config}"
+ fi
+
+ while read -r value
+ do
+ uci_cmd add_list "${config}"="${value}"
+ done <"${__object:?}/parameter/value"
+ ;;
+ (option)
+ printf 'set %s\n' "${config}" >>"${__messages_out:?}"
+
+ value=$(cat "${__object:?}/parameter/value")
+ uci_cmd set "${config}"="${value}"
+ ;;
+ esac
+ ;;
+ (absent)
+ if in_list "${state_is}" 'absent'
+ then
+ exit 0
+ fi
+
+ printf 'delete %s\n' "${config}" >>"${__messages_out:?}"
+ uci_cmd delete "${config}"
+ ;;
+ (*)
+ printf 'Invalid --state: %s\n' "${state_should}" >&2
+ exit 1
+ ;;
+esac
+
+if test -s "${__object:?}/files/uci_batch.txt"
+then
+ cat "${__type:?}/files/uci_apply.sh"
+ printf "uci_apply <<'EOF'\n"
+ cat "${__object:?}/files/uci_batch.txt"
+ printf '\nEOF\n'
+fi
diff --git a/cdist/conf/type/__uci/man.rst b/cdist/conf/type/__uci/man.rst
new file mode 100644
index 00000000..81a53473
--- /dev/null
+++ b/cdist/conf/type/__uci/man.rst
@@ -0,0 +1,78 @@
+cdist-type__uci(7)
+==================
+
+NAME
+----
+cdist-type__uci - Manage configuration values in UCI
+
+
+DESCRIPTION
+-----------
+This cdist type can be used to alter configuration options in OpenWrt's
+Unified Configuration Interface (UCI) system.
+
+
+REQUIRED PARAMETERS
+-------------------
+value
+ The value to be set. Can be used multiple times.
+ This parameter is ignored if ``--state`` is ``absent``.
+
+ Due to the way cdist handles arguments, values **must not** contain newline
+ characters.
+
+ Values do not need special quoting for UCI. The only requirement is that the
+ value is passed to the type as a single shell argument.
+
+OPTIONAL PARAMETERS
+-------------------
+state
+ ``present`` or ``absent``, defaults to ``present``.
+type
+ If the type should generate an option or a list.
+ One of: ``option`` or ``list``.
+ Defaults to auto-detect based on the number of ``--value`` parameters.
+
+
+BOOLEAN PARAMETERS
+------------------
+None.
+
+
+EXAMPLES
+--------
+
+.. code-block:: sh
+
+ # Set the system hostname
+ __uci system.@system[0].hostname --value 'OpenWrt'
+
+ # Set DHCP option 252: tell DHCP clients to not ask for proxy information.
+ __uci dhcp.lan.dhcp_option --type list --value '252,"\n"'
+
+ # Enable NTP and NTPd (each is applied individually)
+ __uci system.ntp.enabled --value 1
+ __uci system.ntp.enable_server --value 1
+ __uci system.ntp.server --type list \
+ --value '0.openwrt.pool.ntp.org' \
+ --value '1.openwrt.pool.ntp.org' \
+ --value '2.openwrt.pool.ntp.org' \
+ --value '3.openwrt.pool.ntp.org'
+
+
+SEE ALSO
+--------
+- https://openwrt.org/docs/guide-user/base-system/uci
+
+
+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/__uci/manifest b/cdist/conf/type/__uci/manifest
new file mode 100755
index 00000000..26920011
--- /dev/null
+++ b/cdist/conf/type/__uci/manifest
@@ -0,0 +1,51 @@
+#!/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 .
+#
+
+os=$(cat "${__global:?}/explorer/os")
+
+state_should=$(cat "${__object:?}/parameter/state")
+
+case ${os}
+in
+ (openwrt)
+ # okay
+ ;;
+ (*)
+ 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
+
+case ${state_should}
+in
+ (present)
+ test -s "${__object:?}/parameter/value" || {
+ echo 'The parameter --value is required.' >&2
+ exit 1
+ }
+ ;;
+ (absent)
+ ;;
+ (*)
+ printf 'Invalid --state: %s\n' "${state_should}" >&2
+ exit 1
+ ;;
+esac
diff --git a/cdist/conf/type/__uci/nonparallel b/cdist/conf/type/__uci/nonparallel
new file mode 100644
index 00000000..e69de29b
diff --git a/cdist/conf/type/__uci/parameter/default/state b/cdist/conf/type/__uci/parameter/default/state
new file mode 100644
index 00000000..e7f6134f
--- /dev/null
+++ b/cdist/conf/type/__uci/parameter/default/state
@@ -0,0 +1 @@
+present
diff --git a/cdist/conf/type/__uci/parameter/optional b/cdist/conf/type/__uci/parameter/optional
new file mode 100644
index 00000000..d9080e3a
--- /dev/null
+++ b/cdist/conf/type/__uci/parameter/optional
@@ -0,0 +1,2 @@
+state
+type
diff --git a/cdist/conf/type/__uci/parameter/optional_multiple b/cdist/conf/type/__uci/parameter/optional_multiple
new file mode 100644
index 00000000..6d4e1507
--- /dev/null
+++ b/cdist/conf/type/__uci/parameter/optional_multiple
@@ -0,0 +1 @@
+value
diff --git a/cdist/conf/type/__uci_section/explorer/match b/cdist/conf/type/__uci_section/explorer/match
new file mode 100644
index 00000000..0768e404
--- /dev/null
+++ b/cdist/conf/type/__uci_section/explorer/match
@@ -0,0 +1,103 @@
+#!/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 the "prefix" of the --type section matching --match
+# if set, or __object_id otherwise.
+
+RS=$(printf '\036')
+NL=$(printf '\n '); NL=${NL% }
+
+squote_values() {
+ sed -e '/=".*"$/{s/="/='\''/;s/"$/'\''/}' \
+ -e "/='.*'$/"'!{s/=/='\''/;s/$/'\''/}'
+}
+count_lines() (
+ IFS=${NL?}
+ # shellcheck disable=SC2048,SC2086
+ set -f -- $*; echo $#
+)
+
+echo "${__object_id:?}" | grep -q -e '^[^.]\{1,\}\.[^.]\{1,\}$' || {
+ echo 'Section identifiers are a package and section name separated by a "." (period).' >&2
+ exit 1
+}
+
+test -s "${__object:?}/parameter/match" || {
+ # If no --match is given, we take the __object_id as the section identifier.
+ echo "${__object_id:?}"
+ exit 0
+}
+test -s "${__object:?}/parameter/type" || {
+ echo 'Parameters --match and --type must be used together.' >&2
+ exit 1
+}
+
+sect_type_param=$(cat "${__object:?}/parameter/type")
+expr "${sect_type_param}" : '[^.]\{1,\}\.[^.]\{1,\}$' >/dev/null 2>&1 || {
+ echo 'Section types are a package name and section type separated by a "." (period).' >&2
+ exit 1
+}
+package_filter=${sect_type_param%%.*}
+section_filter=${sect_type_param#*.}
+
+# Find by --match
+# NOTE: Apart from section types all values are printed in single quotes by uci show.
+match=$(head -n 1 "${__object:?}/parameter/match" | squote_values)
+
+if uci -s -N get "${__object_id:?}" >/dev/null 2>&1
+then
+ # Named section exists: ensure if --match applies to it
+ # if the "matched" option does not exist (e.g. empty section) we use the
+ # section unconditionally.
+ if match_value_is=$(uci -s -N get "${__object_id:?}.${match%%=*}" 2>/dev/null)
+ then
+ match_value_should=$(expr "${match}" : ".*='\\(.*\\)'$")
+
+ test "${match_value_is}" = "${match_value_should}" || {
+ printf 'Named section "%s" does not match --match "%s"\n' \
+ "${__object_id:?}" "${match}" >&2
+ exit 1
+ }
+ fi
+
+ echo "${__object_id:?}"
+ exit 0
+fi
+
+# No correctly named section exists already: find one to which --match applies
+regex="^${package_filter}\\.@${section_filter}\\[[0-9]\\{1,\\}\\]\\.${match%%=*}="
+
+matched_sections=$(
+ uci -s -N -d "${RS}" show "${package_filter}" 2>/dev/null \
+ | grep -e "${regex}" \
+ | while read -r _line
+ do
+ if test "${_line#*=}" = "${match#*=}"
+ then
+ echo "${_line}"
+ fi
+ done \
+ | sed -e 's/\.[^.]*=.*$//')
+
+test "$(count_lines "${matched_sections}")" -le 1 || {
+ printf 'Found multiple matching sections:\n%s\n' "${matched_sections}" >&2
+ exit 1
+}
+
+echo "${matched_sections}"
diff --git a/cdist/conf/type/__uci_section/explorer/options b/cdist/conf/type/__uci_section/explorer/options
new file mode 100644
index 00000000..e1e60668
--- /dev/null
+++ b/cdist/conf/type/__uci_section/explorer/options
@@ -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 .
+#
+# This explorer retrieves the current options of the configuration section.
+
+RS=$(printf '\036')
+
+section=$("${__type_explorer:?}/match")
+test -n "${section}" || exit 0
+
+uci -s -N -d "${RS}" show "${section}" 2>/dev/null \
+| awk -v VSEP="${RS}" '
+ {
+ # Strip off the config and section parts
+ is_opt = sub(/^([^.]*\.){2}/, "")
+
+ if (!is_opt) {
+ # this line represents the section -> skip
+ next
+ }
+
+ if (index($0, VSEP)) {
+ # Put values each on a line, like --option and --list parameters
+ opt = substr($0, 1, index($0, "=") - 1)
+ split(substr($0, length(opt) + 2), values, VSEP)
+ for (i in values) {
+ printf "%s=%s\n", opt, values[i]
+ }
+ } else {
+ print
+ }
+ }'
diff --git a/cdist/conf/type/__uci_section/explorer/type b/cdist/conf/type/__uci_section/explorer/type
new file mode 100644
index 00000000..1675c2e0
--- /dev/null
+++ b/cdist/conf/type/__uci_section/explorer/type
@@ -0,0 +1,25 @@
+#!/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 retrieves the current section type.
+
+section=$("${__type_explorer:?}/match")
+test -n "${section}" || exit 0
+
+uci -s -N get "${section}" 2>/dev/null || true
diff --git a/cdist/conf/type/__uci_section/files/functions.sh b/cdist/conf/type/__uci_section/files/functions.sh
new file mode 100644
index 00000000..60cb9148
--- /dev/null
+++ b/cdist/conf/type/__uci_section/files/functions.sh
@@ -0,0 +1,59 @@
+# -*- mode: sh; indent-tabs-mode: t -*-
+
+NL=$(printf '\n '); NL=${NL% }
+
+grep_line() {
+ { shift; printf '%s\n' "$@"; } | grep -qxF "$1"
+}
+
+print_errors() {
+ awk -v prefix="${1:-Found errors:}" -v suffix="${2-}" '
+ BEGIN {
+ if (getline) {
+ print prefix
+ print
+ rc = 1
+ }
+ }
+ { print }
+ END {
+ if (rc && suffix) print suffix
+ exit rc
+ }' >&2
+}
+
+quote() {
+ for _arg
+ do
+ shift
+ if test -n "$(printf %s "${_arg}" | tr -d -c '\t\n \042-\047\050-\052\073-\077\133\\`|~' | tr -c '' '.')"
+ then
+ # needs quoting
+ set -- "$@" "$(printf "'%s'" "$(printf %s "${_arg}" | sed -e "s/'/'\\\\''/g")")"
+ else
+ set -- "$@" "${_arg}"
+ fi
+ done
+ unset _arg
+ printf '%s' "$*"
+}
+
+uci_cmd() {
+ # Usage: uci_cmd [UCI ARGUMENTS]...
+ mkdir -p "${__object:?}/files"
+ printf '%s\n' "$(quote "$@")" >>"${__object:?}/files/uci_batch.txt"
+}
+
+uci_validate_name() {
+ # like util.c uci_validate_name()
+ test -n "$*" && test -z "$(printf %s "$*" | tr -d '[:alnum:]_' | tr -c '' .)"
+}
+
+unquote_lines() {
+ sed -e '/^".*"$/{s/^"//;s/"$//}' \
+ -e '/'"^'.*'"'$/{s/'"^'"'//;s/'"'$"'//}'
+}
+
+validate_options() {
+ grep -shv -e '^[[:alnum:]_]\{1,\}=' "$@"
+}
diff --git a/cdist/conf/type/__uci_section/files/option_state.awk b/cdist/conf/type/__uci_section/files/option_state.awk
new file mode 100644
index 00000000..97cd94fb
--- /dev/null
+++ b/cdist/conf/type/__uci_section/files/option_state.awk
@@ -0,0 +1,91 @@
+# -*- mode: awk; indent-tabs-mode:t -*-
+# Usage: awk -f option_state.awk option_type option_name
+# e.g. awk -f option_state.awk option title
+# awk -f option_state.awk list entry
+
+function unquote(s) {
+ # simplified dequoting of single quoted strings
+ if (s ~ /^'.*'$/) {
+ s = substr(s, 2, length(s) - 2)
+ sub(/'\\''/, "'", s)
+ }
+ return s
+}
+
+function valueof(line) {
+ if (line !~ /^[[:alpha:]_]+=/) return 0
+ return unquote(substr(line, index(line, "=") + 1))
+}
+
+BEGIN {
+ __object = ENVIRON["__object"]
+ if (!__object) exit 1
+
+ opttype = ARGV[1]
+ optname = ARGV[2]
+
+ if (opttype !~ /^(option|list)/ || !optname) {
+ print "invalid"
+ exit (e=1)
+ }
+
+ ARGV[1] = __object "/parameter/" opttype
+ ARGV[2] = __object "/explorer/options"
+
+ state = "present"
+}
+
+NR == FNR {
+ # memoize "should" state
+ if (index($0, optname "=") == 1) {
+ should[++should_count] = valueof($0)
+ }
+
+ # go to next line (important!)
+ next
+}
+
+{
+ # compare "is" state
+ if (index($0, optname "=") != 1)
+ next
+ ++is_count
+
+ v = valueof($0)
+
+ if (v == should[is_count]) {
+ # looks good, but can't say definitely just from this line
+ } else if (is_count > should_count) {
+ # there are more "is" records than "should" -> definitely different
+ state = "different"
+ exit
+ } else {
+ # see if we can find the "is" value somewhere in "should"
+ for (i in should) {
+ if (v == should[i]) {
+ # value found -> could be rearranged
+ # FIXME: Duplicate values are not properly handled here. Do they matter?
+ state = "rearranged"
+ next
+ }
+ }
+
+ # "is" value could not be found in "should" -> definitely different
+ state = "different"
+ exit
+ }
+}
+
+END {
+ if (e) exit
+
+ if (!is_count) {
+ # no "is" values -> absent
+ state = "absent"
+ } else if (is_count < should_count) {
+ # "is" was shorter than "should" -> different
+ state = "different"
+ }
+
+ print state
+}
diff --git a/cdist/conf/type/__uci_section/files/uci_apply.sh b/cdist/conf/type/__uci_section/files/uci_apply.sh
new file mode 120000
index 00000000..4209151f
--- /dev/null
+++ b/cdist/conf/type/__uci_section/files/uci_apply.sh
@@ -0,0 +1 @@
+../../__uci/files/uci_apply.sh
\ No newline at end of file
diff --git a/cdist/conf/type/__uci_section/gencode-remote b/cdist/conf/type/__uci_section/gencode-remote
new file mode 100755
index 00000000..50fdfa4e
--- /dev/null
+++ b/cdist/conf/type/__uci_section/gencode-remote
@@ -0,0 +1,174 @@
+#!/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 .
+#
+
+# shellcheck source=cdist/conf/type/__uci_section/files/functions.sh
+. "${__type:?}/files/functions.sh"
+
+
+section=$(cat "${__object:?}/explorer/match")
+
+state_is=$(test -s "${__object:?}/explorer/type" && echo present || echo absent)
+state_should=$(cat "${__object:?}/parameter/state")
+
+case $state_should
+in
+ (present)
+ test -f "${__object:?}/parameter/type" || {
+ echo 'Parameter --type is required.' >&2
+ exit 1
+ }
+
+ type_is=$(cat "${__object:?}/explorer/type")
+ type_should=$(cat "${__object:?}/parameter/type")
+
+ if test -n "${type_is}"
+ then
+ sect_type=${type_is}
+ else
+ sect_type=${type_should##*.}
+ fi
+
+ if test -z "${section}"
+ then
+ # No section exists and --match was used.
+ # So we generate a new section identifier from $__object_id.
+ case ${__object_id:?}
+ in
+ (*.*) section=${__object_id:?} ;;
+ (*) section="${type_should%%.*}.${__object_id:?}" ;;
+ esac
+ fi
+
+ # Collect option names
+ if test -f "${__object:?}/parameter/list"
+ then
+ listnames_should=$(
+ sed -e 's/=.*$//' "${__object:?}/parameter/list" | sort -u)
+ fi
+
+ if test -f "${__object:?}/parameter/option"
+ then
+ optnames_should=$(
+ sed -e 's/=.*$//' "${__object:?}/parameter/option" | sort -u)
+ fi
+
+ # Make sure the section itself is present
+ if test "${state_is}" = absent \
+ || test "${type_is}" != "${type_should#*.}"
+ then
+ printf 'set %s\n' "${section}" >>"${__messages_out:?}"
+ # shellcheck disable=SC2140
+ uci_cmd set "${section}"="${sect_type}"
+ fi
+
+ # Delete options/lists not in "should"
+ sed -e 's/=.*$//' "${__object:?}/explorer/options" \
+ | while read -r _optname
+ do
+ grep_line "${_optname}" "${listnames_should}" "${optnames_should}" || {
+ printf 'delete %s\n' "${section}.${_optname}" >>"${__messages_out:?}"
+ uci_cmd delete "${section}.${_optname}"
+ } &2
+ exit 1
+ }
+
+ # Set "should" options
+ echo "${optnames_should}" \
+ | grep -e . \
+ | while read -r _optname
+ do
+ _opt_state=$(awk -f "${__type:?}/files/option_state.awk" option "${_optname}") \
+ || opt_proc_error "${_optname}"
+ case ${_opt_state}
+ in
+ (invalid)
+ opt_proc_error "${_optname}"
+ ;;
+ (present)
+ ;;
+ (*)
+ printf 'set %s\n' "${section}.${_optname}" >>"${__messages_out:?}"
+
+ # shellcheck disable=SC2140
+ uci_cmd set "${section}.${_optname}"="$(
+ grep -e "^${_optname}=" "${__object:?}/parameter/option" \
+ | sed -e 's/^.*=//' \
+ | unquote_lines \
+ | head -n 1)"
+ ;;
+ esac
+ done
+
+ echo "${listnames_should}" \
+ | grep -e . \
+ | while read -r _optname
+ do
+ _list_state=$(awk -f "${__type:?}/files/option_state.awk" list "${_optname}") \
+ || opt_proc_error "${_optname}"
+ case ${_list_state}
+ in
+ (invalid)
+ opt_proc_error "${_optname}"
+ ;;
+ (present)
+ ;;
+ (*)
+ printf 'set_list %s\n' "${section}.${_optname}" >>"${__messages_out:?}"
+
+ if test "${_list_state}" != absent
+ then
+ uci_cmd delete "${section}.${_optname}"
+ fi
+
+ grep "^${_optname}=" "${__object:?}/parameter/list" \
+ | sed -e 's/^.*=//' \
+ | unquote_lines \
+ | while read -r _value
+ do
+ # shellcheck disable=SC2140
+ uci_cmd add_list "${section}.${_optname}"="${_value}"
+ done
+ ;;
+ esac
+ done
+ ;;
+ (absent)
+ if test "${state_is}" = absent
+ then
+ # if explorer found no section there is nothing to delete
+ exit 0
+ fi
+
+ printf 'delete %s\n' "${section}" >>"${__messages_out:?}"
+ uci_cmd delete "${section}"
+ ;;
+esac
+
+if test -s "${__object:?}/files/uci_batch.txt"
+then
+ cat "${__type:?}/files/uci_apply.sh"
+ printf "uci_apply <<'EOF'\n"
+ cat "${__object:?}/files/uci_batch.txt"
+ printf '\nEOF\n'
+fi
diff --git a/cdist/conf/type/__uci_section/man.rst b/cdist/conf/type/__uci_section/man.rst
new file mode 100644
index 00000000..a0ab78e8
--- /dev/null
+++ b/cdist/conf/type/__uci_section/man.rst
@@ -0,0 +1,119 @@
+cdist-type__uci_section(7)
+==========================
+
+NAME
+----
+cdist-type__uci_section - Manage configuration sections in UCI
+
+
+DESCRIPTION
+-----------
+This cdist type can be used to replace whole configuration sections in OpenWrt's
+Unified Configuration Interface (UCI) system.
+It can be thought of as syntactic sugar for :strong:`cdist-type__uci`\ (7),
+as this type will generate the required `__uci` objects to make the section
+contain exactly the options specified using ``--option``.
+
+Since many default UCI sections are unnamed, this type allows to find the
+matching section by one of its options using the ``--match`` parameter.
+
+**NOTE:** Options already present on the target and not listed in ``--option``
+or ``--list`` will be deleted.
+
+
+REQUIRED PARAMETERS
+-------------------
+None.
+
+
+OPTIONAL PARAMETERS
+-------------------
+list
+ An option that is part of a list and should be present in the section (as
+ part of a list). Lists with multiple options can be expressed by using the
+ same ``