aboutsummaryrefslogtreecommitdiffstats
path: root/roles/host
diff options
context:
space:
mode:
Diffstat (limited to 'roles/host')
-rw-r--r--roles/host/files/root_bashrc7
-rw-r--r--roles/host/handlers/main.yml9
-rw-r--r--roles/host/tasks/main.yml217
-rw-r--r--roles/host/tasks/userland.yml59
-rw-r--r--roles/host/templates/devfs.rules.j269
-rw-r--r--roles/host/templates/etc_crontab.j232
-rw-r--r--roles/host/templates/etc_periodic.conf.j24
-rw-r--r--roles/host/templates/etc_rc.conf.j250
-rw-r--r--roles/host/templates/etc_resolv.conf.j22
-rw-r--r--roles/host/templates/etc_ssh_sshd_config.j2121
-rw-r--r--roles/host/templates/jail.conf.j21
-rw-r--r--roles/host/templates/usr_local_bin_logto.sh.j247
-rw-r--r--roles/host/templates/usr_local_bin_pylogsentinel-batch-email.sh.j214
-rw-r--r--roles/host/templates/usr_local_etc_pylogsentinel.conf.j222
-rw-r--r--roles/host/templates/usr_local_etc_rc.d_zpool_gpt_labels.j233
-rw-r--r--roles/host/templates/usr_local_etc_smartd.conf.j24
16 files changed, 691 insertions, 0 deletions
diff --git a/roles/host/files/root_bashrc b/roles/host/files/root_bashrc
new file mode 100644
index 0000000..f580db6
--- /dev/null
+++ b/roles/host/files/root_bashrc
@@ -0,0 +1,7 @@
+HISTCONTROL=ignoredups:erasedups # no duplicate entries
+HISTSIZE=100000 # big big history
+HISTFILESIZE=100000 # big big history
+shopt -s histappend # append to history, don't overwrite it
+
+# Save and reload the history after each command finishes
+PROMPT_COMMAND="history -a; history -c; history -r; $PROMPT_COMMAND"
diff --git a/roles/host/handlers/main.yml b/roles/host/handlers/main.yml
new file mode 100644
index 0000000..5d0a990
--- /dev/null
+++ b/roles/host/handlers/main.yml
@@ -0,0 +1,9 @@
+- name: Restart sshd
+ service:
+ name: sshd
+ state: restarted
+
+- name: Restart cron
+ service:
+ name: cron
+ state: restarted
diff --git a/roles/host/tasks/main.yml b/roles/host/tasks/main.yml
new file mode 100644
index 0000000..ed9b476
--- /dev/null
+++ b/roles/host/tasks/main.yml
@@ -0,0 +1,217 @@
+- name: Disable resolvconf
+ copy:
+ content: "resolvconf=NO\n"
+ dest: /etc/resolvconf.conf
+ owner: root
+ group: wheel
+ mode: "0644"
+
+- name: Set up resolv.conf
+ template:
+ src: etc_resolv.conf.j2
+ dest: /etc/resolv.conf
+ owner: root
+ group: wheel
+ mode: "0644"
+
+- name: Install packages
+ package:
+ name: "{{ item }}"
+ state: present
+ loop:
+ - rsync
+ - dma
+ - jq
+ - curl
+ - bash
+ - python
+ - py311-pip
+ - fastfetch
+
+- name: Set up sshd
+ template:
+ src: etc_ssh_sshd_config.j2
+ dest: /etc/ssh/sshd_config
+ owner: root
+ group: wheel
+ mode: "0644"
+ notify: Restart sshd
+
+- name: Start sshd
+ service:
+ name: sshd
+ state: started
+
+- name: Deploy logto script
+ template:
+ src: usr_local_bin_logto.sh.j2
+ dest: /usr/local/bin/logto
+ owner: root
+ group: wheel
+ mode: "0755"
+
+- name: Check if pylogsentinel is installed
+ shell: pip show pylogsentinel
+ register: pylogsentinel_check
+ failed_when: false
+ changed_when: false
+
+- name: Install pylogsentinel
+ shell: pip install pylogsentinel==0.3.0 --force --no-input
+ when: pylogsentinel_check.rc != 0
+
+- name: Deploy pylogsentinel.conf
+ template:
+ src: usr_local_etc_pylogsentinel.conf.j2
+ dest: /usr/local/etc/pylogsentinel.conf
+ owner: root
+ group: wheel
+ mode: "0644"
+
+- name: Deploy pylogsentinel-batch-email.sh
+ template:
+ src: usr_local_bin_pylogsentinel-batch-email.sh.j2
+ dest: /usr/local/bin/pylogsentinel-batch-email.sh
+ owner: root
+ group: wheel
+ mode: "0755"
+
+- name: Start syslogd
+ service:
+ name: syslogd
+ state: started
+
+- name: Start auditd
+ service:
+ name: auditd
+ state: started
+
+- name: Set up periodic.conf
+ template:
+ src: etc_periodic.conf.j2
+ dest: /etc/periodic.conf
+ owner: root
+ group: wheel
+ mode: "0644"
+
+- name: Set up crontab
+ template:
+ src: etc_crontab.j2
+ dest: /etc/crontab
+ owner: root
+ group: wheel
+ mode: "0644"
+ notify: Restart cron
+
+- name: Install .bashrc
+ copy:
+ src: root_bashrc
+ dest: /root/.bashrc
+ owner: root
+ group: wheel
+ mode: "0644"
+
+# Jail infrastructure
+- name: Discover jail directories
+ find:
+ paths: "{{ playbook_dir }}/roles/jails"
+ patterns: "main.yml"
+ recurse: true
+ delegate_to: localhost
+ register: _jail_specs
+
+- name: Load jail definitions
+ set_fact:
+ jail_defs: "{{ jail_defs | default([]) + [_content | combine({'num': _num, 'name': _name})] }}"
+ vars:
+ _content: "{{ lookup('file', item.path) | from_yaml }}"
+ _num: "{{ item.path | regex_replace('.*/jails/([^/]+)/.*', '\\1') | split('_') | first | int }}"
+ _name: "{{ item.path | regex_replace('.*/jails/([^/]+)/.*', '\\1') | regex_replace('^[0-9]+_', '') }}"
+ loop: "{{ _jail_specs.files | sort(attribute='path') }}"
+ loop_control:
+ label: "{{ item.path | regex_replace('.*/jails/([^/]+)/.*', '\\1') }}"
+ when: "'/defaults/' in item.path"
+
+- name: Collect unique userlands
+ set_fact:
+ jail_userlands: "{{ jail_defs | map(attribute='userland') | unique | list }}"
+
+- name: Create base ZFS datasets
+ community.general.zfs:
+ name: "{{ item.name }}"
+ state: present
+ extra_zfs_properties:
+ mountpoint: "{{ item.mountpoint | default(omit) }}"
+ loop:
+ - { name: "zroot/jails", mountpoint: "/usr/local/jails" }
+ - { name: "zroot/jails/media" }
+ - { name: "zroot/jails/templates" }
+ - { name: "zroot/jails/containers" }
+ - { name: "zroot/jails/volumes", mountpoint: "none" }
+ - { name: "zroot/jails/volumes/goaccess_www", mountpoint: "/usr/local/jails/volumes/goaccess_www" }
+ - { name: "zroot/jails/volumes/postgres_data", mountpoint: "/usr/local/jails/containers/postgres/var/db/postgres" }
+ - { name: "zroot/jails/volumes/irc_thelounge_logs", mountpoint: "/usr/local/jails/containers/irc_thelounge/root/.thelounge/logs" }
+ - { name: "zroot/jails/volumes/irc_thelounge_uploads", mountpoint: "/usr/local/jails/containers/irc_thelounge/root/.thelounge/uploads" }
+ - { name: "zroot/jails/volumes/komga_data", mountpoint: "/usr/local/jails/containers/komga/root/.komga" }
+ - { name: "zroot/storage", mountpoint: "/usr/local/jails/volumes/storage" }
+ loop_control:
+ label: "{{ item.name }}"
+
+- name: Create storage group
+ group:
+ name: storage
+ gid: 1001
+
+- name: Create storage user
+ user:
+ name: storage
+ uid: 1001
+ group: storage
+ home: /nonexistent
+ shell: /usr/sbin/nologin
+ create_home: false
+
+- name: Set storage volume permissions
+ file:
+ path: /usr/local/jails/volumes/storage
+ state: directory
+ owner: "1001"
+ group: "1001"
+ mode: "0777"
+
+- name: Create storage directories
+ file:
+ path: "/usr/local/jails/volumes/storage/{{ item.name }}"
+ state: directory
+ owner: "1001"
+ group: "1001"
+ mode: "{{ item.mode }}"
+ loop:
+ - { name: media, mode: "0777" }
+ - { name: docs, mode: "0775" }
+ - { name: downloads, mode: "0777" }
+ - { name: projects-ableton, mode: "0755" }
+ - { name: vault, mode: "0755" }
+ - { name: jan-systems-2025-content, mode: "0755" }
+
+- name: Set up userland templates
+ include_tasks: userland.yml
+ loop: "{{ jail_userlands }}"
+ loop_control:
+ loop_var: userland
+
+- name: Deploy /etc/jail.conf
+ template:
+ src: jail.conf.j2
+ dest: /etc/jail.conf
+ owner: root
+ group: wheel
+ mode: "0644"
+
+- name: Deploy devfs.rules
+ template:
+ src: devfs.rules.j2
+ dest: /etc/devfs.rules
+ owner: root
+ group: wheel
+ mode: "0644"
diff --git a/roles/host/tasks/userland.yml b/roles/host/tasks/userland.yml
new file mode 100644
index 0000000..59de0dd
--- /dev/null
+++ b/roles/host/tasks/userland.yml
@@ -0,0 +1,59 @@
+- name: "Create template dataset for {{ userland }}"
+ community.general.zfs:
+ name: "zroot/jails/templates/{{ userland }}"
+ state: present
+
+- name: "Check if {{ userland }} snapshot exists"
+ shell: zfs list -t snapshot -o name | grep -Fxq "zroot/jails/templates/{{ userland }}@base"
+ failed_when: false
+ changed_when: false
+ register: userland_snap
+
+- name: "Set up {{ userland }} template"
+ when: userland_snap.rc != 0
+ block:
+ - name: Download userland
+ get_url:
+ url: "https://download.freebsd.org/ftp/releases/{{ arch }}/{{ userland }}/base.txz"
+ dest: "/usr/local/jails/media/{{ userland }}-base.txz"
+ owner: root
+ group: wheel
+ mode: "0644"
+
+ - name: Unarchive userland
+ shell: "chflags -R noschg /usr/local/jails/templates/{{ userland }} 2>/dev/null; tar -xzf /usr/local/jails/media/{{ userland }}-base.txz -C /usr/local/jails/templates/{{ userland }}"
+
+ - name: Copy localtime
+ copy:
+ remote_src: true
+ src: /etc/localtime
+ dest: "/usr/local/jails/templates/{{ userland }}/etc/localtime"
+
+ - name: Deploy resolv.conf
+ template:
+ src: etc_resolv.conf.j2
+ dest: "/usr/local/jails/templates/{{ userland }}/etc/resolv.conf"
+ owner: root
+ group: wheel
+ mode: "0644"
+
+ - name: Disable resolvconf
+ lineinfile:
+ path: "/usr/local/jails/templates/{{ userland }}/etc/resolvconf.conf"
+ line: "resolvconf=NO"
+ create: true
+
+ - name: Update userland
+ shell: "freebsd-update -b /usr/local/jails/templates/{{ userland }}/ fetch install"
+
+ - name: Disable syslogd
+ lineinfile:
+ path: "/usr/local/jails/templates/{{ userland }}/etc/rc.conf"
+ line: 'syslogd_enable="NO"'
+ create: true
+
+ - name: Install packages in template
+ shell: "ASSUME_ALWAYS_YES=yes pkg -c /usr/local/jails/templates/{{ userland }} install -y python3 bash"
+
+ - name: Create snapshot
+ shell: "zfs snapshot zroot/jails/templates/{{ userland }}@base"
diff --git a/roles/host/templates/devfs.rules.j2 b/roles/host/templates/devfs.rules.j2
new file mode 100644
index 0000000..9fc0bd1
--- /dev/null
+++ b/roles/host/templates/devfs.rules.j2
@@ -0,0 +1,69 @@
+[devfsrules_hide_all=1]
+add hide
+
+[devfsrules_unhide_basic=2]
+add path null unhide
+add path zero unhide
+add path crypto unhide
+add path random unhide
+add path urandom unhide
+
+[devfsrules_unhide_login=3]
+add path 'ptyp*' unhide
+add path 'ptyq*' unhide
+add path 'ptyr*' unhide
+add path 'ptys*' unhide
+add path 'ptyP*' unhide
+add path 'ptyQ*' unhide
+add path 'ptyR*' unhide
+add path 'ptyS*' unhide
+add path 'ptyl*' unhide
+add path 'ptym*' unhide
+add path 'ptyn*' unhide
+add path 'ptyo*' unhide
+add path 'ptyL*' unhide
+add path 'ptyM*' unhide
+add path 'ptyN*' unhide
+add path 'ptyO*' unhide
+add path 'ttyp*' unhide
+add path 'ttyq*' unhide
+add path 'ttyr*' unhide
+add path 'ttys*' unhide
+add path 'ttyP*' unhide
+add path 'ttyQ*' unhide
+add path 'ttyR*' unhide
+add path 'ttyS*' unhide
+add path 'ttyl*' unhide
+add path 'ttym*' unhide
+add path 'ttyn*' unhide
+add path 'ttyo*' unhide
+add path 'ttyL*' unhide
+add path 'ttyM*' unhide
+add path 'ttyN*' unhide
+add path 'ttyO*' unhide
+add path ptmx unhide
+add path pts unhide
+add path 'pts/*' unhide
+add path fd unhide
+add path 'fd/*' unhide
+add path stdin unhide
+add path stdout unhide
+add path stderr unhide
+add path 'drm' unhide
+add path 'drm/*' unhide
+add path 'dri' unhide
+add path 'dri/*' unhide
+
+[devfsrules_jail=4]
+add include $devfsrules_hide_all
+add include $devfsrules_unhide_basic
+add include $devfsrules_unhide_login
+
+[devfsrules_jail_postgres=5]
+add include $devfsrules_jail
+add path 'bpf*' unhide
+
+[devfsrules_jail_ingress=6]
+add include $devfsrules_jail
+add path 'bpf*' unhide
+add path 'pf*' unhide
diff --git a/roles/host/templates/etc_crontab.j2 b/roles/host/templates/etc_crontab.j2
new file mode 100644
index 0000000..de1f3c6
--- /dev/null
+++ b/roles/host/templates/etc_crontab.j2
@@ -0,0 +1,32 @@
+# /etc/crontab - root's crontab for FreeBSD
+#
+#
+SHELL=/bin/sh
+PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin
+#
+#minute hour mday month wday who command
+#
+# Save some entropy so that /dev/random can re-seed on boot.
+*/11 * * * * operator /usr/libexec/save-entropy
+#
+# Rotate log files every hour, if necessary.
+0 * * * * root newsyslog
+#
+# Perform daily/weekly/monthly maintenance.
+1 3 * * * root periodic daily
+15 4 * * 6 root periodic weekly
+30 5 1 * * root periodic monthly
+#
+# Adjust the time zone if the CMOS clock keeps local time, as opposed to
+# UTC time. See adjkerntz(8) for details.
+1,31 0-5 * * * root adjkerntz -a
+
+# Take snapshots every day at 3:05 AM
+5 3 * * * root logto /var/log/backup /usr/local/bin/backup snapshot prune-local
+# Send backup snapshots to remote every third day at 4:05 AM
+5 4 */3 * * root logto /var/log/backup /usr/local/bin/backup send-to-remote prune-remote notify
+
+# Run pylogmonitor
+*/10 * * * * root python -m pylogsentinel
+# Run pylogsentinel batch job once a day
+5 6 * * * root /usr/local/bin/pylogsentinel-batch-email.sh
diff --git a/roles/host/templates/etc_periodic.conf.j2 b/roles/host/templates/etc_periodic.conf.j2
new file mode 100644
index 0000000..176b55d
--- /dev/null
+++ b/roles/host/templates/etc_periodic.conf.j2
@@ -0,0 +1,4 @@
+daily_status_smart_enable="YES"
+daily_status_security_inline="YES"
+daily_status_zfs_enable="YES"
+weekly_certbot_enable="YES"
diff --git a/roles/host/templates/etc_rc.conf.j2 b/roles/host/templates/etc_rc.conf.j2
new file mode 100644
index 0000000..7cdf65e
--- /dev/null
+++ b/roles/host/templates/etc_rc.conf.j2
@@ -0,0 +1,50 @@
+hostname="pursotin"
+keymap="fi.kbd"
+sshd_enable="YES"
+ntpd_enable="YES"
+ntpd_sync_on_start="YES"
+powerd_enable="YES"
+moused_nondefault_enable="NO"
+dumpdev="AUTO"
+zfs_enable="YES"
+defaultrouter="{{ lan_ipv4_gateway }}"
+cloned_interfaces="bridge0 bridge1"
+{% if is_prod %}
+ifconfig_{{ nic_lan }}_name="lan0"
+ifconfig_{{ nic_wan }}_name="wan0"
+ifconfig_bridge0_name="brlan0"
+ifconfig_bridge1_name="brwan0"
+ifconfig_lan0="inet {{ lan_ipv4_cidr }}"
+ifconfig_lan0_ipv6="inet6 accept_rtadv"
+ifconfig_wan0="up"
+ifconfig_brlan0="addm lan0 up"
+ifconfig_brwan0="addm wan0 up"
+{% else %}
+ifconfig_bridge0_name="brlan0"
+ifconfig_bridge1_name="brwan0"
+ifconfig_vtnet0="inet {{ lan_ipv4_cidr }}"
+ifconfig_vtnet0_ipv6="inet6 accept_rtadv"
+ifconfig_brlan0="addm vtnet0 up"
+ifconfig_brwan0="up"
+{% endif %}
+zpool_gpt_labels_enable="YES"
+smartd_enable="YES"
+pf_enable="YES"
+syslogd_enable="YES"
+syslogd_flags="-s"
+jail_enable="YES"
+jail_parallel_start="YES"
+jail_reverse_stop="YES"
+clear_tmp_enable="YES"
+cleanvar_enable="YES"
+auditd_enable="YES"
+devd_enable="YES"
+{% if is_prod %}
+vm_enable="YES"
+vm_dir="zfs:zroot/vm"
+vm_list="alpine0"
+vm_delay="5"
+kld_list="pf if_wg i915kms"
+{% else %}
+kld_list="pf"
+{% endif %}
diff --git a/roles/host/templates/etc_resolv.conf.j2 b/roles/host/templates/etc_resolv.conf.j2
new file mode 100644
index 0000000..b382ecf
--- /dev/null
+++ b/roles/host/templates/etc_resolv.conf.j2
@@ -0,0 +1,2 @@
+search {{ lan_search_domain }}
+nameserver {{ dns_nameserver | default(lan_ipv4_gateway) }}
diff --git a/roles/host/templates/etc_ssh_sshd_config.j2 b/roles/host/templates/etc_ssh_sshd_config.j2
new file mode 100644
index 0000000..06179ce
--- /dev/null
+++ b/roles/host/templates/etc_ssh_sshd_config.j2
@@ -0,0 +1,121 @@
+# $OpenBSD: sshd_config,v 1.104 2021/07/02 05:11:21 dtucker Exp $
+
+# This is the sshd server system-wide configuration file. See
+# sshd_config(5) for more information.
+
+# This sshd was compiled with PATH=/usr/bin:/bin:/usr/sbin:/sbin
+
+# The strategy used for options in the default sshd_config shipped with
+# OpenSSH is to specify options with their default value where
+# possible, but leave them commented. Uncommented options override the
+# default value.
+
+# Note that some of FreeBSD's defaults differ from OpenBSD's, and
+# FreeBSD has a few additional options.
+
+#Port 22
+#AddressFamily any
+#ListenAddress 0.0.0.0
+#ListenAddress ::
+
+#HostKey /etc/ssh/ssh_host_rsa_key
+#HostKey /etc/ssh/ssh_host_ecdsa_key
+#HostKey /etc/ssh/ssh_host_ed25519_key
+
+# Ciphers and keying
+#RekeyLimit default none
+
+# Logging
+#SyslogFacility AUTH
+#LogLevel INFO
+
+# Authentication:
+
+#LoginGraceTime 2m
+PermitRootLogin prohibit-password
+#StrictModes yes
+#MaxAuthTries 6
+#MaxSessions 10
+
+#PubkeyAuthentication yes
+
+# The default is to check both .ssh/authorized_keys and .ssh/authorized_keys2
+# but this is overridden so installations will only check .ssh/authorized_keys
+AuthorizedKeysFile .ssh/authorized_keys
+
+#AuthorizedPrincipalsFile none
+
+#AuthorizedKeysCommand none
+#AuthorizedKeysCommandUser nobody
+
+# For this to work you will also need host keys in /etc/ssh/ssh_known_hosts
+#HostbasedAuthentication no
+# Change to yes if you don't trust ~/.ssh/known_hosts for
+# HostbasedAuthentication
+#IgnoreUserKnownHosts no
+# Don't read the user's ~/.rhosts and ~/.shosts files
+#IgnoreRhosts yes
+
+# Change to yes to enable built-in password authentication.
+# Note that passwords may also be accepted via KbdInteractiveAuthentication.
+PasswordAuthentication no
+#PermitEmptyPasswords no
+
+# Change to no to disable PAM authentication
+#KbdInteractiveAuthentication yes
+
+# Kerberos options
+#KerberosAuthentication no
+#KerberosOrLocalPasswd yes
+#KerberosTicketCleanup yes
+#KerberosGetAFSToken no
+
+# GSSAPI options
+#GSSAPIAuthentication no
+#GSSAPICleanupCredentials yes
+
+# Set this to 'no' to disable PAM authentication, account processing,
+# and session processing. If this is enabled, PAM authentication will
+# be allowed through the KbdInteractiveAuthentication and
+# PasswordAuthentication. Depending on your PAM configuration,
+# PAM authentication via KbdInteractiveAuthentication may bypass
+# the setting of "PermitRootLogin prohibit-password".
+# If you just want the PAM account and session checks to run without
+# PAM authentication, then enable this but set PasswordAuthentication
+# and KbdInteractiveAuthentication to 'no'.
+#UsePAM yes
+
+#AllowAgentForwarding yes
+#AllowTcpForwarding yes
+#GatewayPorts no
+#X11Forwarding no
+#X11DisplayOffset 10
+#X11UseLocalhost yes
+#PermitTTY yes
+#PrintMotd yes
+#PrintLastLog yes
+#TCPKeepAlive yes
+#PermitUserEnvironment no
+#Compression delayed
+#ClientAliveInterval 0
+#ClientAliveCountMax 3
+#UseDNS yes
+#PidFile /var/run/sshd.pid
+#MaxStartups 10:30:100
+#PermitTunnel no
+#ChrootDirectory none
+#UseBlacklist no
+#VersionAddendum FreeBSD-20250219
+
+# no default banner path
+#Banner none
+
+# override default of no subsystems
+Subsystem sftp /usr/libexec/sftp-server
+
+# Example of overriding settings on a per-user basis
+#Match User anoncvs
+# X11Forwarding no
+# AllowTcpForwarding no
+# PermitTTY no
+# ForceCommand cvs server
diff --git a/roles/host/templates/jail.conf.j2 b/roles/host/templates/jail.conf.j2
new file mode 100644
index 0000000..81bfbad
--- /dev/null
+++ b/roles/host/templates/jail.conf.j2
@@ -0,0 +1 @@
+.include "/etc/jail.conf.d/*.conf";
diff --git a/roles/host/templates/usr_local_bin_logto.sh.j2 b/roles/host/templates/usr_local_bin_logto.sh.j2
new file mode 100644
index 0000000..9bb5aa1
--- /dev/null
+++ b/roles/host/templates/usr_local_bin_logto.sh.j2
@@ -0,0 +1,47 @@
+#!/bin/sh
+
+set -ue
+
+# Wrapper for logging to file and prepending a timestamp.
+# By default writes both stdout and stderr to the log file.
+
+usage() {
+ echo "Usage: logto [-s|-e] <log_file> <command>"
+ echo "Flags:"
+ echo " -s: Write only stdout to the log file."
+ echo " -e: Write only stderr to the log file."
+ echo ""
+ echo "Example usage:"
+ echo " logto /var/log/my.log run some command"
+ exit 1
+}
+
+mode="all"
+
+while getopts "se" opt; do
+ case $opt in
+ s) mode="stdout" ;;
+ e) mode="stderr" ;;
+ *) usage ;;
+ esac
+done
+shift $((OPTIND-1))
+
+if [ $# -lt 2 ]; then
+ usage
+fi
+
+log_file="$1"
+shift
+
+if [ "$mode" = "stdout" ]; then
+ out=$(2>/dev/null $@)
+elif [ "$mode" = "stderr" ]; then
+ out=$(2>&1 >/dev/null $@)
+else
+ out=$(2>&1 $@)
+fi
+
+if [ ! -z "$out" ]; then
+ echo "$(date +"%Y-%m-%dT%H:%M:%S%z")" "$out" >>"$log_file"
+fi
diff --git a/roles/host/templates/usr_local_bin_pylogsentinel-batch-email.sh.j2 b/roles/host/templates/usr_local_bin_pylogsentinel-batch-email.sh.j2
new file mode 100644
index 0000000..d28efd9
--- /dev/null
+++ b/roles/host/templates/usr_local_bin_pylogsentinel-batch-email.sh.j2
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+FILE=/tmp/pylogsentinel.daily
+
+if [ ! -f "$FILE" ]; then
+ echo "Nothing to do"
+ exit 0
+fi
+
+echo "Sending email"
+cat "$FILE" | mail -s "pylogsentinel alert batch" root
+
+echo "Removing batch file"
+rm "$FILE"
diff --git a/roles/host/templates/usr_local_etc_pylogsentinel.conf.j2 b/roles/host/templates/usr_local_etc_pylogsentinel.conf.j2
new file mode 100644
index 0000000..cb80742
--- /dev/null
+++ b/roles/host/templates/usr_local_etc_pylogsentinel.conf.j2
@@ -0,0 +1,22 @@
+[system]
+state_dir = /var/run/pylogsentinel
+max_block_size = 10M
+
+[logs.standard]
+cmd = find / -type f -path '*/var/log/*' ! -name '*access.log' ! -name '*.bz2' ! -name 'dmesg*' ! -name 'syncthing*'
+
+[logs.access]
+paths = /usr/local/jails/containers/ingress/var/log/nginx/access.log
+
+[action.default]
+cmd = echo -e "---------\nMatched $RULE_ID in $FILE at line $LINE, context:\n\n$CONTEXT\n" >> /tmp/pylogsentinel.daily
+
+[rule.error]
+description = Error-like conditions
+pattern = /(error|fatal|exception|killed)/i
+logs = standard
+
+[rule.access]
+description = HTTP code >=500 in access log
+pattern = /HTTP\/[0-9].[0-9]" 5[0-9][0-9]/
+logs = access
diff --git a/roles/host/templates/usr_local_etc_rc.d_zpool_gpt_labels.j2 b/roles/host/templates/usr_local_etc_rc.d_zpool_gpt_labels.j2
new file mode 100644
index 0000000..36bc715
--- /dev/null
+++ b/roles/host/templates/usr_local_etc_rc.d_zpool_gpt_labels.j2
@@ -0,0 +1,33 @@
+#!/bin/sh
+
+# PROVIDE: zpool_gpt_labels
+# REQUIRE: zfs
+# BEFORE: LOGIN
+# KEYWORD: nojail
+
+. /etc/rc.subr
+
+name="zpool_gpt_labels"
+rcvar="zpool_gpt_labels_enable"
+start_cmd="zpool_gpt_labels_start"
+
+zpool_gpt_labels_start() {
+ logger "Setting zpool vdev paths to GPT labels"
+ local zpool_status="$(zpool status)"
+ for i in 0 1 2 3; do
+ local raw="nda${i}p4.eli"
+ local path="/dev/gpt/zfs${i}.eli"
+ case "$zpool_status" in
+ *"$raw"*)
+ zpool set "path=${path}" zroot "${raw}"
+ logger "Setting ${raw} -> ${path}"
+ ;;
+ *)
+ logger "Skipping ${raw}..."
+ ;;
+ esac
+ done
+}
+
+load_rc_config $name
+run_rc_command "$1"
diff --git a/roles/host/templates/usr_local_etc_smartd.conf.j2 b/roles/host/templates/usr_local_etc_smartd.conf.j2
new file mode 100644
index 0000000..bdcbfd0
--- /dev/null
+++ b/roles/host/templates/usr_local_etc_smartd.conf.j2
@@ -0,0 +1,4 @@
+/dev/nvme0 -a -d nvme
+/dev/nvme1 -a -d nvme
+/dev/nvme2 -a -d nvme
+/dev/nvme3 -a -d nvme