unsymlink_bundles #2
89 changed files with 3991 additions and 0 deletions
6
bundles/apt/files/apt-listchanges.conf
Normal file
6
bundles/apt/files/apt-listchanges.conf
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
[apt]
|
||||||
|
frontend=pager
|
||||||
|
email_address=${data['mail']}
|
||||||
|
confirm=0
|
||||||
|
save_seen=/var/lib/apt/listchanges.db
|
||||||
|
which=both
|
32
bundles/apt/files/check_unattended_upgrades
Normal file
32
bundles/apt/files/check_unattended_upgrades
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
statusfile="/var/tmp/unattended_upgrades.status"
|
||||||
|
if ! [[ -f "$statusfile" ]]
|
||||||
|
then
|
||||||
|
echo "Status file not found"
|
||||||
|
exit 3
|
||||||
|
fi
|
||||||
|
|
||||||
|
mtime=$(stat -c %Y $statusfile)
|
||||||
|
now=$(date +%s)
|
||||||
|
if (( $now - $mtime > 60*60*24*8 ))
|
||||||
|
then
|
||||||
|
echo "Status file is older than 8 days!"
|
||||||
|
exit 3
|
||||||
|
fi
|
||||||
|
|
||||||
|
exitcode=$(cat $statusfile)
|
||||||
|
case "$exitcode" in
|
||||||
|
abort_ssh)
|
||||||
|
echo "Upgrades skipped due to active SSH login"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
0)
|
||||||
|
echo "OK"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Last exitcode was $exitcode"
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
43
bundles/apt/files/do-unattended-upgrades
Normal file
43
bundles/apt/files/do-unattended-upgrades
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -xeuo pipefail
|
||||||
|
|
||||||
|
apt-get update
|
||||||
|
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get -y -q -o Dpkg::Options::=--force-confold dist-upgrade
|
||||||
|
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get -y -q autoclean
|
||||||
|
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get -y -q autoremove
|
||||||
|
|
||||||
|
% if clean_old_kernels:
|
||||||
|
existing=$(dpkg --get-selections | grep -E '^linux-(image|headers)-[0-9]' || true)
|
||||||
|
|
||||||
|
if [[ -z "$existing" ]]
|
||||||
|
then
|
||||||
|
echo "ERROR: No installed kernels found! Aborting!" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
current=$(uname -r | sed -r 's/-[a-zA-Z]+$//')
|
||||||
|
latest=$(echo "$existing" | sort --version-sort -t- -k 3,4 | tail -n 1 | sed -r 's/[^0-9]+([0-9]\.[^-]+-[0-9]+).*/\1/')
|
||||||
|
todelete=$(echo "$existing" | grep -v -E "($current|$latest)" | awk '{ print $1 }' || true)
|
||||||
|
|
||||||
|
if [[ -n "$todelete" ]]
|
||||||
|
then
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get -qy purge $todelete
|
||||||
|
fi
|
||||||
|
% endif
|
||||||
|
|
||||||
|
% for affected, restarts in sorted(restart_triggers.items()):
|
||||||
|
up_since=$(systemctl show "${affected}" | sed -n 's/^ActiveEnterTimestamp=//p' || echo 0)
|
||||||
|
up_since_ts=$(date -d "$up_since" +%s || echo 0)
|
||||||
|
now=$(date +%s)
|
||||||
|
|
||||||
|
if [ $((now - up_since_ts)) -lt 3600 ]
|
||||||
|
then
|
||||||
|
% for restart in sorted(restarts):
|
||||||
|
systemctl restart "${restart}" || true
|
||||||
|
% endfor
|
||||||
|
fi
|
||||||
|
% endfor
|
15
bundles/apt/files/kernel-postinst.d
Normal file
15
bundles/apt/files/kernel-postinst.d
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# /etc/kernel/postinst.d/unattended-upgrades
|
||||||
|
|
||||||
|
case "$DPKG_MAINTSCRIPT_PACKAGE::$DPKG_MAINTSCRIPT_NAME" in
|
||||||
|
linux-image-extra*::postrm)
|
||||||
|
exit 0;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ -d /var/run ]; then
|
||||||
|
touch /var/run/reboot-required
|
||||||
|
if ! grep -q "^$DPKG_MAINTSCRIPT_PACKAGE$" /var/run/reboot-required.pkgs 2> /dev/null ; then
|
||||||
|
echo "$DPKG_MAINTSCRIPT_PACKAGE" >> /var/run/reboot-required.pkgs
|
||||||
|
fi
|
||||||
|
fi
|
3
bundles/apt/files/sources.list-debian-bullseye
Normal file
3
bundles/apt/files/sources.list-debian-bullseye
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
deb http://deb.debian.org/debian/ bullseye main non-free contrib
|
||||||
|
deb http://security.debian.org/debian-security bullseye-security main contrib non-free
|
||||||
|
deb http://deb.debian.org/debian/ bullseye-updates main contrib non-free
|
3
bundles/apt/files/sources.list-debian-buster
Normal file
3
bundles/apt/files/sources.list-debian-buster
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
deb http://deb.debian.org/debian/ buster main non-free contrib
|
||||||
|
deb http://security.debian.org/debian-security buster/updates main contrib non-free
|
||||||
|
deb http://deb.debian.org/debian/ buster-updates main contrib non-free
|
1
bundles/apt/files/sources.list-debian-unstable
Normal file
1
bundles/apt/files/sources.list-debian-unstable
Normal file
|
@ -0,0 +1 @@
|
||||||
|
deb http://deb.debian.org/debian/ unstable main non-free contrib
|
1
bundles/apt/files/sources.list-raspbian-buster
Normal file
1
bundles/apt/files/sources.list-raspbian-buster
Normal file
|
@ -0,0 +1 @@
|
||||||
|
deb http://raspbian.raspberrypi.org/raspbian/ buster main contrib non-free rpi
|
56
bundles/apt/files/upgrade-and-reboot
Normal file
56
bundles/apt/files/upgrade-and-reboot
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# With systemd, we can force logging to the journal. This is better than
|
||||||
|
# spamming the world with cron mails. You can then view these logs using
|
||||||
|
# "journalctl -rat upgrade-and-reboot".
|
||||||
|
if which logger >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
# Dump stdout and stderr to logger, which will then put everything
|
||||||
|
# into the journal.
|
||||||
|
exec 1> >(logger -t upgrade-and-reboot -p user.info)
|
||||||
|
exec 2> >(logger -t upgrade-and-reboot -p user.error)
|
||||||
|
fi
|
||||||
|
|
||||||
|
. /etc/upgrade-and-reboot.conf
|
||||||
|
|
||||||
|
echo "Starting upgrade-and-reboot for node $nodename ..."
|
||||||
|
|
||||||
|
statusfile="/var/tmp/unattended_upgrades.status"
|
||||||
|
# Workaround, because /var/tmp is usually 1777
|
||||||
|
[[ "$UID" == 0 ]] && chown root:root "$statusfile"
|
||||||
|
|
||||||
|
logins=$(ps h -C sshd -o euser | awk '$1 != "root" && $1 != "sshd" && $1 != "sshmon"')
|
||||||
|
if [[ -n "$logins" ]]
|
||||||
|
then
|
||||||
|
echo "Will abort now, there are active SSH logins: $logins"
|
||||||
|
echo "abort_ssh" > "$statusfile"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
softlockdir=/var/lib/bundlewrap/soft-$nodename
|
||||||
|
mkdir -p "$softlockdir"
|
||||||
|
printf '{"comment": "UPDATE", "date": %s, "expiry": %s, "id": "UNATTENDED", "items": ["*"], "user": "root@localhost"}\n' \
|
||||||
|
$(date +%s) \
|
||||||
|
$(date -d 'now + 30 mins' +%s) \
|
||||||
|
>"$softlockdir"/UNATTENDED
|
||||||
|
trap 'rm -f "$softlockdir"/UNATTENDED' EXIT
|
||||||
|
|
||||||
|
do-unattended-upgrades
|
||||||
|
ret=$?
|
||||||
|
|
||||||
|
echo "$ret" > "$statusfile"
|
||||||
|
if (( $ret != 0 ))
|
||||||
|
then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f /var/run/reboot-required ]]
|
||||||
|
then
|
||||||
|
if [[ -n "$reboot_mail_to" ]]
|
||||||
|
then
|
||||||
|
date | mail -s "SYSREBOOTNOW $nodename" "$reboot_mail_to"
|
||||||
|
fi
|
||||||
|
systemctl reboot
|
||||||
|
else
|
||||||
|
echo "upgrade-and-reboot for node $nodename is DONE"
|
||||||
|
fi
|
2
bundles/apt/files/upgrade-and-reboot.conf
Normal file
2
bundles/apt/files/upgrade-and-reboot.conf
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
nodename="${node.name}"
|
||||||
|
reboot_mail_to="${node.metadata.get('apt/unattended-upgrades/reboot_mail_to', '')}"
|
191
bundles/apt/items.py
Normal file
191
bundles/apt/items.py
Normal file
|
@ -0,0 +1,191 @@
|
||||||
|
from bundlewrap.exceptions import BundleError
|
||||||
|
|
||||||
|
supported_os = {
|
||||||
|
'debian': {
|
||||||
|
10: 'buster',
|
||||||
|
11: 'bullseye',
|
||||||
|
99: 'unstable',
|
||||||
|
},
|
||||||
|
'raspbian': {
|
||||||
|
10: 'buster',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
supported_os[node.os][node.os_version[0]]
|
||||||
|
except (KeyError, IndexError):
|
||||||
|
raise BundleError(f'{node.name}: OS {node.os} {node.os_version} is not supported by bundle:apt')
|
||||||
|
|
||||||
|
|
||||||
|
actions = {
|
||||||
|
'apt_update': {
|
||||||
|
'command': 'apt-get update',
|
||||||
|
'needed_by': {
|
||||||
|
'pkg_apt:',
|
||||||
|
},
|
||||||
|
'triggered': True,
|
||||||
|
'cascade_skip': False,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
files = {
|
||||||
|
'/etc/apt/sources.list': {
|
||||||
|
'source': 'sources.list-{}-{}'.format(node.os, supported_os[node.os][node.os_version[0]]),
|
||||||
|
'triggers': {
|
||||||
|
'action:apt_update',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'/etc/cloud': {
|
||||||
|
'delete': True,
|
||||||
|
},
|
||||||
|
'/etc/kernel/postinst.d/unattended-upgrades': {
|
||||||
|
'source': 'kernel-postinst.d',
|
||||||
|
'mode': '0755',
|
||||||
|
},
|
||||||
|
'/etc/netplan': {
|
||||||
|
'delete': True,
|
||||||
|
},
|
||||||
|
'/etc/upgrade-and-reboot.conf': {
|
||||||
|
'content_type': 'mako',
|
||||||
|
},
|
||||||
|
'/usr/local/sbin/upgrade-and-reboot': {
|
||||||
|
'mode': '0700',
|
||||||
|
},
|
||||||
|
'/usr/local/sbin/do-unattended-upgrades': {
|
||||||
|
'content_type': 'mako',
|
||||||
|
'mode': '0700',
|
||||||
|
'context': {
|
||||||
|
'clean_old_kernels': node.metadata.get('apt/clean_old_kernels', True),
|
||||||
|
'restart_triggers': node.metadata.get('apt/restart_triggers', {}),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'/usr/local/share/icinga/plugins/check_unattended_upgrades': {
|
||||||
|
'mode': '0755',
|
||||||
|
},
|
||||||
|
'/var/lib/cloud': {
|
||||||
|
'delete': True,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
directories = {
|
||||||
|
'/etc/apt/sources.list.d': {
|
||||||
|
'purge': True,
|
||||||
|
'triggers': {
|
||||||
|
'action:apt_update',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
svc_systemd = {
|
||||||
|
'apt-daily.timer': {
|
||||||
|
'running': False,
|
||||||
|
'enabled': False,
|
||||||
|
},
|
||||||
|
'apt-daily-upgrade.timer': {
|
||||||
|
'running': False,
|
||||||
|
'enabled': False,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pkg_apt = {
|
||||||
|
'apt-transport-https': {},
|
||||||
|
|
||||||
|
'arping': {},
|
||||||
|
'at': {},
|
||||||
|
'build-essential': {},
|
||||||
|
'bzip2': {},
|
||||||
|
'curl': {},
|
||||||
|
'diffutils': {},
|
||||||
|
'dnsutils': {},
|
||||||
|
'git': {},
|
||||||
|
'grep': {},
|
||||||
|
'gzip': {},
|
||||||
|
'htop': {},
|
||||||
|
'jq': {},
|
||||||
|
'less': {},
|
||||||
|
'logrotate': {},
|
||||||
|
'lsof': {},
|
||||||
|
'mailutils': {},
|
||||||
|
'manpages': {},
|
||||||
|
'moreutils': {},
|
||||||
|
'mount': {},
|
||||||
|
'mtr': {},
|
||||||
|
'ncdu': {},
|
||||||
|
'ncurses-term': {},
|
||||||
|
'netcat': {},
|
||||||
|
'nmap': {},
|
||||||
|
'python3': {},
|
||||||
|
'python3-dev': {},
|
||||||
|
'python3-setuptools': {
|
||||||
|
'needed_by': {
|
||||||
|
'pkg_pip:',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'python3-pip': {
|
||||||
|
'needed_by': {
|
||||||
|
'pkg_pip:',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'python3-virtualenv': {},
|
||||||
|
'rsync': {},
|
||||||
|
'tar': {},
|
||||||
|
'tcpdump': {},
|
||||||
|
'telnet': {},
|
||||||
|
'tmux': {},
|
||||||
|
'tree': {},
|
||||||
|
'unzip': {},
|
||||||
|
'vim': {},
|
||||||
|
'wget': {},
|
||||||
|
'whois': {},
|
||||||
|
'zip': {},
|
||||||
|
|
||||||
|
'cloud-init': {
|
||||||
|
'installed': False,
|
||||||
|
},
|
||||||
|
'netplan.io': {
|
||||||
|
'installed': False,
|
||||||
|
},
|
||||||
|
'popularity-contest': {
|
||||||
|
'installed': False,
|
||||||
|
},
|
||||||
|
'unattended-upgrades': {
|
||||||
|
'installed': False,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if node.os_version[0] >= 11:
|
||||||
|
symlinks = {
|
||||||
|
'/usr/bin/python': {
|
||||||
|
'target': '/usr/bin/python3',
|
||||||
|
'needs': {
|
||||||
|
'pkg_apt:python3',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, data in node.metadata.get('apt/repos', {}).items():
|
||||||
|
files['/etc/apt/sources.list.d/{}.list'.format(name)] = {
|
||||||
|
'content_type': 'mako',
|
||||||
|
'content': ("\n".join(sorted(data['items']))).format(
|
||||||
|
os=node.os,
|
||||||
|
os_release=supported_os[node.os][node.os_version[0]],
|
||||||
|
),
|
||||||
|
'triggers': {
|
||||||
|
'action:apt_update',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if data.get('install_gpg_key', True):
|
||||||
|
files['/etc/apt/sources.list.d/{}.list'.format(name)]['needs'] = {
|
||||||
|
'file:/etc/apt/trusted.gpg.d/{}.list.asc'.format(name),
|
||||||
|
}
|
||||||
|
|
||||||
|
files['/etc/apt/trusted.gpg.d/{}.list.asc'.format(name)] = {
|
||||||
|
'source': 'gpg-keys/{}.asc'.format(name),
|
||||||
|
'triggers': {
|
||||||
|
'action:apt_update',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for package, options in node.metadata.get('apt/packages', {}).items():
|
||||||
|
pkg_apt[package] = options
|
35
bundles/apt/metadata.py
Normal file
35
bundles/apt/metadata.py
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
defaults = {
|
||||||
|
'apt': {
|
||||||
|
'unattended_upgrades': {
|
||||||
|
'day': 5,
|
||||||
|
'hour': 21,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'icinga2_api': {
|
||||||
|
'apt': {
|
||||||
|
'services': {
|
||||||
|
'UNATTENDED UPGRADES': {
|
||||||
|
'command_on_monitored_host': '/usr/local/share/icinga/plugins/check_unattended_upgrades',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@metadata_reactor.provides(
|
||||||
|
'cron/upgrade-and-reboot'
|
||||||
|
)
|
||||||
|
def patchday(metadata):
|
||||||
|
day = metadata.get('apt/unattended_upgrades/day')
|
||||||
|
hour = metadata.get('apt/unattended_upgrades/hour')
|
||||||
|
|
||||||
|
return {
|
||||||
|
'cron': {
|
||||||
|
'upgrade-and-reboot': '{minute} {hour} * * {day} root /usr/local/sbin/upgrade-and-reboot'.format(
|
||||||
|
minute=node.magic_number % 30,
|
||||||
|
hour=hour,
|
||||||
|
day=day,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
12
bundles/basic/files/hosts
Normal file
12
bundles/basic/files/hosts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
127.0.0.1 localhost ${node.name} ${node.metadata['hostname']}
|
||||||
|
|
||||||
|
::1 ip6-localhost
|
||||||
|
fe00::0 ip6-localnet
|
||||||
|
ff00::0 ip6-mcastprefix
|
||||||
|
ff02::1 ip6-allnodes
|
||||||
|
ff02::2 ip6-allrouters
|
||||||
|
ff02::3 ip6-allhosts
|
||||||
|
|
||||||
|
% for ip, entries in sorted(node.metadata.get('hosts/entries', {}).items()):
|
||||||
|
${ip} ${' '.join(sorted(entries))}
|
||||||
|
% endfor
|
39
bundles/basic/files/htoprc
Normal file
39
bundles/basic/files/htoprc
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
# Beware! This file is rewritten by htop when settings are changed in the interface.
|
||||||
|
# The parser is also very primitive, and not human-friendly.
|
||||||
|
fields=0 48 17 18 38 39 40 2 46 47 49 1
|
||||||
|
sort_key=46
|
||||||
|
sort_direction=-1
|
||||||
|
tree_sort_key=0
|
||||||
|
tree_sort_direction=1
|
||||||
|
hide_kernel_threads=1
|
||||||
|
hide_userland_threads=0
|
||||||
|
shadow_other_users=0
|
||||||
|
show_thread_names=0
|
||||||
|
show_program_path=1
|
||||||
|
highlight_base_name=1
|
||||||
|
highlight_megabytes=0
|
||||||
|
highlight_threads=1
|
||||||
|
highlight_changes=0
|
||||||
|
highlight_changes_delay_secs=5
|
||||||
|
find_comm_in_cmdline=1
|
||||||
|
strip_exe_from_cmdline=1
|
||||||
|
show_merged_command=0
|
||||||
|
tree_view=0
|
||||||
|
tree_view_always_by_pid=0
|
||||||
|
header_margin=1
|
||||||
|
detailed_cpu_time=1
|
||||||
|
cpu_count_from_one=1
|
||||||
|
show_cpu_usage=1
|
||||||
|
show_cpu_frequency=0
|
||||||
|
show_cpu_temperature=0
|
||||||
|
degree_fahrenheit=0
|
||||||
|
update_process_names=0
|
||||||
|
account_guest_in_cpu_meter=0
|
||||||
|
color_scheme=0
|
||||||
|
enable_mouse=0
|
||||||
|
delay=10
|
||||||
|
left_meters=Tasks LoadAverage Uptime Memory CPU LeftCPUs CPU
|
||||||
|
left_meter_modes=2 2 2 1 1 1 2
|
||||||
|
right_meters=Hostname CPU RightCPUs
|
||||||
|
right_meter_modes=2 3 1
|
||||||
|
hide_function_bar=0
|
1
bundles/basic/files/locale
Normal file
1
bundles/basic/files/locale
Normal file
|
@ -0,0 +1 @@
|
||||||
|
LANG=${node.metadata['locale']['default']}
|
3
bundles/basic/files/locale.gen
Normal file
3
bundles/basic/files/locale.gen
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
% for locale in sorted(node.metadata['locale']['installed']):
|
||||||
|
${locale} ${locale.split('.')[-1]}
|
||||||
|
% endfor
|
88
bundles/basic/items.py
Normal file
88
bundles/basic/items.py
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
from inspect import cleandoc
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
|
from bundlewrap.utils.text import italic
|
||||||
|
|
||||||
|
files = {
|
||||||
|
'/etc/default/locale': {
|
||||||
|
'content_type': 'mako',
|
||||||
|
'needs': {
|
||||||
|
'action:locale-gen',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'/etc/hosts': {
|
||||||
|
'content_type': 'mako',
|
||||||
|
},
|
||||||
|
'/etc/htoprc.global': {
|
||||||
|
'source': 'htoprc',
|
||||||
|
},
|
||||||
|
'/etc/motd': {
|
||||||
|
'content': '',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
locale_needs = set()
|
||||||
|
for locale in sorted(node.metadata['locale']['installed']):
|
||||||
|
actions[f'ensure_locale_{locale}_is_enabled'] = {
|
||||||
|
'command': f"sed -i '/{locale}/s/^# *//g' /etc/locale.gen",
|
||||||
|
'unless': f"grep -e '^{locale}' /etc/locale.gen",
|
||||||
|
'triggers': {
|
||||||
|
'action:locale-gen',
|
||||||
|
},
|
||||||
|
'needs': locale_needs,
|
||||||
|
}
|
||||||
|
locale_needs = {f'action:ensure_locale_{locale}_is_enabled'}
|
||||||
|
|
||||||
|
actions = {
|
||||||
|
'locale-gen': {
|
||||||
|
'triggered': True,
|
||||||
|
'command': 'locale-gen',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
description = []
|
||||||
|
|
||||||
|
if not node.metadata.get('icinga_options/exclude_from_monitoring', False):
|
||||||
|
description.append('icingaweb2: https://icinga.kunsmann.eu/monitoring/host/show?host={}'.format(node.name))
|
||||||
|
|
||||||
|
if node.has_bundle('telegraf'):
|
||||||
|
description.append('Grafana: https://grafana.kunsmann.eu/d/{}'.format(UUID(int=node.magic_number).hex[:10]))
|
||||||
|
|
||||||
|
if (
|
||||||
|
not node.metadata.get('icinga_options/exclude_from_monitoring', False) or
|
||||||
|
node.has_bundle('telegraf')
|
||||||
|
):
|
||||||
|
description.append('') # divider line
|
||||||
|
|
||||||
|
if node.metadata.get('nginx/vhosts', {}):
|
||||||
|
description.append('nginx vhosts:')
|
||||||
|
|
||||||
|
for vname, vconfig in sorted(node.metadata.get('nginx/vhosts', {}).items()):
|
||||||
|
if vconfig.get('ssl', 'letsencrypt') is not None:
|
||||||
|
proto = 'https'
|
||||||
|
else:
|
||||||
|
proto = 'http'
|
||||||
|
|
||||||
|
domain = vconfig.get('domain', vname)
|
||||||
|
|
||||||
|
description.append(' {}: {}://{}{}'.format(
|
||||||
|
vname,
|
||||||
|
proto,
|
||||||
|
domain,
|
||||||
|
vconfig.get('website_check_path', '/'),
|
||||||
|
))
|
||||||
|
|
||||||
|
if node.metadata.get('description', []):
|
||||||
|
description.append('') # divider line
|
||||||
|
|
||||||
|
for line in node.metadata.get('description', []):
|
||||||
|
description.append('# {}'.format(italic(line)))
|
||||||
|
|
||||||
|
if description:
|
||||||
|
files['/etc/node.description'] = {
|
||||||
|
'content': '\n'.join(description) + '\n',
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
files['/etc/node.description'] = {
|
||||||
|
'delete': True,
|
||||||
|
}
|
25
bundles/basic/metadata.py
Normal file
25
bundles/basic/metadata.py
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
defaults = {
|
||||||
|
'bash_functions': {
|
||||||
|
'h': 'cp /etc/htoprc.global ~/.htoprc; mkdir -p ~/.config/htop; cp /etc/htoprc.global ~/.config/htop/htoprc; htop',
|
||||||
|
},
|
||||||
|
'locale': {
|
||||||
|
'default': 'en_US.UTF-8',
|
||||||
|
'installed': {
|
||||||
|
'de_DE.UTF-8',
|
||||||
|
'en_US.UTF-8',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@metadata_reactor.provides(
|
||||||
|
'locale/installed',
|
||||||
|
)
|
||||||
|
def ensure_default_is_installed(metadata):
|
||||||
|
return {
|
||||||
|
'locale': {
|
||||||
|
'installed': {
|
||||||
|
metadata.get('locale/default'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
5
bundles/letsencrypt/files/config
Normal file
5
bundles/letsencrypt/files/config
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
CONFIG_D=/etc/dehydrated/conf.d
|
||||||
|
BASEDIR=/var/lib/dehydrated
|
||||||
|
WELLKNOWN="${BASEDIR}/acme-challenges"
|
||||||
|
DOMAINS_TXT="/etc/dehydrated/domains.txt"
|
||||||
|
HOOK="/etc/dehydrated/hook.sh"
|
3
bundles/letsencrypt/files/domains.txt
Normal file
3
bundles/letsencrypt/files/domains.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
% for domain, aliases in sorted(node.metadata.get('letsencrypt/domains', {}).items()):
|
||||||
|
${domain} ${' '.join(sorted(aliases))}
|
||||||
|
% endfor
|
37
bundles/letsencrypt/files/hook.sh
Normal file
37
bundles/letsencrypt/files/hook.sh
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
deploy_cert() {<%text>
|
||||||
|
local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}" TIMESTAMP="${6}"</%text>
|
||||||
|
% for service, config in node.metadata.get('letsencrypt/concat_and_deploy', {}).items():
|
||||||
|
|
||||||
|
# concat_and_deploy ${service}
|
||||||
|
if [ "$DOMAIN" = "${config['match_domain']}" ]; then
|
||||||
|
cat $KEYFILE > ${config['target']}
|
||||||
|
cat $FULLCHAINFILE >> ${config['target']}
|
||||||
|
% if 'chown' in config:
|
||||||
|
chown ${config['chown']} ${config['target']}
|
||||||
|
% endif
|
||||||
|
% if 'chmod' in config:
|
||||||
|
chmod ${config['chmod']} ${config['target']}
|
||||||
|
% endif
|
||||||
|
% if 'commands' in config:
|
||||||
|
% for command in config['commands']:
|
||||||
|
${command}
|
||||||
|
% endfor
|
||||||
|
% endif
|
||||||
|
fi
|
||||||
|
% endfor
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
exit_hook() {<%text>
|
||||||
|
local ERROR="${1:-}"</%text>
|
||||||
|
|
||||||
|
% for service in sorted(node.metadata.get('letsencrypt/reload_after', set())):
|
||||||
|
systemctl reload-or-restart ${service}
|
||||||
|
% endfor
|
||||||
|
}
|
||||||
|
|
||||||
|
<%text>
|
||||||
|
HANDLER="$1"; shift
|
||||||
|
if [[ "${HANDLER}" =~ ^(deploy_cert|exit_hook)$ ]]; then
|
||||||
|
"$HANDLER" "$@"
|
||||||
|
fi</%text>
|
|
@ -0,0 +1,31 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
domain=$1
|
||||||
|
just_check=$2
|
||||||
|
|
||||||
|
cert_path="/var/lib/dehydrated/certs/$domain"
|
||||||
|
|
||||||
|
already_exists=false
|
||||||
|
if [ -f "$cert_path/privkey.pem" -a -f "$cert_path/fullchain.pem" -a -f "$cert_path/chain.pem" ]
|
||||||
|
then
|
||||||
|
already_exists=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$just_check" = true ]
|
||||||
|
then
|
||||||
|
if [ "$already_exists" = true ]
|
||||||
|
then
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$already_exists" != true ]
|
||||||
|
then
|
||||||
|
rm -r "$cert_path"
|
||||||
|
mkdir -p "$cert_path"
|
||||||
|
openssl req -x509 -newkey rsa:4096 -nodes -days 1 -subj "/CN=$domain" -keyout "$cert_path/privkey.pem" -out "$cert_path/fullchain.pem"
|
||||||
|
chmod 0600 "$cert_path/privkey.pem"
|
||||||
|
cp "$cert_path/fullchain.pem" "$cert_path/chain.pem"
|
||||||
|
fi
|
52
bundles/letsencrypt/items.py
Normal file
52
bundles/letsencrypt/items.py
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
repo.libs.tools.require_bundle(node, 'nginx', 'letsencrypt bundle needs nginx for http challenge')
|
||||||
|
|
||||||
|
directories = {
|
||||||
|
'/etc/dehydrated/conf.d': {},
|
||||||
|
'/var/lib/dehydrated/acme-challenges': {},
|
||||||
|
}
|
||||||
|
|
||||||
|
actions = {
|
||||||
|
'letsencrypt_update_certificates': {
|
||||||
|
'command': 'dehydrated --cron --accept-terms --challenge http-01',
|
||||||
|
'triggered': True,
|
||||||
|
'needs': {
|
||||||
|
'svc_systemd:nginx',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for domain, _ in node.metadata.get('letsencrypt/domains').items():
|
||||||
|
actions['letsencrypt_ensure-some-certificate_{}'.format(domain)] = {
|
||||||
|
'command': '/etc/dehydrated/letsencrypt-ensure-some-certificate {}'.format(domain),
|
||||||
|
'unless': '/etc/dehydrated/letsencrypt-ensure-some-certificate {} true'.format(domain),
|
||||||
|
'needs': {
|
||||||
|
'file:/etc/dehydrated/letsencrypt-ensure-some-certificate',
|
||||||
|
},
|
||||||
|
'needed_by': {
|
||||||
|
'svc_systemd:nginx',
|
||||||
|
},
|
||||||
|
'triggers': {
|
||||||
|
'action:letsencrypt_update_certificates',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
files = {
|
||||||
|
'/etc/dehydrated/domains.txt': {
|
||||||
|
'content_type': 'mako',
|
||||||
|
'triggers': {
|
||||||
|
'action:letsencrypt_update_certificates',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'/etc/dehydrated/config': {
|
||||||
|
'triggers': {
|
||||||
|
'action:letsencrypt_update_certificates',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'/etc/dehydrated/hook.sh': {
|
||||||
|
'content_type': 'mako',
|
||||||
|
'mode': '0755',
|
||||||
|
},
|
||||||
|
'/etc/dehydrated/letsencrypt-ensure-some-certificate': {
|
||||||
|
'mode': '0755',
|
||||||
|
},
|
||||||
|
}
|
24
bundles/letsencrypt/metadata.py
Normal file
24
bundles/letsencrypt/metadata.py
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
defaults = {
|
||||||
|
'apt': {
|
||||||
|
'packages': {
|
||||||
|
'dehydrated': {
|
||||||
|
'needed_by': {
|
||||||
|
'action:letsencrypt_update_certificates',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'cron': {
|
||||||
|
'letsencrypt_renew': '{} 4 * * * root /usr/bin/dehydrated --cron --accept-terms --challenge http-01 > /dev/null'.format((node.magic_number % 60)),
|
||||||
|
'letsencrypt_cleanup': '{} 4 * * 0 root /usr/bin/dehydrated --cleanup > /dev/null'.format((node.magic_number % 60)),
|
||||||
|
},
|
||||||
|
'pacman': {
|
||||||
|
'packages': {
|
||||||
|
'dehydrated': {
|
||||||
|
'needed_by': {
|
||||||
|
'action:letsencrypt_update_certificates',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
47
bundles/nftables/files/nftables.conf
Normal file
47
bundles/nftables/files/nftables.conf
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
#!/usr/sbin/nft -f
|
||||||
|
|
||||||
|
flush ruleset
|
||||||
|
|
||||||
|
table inet filter {
|
||||||
|
chain input {
|
||||||
|
type filter hook input priority 0
|
||||||
|
policy drop
|
||||||
|
|
||||||
|
tcp flags syn tcp option maxseg size 1-500 drop
|
||||||
|
|
||||||
|
ct state { established, related } accept
|
||||||
|
ct state invalid drop
|
||||||
|
|
||||||
|
iif lo accept
|
||||||
|
|
||||||
|
icmp type timestamp-request drop
|
||||||
|
icmp type timestamp-reply drop
|
||||||
|
ip protocol icmp accept
|
||||||
|
|
||||||
|
ip6 nexthdr ipv6-icmp accept
|
||||||
|
}
|
||||||
|
|
||||||
|
chain output {
|
||||||
|
type filter hook output priority 0
|
||||||
|
policy accept
|
||||||
|
}
|
||||||
|
|
||||||
|
chain forward {
|
||||||
|
type filter hook forward priority 0
|
||||||
|
policy drop
|
||||||
|
|
||||||
|
icmp type timestamp-request drop
|
||||||
|
icmp type timestamp-reply drop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table nat {
|
||||||
|
chain prerouting {
|
||||||
|
type nat hook prerouting priority -100
|
||||||
|
}
|
||||||
|
chain postrouting {
|
||||||
|
type nat hook postrouting priority 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
include "/etc/nftables-rules.d/*-*"
|
10
bundles/nftables/files/override.conf
Normal file
10
bundles/nftables/files/override.conf
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
[Service]
|
||||||
|
RemainAfterExit=yes
|
||||||
|
|
||||||
|
ExecStart=
|
||||||
|
ExecStart=/usr/sbin/nft -f /etc/nftables.conf
|
||||||
|
ExecStart=/usr/local/sbin/apply-sysctl
|
||||||
|
|
||||||
|
ExecReload=
|
||||||
|
ExecReload=/usr/sbin/nft -f /etc/nftables.conf
|
||||||
|
ExecReload=/usr/local/sbin/apply-sysctl
|
3
bundles/nftables/files/rules-template
Normal file
3
bundles/nftables/files/rules-template
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
% for rule in rules:
|
||||||
|
add rule ${rule}
|
||||||
|
% endfor
|
56
bundles/nftables/items.py
Normal file
56
bundles/nftables/items.py
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
if node.has_bundle('pacman'):
|
||||||
|
package = 'pkg_pacman:nftables'
|
||||||
|
else:
|
||||||
|
package = 'pkg_apt:nftables'
|
||||||
|
|
||||||
|
directories = {
|
||||||
|
# used by other bundles
|
||||||
|
'/etc/nftables-rules.d': {
|
||||||
|
'purge': True,
|
||||||
|
'triggers': {
|
||||||
|
'svc_systemd:nftables:reload',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
files = {
|
||||||
|
'/etc/nftables.conf': {
|
||||||
|
'needs': {
|
||||||
|
'directory:/etc/nftables-rules.d',
|
||||||
|
},
|
||||||
|
'triggers': {
|
||||||
|
'svc_systemd:nftables:reload',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'/etc/systemd/system/nftables.service.d/bundlewrap.conf': {
|
||||||
|
'source': 'override.conf',
|
||||||
|
'triggers': {
|
||||||
|
'action:systemd-reload',
|
||||||
|
'svc_systemd:nftables:reload',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for ruleset, rules in node.metadata.get('nftables/rules', {}).items():
|
||||||
|
files[f'/etc/nftables-rules.d/{ruleset}'] = {
|
||||||
|
'source': 'rules-template',
|
||||||
|
'content_type': 'mako',
|
||||||
|
'context': {
|
||||||
|
'rules': rules,
|
||||||
|
},
|
||||||
|
'needed_by': {
|
||||||
|
'svc_systemd:nftables',
|
||||||
|
},
|
||||||
|
'triggers': {
|
||||||
|
'svc_systemd:nftables:reload',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
svc_systemd = {
|
||||||
|
'nftables': {
|
||||||
|
'needs': {
|
||||||
|
'file:/etc/nftables.conf',
|
||||||
|
package,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
96
bundles/nftables/metadata.py
Normal file
96
bundles/nftables/metadata.py
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
from bundlewrap.exceptions import BundleError
|
||||||
|
|
||||||
|
defaults = {
|
||||||
|
'apt': {
|
||||||
|
'packages': {
|
||||||
|
'nftables': {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'pacman': {
|
||||||
|
'packages': {
|
||||||
|
'nftables': {},
|
||||||
|
# https://github.com/bundlewrap/bundlewrap/issues/688
|
||||||
|
# 'iptables': {
|
||||||
|
# 'installed': False,
|
||||||
|
# 'needed_by': {
|
||||||
|
# 'pkg_pacman:iptables-nft',
|
||||||
|
# },
|
||||||
|
# },
|
||||||
|
'iptables-nft': {
|
||||||
|
'needed_by': {
|
||||||
|
'pkg_pacman:nftables',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if not node.has_bundle('vmhost'):
|
||||||
|
# see comment in bundles/vmhost/items.py
|
||||||
|
defaults['apt']['packages']['iptables'] = {
|
||||||
|
'installed': False,
|
||||||
|
'needed_by': {
|
||||||
|
'pkg_apt:nftables',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
@metadata_reactor.provides(
|
||||||
|
'nftables/rules/99-port_rules',
|
||||||
|
)
|
||||||
|
def port_rules_to_nftables(metadata):
|
||||||
|
# Using this, bundles can simply set up port based rules. This
|
||||||
|
# reactor will then take care of converting those rules to actual
|
||||||
|
# nftables rules
|
||||||
|
ruleset = set()
|
||||||
|
|
||||||
|
# Plese note we do not set any defaults for ports. Bundles are
|
||||||
|
# expected to know themselves which default to use.
|
||||||
|
for portdef, targets in metadata.get('firewall/port_rules', {}).items():
|
||||||
|
if '/' in portdef:
|
||||||
|
port, proto = portdef.split('/', 2)
|
||||||
|
|
||||||
|
if proto not in {'udp'}:
|
||||||
|
raise BundleError(f'firewall/port_rules: illegal identifier {portdef} in metadata for {node.name}')
|
||||||
|
else:
|
||||||
|
port = portdef
|
||||||
|
proto = 'tcp'
|
||||||
|
|
||||||
|
for target in targets:
|
||||||
|
if port == '*' and target == '*':
|
||||||
|
raise BundleError('firewall/port_rules: setting both port and target to * is unsupported')
|
||||||
|
|
||||||
|
comment = f'comment "port_rules {target}"'
|
||||||
|
|
||||||
|
if port != '*':
|
||||||
|
if ':' in port:
|
||||||
|
parts = port.split(':')
|
||||||
|
port_str = f'{proto} dport {{ {parts[0]}-{parts[1]} }}'
|
||||||
|
else:
|
||||||
|
port_str = f'{proto} dport {port}'
|
||||||
|
else:
|
||||||
|
port_str = f'meta l4proto {proto}'
|
||||||
|
|
||||||
|
if target in ('ipv4', 'ipv6'):
|
||||||
|
version_str = f'meta nfproto {target}'
|
||||||
|
else:
|
||||||
|
version_str = ''
|
||||||
|
|
||||||
|
if target in ('*', 'ipv4', 'ipv6'):
|
||||||
|
ruleset.add(f'inet filter input {version_str} {port_str} accept {comment}')
|
||||||
|
else:
|
||||||
|
resolved = repo.libs.tools.resolve_identifier(repo, target)
|
||||||
|
|
||||||
|
for address in resolved['ipv4']:
|
||||||
|
ruleset.add(f'inet filter input meta nfproto ipv4 {port_str} ip saddr {address} accept {comment}')
|
||||||
|
|
||||||
|
for address in resolved['ipv6']:
|
||||||
|
ruleset.add(f'inet filter input meta nfproto ipv6 {port_str} ip6 saddr {address} accept {comment}')
|
||||||
|
|
||||||
|
return {
|
||||||
|
'nftables': {
|
||||||
|
'rules': {
|
||||||
|
# order does not matter here.
|
||||||
|
'99-port_rules': sorted(ruleset),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
9
bundles/nginx/files/arch-override.conf
Normal file
9
bundles/nginx/files/arch-override.conf
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
[Service]
|
||||||
|
ExecStart=
|
||||||
|
ExecStart=/usr/sbin/nginx -c /etc/nginx/nginx.conf
|
||||||
|
|
||||||
|
ExecReload=
|
||||||
|
ExecReload=/bin/sh -c "/bin/kill -s HUP $(/bin/cat /var/run/nginx.pid)"
|
||||||
|
|
||||||
|
ExecStop=
|
||||||
|
ExecStop=/bin/sh -c "/bin/kill -s TERM $(/bin/cat /var/run/nginx.pid)"
|
475
bundles/nginx/files/check_nginx_status
Normal file
475
bundles/nginx/files/check_nginx_status
Normal file
|
@ -0,0 +1,475 @@
|
||||||
|
#!/usr/bin/env perl
|
||||||
|
# editorconfig-checker-disable-file
|
||||||
|
# check_nginx_status.pl
|
||||||
|
# Author : regis.leroy at makina-corpus.com
|
||||||
|
# Licence : GPL - http://www.fsf.org/licenses/gpl.txt
|
||||||
|
#
|
||||||
|
# help : ./check_nginx_status.pl -h
|
||||||
|
#
|
||||||
|
# issues & updates: http://github.com/regilero/check_nginx_status
|
||||||
|
use warnings;
|
||||||
|
use strict;
|
||||||
|
use Getopt::Long;
|
||||||
|
use LWP::UserAgent;
|
||||||
|
use Time::HiRes qw(gettimeofday tv_interval);
|
||||||
|
use Digest::MD5 qw(md5 md5_hex);
|
||||||
|
use FindBin;
|
||||||
|
|
||||||
|
# ensure all outputs are in UTF-8
|
||||||
|
binmode(STDOUT, ":utf8");
|
||||||
|
|
||||||
|
# Nagios specific
|
||||||
|
use lib "/usr/lib/nagios/plugins";
|
||||||
|
use utils qw($TIMEOUT);
|
||||||
|
|
||||||
|
# Globals
|
||||||
|
my $Version='0.20';
|
||||||
|
my $Name=$0;
|
||||||
|
|
||||||
|
my $o_host = undef; # hostname
|
||||||
|
my $o_help= undef; # want some help ?
|
||||||
|
my $o_port= undef; # port
|
||||||
|
my $o_url = undef; # url to use, if not the default
|
||||||
|
my $o_user= undef; # user for auth
|
||||||
|
my $o_pass= ''; # password for auth
|
||||||
|
my $o_realm= ''; # password for auth
|
||||||
|
my $o_version= undef; # print version
|
||||||
|
my $o_warn_a_level= -1; # Number of active connections that will cause a warning
|
||||||
|
my $o_crit_a_level= -1; # Number of active connections that will cause an error
|
||||||
|
my $o_warn_rps_level= -1; # Number of Request per second that will cause a warning
|
||||||
|
my $o_crit_rps_level= -1; # Number of request Per second that will cause an error
|
||||||
|
my $o_warn_cps_level= -1; # Number of Connections per second that will cause a warning
|
||||||
|
my $o_crit_cps_level= -1; # Number of Connections per second that will cause an error
|
||||||
|
my $o_timeout= 15; # Default 15s Timeout
|
||||||
|
my $o_warn_thresold= undef; # warning thresolds entry
|
||||||
|
my $o_crit_thresold= undef; # critical thresolds entry
|
||||||
|
my $o_debug= undef; # debug mode
|
||||||
|
my $o_servername= undef; # ServerName (host header in http request)
|
||||||
|
my $o_https= undef; # SSL (HTTPS) mode
|
||||||
|
my $o_disable_sslverifyhostname = 0;
|
||||||
|
|
||||||
|
my $TempPath = '/tmp/'; # temp path
|
||||||
|
my $MaxTimeDif = 60*30; # Maximum uptime difference (seconds), default 30 minutes
|
||||||
|
|
||||||
|
my $nginx = 'NGINX'; # Could be used to store version also
|
||||||
|
|
||||||
|
# functions
|
||||||
|
sub show_versioninfo { print "$Name version : $Version\n"; }
|
||||||
|
|
||||||
|
sub print_usage {
|
||||||
|
print "Usage: $Name -H <host ip> [-p <port>] [-s servername] [-t <timeout>] [-w <WARN_THRESOLD> -c <CRIT_THRESOLD>] [-V] [-d] [-u <url>] [-U user -P pass -r realm]\n";
|
||||||
|
}
|
||||||
|
sub nagios_exit {
|
||||||
|
my ( $nickname, $status, $message, $perfdata , $silent) = @_;
|
||||||
|
my %STATUSCODE = (
|
||||||
|
'OK' => 0
|
||||||
|
, 'WARNING' => 1
|
||||||
|
, 'CRITICAL' => 2
|
||||||
|
, 'UNKNOWN' => 3
|
||||||
|
, 'PENDING' => 4
|
||||||
|
);
|
||||||
|
if(!defined($silent)) {
|
||||||
|
my $output = undef;
|
||||||
|
$output .= sprintf('%1$s %2$s - %3$s', $nickname, $status, $message);
|
||||||
|
if ($perfdata) {
|
||||||
|
$output .= sprintf('|%1$s', $perfdata);
|
||||||
|
}
|
||||||
|
$output .= chr(10);
|
||||||
|
print $output;
|
||||||
|
}
|
||||||
|
exit $STATUSCODE{$status};
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get the alarm signal
|
||||||
|
$SIG{'ALRM'} = sub {
|
||||||
|
nagios_exit($nginx,"CRITICAL","ERROR: Alarm signal (Nagios timeout)");
|
||||||
|
};
|
||||||
|
|
||||||
|
sub help {
|
||||||
|
print "Nginx Monitor for Nagios version ",$Version,"\n";
|
||||||
|
print "GPL licence, (c)2012 Leroy Regis\n\n";
|
||||||
|
print_usage();
|
||||||
|
print <<EOT;
|
||||||
|
-h, --help
|
||||||
|
print this help message
|
||||||
|
-H, --hostname=HOST
|
||||||
|
name or IP address of host to check
|
||||||
|
-p, --port=PORT
|
||||||
|
Http port
|
||||||
|
-u, --url=URL
|
||||||
|
Specific URL to use, instead of the default "http://<hostname or IP>/nginx_status"
|
||||||
|
-s, --servername=SERVERNAME
|
||||||
|
ServerName, (host header of HTTP request) use it if you specified an IP in -H to match the good Virtualhost in your target
|
||||||
|
-S, --ssl
|
||||||
|
Wether we should use HTTPS instead of HTTP
|
||||||
|
--disable-sslverifyhostname
|
||||||
|
Disable SSL hostname verification
|
||||||
|
-U, --user=user
|
||||||
|
Username for basic auth
|
||||||
|
-P, --pass=PASS
|
||||||
|
Password for basic auth
|
||||||
|
-r, --realm=REALM
|
||||||
|
Realm for basic auth
|
||||||
|
-d, --debug
|
||||||
|
Debug mode (show http request response)
|
||||||
|
-m, --maxreach=MAX
|
||||||
|
Number of max processes reached (since last check) that should trigger an alert
|
||||||
|
-t, --timeout=INTEGER
|
||||||
|
timeout in seconds (Default: $o_timeout)
|
||||||
|
-w, --warn=ACTIVE_CONN,REQ_PER_SEC,CONN_PER_SEC
|
||||||
|
number of active connections, ReqPerSec or ConnPerSec that will cause a WARNING
|
||||||
|
-1 for no warning
|
||||||
|
-c, --critical=ACTIVE_CONN,REQ_PER_SEC,CONN_PER_SEC
|
||||||
|
number of active connections, ReqPerSec or ConnPerSec that will cause a CRITICAL
|
||||||
|
-1 for no CRITICAL
|
||||||
|
-V, --version
|
||||||
|
prints version number
|
||||||
|
|
||||||
|
Note :
|
||||||
|
3 items can be managed on this check, this is why -w and -c parameters are using 3 values thresolds
|
||||||
|
- ACTIVE_CONN: Number of all opened connections, including connections to backends
|
||||||
|
- REQ_PER_SEC: Average number of request per second between this check and the previous one
|
||||||
|
- CONN_PER_SEC: Average number of connections per second between this check and the previous one
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
This one will generate WARNING and CRITICIAL alerts if you reach 10 000 or 20 000 active connection; or
|
||||||
|
100 or 200 request per second; or 200 or 300 connections per second
|
||||||
|
check_nginx_status.pl -H 10.0.0.10 -u /foo/nginx_status -s mydomain.example.com -t 8 -w 10000,100,200 -c 20000,200,300
|
||||||
|
|
||||||
|
this will generate WARNING and CRITICAL alerts only on the number of active connections (with low numbers for nginx)
|
||||||
|
check_nginx_status.pl -H 10.0.0.10 -s mydomain.example.com -t 8 -w 10,-1,-1 -c 20,-1,-1
|
||||||
|
|
||||||
|
theses two equivalents will not generate any alert (if the nginx_status page is reachable) but could be used for graphics
|
||||||
|
check_nginx_status.pl -H 10.0.0.10 -s mydomain.example.com -w -1,-1,-1 -c -1,-1,-1
|
||||||
|
check_nginx_status.pl -H 10.0.0.10 -s mydomain.example.com
|
||||||
|
|
||||||
|
EOT
|
||||||
|
}
|
||||||
|
|
||||||
|
sub check_options {
|
||||||
|
Getopt::Long::Configure ("bundling");
|
||||||
|
GetOptions(
|
||||||
|
'h' => \$o_help, 'help' => \$o_help,
|
||||||
|
'd' => \$o_debug, 'debug' => \$o_debug,
|
||||||
|
'H:s' => \$o_host, 'hostname:s' => \$o_host,
|
||||||
|
's:s' => \$o_servername, 'servername:s' => \$o_servername,
|
||||||
|
'S:s' => \$o_https, 'ssl:s' => \$o_https,
|
||||||
|
'u:s' => \$o_url, 'url:s' => \$o_url,
|
||||||
|
'U:s' => \$o_user, 'user:s' => \$o_user,
|
||||||
|
'P:s' => \$o_pass, 'pass:s' => \$o_pass,
|
||||||
|
'r:s' => \$o_realm, 'realm:s' => \$o_realm,
|
||||||
|
'p:i' => \$o_port, 'port:i' => \$o_port,
|
||||||
|
'V' => \$o_version, 'version' => \$o_version,
|
||||||
|
'w:s' => \$o_warn_thresold,'warn:s' => \$o_warn_thresold,
|
||||||
|
'c:s' => \$o_crit_thresold,'critical:s' => \$o_crit_thresold,
|
||||||
|
't:i' => \$o_timeout, 'timeout:i' => \$o_timeout,
|
||||||
|
'disable-sslverifyhostname' => \$o_disable_sslverifyhostname,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (defined ($o_help)) {
|
||||||
|
help();
|
||||||
|
nagios_exit($nginx,"UNKNOWN","leaving","",1);
|
||||||
|
}
|
||||||
|
if (defined($o_version)) {
|
||||||
|
show_versioninfo();
|
||||||
|
nagios_exit($nginx,"UNKNOWN","leaving","",1);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (defined($o_warn_thresold)) {
|
||||||
|
($o_warn_a_level,$o_warn_rps_level,$o_warn_cps_level) = split(',', $o_warn_thresold);
|
||||||
|
}
|
||||||
|
if (defined($o_crit_thresold)) {
|
||||||
|
($o_crit_a_level,$o_crit_rps_level,$o_crit_cps_level) = split(',', $o_crit_thresold);
|
||||||
|
}
|
||||||
|
if (defined($o_debug)) {
|
||||||
|
print("\nDebug thresolds: \nWarning: ($o_warn_thresold) => Active: $o_warn_a_level ReqPerSec :$o_warn_rps_level ConnPerSec: $o_warn_cps_level");
|
||||||
|
print("\nCritical ($o_crit_thresold) => : Active: $o_crit_a_level ReqPerSec: $o_crit_rps_level ConnPerSec : $o_crit_cps_level\n");
|
||||||
|
}
|
||||||
|
if ((defined($o_warn_a_level) && defined($o_crit_a_level)) &&
|
||||||
|
(($o_warn_a_level != -1) && ($o_crit_a_level != -1) && ($o_warn_a_level >= $o_crit_a_level)) ) {
|
||||||
|
nagios_exit($nginx,"UNKNOWN","Check warning and critical values for Active Process (1st part of thresold), warning level must be < crit level!");
|
||||||
|
}
|
||||||
|
if ((defined($o_warn_rps_level) && defined($o_crit_rps_level)) &&
|
||||||
|
(($o_warn_rps_level != -1) && ($o_crit_rps_level != -1) && ($o_warn_rps_level >= $o_crit_rps_level)) ) {
|
||||||
|
nagios_exit($nginx,"UNKNOWN","Check warning and critical values for ReqPerSec (2nd part of thresold), warning level must be < crit level!");
|
||||||
|
}
|
||||||
|
if ((defined($o_warn_cps_level) && defined($o_crit_cps_level)) &&
|
||||||
|
(($o_warn_cps_level != -1) && ($o_crit_cps_level != -1) && ($o_warn_cps_level >= $o_crit_cps_level)) ) {
|
||||||
|
nagios_exit($nginx,"UNKNOWN","Check warning and critical values for ConnPerSec (3rd part of thresold), warning level must be < crit level!");
|
||||||
|
}
|
||||||
|
# Check compulsory attributes
|
||||||
|
if (!defined($o_host)) {
|
||||||
|
print_usage();
|
||||||
|
nagios_exit($nginx,"UNKNOWN","-H host argument required");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
########## MAIN ##########
|
||||||
|
|
||||||
|
check_options();
|
||||||
|
|
||||||
|
my $override_ip = $o_host;
|
||||||
|
my $ua = LWP::UserAgent->new(
|
||||||
|
protocols_allowed => ['http', 'https'],
|
||||||
|
timeout => $o_timeout
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( $o_disable_sslverifyhostname ) {
|
||||||
|
$ua->ssl_opts( 'verify_hostname' => 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
# we need to enforce the HTTP request is made on the Nagios Host IP and
|
||||||
|
# not on the DNS related IP for that domain
|
||||||
|
@LWP::Protocol::http::EXTRA_SOCK_OPTS = ( PeerAddr => $override_ip );
|
||||||
|
# this prevent used only once warning in -w mode
|
||||||
|
my $ua_settings = @LWP::Protocol::http::EXTRA_SOCK_OPTS;
|
||||||
|
|
||||||
|
my $timing0 = [gettimeofday];
|
||||||
|
my $response = undef;
|
||||||
|
my $url = undef;
|
||||||
|
|
||||||
|
if (!defined($o_url)) {
|
||||||
|
$o_url='/nginx_status';
|
||||||
|
} else {
|
||||||
|
# ensure we have a '/' as first char
|
||||||
|
$o_url = '/'.$o_url unless $o_url =~ m(^/)
|
||||||
|
}
|
||||||
|
my $proto='http://';
|
||||||
|
if(defined($o_https)) {
|
||||||
|
$proto='https://';
|
||||||
|
if (defined($o_port) && $o_port!=443) {
|
||||||
|
if (defined ($o_debug)) {
|
||||||
|
print "\nDEBUG: Notice: port is defined at $o_port and not 443, check you really want that in SSL mode! \n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (defined($o_servername)) {
|
||||||
|
if (!defined($o_port)) {
|
||||||
|
$url = $proto . $o_servername . $o_url;
|
||||||
|
} else {
|
||||||
|
$url = $proto . $o_servername . ':' . $o_port . $o_url;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!defined($o_port)) {
|
||||||
|
$url = $proto . $o_host . $o_url;
|
||||||
|
} else {
|
||||||
|
$url = $proto . $o_host . ':' . $o_port . $o_url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (defined ($o_debug)) {
|
||||||
|
print "\nDEBUG: HTTP url: \n";
|
||||||
|
print $url;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $req = HTTP::Request->new( GET => $url );
|
||||||
|
|
||||||
|
if (defined($o_servername)) {
|
||||||
|
$req->header('Host' => $o_servername);
|
||||||
|
}
|
||||||
|
if (defined($o_user)) {
|
||||||
|
$req->authorization_basic($o_user, $o_pass);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defined ($o_debug)) {
|
||||||
|
print "\nDEBUG: HTTP request: \n";
|
||||||
|
print "IP used (better if it's an IP):" . $override_ip . "\n";
|
||||||
|
print $req->as_string;
|
||||||
|
}
|
||||||
|
$response = $ua->request($req);
|
||||||
|
my $timeelapsed = tv_interval ($timing0, [gettimeofday]);
|
||||||
|
|
||||||
|
my $InfoData = '';
|
||||||
|
my $PerfData = '';
|
||||||
|
#my @Time = (localtime); # list context and not scalar as we want the brutal timestamp
|
||||||
|
my $Time = time;
|
||||||
|
|
||||||
|
my $webcontent = undef;
|
||||||
|
if ($response->is_success) {
|
||||||
|
$webcontent=$response->decoded_content;
|
||||||
|
if (defined ($o_debug)) {
|
||||||
|
print "\nDEBUG: HTTP response:";
|
||||||
|
print $response->status_line;
|
||||||
|
print "\n".$response->header('Content-Type');
|
||||||
|
print "\n";
|
||||||
|
print $webcontent;
|
||||||
|
}
|
||||||
|
if ($response->header('Content-Type') =~ m/text\/html/) {
|
||||||
|
nagios_exit($nginx,"CRITICAL", "We have a response page for our request, but it's an HTML page, quite certainly not the status report of nginx");
|
||||||
|
}
|
||||||
|
# example of response content expected:
|
||||||
|
#Active connections: 10
|
||||||
|
#server accepts handled requests
|
||||||
|
#38500 38500 50690
|
||||||
|
#Reading: 5 Writing: 5 Waiting: 0
|
||||||
|
|
||||||
|
# number of all open connections including connections to backends
|
||||||
|
my $ActiveConn = 0;
|
||||||
|
if($webcontent =~ m/Active connections: (.*?)\n/) {
|
||||||
|
$ActiveConn = $1;
|
||||||
|
# triming
|
||||||
|
$ActiveConn =~ s/^\s+|\s+$//g;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# 3 counters with a space: accepted conn, handled conn and number of requests
|
||||||
|
my $counters = '';
|
||||||
|
my $AcceptedConn = 0;
|
||||||
|
my $HandledConn = 0;
|
||||||
|
my $NbRequests = 0;
|
||||||
|
if($webcontent =~ m/\nserver accepts handled requests\n(.*?)\n/) {
|
||||||
|
$counters = $1;
|
||||||
|
# triming
|
||||||
|
$counters =~ s/^\s+|\s+$//g;
|
||||||
|
#splitting
|
||||||
|
($AcceptedConn,$HandledConn,$NbRequests) = split(' ', $counters);
|
||||||
|
# triming
|
||||||
|
$AcceptedConn =~ s/^\s+|\s+$//g;
|
||||||
|
$HandledConn =~ s/^\s+|\s+$//g;
|
||||||
|
$NbRequests =~ s/^\s+|\s+$//g;
|
||||||
|
}
|
||||||
|
|
||||||
|
# nginx reads request header
|
||||||
|
my $Reading = 0;
|
||||||
|
# nginx reads request body, processes request, or writes response to a client
|
||||||
|
my $Writing = 0;
|
||||||
|
# keep-alive connections, actually it is active - (reading + writing)
|
||||||
|
my $Waiting = 0;
|
||||||
|
if($webcontent =~ m/Reading: (.*?)Writing: (.*?)Waiting: (.*?)$/) {
|
||||||
|
$Reading = $1;
|
||||||
|
$Writing = $2;
|
||||||
|
$Waiting = $3;
|
||||||
|
# triming
|
||||||
|
$Reading =~ s/^\s+|\s+$//g;
|
||||||
|
$Writing =~ s/^\s+|\s+$//g;
|
||||||
|
$Waiting =~ s/^\s+|\s+$//g;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Debug
|
||||||
|
if (defined ($o_debug)) {
|
||||||
|
print ("\nDEBUG Parse results => Active :" . $ActiveConn . "\nAcceptedConn :" . $AcceptedConn . "\nHandledConn :" . $HandledConn . "\nNbRequests :".$NbRequests . "\nReading :" .$Reading . "\nWriting :" . $Writing . "\nWaiting :" . $Waiting . "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
my $TempFile = $TempPath.$o_host.'_check_nginx_status'.md5_hex($url);
|
||||||
|
my $FH;
|
||||||
|
|
||||||
|
my $LastTime = 0;
|
||||||
|
my $LastAcceptedConn = 0;
|
||||||
|
my $LastHandledConn = 0;
|
||||||
|
my $LastNbRequests = 0;
|
||||||
|
if ((-e $TempFile) && (-r $TempFile) && (-w $TempFile)) {
|
||||||
|
open ($FH, '<',$TempFile) or nagios_exit($nginx,"UNKNOWN","unable to read temporary data from :".$TempFile);
|
||||||
|
$LastTime = <$FH>;
|
||||||
|
$LastAcceptedConn = <$FH>;
|
||||||
|
$LastHandledConn = <$FH>;
|
||||||
|
$LastNbRequests = <$FH>;
|
||||||
|
close ($FH);
|
||||||
|
if (defined ($o_debug)) {
|
||||||
|
print ("\nDebug: data from temporary file: $TempFile\n");
|
||||||
|
print (" LastTime: $LastTime LastAcceptedConn: $LastAcceptedConn LastHandledConn: $LastHandledConn LastNbRequests: $LastNbRequests \n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open ($FH, '>'.$TempFile) or nagios_exit($nginx,"UNKNOWN","unable to write temporary data in :".$TempFile);
|
||||||
|
#print $FH (@Time),"\n";
|
||||||
|
print $FH "$Time\n";
|
||||||
|
print $FH "$AcceptedConn\n";
|
||||||
|
print $FH "$HandledConn\n";
|
||||||
|
print $FH "$NbRequests\n";
|
||||||
|
close ($FH);
|
||||||
|
|
||||||
|
my $ConnPerSec = 0;
|
||||||
|
my $ReqPerSec = 0;
|
||||||
|
my $RequestsNew = 0;
|
||||||
|
# by default the average
|
||||||
|
my $ReqPerConn = 0;
|
||||||
|
if ($AcceptedConn > 0) {
|
||||||
|
$ReqPerConn = $NbRequests/$AcceptedConn;
|
||||||
|
}
|
||||||
|
my $elapsed = $Time - $LastTime ;
|
||||||
|
if (defined ($o_debug)) {
|
||||||
|
print ("\nDebug: pre-computation\n");
|
||||||
|
print ("Average ReqPerconn: $ReqPerConn, Seconds elapsed Since last check: $elapsed\n");
|
||||||
|
}
|
||||||
|
# check only if the counters may have been incremented
|
||||||
|
# but not if it may have been too much incremented
|
||||||
|
# if nginx was restarted ($NbRequests is now lower than previous value), just skip
|
||||||
|
if ( ($elapsed < $MaxTimeDif) && ($elapsed != 0) && ($NbRequests >= $LastNbRequests) ) {
|
||||||
|
$ConnPerSec = ($AcceptedConn-$LastAcceptedConn)/$elapsed;
|
||||||
|
$RequestsNew = $NbRequests-$LastNbRequests;
|
||||||
|
$ReqPerSec = $RequestsNew/$elapsed;
|
||||||
|
# get finer value
|
||||||
|
if ( $ConnPerSec!=0 ) {
|
||||||
|
my $ReqPerConn = $ReqPerSec/$ConnPerSec;
|
||||||
|
} else {
|
||||||
|
my $ReqPerConn = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (defined ($o_debug)) {
|
||||||
|
print ("\nDebug: data computed\n");
|
||||||
|
print ("ConnPerSec: $ConnPerSec ReqPerSec: $ReqPerSec ReqPerConn: $ReqPerConn\n");
|
||||||
|
}
|
||||||
|
$InfoData = sprintf (" %.3f sec. response time, Active: %d (Writing: %d Reading: %d Waiting: %d)"
|
||||||
|
. " ReqPerSec: %.3f ConnPerSec: %.3f ReqPerConn: %.3f"
|
||||||
|
,$timeelapsed,$ActiveConn,$Writing,$Reading,$Waiting,$ReqPerSec,$ConnPerSec,$ReqPerConn);
|
||||||
|
|
||||||
|
# Manage warn and crit values for the perfdata
|
||||||
|
my $p_warn_a_level = "$o_warn_a_level";
|
||||||
|
my $p_crit_a_level = "$o_crit_a_level";
|
||||||
|
my $p_warn_rps_level = "$o_warn_rps_level";
|
||||||
|
my $p_crit_rps_level = "$o_crit_rps_level";
|
||||||
|
my $p_warn_cps_level = "$o_warn_cps_level";
|
||||||
|
my $p_crit_cps_level = "$o_crit_cps_level";
|
||||||
|
|
||||||
|
if ($p_warn_a_level == "-1") {
|
||||||
|
$p_warn_a_level = "";
|
||||||
|
}
|
||||||
|
if ($p_crit_a_level == "-1") {
|
||||||
|
$p_crit_a_level = "";
|
||||||
|
}
|
||||||
|
if ($p_warn_rps_level == "-1") {
|
||||||
|
$p_warn_rps_level = "";
|
||||||
|
}
|
||||||
|
if ($p_crit_rps_level == "-1") {
|
||||||
|
$p_crit_rps_level = "";
|
||||||
|
}
|
||||||
|
if ($p_warn_cps_level == "-1") {
|
||||||
|
$p_warn_cps_level = "";
|
||||||
|
}
|
||||||
|
if ($p_crit_cps_level == "-1") {
|
||||||
|
$p_crit_cps_level = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
$PerfData = sprintf ("Writing=%d;;;; Reading=%d;;;; Waiting=%d;;;; Active=%d;%s;%s;; "
|
||||||
|
. "ReqPerSec=%f;%s;%s;; ConnPerSec=%f;%s;%s;; ReqPerConn=%f;;;;"
|
||||||
|
,($Writing),($Reading),($Waiting),($ActiveConn)
|
||||||
|
,($p_warn_a_level),($p_crit_a_level)
|
||||||
|
,($ReqPerSec),($p_warn_rps_level),($p_crit_rps_level)
|
||||||
|
,($ConnPerSec),($p_warn_cps_level),($p_crit_cps_level)
|
||||||
|
,($ReqPerConn));
|
||||||
|
# first all critical exists by priority
|
||||||
|
if (defined($o_crit_a_level) && (-1!=$o_crit_a_level) && ($ActiveConn >= $o_crit_a_level)) {
|
||||||
|
nagios_exit($nginx,"CRITICAL", "Active Connections are critically high " . $InfoData,$PerfData);
|
||||||
|
}
|
||||||
|
if (defined($o_crit_rps_level) && (-1!=$o_crit_rps_level) && ($ReqPerSec >= $o_crit_rps_level)) {
|
||||||
|
nagios_exit($nginx,"CRITICAL", "Request per second ratios is critically high " . $InfoData,$PerfData);
|
||||||
|
}
|
||||||
|
if (defined($o_crit_cps_level) && (-1!=$o_crit_cps_level) && ($ConnPerSec >= $o_crit_cps_level)) {
|
||||||
|
nagios_exit($nginx,"CRITICAL", "Connection per second ratio is critically high " . $InfoData,$PerfData);
|
||||||
|
}
|
||||||
|
# Then WARNING exits by priority
|
||||||
|
if (defined($o_warn_a_level) && (-1!=$o_warn_a_level) && ($ActiveConn >= $o_warn_a_level)) {
|
||||||
|
nagios_exit($nginx,"WARNING", "Active Connections are high " . $InfoData,$PerfData);
|
||||||
|
}
|
||||||
|
if (defined($o_warn_rps_level) && (-1!=$o_warn_rps_level) && ($ReqPerSec >= $o_warn_rps_level)) {
|
||||||
|
nagios_exit($nginx,"WARNING", "Requests per second ratio is high " . $InfoData,$PerfData);
|
||||||
|
}
|
||||||
|
if (defined($o_warn_cps_level) && (-1!=$o_warn_cps_level) && ($ConnPerSec >= $o_warn_cps_level)) {
|
||||||
|
nagios_exit($nginx,"WARNING", "Connection per second ratio is high " . $InfoData,$PerfData);
|
||||||
|
}
|
||||||
|
|
||||||
|
nagios_exit($nginx,"OK",$InfoData,$PerfData);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
nagios_exit($nginx,"CRITICAL", $response->status_line);
|
||||||
|
}
|
26
bundles/nginx/files/fastcgi.conf
Normal file
26
bundles/nginx/files/fastcgi.conf
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
fastcgi_param QUERY_STRING $query_string;
|
||||||
|
fastcgi_param REQUEST_METHOD $request_method;
|
||||||
|
fastcgi_param CONTENT_TYPE $content_type;
|
||||||
|
fastcgi_param CONTENT_LENGTH $content_length;
|
||||||
|
|
||||||
|
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
|
||||||
|
fastcgi_param REQUEST_URI $request_uri;
|
||||||
|
fastcgi_param DOCUMENT_URI $document_uri;
|
||||||
|
fastcgi_param DOCUMENT_ROOT $document_root;
|
||||||
|
fastcgi_param SERVER_PROTOCOL $server_protocol;
|
||||||
|
fastcgi_param REQUEST_SCHEME $scheme;
|
||||||
|
fastcgi_param HTTPS $https if_not_empty;
|
||||||
|
|
||||||
|
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
|
||||||
|
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
|
||||||
|
|
||||||
|
fastcgi_param REMOTE_ADDR $remote_addr;
|
||||||
|
fastcgi_param REMOTE_PORT $remote_port;
|
||||||
|
fastcgi_param SERVER_ADDR $server_addr;
|
||||||
|
fastcgi_param SERVER_PORT $server_port;
|
||||||
|
fastcgi_param SERVER_NAME $server_name;
|
||||||
|
|
||||||
|
fastcgi_param REDIRECT_STATUS 200;
|
||||||
|
|
||||||
|
# This is the only thing that's different to the debian default.
|
||||||
|
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
28
bundles/nginx/files/logrotate.conf
Normal file
28
bundles/nginx/files/logrotate.conf
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
/var/log/nginx/*.log {
|
||||||
|
compress
|
||||||
|
copytruncate
|
||||||
|
create 0640 www-data adm
|
||||||
|
daily
|
||||||
|
dateext
|
||||||
|
missingok
|
||||||
|
notifempty
|
||||||
|
rotate ${node.metadata.get('nginx/log_retention_days', 7)}
|
||||||
|
sharedscripts
|
||||||
|
prerotate
|
||||||
|
if [ -d /etc/logrotate.d/httpd-prerotate ]; then \
|
||||||
|
run-parts /etc/logrotate.d/httpd-prerotate; \
|
||||||
|
fi
|
||||||
|
endscript
|
||||||
|
}
|
||||||
|
|
||||||
|
/var/log/nginx-timing/*.log {
|
||||||
|
compress
|
||||||
|
copytruncate
|
||||||
|
create 0644 www-data adm
|
||||||
|
dateext
|
||||||
|
missingok
|
||||||
|
notifempty
|
||||||
|
rotate 3
|
||||||
|
sharedscripts
|
||||||
|
size 1M
|
||||||
|
}
|
61
bundles/nginx/files/nginx.conf
Normal file
61
bundles/nginx/files/nginx.conf
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
user ${username};
|
||||||
|
worker_processes ${worker_processes};
|
||||||
|
|
||||||
|
pid /var/run/nginx.pid;
|
||||||
|
|
||||||
|
|
||||||
|
events {
|
||||||
|
worker_connections ${worker_connections};
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
charset UTF-8;
|
||||||
|
override_charset on;
|
||||||
|
|
||||||
|
sendfile on;
|
||||||
|
#tcp_nopush on;
|
||||||
|
|
||||||
|
keepalive_timeout 15;
|
||||||
|
client_body_timeout 12;
|
||||||
|
client_header_timeout 12;
|
||||||
|
send_timeout 10;
|
||||||
|
|
||||||
|
access_log off;
|
||||||
|
error_log off;
|
||||||
|
|
||||||
|
client_body_buffer_size 16K;
|
||||||
|
client_header_buffer_size 4k;
|
||||||
|
client_max_body_size 1M;
|
||||||
|
large_client_header_buffers 4 8k;
|
||||||
|
|
||||||
|
map $http_upgrade $connection_upgrade {
|
||||||
|
default upgrade;
|
||||||
|
'' close;
|
||||||
|
}
|
||||||
|
|
||||||
|
# GDPR compatible IP smashinator 5000000
|
||||||
|
map $remote_addr $ip_anonym1 {
|
||||||
|
default 0.0.0;
|
||||||
|
"~(?P<ip>(\d+)\.(\d+))\.(\d+)\.\d+" $ip;
|
||||||
|
"~(?P<ip>[^:]+:[^:]+):" $ip;
|
||||||
|
}
|
||||||
|
map $remote_addr $ip_anonym2 {
|
||||||
|
default .0.0;
|
||||||
|
"~(?P<ip>(\d+)\.(\d+)\.(\d+))\.\d+" .0.0;
|
||||||
|
"~(?P<ip>[^:]+:[^:]+):" ::;
|
||||||
|
}
|
||||||
|
map $ip_anonym1$ip_anonym2 $ip_anonymized {
|
||||||
|
default 0.0.0.0;
|
||||||
|
"~(?P<ip>.*)" $ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
log_format gdpr '$ip_anonymized - $remote_user [$time_local] '
|
||||||
|
'"$request" $status $body_bytes_sent '
|
||||||
|
'"<stripped>" "$http_user_agent"';
|
||||||
|
|
||||||
|
log_format anon_timing '[$time_local] $request_time $upstream_response_time "$request" $status';
|
||||||
|
|
||||||
|
include /etc/nginx/sites/*;
|
||||||
|
}
|
13
bundles/nginx/files/port80.conf
Normal file
13
bundles/nginx/files/port80.conf
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
server {
|
||||||
|
listen 80 default_server;
|
||||||
|
listen [::]:80 default_server;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
location /.well-known/acme-challenge/ {
|
||||||
|
alias /var/lib/dehydrated/acme-challenges/;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
return 404;
|
||||||
|
}
|
||||||
|
}
|
9
bundles/nginx/files/security.txt
Normal file
9
bundles/nginx/files/security.txt
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
Contact: ${vhost.get('contact', repo.libs.defaults.security_email)}
|
||||||
|
Expires: ${vhost.get('expires', expiry)}
|
||||||
|
Preferred-Languages: ${','.join(sorted(vhost.get('lang', repo.libs.defaults.security_lang)))}
|
||||||
|
Canonical: ${proto}://${domain}/.well-known/security.txt
|
||||||
|
% for key, value in sorted(vhost.items()):
|
||||||
|
% if key[0].isupper():
|
||||||
|
${key}: ${value}
|
||||||
|
% endif
|
||||||
|
% endfor
|
156
bundles/nginx/files/site_template
Normal file
156
bundles/nginx/files/site_template
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
server {
|
||||||
|
% if domain_aliases:
|
||||||
|
server_name ${domain} ${' '.join(sorted(domain_aliases))};
|
||||||
|
% else:
|
||||||
|
server_name ${domain};
|
||||||
|
% endif
|
||||||
|
root ${webroot if webroot else '/var/www/{}/'.format(vhost)};
|
||||||
|
index ${' '.join(index)};
|
||||||
|
|
||||||
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
|
|
||||||
|
% if ssl:
|
||||||
|
location / {
|
||||||
|
return 308 https://$host$request_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
% if ssl == 'letsencrypt':
|
||||||
|
location /.well-known/acme-challenge/ {
|
||||||
|
alias /var/lib/dehydrated/acme-challenges/;
|
||||||
|
}
|
||||||
|
% endif
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
% if domain_aliases:
|
||||||
|
server_name ${domain} ${' '.join(sorted(domain_aliases))};
|
||||||
|
% else:
|
||||||
|
server_name ${domain};
|
||||||
|
% endif
|
||||||
|
root ${webroot if webroot else '/var/www/{}/'.format(vhost)};
|
||||||
|
index ${' '.join(index)};
|
||||||
|
|
||||||
|
listen 443 ssl http2;
|
||||||
|
listen [::]:443 ssl http2;
|
||||||
|
|
||||||
|
% if ssl == 'letsencrypt':
|
||||||
|
ssl_certificate /var/lib/dehydrated/certs/${domain}/fullchain.pem;
|
||||||
|
ssl_certificate_key /var/lib/dehydrated/certs/${domain}/privkey.pem;
|
||||||
|
% else:
|
||||||
|
ssl_certificate /etc/nginx/ssl/${vhost}.crt;
|
||||||
|
ssl_certificate_key /etc/nginx/ssl/${vhost}.key;
|
||||||
|
% endif
|
||||||
|
ssl_dhparam /etc/ssl/certs/dhparam.pem;
|
||||||
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
|
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
|
||||||
|
ssl_prefer_server_ciphers on;
|
||||||
|
ssl_session_cache shared:SSL:10m;
|
||||||
|
ssl_session_tickets off;
|
||||||
|
|
||||||
|
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
|
||||||
|
% endif
|
||||||
|
|
||||||
|
resolver 8.8.8.8 8.8.4.4 valid=300s;
|
||||||
|
resolver_timeout 5s;
|
||||||
|
|
||||||
|
% if create_access_log:
|
||||||
|
access_log /var/log/nginx/access-${vhost}.log gdpr;
|
||||||
|
% endif
|
||||||
|
access_log /var/log/nginx-timing/${vhost}.log anon_timing;
|
||||||
|
# error_log is disabled globally
|
||||||
|
|
||||||
|
% if max_body_size:
|
||||||
|
client_max_body_size ${max_body_size};
|
||||||
|
% endif
|
||||||
|
|
||||||
|
% if not do_not_set_content_security_headers:
|
||||||
|
add_header Referrer-Policy same-origin;
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN";
|
||||||
|
add_header X-Content-Type-Options nosniff;
|
||||||
|
add_header X-XSS-Protection "1; mode=block";
|
||||||
|
% endif
|
||||||
|
add_header Permissions-Policy interest-cohort=();
|
||||||
|
|
||||||
|
error_page 404 /not_found.html;
|
||||||
|
location = /not_found.html {
|
||||||
|
root /var/www/;
|
||||||
|
internal;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_page 500 502 503 504 /error.html;
|
||||||
|
location = /error.html {
|
||||||
|
root /var/www/;
|
||||||
|
internal;
|
||||||
|
}
|
||||||
|
|
||||||
|
% if ssl == 'letsencrypt':
|
||||||
|
location /.well-known/acme-challenge/ {
|
||||||
|
alias /var/lib/dehydrated/acme-challenges/;
|
||||||
|
}
|
||||||
|
% endif
|
||||||
|
|
||||||
|
% if security_txt:
|
||||||
|
location = /.well-known/security.txt {
|
||||||
|
alias /etc/nginx/security.txt.d/${vhost};
|
||||||
|
}
|
||||||
|
% endif
|
||||||
|
|
||||||
|
% if locations:
|
||||||
|
% for location, options in sorted(locations.items()):
|
||||||
|
location ${location} {
|
||||||
|
% if 'target' in options:
|
||||||
|
proxy_pass ${options['target']};
|
||||||
|
proxy_http_version ${options.get('http_version', '1.1')};
|
||||||
|
proxy_set_header Host ${domain};
|
||||||
|
% if options.get('websockets', False):
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
% endif
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Forwarded-Host ${options.get('x_forwarded_host', domain)};
|
||||||
|
% for option, value in options.get('proxy_set_header', {}).items():
|
||||||
|
proxy_set_header ${option} ${value};
|
||||||
|
% endfor
|
||||||
|
% if location != '/':
|
||||||
|
proxy_set_header X-Script-Name ${location};
|
||||||
|
% endif
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_read_timeout ${options.get('proxy_read_timeout', 60)};
|
||||||
|
client_max_body_size ${options.get('max_body_size', '5M')};
|
||||||
|
% elif 'redirect' in options:
|
||||||
|
return ${options.get('mode', 308)} ${options['redirect']};
|
||||||
|
% elif 'return' in options:
|
||||||
|
return ${options.get('mode', 200)} '${options['return']}';
|
||||||
|
% elif 'root' in options:
|
||||||
|
root ${options['root']};
|
||||||
|
% elif 'alias' in options:
|
||||||
|
alias ${options['alias']};
|
||||||
|
% endif
|
||||||
|
% if 'auth' in options:
|
||||||
|
auth_basic "${options['auth'].get('realm', vhost)}";
|
||||||
|
auth_basic_user_file ${options['auth']['file']};
|
||||||
|
% endif
|
||||||
|
% for opt in sorted(options.get('additional_config', set())):
|
||||||
|
${opt};
|
||||||
|
% endfor
|
||||||
|
}
|
||||||
|
|
||||||
|
% endfor
|
||||||
|
% endif
|
||||||
|
% if php:
|
||||||
|
location ~ \.php$ {
|
||||||
|
include fastcgi.conf;
|
||||||
|
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||||
|
fastcgi_pass unix:/run/php/php${php_version}-fpm.sock;
|
||||||
|
}
|
||||||
|
% if not max_body_size:
|
||||||
|
client_max_body_size 5M;
|
||||||
|
% endif
|
||||||
|
|
||||||
|
% endif
|
||||||
|
% if extras:
|
||||||
|
<%include file="extras/${node.name}/${vhost}" />
|
||||||
|
% endif
|
||||||
|
}
|
6
bundles/nginx/files/ssl_template
Normal file
6
bundles/nginx/files/ssl_template
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<%
|
||||||
|
from os.path import isfile, join
|
||||||
|
%><%include file="ssl/${domain}.crt.pem"/>
|
||||||
|
% if isfile(join(repo.path, 'data', 'ssl', f'{domain}.crt_intermediate.pem')):
|
||||||
|
<%include file="ssl/${domain}.crt_intermediate.pem"/>
|
||||||
|
% endif
|
6
bundles/nginx/files/stub_status
Normal file
6
bundles/nginx/files/stub_status
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
server {
|
||||||
|
listen 127.0.0.1:22999 default_server;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
stub_status;
|
||||||
|
}
|
178
bundles/nginx/items.py
Normal file
178
bundles/nginx/items.py
Normal file
|
@ -0,0 +1,178 @@
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
if node.has_bundle('pacman'):
|
||||||
|
package = 'pkg_pacman:nginx'
|
||||||
|
username = 'http'
|
||||||
|
else:
|
||||||
|
package = 'pkg_apt:nginx'
|
||||||
|
username = 'www-data'
|
||||||
|
|
||||||
|
directories = {
|
||||||
|
'/etc/nginx/sites': {
|
||||||
|
'purge': True,
|
||||||
|
'triggers': {
|
||||||
|
'svc_systemd:nginx:restart',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'/etc/nginx/security.txt.d': {
|
||||||
|
'purge': True,
|
||||||
|
},
|
||||||
|
'/etc/nginx/ssl': {
|
||||||
|
'purge': True,
|
||||||
|
'triggers': {
|
||||||
|
'svc_systemd:nginx:restart',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'/var/log/nginx-timing': {
|
||||||
|
'owner': username,
|
||||||
|
'needs': {
|
||||||
|
package,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'/var/www': {},
|
||||||
|
}
|
||||||
|
|
||||||
|
files = {
|
||||||
|
'/etc/logrotate.d/nginx': {
|
||||||
|
'source': 'logrotate.conf',
|
||||||
|
},
|
||||||
|
'/etc/nginx/nginx.conf': {
|
||||||
|
'content_type': 'mako',
|
||||||
|
'context': {
|
||||||
|
'username': username,
|
||||||
|
**node.metadata['nginx'],
|
||||||
|
},
|
||||||
|
'triggers': {
|
||||||
|
'svc_systemd:nginx:restart',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'/etc/nginx/fastcgi.conf': {
|
||||||
|
'triggers': {
|
||||||
|
'svc_systemd:nginx:restart',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'/etc/nginx/sites/stub_status': {
|
||||||
|
'triggers': {
|
||||||
|
'svc_systemd:nginx:restart',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'/etc/nginx/sites/000-port80.conf': {
|
||||||
|
'source': 'port80.conf',
|
||||||
|
'triggers': {
|
||||||
|
'svc_systemd:nginx:restart',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'/usr/local/share/icinga/plugins/check_nginx_status': {
|
||||||
|
'mode': '0755',
|
||||||
|
},
|
||||||
|
'/var/www/error.html': {},
|
||||||
|
'/var/www/not_found.html': {},
|
||||||
|
}
|
||||||
|
if node.has_bundle('pacman'):
|
||||||
|
files['/etc/systemd/system/nginx.service.d/bundlewrap.conf'] = {
|
||||||
|
'source': 'arch-override.conf',
|
||||||
|
'triggers': {
|
||||||
|
'action:systemd-reload',
|
||||||
|
'svc_systemd:nginx:restart',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
actions = {
|
||||||
|
'nginx-generate-dhparam': {
|
||||||
|
'command': 'openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048',
|
||||||
|
'unless': 'test -f /etc/ssl/certs/dhparam.pem',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
svc_systemd = {
|
||||||
|
'nginx': {
|
||||||
|
'needs': {
|
||||||
|
'action:nginx-generate-dhparam',
|
||||||
|
'directory:/var/log/nginx-timing',
|
||||||
|
package,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
now = datetime.now()
|
||||||
|
in_three_months = now + timedelta(days=90)
|
||||||
|
default_security_expiry = in_three_months.strftime('%Y-%m') + '-01T00:00:00.000Z'
|
||||||
|
|
||||||
|
for vhost, config in node.metadata.get('nginx/vhosts', {}).items():
|
||||||
|
if not 'domain' in config:
|
||||||
|
config['domain'] = vhost
|
||||||
|
|
||||||
|
security_txt_enabled = False
|
||||||
|
if (
|
||||||
|
node.metadata.get('nginx/security.txt/enabled', True) and
|
||||||
|
config.get('security.txt', {}).get('enabled', True)
|
||||||
|
):
|
||||||
|
security_txt_enabled = True
|
||||||
|
|
||||||
|
files[f'/etc/nginx/security.txt.d/{vhost}'] = {
|
||||||
|
'source': 'security.txt',
|
||||||
|
'content_type': 'mako',
|
||||||
|
'context': {
|
||||||
|
'domain': config['domain'],
|
||||||
|
'expiry': default_security_expiry,
|
||||||
|
'proto': 'https' if config.get('ssl', 'letsencrypt') else 'http',
|
||||||
|
'vhost': config.get('security.txt', node.metadata.get('nginx/security.txt', {})),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
files[f'/etc/nginx/sites/{vhost}'] = {
|
||||||
|
'source': 'site_template',
|
||||||
|
'content_type': 'mako',
|
||||||
|
'context': {
|
||||||
|
'create_access_log': config.get('access_log', node.metadata.get('nginx/access_log', False)),
|
||||||
|
'php_version': node.metadata.get('php/version', ''),
|
||||||
|
'security_txt': security_txt_enabled,
|
||||||
|
'vhost': vhost,
|
||||||
|
**config,
|
||||||
|
},
|
||||||
|
'needs': set(),
|
||||||
|
'needed_by': {
|
||||||
|
'svc_systemd:nginx',
|
||||||
|
'svc_systemd:nginx:restart',
|
||||||
|
},
|
||||||
|
'triggers': {
|
||||||
|
'svc_systemd:nginx:restart',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if not 'webroot' in config:
|
||||||
|
directories[f'/var/www/{vhost}'] = config.get('webroot_config', {})
|
||||||
|
|
||||||
|
if config.get('ssl', 'letsencrypt') == 'letsencrypt':
|
||||||
|
files[f'/etc/nginx/sites/{vhost}']['needs'].add('action:letsencrypt_ensure-some-certificate_{}'.format(config['domain']))
|
||||||
|
files[f'/etc/nginx/sites/{vhost}']['needed_by'].add('action:letsencrypt_update_certificates')
|
||||||
|
|
||||||
|
elif config.get('ssl', 'letsencrypt'):
|
||||||
|
files[f'/etc/nginx/ssl/{vhost}.crt'] = {
|
||||||
|
'content_type': 'mako',
|
||||||
|
'source': 'ssl_template',
|
||||||
|
'context': {
|
||||||
|
'domain': config['ssl'],
|
||||||
|
},
|
||||||
|
'needed_by': {
|
||||||
|
'svc_systemd:nginx',
|
||||||
|
'svc_systemd:nginx:restart',
|
||||||
|
},
|
||||||
|
'triggers': {
|
||||||
|
'svc_systemd:nginx:reload',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
files[f'/etc/nginx/ssl/{vhost}.key'] = {
|
||||||
|
'content': repo.vault.decrypt_file('ssl/{}.key.pem.vault'.format(config['ssl'])),
|
||||||
|
'mode': '0600',
|
||||||
|
'needed_by': {
|
||||||
|
'svc_systemd:nginx',
|
||||||
|
'svc_systemd:nginx:restart',
|
||||||
|
},
|
||||||
|
'triggers': {
|
||||||
|
'svc_systemd:nginx:reload',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
files[f'/etc/nginx/sites/{vhost}']['needs'].add(f'file:/etc/nginx/ssl/{vhost}.crt')
|
||||||
|
files[f'/etc/nginx/sites/{vhost}']['needs'].add(f'file:/etc/nginx/ssl/{vhost}.key')
|
209
bundles/nginx/metadata.py
Normal file
209
bundles/nginx/metadata.py
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
from bundlewrap.metadata import atomic
|
||||||
|
|
||||||
|
defaults = {
|
||||||
|
'apt': {
|
||||||
|
'repos': {
|
||||||
|
'nginx': {
|
||||||
|
'items': {
|
||||||
|
'deb http://nginx.org/packages/{os} {os_release} nginx',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'packages': {
|
||||||
|
'nginx': {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'backups': {
|
||||||
|
'paths': {
|
||||||
|
'/var/www',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'icinga2_api': {
|
||||||
|
'nginx': {
|
||||||
|
'services': {
|
||||||
|
'NGINX PROCESS': {
|
||||||
|
'command_on_monitored_host': '/usr/local/share/icinga/plugins/check_systemd_unit nginx',
|
||||||
|
},
|
||||||
|
'NGINX STATUS': {
|
||||||
|
'command_on_monitored_host': '/usr/local/share/icinga/plugins/check_nginx_status',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'nginx': {
|
||||||
|
'worker_connections': 768,
|
||||||
|
},
|
||||||
|
'pacman': {
|
||||||
|
'packages': {
|
||||||
|
'nginx': {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if node.has_bundle('telegraf'):
|
||||||
|
defaults['telegraf'] = {
|
||||||
|
'input_plugins': {
|
||||||
|
'builtin': {
|
||||||
|
'nginx': [{
|
||||||
|
'urls': ['http://localhost:22999/server_status'],
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@metadata_reactor.provides(
|
||||||
|
'nginx/worker_processes',
|
||||||
|
)
|
||||||
|
def worker_processes(metadata):
|
||||||
|
return {
|
||||||
|
'nginx': {
|
||||||
|
'worker_processes': metadata.get('vm/cpu', 2),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@metadata_reactor.provides(
|
||||||
|
'letsencrypt/domains',
|
||||||
|
'letsencrypt/reload_after',
|
||||||
|
'nginx/vhosts',
|
||||||
|
)
|
||||||
|
def letsencrypt(metadata):
|
||||||
|
if not node.has_bundle('letsencrypt'):
|
||||||
|
raise DoNotRunAgain
|
||||||
|
|
||||||
|
domains = {}
|
||||||
|
vhosts = {}
|
||||||
|
|
||||||
|
for vhost, config in metadata.get('nginx/vhosts', {}).items():
|
||||||
|
if config.get('ssl', 'letsencrypt') == 'letsencrypt':
|
||||||
|
domain = config.get('domain', vhost)
|
||||||
|
domains[domain] = config.get('domain_aliases', set())
|
||||||
|
vhosts[vhost] = {
|
||||||
|
'ssl': 'letsencrypt',
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
'letsencrypt': {
|
||||||
|
'domains': domains,
|
||||||
|
'reload_after': {
|
||||||
|
'nginx',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'nginx': {
|
||||||
|
'vhosts': vhosts,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@metadata_reactor.provides(
|
||||||
|
'nginx/vhosts',
|
||||||
|
)
|
||||||
|
def index_files(metadata):
|
||||||
|
vhosts = {}
|
||||||
|
|
||||||
|
for vhost, config in metadata.get('nginx/vhosts', {}).items():
|
||||||
|
vhosts[vhost] = {
|
||||||
|
'index': [
|
||||||
|
'index.html',
|
||||||
|
'index.htm',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.get('php', False):
|
||||||
|
# If we're using PHP, make sure index.php is tried first
|
||||||
|
vhosts[vhost]['index'].insert(0, 'index.php')
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
'nginx': {
|
||||||
|
'vhosts': vhosts,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@metadata_reactor.provides(
|
||||||
|
'icinga2_api/nginx/services',
|
||||||
|
)
|
||||||
|
def monitoring(metadata):
|
||||||
|
services = {}
|
||||||
|
|
||||||
|
for vname, vconfig in metadata.get('nginx/vhosts', {}).items():
|
||||||
|
domain = vconfig.get('domain', vname)
|
||||||
|
|
||||||
|
if vconfig['ssl']:
|
||||||
|
scheme = 'https'
|
||||||
|
else:
|
||||||
|
scheme = 'http'
|
||||||
|
|
||||||
|
if 'website_check_path' in vconfig and 'website_check_string' in vconfig:
|
||||||
|
services['NGINX VHOST {} CONTENT'.format(vname)] = {
|
||||||
|
'check_command': 'check_http_wget',
|
||||||
|
'vars.http_wget_contains': vconfig['website_check_string'],
|
||||||
|
'vars.http_wget_url': '{}://{}{}'.format(scheme, domain, vconfig['website_check_path']),
|
||||||
|
'vars.notification.sms': True,
|
||||||
|
}
|
||||||
|
|
||||||
|
if vconfig.get('check_ssl', vconfig['ssl']):
|
||||||
|
services['NGINX VHOST {} CERTIFICATE'.format(vname)] = {
|
||||||
|
'check_command': 'check_https_cert_at_url',
|
||||||
|
'vars.domain': domain,
|
||||||
|
'vars.notification.mail': True,
|
||||||
|
}
|
||||||
|
|
||||||
|
max_connections = metadata.get('nginx/worker_connections') * metadata.get('nginx/worker_processes')
|
||||||
|
connections_warn = int(max_connections * 0.8)
|
||||||
|
connections_crit = int(max_connections * 0.9)
|
||||||
|
|
||||||
|
services['NGINX STATUS'] = {
|
||||||
|
'command_on_monitored_host': '/usr/local/share/icinga/plugins/check_nginx_status --warn={},-1,-1 --critical={},-1,-1 -H 127.0.0.1:22999'.format(connections_warn, connections_crit),
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
'icinga2_api': {
|
||||||
|
'nginx': {
|
||||||
|
'services': services,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@metadata_reactor.provides(
|
||||||
|
'firewall/port_rules/80',
|
||||||
|
'firewall/port_rules/443',
|
||||||
|
)
|
||||||
|
def firewall(metadata):
|
||||||
|
return {
|
||||||
|
'firewall': {
|
||||||
|
'port_rules': {
|
||||||
|
'80': atomic(metadata.get('nginx/restrict-to', {'*'})),
|
||||||
|
'443': atomic(metadata.get('nginx/restrict-to', {'*'})),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@metadata_reactor.provides(
|
||||||
|
'telegraf/input_plugins/tail',
|
||||||
|
)
|
||||||
|
def telegraf_anon_timing(metadata):
|
||||||
|
result = {}
|
||||||
|
|
||||||
|
for vhost in metadata.get('nginx/vhosts', {}):
|
||||||
|
result[f'nginx-{vhost}'] = {
|
||||||
|
'files': [f'/var/log/nginx-timing/{vhost}.log'],
|
||||||
|
'from_beginning': False,
|
||||||
|
'grok_patterns': ['%{LOGPATTERN}'],
|
||||||
|
'grok_custom_patterns': 'LOGPATTERN \[%{HTTPDATE:ts:ts-httpd}\] %{NUMBER:request_time:float} (?:%{NUMBER:upstream_response_time:float}|-) "%{WORD:verb:tag} %{NOTSPACE:request} HTTP/%{NUMBER:http_version:float}" %{NUMBER:resp_code:tag}',
|
||||||
|
'data_format': 'grok',
|
||||||
|
'name_override': 'nginx_timing',
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
'telegraf': {
|
||||||
|
'input_plugins': {
|
||||||
|
'tail': result,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
8
bundles/openssh/files/override.conf
Normal file
8
bundles/openssh/files/override.conf
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# The default unit already has "Restart=on-failure", but it has set
|
||||||
|
# "RestartPreventExitStatus=255", which prevents a restart on that
|
||||||
|
# specific exit code. I don't think we want that. Please, just restart
|
||||||
|
# ssh.
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
RestartPreventExitStatus=
|
||||||
|
RestartSec=1
|
42
bundles/openssh/files/sshd_config
Normal file
42
bundles/openssh/files/sshd_config
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
Port 22
|
||||||
|
|
||||||
|
PermitRootLogin No
|
||||||
|
Protocol 2
|
||||||
|
AuthorizedKeysFile .ssh/authorized_keys
|
||||||
|
GSSAPIAuthentication no
|
||||||
|
KerberosAuthentication no
|
||||||
|
ChallengeResponseAuthentication no
|
||||||
|
PasswordAuthentication no
|
||||||
|
PubkeyAuthentication yes
|
||||||
|
UseDNS no
|
||||||
|
|
||||||
|
LogLevel INFO
|
||||||
|
X11Forwarding no
|
||||||
|
IgnoreRhosts yes
|
||||||
|
HostbasedAuthentication no
|
||||||
|
PermitEmptyPasswords no
|
||||||
|
PermitUserEnvironment no
|
||||||
|
Ciphers chacha20-poly1305@openssh.com,aes128-ctr,aes192-ctr,aes256-ctr,aes128-gcm@openssh.com,aes256-gcm@openssh.com
|
||||||
|
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-256,hmac-sha2-512
|
||||||
|
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group14-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,ecdh-sha2-nistp521,ecdh-sha2-nistp256,ecdh-sha2-nistp384,diffie-hellman-group-exchange-sha256
|
||||||
|
LoginGraceTime 60
|
||||||
|
AllowUsers ${' '.join(sorted(login_users))}
|
||||||
|
UsePAM yes
|
||||||
|
AllowTcpForwarding no
|
||||||
|
PrintMotd no
|
||||||
|
|
||||||
|
MaxSessions 512
|
||||||
|
MaxStartups 512:30:768
|
||||||
|
|
||||||
|
Subsystem sftp internal-sftp
|
||||||
|
|
||||||
|
Match Group sftp
|
||||||
|
ChrootDirectory %h
|
||||||
|
ForceCommand internal-sftp
|
||||||
|
PasswordAuthentication no
|
||||||
|
|
||||||
|
Match User ${','.join(sorted(admin_users))}
|
||||||
|
AllowTcpForwarding yes
|
||||||
|
% if enable_x_forwarding_for_admins:
|
||||||
|
X11Forwarding yes
|
||||||
|
% endif
|
55
bundles/openssh/items.py
Normal file
55
bundles/openssh/items.py
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
users_from_metadata = set()
|
||||||
|
additional_users = node.metadata.get('openssh/allowed_users', set())
|
||||||
|
|
||||||
|
for user, config in node.metadata.get('users', {}).items():
|
||||||
|
if 'ssh_pubkey' in config and not config.get('delete', False):
|
||||||
|
users_from_metadata.add(user)
|
||||||
|
|
||||||
|
login_users = users_from_metadata.union(additional_users)
|
||||||
|
|
||||||
|
files = {
|
||||||
|
'/etc/ssh/sshd_config': {
|
||||||
|
'content_type': 'mako',
|
||||||
|
'context': {
|
||||||
|
'login_users': login_users,
|
||||||
|
'admin_users': users_from_metadata,
|
||||||
|
'enable_x_forwarding_for_admins': node.metadata.get('openssh/enable_x_forwarding_for_admins', False),
|
||||||
|
},
|
||||||
|
'triggers': {
|
||||||
|
'action:sshd_check_config',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'/etc/systemd/system/ssh.service.d/bundlewrap.conf': {
|
||||||
|
'source': 'override.conf',
|
||||||
|
'triggers': {
|
||||||
|
'action:sshd_check_config',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if node.has_bundle('pacman'):
|
||||||
|
package = 'pkg_pacman:openssh'
|
||||||
|
service = 'sshd'
|
||||||
|
else:
|
||||||
|
package = 'pkg_apt:openssh-server'
|
||||||
|
service = 'ssh'
|
||||||
|
|
||||||
|
actions = {
|
||||||
|
'sshd_check_config': {
|
||||||
|
'command': 'sshd -T -C user=root -C host=localhost -C addr=localhost',
|
||||||
|
'triggered': True,
|
||||||
|
'triggers': {
|
||||||
|
'svc_systemd:{}:restart'.format(service),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
svc_systemd = {
|
||||||
|
service: {
|
||||||
|
'needs': {
|
||||||
|
'file:/etc/systemd/system/ssh.service.d/bundlewrap.conf',
|
||||||
|
'file:/etc/ssh/sshd_config',
|
||||||
|
package,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
28
bundles/openssh/metadata.py
Normal file
28
bundles/openssh/metadata.py
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
from bundlewrap.metadata import atomic
|
||||||
|
|
||||||
|
defaults = {
|
||||||
|
'apt': {
|
||||||
|
'packages': {
|
||||||
|
'openssh-client': {},
|
||||||
|
'openssh-server': {},
|
||||||
|
'openssh-sftp-server': {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'pacman': {
|
||||||
|
'packages': {
|
||||||
|
'openssh': {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
@metadata_reactor.provides(
|
||||||
|
'firewall/port_rules/22',
|
||||||
|
)
|
||||||
|
def firewall(metadata):
|
||||||
|
return {
|
||||||
|
'firewall': {
|
||||||
|
'port_rules': {
|
||||||
|
'22': atomic(metadata.get('openssh/restrict-to', {'*'})),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
23
bundles/php/files/7.3/fpm.conf
Normal file
23
bundles/php/files/7.3/fpm.conf
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
[global]
|
||||||
|
pid=/run/php/php7.4-fpm.pid
|
||||||
|
; We're using journal, put logs there
|
||||||
|
error_log=/var/log/php7.4-fpm.log
|
||||||
|
daemonize=yes
|
||||||
|
|
||||||
|
; The one and only worker pool we have
|
||||||
|
[www]
|
||||||
|
user=www-data
|
||||||
|
group=www-data
|
||||||
|
listen=/run/php/php7.4-fpm.sock
|
||||||
|
listen.owner=www-data
|
||||||
|
listen.group=www-data
|
||||||
|
listen.mode=0600
|
||||||
|
|
||||||
|
; Process Manager Settings
|
||||||
|
pm=dynamic
|
||||||
|
pm.max_children=${num_cpus*4}
|
||||||
|
pm.start_servers=${num_cpus}
|
||||||
|
pm.max_spare_servers=${num_cpus*2}
|
||||||
|
pm.min_spare_servers=${num_cpus}
|
||||||
|
pm.process_idle_timeout=30s
|
||||||
|
pm.max_requests=1024
|
99
bundles/php/files/7.3/php.ini
Normal file
99
bundles/php/files/7.3/php.ini
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
[PHP]
|
||||||
|
; Only needed for libapache2-mod-php?
|
||||||
|
engine = On
|
||||||
|
short_open_tag = Off
|
||||||
|
precision = 14
|
||||||
|
output_buffering = 4096
|
||||||
|
zlib.output_compression = Off
|
||||||
|
implicit_flush = Off
|
||||||
|
serialize_precision = -1
|
||||||
|
disable_functions = pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals
|
||||||
|
ignore_user_abort = Off
|
||||||
|
zend.enable_gc = On
|
||||||
|
expose_php = Off
|
||||||
|
|
||||||
|
max_execution_time = 30
|
||||||
|
max_input_time = 60
|
||||||
|
memory_limit = 256M
|
||||||
|
|
||||||
|
error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT
|
||||||
|
display_startup_errors = Off
|
||||||
|
log_errors = On
|
||||||
|
log_errors_max_len = 1024
|
||||||
|
ignore_repeated_errors = Off
|
||||||
|
ignore_repeated_source = Off
|
||||||
|
report_memleaks = On
|
||||||
|
html_errors = On
|
||||||
|
error_log = syslog
|
||||||
|
syslog.ident = php7.4
|
||||||
|
syslog.filter = ascii
|
||||||
|
|
||||||
|
arg_separator.output = "&"
|
||||||
|
variables_order = "GPCS"
|
||||||
|
request_order = "GP"
|
||||||
|
register_argc_argv = Off
|
||||||
|
auto_globals_jit = On
|
||||||
|
post_max_size = ${post_max_size}M
|
||||||
|
default_mimetype = "text/html"
|
||||||
|
default_charset = "UTF-8"
|
||||||
|
|
||||||
|
enable_dl = Off
|
||||||
|
file_uploads = On
|
||||||
|
upload_max_filesize = ${post_max_size}M
|
||||||
|
max_file_uploads = 20
|
||||||
|
|
||||||
|
allow_url_fopen = On
|
||||||
|
allow_url_include = Off
|
||||||
|
default_socket_timeout = 10
|
||||||
|
|
||||||
|
[CLI Server]
|
||||||
|
cli_server.color = On
|
||||||
|
|
||||||
|
[mail function]
|
||||||
|
mail.add_x_header = Off
|
||||||
|
|
||||||
|
[ODBC]
|
||||||
|
odbc.allow_persistent = On
|
||||||
|
odbc.check_persistent = On
|
||||||
|
odbc.max_persistent = -1
|
||||||
|
odbc.max_links = -1
|
||||||
|
odbc.defaultlrl = 4096
|
||||||
|
odbc.defaultbinmode = 1
|
||||||
|
|
||||||
|
[PostgreSQL]
|
||||||
|
pgsql.allow_persistent = On
|
||||||
|
pgsql.auto_reset_persistent = Off
|
||||||
|
pgsql.max_persistent = -1
|
||||||
|
pgsql.max_links = -1
|
||||||
|
pgsql.ignore_notice = 0
|
||||||
|
pgsql.log_notice = 0
|
||||||
|
|
||||||
|
[bcmath]
|
||||||
|
bcmath.scale = 0
|
||||||
|
|
||||||
|
[Session]
|
||||||
|
session.save_handler = files
|
||||||
|
session.use_strict_mode = 0
|
||||||
|
session.use_cookies = 1
|
||||||
|
session.use_only_cookies = 1
|
||||||
|
session.name = PHPSESSID
|
||||||
|
session.auto_start = 0
|
||||||
|
session.cookie_lifetime = 0
|
||||||
|
session.cookie_path = /
|
||||||
|
session.cookie_domain =
|
||||||
|
session.cookie_httponly =
|
||||||
|
session.cookie_samesite =
|
||||||
|
session.serialize_handler = php
|
||||||
|
session.gc_probability = 1
|
||||||
|
session.gc_divisor = 1000
|
||||||
|
session.gc_maxlifetime = 1440
|
||||||
|
session.referer_check =
|
||||||
|
session.cache_limiter = nocache
|
||||||
|
session.cache_expire = 180
|
||||||
|
session.use_trans_sid = 0
|
||||||
|
session.sid_length = 32
|
||||||
|
session.trans_sid_tags = "a=href,area=href,frame=src,form="
|
||||||
|
session.sid_bits_per_character = 6
|
||||||
|
|
||||||
|
[Assertion]
|
||||||
|
zend.assertions = -1
|
23
bundles/php/files/7.4/fpm.conf
Normal file
23
bundles/php/files/7.4/fpm.conf
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
[global]
|
||||||
|
pid=/run/php/php7.4-fpm.pid
|
||||||
|
; We're using journal, put logs there
|
||||||
|
error_log=/var/log/php7.4-fpm.log
|
||||||
|
daemonize=yes
|
||||||
|
|
||||||
|
; The one and only worker pool we have
|
||||||
|
[www]
|
||||||
|
user=www-data
|
||||||
|
group=www-data
|
||||||
|
listen=/run/php/php7.4-fpm.sock
|
||||||
|
listen.owner=www-data
|
||||||
|
listen.group=www-data
|
||||||
|
listen.mode=0600
|
||||||
|
|
||||||
|
; Process Manager Settings
|
||||||
|
pm=dynamic
|
||||||
|
pm.max_children=${num_cpus*4}
|
||||||
|
pm.start_servers=${num_cpus}
|
||||||
|
pm.max_spare_servers=${num_cpus*2}
|
||||||
|
pm.min_spare_servers=${num_cpus}
|
||||||
|
pm.process_idle_timeout=30s
|
||||||
|
pm.max_requests=1024
|
99
bundles/php/files/7.4/php.ini
Normal file
99
bundles/php/files/7.4/php.ini
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
[PHP]
|
||||||
|
; Only needed for libapache2-mod-php?
|
||||||
|
engine = On
|
||||||
|
short_open_tag = Off
|
||||||
|
precision = 14
|
||||||
|
output_buffering = 4096
|
||||||
|
zlib.output_compression = Off
|
||||||
|
implicit_flush = Off
|
||||||
|
serialize_precision = -1
|
||||||
|
disable_functions = pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals
|
||||||
|
ignore_user_abort = Off
|
||||||
|
zend.enable_gc = On
|
||||||
|
expose_php = Off
|
||||||
|
|
||||||
|
max_execution_time = 30
|
||||||
|
max_input_time = 60
|
||||||
|
memory_limit = 256M
|
||||||
|
|
||||||
|
error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT
|
||||||
|
display_startup_errors = Off
|
||||||
|
log_errors = On
|
||||||
|
log_errors_max_len = 1024
|
||||||
|
ignore_repeated_errors = Off
|
||||||
|
ignore_repeated_source = Off
|
||||||
|
report_memleaks = On
|
||||||
|
html_errors = On
|
||||||
|
error_log = syslog
|
||||||
|
syslog.ident = php7.4
|
||||||
|
syslog.filter = ascii
|
||||||
|
|
||||||
|
arg_separator.output = "&"
|
||||||
|
variables_order = "GPCS"
|
||||||
|
request_order = "GP"
|
||||||
|
register_argc_argv = Off
|
||||||
|
auto_globals_jit = On
|
||||||
|
post_max_size = ${post_max_size}M
|
||||||
|
default_mimetype = "text/html"
|
||||||
|
default_charset = "UTF-8"
|
||||||
|
|
||||||
|
enable_dl = Off
|
||||||
|
file_uploads = On
|
||||||
|
upload_max_filesize = ${post_max_size}M
|
||||||
|
max_file_uploads = 20
|
||||||
|
|
||||||
|
allow_url_fopen = On
|
||||||
|
allow_url_include = Off
|
||||||
|
default_socket_timeout = 10
|
||||||
|
|
||||||
|
[CLI Server]
|
||||||
|
cli_server.color = On
|
||||||
|
|
||||||
|
[mail function]
|
||||||
|
mail.add_x_header = Off
|
||||||
|
|
||||||
|
[ODBC]
|
||||||
|
odbc.allow_persistent = On
|
||||||
|
odbc.check_persistent = On
|
||||||
|
odbc.max_persistent = -1
|
||||||
|
odbc.max_links = -1
|
||||||
|
odbc.defaultlrl = 4096
|
||||||
|
odbc.defaultbinmode = 1
|
||||||
|
|
||||||
|
[PostgreSQL]
|
||||||
|
pgsql.allow_persistent = On
|
||||||
|
pgsql.auto_reset_persistent = Off
|
||||||
|
pgsql.max_persistent = -1
|
||||||
|
pgsql.max_links = -1
|
||||||
|
pgsql.ignore_notice = 0
|
||||||
|
pgsql.log_notice = 0
|
||||||
|
|
||||||
|
[bcmath]
|
||||||
|
bcmath.scale = 0
|
||||||
|
|
||||||
|
[Session]
|
||||||
|
session.save_handler = files
|
||||||
|
session.use_strict_mode = 0
|
||||||
|
session.use_cookies = 1
|
||||||
|
session.use_only_cookies = 1
|
||||||
|
session.name = PHPSESSID
|
||||||
|
session.auto_start = 0
|
||||||
|
session.cookie_lifetime = 0
|
||||||
|
session.cookie_path = /
|
||||||
|
session.cookie_domain =
|
||||||
|
session.cookie_httponly =
|
||||||
|
session.cookie_samesite =
|
||||||
|
session.serialize_handler = php
|
||||||
|
session.gc_probability = 1
|
||||||
|
session.gc_divisor = 1000
|
||||||
|
session.gc_maxlifetime = 1440
|
||||||
|
session.referer_check =
|
||||||
|
session.cache_limiter = nocache
|
||||||
|
session.cache_expire = 180
|
||||||
|
session.use_trans_sid = 0
|
||||||
|
session.sid_length = 32
|
||||||
|
session.trans_sid_tags = "a=href,area=href,frame=src,form="
|
||||||
|
session.sid_bits_per_character = 6
|
||||||
|
|
||||||
|
[Assertion]
|
||||||
|
zend.assertions = -1
|
27
bundles/php/files/8.0/fpm.conf
Normal file
27
bundles/php/files/8.0/fpm.conf
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
[global]
|
||||||
|
pid=/run/php/php8.0-fpm.pid
|
||||||
|
; We're using journal, put logs there
|
||||||
|
error_log=/var/log/php8.0-fpm.log
|
||||||
|
daemonize=yes
|
||||||
|
|
||||||
|
; The one and only worker pool we have
|
||||||
|
[www]
|
||||||
|
user=www-data
|
||||||
|
group=www-data
|
||||||
|
listen=/run/php/php8.0-fpm.sock
|
||||||
|
listen.owner=www-data
|
||||||
|
listen.group=www-data
|
||||||
|
listen.mode=0600
|
||||||
|
|
||||||
|
; Process Manager Settings
|
||||||
|
pm=dynamic
|
||||||
|
pm.max_children=${num_cpus*4}
|
||||||
|
pm.start_servers=${num_cpus}
|
||||||
|
pm.max_spare_servers=${num_cpus*2}
|
||||||
|
pm.min_spare_servers=${num_cpus}
|
||||||
|
pm.process_idle_timeout=30s
|
||||||
|
pm.max_requests=1024
|
||||||
|
|
||||||
|
% if not clear_env:
|
||||||
|
clear_env=no
|
||||||
|
% endif
|
99
bundles/php/files/8.0/php.ini
Normal file
99
bundles/php/files/8.0/php.ini
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
[PHP]
|
||||||
|
; Only needed for libapache2-mod-php?
|
||||||
|
engine = On
|
||||||
|
short_open_tag = Off
|
||||||
|
precision = 14
|
||||||
|
output_buffering = 4096
|
||||||
|
zlib.output_compression = Off
|
||||||
|
implicit_flush = Off
|
||||||
|
serialize_precision = -1
|
||||||
|
disable_functions = pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals
|
||||||
|
ignore_user_abort = Off
|
||||||
|
zend.enable_gc = On
|
||||||
|
expose_php = Off
|
||||||
|
|
||||||
|
max_execution_time = 30
|
||||||
|
max_input_time = 60
|
||||||
|
memory_limit = ${memory_limit}M
|
||||||
|
|
||||||
|
error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT
|
||||||
|
display_startup_errors = Off
|
||||||
|
log_errors = On
|
||||||
|
log_errors_max_len = 1024
|
||||||
|
ignore_repeated_errors = Off
|
||||||
|
ignore_repeated_source = Off
|
||||||
|
report_memleaks = On
|
||||||
|
html_errors = On
|
||||||
|
error_log = syslog
|
||||||
|
syslog.ident = php7.4
|
||||||
|
syslog.filter = ascii
|
||||||
|
|
||||||
|
arg_separator.output = "&"
|
||||||
|
variables_order = "GPCS"
|
||||||
|
request_order = "GP"
|
||||||
|
register_argc_argv = Off
|
||||||
|
auto_globals_jit = On
|
||||||
|
post_max_size = ${post_max_size}M
|
||||||
|
default_mimetype = "text/html"
|
||||||
|
default_charset = "UTF-8"
|
||||||
|
|
||||||
|
enable_dl = Off
|
||||||
|
file_uploads = On
|
||||||
|
upload_max_filesize = ${post_max_size}M
|
||||||
|
max_file_uploads = 20
|
||||||
|
|
||||||
|
allow_url_fopen = On
|
||||||
|
allow_url_include = Off
|
||||||
|
default_socket_timeout = 10
|
||||||
|
|
||||||
|
[CLI Server]
|
||||||
|
cli_server.color = On
|
||||||
|
|
||||||
|
[mail function]
|
||||||
|
mail.add_x_header = Off
|
||||||
|
|
||||||
|
[ODBC]
|
||||||
|
odbc.allow_persistent = On
|
||||||
|
odbc.check_persistent = On
|
||||||
|
odbc.max_persistent = -1
|
||||||
|
odbc.max_links = -1
|
||||||
|
odbc.defaultlrl = 4096
|
||||||
|
odbc.defaultbinmode = 1
|
||||||
|
|
||||||
|
[PostgreSQL]
|
||||||
|
pgsql.allow_persistent = On
|
||||||
|
pgsql.auto_reset_persistent = Off
|
||||||
|
pgsql.max_persistent = -1
|
||||||
|
pgsql.max_links = -1
|
||||||
|
pgsql.ignore_notice = 0
|
||||||
|
pgsql.log_notice = 0
|
||||||
|
|
||||||
|
[bcmath]
|
||||||
|
bcmath.scale = 0
|
||||||
|
|
||||||
|
[Session]
|
||||||
|
session.save_handler = files
|
||||||
|
session.use_strict_mode = 0
|
||||||
|
session.use_cookies = 1
|
||||||
|
session.use_only_cookies = 1
|
||||||
|
session.name = PHPSESSID
|
||||||
|
session.auto_start = 0
|
||||||
|
session.cookie_lifetime = 0
|
||||||
|
session.cookie_path = /
|
||||||
|
session.cookie_domain =
|
||||||
|
session.cookie_httponly =
|
||||||
|
session.cookie_samesite =
|
||||||
|
session.serialize_handler = php
|
||||||
|
session.gc_probability = 1
|
||||||
|
session.gc_divisor = 1000
|
||||||
|
session.gc_maxlifetime = 1440
|
||||||
|
session.referer_check =
|
||||||
|
session.cache_limiter = nocache
|
||||||
|
session.cache_expire = 180
|
||||||
|
session.use_trans_sid = 0
|
||||||
|
session.sid_length = 32
|
||||||
|
session.trans_sid_tags = "a=href,area=href,frame=src,form="
|
||||||
|
session.sid_bits_per_character = 6
|
||||||
|
|
||||||
|
[Assertion]
|
||||||
|
zend.assertions = -1
|
58
bundles/php/items.py
Normal file
58
bundles/php/items.py
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
version = node.metadata['php']['version']
|
||||||
|
|
||||||
|
files = {
|
||||||
|
f'/etc/php/{version}/fpm/php-fpm.conf': {
|
||||||
|
'source': f'{version}/fpm.conf',
|
||||||
|
'content_type': 'mako',
|
||||||
|
'context': {
|
||||||
|
'num_cpus': node.metadata['vm']['cpu'],
|
||||||
|
'clear_env': node.metadata.get('php/clear_env', True),
|
||||||
|
},
|
||||||
|
'needs': {
|
||||||
|
# "all php packages"
|
||||||
|
'pkg_apt:'
|
||||||
|
},
|
||||||
|
'triggers': {
|
||||||
|
f'svc_systemd:php{version}-fpm:restart',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
f'/etc/php/{version}/fpm/php.ini': {
|
||||||
|
'source': f'{version}/php.ini',
|
||||||
|
'content_type': 'mako',
|
||||||
|
'context': {
|
||||||
|
'num_cpus': node.metadata['vm']['cpu'],
|
||||||
|
'post_max_size': node.metadata['php'].get('post_max_size', 10),
|
||||||
|
'memory_limit': node.metadata.get('php/memory_limit', 256),
|
||||||
|
},
|
||||||
|
'needs': {
|
||||||
|
# "all php packages"
|
||||||
|
'pkg_apt:'
|
||||||
|
},
|
||||||
|
'triggers': {
|
||||||
|
f'svc_systemd:php{version}-fpm:restart',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
f'/etc/php/{version}/cli/php.ini': {
|
||||||
|
'source': f'{version}/php.ini',
|
||||||
|
'content_type': 'mako',
|
||||||
|
'context': {
|
||||||
|
'num_cpus': node.metadata['vm']['cpu'],
|
||||||
|
'post_max_size': node.metadata['php'].get('post_max_size', 10),
|
||||||
|
'memory_limit': node.metadata.get('php/memory_limit', 256),
|
||||||
|
},
|
||||||
|
'needs': {
|
||||||
|
# "all php packages"
|
||||||
|
'pkg_apt:'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
svc_systemd = {
|
||||||
|
f'php{version}-fpm': {
|
||||||
|
'needs': {
|
||||||
|
'pkg_apt:',
|
||||||
|
f'file:/etc/php/{version}/fpm/php-fpm.conf',
|
||||||
|
f'file:/etc/php/{version}/fpm/php.ini',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
37
bundles/php/metadata.py
Normal file
37
bundles/php/metadata.py
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
defaults = {
|
||||||
|
'apt': {
|
||||||
|
'repos': {
|
||||||
|
'php': {
|
||||||
|
'items': {
|
||||||
|
'deb https://packages.sury.org/php/ {os_release} main',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@metadata_reactor.provides(
|
||||||
|
'apt/packages',
|
||||||
|
)
|
||||||
|
def php_packages_with_features(metadata):
|
||||||
|
version = metadata.get('php/version')
|
||||||
|
|
||||||
|
packages = {
|
||||||
|
f'php{version}': {},
|
||||||
|
f'php{version}-cli': {},
|
||||||
|
f'php{version}-fpm': {},
|
||||||
|
}
|
||||||
|
|
||||||
|
for package in metadata.get('php/packages', set()):
|
||||||
|
packages[f'php{version}-{package}'] = {
|
||||||
|
'triggers': {
|
||||||
|
f'svc_systemd:php{version}-fpm:restart',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
'apt': {
|
||||||
|
'packages': packages,
|
||||||
|
},
|
||||||
|
}
|
9
bundles/postgresql/files/backup-pre-hook
Normal file
9
bundles/postgresql/files/backup-pre-hook
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
target="/var/tmp/postgresdumps"
|
||||||
|
|
||||||
|
pg_dumpall --globals-only | gzip --quiet --rsyncable >"$target/globals.sql.gz"
|
||||||
|
|
||||||
|
% for db in sorted(databases):
|
||||||
|
pg_dump -C "${db}" | gzip --quiet --rsyncable >"$target/db_${db}.sql.gz"
|
||||||
|
% endfor
|
8
bundles/postgresql/files/pg_hba.conf
Normal file
8
bundles/postgresql/files/pg_hba.conf
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
% for custom_rule in sorted(node.metadata.get('postgresql/custom_rules', [])):
|
||||||
|
${custom_rule}
|
||||||
|
% endfor
|
||||||
|
local all postgres peer
|
||||||
|
local all all peer
|
||||||
|
host all all 127.0.0.1/32 md5
|
||||||
|
host all all ::1/128 md5
|
||||||
|
host all all all md5
|
32
bundles/postgresql/files/postgresql.conf
Normal file
32
bundles/postgresql/files/postgresql.conf
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
data_directory = '/var/lib/postgresql/${version}/main'
|
||||||
|
hba_file = '/etc/postgresql/${version}/main/pg_hba.conf'
|
||||||
|
ident_file = '/etc/postgresql/${version}/main/pg_ident.conf'
|
||||||
|
external_pid_file = '/var/run/postgresql/${version}-main.pid'
|
||||||
|
unix_socket_directories = '/var/run/postgresql'
|
||||||
|
port = 5432
|
||||||
|
listen_addresses = 'localhost'
|
||||||
|
max_connections = ${max_connections}
|
||||||
|
autovacuum_max_workers = ${autovacuum_max_workers}
|
||||||
|
maintenance_work_mem = ${maintenance_work_mem}MB
|
||||||
|
work_mem = ${work_mem}MB
|
||||||
|
shared_buffers = ${shared_buffers}MB
|
||||||
|
temp_buffers = ${temp_buffers}MB
|
||||||
|
log_destination = syslog
|
||||||
|
datestyle = 'iso, ymd'
|
||||||
|
timezone = 'localtime'
|
||||||
|
lc_messages = 'en_US.UTF-8'
|
||||||
|
lc_monetary = 'en_US.UTF-8'
|
||||||
|
lc_numeric = 'en_US.UTF-8'
|
||||||
|
lc_time = 'en_US.UTF-8'
|
||||||
|
default_text_search_config = 'pg_catalog.english'
|
||||||
|
% if slow_query_log_sec > 0:
|
||||||
|
log_min_duration_statement = ${slow_query_log_sec*1000}
|
||||||
|
% else:
|
||||||
|
log_min_duration_statement = -1
|
||||||
|
% endif
|
||||||
|
effective_io_concurrency = ${effective_io_concurrency}
|
||||||
|
max_worker_processes = ${max_worker_processes}
|
||||||
|
% if version_list >= [10]:
|
||||||
|
max_parallel_workers = ${max_parallel_workers}
|
||||||
|
% endif
|
||||||
|
max_parallel_workers_per_gather = ${max_parallel_workers_per_gather}
|
124
bundles/postgresql/items.py
Normal file
124
bundles/postgresql/items.py
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
postgresql_version = node.metadata['postgresql']['version']
|
||||||
|
|
||||||
|
pkg_apt = {
|
||||||
|
'postgresql-common': {},
|
||||||
|
'postgresql-client-common': {},
|
||||||
|
'postgresql-{}'.format(postgresql_version): {},
|
||||||
|
'postgresql-client-{}'.format(postgresql_version): {},
|
||||||
|
'postgresql-server-dev-{}'.format(postgresql_version): {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if node.has_bundle('zfs'):
|
||||||
|
for pkgname, pkgconfig in pkg_apt.items():
|
||||||
|
pkg_apt[pkgname]['needs'] = {
|
||||||
|
'zfs_dataset:tank/postgresql',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
directories = {
|
||||||
|
'/etc/postgresql': {
|
||||||
|
'owner': None,
|
||||||
|
'group': None,
|
||||||
|
'mode': None,
|
||||||
|
# Keeping old configs messes with cluster-auto-detection.
|
||||||
|
'purge': True,
|
||||||
|
},
|
||||||
|
# This is needed so the above purge does not remove the version
|
||||||
|
# currently installed.
|
||||||
|
'/etc/postgresql/{}/main'.format(postgresql_version): {
|
||||||
|
'owner': 'postgres',
|
||||||
|
'group': 'postgres',
|
||||||
|
'mode': '0755',
|
||||||
|
'needs': {f'pkg_apt:{i}' for i in pkg_apt.keys()},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
files = {
|
||||||
|
"/etc/postgresql/{}/main/pg_hba.conf".format(postgresql_version): {
|
||||||
|
'content_type': 'mako',
|
||||||
|
'owner': 'postgres',
|
||||||
|
'group': 'postgres',
|
||||||
|
'triggers': {
|
||||||
|
'svc_systemd:postgresql:restart',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"/etc/postgresql/{}/main/postgresql.conf".format(postgresql_version): {
|
||||||
|
'content_type': 'mako',
|
||||||
|
'context': {
|
||||||
|
'version_list': [int(i) for i in node.metadata['postgresql']['version'].split('.')],
|
||||||
|
**node.metadata['postgresql'],
|
||||||
|
},
|
||||||
|
'owner': 'postgres',
|
||||||
|
'group': 'postgres',
|
||||||
|
'needs': {
|
||||||
|
# postgresql won't start if the configured locale isn't available
|
||||||
|
'action:locale-gen',
|
||||||
|
} if node.has_bundle('basic') else set(),
|
||||||
|
'triggers': {
|
||||||
|
'svc_systemd:postgresql:restart',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if node.has_bundle('backup-client') and not node.has_bundle('zfs'):
|
||||||
|
files['/etc/backup-pre-hooks.d/90-postgresql-dump-all'] = {
|
||||||
|
'source': 'backup-pre-hook',
|
||||||
|
'content_type': 'mako',
|
||||||
|
'context': {
|
||||||
|
'databases': node.metadata.get('postgresql/databases', {}).keys(),
|
||||||
|
},
|
||||||
|
'mode': '0700',
|
||||||
|
}
|
||||||
|
directories['/var/tmp/postgresdumps'] = {}
|
||||||
|
else:
|
||||||
|
files['/var/tmp/postgresdumps'] = {
|
||||||
|
'delete': True,
|
||||||
|
}
|
||||||
|
|
||||||
|
postgres_roles = {
|
||||||
|
'root': {
|
||||||
|
'password': repo.vault.password_for('{} postgresql root'.format(node.name)),
|
||||||
|
'superuser': True,
|
||||||
|
'needs': {
|
||||||
|
'svc_systemd:postgresql',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
restart_deps = {
|
||||||
|
f'file:/etc/postgresql/{postgresql_version}/main/pg_hba.conf',
|
||||||
|
f'file:/etc/postgresql/{postgresql_version}/main/postgresql.conf',
|
||||||
|
*{f'pkg_apt:{i}' for i in pkg_apt.keys()},
|
||||||
|
}
|
||||||
|
|
||||||
|
svc_systemd = {
|
||||||
|
'postgresql': {
|
||||||
|
'needs': restart_deps,
|
||||||
|
'triggers': {
|
||||||
|
'action:postgresql_wait_after_restart',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
actions = {
|
||||||
|
'postgresql_wait_after_restart': {
|
||||||
|
# postgresql doesn't accept connections immediately after restarting
|
||||||
|
'command': 'sleep 10',
|
||||||
|
'triggered': True,
|
||||||
|
'before': {
|
||||||
|
'postgres_role:',
|
||||||
|
'postgres_db:',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for user, config in node.metadata.get('postgresql/roles', {}).items():
|
||||||
|
postgres_roles[user] = {
|
||||||
|
'password': config['password'],
|
||||||
|
'needs': {
|
||||||
|
'svc_systemd:postgresql',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for database, config in node.metadata.get('postgresql/databases', {}).items():
|
||||||
|
postgres_dbs[database] = config
|
127
bundles/postgresql/metadata.py
Normal file
127
bundles/postgresql/metadata.py
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
defaults = {
|
||||||
|
'backups': {
|
||||||
|
'paths': {
|
||||||
|
'/var/lib/postgresql',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'bash_functions': {
|
||||||
|
'pg_query_mon': "watch -n 2 \"echo \\\"SELECT pid, age(clock_timestamp(), query_start), usename, query FROM pg_stat_activity WHERE query != '<IDLE>' AND query NOT ILIKE '%pg_stat_activity%' ORDER BY query_start desc;\\\" | psql postgres\""
|
||||||
|
},
|
||||||
|
'icinga2_api': {
|
||||||
|
'postgresql': {
|
||||||
|
'services': {
|
||||||
|
'POSTGRESQL PROCESS': {
|
||||||
|
'command_on_monitored_host': '/usr/lib/nagios/plugins/check_procs -C postgres -c 1:',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'postgresql': {
|
||||||
|
'max_connections': 100,
|
||||||
|
'autovacuum_max_workers': 3,
|
||||||
|
'maintenance_work_mem': 64,
|
||||||
|
'work_mem': 4,
|
||||||
|
'shared_buffers': 128,
|
||||||
|
'temp_buffers': 8,
|
||||||
|
'slow_query_log_sec': 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if node.has_bundle('telegraf'):
|
||||||
|
defaults['telegraf'] = {
|
||||||
|
'input_plugins': {
|
||||||
|
'builtin': {
|
||||||
|
'postgresql': [{
|
||||||
|
'address': repo.vault.password_for(f'{node.name} postgresql telegraf').format_into('postgres://telegraf:{}@localhost:5432/telegraf?sslmode=disable'),
|
||||||
|
'ignored_databases': [
|
||||||
|
'template0',
|
||||||
|
'template1',
|
||||||
|
'telegraf',
|
||||||
|
],
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
defaults['postgresql'].update({
|
||||||
|
'roles': {
|
||||||
|
'telegraf': {
|
||||||
|
'password': repo.vault.password_for(f'{node.name} postgresql telegraf'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'databases': {
|
||||||
|
'telegraf': {
|
||||||
|
'owner': 'telegraf',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if node.has_bundle('zfs'):
|
||||||
|
defaults['zfs'] = {
|
||||||
|
'datasets': {
|
||||||
|
'tank/postgresql': {
|
||||||
|
'mountpoint': '/var/lib/postgresql',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
defaults['backups']['paths'].add('/var/tmp/postgresdumps')
|
||||||
|
|
||||||
|
|
||||||
|
@metadata_reactor.provides(
|
||||||
|
'apt/repos/postgresql',
|
||||||
|
'postgresql/version',
|
||||||
|
)
|
||||||
|
def default_postgresql_version_for_debian(metadata):
|
||||||
|
# <https://packages.debian.org/search?keywords=postgresql>
|
||||||
|
versions_in_debian = {
|
||||||
|
'10': '11', # buster
|
||||||
|
'11': '13', # bullseye
|
||||||
|
}
|
||||||
|
os = str(node.os_version[0])
|
||||||
|
version_to_be_installed = metadata.get('postgresql/version', versions_in_debian[os])
|
||||||
|
|
||||||
|
if version_to_be_installed != versions_in_debian[os]:
|
||||||
|
return {
|
||||||
|
'apt': {
|
||||||
|
'repos': {
|
||||||
|
'postgresql': {
|
||||||
|
'items': {
|
||||||
|
'deb https://apt.postgresql.org/pub/repos/apt/ {os_release}-pgdg main',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'postgresql': {
|
||||||
|
'version': version_to_be_installed,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
'postgresql': {
|
||||||
|
'version': version_to_be_installed,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@metadata_reactor.provides(
|
||||||
|
'postgresql/effective_io_concurrency',
|
||||||
|
'postgresql/max_worker_processes',
|
||||||
|
'postgresql/max_parallel_workers',
|
||||||
|
'postgresql/max_parallel_workers_per_gather',
|
||||||
|
)
|
||||||
|
def worker_processes(metadata):
|
||||||
|
return {
|
||||||
|
'postgresql': {
|
||||||
|
# This is the amount of parallel I/O Operations the
|
||||||
|
# postgresql process is allowed to do on disk. We set
|
||||||
|
# this to max_connections by default.
|
||||||
|
'effective_io_concurrency': metadata.get('postgresql/max_connections'),
|
||||||
|
|
||||||
|
# Try to request one worker process per 10 configured
|
||||||
|
# connections. The default is 8 for both of these values.
|
||||||
|
'max_worker_processes': int(metadata.get('postgresql/max_connections')/10),
|
||||||
|
'max_parallel_workers': int(metadata.get('postgresql/max_connections')/10),
|
||||||
|
# default 2
|
||||||
|
'max_parallel_workers_per_gather': max(int(metadata.get('postgresql/max_connections')/100), 2),
|
||||||
|
},
|
||||||
|
}
|
50
bundles/redis/files/redis.conf
Normal file
50
bundles/redis/files/redis.conf
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
activerehashing yes
|
||||||
|
aof-load-truncated yes
|
||||||
|
aof-rewrite-incremental-fsync yes
|
||||||
|
appendfilename "appendonly.aof"
|
||||||
|
appendfsync everysec
|
||||||
|
appendonly ${node.metadata.get('redis/appendonly', "no")}
|
||||||
|
auto-aof-rewrite-min-size 64mb
|
||||||
|
auto-aof-rewrite-percentage 100
|
||||||
|
bind ${node.metadata.get('redis/bind', '127.0.0.1')}
|
||||||
|
client-output-buffer-limit normal 0 0 0
|
||||||
|
client-output-buffer-limit pubsub 32mb 8mb 60
|
||||||
|
client-output-buffer-limit slave 256mb 64mb 60
|
||||||
|
daemonize yes
|
||||||
|
databases 16
|
||||||
|
dbfilename dump.rdb
|
||||||
|
dir /var/lib/redis
|
||||||
|
hash-max-ziplist-entries 512
|
||||||
|
hash-max-ziplist-value 64
|
||||||
|
hll-sparse-max-bytes 3000
|
||||||
|
hz 10
|
||||||
|
latency-monitor-threshold 0
|
||||||
|
list-max-ziplist-entries 512
|
||||||
|
list-max-ziplist-value 64
|
||||||
|
logfile /var/log/redis/redis-server.log
|
||||||
|
loglevel notice
|
||||||
|
lua-time-limit 5000
|
||||||
|
no-appendfsync-on-rewrite no
|
||||||
|
notify-keyspace-events ""
|
||||||
|
pidfile /var/run/redis/redis-server.pid
|
||||||
|
port 6379
|
||||||
|
rdbchecksum yes
|
||||||
|
rdbcompression yes
|
||||||
|
repl-disable-tcp-nodelay no
|
||||||
|
repl-diskless-sync no
|
||||||
|
repl-diskless-sync-delay 5
|
||||||
|
save 300 10
|
||||||
|
save 60 10000
|
||||||
|
save 900 1
|
||||||
|
set-max-intset-entries 512
|
||||||
|
slave-priority 100
|
||||||
|
slave-read-only yes
|
||||||
|
slave-serve-stale-data yes
|
||||||
|
slowlog-log-slower-than 10000
|
||||||
|
slowlog-max-len 128
|
||||||
|
stop-writes-on-bgsave-error yes
|
||||||
|
tcp-backlog 511
|
||||||
|
tcp-keepalive 0
|
||||||
|
timeout 0
|
||||||
|
zset-max-ziplist-entries 128
|
||||||
|
zset-max-ziplist-value 64
|
22
bundles/redis/items.py
Normal file
22
bundles/redis/items.py
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
svc_systemd = {
|
||||||
|
'redis-server': {
|
||||||
|
'needs': {
|
||||||
|
'file:/etc/redis/redis.conf',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
files = {
|
||||||
|
'/etc/redis/redis.conf': {
|
||||||
|
'content_type': 'mako',
|
||||||
|
'mode': '0640',
|
||||||
|
'owner': 'redis',
|
||||||
|
'group': 'redis',
|
||||||
|
'needs': {
|
||||||
|
'pkg_apt:redis-server',
|
||||||
|
},
|
||||||
|
'triggers': {
|
||||||
|
'svc_systemd:redis-server:restart',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
40
bundles/redis/metadata.py
Normal file
40
bundles/redis/metadata.py
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
defaults = {
|
||||||
|
'apt': {
|
||||||
|
'packages': {
|
||||||
|
'redis-server': {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'backups': {
|
||||||
|
'paths': {
|
||||||
|
'/var/lib/redis',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'icinga2_api': {
|
||||||
|
'redis': {
|
||||||
|
'services': {
|
||||||
|
'REDIS PROCESS': {
|
||||||
|
'command_on_monitored_host': '/usr/lib/nagios/plugins/check_procs -C redis-server -c 1:',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'zfs': {
|
||||||
|
'datasets': {
|
||||||
|
'tank/redis': {
|
||||||
|
'mountpoint': '/var/lib/redis',
|
||||||
|
'needed_by': {
|
||||||
|
'pkg_apt:redis-server',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if node.has_bundle('telegraf'):
|
||||||
|
defaults['telegraf'] = {
|
||||||
|
'input_plugins': {
|
||||||
|
'builtin': {
|
||||||
|
'redis': [{}],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
9
bundles/sudo/files/bwusers
Normal file
9
bundles/sudo/files/bwusers
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
% for user, config in sorted(node.metadata['users'].items()):
|
||||||
|
% if config.get('is_admin', False):
|
||||||
|
${user} ALL=(ALL) NOPASSWD:ALL
|
||||||
|
% else:
|
||||||
|
% for p in sorted(config.get('sudo_commands', [])):
|
||||||
|
${user} ALL=(ALL) NOPASSWD:${p}
|
||||||
|
% endfor
|
||||||
|
% endif
|
||||||
|
% endfor
|
10
bundles/sudo/files/sudoers
Normal file
10
bundles/sudo/files/sudoers
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
Defaults timestamp_timeout=5
|
||||||
|
Defaults passwd_timeout=10
|
||||||
|
Defaults env_reset
|
||||||
|
Defaults secure_path=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||||
|
Defaults !syslog
|
||||||
|
Defaults !pam_session
|
||||||
|
|
||||||
|
root ALL=(ALL) ALL
|
||||||
|
|
||||||
|
#includedir /etc/sudoers.d
|
27
bundles/sudo/items.py
Normal file
27
bundles/sudo/items.py
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
groups = {
|
||||||
|
'sudo': {},
|
||||||
|
}
|
||||||
|
|
||||||
|
directories = {
|
||||||
|
'/etc/sudoers.d': {
|
||||||
|
'purge': True,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
files = {
|
||||||
|
'/etc/sudoers': {
|
||||||
|
'mode': '0440',
|
||||||
|
'needs': {
|
||||||
|
'file:/etc/sudoers.d/bwusers',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'/etc/sudoers.d/bwusers': {
|
||||||
|
'content_type': 'mako',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for filename, content in node.metadata.get('sudo/extra_configs', {}).items():
|
||||||
|
files[f'/etc/sudoers.d/{filename}'] = {
|
||||||
|
'content': '\n'.join(sorted(content)) + '\n',
|
||||||
|
'mode': '0440',
|
||||||
|
}
|
12
bundles/sudo/metadata.py
Normal file
12
bundles/sudo/metadata.py
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
defaults = {
|
||||||
|
'apt': {
|
||||||
|
'packages': {
|
||||||
|
'sudo': {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'pacman': {
|
||||||
|
'packages': {
|
||||||
|
'sudo': {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
3
bundles/sysctl/files/98-sysctl.conf
Normal file
3
bundles/sysctl/files/98-sysctl.conf
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
% for option, value in sorted(node.metadata.get('sysctl/options', {}).items()):
|
||||||
|
${option}=${value}
|
||||||
|
% endfor
|
40
bundles/sysctl/items.py
Normal file
40
bundles/sysctl/items.py
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
files = {
|
||||||
|
'/usr/local/sbin/apply-sysctl': {
|
||||||
|
'content':
|
||||||
|
'#!/bin/sh\n'
|
||||||
|
'\n'
|
||||||
|
'cat /etc/sysctl.d/*.conf | sysctl -e -p -',
|
||||||
|
'mode': '0700',
|
||||||
|
},
|
||||||
|
'/etc/sysctl.d/98-sysctl.conf': {
|
||||||
|
'content_type': 'mako',
|
||||||
|
'triggers': {
|
||||||
|
'action:apply-sysctl-settings',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'/etc/sysctl.conf': {
|
||||||
|
'delete': True,
|
||||||
|
'triggers': {
|
||||||
|
'action:apply-sysctl-settings',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
directories = {
|
||||||
|
'/etc/sysctl.d': {
|
||||||
|
'purge': True,
|
||||||
|
'triggers': {
|
||||||
|
'action:apply-sysctl-settings',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
actions = {
|
||||||
|
'apply-sysctl-settings': {
|
||||||
|
'command': '/usr/local/sbin/apply-sysctl',
|
||||||
|
'triggered': True,
|
||||||
|
'needs': {
|
||||||
|
'file:/usr/local/sbin/apply-sysctl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
3
bundles/systemd-networkd/files/resolv.conf
Normal file
3
bundles/systemd-networkd/files/resolv.conf
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
% for nameserver in sorted(node.metadata.get('nameservers', {'9.9.9.10', '2620:fe::10'})):
|
||||||
|
nameserver ${nameserver}
|
||||||
|
% endfor
|
13
bundles/systemd-networkd/files/template-bond.netdev
Normal file
13
bundles/systemd-networkd/files/template-bond.netdev
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
[NetDev]
|
||||||
|
Name=${bond}
|
||||||
|
Kind=bond
|
||||||
|
|
||||||
|
[Bond]
|
||||||
|
Mode=${mode}
|
||||||
|
% if mode in {'balance-rr', '802.3ad', 'balance-tlp'}:
|
||||||
|
TransmitHashPolicy=layer3+4
|
||||||
|
% endif
|
||||||
|
MIIMonitorSec=0.1
|
||||||
|
% if mode == '802.3ad':
|
||||||
|
LACPTransmitRate=fast
|
||||||
|
% endif
|
5
bundles/systemd-networkd/files/template-bond.network
Normal file
5
bundles/systemd-networkd/files/template-bond.network
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
[Match]
|
||||||
|
Name=${' '.join(sorted(match))}
|
||||||
|
|
||||||
|
[Network]
|
||||||
|
Bond=${bond}
|
|
@ -0,0 +1,7 @@
|
||||||
|
[Match]
|
||||||
|
Name=${bridge}
|
||||||
|
|
||||||
|
[Network]
|
||||||
|
% for vlan in sorted(vlans):
|
||||||
|
VLAN=${bridge}.${vlan}
|
||||||
|
% endfor
|
6
bundles/systemd-networkd/files/template-bridge.netdev
Normal file
6
bundles/systemd-networkd/files/template-bridge.netdev
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
[NetDev]
|
||||||
|
Name=${bridge}
|
||||||
|
Kind=bridge
|
||||||
|
|
||||||
|
[Bridge]
|
||||||
|
STP=off
|
6
bundles/systemd-networkd/files/template-bridge.network
Normal file
6
bundles/systemd-networkd/files/template-bridge.network
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
[Match]
|
||||||
|
Name=${' '.join(sorted(match))}
|
||||||
|
|
||||||
|
[Network]
|
||||||
|
Bridge=${bridge}
|
||||||
|
BindCarrier=${' '.join(sorted(match))}
|
3
bundles/systemd-networkd/files/template-dummy.netdev
Normal file
3
bundles/systemd-networkd/files/template-dummy.netdev
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[NetDev]
|
||||||
|
Name=${name}
|
||||||
|
Kind=dummy
|
27
bundles/systemd-networkd/files/template-iface-dhcp.network
Normal file
27
bundles/systemd-networkd/files/template-iface-dhcp.network
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<%
|
||||||
|
from ipaddress import ip_network
|
||||||
|
%>\
|
||||||
|
[Match]
|
||||||
|
Name=${interface}
|
||||||
|
|
||||||
|
[Network]
|
||||||
|
DHCP=yes
|
||||||
|
IPv6AcceptRA=yes
|
||||||
|
|
||||||
|
[DHCPv4]
|
||||||
|
UseDomains=${str(config.get('use_dhcp_domains', False)).lower()}
|
||||||
|
UseHostname=no
|
||||||
|
UseMTU=${str(config.get('use_dhcp_mtu', True)).lower()}
|
||||||
|
UseNTP=${str(config.get('use_dhcp_ntp', False)).lower()}
|
||||||
|
UseTimezone=no
|
||||||
|
|
||||||
|
% if config.get('send_hostname', True):
|
||||||
|
SendHostname=yes
|
||||||
|
Hostname=${node.name.split('.')[-1]}
|
||||||
|
% else:
|
||||||
|
SendHostname=no
|
||||||
|
% endif
|
||||||
|
|
||||||
|
% if config.get('forwarding', False):
|
||||||
|
IPForward=yes
|
||||||
|
%endif
|
50
bundles/systemd-networkd/files/template-iface-nodhcp.network
Normal file
50
bundles/systemd-networkd/files/template-iface-nodhcp.network
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
<%
|
||||||
|
from ipaddress import ip_network
|
||||||
|
%>\
|
||||||
|
[Match]
|
||||||
|
Name=${interface}
|
||||||
|
|
||||||
|
% for addr in sorted(config.get('ips', set())):
|
||||||
|
[Address]
|
||||||
|
<%
|
||||||
|
if '/' in addr:
|
||||||
|
ip, prefix = addr.split('/')
|
||||||
|
else:
|
||||||
|
ip = addr
|
||||||
|
prefix = '32'
|
||||||
|
%>\
|
||||||
|
Address=${ip}/${prefix}
|
||||||
|
|
||||||
|
% endfor
|
||||||
|
% for route, rconfig in sorted(config.get('routes', {}).items()):
|
||||||
|
[Route]
|
||||||
|
% if 'via' in rconfig:
|
||||||
|
Gateway=${rconfig['via']}
|
||||||
|
% endif
|
||||||
|
Destination=${route}
|
||||||
|
GatewayOnlink=yes
|
||||||
|
|
||||||
|
% endfor
|
||||||
|
% if 'gateway4' in config:
|
||||||
|
[Route]
|
||||||
|
Gateway=${config['gateway4']}
|
||||||
|
GatewayOnlink=yes
|
||||||
|
|
||||||
|
% endif
|
||||||
|
% if 'gateway6' in config:
|
||||||
|
[Route]
|
||||||
|
Gateway=${config['gateway6']}
|
||||||
|
GatewayOnlink=yes
|
||||||
|
|
||||||
|
% endif
|
||||||
|
[Network]
|
||||||
|
DHCP=no
|
||||||
|
IPv6AcceptRA=no
|
||||||
|
|
||||||
|
% if config.get('forwarding', False):
|
||||||
|
IPForward=yes
|
||||||
|
%endif
|
||||||
|
|
||||||
|
% for vlan in sorted(config.get('vlans', set())):
|
||||||
|
VLAN=${interface}.${vlan}
|
||||||
|
% endfor
|
|
@ -0,0 +1,7 @@
|
||||||
|
[NetDev]
|
||||||
|
Name=${interface}
|
||||||
|
Kind=vlan
|
||||||
|
MACAddress=${mac}
|
||||||
|
|
||||||
|
[VLAN]
|
||||||
|
Id=${vlan}
|
181
bundles/systemd-networkd/items.py
Normal file
181
bundles/systemd-networkd/items.py
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
from bundlewrap.exceptions import BundleError
|
||||||
|
|
||||||
|
repo.libs.tools.require_bundle(node, 'systemd')
|
||||||
|
|
||||||
|
files = {
|
||||||
|
'/etc/network/interfaces': {
|
||||||
|
'delete': True,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if node.metadata.get('systemd-networkd/enable-resolved', False):
|
||||||
|
symlinks['/etc/resolv.conf'] = {
|
||||||
|
'target': '/run/systemd/resolve/stub-resolv.conf',
|
||||||
|
}
|
||||||
|
svc_systemd['systemd-resolved'] = {}
|
||||||
|
else:
|
||||||
|
files['/etc/resolv.conf'] = {
|
||||||
|
'content_type': 'mako',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
directories = {
|
||||||
|
'/etc/systemd/network': {
|
||||||
|
'purge': True,
|
||||||
|
'needed_by': {
|
||||||
|
'svc_systemd:systemd-networkd',
|
||||||
|
},
|
||||||
|
'triggers': {
|
||||||
|
'svc_systemd:systemd-networkd:restart',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
mac_host_prefix = '%04x' % (node.magic_number % 65534)
|
||||||
|
generated_mac = f'52:54:00:{mac_host_prefix[0:2]}:{mac_host_prefix[2:4]}:{{}}'
|
||||||
|
|
||||||
|
# Don't use .get() here. We might end up with a node without a network
|
||||||
|
# config!
|
||||||
|
for interface, config in node.metadata['interfaces'].items():
|
||||||
|
if config.get('dhcp', False):
|
||||||
|
if 'vlans' in config:
|
||||||
|
raise BundleError(f'{node.name} interface {interface} cannot use vlans and dhcp!')
|
||||||
|
template = 'template-iface-dhcp.network'
|
||||||
|
else:
|
||||||
|
template = 'template-iface-nodhcp.network'
|
||||||
|
|
||||||
|
if '.' in interface:
|
||||||
|
vlan_id = int(interface.split('.')[1])
|
||||||
|
vlan_hex = '%02x' % (vlan_id % 255)
|
||||||
|
files[f'/etc/systemd/network/{interface}.netdev'] = {
|
||||||
|
'source': 'template-iface-vlan.netdev',
|
||||||
|
'content_type': 'mako',
|
||||||
|
'context': {
|
||||||
|
'interface': interface,
|
||||||
|
'vlan': vlan_id,
|
||||||
|
'mac': generated_mac.format(vlan_hex)
|
||||||
|
},
|
||||||
|
'needed_by': {
|
||||||
|
'svc_systemd:systemd-networkd',
|
||||||
|
},
|
||||||
|
'triggers': {
|
||||||
|
'svc_systemd:systemd-networkd:restart',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
elif interface.startswith('dummy'):
|
||||||
|
files[f'/etc/systemd/network/{interface}.netdev'] = {
|
||||||
|
'source': 'template-dummy.netdev',
|
||||||
|
'content_type': 'mako',
|
||||||
|
'context': {
|
||||||
|
'name': interface,
|
||||||
|
},
|
||||||
|
'needed_by': {
|
||||||
|
'svc_systemd:systemd-networkd',
|
||||||
|
},
|
||||||
|
'triggers': {
|
||||||
|
'svc_systemd:systemd-networkd:restart',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if not config.get('ignore', False):
|
||||||
|
files[f'/etc/systemd/network/{interface}.network'] = {
|
||||||
|
'source': template,
|
||||||
|
'content_type': 'mako',
|
||||||
|
'context': {
|
||||||
|
'interface': interface,
|
||||||
|
'config': config,
|
||||||
|
},
|
||||||
|
'needed_by': {
|
||||||
|
'svc_systemd:systemd-networkd',
|
||||||
|
},
|
||||||
|
'triggers': {
|
||||||
|
'svc_systemd:systemd-networkd:restart',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for bond, config in node.metadata.get('systemd-networkd/bonds', {}).items():
|
||||||
|
files[f'/etc/systemd/network/{bond}.netdev'] = {
|
||||||
|
'source': 'template-bond.netdev',
|
||||||
|
'content_type': 'mako',
|
||||||
|
'context': {
|
||||||
|
'bond': bond,
|
||||||
|
'mode': config.get('mode', '802.3ad'),
|
||||||
|
'prio': config.get('priority', '32768'),
|
||||||
|
},
|
||||||
|
'needed_by': {
|
||||||
|
'svc_systemd:systemd-networkd',
|
||||||
|
},
|
||||||
|
'triggers': {
|
||||||
|
'svc_systemd:systemd-networkd:restart',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
files[f'/etc/systemd/network/{bond}.network'] = {
|
||||||
|
'source': 'template-bond.network',
|
||||||
|
'content_type': 'mako',
|
||||||
|
'context': {
|
||||||
|
'bond': bond,
|
||||||
|
'match': config['match'],
|
||||||
|
},
|
||||||
|
'needed_by': {
|
||||||
|
'svc_systemd:systemd-networkd',
|
||||||
|
},
|
||||||
|
'triggers': {
|
||||||
|
'svc_systemd:systemd-networkd:restart',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for brname, config in node.metadata.get('systemd-networkd/bridges', {}).items():
|
||||||
|
filename = '{}-match-{}'.format(
|
||||||
|
brname,
|
||||||
|
'-'.join(sorted(config['match'])),
|
||||||
|
)
|
||||||
|
|
||||||
|
files[f'/etc/systemd/network/{brname}.netdev'] = {
|
||||||
|
'source': 'template-bridge.netdev',
|
||||||
|
'content_type': 'mako',
|
||||||
|
'context': {
|
||||||
|
'bridge': brname,
|
||||||
|
},
|
||||||
|
'needed_by': {
|
||||||
|
'svc_systemd:systemd-networkd',
|
||||||
|
},
|
||||||
|
'triggers': {
|
||||||
|
'svc_systemd:systemd-networkd:restart',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
files[f'/etc/systemd/network/{filename}.network'] = {
|
||||||
|
'source': 'template-bridge.network',
|
||||||
|
'content_type': 'mako',
|
||||||
|
'context': {
|
||||||
|
'bridge': brname,
|
||||||
|
'match': config['match'],
|
||||||
|
},
|
||||||
|
'needed_by': {
|
||||||
|
'svc_systemd:systemd-networkd',
|
||||||
|
},
|
||||||
|
'triggers': {
|
||||||
|
'svc_systemd:systemd-networkd:restart',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.get('vlans', set()):
|
||||||
|
files[f'/etc/systemd/network/{brname}.network'] = {
|
||||||
|
'source': 'template-bridge-vlan.network',
|
||||||
|
'content_type': 'mako',
|
||||||
|
'context': {
|
||||||
|
'bridge': brname,
|
||||||
|
'vlans': config.get('vlans', set()),
|
||||||
|
},
|
||||||
|
'needed_by': {
|
||||||
|
'svc_systemd:systemd-networkd',
|
||||||
|
},
|
||||||
|
'triggers': {
|
||||||
|
'svc_systemd:systemd-networkd:restart',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
svc_systemd = {
|
||||||
|
'systemd-networkd': {},
|
||||||
|
}
|
46
bundles/systemd-networkd/metadata.py
Normal file
46
bundles/systemd-networkd/metadata.py
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
defaults = {
|
||||||
|
'apt': {
|
||||||
|
'packages': {
|
||||||
|
'resolvconf': {
|
||||||
|
'installed': False,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@metadata_reactor.provides(
|
||||||
|
'interfaces',
|
||||||
|
'systemd-networkd/bridges',
|
||||||
|
)
|
||||||
|
def add_vlan_infos_to_interface(metadata):
|
||||||
|
interfaces = {}
|
||||||
|
bridges = {}
|
||||||
|
|
||||||
|
for iface in metadata.get('interfaces', {}):
|
||||||
|
if '.' not in iface:
|
||||||
|
continue
|
||||||
|
|
||||||
|
interface,vlan = iface.split('.')
|
||||||
|
|
||||||
|
interfaces.setdefault(interface, {}).setdefault('vlans', set())
|
||||||
|
interfaces[interface]['vlans'].add(vlan)
|
||||||
|
|
||||||
|
for bridge, config in metadata.get('systemd-networkd/bridges', {}).items():
|
||||||
|
for iface in config.get('match', {}):
|
||||||
|
if '.' not in iface:
|
||||||
|
continue
|
||||||
|
|
||||||
|
interface,vlan = iface.split('.')
|
||||||
|
|
||||||
|
bridges.setdefault(interface, {}).setdefault('vlans', set())
|
||||||
|
bridges[interface]['vlans'].add(vlan)
|
||||||
|
|
||||||
|
interfaces.setdefault(iface, {'ignore': True})
|
||||||
|
|
||||||
|
return {
|
||||||
|
'interfaces': interfaces,
|
||||||
|
'systemd-networkd': {
|
||||||
|
'bridges': bridges,
|
||||||
|
},
|
||||||
|
}
|
19
bundles/systemd/files/journald.conf
Normal file
19
bundles/systemd/files/journald.conf
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
[Journal]
|
||||||
|
Storage=${journal.get('storage', 'persistent')}
|
||||||
|
Compress=yes
|
||||||
|
SplitMode=uid
|
||||||
|
|
||||||
|
# Disable rate limiting.
|
||||||
|
RateLimitIntervalSec=0
|
||||||
|
RateLimitBurst=0
|
||||||
|
|
||||||
|
SystemMaxUse=${journal.get('maxuse', '500M')}
|
||||||
|
SystemKeepFree=${journal.get('keepfree', '2G')}
|
||||||
|
SystemMaxFileSize=100M
|
||||||
|
RuntimeMaxUse=${journal.get('maxuse', '500M')}
|
||||||
|
RuntimeKeepFree=${journal.get('keepfree', '2G')}
|
||||||
|
RuntimeMaxFileSize=100M
|
||||||
|
MaxFileSec=1d
|
||||||
|
|
||||||
|
# Disable auditing
|
||||||
|
Audit=no
|
48
bundles/systemd/items.py
Normal file
48
bundles/systemd/items.py
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
timezone = node.metadata.get('timezone', 'UTC')
|
||||||
|
|
||||||
|
actions = {
|
||||||
|
'systemd-reload': {
|
||||||
|
'command': 'systemctl daemon-reload',
|
||||||
|
'cascade_skip': False,
|
||||||
|
'triggered': True,
|
||||||
|
'needed_by': {
|
||||||
|
'svc_systemd:',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'systemd-hostname': {
|
||||||
|
'command': 'hostnamectl set-hostname {}'.format(node.metadata['hostname']),
|
||||||
|
'unless': '[ "$(hostnamectl --static)" = "{}" ]'.format(node.metadata['hostname']),
|
||||||
|
# Provided by bundle:basic
|
||||||
|
'needs': {
|
||||||
|
'file:/etc/hosts',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'systemd-timezone': {
|
||||||
|
'command': 'timedatectl set-timezone {}'.format(timezone),
|
||||||
|
'unless': 'timedatectl status | grep -Fi \'time zone\' | grep -i \'{}\''.format(timezone.lower()),
|
||||||
|
},
|
||||||
|
'systemd-enable-ntp': {
|
||||||
|
'command': 'timedatectl set-ntp true',
|
||||||
|
'unless': 'timedatectl status | grep -Fi \'ntp service\' | grep -i \'active\'',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
files = {
|
||||||
|
'/etc/systemd/journald.conf': {
|
||||||
|
'content_type': 'mako',
|
||||||
|
'context': {
|
||||||
|
'journal': node.metadata.get('systemd/journal', {}),
|
||||||
|
},
|
||||||
|
'triggers': {
|
||||||
|
'svc_systemd:systemd-journald:restart',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
svc_systemd = {
|
||||||
|
'systemd-journald': {
|
||||||
|
'needs': {
|
||||||
|
'file:/etc/systemd/journald.conf',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
27
bundles/users/README.md
Normal file
27
bundles/users/README.md
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
# bundles/users
|
||||||
|
|
||||||
|
This bundle evaluates node metadata to determine which users should
|
||||||
|
exist on the system.
|
||||||
|
It will also create a home directory, add ssh keys and deploy shell
|
||||||
|
configs, if user-specific configuration exists.
|
||||||
|
|
||||||
|
## metadata
|
||||||
|
'users': {
|
||||||
|
'username': {
|
||||||
|
'home': '/home/username', # this is the default
|
||||||
|
'shell': '/bin/bash', # this is the default
|
||||||
|
'groups': {
|
||||||
|
# list of groups the user should be in
|
||||||
|
},
|
||||||
|
'ssh_pubkey': [
|
||||||
|
# list of ssh pubkeys that are allowed to log in
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
## custom shell config
|
||||||
|
Deploy your custom config to these paths:
|
||||||
|
|
||||||
|
* data/users/files/tmux/username.conf
|
||||||
|
* data/users/files/fish/username.conf
|
||||||
|
* data/users/files/bash/username.bashrc
|
72
bundles/users/files/bashrc
Normal file
72
bundles/users/files/bashrc
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
[ -z "$PS1" ] && return
|
||||||
|
|
||||||
|
__node_name="${node.name}"
|
||||||
|
|
||||||
|
<%text>
|
||||||
|
if [[ "$(id -u)" -eq 0 ]]
|
||||||
|
then
|
||||||
|
export PS1='\[\e[1;34m\][\[\e[1;91m\]'"$__node_name"'\[\e[1;34m\]][\[\e[1;91m\]\u\[\e[1;34m\]@\[\e[1;91m\]$PWD\[\e[1;34m\]] > \[\e[0m\]'
|
||||||
|
else
|
||||||
|
export PS1='\[\e[1;34m\][\[\e[1;32m\]'"$__node_name"'\[\e[1;34m\]][\[\e[1;32m\]\u\[\e[1;34m\]@\[\e[1;32m\]\w\[\e[1;34m\]] > \[\e[0m\]'
|
||||||
|
fi
|
||||||
|
case $TERM in
|
||||||
|
xterm*|rxvt*)
|
||||||
|
export PROMPT_COMMAND='echo -ne "\a\e]0;'"$__node_name"':${PWD}\a"'
|
||||||
|
;;
|
||||||
|
screen*)
|
||||||
|
export PROMPT_COMMAND='echo -ne "\a\ek'"$__node_name"':${PWD}\e\\"'
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
unset PROMPT_COMMAND
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [[ -f "/etc/node.description" ]]
|
||||||
|
then
|
||||||
|
echo
|
||||||
|
cat "/etc/node.description"
|
||||||
|
echo
|
||||||
|
fi
|
||||||
|
|
||||||
|
uptime
|
||||||
|
last | grep 'still logged in'
|
||||||
|
|
||||||
|
export HISTCONTROL=ignoredups
|
||||||
|
export HISTSIZE=50000
|
||||||
|
export HISTTIMEFORMAT="%d/%m/%y %T "
|
||||||
|
export SAVEHIST=50000
|
||||||
|
shopt -s checkjobs
|
||||||
|
shopt -s checkwinsize
|
||||||
|
shopt -s globstar
|
||||||
|
shopt -s histreedit
|
||||||
|
|
||||||
|
export LESS="-iRS -# 2"
|
||||||
|
|
||||||
|
export EDITOR=vim
|
||||||
|
export VISUAL=vim
|
||||||
|
|
||||||
|
alias ipb='ip -brief'
|
||||||
|
alias l='ls -lAh'
|
||||||
|
alias s='sudo -i'
|
||||||
|
alias v='vim -p'
|
||||||
|
</%text>
|
||||||
|
% for k, v in sorted(node.metadata.get('bash_aliases', {}).items()):
|
||||||
|
alias ${k}='${v}'
|
||||||
|
% endfor
|
||||||
|
|
||||||
|
rsback()
|
||||||
|
{
|
||||||
|
for i
|
||||||
|
do
|
||||||
|
[ -e "$i" ] || { echo "ERROR: $i does not exist" >&2; continue; }
|
||||||
|
printf 'rsync -zaP -e ssh %q ' '--rsync-path=sudo rsync'
|
||||||
|
printf '%q:%q .' "${node.hostname}" "$(printf '%q' "$(readlink -e -- "$i")")"
|
||||||
|
printf '\n'
|
||||||
|
done
|
||||||
|
}
|
||||||
|
% for k, v in sorted(node.metadata.get('bash_functions', {}).items()):
|
||||||
|
|
||||||
|
${k}() {
|
||||||
|
${v}
|
||||||
|
}
|
||||||
|
% endfor
|
23
bundles/users/files/fish.conf
Normal file
23
bundles/users/files/fish.conf
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
function fish_greeting
|
||||||
|
uptime
|
||||||
|
last | grep 'still logged in'
|
||||||
|
end
|
||||||
|
|
||||||
|
function fish_prompt
|
||||||
|
set -l last_status $status
|
||||||
|
|
||||||
|
echo -n "$USER@${node.name}:"
|
||||||
|
|
||||||
|
set_color $fish_color_cwd
|
||||||
|
echo -n (pwd)
|
||||||
|
set_color normal
|
||||||
|
|
||||||
|
if not test $last_status -eq 0
|
||||||
|
set_color $fish_color_error
|
||||||
|
echo -n " [$status]"
|
||||||
|
set_color normal
|
||||||
|
end
|
||||||
|
|
||||||
|
echo -n '➤ '
|
||||||
|
set_color normal
|
||||||
|
end
|
32
bundles/users/files/fish_variables
Normal file
32
bundles/users/files/fish_variables
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
# This file contains fish universal variable definitions.
|
||||||
|
# VERSION: 3.0
|
||||||
|
SETUVAR fish_color_autosuggestion:555\x1ebrblack
|
||||||
|
SETUVAR fish_color_cancel:\x2dr
|
||||||
|
SETUVAR fish_color_command:005fd7
|
||||||
|
SETUVAR fish_color_comment:990000
|
||||||
|
SETUVAR fish_color_cwd:green
|
||||||
|
SETUVAR fish_color_cwd_root:red
|
||||||
|
SETUVAR fish_color_end:009900
|
||||||
|
SETUVAR fish_color_error:ff0000
|
||||||
|
SETUVAR fish_color_escape:00a6b2
|
||||||
|
SETUVAR fish_color_history_current:\x2d\x2dbold
|
||||||
|
SETUVAR fish_color_host:normal
|
||||||
|
SETUVAR fish_color_host_remote:yellow
|
||||||
|
SETUVAR fish_color_match:\x2d\x2dbackground\x3dbrblue
|
||||||
|
SETUVAR fish_color_normal:normal
|
||||||
|
SETUVAR fish_color_operator:00a6b2
|
||||||
|
SETUVAR fish_color_param:00afff
|
||||||
|
SETUVAR fish_color_quote:999900
|
||||||
|
SETUVAR fish_color_redirection:00afff
|
||||||
|
SETUVAR fish_color_search_match:bryellow\x1e\x2d\x2dbackground\x3dbrblack
|
||||||
|
SETUVAR fish_color_selection:white\x1e\x2d\x2dbold\x1e\x2d\x2dbackground\x3dbrblack
|
||||||
|
SETUVAR fish_color_status:red
|
||||||
|
SETUVAR fish_color_user:brgreen
|
||||||
|
SETUVAR fish_color_valid_path:\x2d\x2dunderline
|
||||||
|
SETUVAR fish_features:stderr\x2dnocaret\x1eqmark\x2dnoglob
|
||||||
|
SETUVAR fish_greeting:Welcome\x20to\x20fish\x2c\x20the\x20friendly\x20interactive\x20shell
|
||||||
|
SETUVAR fish_key_bindings:fish_default_key_bindings
|
||||||
|
SETUVAR fish_pager_color_completion:\x1d
|
||||||
|
SETUVAR fish_pager_color_description:B3A06D\x1eyellow
|
||||||
|
SETUVAR fish_pager_color_prefix:white\x1e\x2d\x2dbold\x1e\x2d\x2dunderline
|
||||||
|
SETUVAR fish_pager_color_progress:brwhite\x1e\x2d\x2dbackground\x3dcyan
|
51
bundles/users/files/tmux.conf
Normal file
51
bundles/users/files/tmux.conf
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
bind C-a send-prefix # Pass on ctrl-a for other apps
|
||||||
|
|
||||||
|
set-option -g set-titles on
|
||||||
|
set-option -g set-titles-string '#W [#(logname)@${node.name}]'
|
||||||
|
|
||||||
|
# Status bar
|
||||||
|
set -g status on
|
||||||
|
set -g status-interval 1
|
||||||
|
set -g status-left '[#(logname)@${node.name}] '
|
||||||
|
set -g status-left-length 100
|
||||||
|
set -g status-right ' [#(lsb_release -ds)] [#(date --rfc-3339=seconds)]'
|
||||||
|
set -g status-right-length 200
|
||||||
|
|
||||||
|
# Numbering starts at 1
|
||||||
|
set -g base-index 1
|
||||||
|
setw -g pane-base-index 1
|
||||||
|
set-option -g renumber-windows on
|
||||||
|
|
||||||
|
# Activity monitoring
|
||||||
|
setw -g monitor-activity on
|
||||||
|
set -g visual-activity on
|
||||||
|
|
||||||
|
# Terminal
|
||||||
|
set-option -g default-shell $SHELL
|
||||||
|
set -g default-terminal "tmux-256color"
|
||||||
|
|
||||||
|
# Keybindings
|
||||||
|
bind-key -n M-Left select-pane -L
|
||||||
|
bind-key -n M-Right select-pane -R
|
||||||
|
bind-key -n M-Up select-pane -U
|
||||||
|
bind-key -n M-Down select-pane -D
|
||||||
|
|
||||||
|
# Vim-Keybindings, adapted for neo layout, shifted one key to the right
|
||||||
|
bind-key -n M-n select-pane -L
|
||||||
|
bind-key -n M-d select-pane -R
|
||||||
|
bind-key -n M-t select-pane -U
|
||||||
|
bind-key -n M-r select-pane -D
|
||||||
|
|
||||||
|
#### COLOUR (Solarized dark)
|
||||||
|
set-option -g status-bg black #base02
|
||||||
|
set-option -g status-fg yellow #yellow
|
||||||
|
set-option -g status-style default
|
||||||
|
set-window-option -g window-status-style fg=brightblue,bg=default
|
||||||
|
set-window-option -g window-status-activity-style fg=green,bg=default,none
|
||||||
|
set-window-option -g window-status-current-style fg=brightred,bg=default,none
|
||||||
|
set-option -g pane-border-style fg=black
|
||||||
|
set-option -g pane-active-border-style fg=brightgreen
|
||||||
|
set-option -g message-style bg=black,fg=brightred
|
||||||
|
set-option -g display-panes-active-colour blue #blue
|
||||||
|
set-option -g display-panes-colour brightred #orange
|
||||||
|
set-window-option -g clock-mode-colour green #green
|
28
bundles/users/files/vimrc
Normal file
28
bundles/users/files/vimrc
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
set number
|
||||||
|
set lbr
|
||||||
|
set fdc=0
|
||||||
|
set vb
|
||||||
|
set expandtab
|
||||||
|
set shiftwidth=4
|
||||||
|
set tabstop=4
|
||||||
|
set softtabstop=4
|
||||||
|
set linespace=0
|
||||||
|
set autoindent
|
||||||
|
set smartindent
|
||||||
|
set mouse=a
|
||||||
|
set cursorline
|
||||||
|
syntax on
|
||||||
|
set showcmd
|
||||||
|
set encoding=utf-8
|
||||||
|
set autowrite
|
||||||
|
set noautochdir
|
||||||
|
set list
|
||||||
|
set listchars=trail:␣,tab:→\ ,extends:>,precedes:<
|
||||||
|
set hlsearch
|
||||||
|
map <silent> <c-k> :nohlsearch<CR>
|
||||||
|
|
||||||
|
set colorcolumn=72,120
|
||||||
|
hi colorcolumn ctermbg=NONE ctermfg=red cterm=bold guibg=NONE guifg=red gui=bold
|
||||||
|
|
||||||
|
au BufRead /tmp/neomutt-* set tw=72
|
||||||
|
au BufRead /tmp/mutt-* set tw=72
|
89
bundles/users/items.py
Normal file
89
bundles/users/items.py
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
from os.path import join, exists
|
||||||
|
|
||||||
|
files = {
|
||||||
|
'/etc/bash.bashrc': {
|
||||||
|
'source': 'bashrc',
|
||||||
|
'content_type': 'mako',
|
||||||
|
},
|
||||||
|
'/etc/tmux.conf': {
|
||||||
|
'source': 'tmux.conf',
|
||||||
|
'content_type': 'mako',
|
||||||
|
},
|
||||||
|
'/etc/vim/vimrc.local': {
|
||||||
|
'source': 'vimrc',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for group, attrs in node.metadata.get('groups', {}).items():
|
||||||
|
groups[group] = attrs
|
||||||
|
|
||||||
|
for username, attrs in node.metadata['users'].items():
|
||||||
|
home = attrs.get('home', '/home/{}'.format(username))
|
||||||
|
|
||||||
|
if attrs.get('delete', False):
|
||||||
|
users[username] = {'delete': True}
|
||||||
|
files[home] = {'delete': True}
|
||||||
|
|
||||||
|
else:
|
||||||
|
user = users.setdefault(username, {})
|
||||||
|
|
||||||
|
user['home'] = home
|
||||||
|
user['shell'] = attrs.get('shell', '/bin/bash')
|
||||||
|
|
||||||
|
if 'password' in attrs:
|
||||||
|
user['password'] = attrs['password']
|
||||||
|
else:
|
||||||
|
user['password_hash'] = 'x' if node.use_shadow_passwords else '*'
|
||||||
|
|
||||||
|
if 'groups' in attrs:
|
||||||
|
user['groups'] = attrs['groups']
|
||||||
|
|
||||||
|
directories[home] = {
|
||||||
|
'owner': username,
|
||||||
|
'mode': attrs.get('home-mode', '0700'),
|
||||||
|
}
|
||||||
|
|
||||||
|
if 'ssh_pubkey' in attrs:
|
||||||
|
files[home + '/.ssh/authorized_keys'] = {
|
||||||
|
'content': '\n'.join(sorted(set(attrs['ssh_pubkey']))) + '\n',
|
||||||
|
'owner': username,
|
||||||
|
'mode': '0600',
|
||||||
|
}
|
||||||
|
|
||||||
|
elif not attrs.get('do_not_remove_authorized_keys_from_home', False):
|
||||||
|
files[home + '/.ssh/authorized_keys'] = {'delete': True}
|
||||||
|
|
||||||
|
if exists(join(repo.path, 'data', 'users', 'files', 'tmux', '{}.conf'.format(username))):
|
||||||
|
files[home + '/.tmux.conf'] = {
|
||||||
|
'content_type': 'mako',
|
||||||
|
'source': 'tmux/{}.conf'.format(username),
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
files[home + '/.tmux.conf'] = {
|
||||||
|
'delete': True,
|
||||||
|
}
|
||||||
|
|
||||||
|
if exists(join(repo.path, 'data', 'users', 'files', 'bash', '{}.bashrc'.format(username))):
|
||||||
|
files[home + '/.bashrc'] = {
|
||||||
|
'content_type': 'mako',
|
||||||
|
'source': 'bash/{}.bashrc'.format(username),
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
files[home + '/.bashrc'] = {
|
||||||
|
'delete': True,
|
||||||
|
}
|
||||||
|
|
||||||
|
if attrs.get('enable_linger', False):
|
||||||
|
linger_test = ''
|
||||||
|
linger_command = 'enable'
|
||||||
|
else:
|
||||||
|
linger_test = '!'
|
||||||
|
linger_command = 'disable'
|
||||||
|
|
||||||
|
actions[f'ensure_linger_state_for_user_{username}'] = {
|
||||||
|
'command': f'loginctl {linger_command}-linger {username}',
|
||||||
|
'unless': f'{linger_test} test -f /var/lib/systemd/linger/{username}',
|
||||||
|
'needs': {
|
||||||
|
f'user:{username}',
|
||||||
|
},
|
||||||
|
}
|
41
bundles/users/metadata.py
Normal file
41
bundles/users/metadata.py
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
from json import loads
|
||||||
|
from os.path import join
|
||||||
|
|
||||||
|
defaults = {
|
||||||
|
'users': {
|
||||||
|
'root': {
|
||||||
|
'home': '/root',
|
||||||
|
'shell': '/bin/bash',
|
||||||
|
'password': repo.vault.human_password_for('root on {}'.format(node.name)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@metadata_reactor.provides(
|
||||||
|
'users',
|
||||||
|
)
|
||||||
|
def add_users_from_json(metadata):
|
||||||
|
with open(join(repo.path, 'users.json'), 'r') as f:
|
||||||
|
json = loads(f.read())
|
||||||
|
|
||||||
|
users = {}
|
||||||
|
metadata_users = metadata.get('users', {})
|
||||||
|
# First, add all admin users
|
||||||
|
for uname, config in json.items():
|
||||||
|
if config.get('is_admin', False) or uname in metadata_users:
|
||||||
|
users[uname] = {
|
||||||
|
'ssh_pubkey': set(config['ssh_pubkey']),
|
||||||
|
'is_admin': config.get('is_admin', False),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Then, run again to get all 'to be deleted' users
|
||||||
|
for uname, config in json.items():
|
||||||
|
if uname not in metadata_users:
|
||||||
|
users.setdefault(uname, {
|
||||||
|
'delete': True,
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
'users': users,
|
||||||
|
}
|
Loading…
Reference in a new issue