Compare commits
11 commits
main
...
kunsi-wood
Author | SHA1 | Date | |
---|---|---|---|
d282d77a99 | |||
cb4d28c994 | |||
071250d798 | |||
efdff6ef28 | |||
d2caadb41b | |||
9b44bcf3a8 | |||
24f9f87734 | |||
019cc69371 | |||
eee786fabf | |||
c2e93c0abb | |||
cc767867cf |
460 changed files with 6300 additions and 8609 deletions
|
@ -22,6 +22,3 @@ indent_size = unset
|
||||||
[*.vault]
|
[*.vault]
|
||||||
end_of_line = unset
|
end_of_line = unset
|
||||||
insert_final_newline = unset
|
insert_final_newline = unset
|
||||||
|
|
||||||
[*.json]
|
|
||||||
insert_final_newline = unset
|
|
||||||
|
|
26
.woodpecker/bw-test.yml
Normal file
26
.woodpecker/bw-test.yml
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
pipeline:
|
||||||
|
install-deps:
|
||||||
|
image: python:3.10-slim
|
||||||
|
commands:
|
||||||
|
- pip install -r requirements.txt
|
||||||
|
|
||||||
|
test-dummymode:
|
||||||
|
image: python:3.10-slim
|
||||||
|
commands:
|
||||||
|
- bw test
|
||||||
|
environment:
|
||||||
|
BW_VAULT_DUMMY_MODE: 1
|
||||||
|
BW_PASS_DUMMY_MODE: 1
|
||||||
|
|
||||||
|
test-ignore-missing-faults:
|
||||||
|
image: python:3.10-slim
|
||||||
|
commands:
|
||||||
|
- bw test --ignore-missing-faults
|
||||||
|
|
||||||
|
test-determinism:
|
||||||
|
image: python:3.10-slim
|
||||||
|
commands:
|
||||||
|
- bw test --metadata-determinism 3 --config-determinism 3
|
||||||
|
environment:
|
||||||
|
BW_VAULT_DUMMY_MODE: 1
|
||||||
|
BW_PASS_DUMMY_MODE: 1
|
8
.woodpecker/editorconfig.yml
Normal file
8
.woodpecker/editorconfig.yml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
pipeline:
|
||||||
|
editorconfig:
|
||||||
|
image: alpine:latest
|
||||||
|
commands:
|
||||||
|
- wget -O ec-linux-amd64.tar.gz https://github.com/editorconfig-checker/editorconfig-checker/releases/latest/download/ec-linux-amd64.tar.gz
|
||||||
|
- tar -xzf ec-linux-amd64.tar.gz
|
||||||
|
- rm ec-linux-amd64.tar.gz
|
||||||
|
- bin/ec-linux-amd64 -no-color -exclude '^bin/'
|
9
Jenkinsfile
vendored
9
Jenkinsfile
vendored
|
@ -25,6 +25,15 @@ pipeline {
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
stage('syntax checking using isort') {
|
||||||
|
steps {
|
||||||
|
sh """
|
||||||
|
. venv/bin/activate
|
||||||
|
|
||||||
|
isort --check .
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
}
|
||||||
stage('config and metadata determinism') {
|
stage('config and metadata determinism') {
|
||||||
steps {
|
steps {
|
||||||
sh """
|
sh """
|
||||||
|
|
|
@ -30,13 +30,13 @@ Rule of thumb: keep ports below 10000 free for stuff that reserves ports.
|
||||||
| 20010 | mautrix-telegram | Bridge |
|
| 20010 | mautrix-telegram | Bridge |
|
||||||
| 20020 | mautrix-whatsapp | Bridge |
|
| 20020 | mautrix-whatsapp | Bridge |
|
||||||
| 20030 | matrix-dimension | Matrix Integrations Manager|
|
| 20030 | matrix-dimension | Matrix Integrations Manager|
|
||||||
| 20070 | matrix-synapse | sliding-sync |
|
|
||||||
| 20080 | matrix-synapse | client, federation |
|
| 20080 | matrix-synapse | client, federation |
|
||||||
| 20081 | matrix-synapse | prometheus metrics |
|
| 20081 | matrix-synapse | prometheus metrics |
|
||||||
| 20090 | matrix-media-repo | media_repo |
|
| 20090 | matrix-media-repo | media_repo |
|
||||||
| 20090 | matrix-media-repo | prometheus metrics |
|
| 20090 | matrix-media-repo | prometheus metrics |
|
||||||
|
| 21000 | pleroma | pleroma |
|
||||||
| 21010 | grafana | grafana |
|
| 21010 | grafana | grafana |
|
||||||
| 22000 | forgejo | forgejo |
|
| 22000 | gitea | forgejo |
|
||||||
| 22010 | jenkins-ci | Jenkins CI |
|
| 22010 | jenkins-ci | Jenkins CI |
|
||||||
| 22020 | travelynx | Travelynx Web |
|
| 22020 | travelynx | Travelynx Web |
|
||||||
| 22030 | octoprint | OctoPrint Web Interface |
|
| 22030 | octoprint | OctoPrint Web Interface |
|
||||||
|
@ -45,9 +45,8 @@ Rule of thumb: keep ports below 10000 free for stuff that reserves ports.
|
||||||
| 22060 | pretalx | gunicorn |
|
| 22060 | pretalx | gunicorn |
|
||||||
| 22070 | paperless-ng | gunicorn |
|
| 22070 | paperless-ng | gunicorn |
|
||||||
| 22080 | netbox | gunicorn |
|
| 22080 | netbox | gunicorn |
|
||||||
| 22090 | jugendhackt_tools | gunicorn |
|
| 22100 | woodpecker-server | http |
|
||||||
| 22100 | powerdnsadmin | gunicorn |
|
| 22101 | woodpecker-server | gRPC |
|
||||||
| 22110 | icinga2-statuspage | gunicorn |
|
|
||||||
| 22999 | nginx | stub_status |
|
| 22999 | nginx | stub_status |
|
||||||
| 22100 | ntfy | http |
|
| 22100 | ntfy | http |
|
||||||
|
|
||||||
|
|
13
README.md
13
README.md
|
@ -7,16 +7,3 @@ onto shared webhosting.
|
||||||
|
|
||||||
`bw test` runs according to Jenkinsfile after every commit.
|
`bw test` runs according to Jenkinsfile after every commit.
|
||||||
[![Build Status](https://jenkins.franzi.business/buildStatus/icon?job=kunsi%2Fbundlewrap%2Fmain)](https://jenkins.franzi.business/job/kunsi/job/bundlewrap/job/main/)
|
[![Build Status](https://jenkins.franzi.business/buildStatus/icon?job=kunsi%2Fbundlewrap%2Fmain)](https://jenkins.franzi.business/job/kunsi/job/bundlewrap/job/main/)
|
||||||
|
|
||||||
## automatix
|
|
||||||
|
|
||||||
Ensure you set `bundlewrap: true` in your `~/.automatix.cfg.yaml`.
|
|
||||||
|
|
||||||
## system naming
|
|
||||||
|
|
||||||
All systems should be named after their location and use.
|
|
||||||
|
|
||||||
For example, influxdb hosted at hetzner cloud will be `htz-cloud.influxdb`.
|
|
||||||
|
|
||||||
The only exception to this are name servers, they are named after [demons
|
|
||||||
in fiction](https://en.wikipedia.org/wiki/List_of_demons_in_fiction).
|
|
||||||
|
|
|
@ -1,45 +0,0 @@
|
||||||
name: Upgrade to debian bullseye
|
|
||||||
systems:
|
|
||||||
node: foonode
|
|
||||||
|
|
||||||
always:
|
|
||||||
- has_zfs=python: NODES.node.has_bundle('zfs')
|
|
||||||
|
|
||||||
pipeline:
|
|
||||||
- manual: "set icinga2 downtime: https://icinga.franzi.business/monitoring/host/schedule-downtime?host={SYSTEMS.node}"
|
|
||||||
|
|
||||||
# apply first so we only see the upgrade changes later
|
|
||||||
- local: bw apply {SYSTEMS.node}
|
|
||||||
- manual: update debian version in node groups
|
|
||||||
- local: "bw apply -o bundle:apt -s symlink:/usr/bin/python pkg_apt: -- {SYSTEMS.node}"
|
|
||||||
|
|
||||||
# double time!
|
|
||||||
- remote@node: DEBIAN_FRONTEND=noninteractive apt-get -y -q -o Dpkg::Options::=--force-confold dist-upgrade
|
|
||||||
- remote@node: DEBIAN_FRONTEND=noninteractive apt-get -y -q -o Dpkg::Options::=--force-confold dist-upgrade
|
|
||||||
|
|
||||||
# reboot into bullseye
|
|
||||||
- remote@node: systemctl reboot
|
|
||||||
- local: |
|
|
||||||
exit=1
|
|
||||||
while [[ $exit -ne 0 ]];
|
|
||||||
do
|
|
||||||
sleep 1
|
|
||||||
ssh {SYSTEMS.node} true
|
|
||||||
exit=$?
|
|
||||||
done
|
|
||||||
|
|
||||||
# fix zfs and reboot again
|
|
||||||
- has_zfs?remote@node: zpool import tank -f
|
|
||||||
- has_zfs?remote@node: zpool upgrade -a
|
|
||||||
- has_zfs?remote@node: systemctl reboot
|
|
||||||
- has_zfs?local: |
|
|
||||||
exit=1
|
|
||||||
while [[ $exit -ne 0 ]];
|
|
||||||
do
|
|
||||||
sleep 1
|
|
||||||
ssh {SYSTEMS.node} true
|
|
||||||
exit=$?
|
|
||||||
done
|
|
||||||
|
|
||||||
# final apply
|
|
||||||
- local: bw apply {SYSTEMS.node}
|
|
|
@ -1,9 +0,0 @@
|
||||||
% for uri in sorted(uris):
|
|
||||||
Types: ${' '.join(sorted(data.get('types', {'deb'})))}
|
|
||||||
URIs: ${uri}
|
|
||||||
Suites: ${os_release}
|
|
||||||
Components: ${' '.join(sorted(data.get('components', {'main'})))}
|
|
||||||
Architectures: ${' '.join(sorted(data.get('architectures', {'amd64'})))}
|
|
||||||
Signed-By: /etc/apt/trusted.gpg.d/${name}.list.asc
|
|
||||||
|
|
||||||
% endfor
|
|
|
@ -6,9 +6,9 @@ apt-get update
|
||||||
|
|
||||||
DEBIAN_FRONTEND=noninteractive apt-get -y -q -o Dpkg::Options::=--force-confold dist-upgrade
|
DEBIAN_FRONTEND=noninteractive apt-get -y -q -o Dpkg::Options::=--force-confold dist-upgrade
|
||||||
|
|
||||||
DEBIAN_FRONTEND=noninteractive apt-get -y -q autoremove
|
DEBIAN_FRONTEND=noninteractive apt-get -y -q autoclean
|
||||||
|
|
||||||
DEBIAN_FRONTEND=noninteractive apt-get -y -q clean
|
DEBIAN_FRONTEND=noninteractive apt-get -y -q autoremove
|
||||||
|
|
||||||
% if clean_old_kernels:
|
% if clean_old_kernels:
|
||||||
existing=$(dpkg --get-selections | grep -E '^linux-(image|headers)-[0-9]' || true)
|
existing=$(dpkg --get-selections | grep -E '^linux-(image|headers)-[0-9]' || true)
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
deb http://deb.debian.org/debian/ bookworm main non-free contrib non-free-firmware
|
|
||||||
deb http://security.debian.org/debian-security bookworm-security main contrib non-free
|
|
||||||
deb http://deb.debian.org/debian/ bookworm-updates main contrib non-free
|
|
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
|
|
@ -19,7 +19,7 @@ statusfile="/var/tmp/unattended_upgrades.status"
|
||||||
# Workaround, because /var/tmp is usually 1777
|
# Workaround, because /var/tmp is usually 1777
|
||||||
[[ "$UID" == 0 ]] && chown root:root "$statusfile"
|
[[ "$UID" == 0 ]] && chown root:root "$statusfile"
|
||||||
|
|
||||||
logins=$(ps h -C sshd -o euser | awk '$1 != "root" && $1 != "sshd" && $1 != "sshmon" && $1 != "nobody"')
|
logins=$(ps h -C sshd -o euser | awk '$1 != "root" && $1 != "sshd" && $1 != "sshmon"')
|
||||||
if [[ -n "$logins" ]]
|
if [[ -n "$logins" ]]
|
||||||
then
|
then
|
||||||
echo "Will abort now, there are active SSH logins: $logins"
|
echo "Will abort now, there are active SSH logins: $logins"
|
||||||
|
@ -46,6 +46,10 @@ fi
|
||||||
|
|
||||||
if [[ -f /var/run/reboot-required ]] && [[ "$auto_reboot_enabled" == "True" ]]
|
if [[ -f /var/run/reboot-required ]] && [[ "$auto_reboot_enabled" == "True" ]]
|
||||||
then
|
then
|
||||||
|
if [[ -n "$reboot_mail_to" ]]
|
||||||
|
then
|
||||||
|
date | mail -s "SYSREBOOTNOW $nodename" "$reboot_mail_to"
|
||||||
|
fi
|
||||||
systemctl reboot
|
systemctl reboot
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
nodename="${node.name}"
|
nodename="${node.name}"
|
||||||
|
reboot_mail_to="${node.metadata.get('apt/unattended-upgrades/reboot_mail_to', '')}"
|
||||||
auto_reboot_enabled="${node.metadata.get('apt/unattended-upgrades/reboot_enabled', True)}"
|
auto_reboot_enabled="${node.metadata.get('apt/unattended-upgrades/reboot_enabled', True)}"
|
||||||
|
|
|
@ -4,9 +4,11 @@ supported_os = {
|
||||||
'debian': {
|
'debian': {
|
||||||
10: 'buster',
|
10: 'buster',
|
||||||
11: 'bullseye',
|
11: 'bullseye',
|
||||||
12: 'bookworm',
|
|
||||||
99: 'unstable',
|
99: 'unstable',
|
||||||
},
|
},
|
||||||
|
'raspbian': {
|
||||||
|
10: 'buster',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -24,10 +26,6 @@ actions = {
|
||||||
'triggered': True,
|
'triggered': True,
|
||||||
'cascade_skip': False,
|
'cascade_skip': False,
|
||||||
},
|
},
|
||||||
'apt_execute_update_commands': {
|
|
||||||
'command': ' && '.join(sorted(node.metadata.get('apt/additional_update_commands', {'true'}))),
|
|
||||||
'triggered': True,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
files = {
|
files = {
|
||||||
|
@ -115,7 +113,7 @@ pkg_apt = {
|
||||||
'mtr': {},
|
'mtr': {},
|
||||||
'ncdu': {},
|
'ncdu': {},
|
||||||
'ncurses-term': {},
|
'ncurses-term': {},
|
||||||
'netcat-openbsd': {},
|
'netcat': {},
|
||||||
'nmap': {},
|
'nmap': {},
|
||||||
'python3': {},
|
'python3': {},
|
||||||
'python3-dev': {},
|
'python3-dev': {},
|
||||||
|
@ -154,9 +152,6 @@ pkg_apt = {
|
||||||
'popularity-contest': {
|
'popularity-contest': {
|
||||||
'installed': False,
|
'installed': False,
|
||||||
},
|
},
|
||||||
'python3-packaging': {
|
|
||||||
'installed': False,
|
|
||||||
},
|
|
||||||
'unattended-upgrades': {
|
'unattended-upgrades': {
|
||||||
'installed': False,
|
'installed': False,
|
||||||
},
|
},
|
||||||
|
@ -173,7 +168,6 @@ if node.os_version[0] >= 11:
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, data in node.metadata.get('apt/repos', {}).items():
|
for name, data in node.metadata.get('apt/repos', {}).items():
|
||||||
if 'items' in data:
|
|
||||||
files['/etc/apt/sources.list.d/{}.list'.format(name)] = {
|
files['/etc/apt/sources.list.d/{}.list'.format(name)] = {
|
||||||
'content_type': 'mako',
|
'content_type': 'mako',
|
||||||
'content': ("\n".join(sorted(data['items']))).format(
|
'content': ("\n".join(sorted(data['items']))).format(
|
||||||
|
@ -184,30 +178,8 @@ for name, data in node.metadata.get('apt/repos', {}).items():
|
||||||
'action:apt_update',
|
'action:apt_update',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
elif 'uris' in data:
|
|
||||||
uris = {
|
|
||||||
x.format(
|
|
||||||
os=node.os,
|
|
||||||
os_release=supported_os[node.os][node.os_version[0]],
|
|
||||||
) for x in data['uris']
|
|
||||||
}
|
|
||||||
|
|
||||||
files['/etc/apt/sources.list.d/{}.sources'.format(name)] = {
|
|
||||||
'source': 'deb822-sources',
|
|
||||||
'content_type': 'mako',
|
|
||||||
'context': {
|
|
||||||
'data': data,
|
|
||||||
'name': name,
|
|
||||||
'os_release': supported_os[node.os][node.os_version[0]],
|
|
||||||
'uris': uris,
|
|
||||||
},
|
|
||||||
'triggers': {
|
|
||||||
'action:apt_update',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if data.get('install_gpg_key', True):
|
if data.get('install_gpg_key', True):
|
||||||
if 'items' in data:
|
|
||||||
files['/etc/apt/sources.list.d/{}.list'.format(name)]['needs'] = {
|
files['/etc/apt/sources.list.d/{}.list'.format(name)]['needs'] = {
|
||||||
'file:/etc/apt/trusted.gpg.d/{}.list.asc'.format(name),
|
'file:/etc/apt/trusted.gpg.d/{}.list.asc'.format(name),
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,24 +21,16 @@ defaults = {
|
||||||
'cron/jobs/upgrade-and-reboot'
|
'cron/jobs/upgrade-and-reboot'
|
||||||
)
|
)
|
||||||
def patchday(metadata):
|
def patchday(metadata):
|
||||||
if not node.metadata.get('apt/unattended-upgrades/enabled', True):
|
|
||||||
return {}
|
|
||||||
|
|
||||||
day = metadata.get('apt/unattended-upgrades/day')
|
day = metadata.get('apt/unattended-upgrades/day')
|
||||||
hour = metadata.get('apt/unattended-upgrades/hour')
|
hour = metadata.get('apt/unattended-upgrades/hour')
|
||||||
|
|
||||||
spread = metadata.get('apt/unattended-upgrades/spread_in_group', None)
|
|
||||||
if spread is not None:
|
|
||||||
spread_nodes = sorted(repo.nodes_in_group(spread))
|
|
||||||
day += spread_nodes.index(node)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'cron': {
|
'cron': {
|
||||||
'jobs': {
|
'jobs': {
|
||||||
'upgrade-and-reboot': '{minute} {hour} * * {day} root /usr/local/sbin/upgrade-and-reboot'.format(
|
'upgrade-and-reboot': '{minute} {hour} * * {day} root /usr/local/sbin/upgrade-and-reboot'.format(
|
||||||
minute=node.magic_number % 30,
|
minute=node.magic_number % 30,
|
||||||
hour=hour,
|
hour=hour,
|
||||||
day=day%7,
|
day=day,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
context.exec = [
|
|
||||||
{ path = "pactl" args = "load-module module-native-protocol-tcp" }
|
|
||||||
{ path = "pactl" args = "load-module module-zeroconf-discover" }
|
|
||||||
{ path = "pactl" args = "load-module module-zeroconf-publish" }
|
|
||||||
]
|
|
|
@ -44,11 +44,6 @@ directories = {
|
||||||
}
|
}
|
||||||
|
|
||||||
svc_systemd = {
|
svc_systemd = {
|
||||||
'avahi-daemon': {
|
|
||||||
'needs': {
|
|
||||||
'pkg_pacman:avahi',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'sddm': {
|
'sddm': {
|
||||||
'needs': {
|
'needs': {
|
||||||
'pkg_pacman:sddm',
|
'pkg_pacman:sddm',
|
||||||
|
@ -66,8 +61,6 @@ git_deploy = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
files['/etc/pipewire/pipewire-pulse.conf.d/50-network.conf'] = {}
|
|
||||||
|
|
||||||
for filename in listdir(join(repo.path, 'data', 'arch-with-gui', 'files', 'fonts')):
|
for filename in listdir(join(repo.path, 'data', 'arch-with-gui', 'files', 'fonts')):
|
||||||
if filename.startswith('.'):
|
if filename.startswith('.'):
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -9,14 +9,6 @@ defaults = {
|
||||||
'icinga_options': {
|
'icinga_options': {
|
||||||
'exclude_from_monitoring': True,
|
'exclude_from_monitoring': True,
|
||||||
},
|
},
|
||||||
'nftables': {
|
|
||||||
'input': {
|
|
||||||
'50-avahi': {
|
|
||||||
'udp dport 5353 accept',
|
|
||||||
'udp sport 5353 accept',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'pacman': {
|
'pacman': {
|
||||||
'packages': {
|
'packages': {
|
||||||
# fonts
|
# fonts
|
||||||
|
@ -31,9 +23,8 @@ defaults = {
|
||||||
'sddm': {},
|
'sddm': {},
|
||||||
|
|
||||||
# networking
|
# networking
|
||||||
'avahi': {},
|
|
||||||
'netctl': {},
|
'netctl': {},
|
||||||
'util-linux': {}, # provides rfkill
|
'rfkill': {},
|
||||||
'wpa_supplicant': {},
|
'wpa_supplicant': {},
|
||||||
'wpa_actiond': {},
|
'wpa_actiond': {},
|
||||||
|
|
||||||
|
@ -54,7 +45,6 @@ defaults = {
|
||||||
'pipewire': {},
|
'pipewire': {},
|
||||||
'pipewire-jack': {},
|
'pipewire-jack': {},
|
||||||
'pipewire-pulse': {},
|
'pipewire-pulse': {},
|
||||||
'pipewire-zeroconf': {},
|
|
||||||
'qpwgraph': {},
|
'qpwgraph': {},
|
||||||
|
|
||||||
# window management
|
# window management
|
||||||
|
|
|
@ -62,13 +62,10 @@ trap "on_exit" EXIT
|
||||||
|
|
||||||
# redirect stdout and stderr to logfile
|
# redirect stdout and stderr to logfile
|
||||||
prepare_and_cleanup_logdir
|
prepare_and_cleanup_logdir
|
||||||
if [[ -z "$DEBUG" ]]
|
logfile="$logdir/backup--$(date '+%F--%H-%M-%S')--$$.log.gz"
|
||||||
then
|
echo "All log output will go to $logfile" | logger -it backup-client
|
||||||
logfile="$logdir/backup--$(date '+%F--%H-%M-%S')--$$.log.gz"
|
exec > >(gzip >"$logfile")
|
||||||
echo "All log output will go to $logfile" | logger -it backup-client
|
exec 2>&1
|
||||||
exec > >(gzip >"$logfile")
|
|
||||||
exec 2>&1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# this is where the real work starts
|
# this is where the real work starts
|
||||||
ts_begin=$(date +%s)
|
ts_begin=$(date +%s)
|
||||||
|
|
|
@ -19,12 +19,12 @@ else:
|
||||||
|
|
||||||
if node.metadata.get('backups/exclude_from_backups', False):
|
if node.metadata.get('backups/exclude_from_backups', False):
|
||||||
# make sure nobody tries to do something funny
|
# make sure nobody tries to do something funny
|
||||||
for file in {
|
for file in [
|
||||||
'/etc/backup.priv',
|
'/etc/backup.priv',
|
||||||
'/usr/local/bin/generate-backup',
|
'/usr/local/bin/generate-backup',
|
||||||
'/usr/local/bin/generate-backup-with-retries',
|
'/usr/local/bin/generate-backup-with-retries',
|
||||||
'/var/tmp/backup.monitoring', # status file
|
'/var/tmp/backup.monitoring', # status file
|
||||||
}:
|
]:
|
||||||
files[file] = {
|
files[file] = {
|
||||||
'delete': True,
|
'delete': True,
|
||||||
}
|
}
|
||||||
|
@ -33,17 +33,14 @@ else:
|
||||||
backup_target = repo.get_node(node.metadata.get('backup-client/target'))
|
backup_target = repo.get_node(node.metadata.get('backup-client/target'))
|
||||||
|
|
||||||
files['/etc/backup.priv'] = {
|
files['/etc/backup.priv'] = {
|
||||||
'content': repo.libs.ssh.generate_ed25519_private_key(
|
'content': repo.vault.decrypt_file(join('backup', 'keys', f'{node.name}.key.vault')),
|
||||||
node.metadata.get('backup-client/user-name'),
|
|
||||||
backup_target,
|
|
||||||
),
|
|
||||||
'mode': '0400',
|
'mode': '0400',
|
||||||
}
|
}
|
||||||
|
|
||||||
files['/usr/local/bin/generate-backup'] = {
|
files['/usr/local/bin/generate-backup'] = {
|
||||||
'content_type': 'mako',
|
'content_type': 'mako',
|
||||||
'context': {
|
'context': {
|
||||||
'username': node.metadata.get('backup-client/user-name'),
|
'username': node.metadata['backup-client']['user-name'],
|
||||||
'server': backup_target.metadata.get('backup-server/my_hostname'),
|
'server': backup_target.metadata.get('backup-server/my_hostname'),
|
||||||
'port': backup_target.metadata.get('backup-server/my_ssh_port'),
|
'port': backup_target.metadata.get('backup-server/my_ssh_port'),
|
||||||
'paths': backup_paths,
|
'paths': backup_paths,
|
||||||
|
|
|
@ -27,6 +27,9 @@ directories['/etc/backup-server/clients'] = {
|
||||||
sudoers = {}
|
sudoers = {}
|
||||||
|
|
||||||
for nodename, config in node.metadata.get('backup-server/clients', {}).items():
|
for nodename, config in node.metadata.get('backup-server/clients', {}).items():
|
||||||
|
with open(join(repo.path, 'data', 'backup', 'keys', f'{nodename}.pub'), 'r') as f:
|
||||||
|
pubkey = f.read().strip()
|
||||||
|
|
||||||
sudoers[config['user']] = nodename
|
sudoers[config['user']] = nodename
|
||||||
|
|
||||||
users[config['user']] = {
|
users[config['user']] = {
|
||||||
|
@ -38,10 +41,7 @@ for nodename, config in node.metadata.get('backup-server/clients', {}).items():
|
||||||
}
|
}
|
||||||
|
|
||||||
files[f'/srv/backups/{nodename}/.ssh/authorized_keys'] = {
|
files[f'/srv/backups/{nodename}/.ssh/authorized_keys'] = {
|
||||||
'content': repo.libs.ssh.generate_ed25519_public_key(
|
'content': pubkey,
|
||||||
config['user'],
|
|
||||||
node,
|
|
||||||
),
|
|
||||||
'owner': config['user'],
|
'owner': config['user'],
|
||||||
'mode': '0400',
|
'mode': '0400',
|
||||||
'needs': {
|
'needs': {
|
||||||
|
|
|
@ -35,15 +35,8 @@ def get_my_clients(metadata):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
my_clients[rnode.name] = {
|
my_clients[rnode.name] = {
|
||||||
'exclude_from_monitoring': rnode.metadata.get(
|
|
||||||
'backup-client/exclude_from_monitoring',
|
|
||||||
rnode.metadata.get(
|
|
||||||
'icinga_options/exclude_from_monitoring',
|
|
||||||
False,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
'one_backup_every_hours': rnode.metadata.get('backup-client/one_backup_every_hours', 24),
|
|
||||||
'user': rnode.metadata.get('backup-client/user-name'),
|
'user': rnode.metadata.get('backup-client/user-name'),
|
||||||
|
'one_backup_every_hours': rnode.metadata.get('backup-client/one_backup_every_hours', 24),
|
||||||
'retain': {
|
'retain': {
|
||||||
'daily': rnode.metadata.get('backups/retain/daily', retain_defaults['daily']),
|
'daily': rnode.metadata.get('backups/retain/daily', retain_defaults['daily']),
|
||||||
'weekly': rnode.metadata.get('backups/retain/weekly', retain_defaults['weekly']),
|
'weekly': rnode.metadata.get('backups/retain/weekly', retain_defaults['weekly']),
|
||||||
|
@ -160,7 +153,7 @@ def monitoring(metadata):
|
||||||
client,
|
client,
|
||||||
config['one_backup_every_hours'],
|
config['one_backup_every_hours'],
|
||||||
),
|
),
|
||||||
'vars.sshmon_timeout': 40,
|
'vars.sshmon_timeout': 20,
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -32,8 +32,8 @@ account_guest_in_cpu_meter=0
|
||||||
color_scheme=0
|
color_scheme=0
|
||||||
enable_mouse=0
|
enable_mouse=0
|
||||||
delay=10
|
delay=10
|
||||||
left_meters=Tasks LoadAverage Uptime Memory CPU LeftCPUs2 CPU
|
left_meters=Tasks LoadAverage Uptime Memory CPU LeftCPUs CPU
|
||||||
left_meter_modes=2 2 2 1 1 1 2
|
left_meter_modes=2 2 2 1 1 1 2
|
||||||
right_meters=Hostname CPU RightCPUs2
|
right_meters=Hostname CPU RightCPUs
|
||||||
right_meter_modes=2 3 1
|
right_meter_modes=2 3 1
|
||||||
hide_function_bar=0
|
hide_function_bar=0
|
||||||
|
|
|
@ -29,19 +29,8 @@ files = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if node.has_any_bundle([
|
|
||||||
'dovecot',
|
|
||||||
'nginx',
|
|
||||||
'postfix',
|
|
||||||
]):
|
|
||||||
actions['generate-dhparam'] = {
|
|
||||||
'command': 'openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048',
|
|
||||||
'unless': 'test -f /etc/ssl/certs/dhparam.pem',
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
locale_needs = set()
|
locale_needs = set()
|
||||||
for locale in sorted(node.metadata.get('locale/installed')):
|
for locale in sorted(node.metadata['locale']['installed']):
|
||||||
actions[f'ensure_locale_{locale}_is_enabled'] = {
|
actions[f'ensure_locale_{locale}_is_enabled'] = {
|
||||||
'command': f"sed -i '/{locale}/s/^# *//g' /etc/locale.gen",
|
'command': f"sed -i '/{locale}/s/^# *//g' /etc/locale.gen",
|
||||||
'unless': f"grep -e '^{locale}' /etc/locale.gen",
|
'unless': f"grep -e '^{locale}' /etc/locale.gen",
|
||||||
|
@ -52,15 +41,17 @@ for locale in sorted(node.metadata.get('locale/installed')):
|
||||||
}
|
}
|
||||||
locale_needs = {f'action:ensure_locale_{locale}_is_enabled'}
|
locale_needs = {f'action:ensure_locale_{locale}_is_enabled'}
|
||||||
|
|
||||||
actions['locale-gen'] = {
|
actions = {
|
||||||
|
'locale-gen': {
|
||||||
'triggered': True,
|
'triggered': True,
|
||||||
'command': 'locale-gen',
|
'command': 'locale-gen',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
description = []
|
description = []
|
||||||
|
|
||||||
if not node.metadata.get('icinga_options/exclude_from_monitoring', False):
|
if not node.metadata.get('icinga_options/exclude_from_monitoring', False):
|
||||||
description.append('icingaweb2: https://icinga.franzi.business/monitoring/host/show?host={}'.format(node.name))
|
description.append('icingaweb2: https://icinga.kunsmann.eu/monitoring/host/show?host={}'.format(node.name))
|
||||||
|
|
||||||
if node.has_bundle('telegraf'):
|
if node.has_bundle('telegraf'):
|
||||||
description.append('Grafana: https://grafana.kunsmann.eu/d/{}'.format(UUID(int=node.magic_number).hex[:10]))
|
description.append('Grafana: https://grafana.kunsmann.eu/d/{}'.format(UUID(int=node.magic_number).hex[:10]))
|
||||||
|
|
|
@ -19,9 +19,7 @@ protocol static {
|
||||||
ipv4;
|
ipv4;
|
||||||
|
|
||||||
% for route in sorted(node.metadata.get('bird/static_routes', set())):
|
% for route in sorted(node.metadata.get('bird/static_routes', set())):
|
||||||
% for name, config in sorted(node.metadata.get('bird/bgp_neighbors', {}).items()):
|
route ${route} via ${node.metadata.get('bird/my_ip')};
|
||||||
route ${route} via ${config['local_ip']};
|
|
||||||
% endfor
|
|
||||||
% endfor
|
% endfor
|
||||||
}
|
}
|
||||||
% endif
|
% endif
|
||||||
|
|
|
@ -24,7 +24,7 @@ defaults = {
|
||||||
},
|
},
|
||||||
'sysctl': {
|
'sysctl': {
|
||||||
'options': {
|
'options': {
|
||||||
'net.ipv4.conf.all.forwarding': '1',
|
'net.ipv4.ip_forward': '1',
|
||||||
'net.ipv6.conf.all.forwarding': '1',
|
'net.ipv6.conf.all.forwarding': '1',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -43,9 +43,6 @@ def neighbor_info_from_wireguard(metadata):
|
||||||
except NoSuchNode:
|
except NoSuchNode:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not rnode.has_bundle('bird'):
|
|
||||||
continue
|
|
||||||
|
|
||||||
neighbors[name] = {
|
neighbors[name] = {
|
||||||
'local_ip': config['my_ip'],
|
'local_ip': config['my_ip'],
|
||||||
'local_as': my_as,
|
'local_as': my_as,
|
||||||
|
@ -65,10 +62,7 @@ def neighbor_info_from_wireguard(metadata):
|
||||||
)
|
)
|
||||||
def my_ip(metadata):
|
def my_ip(metadata):
|
||||||
if node.has_bundle('wireguard'):
|
if node.has_bundle('wireguard'):
|
||||||
wg_ifaces = sorted({iface for iface in metadata.get('interfaces').keys() if iface.startswith('wg_')})
|
my_ip = sorted(metadata.get('interfaces/wg0/ips'))[0].split('/')[0]
|
||||||
if not wg_ifaces:
|
|
||||||
return {}
|
|
||||||
my_ip = sorted(metadata.get(f'interfaces/{wg_ifaces[0]}/ips'))[0].split('/')[0]
|
|
||||||
else:
|
else:
|
||||||
my_ip = str(sorted(repo.libs.tools.resolve_identifier(repo, node.name))[0])
|
my_ip = str(sorted(repo.libs.tools.resolve_identifier(repo, node.name))[0])
|
||||||
|
|
||||||
|
@ -90,7 +84,7 @@ def firewall(metadata):
|
||||||
return {
|
return {
|
||||||
'firewall': {
|
'firewall': {
|
||||||
'port_rules': {
|
'port_rules': {
|
||||||
'179/tcp': atomic(sources),
|
'179': atomic(sources),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,5 @@
|
||||||
from bundlewrap.exceptions import BundleError
|
from bundlewrap.exceptions import BundleError
|
||||||
|
|
||||||
supported_os = {
|
|
||||||
'debian': {
|
|
||||||
10: 'buster',
|
|
||||||
11: 'bullseye',
|
|
||||||
12: 'bookworm',
|
|
||||||
99: 'unstable',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
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')
|
|
||||||
|
|
||||||
CONFLICTING_BUNDLES = {
|
CONFLICTING_BUNDLES = {
|
||||||
'apt',
|
'apt',
|
||||||
'nginx',
|
'nginx',
|
||||||
|
@ -71,18 +57,6 @@ actions = {
|
||||||
'svc_systemd:',
|
'svc_systemd:',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'apt_update': {
|
|
||||||
'command': 'apt-get update',
|
|
||||||
'needed_by': {
|
|
||||||
'pkg_apt:',
|
|
||||||
},
|
|
||||||
'triggered': True,
|
|
||||||
'cascade_skip': False,
|
|
||||||
},
|
|
||||||
'apt_execute_update_commands': {
|
|
||||||
'command': ' && '.join(sorted(node.metadata.get('apt/additional_update_commands', {'true'}))),
|
|
||||||
'triggered': True,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
directories = {
|
directories = {
|
||||||
|
@ -118,30 +92,6 @@ files = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
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 crontab, content in node.metadata.get('cron/jobs', {}).items():
|
for crontab, content in node.metadata.get('cron/jobs', {}).items():
|
||||||
files['/etc/cron.d/{}'.format(crontab)] = {
|
files['/etc/cron.d/{}'.format(crontab)] = {
|
||||||
'source': 'cron_template',
|
'source': 'cron_template',
|
||||||
|
|
|
@ -17,7 +17,7 @@ files = {
|
||||||
directories = {
|
directories = {
|
||||||
'/etc/cron.d': {
|
'/etc/cron.d': {
|
||||||
'purge': True,
|
'purge': True,
|
||||||
'after': {
|
'needs': {
|
||||||
'pkg_apt:',
|
'pkg_apt:',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
36
bundles/dhcpd/files/dhcpd.conf
Normal file
36
bundles/dhcpd/files/dhcpd.conf
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
<%
|
||||||
|
import re
|
||||||
|
from ipaddress import ip_network
|
||||||
|
%>
|
||||||
|
ddns-update-style none;
|
||||||
|
|
||||||
|
authoritative;
|
||||||
|
|
||||||
|
% for interface, subnet in sorted(dhcp_config.get('subnets', {}).items()):
|
||||||
|
<%
|
||||||
|
network = ip_network(subnet['subnet'])
|
||||||
|
%>
|
||||||
|
# interface ${interface} provides ${subnet['subnet']}
|
||||||
|
subnet ${network.network_address} netmask ${network.netmask} {
|
||||||
|
% if subnet.get('range_lower', None) and subnet.get('range_higher', None):
|
||||||
|
range ${subnet['range_lower']} ${subnet['range_higher']};
|
||||||
|
% endif
|
||||||
|
interface "${interface}";
|
||||||
|
default-lease-time ${subnet.get('default-lease-time', 600)};
|
||||||
|
max-lease-time ${subnet.get('max-lease-time', 3600)};
|
||||||
|
% for option, value in sorted(subnet.get('options', {}).items()):
|
||||||
|
% if re.match('([^0-9\.,\ ])', value):
|
||||||
|
option ${option} "${value}";
|
||||||
|
% else:
|
||||||
|
option ${option} ${value};
|
||||||
|
% endif
|
||||||
|
% endfor
|
||||||
|
}
|
||||||
|
% endfor
|
||||||
|
|
||||||
|
% for identifier, allocation in dhcp_config.get('fixed_allocations', {}).items():
|
||||||
|
host ${identifier} {
|
||||||
|
hardware ethernet ${allocation['mac']};
|
||||||
|
fixed-address ${allocation['ipv4']};
|
||||||
|
}
|
||||||
|
% endfor
|
18
bundles/dhcpd/files/isc-dhcp-server
Normal file
18
bundles/dhcpd/files/isc-dhcp-server
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# Defaults for isc-dhcp-server (sourced by /etc/init.d/isc-dhcp-server)
|
||||||
|
|
||||||
|
# Path to dhcpd's config file (default: /etc/dhcp/dhcpd.conf).
|
||||||
|
#DHCPDv4_CONF=/etc/dhcp/dhcpd.conf
|
||||||
|
#DHCPDv6_CONF=/etc/dhcp/dhcpd6.conf
|
||||||
|
|
||||||
|
# Path to dhcpd's PID file (default: /var/run/dhcpd.pid).
|
||||||
|
#DHCPDv4_PID=/var/run/dhcpd.pid
|
||||||
|
#DHCPDv6_PID=/var/run/dhcpd6.pid
|
||||||
|
|
||||||
|
# Additional options to start dhcpd with.
|
||||||
|
# Don't use options -cf or -pf here; use DHCPD_CONF/ DHCPD_PID instead
|
||||||
|
#OPTIONS=""
|
||||||
|
|
||||||
|
# On what interfaces should the DHCP server (dhcpd) serve DHCP requests?
|
||||||
|
# Separate multiple interfaces with spaces, e.g. "eth0 eth1".
|
||||||
|
INTERFACESv4="${' '.join(sorted(node.metadata.get('dhcpd/subnets', {})))}"
|
||||||
|
INTERFACESv6=""
|
41
bundles/dhcpd/items.py
Normal file
41
bundles/dhcpd/items.py
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
files = {
|
||||||
|
'/etc/dhcp/dhcpd.conf': {
|
||||||
|
'content_type': 'mako',
|
||||||
|
'context': {
|
||||||
|
'dhcp_config': node.metadata['dhcpd'],
|
||||||
|
},
|
||||||
|
'needs': {
|
||||||
|
'pkg_apt:isc-dhcp-server'
|
||||||
|
},
|
||||||
|
'triggers': {
|
||||||
|
'svc_systemd:isc-dhcp-server:restart',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'/etc/default/isc-dhcp-server': {
|
||||||
|
'content_type': 'mako',
|
||||||
|
'needs': {
|
||||||
|
'pkg_apt:isc-dhcp-server'
|
||||||
|
},
|
||||||
|
'triggers': {
|
||||||
|
'svc_systemd:isc-dhcp-server:restart',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
actions = {
|
||||||
|
# needed for dhcp-lease-list
|
||||||
|
'dhcpd_download_oui.txt': {
|
||||||
|
'command': 'wget http://standards-oui.ieee.org/oui.txt -O /usr/local/etc/oui.txt',
|
||||||
|
'unless': 'test -f /usr/local/etc/oui.txt',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
svc_systemd = {
|
||||||
|
'isc-dhcp-server': {
|
||||||
|
'needs': {
|
||||||
|
'pkg_apt:isc-dhcp-server',
|
||||||
|
'file:/etc/dhcp/dhcpd.conf',
|
||||||
|
'file:/etc/default/isc-dhcp-server',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
54
bundles/dhcpd/metadata.py
Normal file
54
bundles/dhcpd/metadata.py
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
defaults = {
|
||||||
|
'apt': {
|
||||||
|
'packages': {
|
||||||
|
'isc-dhcp-server': {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'bash_aliases': {
|
||||||
|
'leases': 'sudo dhcp-lease-list | tail -n +4 | sort -k 2,2',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@metadata_reactor.provides(
|
||||||
|
'dhcpd/fixed_allocations',
|
||||||
|
)
|
||||||
|
def get_static_allocations(metadata):
|
||||||
|
allocations = {}
|
||||||
|
for rnode in repo.nodes:
|
||||||
|
if rnode.metadata.get('location', '') != metadata.get('location', ''):
|
||||||
|
continue
|
||||||
|
|
||||||
|
for iface_name, iface_config in rnode.metadata.get('interfaces', {}).items():
|
||||||
|
if iface_config.get('dhcp', False):
|
||||||
|
try:
|
||||||
|
allocations[f'{rnode.name}_{iface_name}'] = {
|
||||||
|
'ipv4': sorted(iface_config['ips'])[0],
|
||||||
|
'mac': iface_config['mac'],
|
||||||
|
}
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return {
|
||||||
|
'dhcpd': {
|
||||||
|
'fixed_allocations': allocations,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@metadata_reactor.provides(
|
||||||
|
'nftables/rules/10-dhcpd',
|
||||||
|
)
|
||||||
|
def nftables(metadata):
|
||||||
|
rules = set()
|
||||||
|
for iface in node.metadata.get('dhcpd/subnets', {}):
|
||||||
|
rules.add(f'inet filter input udp dport {{ 67, 68 }} iif {iface} accept')
|
||||||
|
|
||||||
|
return {
|
||||||
|
'nftables': {
|
||||||
|
'rules': {
|
||||||
|
# can't use port_rules here, because we're generating interface based rules.
|
||||||
|
'10-dhcpd': sorted(rules),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
11
bundles/docker-ce/items.py
Normal file
11
bundles/docker-ce/items.py
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
from bundlewrap.metadata import metadata_to_json
|
||||||
|
|
||||||
|
files['/etc/docker/daemon.json'] = {
|
||||||
|
'content': metadata_to_json({
|
||||||
|
'iptables': False,
|
||||||
|
}),
|
||||||
|
'before': {
|
||||||
|
'pkg_apt:docker-ce',
|
||||||
|
'pkg_apt:docker-ce-cli',
|
||||||
|
}
|
||||||
|
}
|
36
bundles/docker-ce/metadata.py
Normal file
36
bundles/docker-ce/metadata.py
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
defaults = {
|
||||||
|
'apt': {
|
||||||
|
'repos': {
|
||||||
|
'docker': {
|
||||||
|
'items': {
|
||||||
|
'deb https://download.docker.com/linux/debian {os_release} stable',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'packages': {
|
||||||
|
'docker-ce': {},
|
||||||
|
'docker-ce-cli': {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@metadata_reactor.provides(
|
||||||
|
'nftables/rules/00-docker-ce',
|
||||||
|
)
|
||||||
|
def nftables_nat(metadata):
|
||||||
|
rules = {
|
||||||
|
'inet filter forward ct state { related, established } accept',
|
||||||
|
'inet filter forward iifname docker0 accept',
|
||||||
|
}
|
||||||
|
|
||||||
|
for iface in metadata.get('interfaces'):
|
||||||
|
rules.add(f'nat postrouting oifname {iface} masquerade')
|
||||||
|
|
||||||
|
return {
|
||||||
|
'nftables': {
|
||||||
|
'rules': {
|
||||||
|
'00-docker-ce': sorted(rules),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
|
@ -1,39 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
from json import loads
|
|
||||||
from subprocess import check_output
|
|
||||||
from sys import argv
|
|
||||||
|
|
||||||
try:
|
|
||||||
container_name = argv[1]
|
|
||||||
|
|
||||||
docker_ps = check_output([
|
|
||||||
'docker',
|
|
||||||
'container',
|
|
||||||
'ls',
|
|
||||||
'--all',
|
|
||||||
'--format',
|
|
||||||
'json',
|
|
||||||
'--filter',
|
|
||||||
f'name={container_name}'
|
|
||||||
])
|
|
||||||
|
|
||||||
containers = loads(f"[{','.join([l for l in docker_ps.decode().splitlines() if l])}]")
|
|
||||||
|
|
||||||
if not containers:
|
|
||||||
print(f'CRITICAL: container {container_name} not found!')
|
|
||||||
exit(2)
|
|
||||||
|
|
||||||
if len(containers) > 1:
|
|
||||||
print(f'Found more than one container matching {container_name}!')
|
|
||||||
print(docker_ps)
|
|
||||||
exit(3)
|
|
||||||
|
|
||||||
if containers[0]['State'] != 'running':
|
|
||||||
print(f'WARNING: container {container_name} not "running"')
|
|
||||||
exit(2)
|
|
||||||
|
|
||||||
print(f"OK: {containers[0]['Status']}")
|
|
||||||
except Exception as e:
|
|
||||||
print(repr(e))
|
|
||||||
exit(2)
|
|
|
@ -1,50 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
[[ -n "$DEBUG" ]] && set -x
|
|
||||||
|
|
||||||
ACTION="$1"
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
if [[ -z "$ACTION" ]]
|
|
||||||
then
|
|
||||||
echo "Usage: $0 start|stop"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
PUID="$(id -u "docker-${name}")"
|
|
||||||
PGID="$(id -g "docker-${name}")"
|
|
||||||
|
|
||||||
if [ "$ACTION" == "start" ]
|
|
||||||
then
|
|
||||||
docker run -d \
|
|
||||||
--name "${name}" \
|
|
||||||
--env "PUID=$PUID" \
|
|
||||||
--env "PGID=$PGID" \
|
|
||||||
--env "TZ=${timezone}" \
|
|
||||||
% for k, v in sorted(environment.items()):
|
|
||||||
--env "${k}=${v}" \
|
|
||||||
% endfor
|
|
||||||
--network host \
|
|
||||||
% for host_port, container_port in sorted(ports.items()):
|
|
||||||
--expose "127.0.0.1:${host_port}:${container_port}" \
|
|
||||||
% endfor
|
|
||||||
% for host_path, container_path in sorted(volumes.items()):
|
|
||||||
--volume "/var/opt/docker-engine/${name}/${host_path}:${container_path}" \
|
|
||||||
% endfor
|
|
||||||
--restart unless-stopped \
|
|
||||||
"${image}"
|
|
||||||
|
|
||||||
elif [ "$ACTION" == "stop" ]
|
|
||||||
then
|
|
||||||
docker stop "${name}"
|
|
||||||
docker rm "${name}"
|
|
||||||
|
|
||||||
else
|
|
||||||
echo "Unknown action $ACTION"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
% if node.has_bundle('nftables'):
|
|
||||||
systemctl reload nftables
|
|
||||||
% endif
|
|
|
@ -1,14 +0,0 @@
|
||||||
[Unit]
|
|
||||||
Description=docker-engine app ${name}
|
|
||||||
After=network.target
|
|
||||||
Requires=${' '.join(sorted(requires))}
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
WorkingDirectory=/var/opt/docker-engine/${name}/
|
|
||||||
ExecStart=/opt/docker-engine/${name} start
|
|
||||||
ExecStop=/opt/docker-engine/${name} stop
|
|
||||||
Type=simple
|
|
||||||
RemainAfterExit=true
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
|
@ -1,99 +0,0 @@
|
||||||
from bundlewrap.metadata import metadata_to_json
|
|
||||||
|
|
||||||
deps = {
|
|
||||||
'pkg_apt:docker-ce',
|
|
||||||
'pkg_apt:docker-ce-cli',
|
|
||||||
}
|
|
||||||
|
|
||||||
directories['/opt/docker-engine'] = {
|
|
||||||
'purge': True,
|
|
||||||
}
|
|
||||||
directories['/var/opt/docker-engine'] = {}
|
|
||||||
|
|
||||||
files['/etc/docker/daemon.json'] = {
|
|
||||||
'content': metadata_to_json(node.metadata.get('docker-engine/config')),
|
|
||||||
'triggers': {
|
|
||||||
'svc_systemd:docker:restart',
|
|
||||||
},
|
|
||||||
# install config before installing packages to ensure the config is
|
|
||||||
# applied to the first start as well
|
|
||||||
'before': deps,
|
|
||||||
}
|
|
||||||
|
|
||||||
svc_systemd['docker'] = {
|
|
||||||
'needs': deps,
|
|
||||||
}
|
|
||||||
|
|
||||||
files['/usr/local/share/icinga/plugins/check_docker_container'] = {
|
|
||||||
'mode': '0755',
|
|
||||||
}
|
|
||||||
|
|
||||||
for app, config in node.metadata.get('docker-engine/containers', {}).items():
|
|
||||||
volumes = config.get('volumes', {})
|
|
||||||
|
|
||||||
files[f'/opt/docker-engine/{app}'] = {
|
|
||||||
'source': 'docker-wrapper',
|
|
||||||
'content_type': 'mako',
|
|
||||||
'context': {
|
|
||||||
'environment': config.get('environment', {}),
|
|
||||||
'image': config['image'],
|
|
||||||
'name': app,
|
|
||||||
'ports': config.get('ports', {}),
|
|
||||||
'timezone': node.metadata.get('timezone'),
|
|
||||||
'volumes': volumes,
|
|
||||||
},
|
|
||||||
'mode': '0755',
|
|
||||||
'triggers': {
|
|
||||||
f'svc_systemd:docker-{app}:restart',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
users[f'docker-{app}'] = {
|
|
||||||
'home': f'/var/opt/docker-engine/{app}',
|
|
||||||
'groups': {
|
|
||||||
'docker',
|
|
||||||
},
|
|
||||||
'after': {
|
|
||||||
# provides docker group
|
|
||||||
'pkg_apt:docker-ce',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
files[f'/usr/local/lib/systemd/system/docker-{app}.service'] = {
|
|
||||||
'source': 'docker-wrapper.service',
|
|
||||||
'content_type': 'mako',
|
|
||||||
'context': {
|
|
||||||
'name': app,
|
|
||||||
'requires': {
|
|
||||||
*set(config.get('requires', set())),
|
|
||||||
'docker.service',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'triggers': {
|
|
||||||
'action:systemd-reload',
|
|
||||||
f'svc_systemd:docker-{app}:restart',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
svc_systemd[f'docker-{app}'] = {
|
|
||||||
'needs': {
|
|
||||||
*deps,
|
|
||||||
f'file:/opt/docker-engine/{app}',
|
|
||||||
f'file:/usr/local/lib/systemd/system/docker-{app}.service',
|
|
||||||
f'user:docker-{app}',
|
|
||||||
'svc_systemd:docker',
|
|
||||||
*set(config.get('needs', set())),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for volume in volumes:
|
|
||||||
directories[f'/var/opt/docker-engine/{app}/{volume}'] = {
|
|
||||||
'owner': f'docker-{app}',
|
|
||||||
'group': f'docker-{app}',
|
|
||||||
'needed_by': {
|
|
||||||
f'svc_systemd:docker-{app}',
|
|
||||||
},
|
|
||||||
# don't do anything if the directory exists, docker images
|
|
||||||
# mangle owners
|
|
||||||
'unless': f'test -d /var/opt/docker-engine/{app}/{volume}',
|
|
||||||
}
|
|
|
@ -1,83 +0,0 @@
|
||||||
defaults = {
|
|
||||||
'apt': {
|
|
||||||
'packages': {
|
|
||||||
'docker-ce': {},
|
|
||||||
'docker-ce-cli': {},
|
|
||||||
'docker-compose-plugin': {},
|
|
||||||
},
|
|
||||||
'repos': {
|
|
||||||
'docker': {
|
|
||||||
'items': {
|
|
||||||
'deb https://download.docker.com/linux/debian {os_release} stable',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'backups': {
|
|
||||||
'paths': {
|
|
||||||
'/var/opt/docker-engine',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'hosts': {
|
|
||||||
'entries': {
|
|
||||||
'172.17.0.1': {
|
|
||||||
'host.docker.internal',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'docker-engine': {
|
|
||||||
'config': {
|
|
||||||
'iptables': False,
|
|
||||||
'no-new-privileges': True,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'zfs': {
|
|
||||||
'datasets': {
|
|
||||||
'tank/docker-data': {
|
|
||||||
'mountpoint': '/var/opt/docker-engine',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@metadata_reactor.provides(
|
|
||||||
'icinga2_api/docker-engine/services',
|
|
||||||
)
|
|
||||||
def monitoring(metadata):
|
|
||||||
services = {
|
|
||||||
'DOCKER PROCESS': {
|
|
||||||
'command_on_monitored_host': '/usr/lib/nagios/plugins/check_procs -C dockerd -c 1:',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for app in metadata.get('docker-engine/containers', {}):
|
|
||||||
services[f'DOCKER CONTAINER {app}'] = {
|
|
||||||
'command_on_monitored_host': f'sudo /usr/local/share/icinga/plugins/check_docker_container {app}'
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
'icinga2_api': {
|
|
||||||
'docker-engine': {
|
|
||||||
'services': services,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@metadata_reactor.provides(
|
|
||||||
'zfs/datasets',
|
|
||||||
)
|
|
||||||
def zfs(metadata):
|
|
||||||
datasets = {}
|
|
||||||
|
|
||||||
for app in metadata.get('docker-engine/containers', {}):
|
|
||||||
datasets[f'tank/docker-data/{app}'] = {
|
|
||||||
'mountpoint': f'/var/opt/docker-engine/{app}'
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
'zfs': {
|
|
||||||
'datasets': datasets,
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -1,64 +0,0 @@
|
||||||
assert node.has_bundle('docker-engine')
|
|
||||||
assert node.has_bundle('redis')
|
|
||||||
assert not node.has_bundle('postgresql') # docker container uses that port
|
|
||||||
|
|
||||||
defaults = {
|
|
||||||
'docker-engine': {
|
|
||||||
'containers': {
|
|
||||||
'immich': {
|
|
||||||
'image': 'ghcr.io/imagegenius/immich:latest',
|
|
||||||
'environment': {
|
|
||||||
'DB_DATABASE_NAME': 'immich',
|
|
||||||
'DB_HOSTNAME': 'host.docker.internal',
|
|
||||||
'DB_PASSWORD': repo.vault.password_for(f'{node.name} postgresql immich'),
|
|
||||||
'DB_USERNAME': 'immich',
|
|
||||||
'REDIS_HOSTNAME': 'host.docker.internal',
|
|
||||||
},
|
|
||||||
'volumes': {
|
|
||||||
'config': '/config',
|
|
||||||
'libraries': '/libraries',
|
|
||||||
'photos': '/photos',
|
|
||||||
},
|
|
||||||
'needs': {
|
|
||||||
'svc_systemd:docker-postgresql14',
|
|
||||||
},
|
|
||||||
'requires': {
|
|
||||||
'docker-postgresql14.service',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'postgresql14': {
|
|
||||||
'image': 'tensorchord/pgvecto-rs:pg14-v0.2.0',
|
|
||||||
'environment': {
|
|
||||||
'POSTGRES_PASSWORD': repo.vault.password_for(f'{node.name} postgresql immich'),
|
|
||||||
'POSTGRES_USER': 'immich',
|
|
||||||
'POSTGRES_DB': 'immich',
|
|
||||||
},
|
|
||||||
'volumes': {
|
|
||||||
'database': '/var/lib/postgresql/data',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'nginx': {
|
|
||||||
'vhosts': {
|
|
||||||
'immich': {
|
|
||||||
'locations': {
|
|
||||||
'/': {
|
|
||||||
'target': 'http://127.0.0.1:8080/',
|
|
||||||
'websockets': True,
|
|
||||||
'max_body_size': '500m',
|
|
||||||
},
|
|
||||||
#'/api/socket.io/': {
|
|
||||||
# 'target': 'http://127.0.0.1:8081/',
|
|
||||||
# 'websockets': True,
|
|
||||||
#},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'redis': {
|
|
||||||
'bind': '0.0.0.0',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -3,4 +3,3 @@ driver = pgsql
|
||||||
default_pass_scheme = MD5-CRYPT
|
default_pass_scheme = MD5-CRYPT
|
||||||
password_query = SELECT username as user, password FROM mailbox WHERE username = '%u' AND active = true
|
password_query = SELECT username as user, password FROM mailbox WHERE username = '%u' AND active = true
|
||||||
user_query = SELECT '/var/mail/vmail/' || maildir as home, 65534 as uid, 65534 as gid FROM mailbox WHERE username = '%u' AND active = true
|
user_query = SELECT '/var/mail/vmail/' || maildir as home, 65534 as uid, 65534 as gid FROM mailbox WHERE username = '%u' AND active = true
|
||||||
iterate_query = SELECT username as user FROM mailbox WHERE active = true
|
|
||||||
|
|
|
@ -28,19 +28,19 @@ namespace inbox {
|
||||||
mail_location = maildir:/var/mail/vmail/%d/%n
|
mail_location = maildir:/var/mail/vmail/%d/%n
|
||||||
protocols = imap lmtp sieve
|
protocols = imap lmtp sieve
|
||||||
|
|
||||||
ssl = required
|
ssl = yes
|
||||||
ssl_cert = </var/lib/dehydrated/certs/${node.metadata.get('postfix/myhostname')}/fullchain.pem
|
ssl_cert = </var/lib/dehydrated/certs/${node.metadata.get('postfix/myhostname', node.metadata['hostname'])}/fullchain.pem
|
||||||
ssl_key = </var/lib/dehydrated/certs/${node.metadata.get('postfix/myhostname')}/privkey.pem
|
ssl_key = </var/lib/dehydrated/certs/${node.metadata.get('postfix/myhostname', node.metadata['hostname'])}/privkey.pem
|
||||||
ssl_dh = </etc/ssl/certs/dhparam.pem
|
ssl_dh = </etc/dovecot/ssl/dhparam.pem
|
||||||
ssl_min_protocol = TLSv1.2
|
ssl_min_protocol = TLSv1.2
|
||||||
ssl_cipher_list = 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:DHE-RSA-CHACHA20-POLY1305
|
ssl_cipher_list = EECDH+AESGCM:EDH+AESGCM
|
||||||
ssl_prefer_server_ciphers = no
|
ssl_prefer_server_ciphers = yes
|
||||||
|
|
||||||
login_greeting = IMAPd ready
|
login_greeting = IMAPd ready
|
||||||
auth_mechanisms = plain login
|
auth_mechanisms = plain login
|
||||||
first_valid_uid = 65534
|
first_valid_uid = 65534
|
||||||
disable_plaintext_auth = yes
|
disable_plaintext_auth = yes
|
||||||
mail_plugins = $mail_plugins zlib old_stats fts fts_xapian
|
mail_plugins = $mail_plugins zlib old_stats
|
||||||
|
|
||||||
plugin {
|
plugin {
|
||||||
zlib_save_level = 6
|
zlib_save_level = 6
|
||||||
|
@ -56,15 +56,6 @@ plugin {
|
||||||
old_stats_refresh = 30 secs
|
old_stats_refresh = 30 secs
|
||||||
old_stats_track_cmds = yes
|
old_stats_track_cmds = yes
|
||||||
|
|
||||||
fts = xapian
|
|
||||||
fts_xapian = partial=3 full=20
|
|
||||||
|
|
||||||
fts_autoindex = yes
|
|
||||||
fts_enforced = yes
|
|
||||||
|
|
||||||
# Index attachements
|
|
||||||
fts_decoder = decode2text
|
|
||||||
|
|
||||||
% if node.has_bundle('rspamd'):
|
% if node.has_bundle('rspamd'):
|
||||||
sieve_before = /var/mail/vmail/sieve/global/spam-global.sieve
|
sieve_before = /var/mail/vmail/sieve/global/spam-global.sieve
|
||||||
|
|
||||||
|
@ -95,19 +86,14 @@ service auth {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
service decode2text {
|
service lmtp {
|
||||||
executable = script /usr/lib/dovecot/decode2text.sh
|
unix_listener /var/spool/postfix/private/dovecot-lmtp {
|
||||||
user = dovecot
|
group = postfix
|
||||||
unix_listener decode2text {
|
mode = 0600
|
||||||
mode = 0666
|
user = postfix
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
service indexer-worker {
|
|
||||||
vsz_limit = 0
|
|
||||||
process_limit = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
service imap {
|
service imap {
|
||||||
executable = imap
|
executable = imap
|
||||||
}
|
}
|
||||||
|
@ -118,14 +104,6 @@ service imap-login {
|
||||||
vsz_limit = 64M
|
vsz_limit = 64M
|
||||||
}
|
}
|
||||||
|
|
||||||
service lmtp {
|
|
||||||
unix_listener /var/spool/postfix/private/dovecot-lmtp {
|
|
||||||
group = postfix
|
|
||||||
mode = 0600
|
|
||||||
user = postfix
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
service managesieve-login {
|
service managesieve-login {
|
||||||
inet_listener sieve {
|
inet_listener sieve {
|
||||||
port = 4190
|
port = 4190
|
||||||
|
|
|
@ -2,6 +2,10 @@
|
||||||
# by this bundle
|
# by this bundle
|
||||||
repo.libs.tools.require_bundle(node, 'postfix')
|
repo.libs.tools.require_bundle(node, 'postfix')
|
||||||
|
|
||||||
|
directories = {
|
||||||
|
'/etc/dovecot/ssl': {},
|
||||||
|
}
|
||||||
|
|
||||||
files = {
|
files = {
|
||||||
'/etc/dovecot/dovecot.conf': {
|
'/etc/dovecot/dovecot.conf': {
|
||||||
'content_type': 'mako',
|
'content_type': 'mako',
|
||||||
|
@ -45,17 +49,25 @@ files = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
symlinks['/usr/lib/dovecot/decode2text.sh'] = {
|
actions = {
|
||||||
'target': '/usr/share/doc/dovecot-core/examples/decode2text.sh',
|
'dovecot_generate_dhparam': {
|
||||||
'before': {
|
'command': 'openssl dhparam -out /etc/dovecot/ssl/dhparam.pem 2048',
|
||||||
'svc_systemd:dovecot',
|
'unless': 'test -f /etc/dovecot/ssl/dhparam.pem',
|
||||||
|
'cascade_skip': False,
|
||||||
|
'needs': {
|
||||||
|
'directory:/etc/dovecot/ssl',
|
||||||
|
'pkg_apt:'
|
||||||
|
},
|
||||||
|
'triggers': {
|
||||||
|
'svc_systemd:dovecot:restart',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
svc_systemd = {
|
svc_systemd = {
|
||||||
'dovecot': {
|
'dovecot': {
|
||||||
'needs': {
|
'needs': {
|
||||||
'action:generate-dhparam',
|
'action:dovecot_generate_dhparam',
|
||||||
'file:/etc/dovecot/dovecot.conf',
|
'file:/etc/dovecot/dovecot.conf',
|
||||||
'file:/etc/dovecot/dovecot-sql.conf',
|
'file:/etc/dovecot/dovecot-sql.conf',
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,7 +3,6 @@ from bundlewrap.metadata import atomic
|
||||||
defaults = {
|
defaults = {
|
||||||
'apt': {
|
'apt': {
|
||||||
'packages': {
|
'packages': {
|
||||||
'dovecot-fts-xapian': {},
|
|
||||||
'dovecot-imapd': {},
|
'dovecot-imapd': {},
|
||||||
'dovecot-lmtpd': {},
|
'dovecot-lmtpd': {},
|
||||||
'dovecot-managesieved': {},
|
'dovecot-managesieved': {},
|
||||||
|
@ -36,16 +35,6 @@ defaults = {
|
||||||
'dovecot',
|
'dovecot',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'systemd-timers': {
|
|
||||||
'timers': {
|
|
||||||
'dovecot_fts_optimize': {
|
|
||||||
'command': [
|
|
||||||
'/usr/bin/doveadm fts optimize -A',
|
|
||||||
],
|
|
||||||
'when': '02:{}:00'.format(node.magic_number % 60),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if node.has_bundle('postfixadmin'):
|
if node.has_bundle('postfixadmin'):
|
||||||
|
@ -87,19 +76,19 @@ def import_database_settings_from_postfixadmin(metadata):
|
||||||
|
|
||||||
|
|
||||||
@metadata_reactor.provides(
|
@metadata_reactor.provides(
|
||||||
'firewall/port_rules',
|
'firewall/port_rules/143',
|
||||||
'firewall/port_rules',
|
'firewall/port_rules/993',
|
||||||
'firewall/port_rules',
|
'firewall/port_rules/4190',
|
||||||
)
|
)
|
||||||
def firewall(metadata):
|
def firewall(metadata):
|
||||||
return {
|
return {
|
||||||
'firewall': {
|
'firewall': {
|
||||||
'port_rules': {
|
'port_rules': {
|
||||||
# imap(s)
|
# imap(s)
|
||||||
'143/tcp': atomic(metadata.get('dovecot/restrict-to', {'*'})),
|
'143': atomic(metadata.get('dovecot/restrict-to', {'*'})),
|
||||||
'993/tcp': atomic(metadata.get('dovecot/restrict-to', {'*'})),
|
'993': atomic(metadata.get('dovecot/restrict-to', {'*'})),
|
||||||
# managesieve
|
# managesieve
|
||||||
'4190/tcp': atomic(metadata.get('dovecot/restrict-to', {'*'})),
|
'4190': atomic(metadata.get('dovecot/restrict-to', {'*'})),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ directories = {
|
||||||
|
|
||||||
git_deploy = {
|
git_deploy = {
|
||||||
'/opt/element-web': {
|
'/opt/element-web': {
|
||||||
'rev': node.metadata.get('element-web/version'),
|
'rev': node.metadata['element-web']['version'],
|
||||||
'repo': 'https://github.com/vector-im/element-web.git',
|
'repo': 'https://github.com/vector-im/element-web.git',
|
||||||
'triggers': {
|
'triggers': {
|
||||||
'action:element-web_yarn',
|
'action:element-web_yarn',
|
||||||
|
@ -18,22 +18,28 @@ git_deploy = {
|
||||||
|
|
||||||
files = {
|
files = {
|
||||||
'/opt/element-web/webapp/config.json': {
|
'/opt/element-web/webapp/config.json': {
|
||||||
'content': metadata_to_json(node.metadata.get('element-web/config')),
|
'content': metadata_to_json(node.metadata['element-web']['config']),
|
||||||
'needs': {
|
'needs': {
|
||||||
'action:element-web_yarn',
|
'action:element-web_yarn',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extra_install_cmds = []
|
||||||
|
if node.metadata.get('nodejs/version') >= 17:
|
||||||
|
# TODO verify this is still needed when upgrading to 1.12
|
||||||
|
extra_install_cmds.append('export NODE_OPTIONS=--openssl-legacy-provider')
|
||||||
|
|
||||||
actions = {
|
actions = {
|
||||||
'element-web_yarn': {
|
'element-web_yarn': {
|
||||||
'command': ' && '.join([
|
'command': ' && '.join([
|
||||||
|
*extra_install_cmds,
|
||||||
'cd /opt/element-web',
|
'cd /opt/element-web',
|
||||||
'yarn install --pure-lockfile --ignore-scripts',
|
'yarn install --pure-lockfile --ignore-scripts',
|
||||||
'yarn build',
|
'yarn build',
|
||||||
]),
|
]),
|
||||||
'needs': {
|
'needs': {
|
||||||
'action:apt_execute_update_commands',
|
'action:nodejs_install_yarn',
|
||||||
'pkg_apt:nodejs',
|
'pkg_apt:nodejs',
|
||||||
},
|
},
|
||||||
'triggered': True,
|
'triggered': True,
|
||||||
|
|
|
@ -11,26 +11,6 @@ defaults = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@metadata_reactor.provides(
|
|
||||||
'nodejs/version',
|
|
||||||
)
|
|
||||||
def nodejs(metadata):
|
|
||||||
version = tuple([int(i) for i in metadata.get('element-web/version')[1:].split('.')])
|
|
||||||
|
|
||||||
if version >= (1, 11, 71):
|
|
||||||
return {
|
|
||||||
'nodejs': {
|
|
||||||
'version': 20,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
return {
|
|
||||||
'nodejs': {
|
|
||||||
'version': 18,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@metadata_reactor.provides(
|
@metadata_reactor.provides(
|
||||||
'nginx/vhosts/element-web',
|
'nginx/vhosts/element-web',
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,65 +0,0 @@
|
||||||
users = {
|
|
||||||
'git': {
|
|
||||||
'home': '/var/lib/forgejo',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
directories = {
|
|
||||||
'/var/lib/forgejo/.ssh': {
|
|
||||||
'mode': '0700',
|
|
||||||
'owner': 'git',
|
|
||||||
'group': 'git',
|
|
||||||
},
|
|
||||||
'/var/lib/forgejo': {
|
|
||||||
'owner': 'git',
|
|
||||||
'mode': '0700',
|
|
||||||
'triggers': {
|
|
||||||
'svc_systemd:forgejo:restart',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
files = {
|
|
||||||
'/usr/local/lib/systemd/system/forgejo.service': {
|
|
||||||
'content_type': 'mako',
|
|
||||||
'context': node.metadata.get('forgejo'),
|
|
||||||
'triggers': {
|
|
||||||
'action:systemd-reload',
|
|
||||||
'svc_systemd:forgejo:restart',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'/etc/forgejo/app.ini': {
|
|
||||||
'content_type': 'mako',
|
|
||||||
'context': node.metadata.get('forgejo'),
|
|
||||||
'triggers': {
|
|
||||||
'svc_systemd:forgejo:restart',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'/usr/local/bin/forgejo': {
|
|
||||||
'content_type': 'download',
|
|
||||||
'source': 'https://codeberg.org/forgejo/forgejo/releases/download/v{0}/forgejo-{0}-linux-amd64'.format(node.metadata.get('forgejo/version')),
|
|
||||||
'content_hash': node.metadata.get('forgejo/sha1', None),
|
|
||||||
'mode': '0755',
|
|
||||||
'triggers': {
|
|
||||||
'svc_systemd:forgejo:restart',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if node.metadata.get('forgejo/install_ssh_key', False):
|
|
||||||
files['/var/lib/forgejo/.ssh/id_ed25519'] = {
|
|
||||||
'content': repo.vault.decrypt_file(f'forgejo/files/ssh-keys/{node.name}.key.vault'),
|
|
||||||
'mode': '0600',
|
|
||||||
'owner': 'git',
|
|
||||||
'group': 'git',
|
|
||||||
}
|
|
||||||
|
|
||||||
svc_systemd = {
|
|
||||||
'forgejo': {
|
|
||||||
'needs': {
|
|
||||||
'file:/etc/forgejo/app.ini',
|
|
||||||
'file:/usr/local/bin/forgejo',
|
|
||||||
'file:/usr/local/lib/systemd/system/forgejo.service',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
33
bundles/gce-workaround/items.py
Normal file
33
bundles/gce-workaround/items.py
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
svc_systemd = {}
|
||||||
|
pkg_apt = {}
|
||||||
|
|
||||||
|
for i in {
|
||||||
|
'gce-disk-expand',
|
||||||
|
'google-cloud-packages-archive-keyring',
|
||||||
|
'google-cloud-sdk',
|
||||||
|
'google-compute-engine',
|
||||||
|
'google-compute-engine-oslogin',
|
||||||
|
'google-guest-agent',
|
||||||
|
'google-osconfig-agent',
|
||||||
|
}:
|
||||||
|
pkg_apt[i] = {
|
||||||
|
'installed': False,
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in {
|
||||||
|
'google-accounts-daemon.service',
|
||||||
|
'google-accounts-manager.service',
|
||||||
|
'google-clock-skew-daemon.service',
|
||||||
|
'google-clock-sync-manager.service',
|
||||||
|
'google-guest-agent.service',
|
||||||
|
'google-osconfig-agent.service',
|
||||||
|
'google-shutdown-scripts.service',
|
||||||
|
'google-startup-scripts.service',
|
||||||
|
'sshguard.service',
|
||||||
|
|
||||||
|
'google-oslogin-cache.timer',
|
||||||
|
}:
|
||||||
|
svc_systemd[i] = {
|
||||||
|
'enabled': False,
|
||||||
|
'running': False,
|
||||||
|
}
|
|
@ -1,10 +1,9 @@
|
||||||
APP_NAME = ${app_name}
|
APP_NAME = ${app_name}
|
||||||
RUN_USER = git
|
RUN_USER = git
|
||||||
RUN_MODE = prod
|
RUN_MODE = prod
|
||||||
WORK_PATH = /var/lib/forgejo
|
|
||||||
|
|
||||||
[repository]
|
[repository]
|
||||||
ROOT = /var/lib/forgejo/repositories
|
ROOT = /home/git/gitea-repositories
|
||||||
MAX_CREATION_LIMIT = 0
|
MAX_CREATION_LIMIT = 0
|
||||||
DEFAULT_BRANCH = main
|
DEFAULT_BRANCH = main
|
||||||
|
|
|
@ -5,13 +5,14 @@ After=network.target
|
||||||
Requires=postgresql.service
|
Requires=postgresql.service
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
RestartSec=10
|
RestartSec=2s
|
||||||
Type=simple
|
Type=simple
|
||||||
User=git
|
User=git
|
||||||
Group=git
|
Group=git
|
||||||
WorkingDirectory=/var/lib/forgejo
|
WorkingDirectory=/var/lib/gitea/
|
||||||
ExecStart=/usr/local/bin/forgejo web -c /etc/forgejo/app.ini
|
ExecStart=/usr/local/bin/gitea web -c /etc/gitea/app.ini
|
||||||
Restart=always
|
Restart=always
|
||||||
|
Environment=USER=git HOME=/home/git GITEA_WORK_DIR=/var/lib/gitea
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
68
bundles/gitea/items.py
Normal file
68
bundles/gitea/items.py
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
users = {
|
||||||
|
'git': {},
|
||||||
|
}
|
||||||
|
|
||||||
|
directories = {
|
||||||
|
'/home/git': {
|
||||||
|
'mode': '0755',
|
||||||
|
'owner': 'git',
|
||||||
|
'group': 'git',
|
||||||
|
},
|
||||||
|
'/home/git/.ssh': {
|
||||||
|
'mode': '0755',
|
||||||
|
'owner': 'git',
|
||||||
|
'group': 'git',
|
||||||
|
},
|
||||||
|
'/var/lib/gitea': {
|
||||||
|
'owner': 'git',
|
||||||
|
'mode': '0700',
|
||||||
|
'triggers': {
|
||||||
|
'svc_systemd:gitea:restart',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
files = {
|
||||||
|
'/etc/systemd/system/gitea.service': {
|
||||||
|
'content_type': 'mako',
|
||||||
|
'context': node.metadata.get('gitea'),
|
||||||
|
'triggers': {
|
||||||
|
'action:systemd-reload',
|
||||||
|
'svc_systemd:gitea:restart',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'/etc/gitea/app.ini': {
|
||||||
|
'content_type': 'mako',
|
||||||
|
'context': node.metadata.get('gitea'),
|
||||||
|
'triggers': {
|
||||||
|
'svc_systemd:gitea:restart',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'/usr/local/bin/gitea': {
|
||||||
|
'content_type': 'download',
|
||||||
|
'source': node.metadata.get('gitea/url'),
|
||||||
|
'content_hash': node.metadata.get('gitea/sha1', None),
|
||||||
|
'mode': '0755',
|
||||||
|
'triggers': {
|
||||||
|
'svc_systemd:gitea:restart',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if node.metadata['gitea'].get('install_ssh_key', False):
|
||||||
|
files['/home/git/.ssh/id_ed25519'] = {
|
||||||
|
'content': repo.vault.decrypt_file(f'gitea/files/ssh-keys/{node.name}.key.vault'),
|
||||||
|
'mode': '0600',
|
||||||
|
'owner': 'git',
|
||||||
|
'group': 'git',
|
||||||
|
}
|
||||||
|
|
||||||
|
svc_systemd = {
|
||||||
|
'gitea': {
|
||||||
|
'needs': {
|
||||||
|
'file:/etc/gitea/app.ini',
|
||||||
|
'file:/etc/systemd/system/gitea.service',
|
||||||
|
'file:/usr/local/bin/gitea',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
|
@ -1,31 +1,33 @@
|
||||||
defaults = {
|
defaults = {
|
||||||
'backups': {
|
'backups': {
|
||||||
'paths': {
|
'paths': {
|
||||||
'/var/lib/forgejo',
|
'/home/git',
|
||||||
|
'/var/lib/gitea',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'forgejo': {
|
'gitea': {
|
||||||
'app_name': 'Forgejo',
|
'app_name': 'Forgejo',
|
||||||
'database': {
|
'database': {
|
||||||
'username': 'forgejo',
|
'username': 'gitea',
|
||||||
'password': repo.vault.password_for('{} postgresql forgejo'.format(node.name)),
|
'password': repo.vault.password_for('{} postgresql gitea'.format(node.name)),
|
||||||
'database': 'forgejo',
|
'database': 'gitea',
|
||||||
},
|
},
|
||||||
'disable_registration': True,
|
'disable_registration': True,
|
||||||
'email_domain_blocklist': set(),
|
'email_domain_blocklist': set(),
|
||||||
'enable_git_hooks': False,
|
'enable_git_hooks': False,
|
||||||
'internal_token': repo.vault.password_for('{} forgejo internal_token'.format(node.name)),
|
'internal_token': repo.vault.password_for('{} gitea internal_token'.format(node.name)),
|
||||||
'lfs_secret_key': repo.vault.password_for('{} forgejo lfs_secret_key'.format(node.name)),
|
'lfs_secret_key': repo.vault.password_for('{} gitea lfs_secret_key'.format(node.name)),
|
||||||
'oauth_secret_key': repo.vault.password_for('{} forgejo oauth_secret_key'.format(node.name)),
|
'oauth_secret_key': repo.vault.password_for('{} gitea oauth_secret_key'.format(node.name)),
|
||||||
'security_secret_key': repo.vault.password_for('{} forgejo security_secret_key'.format(node.name)),
|
'security_secret_key': repo.vault.password_for('{} gitea security_secret_key'.format(node.name)),
|
||||||
},
|
},
|
||||||
'icinga2_api': {
|
'icinga2_api': {
|
||||||
'forgejo': {
|
'gitea': {
|
||||||
'services': {
|
'services': {
|
||||||
'FORGEJO PROCESS': {
|
'FORGEJO PROCESS': {
|
||||||
'command_on_monitored_host': '/usr/local/share/icinga/plugins/check_systemd_unit forgejo',
|
'command_on_monitored_host': '/usr/local/share/icinga/plugins/check_systemd_unit gitea',
|
||||||
},
|
},
|
||||||
'FORGEJO UPDATE': {
|
'FORGEJO UPDATE': {
|
||||||
|
'command_on_monitored_host': '/usr/local/share/icinga/plugins/check_forgejo_for_new_release codeberg.org forgejo/forgejo v$(gitea --version | cut -d" " -f3)',
|
||||||
'vars.notification.mail': True,
|
'vars.notification.mail': True,
|
||||||
'check_interval': '60m',
|
'check_interval': '60m',
|
||||||
},
|
},
|
||||||
|
@ -39,22 +41,29 @@ defaults = {
|
||||||
},
|
},
|
||||||
'postgresql': {
|
'postgresql': {
|
||||||
'roles': {
|
'roles': {
|
||||||
'forgejo': {
|
'gitea': {
|
||||||
'password': repo.vault.password_for('{} postgresql forgejo'.format(node.name)),
|
'password': repo.vault.password_for('{} postgresql gitea'.format(node.name)),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'databases': {
|
'databases': {
|
||||||
'forgejo': {
|
'gitea': {
|
||||||
'owner': 'forgejo',
|
'owner': 'gitea',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'zfs': {
|
'zfs': {
|
||||||
'datasets': {
|
'datasets': {
|
||||||
'tank/forgejo': {
|
'tank/gitea': {},
|
||||||
'mountpoint': '/var/lib/forgejo',
|
'tank/gitea/home': {
|
||||||
|
'mountpoint': '/home/git',
|
||||||
'needed_by': {
|
'needed_by': {
|
||||||
'directory:/var/lib/forgejo',
|
'directory:/home/git',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'tank/gitea/var': {
|
||||||
|
'mountpoint': '/var/lib/gitea',
|
||||||
|
'needed_by': {
|
||||||
|
'directory:/var/lib/gitea',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -62,23 +71,6 @@ defaults = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@metadata_reactor.provides(
|
|
||||||
'icinga2_api/forgejo',
|
|
||||||
)
|
|
||||||
def update_monitoring(metadata):
|
|
||||||
return {
|
|
||||||
'icinga2_api': {
|
|
||||||
'forgejo': {
|
|
||||||
'services': {
|
|
||||||
'FORGEJO UPDATE': {
|
|
||||||
'command_on_monitored_host': '/usr/local/share/icinga/plugins/check_forgejo_for_new_release codeberg.org forgejo/forgejo v{}'.format(metadata.get('forgejo/version')),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@metadata_reactor.provides(
|
@metadata_reactor.provides(
|
||||||
'nginx/vhosts/forgejo',
|
'nginx/vhosts/forgejo',
|
||||||
)
|
)
|
||||||
|
@ -90,7 +82,7 @@ def nginx(metadata):
|
||||||
'nginx': {
|
'nginx': {
|
||||||
'vhosts': {
|
'vhosts': {
|
||||||
'forgejo': {
|
'forgejo': {
|
||||||
'domain': metadata.get('forgejo/domain'),
|
'domain': metadata.get('gitea/domain'),
|
||||||
'locations': {
|
'locations': {
|
||||||
'/': {
|
'/': {
|
||||||
'target': 'http://127.0.0.1:22000',
|
'target': 'http://127.0.0.1:22000',
|
||||||
|
@ -100,8 +92,16 @@ def nginx(metadata):
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'website_check_path': '/user/login',
|
'website_check_path': '/user/login',
|
||||||
'website_check_string': 'Sign in',
|
'website_check_string': 'Sign In',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@metadata_reactor.provides(
|
||||||
|
'icinga2_api/gitea/services',
|
||||||
|
)
|
||||||
|
def icinga_check_for_new_release(metadata):
|
||||||
|
return {
|
||||||
|
}
|
|
@ -47,7 +47,7 @@ def dashboard_row_smartd(panel_id, node):
|
||||||
'renderer': 'flot',
|
'renderer': 'flot',
|
||||||
'seriesOverrides': [],
|
'seriesOverrides': [],
|
||||||
'spaceLength': 10,
|
'spaceLength': 10,
|
||||||
'span': 12,
|
'span': 8,
|
||||||
'stack': False,
|
'stack': False,
|
||||||
'steppedLine': False,
|
'steppedLine': False,
|
||||||
'targets': [
|
'targets': [
|
||||||
|
@ -114,5 +114,115 @@ def dashboard_row_smartd(panel_id, node):
|
||||||
'alignLevel': None
|
'alignLevel': None
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'aliasColors': {},
|
||||||
|
'bars': False,
|
||||||
|
'dashLength': 10,
|
||||||
|
'dashes': False,
|
||||||
|
'datasource': None,
|
||||||
|
'fieldConfig': {
|
||||||
|
'defaults': {
|
||||||
|
'displayName': '${__field.labels.device}'
|
||||||
|
},
|
||||||
|
'overrides': []
|
||||||
|
},
|
||||||
|
'fill': 0,
|
||||||
|
'fillGradient': 0,
|
||||||
|
'hiddenSeries': False,
|
||||||
|
'id': next(panel_id),
|
||||||
|
'legend': {
|
||||||
|
'alignAsTable': False,
|
||||||
|
'avg': False,
|
||||||
|
'current': False,
|
||||||
|
'hideEmpty': True,
|
||||||
|
'hideZero': True,
|
||||||
|
'max': False,
|
||||||
|
'min': False,
|
||||||
|
'rightSide': False,
|
||||||
|
'show': True,
|
||||||
|
'total': False,
|
||||||
|
'values': False
|
||||||
|
},
|
||||||
|
'lines': True,
|
||||||
|
'linewidth': 1,
|
||||||
|
'NonePointMode': 'None',
|
||||||
|
'options': {
|
||||||
|
'alertThreshold': True
|
||||||
|
},
|
||||||
|
'percentage': False,
|
||||||
|
'pluginVersion': '7.5.5',
|
||||||
|
'pointradius': 2,
|
||||||
|
'points': False,
|
||||||
|
'renderer': 'flot',
|
||||||
|
'seriesOverrides': [],
|
||||||
|
'spaceLength': 10,
|
||||||
|
'span': 4,
|
||||||
|
'stack': False,
|
||||||
|
'steppedLine': False,
|
||||||
|
'targets': [
|
||||||
|
{
|
||||||
|
'groupBy': [
|
||||||
|
{'type': 'time', 'params': ['$__interval']},
|
||||||
|
{'type': 'fill', 'params': ['linear']},
|
||||||
|
],
|
||||||
|
'orderByTime': "ASC",
|
||||||
|
'policy': "default",
|
||||||
|
'query': f"""from(bucket: "telegraf")
|
||||||
|
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|
||||||
|
|> filter(fn: (r) =>
|
||||||
|
r["_measurement"] == "smartd_stats" and
|
||||||
|
r["_field"] == "power_on_hours" and
|
||||||
|
r["host"] == "{node.name}"
|
||||||
|
)
|
||||||
|
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||||
|
|> yield(name: "fan")""",
|
||||||
|
'resultFormat': 'time_series',
|
||||||
|
'select': [[
|
||||||
|
{'type': 'field', 'params': ['value']},
|
||||||
|
{'type': 'mean', 'params': []},
|
||||||
|
]],
|
||||||
|
"tags": []
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'thresholds': [],
|
||||||
|
'timeRegions': [],
|
||||||
|
'title': 'fans',
|
||||||
|
'tooltip': {
|
||||||
|
'shared': True,
|
||||||
|
'sort': 0,
|
||||||
|
'value_type': 'individual'
|
||||||
|
},
|
||||||
|
'type': 'graph',
|
||||||
|
'xaxis': {
|
||||||
|
'buckets': None,
|
||||||
|
'mode': 'time',
|
||||||
|
'name': None,
|
||||||
|
'show': True,
|
||||||
|
'values': []
|
||||||
|
},
|
||||||
|
'yaxes': [
|
||||||
|
{
|
||||||
|
'format': 'hours',
|
||||||
|
'label': None,
|
||||||
|
'logBase': 1,
|
||||||
|
'max': None,
|
||||||
|
'min': None,
|
||||||
|
'show': True,
|
||||||
|
'decimals': 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'format': 'short',
|
||||||
|
'label': None,
|
||||||
|
'logBase': 1,
|
||||||
|
'max': None,
|
||||||
|
'min': None,
|
||||||
|
'show': False,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'yaxis': {
|
||||||
|
'align': False,
|
||||||
|
'alignLevel': None
|
||||||
|
}
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,6 @@ def nginx(metadata):
|
||||||
'locations': {
|
'locations': {
|
||||||
'/': {
|
'/': {
|
||||||
'target': 'http://127.0.0.1:21010',
|
'target': 'http://127.0.0.1:21010',
|
||||||
'websockets': True,
|
|
||||||
},
|
},
|
||||||
'/api/ds/query': {
|
'/api/ds/query': {
|
||||||
'target': 'http://127.0.0.1:21010',
|
'target': 'http://127.0.0.1:21010',
|
||||||
|
|
|
@ -33,11 +33,7 @@ ProtectSystem=strict
|
||||||
ProtectHome=true
|
ProtectHome=true
|
||||||
PrivateTmp=true
|
PrivateTmp=true
|
||||||
SystemCallArchitectures=native
|
SystemCallArchitectures=native
|
||||||
# FIXME
|
SystemCallFilter=@system-service
|
||||||
# causes problems on bookworm
|
|
||||||
# see https://github.com/hedgedoc/hedgedoc/issues/4686
|
|
||||||
# cmmented out for now ...
|
|
||||||
#SystemCallFilter=@system-service
|
|
||||||
|
|
||||||
# You may have to adjust these settings
|
# You may have to adjust these settings
|
||||||
User=hedgedoc
|
User=hedgedoc
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
from semver import compare
|
|
||||||
|
|
||||||
repo.libs.tools.require_bundle(node, 'nodejs')
|
repo.libs.tools.require_bundle(node, 'nodejs')
|
||||||
|
|
||||||
git_deploy = {
|
git_deploy = {
|
||||||
|
@ -49,29 +47,16 @@ directories = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if compare(node.metadata.get('hedgedoc/version'), '1.9.7') <= 0:
|
|
||||||
command = ' && '.join([
|
|
||||||
'cd /opt/hedgedoc',
|
|
||||||
'yarn workspaces focus --production',
|
|
||||||
'yarn install --ignore-scripts',
|
|
||||||
'yarn build',
|
|
||||||
])
|
|
||||||
elif compare(node.metadata.get('hedgedoc/version'), '1.9.9') >= 0:
|
|
||||||
command = ' && '.join([
|
|
||||||
'cd /opt/hedgedoc',
|
|
||||||
'bin/setup',
|
|
||||||
'yarn install --immutable',
|
|
||||||
'yarn build',
|
|
||||||
])
|
|
||||||
|
|
||||||
actions = {
|
actions = {
|
||||||
'hedgedoc_yarn': {
|
'hedgedoc_yarn': {
|
||||||
'command': ' && '.join([
|
'command': ' && '.join([
|
||||||
'cd /opt/hedgedoc',
|
'cd /opt/hedgedoc',
|
||||||
'yarn install --immutable',
|
'yarn install --production=true --pure-lockfile --ignore-scripts',
|
||||||
|
'yarn install --ignore-scripts',
|
||||||
'yarn build',
|
'yarn build',
|
||||||
]),
|
]),
|
||||||
'needs': {
|
'needs': {
|
||||||
|
'action:nodejs_install_yarn',
|
||||||
'file:/opt/hedgedoc/config.json',
|
'file:/opt/hedgedoc/config.json',
|
||||||
'git_deploy:/opt/hedgedoc',
|
'git_deploy:/opt/hedgedoc',
|
||||||
'pkg_apt:nodejs',
|
'pkg_apt:nodejs',
|
||||||
|
|
|
@ -2,42 +2,48 @@
|
||||||
|
|
||||||
from sys import exit
|
from sys import exit
|
||||||
|
|
||||||
from packaging.version import parse
|
import requests
|
||||||
from requests import get
|
from packaging import version
|
||||||
|
|
||||||
API_TOKEN = "${token}"
|
bearer = "${bearer}"
|
||||||
DOMAIN = "${domain}"
|
domain = "${domain}"
|
||||||
|
OK = 0
|
||||||
|
WARN = 1
|
||||||
|
CRITICAL = 2
|
||||||
|
UNKNOWN = 3
|
||||||
|
|
||||||
|
status = 3
|
||||||
|
message = "Unknown Update Status"
|
||||||
|
|
||||||
|
|
||||||
|
domain = "hass.home.kunbox.net"
|
||||||
|
|
||||||
|
s = requests.Session()
|
||||||
|
s.headers.update({"Content-Type": "application/json"})
|
||||||
|
|
||||||
try:
|
try:
|
||||||
r = get("https://version.home-assistant.io/stable.json")
|
stable_version = version.parse(
|
||||||
r.raise_for_status()
|
s.get("https://version.home-assistant.io/stable.json").json()["homeassistant"][
|
||||||
stable_version = parse(r.json()["homeassistant"]["generic-x86-64"])
|
"generic-x86-64"
|
||||||
except Exception as e:
|
]
|
||||||
print(f"Could not get stable version information from home-assistant.io: {e!r}")
|
|
||||||
exit(3)
|
|
||||||
|
|
||||||
try:
|
|
||||||
r = get(
|
|
||||||
f"https://{DOMAIN}/api/config",
|
|
||||||
headers={"Authorization": f"Bearer {API_TOKEN}", "Content-Type": "application/json"},
|
|
||||||
)
|
)
|
||||||
r.raise_for_status()
|
s.headers.update(
|
||||||
running_version = parse(r.json()["version"])
|
{"Authorization": f"Bearer {bearer}", "Content-Type": "application/json"}
|
||||||
except Exception as e:
|
|
||||||
print(f"Could not get running version information from homeassistant: {e!r}")
|
|
||||||
exit(3)
|
|
||||||
|
|
||||||
try:
|
|
||||||
if stable_version > running_version:
|
|
||||||
print(
|
|
||||||
f"There is a newer version available: {stable_version} (currently installed: {running_version})"
|
|
||||||
)
|
)
|
||||||
exit(2)
|
running_version = version.parse(
|
||||||
|
s.get(f"https://{domain}/api/config").json()["version"]
|
||||||
|
)
|
||||||
|
if running_version == stable_version:
|
||||||
|
status = 0
|
||||||
|
message = f"OK - running version {running_version} equals stable version {stable_version}"
|
||||||
|
elif running_version > stable_version:
|
||||||
|
status = 1
|
||||||
|
message = f"WARNING - stable version {stable_version} is lower than running version {running_version}, check if downgrade is necessary."
|
||||||
else:
|
else:
|
||||||
print(
|
status = 2
|
||||||
f"Currently running version {running_version} matches newest release on home-assistant.io"
|
message = f"CRITICAL - update necessary, running version {running_version} is lower than stable version {stable_version}"
|
||||||
)
|
|
||||||
exit(0)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(repr(e))
|
message = f"{message}: {repr(e)}"
|
||||||
exit(3)
|
|
||||||
|
print(message)
|
||||||
|
exit(status)
|
||||||
|
|
|
@ -5,13 +5,9 @@ After=network-online.target
|
||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
User=homeassistant
|
User=homeassistant
|
||||||
Environment="VIRTUAL_ENV=/opt/homeassistant/venv"
|
|
||||||
Environment="PATH=/opt/homeassistant/venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
|
||||||
WorkingDirectory=/var/opt/homeassistant
|
WorkingDirectory=/var/opt/homeassistant
|
||||||
ExecStart=/opt/homeassistant/venv/bin/hass -c "/var/opt/homeassistant"
|
ExecStart=/opt/homeassistant/venv/bin/hass -c "/var/opt/homeassistant"
|
||||||
RestartForceExitStatus=100
|
RestartForceExitStatus=100
|
||||||
Restart=on-failure
|
|
||||||
RestartSec=2
|
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
|
|
@ -1,13 +1,6 @@
|
||||||
if node.has_bundle('pyenv'):
|
|
||||||
python_version = sorted(node.metadata.get('pyenv/python_versions'))[-1]
|
|
||||||
python_path = f'/opt/pyenv/versions/{python_version}/bin/python'
|
|
||||||
else:
|
|
||||||
python_path = '/usr/bin/python3'
|
|
||||||
|
|
||||||
users = {
|
users = {
|
||||||
'homeassistant': {
|
'homeassistant': {
|
||||||
'home': '/var/opt/homeassistant',
|
'home': '/var/opt/homeassistant',
|
||||||
"groups": ["dialout"],
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +23,7 @@ files = {
|
||||||
'/usr/local/share/icinga/plugins/check_homeassistant_update': {
|
'/usr/local/share/icinga/plugins/check_homeassistant_update': {
|
||||||
'content_type': 'mako',
|
'content_type': 'mako',
|
||||||
'context': {
|
'context': {
|
||||||
'token': node.metadata.get('homeassistant/api_secret'),
|
'bearer': repo.vault.decrypt(node.metadata.get('homeassistant/api_secret')),
|
||||||
'domain': node.metadata.get('homeassistant/domain'),
|
'domain': node.metadata.get('homeassistant/domain'),
|
||||||
},
|
},
|
||||||
'mode': '0755',
|
'mode': '0755',
|
||||||
|
@ -39,18 +32,11 @@ files = {
|
||||||
|
|
||||||
actions = {
|
actions = {
|
||||||
'homeassistant_create_virtualenv': {
|
'homeassistant_create_virtualenv': {
|
||||||
'command': f'sudo -u homeassistant virtualenv -p {python_path} /opt/homeassistant/venv/',
|
'command': 'sudo -u homeassistant /usr/bin/python3 -m virtualenv -p python3 /opt/homeassistant/venv/',
|
||||||
'unless': 'test -d /opt/homeassistant/venv/',
|
'unless': 'test -d /opt/homeassistant/venv/',
|
||||||
'needs': {
|
'needs': {
|
||||||
'directory:/opt/homeassistant',
|
'directory:/opt/homeassistant',
|
||||||
'user:homeassistant',
|
'user:homeassistant',
|
||||||
},
|
|
||||||
},
|
|
||||||
'homeassistant_install': {
|
|
||||||
'command': 'sudo -u homeassistant /opt/homeassistant/venv/bin/pip install homeassistant',
|
|
||||||
'unless': 'test -f /opt/homeassistant/venv/bin/hass',
|
|
||||||
'needs': {
|
|
||||||
'action:homeassistant_create_virtualenv',
|
|
||||||
'pkg_apt:bluez',
|
'pkg_apt:bluez',
|
||||||
'pkg_apt:libffi-dev',
|
'pkg_apt:libffi-dev',
|
||||||
'pkg_apt:libssl-dev',
|
'pkg_apt:libssl-dev',
|
||||||
|
@ -59,10 +45,17 @@ actions = {
|
||||||
'pkg_apt:autoconf',
|
'pkg_apt:autoconf',
|
||||||
'pkg_apt:build-essential',
|
'pkg_apt:build-essential',
|
||||||
'pkg_apt:libopenjp2-7',
|
'pkg_apt:libopenjp2-7',
|
||||||
'pkg_apt:libtiff6',
|
'pkg_apt:libtiff5',
|
||||||
'pkg_apt:libturbojpeg0-dev',
|
'pkg_apt:libturbojpeg0-dev',
|
||||||
'pkg_apt:tzdata',
|
'pkg_apt:tzdata',
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
'homeassistant_install': {
|
||||||
|
'command': 'sudo -u homeassistant /opt/homeassistant/venv/bin/pip install homeassistant',
|
||||||
|
'unless': 'test -f /opt/homeassistant/venv/bin/hass',
|
||||||
|
'needs': {
|
||||||
|
'action:homeassistant_create_virtualenv',
|
||||||
|
},
|
||||||
'triggers': {
|
'triggers': {
|
||||||
'svc_systemd:homeassistant:restart',
|
'svc_systemd:homeassistant:restart',
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,12 +4,11 @@ defaults = {
|
||||||
'autoconf': {},
|
'autoconf': {},
|
||||||
'bluez': {},
|
'bluez': {},
|
||||||
'build-essential': {},
|
'build-essential': {},
|
||||||
'ffmpeg': {},
|
|
||||||
'libffi-dev': {},
|
'libffi-dev': {},
|
||||||
'libjpeg-dev': {},
|
'libjpeg-dev': {},
|
||||||
'libopenjp2-7': {},
|
'libopenjp2-7': {},
|
||||||
'libssl-dev': {},
|
'libssl-dev': {},
|
||||||
'libtiff6': {},
|
'libtiff5': {},
|
||||||
'libturbojpeg0-dev': {},
|
'libturbojpeg0-dev': {},
|
||||||
'python3-packaging': {},
|
'python3-packaging': {},
|
||||||
'tzdata': {},
|
'tzdata': {},
|
||||||
|
@ -23,8 +22,6 @@ defaults = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@metadata_reactor.provides(
|
@metadata_reactor.provides(
|
||||||
'icinga2_api/homeassistant/services',
|
'icinga2_api/homeassistant/services',
|
||||||
)
|
)
|
||||||
|
@ -34,17 +31,15 @@ def icinga_check_for_new_release(metadata):
|
||||||
'homeassistant': {
|
'homeassistant': {
|
||||||
'services': {
|
'services': {
|
||||||
'HOMEASSISTANT UPDATE': {
|
'HOMEASSISTANT UPDATE': {
|
||||||
'check_interval': '60m',
|
|
||||||
'command_on_monitored_host': '/usr/local/share/icinga/plugins/check_homeassistant_update',
|
'command_on_monitored_host': '/usr/local/share/icinga/plugins/check_homeassistant_update',
|
||||||
'vars.notification.mail': True,
|
'vars.notification.mail': True,
|
||||||
'vars.sshmon_timeout': 20,
|
'check_interval': '60m',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@metadata_reactor.provides(
|
@metadata_reactor.provides(
|
||||||
'nginx/vhosts/homeassistant',
|
'nginx/vhosts/homeassistant',
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
[Unit]
|
|
||||||
Description=icinga2-statuspage
|
|
||||||
After=network.target
|
|
||||||
Requires=postgresql.service
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
User=www-data
|
|
||||||
Group=www-data
|
|
||||||
Environment=APP_CONFIG=/opt/icinga2-statuspage/config.json
|
|
||||||
WorkingDirectory=/opt/icinga2-statuspage/src
|
|
||||||
ExecStart=/usr/bin/gunicorn statuspage:app --workers 4 --max-requests 1200 --max-requests-jitter 50 --log-level=info --bind=127.0.0.1:22110
|
|
||||||
Restart=always
|
|
||||||
RestartSec=10
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
|
@ -1,34 +0,0 @@
|
||||||
directories['/opt/icinga2-statuspage/src'] = {}
|
|
||||||
|
|
||||||
git_deploy['/opt/icinga2-statuspage/src'] = {
|
|
||||||
'repo': 'https://git.franzi.business/kunsi/icinga-dynamic-statuspage.git',
|
|
||||||
'rev': 'main',
|
|
||||||
'triggers': {
|
|
||||||
'svc_systemd:icinga2-statuspage:restart',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
files['/opt/icinga2-statuspage/config.json'] = {
|
|
||||||
'content': repo.libs.faults.dict_as_json(node.metadata.get('icinga2-statuspage')),
|
|
||||||
'triggers': {
|
|
||||||
'svc_systemd:icinga2-statuspage:restart',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
files['/usr/local/lib/systemd/system/icinga2-statuspage.service'] = {
|
|
||||||
'triggers': {
|
|
||||||
'action:systemd-reload',
|
|
||||||
'svc_systemd:icinga2-statuspage:restart',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
svc_systemd['icinga2-statuspage'] = {
|
|
||||||
'needs': {
|
|
||||||
'file:/opt/icinga2-statuspage/config.json',
|
|
||||||
'git_deploy:/opt/icinga2-statuspage/src',
|
|
||||||
'pkg_apt:gunicorn',
|
|
||||||
'pkg_apt:python3-flask',
|
|
||||||
'pkg_apt:python3-psycopg2',
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
defaults = {
|
|
||||||
'apt': {
|
|
||||||
'packages': {
|
|
||||||
'gunicorn': {},
|
|
||||||
'python3-flask': {},
|
|
||||||
'python3-psycopg2': {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@metadata_reactor.provides(
|
|
||||||
'icinga2-statuspage',
|
|
||||||
)
|
|
||||||
def import_db_settings_from_icinga(metadata):
|
|
||||||
return {
|
|
||||||
'icinga2-statuspage': {
|
|
||||||
'DB_USER': 'icinga2',
|
|
||||||
'DB_PASS': metadata.get('postgresql/roles/icinga2/password'),
|
|
||||||
'DB_NAME': 'icinga2',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@metadata_reactor.provides(
|
|
||||||
'nginx/vhosts/icinga2-statuspage',
|
|
||||||
)
|
|
||||||
def nginx(metadata):
|
|
||||||
if not node.has_bundle('nginx'):
|
|
||||||
raise DoNotRunAgain
|
|
||||||
|
|
||||||
return {
|
|
||||||
'nginx': {
|
|
||||||
'vhosts': {
|
|
||||||
'icinga2-statuspage': {
|
|
||||||
'domain': metadata.get('icinga2-statuspage/DOMAIN'),
|
|
||||||
'locations': {
|
|
||||||
'/': {
|
|
||||||
'target': 'http://127.0.0.1:22110',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'website_check_path': '/',
|
|
||||||
'website_check_string': 'status page',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -1,132 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import re
|
|
||||||
from hashlib import md5
|
|
||||||
from sys import argv, exit
|
|
||||||
|
|
||||||
# Supress SSL certificate warnings for ssl_verify=False
|
|
||||||
import urllib3
|
|
||||||
from lxml import html
|
|
||||||
from requests import Session
|
|
||||||
|
|
||||||
USERNAME_FIELD = "g2"
|
|
||||||
PASSWORD_FIELD = "g3"
|
|
||||||
CRSF_FIELD = "password"
|
|
||||||
|
|
||||||
STATUS_OK = 0
|
|
||||||
STATUS_WARNING = 1
|
|
||||||
STATUS_CRITICAL = 2
|
|
||||||
STATUS_UNKNOWN = 3
|
|
||||||
|
|
||||||
|
|
||||||
class OMMCrawler:
|
|
||||||
def __init__(self, hostname, username, password):
|
|
||||||
self.session = Session()
|
|
||||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
||||||
self.session.verify = False
|
|
||||||
|
|
||||||
self.url = f"https://{hostname}"
|
|
||||||
self.login_data = {
|
|
||||||
USERNAME_FIELD: username,
|
|
||||||
PASSWORD_FIELD: password,
|
|
||||||
CRSF_FIELD: md5(password.encode()).hexdigest(),
|
|
||||||
}
|
|
||||||
self.logged_in = False
|
|
||||||
|
|
||||||
def login(self):
|
|
||||||
# if we have multiple dect masters, find out which one is the current master
|
|
||||||
current_master_url = self.session.get(self.url, verify=False).url
|
|
||||||
self.hostname = re.search(r"^(.*[\\\/])", current_master_url).group(0)[:-1]
|
|
||||||
|
|
||||||
response = self.session.post(f"{self.url}/login_set.html", data=self.login_data)
|
|
||||||
response.raise_for_status()
|
|
||||||
|
|
||||||
# set cookie
|
|
||||||
pass_value = re.search(r"(?<=pass=)\d+(?=;)", response.text).group(0)
|
|
||||||
self.session.cookies.set("pass", pass_value)
|
|
||||||
self.logged_in = True
|
|
||||||
|
|
||||||
def get_station_status(self):
|
|
||||||
if not self.logged_in:
|
|
||||||
self.login()
|
|
||||||
|
|
||||||
data = {}
|
|
||||||
response = self.session.get(f"{self.url}/fp_pnp_status.html")
|
|
||||||
response.raise_for_status()
|
|
||||||
tree = html.fromstring(response.text)
|
|
||||||
xpath_results = tree.xpath('//tr[@class="l0" or @class="l1"]')
|
|
||||||
|
|
||||||
for result in xpath_results:
|
|
||||||
bubble_is_in_inactive_cluster = False
|
|
||||||
bubble_is_connected = False
|
|
||||||
bubble_is_active = False
|
|
||||||
|
|
||||||
bubble_name = result.xpath("td[4]/text()")[0]
|
|
||||||
try:
|
|
||||||
bubble_is_connected = result.xpath("td[11]/img/@alt")[0] == "yes"
|
|
||||||
|
|
||||||
if bubble_is_connected:
|
|
||||||
try:
|
|
||||||
bubble_is_active = result.xpath("td[12]/img/@alt")[0] == "yes"
|
|
||||||
except IndexError:
|
|
||||||
# If an IndexError occurs, there is no image in the
|
|
||||||
# 12th td. This means this bubble is in the not inside
|
|
||||||
# an active DECT cluster, but is a backup bubble.
|
|
||||||
# This is probably fine.
|
|
||||||
bubble_is_active = False
|
|
||||||
bubble_is_in_inactive_cluster = True
|
|
||||||
else:
|
|
||||||
bubble_is_active = False
|
|
||||||
except:
|
|
||||||
# There is no Image in the 11th td. This usually means there
|
|
||||||
# is a warning message in the 10th td. We do not care about
|
|
||||||
# that, currently.
|
|
||||||
pass
|
|
||||||
|
|
||||||
data[bubble_name] = {
|
|
||||||
"is_connected": bubble_is_connected,
|
|
||||||
"is_active": bubble_is_active,
|
|
||||||
"is_in_inactive_cluster": bubble_is_in_inactive_cluster,
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
|
|
||||||
def handle_station_data(self):
|
|
||||||
try:
|
|
||||||
data = self.get_station_status()
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Something went wrong. You should take a look at {self.url}")
|
|
||||||
print(repr(e))
|
|
||||||
exit(STATUS_UNKNOWN)
|
|
||||||
|
|
||||||
critical = False
|
|
||||||
for name, status in data.items():
|
|
||||||
if not status["is_active"] and not status["is_connected"]:
|
|
||||||
print(
|
|
||||||
f"Base station {name} is not active or connected! Check manually!"
|
|
||||||
)
|
|
||||||
critical = True
|
|
||||||
elif not status["is_active"] and not status["is_in_inactive_cluster"]:
|
|
||||||
# Bubble is part of an active DECT cluster, but not active.
|
|
||||||
# This shouldn't happen.
|
|
||||||
print(
|
|
||||||
f"Base station {name} is not active but connected! Check manually!"
|
|
||||||
)
|
|
||||||
critical = True
|
|
||||||
elif not status["is_connected"]:
|
|
||||||
# This should never happen. Seeing this state means OMM
|
|
||||||
# itself is broken.
|
|
||||||
print(
|
|
||||||
f"Base station {name} is not connected but active! Check manually!"
|
|
||||||
)
|
|
||||||
critical = True
|
|
||||||
|
|
||||||
if critical:
|
|
||||||
exit(STATUS_CRITICAL)
|
|
||||||
else:
|
|
||||||
print(f"OK - {len(data)} base stations connected")
|
|
||||||
exit(STATUS_OK)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
omm = OMMCrawler(argv[1], argv[2], argv[3])
|
|
||||||
omm.handle_station_data()
|
|
|
@ -1,17 +1,16 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
from json import load
|
|
||||||
from sys import exit
|
from sys import exit
|
||||||
|
|
||||||
from requests import get
|
from requests import get
|
||||||
|
|
||||||
with open('/etc/icinga2/notification_config.json') as f:
|
SIPGATE_USER = '${node.metadata['icinga2']['sipgate_user']}'
|
||||||
CONFIG = load(f)
|
SIPGATE_PASS = '${node.metadata['icinga2']['sipgate_pass']}'
|
||||||
|
|
||||||
try:
|
try:
|
||||||
r = get(
|
r = get(
|
||||||
'https://api.sipgate.com/v2/balance',
|
'https://api.sipgate.com/v2/balance',
|
||||||
auth=(CONFIG['sipgate']['user'], CONFIG['sipgate']['password']),
|
auth=(SIPGATE_USER, SIPGATE_PASS),
|
||||||
headers={'Accept': 'application/json'},
|
headers={'Accept': 'application/json'},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -5,33 +5,30 @@ from ipaddress import IPv6Address, ip_address
|
||||||
from subprocess import check_output
|
from subprocess import check_output
|
||||||
from sys import argv, exit
|
from sys import argv, exit
|
||||||
|
|
||||||
BLOCKLISTS = {
|
BLOCKLISTS = [
|
||||||
'0spam.fusionzero.com': set(),
|
'0spam.fusionzero.com',
|
||||||
'bl.mailspike.org': set(),
|
'bl.mailspike.org',
|
||||||
'bl.spamcop.net': set(),
|
'bl.spamcop.net',
|
||||||
'blackholes.brainerd.net': set(),
|
'blackholes.brainerd.net',
|
||||||
'dnsbl-1.uceprotect.net': set(),
|
'dnsbl-1.uceprotect.net',
|
||||||
'l2.spews.dnsbl.sorbs.net': set(),
|
'dnsbl-2.uceprotect.net',
|
||||||
'list.dsbl.org': set(),
|
'l2.spews.dnsbl.sorbs.net',
|
||||||
'multihop.dsbl.org': set(),
|
'list.dsbl.org',
|
||||||
'ns1.unsubscore.com': set(),
|
'map.spam-rbl.com',
|
||||||
'opm.blitzed.org': set(),
|
'multihop.dsbl.org',
|
||||||
'psbl.surriel.com': set(),
|
'ns1.unsubscore.com',
|
||||||
'rbl.efnet.org': set(),
|
'opm.blitzed.org',
|
||||||
'rbl.schulte.org': set(),
|
'psbl.surriel.com',
|
||||||
'spamguard.leadmon.net': set(),
|
'rbl.efnet.org',
|
||||||
'ubl.unsubscore.com': set(),
|
'rbl.schulte.org',
|
||||||
'unconfirmed.dsbl.org': set(),
|
'spamguard.leadmon.net',
|
||||||
'virbl.dnsbl.bit.nl': set(),
|
'ubl.unsubscore.com',
|
||||||
'zen.spamhaus.org': {
|
'unconfirmed.dsbl.org',
|
||||||
# https://www.spamhaus.org/news/article/807/using-our-public-mirrors-check-your-return-codes-now.
|
'virbl.dnsbl.bit.nl',
|
||||||
'127.255.255.252', # Typing Error
|
'zen.spamhaus.org',
|
||||||
'127.255.255.254', # public resolver / generic rdns
|
]
|
||||||
'127.255.255.255', # rate limited
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
def check_list(ip_list, blocklist, warn_ips):
|
def check_list(ip_list, blocklist):
|
||||||
dns_name = '{}.{}'.format(
|
dns_name = '{}.{}'.format(
|
||||||
'.'.join(ip_list),
|
'.'.join(ip_list),
|
||||||
blocklist,
|
blocklist,
|
||||||
|
@ -44,21 +41,16 @@ def check_list(ip_list, blocklist, warn_ips):
|
||||||
result = check_output([
|
result = check_output([
|
||||||
'dig',
|
'dig',
|
||||||
'+tries=2',
|
'+tries=2',
|
||||||
'+time=10',
|
'+time=5',
|
||||||
'+short',
|
'+short',
|
||||||
dns_name
|
dns_name
|
||||||
]).decode().splitlines()
|
]).decode().splitlines()
|
||||||
for item in result:
|
for item in result:
|
||||||
if item.startswith(';;'):
|
|
||||||
continue
|
|
||||||
msgs.append('{} listed in {} as {}'.format(
|
msgs.append('{} listed in {} as {}'.format(
|
||||||
ip,
|
ip,
|
||||||
blocklist,
|
blocklist,
|
||||||
item,
|
item,
|
||||||
))
|
))
|
||||||
if item in warn_ips and returncode < 2:
|
|
||||||
returncode = 1
|
|
||||||
else:
|
|
||||||
returncode = 2
|
returncode = 2
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if e.returncode == 9:
|
if e.returncode == 9:
|
||||||
|
@ -86,8 +78,8 @@ exitcode = 0
|
||||||
with ThreadPoolExecutor(max_workers=len(BLOCKLISTS)) as executor:
|
with ThreadPoolExecutor(max_workers=len(BLOCKLISTS)) as executor:
|
||||||
futures = set()
|
futures = set()
|
||||||
|
|
||||||
for blocklist, warn_ips in BLOCKLISTS.items():
|
for blocklist in BLOCKLISTS:
|
||||||
futures.add(executor.submit(check_list, ip_list, blocklist, warn_ips))
|
futures.add(executor.submit(check_list, ip_list, blocklist))
|
||||||
|
|
||||||
for future in as_completed(futures):
|
for future in as_completed(futures):
|
||||||
msgs, this_exitcode = future.result()
|
msgs, this_exitcode = future.result()
|
||||||
|
|
|
@ -1,18 +1,31 @@
|
||||||
% for dt in downtimes:
|
% for monitored_node in sorted(repo.nodes):
|
||||||
object ScheduledDowntime "${dt['name']}" {
|
<%
|
||||||
host_name = "${dt['host']}"
|
auto_updates_enabled = (
|
||||||
|
monitored_node.has_any_bundle(['apt', 'c3voc-addons'])
|
||||||
|
or (
|
||||||
|
monitored_node.has_bundle('pacman')
|
||||||
|
and monitored_node.metadata.get('pacman/unattended-upgrades/is_enabled', False)
|
||||||
|
)
|
||||||
|
) and not monitored_node.metadata.get('icinga_options/exclude_from_monitoring', False)
|
||||||
|
%>\
|
||||||
|
% if auto_updates_enabled:
|
||||||
|
object ScheduledDowntime "unattended_upgrades" {
|
||||||
|
host_name = "${monitored_node.name}"
|
||||||
|
|
||||||
author = "${dt['name']}"
|
author = "unattended-upgrades"
|
||||||
comment = "${dt['comment']}"
|
comment = "Downtime for upgrade-and-reboot of node ${monitored_node.name}"
|
||||||
|
|
||||||
fixed = true
|
fixed = true
|
||||||
|
|
||||||
ranges = {
|
ranges = {
|
||||||
% for d,t in dt['times'].items():
|
% if monitored_node.has_bundle('pacman'):
|
||||||
"${d}" = "${t}"
|
"${days[monitored_node.metadata.get('pacman/unattended-upgrades/day')]}" = "${monitored_node.metadata.get('pacman/unattended-upgrades/hour')}:${monitored_node.magic_number%30}-${monitored_node.metadata.get('pacman/unattended-upgrades/hour')}:${(monitored_node.magic_number%30)+30}"
|
||||||
% endfor
|
% else:
|
||||||
|
"${days[monitored_node.metadata.get('apt/unattended-upgrades/day')]}" = "${monitored_node.metadata.get('apt/unattended-upgrades/hour')}:${monitored_node.magic_number%30}-${monitored_node.metadata.get('apt/unattended-upgrades/hour')}:${(monitored_node.magic_number%30)+30}"
|
||||||
|
% endif
|
||||||
}
|
}
|
||||||
|
|
||||||
child_options = "DowntimeTriggeredChildren"
|
child_options = "DowntimeTriggeredChildren"
|
||||||
}
|
}
|
||||||
|
% endif
|
||||||
% endfor
|
% endfor
|
||||||
|
|
|
@ -33,11 +33,3 @@ object ServiceGroup "checks_with_sms" {
|
||||||
assign where service.vars.notification.sms == true
|
assign where service.vars.notification.sms == true
|
||||||
ignore where host.vars.notification.sms == false
|
ignore where host.vars.notification.sms == false
|
||||||
}
|
}
|
||||||
|
|
||||||
object ServiceGroup "statuspage" {
|
|
||||||
display_name = "Checks which are show on the public status page"
|
|
||||||
|
|
||||||
assign where service.vars.notification.sms == true
|
|
||||||
ignore where host.vars.notification.sms == false
|
|
||||||
ignore where host.vars.show_on_statuspage == false
|
|
||||||
}
|
|
||||||
|
|
|
@ -14,8 +14,7 @@ object Host "${rnode.name}" {
|
||||||
vars.os = "${rnode.os}"
|
vars.os = "${rnode.os}"
|
||||||
|
|
||||||
# used for status page
|
# used for status page
|
||||||
vars.pretty_name = "${rnode.metadata.get('icinga_options/pretty_name', rnode.metadata.get('hostname'))}"
|
vars.pretty_name = "${rnode.metadata.get('icinga_options/pretty_name', rnode.name)}"
|
||||||
vars.show_on_statuspage = ${str(rnode.metadata.get('icinga_options/show_on_statuspage', True)).lower()}
|
|
||||||
|
|
||||||
vars.period = "${rnode.metadata.get('icinga_options/period', '24x7')}"
|
vars.period = "${rnode.metadata.get('icinga_options/period', '24x7')}"
|
||||||
|
|
||||||
|
|
|
@ -9,11 +9,6 @@ app = Flask(__name__)
|
||||||
@app.route('/status')
|
@app.route('/status')
|
||||||
def statuspage():
|
def statuspage():
|
||||||
everything_fine = True
|
everything_fine = True
|
||||||
try:
|
|
||||||
check_output(['/usr/local/share/icinga/plugins/check_mounts'])
|
|
||||||
except:
|
|
||||||
everything_fine = False
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
check_output(['/usr/lib/nagios/plugins/check_procs', '-C', 'icinga2', '-c', '1:'])
|
check_output(['/usr/lib/nagios/plugins/check_procs', '-C', 'icinga2', '-c', '1:'])
|
||||||
except:
|
except:
|
||||||
|
|
|
@ -3,6 +3,8 @@ Description=Icinga2 Statusmonitor
|
||||||
After=network.target
|
After=network.target
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
|
User=nagios
|
||||||
|
Group=nagios
|
||||||
Environment="FLASK_APP=/etc/icinga2/icinga_statusmonitor.py"
|
Environment="FLASK_APP=/etc/icinga2/icinga_statusmonitor.py"
|
||||||
ExecStart=/usr/bin/python3 -m flask run
|
ExecStart=/usr/bin/python3 -m flask run
|
||||||
WorkingDirectory=/tmp
|
WorkingDirectory=/tmp
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
[settings]
|
|
||||||
acknowledge_sticky = 1
|
|
||||||
hostdowntime_all_services = 1
|
|
||||||
hostdowntime_end_fixed = P1W
|
|
||||||
servicedowntime_end_fixed = P2D
|
|
|
@ -3,14 +3,22 @@
|
||||||
import email.mime.text
|
import email.mime.text
|
||||||
import smtplib
|
import smtplib
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
from json import dumps, load
|
from json import dumps
|
||||||
from subprocess import run
|
from subprocess import run
|
||||||
from sys import argv
|
from sys import argv
|
||||||
|
|
||||||
from requests import post
|
from requests import post
|
||||||
|
|
||||||
with open('/etc/icinga2/notification_config.json') as f:
|
SIPGATE_USER='${node.metadata['icinga2']['sipgate_user']}'
|
||||||
CONFIG = load(f)
|
SIPGATE_PASS='${node.metadata['icinga2']['sipgate_pass']}'
|
||||||
|
|
||||||
|
STATUS_TO_EMOJI = {
|
||||||
|
'critical': '🔥',
|
||||||
|
'down': '🚨🚨🚨',
|
||||||
|
'ok': '🆗',
|
||||||
|
'up': '👌',
|
||||||
|
'warning': '⚡',
|
||||||
|
}
|
||||||
|
|
||||||
parser = ArgumentParser(
|
parser = ArgumentParser(
|
||||||
prog='icinga_notification_wrapper',
|
prog='icinga_notification_wrapper',
|
||||||
|
@ -65,31 +73,36 @@ def notify_per_sms():
|
||||||
output_text = ''
|
output_text = ''
|
||||||
else:
|
else:
|
||||||
output_text = '\n\n{}'.format(args.output)
|
output_text = '\n\n{}'.format(args.output)
|
||||||
|
if args.state.lower() in STATUS_TO_EMOJI:
|
||||||
|
message_text = '{emoji} {host}{service} {emoji}{output}'.format(
|
||||||
|
emoji=STATUS_TO_EMOJI[args.state.lower()],
|
||||||
|
host=args.host_name,
|
||||||
|
service=('/'+args.service_name if args.service_name else ''),
|
||||||
|
state=args.state.upper(),
|
||||||
|
output=output_text,
|
||||||
|
)
|
||||||
|
else:
|
||||||
message_text = 'ICINGA: {host}{service} is {state}{output}'.format(
|
message_text = 'ICINGA: {host}{service} is {state}{output}'.format(
|
||||||
host=args.host_name,
|
host=args.host_name,
|
||||||
service=('/'+args.service_name if args.service_name else ''),
|
service=('/'+args.service_name if args.service_name else ''),
|
||||||
state=args.state.upper(),
|
state=args.state.upper(),
|
||||||
output=output_text,
|
output=output_text,
|
||||||
)
|
)
|
||||||
|
|
||||||
message = {
|
message = {
|
||||||
'message': message_text,
|
'message': message_text,
|
||||||
'smsId': 's0', # XXX what does this mean? Documentation is unclear
|
'smsId': 's0', # XXX what does this mean? Documentation is unclear
|
||||||
'recipient': args.sms
|
'recipient': args.sms
|
||||||
}
|
}
|
||||||
|
|
||||||
headers = {
|
headers = {
|
||||||
'Content-type': 'application/json',
|
'Content-type': 'application/json',
|
||||||
'Accept': 'application/json'
|
'Accept': 'application/json'
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
r = post(
|
r = post(
|
||||||
'https://api.sipgate.com/v2/sessions/sms',
|
'https://api.sipgate.com/v2/sessions/sms',
|
||||||
json=message,
|
json=message,
|
||||||
headers=headers,
|
headers=headers,
|
||||||
auth=(CONFIG['sipgate']['user'], CONFIG['sipgate']['password']),
|
auth=(SIPGATE_USER, SIPGATE_PASS),
|
||||||
)
|
)
|
||||||
|
|
||||||
if r.status_code == 204:
|
if r.status_code == 204:
|
||||||
|
@ -100,42 +113,6 @@ def notify_per_sms():
|
||||||
log_to_syslog('Sending a SMS to "{}" failed: {}'.format(args.sms, repr(e)))
|
log_to_syslog('Sending a SMS to "{}" failed: {}'.format(args.sms, repr(e)))
|
||||||
|
|
||||||
|
|
||||||
def notify_per_ntfy():
|
|
||||||
message_text = 'ICINGA: {host}{service} is {state}\n\n{output}'.format(
|
|
||||||
host=args.host_name,
|
|
||||||
service=('/'+args.service_name if args.service_name else ''),
|
|
||||||
state=args.state.upper(),
|
|
||||||
output=args.output,
|
|
||||||
)
|
|
||||||
|
|
||||||
if args.service_name:
|
|
||||||
subject = '[ICINGA] {}/{}'.format(args.host_name, args.service_name)
|
|
||||||
else:
|
|
||||||
subject = '[ICINGA] {}'.format(args.host_name)
|
|
||||||
|
|
||||||
if args.notification_type.lower() == 'recovery':
|
|
||||||
priority = 'default'
|
|
||||||
else:
|
|
||||||
priority = 'urgent'
|
|
||||||
|
|
||||||
headers = {
|
|
||||||
'Title': subject,
|
|
||||||
'Priority': priority,
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
|
||||||
r = post(
|
|
||||||
CONFIG['ntfy']['url'],
|
|
||||||
data=message_text,
|
|
||||||
headers=headers,
|
|
||||||
auth=(CONFIG['ntfy']['user'], CONFIG['ntfy']['password']),
|
|
||||||
)
|
|
||||||
|
|
||||||
r.raise_for_status()
|
|
||||||
except Exception as e:
|
|
||||||
log_to_syslog('Sending a Notification failed: {}'.format(repr(e)))
|
|
||||||
|
|
||||||
|
|
||||||
def notify_per_mail():
|
def notify_per_mail():
|
||||||
if args.notification_type.lower() == 'recovery':
|
if args.notification_type.lower() == 'recovery':
|
||||||
# Do not send recovery emails.
|
# Do not send recovery emails.
|
||||||
|
@ -199,7 +176,4 @@ if __name__ == '__main__':
|
||||||
notify_per_mail()
|
notify_per_mail()
|
||||||
|
|
||||||
if args.sms:
|
if args.sms:
|
||||||
if not args.service_name:
|
|
||||||
notify_per_sms()
|
notify_per_sms()
|
||||||
if CONFIG['ntfy']['user']:
|
|
||||||
notify_per_ntfy()
|
|
||||||
|
|
|
@ -76,6 +76,8 @@ files = {
|
||||||
},
|
},
|
||||||
'/usr/local/share/icinga/plugins/check_sipgate_account_balance': {
|
'/usr/local/share/icinga/plugins/check_sipgate_account_balance': {
|
||||||
'mode': '0755',
|
'mode': '0755',
|
||||||
|
'content_type': 'mako',
|
||||||
|
'cascade_skip': False, # contains faults
|
||||||
},
|
},
|
||||||
'/usr/local/share/icinga/plugins/check_freifunk_node': {
|
'/usr/local/share/icinga/plugins/check_freifunk_node': {
|
||||||
'mode': '0755',
|
'mode': '0755',
|
||||||
|
@ -112,22 +114,11 @@ files = {
|
||||||
'svc_systemd:icinga2:restart',
|
'svc_systemd:icinga2:restart',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'/etc/icinga2/notification_config.json': {
|
|
||||||
'content': repo.libs.faults.dict_as_json({
|
|
||||||
'sipgate': {
|
|
||||||
'user': node.metadata.get('icinga2/sipgate/user'),
|
|
||||||
'password': node.metadata.get('icinga2/sipgate/pass'),
|
|
||||||
},
|
|
||||||
'ntfy': {
|
|
||||||
'url': node.metadata.get('icinga2/ntfy/url'),
|
|
||||||
'user': node.metadata.get('icinga2/ntfy/user'),
|
|
||||||
'password': node.metadata.get('icinga2/ntfy/pass'),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
'/etc/icinga2/scripts/icinga_notification_wrapper': {
|
'/etc/icinga2/scripts/icinga_notification_wrapper': {
|
||||||
'source': 'scripts/icinga_notification_wrapper',
|
'source': 'scripts/icinga_notification_wrapper',
|
||||||
|
'content_type': 'mako',
|
||||||
'mode': '0755',
|
'mode': '0755',
|
||||||
|
'cascade_skip': False, # contains faults
|
||||||
},
|
},
|
||||||
'/etc/icinga2/features-available/ido-pgsql.conf': {
|
'/etc/icinga2/features-available/ido-pgsql.conf': {
|
||||||
'source': 'icinga2/ido-pgsql.conf',
|
'source': 'icinga2/ido-pgsql.conf',
|
||||||
|
@ -254,11 +245,6 @@ files = {
|
||||||
'mode': '0660',
|
'mode': '0660',
|
||||||
'group': 'icingaweb2',
|
'group': 'icingaweb2',
|
||||||
},
|
},
|
||||||
'/etc/icingaweb2/modules/monitoring/config.ini': {
|
|
||||||
'source': 'icingaweb2/monitoring_config.ini',
|
|
||||||
'mode': '0660',
|
|
||||||
'group': 'icingaweb2',
|
|
||||||
},
|
|
||||||
'/etc/icingaweb2/groups.ini': {
|
'/etc/icingaweb2/groups.ini': {
|
||||||
'source': 'icingaweb2/groups.ini',
|
'source': 'icingaweb2/groups.ini',
|
||||||
'mode': '0660',
|
'mode': '0660',
|
||||||
|
@ -276,13 +262,13 @@ files = {
|
||||||
'group': 'icingaweb2',
|
'group': 'icingaweb2',
|
||||||
},
|
},
|
||||||
|
|
||||||
# monitoring
|
# Statusmonitor
|
||||||
'/etc/icinga2/icinga_statusmonitor.py': {
|
'/etc/icinga2/icinga_statusmonitor.py': {
|
||||||
'triggers': {
|
'triggers': {
|
||||||
'svc_systemd:icinga_statusmonitor:restart',
|
'svc_systemd:icinga_statusmonitor:restart',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'/usr/local/lib/systemd/system/icinga_statusmonitor.service': {
|
'/etc/systemd/system/icinga_statusmonitor.service': {
|
||||||
'triggers': {
|
'triggers': {
|
||||||
'action:systemd-reload',
|
'action:systemd-reload',
|
||||||
'svc_systemd:icinga_statusmonitor:restart',
|
'svc_systemd:icinga_statusmonitor:restart',
|
||||||
|
@ -290,12 +276,8 @@ files = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
svc_systemd['icinga_statusmonitor'] = {
|
pkg_pip = {
|
||||||
'needs': {
|
'easysnmp': {}, # for check_usv_snmp
|
||||||
'file:/etc/icinga2/icinga_statusmonitor.py',
|
|
||||||
'file:/usr/local/lib/systemd/system/icinga_statusmonitor.service',
|
|
||||||
'pkg_apt:python3-flask',
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
actions = {
|
actions = {
|
||||||
|
@ -337,22 +319,36 @@ for name in files:
|
||||||
for name in symlinks:
|
for name in symlinks:
|
||||||
icinga_run_deps.add(f'symlink:{name}')
|
icinga_run_deps.add(f'symlink:{name}')
|
||||||
|
|
||||||
svc_systemd['icinga2'] = {
|
svc_systemd = {
|
||||||
|
'icinga2': {
|
||||||
'needs': icinga_run_deps,
|
'needs': icinga_run_deps,
|
||||||
|
},
|
||||||
|
'icinga_statusmonitor': {
|
||||||
|
'needs': {
|
||||||
|
'file:/etc/icinga2/icinga_statusmonitor.py',
|
||||||
|
'file:/etc/systemd/system/icinga_statusmonitor.service',
|
||||||
|
'pkg_apt:python3-flask',
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# The actual hosts and services management starts here
|
# The actual hosts and services management starts here
|
||||||
bundles = set()
|
bundles = set()
|
||||||
downtimes = []
|
for rnode in repo.nodes:
|
||||||
for rnode in sorted(repo.nodes):
|
|
||||||
if rnode.metadata.get('icinga_options/exclude_from_monitoring', False):
|
if rnode.metadata.get('icinga_options/exclude_from_monitoring', False):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
host_ips = repo.libs.tools.resolve_identifier(repo, rnode.name, only_physical=True)
|
host_ips = repo.libs.tools.resolve_identifier(repo, rnode.name)
|
||||||
icinga_ips = {}
|
icinga_ips = {}
|
||||||
|
|
||||||
|
# XXX for the love of god, PLEASE remove this once DNS is no longer
|
||||||
|
# hosted at GCE
|
||||||
|
if rnode.in_group('gce'):
|
||||||
|
icinga_ips['ipv4'] = rnode.metadata.get('external_ipv4')
|
||||||
|
else:
|
||||||
for ip_type in ('ipv4', 'ipv6'):
|
for ip_type in ('ipv4', 'ipv6'):
|
||||||
for ip in sorted(host_ips[ip_type]):
|
for ip in sorted(host_ips[ip_type]):
|
||||||
if ip.is_private and not ip.is_link_local:
|
if ip.is_private and not ip.is_link_local:
|
||||||
|
@ -383,41 +379,6 @@ for rnode in sorted(repo.nodes):
|
||||||
|
|
||||||
bundles |= set(rnode.metadata.get('icinga2_api', {}).keys())
|
bundles |= set(rnode.metadata.get('icinga2_api', {}).keys())
|
||||||
|
|
||||||
if rnode.has_any_bundle(['apt', 'c3voc-addons']):
|
|
||||||
day = rnode.metadata.get('apt/unattended-upgrades/day')
|
|
||||||
hour = rnode.metadata.get('apt/unattended-upgrades/hour')
|
|
||||||
minute = rnode.magic_number%30
|
|
||||||
|
|
||||||
spread = rnode.metadata.get('apt/unattended-upgrades/spread_in_group', None)
|
|
||||||
if spread is not None:
|
|
||||||
spread_nodes = sorted(repo.nodes_in_group(spread))
|
|
||||||
day += spread_nodes.index(rnode)
|
|
||||||
|
|
||||||
downtimes.append({
|
|
||||||
'name': 'unattended-upgrades',
|
|
||||||
'host': rnode.name,
|
|
||||||
'comment': f'Downtime for upgrade-and-reboot of node {rnode.name}',
|
|
||||||
'times': {
|
|
||||||
DAYS_TO_STRING[day%7]: f'{hour}:{minute}-{hour}:{minute+15}',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
elif (
|
|
||||||
rnode.has_bundle('pacman')
|
|
||||||
and rnode.metadata.get('pacman/unattended-upgrades/is_enabled', False)
|
|
||||||
):
|
|
||||||
day = rnode.metadata.get('pacman/unattended-upgrades/day')
|
|
||||||
hour = rnode.metadata.get('pacman/unattended-upgrades/hour')
|
|
||||||
minute = rnode.magic_number%30
|
|
||||||
|
|
||||||
downtimes.append({
|
|
||||||
'name': 'unattended-upgrades',
|
|
||||||
'host': rnode.name,
|
|
||||||
'comment': f'Downtime for upgrade-and-reboot of node {rnode.name}',
|
|
||||||
'times': {
|
|
||||||
DAYS_TO_STRING[day%7]: f'{hour}:{minute}-{hour}:{minute+15}',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
files['/etc/icinga2/conf.d/groups.conf'] = {
|
files['/etc/icinga2/conf.d/groups.conf'] = {
|
||||||
'source': 'icinga2/groups.conf',
|
'source': 'icinga2/groups.conf',
|
||||||
'content_type': 'mako',
|
'content_type': 'mako',
|
||||||
|
@ -438,7 +399,7 @@ files['/etc/icinga2/conf.d/downtimes.conf'] = {
|
||||||
'source': 'icinga2/downtimes.conf',
|
'source': 'icinga2/downtimes.conf',
|
||||||
'content_type': 'mako',
|
'content_type': 'mako',
|
||||||
'context': {
|
'context': {
|
||||||
'downtimes': downtimes,
|
'days': DAYS_TO_STRING,
|
||||||
},
|
},
|
||||||
'owner': 'nagios',
|
'owner': 'nagios',
|
||||||
'group': 'nagios',
|
'group': 'nagios',
|
||||||
|
|
|
@ -17,9 +17,12 @@ defaults = {
|
||||||
'icinga2': {},
|
'icinga2': {},
|
||||||
'icinga2-ido-pgsql': {},
|
'icinga2-ido-pgsql': {},
|
||||||
'icingaweb2': {},
|
'icingaweb2': {},
|
||||||
'python3-easysnmp': {},
|
|
||||||
|
# apparently no longer needed
|
||||||
|
#'icingaweb2-module-monitoring': {},
|
||||||
|
|
||||||
|
# neeeded for statusmonitor
|
||||||
'python3-flask': {},
|
'python3-flask': {},
|
||||||
'snmp': {},
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'icinga2': {
|
'icinga2': {
|
||||||
|
@ -40,6 +43,9 @@ defaults = {
|
||||||
'check_interval': '30m',
|
'check_interval': '30m',
|
||||||
'vars.notification.mail': True,
|
'vars.notification.mail': True,
|
||||||
},
|
},
|
||||||
|
'ICINGA STATUSMONITOR': {
|
||||||
|
'command_on_monitored_host': '/usr/local/share/icinga/plugins/check_systemd_unit icinga_statusmonitor',
|
||||||
|
},
|
||||||
'IDO-PGSQL': {
|
'IDO-PGSQL': {
|
||||||
'check_command': 'ido',
|
'check_command': 'ido',
|
||||||
'vars.ido_type': 'IdoPgsqlConnection',
|
'vars.ido_type': 'IdoPgsqlConnection',
|
||||||
|
@ -53,21 +59,6 @@ defaults = {
|
||||||
'icingaweb2': {
|
'icingaweb2': {
|
||||||
'setup-token': repo.vault.password_for(f'{node.name} icingaweb2 setup-token'),
|
'setup-token': repo.vault.password_for(f'{node.name} icingaweb2 setup-token'),
|
||||||
},
|
},
|
||||||
'php': {
|
|
||||||
'version': '8.2',
|
|
||||||
'packages': {
|
|
||||||
'curl',
|
|
||||||
'gd',
|
|
||||||
'intl',
|
|
||||||
'imagick',
|
|
||||||
'ldap',
|
|
||||||
'mysql',
|
|
||||||
'opcache',
|
|
||||||
'pgsql',
|
|
||||||
'readline',
|
|
||||||
'xml',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'postgresql': {
|
'postgresql': {
|
||||||
'roles': {
|
'roles': {
|
||||||
'icinga2': {
|
'icinga2': {
|
||||||
|
@ -114,29 +105,13 @@ def add_users_from_json(metadata):
|
||||||
|
|
||||||
|
|
||||||
@metadata_reactor.provides(
|
@metadata_reactor.provides(
|
||||||
'nginx/vhosts/icingaweb2',
|
'firewall/port_rules/5665',
|
||||||
'nginx/vhosts/icinga_statusmonitor',
|
|
||||||
)
|
)
|
||||||
def nginx(metadata):
|
def firewall(metadata):
|
||||||
if not node.has_bundle('nginx'):
|
|
||||||
raise DoNotRunAgain
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'nginx': {
|
'firewall': {
|
||||||
'vhosts': {
|
'port_rules': {
|
||||||
'icingaweb2': {
|
'5665': atomic(metadata.get('icinga2/restrict-to', set())),
|
||||||
'domain': metadata.get('icinga2/web_domain'),
|
|
||||||
'webroot': '/usr/share/icingaweb2/public',
|
|
||||||
'locations': {
|
|
||||||
'/api/': {
|
|
||||||
'target': 'https://127.0.0.1:5665/',
|
|
||||||
},
|
|
||||||
'/statusmonitor/': {
|
|
||||||
'target': 'http://127.0.0.1:5000/',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'extras': True,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ defaults = {
|
||||||
'repos': {
|
'repos': {
|
||||||
'influxdb': {
|
'influxdb': {
|
||||||
'items': {
|
'items': {
|
||||||
'deb https://repos.influxdata.com/{os} stable main',
|
'deb https://repos.influxdata.com/{os} {os_release} stable',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,8 +4,7 @@ After=network.target
|
||||||
Requires=infobeamer-cms.service
|
Requires=infobeamer-cms.service
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Environment=SETTINGS=/opt/infobeamer-cms/settings.toml
|
|
||||||
WorkingDirectory=/opt/infobeamer-cms/src
|
|
||||||
User=infobeamer-cms
|
User=infobeamer-cms
|
||||||
Group=infobeamer-cms
|
Group=infobeamer-cms
|
||||||
ExecStart=/opt/infobeamer-cms/venv/bin/python syncer.py
|
WorkingDirectory=/opt/infobeamer-cms
|
||||||
|
ExecStart=curl -s -H "Host: ${domain}" http://127.0.0.1:8000/sync
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
Description=Run infobeamer-cms sync
|
Description=Run infobeamer-cms sync
|
||||||
|
|
||||||
[Timer]
|
[Timer]
|
||||||
OnCalendar=minutely
|
OnCalendar=*:0/5
|
||||||
Persistent=true
|
Persistent=true
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
|
|
4
bundles/infobeamer-cms/files/settings.toml
Normal file
4
bundles/infobeamer-cms/files/settings.toml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<%
|
||||||
|
from tomlkit import dumps as toml_dumps
|
||||||
|
from bundlewrap.utils.text import toml_clean
|
||||||
|
%>${toml_clean(toml_dumps(repo.libs.faults.resolve_faults(config), sort_keys=True))}
|
|
@ -1,4 +1,8 @@
|
||||||
actions = {
|
actions = {
|
||||||
|
'infobeamer-cms_set_directory_permissions': {
|
||||||
|
'triggered': True,
|
||||||
|
'command': 'chown -R infobeamer-cms:infobeamer-cms /opt/infobeamer-cms/src/static/'
|
||||||
|
},
|
||||||
'infobeamer-cms_create_virtualenv': {
|
'infobeamer-cms_create_virtualenv': {
|
||||||
'command': '/usr/bin/python3 -m virtualenv -p python3 /opt/infobeamer-cms/venv/',
|
'command': '/usr/bin/python3 -m virtualenv -p python3 /opt/infobeamer-cms/venv/',
|
||||||
'unless': 'test -d /opt/infobeamer-cms/venv/',
|
'unless': 'test -d /opt/infobeamer-cms/venv/',
|
||||||
|
@ -8,11 +12,7 @@ actions = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'infobeamer-cms_install_requirements': {
|
'infobeamer-cms_install_requirements': {
|
||||||
'command': ' && '.join([
|
'command': 'cd /opt/infobeamer-cms/src && /opt/infobeamer-cms/venv/bin/pip install --upgrade pip gunicorn -r requirements.txt',
|
||||||
'cd /opt/infobeamer-cms/src',
|
|
||||||
'/opt/infobeamer-cms/venv/bin/pip install --upgrade pip gunicorn -r requirements.txt',
|
|
||||||
'rsync /opt/infobeamer-cms/src/static/* /opt/infobeamer-cms/static/',
|
|
||||||
]),
|
|
||||||
'needs': {
|
'needs': {
|
||||||
'action:infobeamer-cms_create_virtualenv',
|
'action:infobeamer-cms_create_virtualenv',
|
||||||
},
|
},
|
||||||
|
@ -23,12 +23,13 @@ actions = {
|
||||||
git_deploy = {
|
git_deploy = {
|
||||||
'/opt/infobeamer-cms/src': {
|
'/opt/infobeamer-cms/src': {
|
||||||
'rev': 'master',
|
'rev': 'master',
|
||||||
'repo': 'https://github.com/voc/infobeamer-cms.git',
|
'repo': 'https://github.com/sophieschi/36c3-cms.git',
|
||||||
'needs': {
|
'needs': {
|
||||||
'directory:/opt/infobeamer-cms/src',
|
'directory:/opt/infobeamer-cms/src',
|
||||||
},
|
},
|
||||||
'triggers': {
|
'triggers': {
|
||||||
'svc_systemd:infobeamer-cms:restart',
|
'svc_systemd:infobeamer-cms:restart',
|
||||||
|
'action:infobeamer-cms_set_directory_permissions',
|
||||||
'action:infobeamer-cms_install_requirements',
|
'action:infobeamer-cms_install_requirements',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -36,9 +37,6 @@ git_deploy = {
|
||||||
|
|
||||||
directories = {
|
directories = {
|
||||||
'/opt/infobeamer-cms/src': {},
|
'/opt/infobeamer-cms/src': {},
|
||||||
'/opt/infobeamer-cms/static': {
|
|
||||||
'owner': 'infobeamer-cms',
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
config = node.metadata.get('infobeamer-cms/config', {})
|
config = node.metadata.get('infobeamer-cms/config', {})
|
||||||
|
@ -68,7 +66,10 @@ for room, device_id in sorted(node.metadata.get('infobeamer-cms/rooms', {}).item
|
||||||
|
|
||||||
files = {
|
files = {
|
||||||
'/opt/infobeamer-cms/settings.toml': {
|
'/opt/infobeamer-cms/settings.toml': {
|
||||||
'content': repo.libs.faults.dict_as_toml(config),
|
'content_type': 'mako',
|
||||||
|
'context': {
|
||||||
|
'config': config,
|
||||||
|
},
|
||||||
'triggers': {
|
'triggers': {
|
||||||
'svc_systemd:infobeamer-cms:restart',
|
'svc_systemd:infobeamer-cms:restart',
|
||||||
},
|
},
|
||||||
|
@ -96,11 +97,19 @@ files = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pkg_pip = {
|
||||||
|
'github-flask': {
|
||||||
|
'needed_by': {
|
||||||
|
'svc_systemd:infobeamer-cms',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
svc_systemd = {
|
svc_systemd = {
|
||||||
'infobeamer-cms': {
|
'infobeamer-cms': {
|
||||||
'needs': {
|
'needs': {
|
||||||
'action:infobeamer-cms_install_requirements',
|
'action:infobeamer-cms_install_requirements',
|
||||||
'directory:/opt/infobeamer-cms/static',
|
'action:infobeamer-cms_set_directory_permissions',
|
||||||
'file:/etc/systemd/system/infobeamer-cms.service',
|
'file:/etc/systemd/system/infobeamer-cms.service',
|
||||||
'file:/opt/infobeamer-cms/settings.toml',
|
'file:/opt/infobeamer-cms/settings.toml',
|
||||||
'git_deploy:/opt/infobeamer-cms/src',
|
'git_deploy:/opt/infobeamer-cms/src',
|
||||||
|
@ -108,12 +117,8 @@ svc_systemd = {
|
||||||
},
|
},
|
||||||
'infobeamer-cms-runperiodic.timer': {
|
'infobeamer-cms-runperiodic.timer': {
|
||||||
'needs': {
|
'needs': {
|
||||||
'action:infobeamer-cms_install_requirements',
|
|
||||||
'directory:/opt/infobeamer-cms/static',
|
|
||||||
'file:/etc/systemd/system/infobeamer-cms-runperiodic.service',
|
|
||||||
'file:/etc/systemd/system/infobeamer-cms-runperiodic.timer',
|
'file:/etc/systemd/system/infobeamer-cms-runperiodic.timer',
|
||||||
'file:/opt/infobeamer-cms/settings.toml',
|
'file:/etc/systemd/system/infobeamer-cms-runperiodic.service',
|
||||||
'git_deploy:/opt/infobeamer-cms/src',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ defaults = {
|
||||||
'MAX_UPLOADS': 5,
|
'MAX_UPLOADS': 5,
|
||||||
'PREFERRED_URL_SCHEME': 'https',
|
'PREFERRED_URL_SCHEME': 'https',
|
||||||
'SESSION_COOKIE_NAME': '__Host-sess',
|
'SESSION_COOKIE_NAME': '__Host-sess',
|
||||||
'STATIC_PATH': '/opt/infobeamer-cms/static',
|
|
||||||
'URL_KEY': repo.vault.password_for(f'{node.name} infobeamer-cms url key'),
|
'URL_KEY': repo.vault.password_for(f'{node.name} infobeamer-cms url key'),
|
||||||
'VERSION': 1,
|
'VERSION': 1,
|
||||||
},
|
},
|
||||||
|
@ -30,13 +29,15 @@ def nginx(metadata):
|
||||||
'/': {
|
'/': {
|
||||||
'target': 'http://127.0.0.1:8000',
|
'target': 'http://127.0.0.1:8000',
|
||||||
},
|
},
|
||||||
|
'/sync': {
|
||||||
|
'return': 403,
|
||||||
|
},
|
||||||
'/static': {
|
'/static': {
|
||||||
'alias': '/opt/infobeamer-cms/static',
|
'alias': '/opt/infobeamer-cms/src/static',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'website_check_path': '/',
|
'website_check_path': '/',
|
||||||
'website_check_string': 'Share your projects',
|
'website_check_string': 'Share your projects',
|
||||||
'do_not_set_content_security_headers': True,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -44,7 +45,6 @@ def nginx(metadata):
|
||||||
|
|
||||||
|
|
||||||
@metadata_reactor.provides(
|
@metadata_reactor.provides(
|
||||||
'infobeamer-cms/config/DOMAIN',
|
|
||||||
'infobeamer-cms/config/TIME_MAX',
|
'infobeamer-cms/config/TIME_MAX',
|
||||||
'infobeamer-cms/config/TIME_MIN',
|
'infobeamer-cms/config/TIME_MIN',
|
||||||
)
|
)
|
||||||
|
@ -57,7 +57,6 @@ def event_times(metadata):
|
||||||
return {
|
return {
|
||||||
'infobeamer-cms': {
|
'infobeamer-cms': {
|
||||||
'config': {
|
'config': {
|
||||||
'DOMAIN': metadata.get('infobeamer-cms/domain'),
|
|
||||||
'TIME_MAX': int(event_end.timestamp()),
|
'TIME_MAX': int(event_end.timestamp()),
|
||||||
'TIME_MIN': int(event_start.timestamp()),
|
'TIME_MIN': int(event_start.timestamp()),
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
[Unit]
|
|
||||||
Description=infobeamer-monitor
|
|
||||||
After=network.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=exec
|
|
||||||
Restart=always
|
|
||||||
RestartSec=5s
|
|
||||||
ExecStart=/opt/infobeamer-cms/venv/bin/python monitor.py
|
|
||||||
User=infobeamer-cms
|
|
||||||
Group=infobeamer-cms
|
|
||||||
WorkingDirectory=/opt/infobeamer-monitor/
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
|
@ -1,227 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import logging
|
|
||||||
from datetime import datetime
|
|
||||||
from json import dumps
|
|
||||||
from time import sleep
|
|
||||||
from zoneinfo import ZoneInfo
|
|
||||||
|
|
||||||
import paho.mqtt.client as mqtt
|
|
||||||
from requests import RequestException, get
|
|
||||||
|
|
||||||
try:
|
|
||||||
# python 3.11
|
|
||||||
from tomllib import loads as toml_load
|
|
||||||
except ImportError:
|
|
||||||
from rtoml import load as toml_load
|
|
||||||
|
|
||||||
with open("config.toml") as f:
|
|
||||||
CONFIG = toml_load(f.read())
|
|
||||||
|
|
||||||
|
|
||||||
logging.basicConfig(
|
|
||||||
format="[%(levelname)s %(name)s] %(message)s",
|
|
||||||
level=logging.INFO,
|
|
||||||
)
|
|
||||||
|
|
||||||
LOG = logging.getLogger("main")
|
|
||||||
MLOG = logging.getLogger("mqtt")
|
|
||||||
|
|
||||||
state = None
|
|
||||||
|
|
||||||
client = mqtt.Client()
|
|
||||||
client.username_pw_set(CONFIG["mqtt"]["user"], CONFIG["mqtt"]["password"])
|
|
||||||
client.connect(CONFIG["mqtt"]["host"], 1883, 60)
|
|
||||||
client.loop_start()
|
|
||||||
|
|
||||||
|
|
||||||
def mqtt_out(message, level="INFO", device=None):
|
|
||||||
key = "infobeamer"
|
|
||||||
if device:
|
|
||||||
key += f"/{device['id']}"
|
|
||||||
message = f"[{device['description']}] {message}"
|
|
||||||
|
|
||||||
client.publish(
|
|
||||||
CONFIG["mqtt"]["topic"],
|
|
||||||
dumps(
|
|
||||||
{
|
|
||||||
"level": level,
|
|
||||||
"component": key,
|
|
||||||
"msg": message,
|
|
||||||
}
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def mqtt_dump_state(device):
|
|
||||||
if not device["is_online"]:
|
|
||||||
return
|
|
||||||
|
|
||||||
out = []
|
|
||||||
if device["location"]:
|
|
||||||
out.append("Location: {}".format(device["location"]))
|
|
||||||
out.append("Setup: {} ({})".format(device["setup"]["name"], device["setup"]["id"]))
|
|
||||||
out.append("Resolution: {}".format(device["run"].get("resolution", "unknown")))
|
|
||||||
|
|
||||||
mqtt_out(
|
|
||||||
" - ".join(out),
|
|
||||||
device=device,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
mqtt_out("Monitor starting up")
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
online_devices = set()
|
|
||||||
available_credits = None
|
|
||||||
|
|
||||||
try:
|
|
||||||
r = get(
|
|
||||||
"https://info-beamer.com/api/v1/device/list",
|
|
||||||
auth=("", CONFIG["api_key"]),
|
|
||||||
)
|
|
||||||
r.raise_for_status()
|
|
||||||
ib_state = r.json()["devices"]
|
|
||||||
except RequestException as e:
|
|
||||||
LOG.exception("Could not get data from info-beamer")
|
|
||||||
mqtt_out(
|
|
||||||
f"Could not get data from info-beamer: {e!r}",
|
|
||||||
level="WARN",
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
new_state = {}
|
|
||||||
for device in ib_state:
|
|
||||||
did = str(device["id"])
|
|
||||||
|
|
||||||
if did in new_state:
|
|
||||||
mqtt_out("DUPLICATE DETECTED!", level="ERROR", device=device)
|
|
||||||
continue
|
|
||||||
|
|
||||||
new_state[did] = device
|
|
||||||
must_dump_state = False
|
|
||||||
|
|
||||||
if state is not None:
|
|
||||||
if did not in state:
|
|
||||||
LOG.info(
|
|
||||||
"new device found: {} [{}]".format(
|
|
||||||
did,
|
|
||||||
device["description"],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
mqtt_out(
|
|
||||||
"new device found!",
|
|
||||||
device=device,
|
|
||||||
)
|
|
||||||
must_dump_state = True
|
|
||||||
|
|
||||||
else:
|
|
||||||
if device["is_online"] != state[did]["is_online"]:
|
|
||||||
online_status = (
|
|
||||||
"online from {}".format(device["run"]["public_addr"])
|
|
||||||
if device["is_online"]
|
|
||||||
else "offline"
|
|
||||||
)
|
|
||||||
|
|
||||||
LOG.info("device {} is now {}".format(did, online_status))
|
|
||||||
mqtt_out(
|
|
||||||
f"status changed to {online_status}",
|
|
||||||
level="INFO" if device["is_online"] else "WARN",
|
|
||||||
device=device,
|
|
||||||
)
|
|
||||||
must_dump_state = True
|
|
||||||
|
|
||||||
if device["description"] != state[did]["description"]:
|
|
||||||
LOG.info(
|
|
||||||
"device {} changed name to {}".format(
|
|
||||||
did, device["description"]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
must_dump_state = True
|
|
||||||
|
|
||||||
if device["is_online"]:
|
|
||||||
if device["maintenance"]:
|
|
||||||
mqtt_out(
|
|
||||||
"maintenance required: {}".format(
|
|
||||||
" ".join(sorted(device["maintenance"]))
|
|
||||||
),
|
|
||||||
level="WARN",
|
|
||||||
device=device,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (
|
|
||||||
device["location"] != state[did]["location"]
|
|
||||||
or device["setup"]["id"] != state[did]["setup"]["id"]
|
|
||||||
or device["run"].get("resolution")
|
|
||||||
!= state[did]["run"].get("resolution")
|
|
||||||
):
|
|
||||||
must_dump_state = True
|
|
||||||
|
|
||||||
if must_dump_state:
|
|
||||||
mqtt_dump_state(device)
|
|
||||||
else:
|
|
||||||
LOG.info("adding device {} to empty state".format(device["id"]))
|
|
||||||
|
|
||||||
if device["is_online"]:
|
|
||||||
online_devices.add(
|
|
||||||
"{} ({})".format(
|
|
||||||
device["id"],
|
|
||||||
device["description"],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
state = new_state
|
|
||||||
|
|
||||||
try:
|
|
||||||
r = get(
|
|
||||||
"https://info-beamer.com/api/v1/account",
|
|
||||||
auth=("", CONFIG["api_key"]),
|
|
||||||
)
|
|
||||||
r.raise_for_status()
|
|
||||||
ib_account = r.json()
|
|
||||||
except RequestException as e:
|
|
||||||
LOG.exception("Could not get data from info-beamer")
|
|
||||||
mqtt_out(
|
|
||||||
f"Could not get data from info-beamer: {e!r}",
|
|
||||||
level="WARN",
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
available_credits = ib_account["balance"]
|
|
||||||
if available_credits < 50:
|
|
||||||
mqtt_out(
|
|
||||||
f"balance has dropped below 50 credits! (available: {available_credits})",
|
|
||||||
level="ERROR",
|
|
||||||
)
|
|
||||||
elif available_credits < 100:
|
|
||||||
mqtt_out(
|
|
||||||
f"balance has dropped below 100 credits! (available: {available_credits})",
|
|
||||||
level="WARN",
|
|
||||||
)
|
|
||||||
|
|
||||||
for quota_name, quota_config in sorted(ib_account["quotas"].items()):
|
|
||||||
value = quota_config["count"]["value"]
|
|
||||||
limit = quota_config["count"]["limit"]
|
|
||||||
if value > limit * 0.9:
|
|
||||||
mqtt_out(
|
|
||||||
f"quota {quota_name} is over 90% (limit {limit}, value {value})",
|
|
||||||
level="ERROR",
|
|
||||||
)
|
|
||||||
elif value > limit * 0.8:
|
|
||||||
mqtt_out(
|
|
||||||
f"quota {quota_name} is over 80% (limit {limit}, value {value})",
|
|
||||||
level="WARN",
|
|
||||||
)
|
|
||||||
|
|
||||||
if datetime.now(ZoneInfo("Europe/Berlin")).strftime("%H%M") == "0900":
|
|
||||||
if available_credits is not None:
|
|
||||||
mqtt_out(f"Available Credits: {available_credits}")
|
|
||||||
|
|
||||||
if online_devices:
|
|
||||||
mqtt_out(
|
|
||||||
"Online Devices: {}".format(", ".join(sorted(online_devices)))
|
|
||||||
)
|
|
||||||
|
|
||||||
sleep(60)
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
break
|
|
||||||
|
|
||||||
mqtt_out("Monitor exiting")
|
|
|
@ -1,30 +0,0 @@
|
||||||
assert node.has_bundle('infobeamer-cms') # uses same venv
|
|
||||||
|
|
||||||
files['/opt/infobeamer-monitor/config.toml'] = {
|
|
||||||
'content': repo.libs.faults.dict_as_toml(node.metadata.get('infobeamer-monitor')),
|
|
||||||
'triggers': {
|
|
||||||
'svc_systemd:infobeamer-monitor:restart',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
files['/opt/infobeamer-monitor/monitor.py'] = {
|
|
||||||
'mode': '0755',
|
|
||||||
'triggers': {
|
|
||||||
'svc_systemd:infobeamer-monitor:restart',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
files['/usr/local/lib/systemd/system/infobeamer-monitor.service'] = {
|
|
||||||
'triggers': {
|
|
||||||
'action:systemd-reload',
|
|
||||||
'svc_systemd:infobeamer-monitor:restart',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
svc_systemd['infobeamer-monitor'] = {
|
|
||||||
'needs': {
|
|
||||||
'file:/opt/infobeamer-monitor/config.toml',
|
|
||||||
'file:/opt/infobeamer-monitor/monitor.py',
|
|
||||||
'file:/usr/local/lib/systemd/system/infobeamer-monitor.service',
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
Cmnd_Alias RESTARTSERVER_SYSTEMD = /usr/bin/systemd-run systemctl restart jellyfin
|
|
||||||
Cmnd_Alias STARTSERVER_SYSTEMD = /usr/bin/systemd-run systemctl start jellyfin
|
|
||||||
Cmnd_Alias STOPSERVER_SYSTEMD = /usr/bin/systemd-run systemctl stop jellyfin
|
|
||||||
|
|
||||||
jellyfin ALL=(ALL) NOPASSWD: RESTARTSERVER_SYSTEMD
|
|
||||||
jellyfin ALL=(ALL) NOPASSWD: STARTSERVER_SYSTEMD
|
|
||||||
jellyfin ALL=(ALL) NOPASSWD: STOPSERVER_SYSTEMD
|
|
|
@ -1,5 +0,0 @@
|
||||||
files['/etc/sudoers.d/jellyfin-sudoers'] = {
|
|
||||||
'after': {
|
|
||||||
'pkg_apt:jellyfin',
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -1,69 +0,0 @@
|
||||||
from bundlewrap.metadata import atomic
|
|
||||||
|
|
||||||
defaults = {
|
|
||||||
'apt': {
|
|
||||||
'packages': {
|
|
||||||
'jellyfin': {},
|
|
||||||
},
|
|
||||||
'repos': {
|
|
||||||
'jellyfin': {
|
|
||||||
'uris': {
|
|
||||||
'https://repo.jellyfin.org/{os}'
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'backups': {
|
|
||||||
'paths': {
|
|
||||||
f'/var/lib/jellyfin/{x}' for x in ('data', 'metadata', 'plugins', 'root')
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'icinga2_api': {
|
|
||||||
'transmission': {
|
|
||||||
'services': {
|
|
||||||
'JELLYFIN PROCESS': {
|
|
||||||
'command_on_monitored_host': '/usr/lib/nagios/plugins/check_procs -C jellyfin -c 1:',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@metadata_reactor.provides(
|
|
||||||
'nginx/vhosts/jellyfin',
|
|
||||||
)
|
|
||||||
def nginx(metadata):
|
|
||||||
if not node.has_bundle('nginx'):
|
|
||||||
raise DoNotRunAgain
|
|
||||||
|
|
||||||
if 'jellyfin' not in metadata.get('nginx/vhosts', {}):
|
|
||||||
return {}
|
|
||||||
|
|
||||||
return {
|
|
||||||
'nginx': {
|
|
||||||
'vhosts': {
|
|
||||||
'jellyfin': {
|
|
||||||
'do_not_add_content_security_headers': True,
|
|
||||||
'locations': {
|
|
||||||
'/': {
|
|
||||||
'target': 'http://127.0.0.1:8096',
|
|
||||||
'websockets': True,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
@metadata_reactor.provides(
|
|
||||||
'firewall/port_rules',
|
|
||||||
)
|
|
||||||
def firewall(metadata):
|
|
||||||
return {
|
|
||||||
'firewall': {
|
|
||||||
'port_rules': {
|
|
||||||
'8096/tcp': atomic(metadata.get('jellyfin/restrict-to', set())),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
actions['modprobe_jool'] = {
|
|
||||||
'command': 'modprobe jool',
|
|
||||||
'unless': 'lsmod | grep -F jool',
|
|
||||||
}
|
|
||||||
|
|
||||||
actions['jool_add_nat64_instance'] = {
|
|
||||||
'command': 'jool instance add "nat64" --netfilter --pool6 64:ff9b::/96',
|
|
||||||
'unless': 'jool instance display --no-headers --csv | grep -E ",nat64,netfilter$"',
|
|
||||||
'needs': {
|
|
||||||
'action:modprobe_jool',
|
|
||||||
'pkg_apt:jool-dkms',
|
|
||||||
'pkg_apt:jool-tools',
|
|
||||||
'pkg_apt:linux-headers-amd64',
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
defaults = {
|
|
||||||
'apt': {
|
|
||||||
'packages': {
|
|
||||||
'jool-dkms': {},
|
|
||||||
'jool-tools': {},
|
|
||||||
'linux-headers-amd64': {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'modules': {
|
|
||||||
'jool': [
|
|
||||||
'jool',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
[Unit]
|
|
||||||
Description=jugendhackt_tools web service
|
|
||||||
After=network.target
|
|
||||||
Requires=postgresql.service
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
User=jugendhackt_tools
|
|
||||||
Group=jugendhackt_tools
|
|
||||||
Environment=CONFIG_PATH=/opt/jugendhackt_tools/config.toml
|
|
||||||
WorkingDirectory=/opt/jugendhackt_tools/src
|
|
||||||
ExecStart=/opt/jugendhackt_tools/venv/bin/gunicorn jugendhackt_tools.wsgi --name jugendhackt_tools --workers 4 --max-requests 1200 --max-requests-jitter 50 --log-level=info --bind=127.0.0.1:22090
|
|
||||||
Restart=always
|
|
||||||
RestartSec=5
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
|
@ -1,75 +0,0 @@
|
||||||
directories['/opt/jugendhackt_tools/src'] = {}
|
|
||||||
|
|
||||||
git_deploy['/opt/jugendhackt_tools/src'] = {
|
|
||||||
'repo': 'https://github.com/kunsi/jugendhackt_schedule.git',
|
|
||||||
'rev': 'main',
|
|
||||||
'triggers': {
|
|
||||||
'action:jugendhackt_tools_install',
|
|
||||||
'action:jugendhackt_tools_migrate',
|
|
||||||
'svc_systemd:jugendhackt_tools:restart',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
actions['jugendhackt_tools_create_virtualenv'] = {
|
|
||||||
'command': '/usr/bin/python3 -m virtualenv -p python3 /opt/jugendhackt_tools/venv/',
|
|
||||||
'unless': 'test -d /opt/jugendhackt_tools/venv/',
|
|
||||||
'needs': {
|
|
||||||
# actually /opt/jugendhackt_tools, but we don't create that
|
|
||||||
'directory:/opt/jugendhackt_tools/src',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
actions['jugendhackt_tools_install'] = {
|
|
||||||
'command': ' && '.join([
|
|
||||||
'cd /opt/jugendhackt_tools/src',
|
|
||||||
'/opt/jugendhackt_tools/venv/bin/pip install --upgrade pip wheel gunicorn psycopg2-binary',
|
|
||||||
'/opt/jugendhackt_tools/venv/bin/pip install --upgrade -r requirements.txt',
|
|
||||||
]),
|
|
||||||
'needs': {
|
|
||||||
'action:jugendhackt_tools_create_virtualenv',
|
|
||||||
},
|
|
||||||
'triggered': True,
|
|
||||||
}
|
|
||||||
|
|
||||||
actions['jugendhackt_tools_migrate'] = {
|
|
||||||
'command': ' && '.join([
|
|
||||||
'cd /opt/jugendhackt_tools/src',
|
|
||||||
'CONFIG_PATH=/opt/jugendhackt_tools/config.toml /opt/jugendhackt_tools/venv/bin/python manage.py migrate',
|
|
||||||
'CONFIG_PATH=/opt/jugendhackt_tools/config.toml /opt/jugendhackt_tools/venv/bin/python manage.py collectstatic --noinput',
|
|
||||||
]),
|
|
||||||
'needs': {
|
|
||||||
'action:jugendhackt_tools_install',
|
|
||||||
'file:/opt/jugendhackt_tools/config.toml',
|
|
||||||
'postgres_db:jugendhackt_tools',
|
|
||||||
'postgres_role:jugendhackt_tools',
|
|
||||||
},
|
|
||||||
'triggered': True,
|
|
||||||
}
|
|
||||||
|
|
||||||
files['/opt/jugendhackt_tools/config.toml'] = {
|
|
||||||
'content': repo.libs.faults.dict_as_toml(node.metadata.get('jugendhackt_tools')),
|
|
||||||
'triggers': {
|
|
||||||
'svc_systemd:jugendhackt_tools:restart',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
files['/usr/local/lib/systemd/system/jugendhackt_tools.service'] = {
|
|
||||||
'triggers': {
|
|
||||||
'action:systemd-reload',
|
|
||||||
'svc_systemd:jugendhackt_tools:restart',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
svc_systemd['jugendhackt_tools'] = {
|
|
||||||
'needs': {
|
|
||||||
'action:jugendhackt_tools_migrate',
|
|
||||||
'file:/opt/jugendhackt_tools/config.toml',
|
|
||||||
'file:/usr/local/lib/systemd/system/jugendhackt_tools.service',
|
|
||||||
'git_deploy:/opt/jugendhackt_tools/src',
|
|
||||||
'user:jugendhackt_tools',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
users['jugendhackt_tools'] = {
|
|
||||||
'home': '/opt/jugendhackt_tools/src',
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
defaults = {
|
|
||||||
'jugendhackt_tools': {
|
|
||||||
'django_secret': repo.vault.random_bytes_as_base64_for(f'{node.name} jugendhackt_tools django_secret'),
|
|
||||||
'django_debug': False,
|
|
||||||
'static_root': '/opt/jugendhackt_tools/src/static/',
|
|
||||||
'database': {
|
|
||||||
'ENGINE': 'django.db.backends.postgresql',
|
|
||||||
'NAME': 'jugendhackt_tools',
|
|
||||||
'USER': 'jugendhackt_tools',
|
|
||||||
'PASSWORD': repo.vault.password_for(f'{node.name} postgresql jugendhackt_tools'),
|
|
||||||
'HOST': 'localhost',
|
|
||||||
'PORT': '5432'
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'postgresql': {
|
|
||||||
'roles': {
|
|
||||||
'jugendhackt_tools': {
|
|
||||||
'password': repo.vault.password_for(f'{node.name} postgresql jugendhackt_tools'),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'databases': {
|
|
||||||
'jugendhackt_tools': {
|
|
||||||
'owner': 'jugendhackt_tools',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,37 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
from csv import DictReader
|
|
||||||
from datetime import datetime, timezone
|
|
||||||
from os import scandir
|
|
||||||
from os.path import join
|
|
||||||
|
|
||||||
|
|
||||||
def parse():
|
|
||||||
NOW = datetime.now()
|
|
||||||
active_leases = {}
|
|
||||||
for file in scandir("/var/lib/kea/"):
|
|
||||||
with open(file.path) as f:
|
|
||||||
for row in DictReader(f):
|
|
||||||
expires = datetime.fromtimestamp(int(row["expire"]))
|
|
||||||
|
|
||||||
if expires >= NOW:
|
|
||||||
if (
|
|
||||||
row["address"] not in active_leases
|
|
||||||
or active_leases[row["address"]]["expires_dt"] < expires
|
|
||||||
):
|
|
||||||
row["expires_dt"] = expires
|
|
||||||
active_leases[row["address"]] = row
|
|
||||||
return active_leases.values()
|
|
||||||
|
|
||||||
|
|
||||||
def print_table(leases):
|
|
||||||
print(""" address | MAC | expires | hostname
|
|
||||||
-----------------+-------------------+---------+----------""")
|
|
||||||
for lease in sorted(leases, key=lambda r: r["address"]):
|
|
||||||
print(
|
|
||||||
f' {lease["address"]:<15} | {lease["hwaddr"].lower()} | {lease["expires_dt"]:%H:%M} | {lease["hostname"]}'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
print_table(parse())
|
|
|
@ -1,56 +0,0 @@
|
||||||
kea_config = {
|
|
||||||
'Dhcp4': {
|
|
||||||
**node.metadata.get('kea-dhcp-server/config'),
|
|
||||||
'interfaces-config': {
|
|
||||||
'interfaces': sorted(node.metadata.get('kea-dhcp-server/subnets', {}).keys()),
|
|
||||||
},
|
|
||||||
'subnet4': [],
|
|
||||||
'loggers': [{
|
|
||||||
'name': 'kea-dhcp4',
|
|
||||||
'output_options': [{
|
|
||||||
# -> journal
|
|
||||||
'output': 'stdout',
|
|
||||||
}],
|
|
||||||
'severity': 'WARN',
|
|
||||||
}],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for iface, config in sorted(node.metadata.get('kea-dhcp-server/subnets', {}).items()):
|
|
||||||
kea_config['Dhcp4']['subnet4'].append({
|
|
||||||
'subnet': config['subnet'],
|
|
||||||
'pools': [{
|
|
||||||
'pool': f'{config["lower"]} - {config["higher"]}',
|
|
||||||
}],
|
|
||||||
'option-data': [
|
|
||||||
{
|
|
||||||
'name': k,
|
|
||||||
'data': v,
|
|
||||||
} for k, v in sorted(config.get('options', {}).items())
|
|
||||||
],
|
|
||||||
'reservations': [
|
|
||||||
{
|
|
||||||
'ip-address': v['ip'],
|
|
||||||
'hw-address': v['mac'],
|
|
||||||
'hostname': k,
|
|
||||||
} for k, v in sorted(node.metadata.get(f'kea-dhcp-server/fixed_allocations/{iface}', {}).items())
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
files['/etc/kea/kea-dhcp4.conf'] = {
|
|
||||||
'content': repo.libs.faults.dict_as_json(kea_config),
|
|
||||||
'triggers': {
|
|
||||||
'svc_systemd:kea-dhcp4-server:restart',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
files['/usr/local/bin/kea-lease-list'] = {
|
|
||||||
'mode': '0500',
|
|
||||||
}
|
|
||||||
|
|
||||||
svc_systemd['kea-dhcp4-server'] = {
|
|
||||||
'needs': {
|
|
||||||
'file:/etc/kea/kea-dhcp4.conf',
|
|
||||||
'pkg_apt:kea-dhcp4-server',
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -1,83 +0,0 @@
|
||||||
from ipaddress import ip_address, ip_network
|
|
||||||
|
|
||||||
defaults = {
|
|
||||||
'apt': {
|
|
||||||
'packages': {
|
|
||||||
'kea-dhcp4-server': {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'kea-dhcp-server': {
|
|
||||||
'config': {
|
|
||||||
'authoritative': True,
|
|
||||||
'rebind-timer': 450,
|
|
||||||
'renew-timer': 300,
|
|
||||||
'valid-lifetime': 600,
|
|
||||||
'expired-leases-processing': {
|
|
||||||
'max-reclaim-leases': 0,
|
|
||||||
'max-reclaim-time': 0,
|
|
||||||
},
|
|
||||||
'lease-database': {
|
|
||||||
'lfc-interval': 3600,
|
|
||||||
'name': '/var/lib/kea/kea-leases4.csv',
|
|
||||||
'persist': True,
|
|
||||||
'type': 'memfile',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@metadata_reactor.provides(
|
|
||||||
'kea-dhcp-server/fixed_allocations',
|
|
||||||
)
|
|
||||||
def get_static_allocations(metadata):
|
|
||||||
result = {}
|
|
||||||
mapping = {}
|
|
||||||
|
|
||||||
for iface, config in metadata.get('kea-dhcp-server/subnets', {}).items():
|
|
||||||
result[iface] = {}
|
|
||||||
mapping[iface] = ip_network(config['subnet'])
|
|
||||||
|
|
||||||
for rnode in repo.nodes:
|
|
||||||
if (
|
|
||||||
rnode.metadata.get('location', '') != metadata.get('location', '')
|
|
||||||
or rnode == node
|
|
||||||
):
|
|
||||||
continue
|
|
||||||
|
|
||||||
for iface_name, iface_config in rnode.metadata.get('interfaces', {}).items():
|
|
||||||
if iface_config.get('dhcp', False) and iface_config.get('mac'):
|
|
||||||
for ip in iface_config.get('ips', set()):
|
|
||||||
ipaddr = ip_address(ip)
|
|
||||||
|
|
||||||
for kea_iface, kea_subnet in mapping.items():
|
|
||||||
if ipaddr in kea_subnet:
|
|
||||||
result[kea_iface][f'{rnode.name}_{iface_name}'] = {
|
|
||||||
'ip': ip,
|
|
||||||
'mac': iface_config['mac'],
|
|
||||||
}
|
|
||||||
break
|
|
||||||
|
|
||||||
return {
|
|
||||||
'kea-dhcp-server': {
|
|
||||||
'fixed_allocations': result,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@metadata_reactor.provides(
|
|
||||||
'nftables/input/10-kea-dhcp-server',
|
|
||||||
)
|
|
||||||
def nftables(metadata):
|
|
||||||
rules = set()
|
|
||||||
for iface in node.metadata.get('kea-dhcp-server/subnets', {}):
|
|
||||||
rules.add(f'udp dport {{ 67, 68 }} iifname {iface} accept')
|
|
||||||
|
|
||||||
return {
|
|
||||||
'nftables': {
|
|
||||||
'input': {
|
|
||||||
# can't use port_rules here, because we're generating interface based rules.
|
|
||||||
'10-kea-dhcp-server': sorted(rules),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
# This file is managed using bundlewrap
|
|
||||||
% for identifier, modules in sorted(node.metadata.get('modules', {}).items()):
|
|
||||||
|
|
||||||
# ${identifier}
|
|
||||||
% for module in modules:
|
|
||||||
${module}
|
|
||||||
% endfor
|
|
||||||
% endfor
|
|
|
@ -1,3 +0,0 @@
|
||||||
files['/etc/modules'] = {
|
|
||||||
'content_type': 'mako',
|
|
||||||
}
|
|
|
@ -43,15 +43,15 @@ defaults = {
|
||||||
|
|
||||||
|
|
||||||
@metadata_reactor.provides(
|
@metadata_reactor.provides(
|
||||||
'firewall/port_rules',
|
'firewall/port_rules/8080',
|
||||||
'firewall/port_rules',
|
'firewall/port_rules/9090',
|
||||||
)
|
)
|
||||||
def firewall(metadata):
|
def firewall(metadata):
|
||||||
return {
|
return {
|
||||||
'firewall': {
|
'firewall': {
|
||||||
'port_rules': {
|
'port_rules': {
|
||||||
'8080/tcp': atomic(metadata.get('kodi/restrict-to', {'*'})),
|
'8080': atomic(metadata.get('kodi/restrict-to', {'*'})),
|
||||||
'9090/tcp': atomic(metadata.get('kodi/restrict-to', {'*'})),
|
'9090': atomic(metadata.get('kodi/restrict-to', {'*'})),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,6 @@ def cron(metadata):
|
||||||
'/usr/bin/dehydrated --cleanup',
|
'/usr/bin/dehydrated --cleanup',
|
||||||
],
|
],
|
||||||
'when': '04:{}:00'.format(node.magic_number % 60),
|
'when': '04:{}:00'.format(node.magic_number % 60),
|
||||||
'exclude_from_monitoring': True,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
if node.os != 'routeros':
|
directories = {
|
||||||
directories = {
|
|
||||||
'/etc/lldpd.d': {
|
'/etc/lldpd.d': {
|
||||||
'purge': True,
|
'purge': True,
|
||||||
'triggers': {
|
'triggers': {
|
||||||
'svc_systemd:lldpd:restart',
|
'svc_systemd:lldpd:restart',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
files = {
|
files = {
|
||||||
'/etc/lldpd.conf': {
|
'/etc/lldpd.conf': {
|
||||||
'delete': True,
|
'delete': True,
|
||||||
},
|
},
|
||||||
|
@ -18,12 +17,12 @@ if node.os != 'routeros':
|
||||||
'svc_systemd:lldpd:restart',
|
'svc_systemd:lldpd:restart',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
svc_systemd = {
|
svc_systemd = {
|
||||||
'lldpd': {
|
'lldpd': {
|
||||||
'needs': {
|
'needs': {
|
||||||
'file:/etc/lldpd.d/bundlewrap.conf',
|
'file:/etc/lldpd.d/bundlewrap.conf',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue