diff options
| author | Jan Tuomi <jan@jantuomi.fi> | 2026-05-13 00:13:57 +0300 |
|---|---|---|
| committer | Jan Tuomi <jan@jantuomi.fi> | 2026-05-16 18:42:27 +0300 |
| commit | b5860daf11ac353049cb1654b9414a129e5cfb96 (patch) | |
| tree | 87ed89711e4f0e85ace0a97fa123199152c67302 /templates | |
| parent | 4715a28fdcd87440400d17154bfa361d99db29cc (diff) | |
Rework
Diffstat (limited to 'templates')
49 files changed, 0 insertions, 1783 deletions
diff --git a/templates/diddle/root_diddle_env.j2 b/templates/diddle/root_diddle_env.j2 deleted file mode 100644 index ca7db78..0000000 --- a/templates/diddle/root_diddle_env.j2 +++ /dev/null @@ -1,9 +0,0 @@ -PYTHONUNBUFFERED="1" -BASE_URL="https://diddle.jan.systems" -DB_PATH="db.sqlite3" -EMAIL_HOST="smtp.postmarkapp.com" -EMAIL_PORT="587" -EMAIL_HOST_USER="{{ diddle_email_host_user }}" -EMAIL_HOST_PASSWORD="{{ diddle_email_host_password }}" -EMAIL_USE_TLS="true" -EMAIL_MESSAGE_FROM="diddle@jan.systems" diff --git a/templates/diddle/usr_local_bin_diddle b/templates/diddle/usr_local_bin_diddle deleted file mode 100644 index d07cd5e..0000000 --- a/templates/diddle/usr_local_bin_diddle +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/sh - -set -eux - -PATH=$PATH:/usr/local/bin - -cd /root/diddle - -if [ ! -f venv ]; then - python3.11 -m venv venv -fi - -PATH=$PATH:/root/diddle/venv/bin - -pip install -r requirements.txt -python apply_migrations.py -gunicorn --bind "0.0.0.0:80" -w 4 app:app diff --git a/templates/diddle/usr_local_etc_rc.d_diddle b/templates/diddle/usr_local_etc_rc.d_diddle deleted file mode 100644 index 01deaad..0000000 --- a/templates/diddle/usr_local_etc_rc.d_diddle +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/sh -# -# PROVIDE: diddle -# REQUIRE: NETWORKING -# KEYWORD: shutdown -# -# Enable in /etc/rc.conf: -# diddle_enable="YES" -# -. /etc/rc.subr - -name="diddle" -rcvar=diddle_enable - -load_rc_config $name - -: ${diddle_command:=/usr/local/bin/diddle} -: ${diddle_log:=/var/log/${name}.log} - -start_cmd="${name}_start" -stop_cmd="${name}_stop" -status_cmd="${name}_status" - -extra_commands="status" - -diddle_start() { - /usr/local/bin/logto ${diddle_log} ${diddle_command} & -} - -diddle_status() { - if ps aux | grep diddle | grep -v grep | grep -v rc.d/diddle; then - echo "diddle is running" - else - echo "diddle is not running" - exit 1 - fi -} - -diddle_stop() { - ps aux | grep diddle | grep -v grep | grep -v rc.d/diddle | awk '{print $2}' | xargs kill -TERM -} - -run_rc_command "$1" diff --git a/templates/etc_aliases.j2 b/templates/etc_aliases.j2 deleted file mode 100644 index 4fe3e7f..0000000 --- a/templates/etc_aliases.j2 +++ /dev/null @@ -1 +0,0 @@ -*: {{ dma_to_address }} diff --git a/templates/etc_crontab.j2 b/templates/etc_crontab.j2 deleted file mode 100644 index de1f3c6..0000000 --- a/templates/etc_crontab.j2 +++ /dev/null @@ -1,32 +0,0 @@ -# /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/templates/etc_devfs.rules.j2 b/templates/etc_devfs.rules.j2 deleted file mode 100644 index 531b767..0000000 --- a/templates/etc_devfs.rules.j2 +++ /dev/null @@ -1,91 +0,0 @@ -# "as-is" to the devfs(8) command with the exception that -# any references to other rulesets will be expanded first. These -# references must include a dollar sign '$' in front of the -# name to be expanded properly. -# -# - -# Very basic and secure ruleset: Hide everything. -# Used as a basis for other rules. -# -[devfsrules_hide_all=1] -add hide - -# Basic devices typically necessary. -# Requires: devfsrules_hide_all -# -[devfsrules_unhide_basic=2] -add path null unhide -add path zero unhide -add path crypto unhide -add path random unhide -add path urandom unhide - -# Devices typically needed to support logged-in users. -# Requires: devfsrules_hide_all -# -[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_hide_all -add include $devfsrules_unhide_basic -add include $devfsrules_unhide_login -add include $devfsrules_jail -add path 'bpf*' unhide - -[devfsrules_jail_ingress=5] -add include $devfsrules_hide_all -add include $devfsrules_unhide_basic -add include $devfsrules_unhide_login -add include $devfsrules_jail -add path 'bpf*' unhide -add path 'pf*' unhide diff --git a/templates/etc_dma_auth.conf.j2 b/templates/etc_dma_auth.conf.j2 deleted file mode 100644 index a1000ea..0000000 --- a/templates/etc_dma_auth.conf.j2 +++ /dev/null @@ -1 +0,0 @@ -{{ smtp_user }}|{{ smtp_host }}:{{ smtp_password }} diff --git a/templates/etc_dma_dma.conf.j2 b/templates/etc_dma_dma.conf.j2 deleted file mode 100644 index dfaa04f..0000000 --- a/templates/etc_dma_dma.conf.j2 +++ /dev/null @@ -1,8 +0,0 @@ -SMARTHOST {{ smtp_host }} -PORT {{ smtp_port }} -AUTHPATH /etc/dma/auth.conf -SECURETRANSFER -STARTTLS -MAILNAME {{ dma_mail_hostname }} -MASQUERADE {{ ansible_hostname }}@{{ dma_mail_hostname }} -#NULLCLIENT diff --git a/templates/etc_fstab.j2 b/templates/etc_fstab.j2 deleted file mode 100644 index 95d3fa0..0000000 --- a/templates/etc_fstab.j2 +++ /dev/null @@ -1,7 +0,0 @@ -# Device Mountpoint FStype Options Dump Pass# -/dev/gpt/efiboot0 /boot/efi msdosfs rw 2 2 -/dev/nda0p3.eli none swap sw 0 0 -/dev/nda1p3.eli none swap sw 0 0 -/dev/nda2p3.eli none swap sw 0 0 -/dev/nda3p3.eli none swap sw 0 0 -tmpfs /tmp tmpfs rw,mode=777,size=2g 0 0 diff --git a/templates/etc_jail.conf.d_[jailname].conf.j2 b/templates/etc_jail.conf.d_[jailname].conf.j2 deleted file mode 100644 index df10f47..0000000 --- a/templates/etc_jail.conf.d_[jailname].conf.j2 +++ /dev/null @@ -1,43 +0,0 @@ -# eplXa is host end (local network bridge), eplXb is jail end. -# The corresponding pubnet interface is epwX, but that's not created for all jails. -{{ jail.name }} { - # STARTUP/LOGGING/VNET - vnet; - persist; - exec.clean; - - exec.prestart = ""; - exec.start = "/bin/sh /etc/rc"; - exec.poststart = ""; - exec.prestop = ""; - exec.stop = "/bin/sh /etc/rc.shutdown"; - exec.poststop = ""; - - exec.consolelog = "/var/log/jail_console_${name}.log"; - - # PERMISSIONS - allow.raw_sockets; - exec.clean; - mount.devfs; - - # HOSTNAME/PATH - host.hostname = "${name}"; - path = "/usr/local/jails/containers/${name}"; - - # JAIL-SPECIFIC CONFIGURATION -{% set t = lookup( - 'ansible.builtin.first_found', - { - 'files': [ - 'jail_confs/' ~ jail.name ~ '.j2', - 'jail_confs/_default.j2', - ], - 'paths': [ playbook_dir ~ '/templates' ] - }, - errors='ignore' -) %} - -{% if t %} -{{ lookup('ansible.builtin.template', t) | indent(2, true) }} -{% endif %} -} diff --git a/templates/etc_jail.conf.j2 b/templates/etc_jail.conf.j2 deleted file mode 100644 index 7190816..0000000 --- a/templates/etc_jail.conf.j2 +++ /dev/null @@ -1,6 +0,0 @@ -# Include configurations from standard locations. -.include "/etc/jail.conf.d/*.conf"; -.include "/etc/jail.*.conf"; -.include "/usr/local/etc/jail[.]conf"; -.include "/usr/local/etc/jail.conf.d/*.conf"; -.include "/usr/local/etc/jail.*.conf"; diff --git a/templates/etc_periodic.conf.j2 b/templates/etc_periodic.conf.j2 deleted file mode 100644 index 176b55d..0000000 --- a/templates/etc_periodic.conf.j2 +++ /dev/null @@ -1,4 +0,0 @@ -daily_status_smart_enable="YES" -daily_status_security_inline="YES" -daily_status_zfs_enable="YES" -weekly_certbot_enable="YES" diff --git a/templates/etc_rc.conf.j2 b/templates/etc_rc.conf.j2 deleted file mode 100644 index 5380056..0000000 --- a/templates/etc_rc.conf.j2 +++ /dev/null @@ -1,48 +0,0 @@ -hostname="pursotin" -keymap="fi.kbd" -sshd_enable="YES" -ntpd_enable="YES" -ntpd_sync_on_start="YES" -powerd_enable="YES" -moused_nondefault_enable="NO" -# Set dumpdev to "AUTO" to enable crash dumps, "NO" to disable -dumpdev="AUTO" -zfs_enable="YES" -{% if is_test_vm %} -defaultrouter="10.0.20.1" -{% else %} -defaultrouter="{{ lan_ipv4_gateway }}" -{% endif %} -cloned_interfaces="bridge0 bridge1" -ifconfig_{{ nic_lan }}_name="lan0" -ifconfig_{{ nic_wan }}_name="wan0" -ifconfig_bridge0_name="brlan0" -ifconfig_bridge1_name="brwan0" -{% if is_test_vm %} -ifconfig_lan0="inet 10.0.20.2/24" -{% else %} -ifconfig_lan0="inet {{ lan_ipv4_cidr }}" -{% endif %} -ifconfig_lan0_ipv6="inet6 accept_rtadv" -ifconfig_wan0="up" -ifconfig_brlan0="addm lan0 up" -ifconfig_brwan0="addm wan0 up" -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 not is_test_vm %} -vm_enable="YES" -vm_dir="zfs:zroot/vm" -vm_list="alpine0" -vm_delay="5" -kld_list="if_wg i915kms" -{% endif %} diff --git a/templates/etc_resolv.conf.j2 b/templates/etc_resolv.conf.j2 deleted file mode 100644 index b382ecf..0000000 --- a/templates/etc_resolv.conf.j2 +++ /dev/null @@ -1,2 +0,0 @@ -search {{ lan_search_domain }} -nameserver {{ dns_nameserver | default(lan_ipv4_gateway) }} diff --git a/templates/etc_ssh_sshd_config.j2 b/templates/etc_ssh_sshd_config.j2 deleted file mode 100644 index 06179ce..0000000 --- a/templates/etc_ssh_sshd_config.j2 +++ /dev/null @@ -1,121 +0,0 @@ -# $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/templates/homepage/etc_crontab.j2 b/templates/homepage/etc_crontab.j2 deleted file mode 100644 index 8541f57..0000000 --- a/templates/homepage/etc_crontab.j2 +++ /dev/null @@ -1,9 +0,0 @@ -# /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 - -# TODO: update linklog diff --git a/templates/homepage/usr_local_etc_nginx_nginx.conf.j2 b/templates/homepage/usr_local_etc_nginx_nginx.conf.j2 deleted file mode 100644 index ee45405..0000000 --- a/templates/homepage/usr_local_etc_nginx_nginx.conf.j2 +++ /dev/null @@ -1,55 +0,0 @@ -worker_processes auto; - -events { - worker_connections 1024; -} - -http { - include mime.types; - default_type application/octet-stream; - - sendfile on; - keepalive_timeout 65; - - gzip on; - gzip_vary on; - gzip_proxied any; - gzip_comp_level 6; - gzip_min_length 256; - - # Compress sensible, text-based response types (HTML is covered by default) - gzip_types - text/plain - text/css - text/xml - text/javascript - application/javascript - application/json - application/ld+json - application/xml - application/rss+xml - application/atom+xml - image/svg+xml; - - server { - listen 80 default_server; - server_name _; - - root /var/www; - - index index.html; - - # Migration redirects from legacy site - location ~ ^/archive(?:/(.*))?$ { - return 307 /posts/$1$is_args$args; - } - location = /feed.xml { - # Serve atom.xml content at the legacy feed.xml URL (no redirect) - rewrite ^ /atom.xml break; - } - - location / { - try_files $uri $uri.html $uri/ =404; - } - } -} diff --git a/templates/hommabot/etc_crontab.j2 b/templates/hommabot/etc_crontab.j2 deleted file mode 100644 index 556d12a..0000000 --- a/templates/hommabot/etc_crontab.j2 +++ /dev/null @@ -1,10 +0,0 @@ -# /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 - -# Run hommabot every Monday at 9:00 AM -0 9 * * 1 root /bin/sh -c "cd /root/hommabot && node index.js" diff --git a/templates/hommabot/root_hommabot_deps.sh b/templates/hommabot/root_hommabot_deps.sh deleted file mode 100644 index 25c0e56..0000000 --- a/templates/hommabot/root_hommabot_deps.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh - -set -eux -cd /root/hommabot -npm ci - -# We need to build the better-sqlite3.node file before running the app -if [ ! -f build/better_sqlite3.node ]; then ( - mkdir -p build - cd node_modules/better-sqlite3 - npm run build-release - cp build/Release/better_sqlite3.node ../../build/better_sqlite3.node -) -fi diff --git a/templates/hommabot/root_hommabot_env.j2 b/templates/hommabot/root_hommabot_env.j2 deleted file mode 100644 index c66c666..0000000 --- a/templates/hommabot/root_hommabot_env.j2 +++ /dev/null @@ -1,4 +0,0 @@ -TELEGRAM_BOT_TOKEN="{{ hommabot_telegram_bot_token }}" -SHEETS_SPREADSHEET_ID="{{ hommabot_sheets_spreadsheet_id }}" -SHEETS_RANGE="{{ hommabot_sheets_range }}" -G_SA_JSON_B64="{{ hommabot_g_sa_json_b64 }}" diff --git a/templates/ingress/acme-dns-auth.py b/templates/ingress/acme-dns-auth.py deleted file mode 100755 index 77928e6..0000000 --- a/templates/ingress/acme-dns-auth.py +++ /dev/null @@ -1,170 +0,0 @@ -#!/usr/bin/env python3 -# -# Source: https://github.com/joohoi/acme-dns-certbot-joohoi/blob/master/acme-dns-auth.py -# Some modifications to configuration values present. - - -import json -import os -import sys - -import requests - -### EDIT THESE: Configuration values ### - -# URL to acme-dns instance -ACMEDNS_URL = "https://auth.acme-dns.io" -# Path for acme-dns credential storage -STORAGE_PATH = "/usr/local/etc/letsencrypt/acmedns.json" -# Whitelist for address ranges to allow the updates from -# Example: ALLOW_FROM = ["192.168.10.0/24", "::1/128"] -ALLOW_FROM = [] -# Force re-registration. Overwrites the already existing acme-dns accounts. -FORCE_REGISTER = False - -### DO NOT EDIT BELOW THIS POINT ### -### HERE BE DRAGONS ### - -DOMAIN = os.environ["CERTBOT_DOMAIN"] -if DOMAIN.startswith("*."): - DOMAIN = DOMAIN[2:] -VALIDATION_DOMAIN = "_acme-challenge." + DOMAIN -VALIDATION_TOKEN = os.environ["CERTBOT_VALIDATION"] - - -class AcmeDnsClient(object): - """ - Handles the communication with ACME-DNS API - """ - - def __init__(self, acmedns_url): - self.acmedns_url = acmedns_url - - def register_account(self, allowfrom): - """Registers a new ACME-DNS account""" - - if allowfrom: - # Include whitelisted networks to the registration call - reg_data = {"allowfrom": allowfrom} - res = requests.post( - self.acmedns_url + "/register", data=json.dumps(reg_data) - ) - else: - res = requests.post(self.acmedns_url + "/register") - if res.status_code == 201: - # The request was successful - return res.json() - else: - # Encountered an error - msg = ( - "Encountered an error while trying to register a new acme-dns " - "account. HTTP status {}, Response body: {}" - ) - print(msg.format(res.status_code, res.text)) - sys.exit(1) - - def update_txt_record(self, account, txt): - """Updates the TXT challenge record to ACME-DNS subdomain.""" - update = {"subdomain": account["subdomain"], "txt": txt} - headers = { - "X-Api-User": account["username"], - "X-Api-Key": account["password"], - "Content-Type": "application/json", - } - res = requests.post( - self.acmedns_url + "/update", headers=headers, data=json.dumps(update) - ) - if res.status_code == 200: - # Successful update - return - else: - msg = ( - "Encountered an error while trying to update TXT record in " - "acme-dns. \n" - "------- Request headers:\n{}\n" - "------- Request body:\n{}\n" - "------- Response HTTP status: {}\n" - "------- Response body: {}" - ) - s_headers = json.dumps(headers, indent=2, sort_keys=True) - s_update = json.dumps(update, indent=2, sort_keys=True) - s_body = json.dumps(res.json(), indent=2, sort_keys=True) - print(msg.format(s_headers, s_update, res.status_code, s_body)) - sys.exit(1) - - -class Storage(object): - def __init__(self, storagepath): - self.storagepath = storagepath - self._data = self.load() - - def load(self): - """Reads the storage content from the disk to a dict structure""" - data = dict() - filedata = "" - try: - with open(self.storagepath, "r") as fh: - filedata = fh.read() - except IOError as e: - if os.path.isfile(self.storagepath): - # Only error out if file exists, but cannot be read - print("ERROR: Storage file exists but cannot be read") - sys.exit(1) - try: - data = json.loads(filedata) - except ValueError: - if len(filedata) > 0: - # Storage file is corrupted - print("ERROR: Storage JSON is corrupted") - sys.exit(1) - return data - - def save(self): - """Saves the storage content to disk""" - serialized = json.dumps(self._data) - try: - with os.fdopen( - os.open(self.storagepath, os.O_WRONLY | os.O_CREAT, 0o600), "w" - ) as fh: - fh.truncate() - fh.write(serialized) - except IOError as e: - print("ERROR: Could not write storage file.") - sys.exit(1) - - def put(self, key, value): - """Puts the configuration value to storage and sanitize it""" - # If wildcard domain, remove the wildcard part as this will use the - # same validation record name as the base domain - if key.startswith("*."): - key = key[2:] - self._data[key] = value - - def fetch(self, key): - """Gets configuration value from storage""" - try: - return self._data[key] - except KeyError: - return None - - -if __name__ == "__main__": - # Init - client = AcmeDnsClient(ACMEDNS_URL) - storage = Storage(STORAGE_PATH) - - # Check if an account already exists in storage - account = storage.fetch(DOMAIN) - if FORCE_REGISTER or not account: - # Create and save the new account - account = client.register_account(ALLOW_FROM) - storage.put(DOMAIN, account) - storage.save() - - # Display the notification for the user to update the main zone - msg = "Please add the following CNAME record to your main DNS zone:\n{}" - cname = "{} CNAME {}.".format(VALIDATION_DOMAIN, account["fulldomain"]) - print(msg.format(cname)) - - # Update the TXT record in acme-dns instance - client.update_txt_record(account, VALIDATION_TOKEN) diff --git a/templates/ingress/etc_crontab.j2 b/templates/ingress/etc_crontab.j2 deleted file mode 100644 index 6879766..0000000 --- a/templates/ingress/etc_crontab.j2 +++ /dev/null @@ -1,18 +0,0 @@ -# /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 - -# Update LetsEncrypt certificates every day after 2:00 AM -0 2 * * * root certbot certonly -n -m {{ contact_email }} --agree-tos --manual --manual-auth-hook /usr/local/bin/acme-dns-auth.py --preferred-challenges dns --debug-challenges {% for domain in cert_domains %}-d '{{ domain }}' {% endfor %} || echo "ERROR: Failed to renew certs" >&2 - -# Update dynamic DNS (Hetzner) -{% for elem in hetzner_zone_record_ids %} -* * * * * root /usr/local/bin/hetzner_ddns.sh --zone '{{ elem.zone }}' --rr-id '{{ elem.rr_id }}' --iface-cmd 'ifconfig epw1b' >>/var/log/hetzner_ddns.log 2>&1 -{% endfor %} - -# Update goaccess report HTML -0 * * * * root /usr/local/bin/gen_goaccess.sh diff --git a/templates/ingress/etc_pf.conf.j2 b/templates/ingress/etc_pf.conf.j2 deleted file mode 100644 index c0528e1..0000000 --- a/templates/ingress/etc_pf.conf.j2 +++ /dev/null @@ -1,27 +0,0 @@ -{% for jail in jails -%} -{% if jail.name == 'ingress' -%} -# Interfaces & nets -lan = "epl{{ jail.num }}b" -wan = "epw{{ jail.num }}b" -lan_net = "{{ lan_ipv4_network }}" - -table <blocked> persist - -# Keep PF out of loopback, drop by default if you add blocks later -set skip on lo0 -set block-policy drop - -# NAT: translate LAN traffic to the WAN interface address -nat on $wan from $lan_net to any -> ($wan) - -# Block traffic from IPs in the blocked table -block in quick from <blocked> to any - -# Allow all outbound traffic from the jail and LAN via both interfaces -# NAT will be applied automatically when source is in $lan_net and going out $wan -pass out on $wan all keep state -pass out on $lan all keep state - -pass in on $wan inet proto {tcp, udp} from any to any port {80, 443} keep state -{% endif %} -{% endfor %} diff --git a/templates/ingress/nginx_snippet_ban.inc b/templates/ingress/nginx_snippet_ban.inc deleted file mode 100644 index 029b965..0000000 --- a/templates/ingress/nginx_snippet_ban.inc +++ /dev/null @@ -1,17 +0,0 @@ -# Trap route -location ^~ /wp-admin/ { - proxy_set_header X-IP $remote_addr; - proxy_method POST; - proxy_pass http://unix:/var/run/pfban/ban.sock:/ban; - - proxy_connect_timeout 50ms; - proxy_send_timeout 50ms; - proxy_read_timeout 50ms; - - # If the socket isn't up yet, still return something - error_page 500 502 503 504 = @ban_fallback; -} - -location @ban_fallback { - return 204; -} diff --git a/templates/ingress/pf-ban-socket.py b/templates/ingress/pf-ban-socket.py deleted file mode 100644 index cbcd80e..0000000 --- a/templates/ingress/pf-ban-socket.py +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/env python3 -import http.server -import os -import re -import socketserver -import subprocess - -SOCK_PATH = "/var/run/pfban/ban.sock" -PF_TABLE = "blocked" - -# Simple, conservative filter to avoid junk / injection -IP_RE = re.compile(r"^[0-9A-Fa-f:.]{3,}$") - - -def ensure_socket_dir(path: str) -> None: - os.makedirs(os.path.dirname(path), mode=0o755, exist_ok=True) - - -class BanHandler(http.server.BaseHTTPRequestHandler): - # Silence default logging - def log_message(self, format, *args): - return - - def do_POST(self): - if self.path != "/ban": - self.send_response(404) - self.end_headers() - return - - ip = self.headers.get("X-IP", "").strip() - - if ip and IP_RE.match(ip): - subprocess.run( - ["/sbin/pfctl", "-t", PF_TABLE, "-T", "add", ip], - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - check=False, - ) - - self.send_response(204) - self.end_headers() - - def do_GET(self): - self.send_response(405) - self.end_headers() - - -class ThreadingUnixHTTPServer( - socketserver.ThreadingMixIn, - socketserver.UnixStreamServer, -): - daemon_threads = True - - -def main() -> None: - ensure_socket_dir(SOCK_PATH) - - # Remove stale socket if present - try: - os.unlink(SOCK_PATH) - except FileNotFoundError: - pass - - with ThreadingUnixHTTPServer(SOCK_PATH, BanHandler) as httpd: - # Allow nginx workers (www) to connect - os.chmod(SOCK_PATH, 0o660) - - httpd.serve_forever() - - -if __name__ == "__main__": - main() diff --git a/templates/ingress/usr_local_bin_gen_goaccess.sh.j2 b/templates/ingress/usr_local_bin_gen_goaccess.sh.j2 deleted file mode 100644 index 2cfc93a..0000000 --- a/templates/ingress/usr_local_bin_gen_goaccess.sh.j2 +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh - -TARGET=/mnt/www_goaccess/index.html - -cat /var/log/nginx/access.log | awk '$8=$1$8' | /usr/local/bin/goaccess --log-format=VCOMBINED -j "$(($(nproc) + 1))" --keep-last=30 -a -o $TARGET --restore --persist -chmod 644 $TARGET -chown www $TARGET -chgrp www $TARGET diff --git a/templates/ingress/usr_local_bin_hetzner_ddns.sh.j2 b/templates/ingress/usr_local_bin_hetzner_ddns.sh.j2 deleted file mode 100644 index a2f4430..0000000 --- a/templates/ingress/usr_local_bin_hetzner_ddns.sh.j2 +++ /dev/null @@ -1,130 +0,0 @@ -#!/bin/sh -# -# Hetzner DNS record updater (one-shot for cron) -# Requirements: curl, awk - -set -eu - -# ---------------------------- Defaults --------------------------------------- -# Env-overridable: -: "${API_TOKEN_FILE:=/usr/local/etc/hetzner_auth}" -: "${API_BASE:=https://api.hetzner.cloud/v1}" -# ----------------------------------------------------------------------------- - -# Defaults -TTL=300 - -usage() { - cat <<'USAGE' >&2 -Usage: hetzner_ddns.sh [OPTIONS] - -Options (named): - --zone NAME_OR_ID Hetzner Zone name or ID (primary mode only) - --rr-id ID RRSet identifier in the form "rr-name/rr-type" (e.g., "host/A") - --iface-cmd CMD Command that prints interface info (for IP discovery) - --ttl TTL Time-to-live of the record (optional) - -h, --help Show this help - -Environment: - API_TOKEN_FILE Path to file containing ONLY the API token - (default: /usr/local/etc/hetzner_auth) - API_BASE Hetzner Cloud DNS API base URL - (default: https://api.hetzner.cloud/v1) - -Examples: - API_TOKEN_FILE=/secret/token \ - ./hetzner_ddns.sh \ - --zone example.com --rr-name host --rr-type A \ - --iface-cmd "ifconfig em0" -USAGE -} - -ts() { date +"%Y-%m-%dT%H:%M:%S%z"; } -fail() { echo "$(ts) ERROR: $*" >&2; exit 2; } -error() { echo "$(ts) ERROR: $*" >&2; } -warn() { echo "$(ts) WARN: $*" >&2; } -info() { echo "$(ts) INFO: $*"; } - -# ----------------------------- Arg parsing ----------------------------------- -# Accept both `--key value` and `--key=value` - -while [ $# -gt 0 ]; do - case "$1" in - --zone=*) ZONE=${1#*=} ;; - --zone) ZONE=$2; shift ;; - --rr-id=*) RR_ID=${1#*=} ;; - --rr-id) RR_ID=$2; shift ;; - --iface-cmd=*) IFACE_CMD=${1#*=} ;; - --iface-cmd) IFACE_CMD=$2; shift ;; - --ttl=*) TTL=${1#*=} ;; - --ttl) TTL=$2; shift ;; - -h|--help) usage; exit 0 ;; - --) shift; break ;; - -*) - error "Unknown option: $1" - usage - exit 2 - ;; - *) - error "Unexpected positional argument: $1" - usage - exit 2 - ;; - esac - shift -done - -# ----------------------------- Validation ------------------------------------ -[ -n "${ZONE:-}" ] || fail "Missing --zone" -[ -n "${RR_ID:-}" ] || fail "Missing --rr-id" -[ -n "${IFACE_CMD:-}" ] || fail "Missing --iface-cmd" - -# ------------------------------ Auth ----------------------------------------- -if [ ! -r "${API_TOKEN_FILE}" ]; then - fail "Token file missing or unreadable: ${API_TOKEN_FILE}" -fi -API_TOKEN="$(cat "${API_TOKEN_FILE}" | tr -d '[:space:]')" -[ -n "${API_TOKEN}" ] || fail "API token is empty" - -# ------------------------------ Helpers -------------------------------------- -get_ip() { - # Extract first IPv4 after 'inet ' (ignore inet6) - sh -c "${IFACE_CMD}" 2>/dev/null | awk '/(^|[[:space:]])inet[[:space:]]/ {print $2; exit}' -} - -# ------------------------------ Main ----------------------------------------- -IFACE_IP="$(get_ip || true)" -if [ -z "${IFACE_IP}" ]; then - warn "No IPv4 address found via '${IFACE_CMD}' (interface not ready?)" - exit 1 -fi - -BODY=$(printf '{"records":[{"value":"%s","comment":"Updated by hetzner_ddns.sh at %s","ttl":"%s"}]}' \ - "${IFACE_IP}" "$(ts)" "${TTL}") - -HTTP_CODE=$( - curl -sS -o /dev/null -w "%{http_code}" -X POST \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer ${API_TOKEN}" \ - --data "${BODY}" \ - "${API_BASE}/zones/${ZONE}/rrsets/${RR_ID}/actions/set_records" -) - -case "${HTTP_CODE}" in - 2*) - info "Updated zone ${ZONE} RRSet ${RR_ID} -> ${IFACE_IP}" - exit 0 - ;; - 4*) - error "Client error from API (HTTP ${HTTP_CODE}) - check zone/rr params/token/body" - exit 2 - ;; - 5*) - warn "Server error from API (HTTP ${HTTP_CODE})" - exit 1 - ;; - *) - warn "Unexpected HTTP status ${HTTP_CODE}" - exit 1 - ;; -esac diff --git a/templates/ingress/usr_local_etc_hetzner_auth.j2 b/templates/ingress/usr_local_etc_hetzner_auth.j2 deleted file mode 100644 index 129dccf..0000000 --- a/templates/ingress/usr_local_etc_hetzner_auth.j2 +++ /dev/null @@ -1 +0,0 @@ -{{ hetzner_pat }} diff --git a/templates/ingress/usr_local_etc_nginx_nginx.conf.j2 b/templates/ingress/usr_local_etc_nginx_nginx.conf.j2 deleted file mode 100644 index 16299ef..0000000 --- a/templates/ingress/usr_local_etc_nginx_nginx.conf.j2 +++ /dev/null @@ -1,141 +0,0 @@ -worker_processes auto; - -events { - worker_connections 1024; -} - -http { - include mime.types; - default_type application/octet-stream; - - sendfile on; - keepalive_timeout 65; - - log_format vcombined '$host:$server_port ' - '$remote_addr - $remote_user [$time_local] ' - '"$request" $status $body_bytes_sent ' - '"$http_referer" "$http_user_agent"'; - - access_log /var/log/nginx/access.log vcombined; - - gzip on; - gzip_vary on; - gzip_min_length 512; - gzip_types - text/plain - text/css - application/json - application/javascript - application/xml - image/svg+xml; - - server { - listen 80 default_server; - server_name _; - - include /usr/local/etc/nginx/snippets/ban.inc; - - location / { - return 404; - } - } - - {% for route in ingress_routes -%} - server { - listen 80; - listen [::]:80; - server_name {{ route.host }}; - - include /usr/local/etc/nginx/snippets/ban.inc; - - return 307 https://$host$request_uri; - } - - server { - server_name {{ route.host }}; - http2 on; - - include /usr/local/etc/nginx/snippets/ban.inc; - - listen 443 ssl; - listen [::]:443 ssl; - - # See https://ssl-config.mozilla.org/#server=nginx&version=1.28.0&config=intermediate&openssl=3.4.0&guideline=5.7 - add_header Strict-Transport-Security "max-age=63072000" always; - - # Common hardening headers - add_header X-Content-Type-Options nosniff always; - add_header X-Frame-Options DENY always; - add_header Referrer-Policy strict-origin-when-cross-origin always; - add_header Permissions-Policy interest-cohort=(); - - # Hide "Server: nginx/1.28.0" header - server_tokens off; - - ssl_certificate /usr/local/etc/letsencrypt/live/{{ cert_name }}/fullchain.pem; - ssl_certificate_key /usr/local/etc/letsencrypt/live/{{ cert_name }}/privkey.pem; - include /usr/local/etc/letsencrypt/options-ssl-nginx.conf; - ssl_dhparam /usr/local/etc/letsencrypt/ssl-dhparams.pem; - - {% if route.jail is defined -%} - {% for jail in jails if jail.name == route.jail -%} - location / { - proxy_pass http://192.168.2.{{ jail.num }}{% if route.port is defined %}:{{ route.port }}{% endif %}; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - {% if jail.name == 'irc_thelounge' -%} - proxy_http_version 1.1; - proxy_set_header Connection "Upgrade"; - proxy_set_header Upgrade $http_upgrade; - - # by default nginx times out connections in one minute - proxy_read_timeout 1d; - proxy_send_timeout 1d; - proxy_buffering off; - proxy_request_buffering off; - - client_max_body_size 100M; - {% elif jail.name == 'plex' -%} - proxy_http_version 1.1; - proxy_set_header Connection "Upgrade"; - proxy_set_header Upgrade $http_upgrade; - - # Streaming-friendly behavior - proxy_redirect off; - proxy_buffering off; - - # Long streams / slow clients - proxy_read_timeout 3600s; - proxy_send_timeout 3600s; - {% endif %} - } - {% endfor %} - {% elif route.redirect is defined -%} - return 307 https://{{ route.redirect }}$request_uri; - {% elif route.ip is defined -%} - location / { - proxy_pass http://{{ route.ip }}:{{ route.port }}; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - - # TODO: these shouldn't configured for all ip:port proxies but Immich needs them - proxy_http_version 1.1; - proxy_set_header Connection "upgrade"; - proxy_set_header Upgrade $http_upgrade; - - # by default nginx times out connections in one minute - proxy_read_timeout 1d; - proxy_send_timeout 1d; - proxy_buffering off; - proxy_request_buffering off; - - client_max_body_size 10G; - } - {% endif %} - } - {% endfor %} -} diff --git a/templates/ingress/usr_local_etc_rc.d_pf_ban_socket b/templates/ingress/usr_local_etc_rc.d_pf_ban_socket deleted file mode 100644 index d21bc2d..0000000 --- a/templates/ingress/usr_local_etc_rc.d_pf_ban_socket +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/sh -# -# PROVIDE: pf_ban_socket -# REQUIRE: NETWORKING pf -# KEYWORD: shutdown -# -# Enable in /etc/rc.conf: -# pf_ban_socket_enable="YES" -# -. /etc/rc.subr - -name="pf_ban_socket" -rcvar=pf_ban_socket_enable - -load_rc_config $name - -: ${pf_ban_socket_command:=/usr/local/bin/pf-ban-socket.py} -: ${pf_ban_socket_log:=/var/log/${name}.log} - -start_cmd="${name}_start" -stop_cmd="${name}_stop" -status_cmd="${name}_status" - -extra_commands="status" - -pf_ban_socket_start() { - /usr/local/bin/logto ${pf_ban_socket_log} ${pf_ban_socket_command} & -} - -pf_ban_socket_status() { - if pgrep -f "pf-ban-socket.py"; then - echo "pf_ban_socket is running" - else - echo "pf_ban_socket is not running" - exit 1 - fi -} - -pf_ban_socket_stop() { - pkill "pf-ban-socket.py" -} - -run_rc_command "$1" diff --git a/templates/jail_confs/_default.j2 b/templates/jail_confs/_default.j2 deleted file mode 100644 index 3a4e492..0000000 --- a/templates/jail_confs/_default.j2 +++ /dev/null @@ -1,5 +0,0 @@ -devfs_ruleset = 4; - -{% include 'jail_confs/_epl_network.j2' %} - -exec.poststart += "jexec ${name} route add default {{ ingress_ip }} || echo 'Failed to add default route'"; diff --git a/templates/jail_confs/_epl_network.j2 b/templates/jail_confs/_epl_network.j2 deleted file mode 100644 index fa3744a..0000000 --- a/templates/jail_confs/_epl_network.j2 +++ /dev/null @@ -1,18 +0,0 @@ -## _epl_network begin -exec.prestart += "ifconfig epair{{ jail.num }}000 create || echo 'Failed to create epair{{ jail.num }}000'"; -exec.prestart += "ifconfig epair{{ jail.num }}000a name epl{{ jail.num }}a"; -exec.prestart += "ifconfig epair{{ jail.num }}000b name epl{{ jail.num }}b"; -exec.prestart += "ifconfig epl{{ jail.num }}b ether random"; -exec.prestart += "ifconfig brlan0 addm epl{{ jail.num }}a"; - -exec.poststart += "ifconfig epl{{ jail.num }}b vnet ${name}"; -exec.poststart += "jexec ${name} ifconfig epl{{ jail.num }}b up"; -exec.poststart += "ifconfig epl{{ jail.num }}a up"; -exec.poststart += "jexec ${name} ifconfig epl{{ jail.num }}b {{ jail_lan_prefix }}.{{ jail.num + jail_ip_offset }}/{{ jail_lan_prefixlen }}"; -exec.poststart += "jexec ${name} route delete default || echo 'No default route to delete'"; - -{% if not is_test_vm %} -exec.poststart += "jexec ${name} route add 10.6.210.0/24 {{ lan_ipv4_gateway }} || echo 'Failed to add Wireguard return route'"; -{% endif %} -exec.poststop += "ifconfig epl{{ jail.num }}a destroy"; -## _epl_network end diff --git a/templates/jail_confs/dl.j2 b/templates/jail_confs/dl.j2 deleted file mode 100644 index 8d98ef4..0000000 --- a/templates/jail_confs/dl.j2 +++ /dev/null @@ -1,6 +0,0 @@ -{% include 'jail_confs/_epl_network.j2' %} -devfs_ruleset = 4; -allow.mlock; - -exec.prestart += "mount -t nullfs /usr/local/jails/volumes/storage /usr/local/jails/containers/dl/mnt/storage || echo 'Failed to mount'"; -exec.poststop += "umount /usr/local/jails/containers/dl/mnt/storage || echo 'Failed to umount'"; diff --git a/templates/jail_confs/ingress.j2 b/templates/jail_confs/ingress.j2 deleted file mode 100644 index 5964d06..0000000 --- a/templates/jail_confs/ingress.j2 +++ /dev/null @@ -1,22 +0,0 @@ -devfs_ruleset = 6; - -{% include 'jail_confs/_epl_network.j2' %} - -exec.prestart += "ifconfig epair{{ jail.num }}001 create || echo 'Failed to create epair{{ jail.num }}001'"; -exec.prestart += "ifconfig epair{{ jail.num }}001a name epw1a"; -exec.prestart += "ifconfig epair{{ jail.num }}001b name epw1b"; -exec.prestart += "ifconfig brwan0 addm epw{{ jail.num }}a"; - -exec.poststart += "ifconfig epw{{ jail.num }}b vnet ${name}"; -exec.poststart += "jexec ${name} ifconfig epw{{ jail.num }}b up"; -exec.poststart += "ifconfig epw{{ jail.num }}a up"; -{% if is_test_vm %} -exec.poststart += "jexec ${name} ifconfig epw{{ jail.num }}b inet 10.0.20.3/24"; -exec.poststart += "jexec ${name} route add default 10.0.20.1"; -{% else %} -exec.poststart += "jexec ${name} service dhclient restart epw{{ jail.num }}b"; -{% endif %} - -exec.prestart += "mount -t nullfs /usr/local/jails/containers/goaccess/var/www/goaccess /usr/local/jails/containers/ingress/mnt/www_goaccess || echo 'Failed to mount'"; -exec.poststop += "umount /usr/local/jails/containers/ingress/mnt/www_goaccess || echo 'Failed to umount'"; -exec.poststop += "ifconfig epw{{ jail.num }}a destroy"; diff --git a/templates/jail_confs/komga.j2 b/templates/jail_confs/komga.j2 deleted file mode 100644 index 2567fa2..0000000 --- a/templates/jail_confs/komga.j2 +++ /dev/null @@ -1,4 +0,0 @@ -{% include 'jail_confs/_default.j2' %} - -exec.prestart += "mount -t nullfs /usr/local/jails/volumes/storage/media/manga /usr/local/jails/containers/komga/mnt/manga || echo 'Failed to mount'"; -exec.poststop += "umount /usr/local/jails/containers/komga/mnt/manga || echo 'Failed to umount'"; diff --git a/templates/jail_confs/plex.j2 b/templates/jail_confs/plex.j2 deleted file mode 100644 index 2446a14..0000000 --- a/templates/jail_confs/plex.j2 +++ /dev/null @@ -1,4 +0,0 @@ -{% include 'jail_confs/_default.j2' %} - -exec.prestart += "mount -t nullfs /usr/local/jails/volumes/storage/media /usr/local/jails/containers/plex/mnt/media || echo 'Failed to mount'"; -exec.poststop += "umount /usr/local/jails/containers/plex/mnt/media || echo 'Failed to umount'"; diff --git a/templates/jail_confs/postgres.j2 b/templates/jail_confs/postgres.j2 deleted file mode 100644 index bb3d1f1..0000000 --- a/templates/jail_confs/postgres.j2 +++ /dev/null @@ -1,4 +0,0 @@ -{% include 'jail_confs/_epl_network.j2' %} -devfs_ruleset = 5; - -allow.sysvipc; diff --git a/templates/jail_confs/samba.j2 b/templates/jail_confs/samba.j2 deleted file mode 100644 index 24835fa..0000000 --- a/templates/jail_confs/samba.j2 +++ /dev/null @@ -1,4 +0,0 @@ -{% include 'jail_confs/_default.j2' %} - -exec.prestart += "mount -t nullfs /usr/local/jails/volumes/storage /usr/local/jails/containers/samba/mnt/storage || echo 'Failed to mount'"; -exec.poststop += "umount /usr/local/jails/containers/samba/mnt/storage || echo 'Failed to umount'"; diff --git a/templates/jail_confs/syncthing.j2 b/templates/jail_confs/syncthing.j2 deleted file mode 100644 index 980d47c..0000000 --- a/templates/jail_confs/syncthing.j2 +++ /dev/null @@ -1,11 +0,0 @@ -{% include 'jail_confs/_default.j2' %} - -exec.prestart += "mount -t nullfs /usr/local/jails/volumes/storage/docs /usr/local/jails/containers/syncthing/mnt/docs || echo 'Failed to mount'"; -exec.prestart += "mount -t nullfs /usr/local/jails/volumes/storage/vault /usr/local/jails/containers/syncthing/mnt/vault || echo 'Failed to mount'"; -exec.prestart += "mount -t nullfs /usr/local/jails/volumes/storage/jan-systems-2025-content /usr/local/jails/containers/syncthing/mnt/jan-systems-2025-content || echo 'Failed to mount'"; -exec.prestart += "mount -t nullfs /usr/local/jails/volumes/storage/projects-ableton /usr/local/jails/containers/syncthing/mnt/projects-ableton || echo 'Failed to mount'"; - -exec.poststop += "umount /usr/local/jails/containers/syncthing/mnt/projects-ableton || echo 'Failed to umount'"; -exec.poststop += "umount /usr/local/jails/containers/syncthing/mnt/jan-systems-2025-content || echo 'Failed to umount'"; -exec.poststop += "umount /usr/local/jails/containers/syncthing/mnt/vault || echo 'Failed to umount'"; -exec.poststop += "umount /usr/local/jails/containers/syncthing/mnt/docs || echo 'Failed to umount'"; diff --git a/templates/root_bashrc b/templates/root_bashrc deleted file mode 100644 index f580db6..0000000 --- a/templates/root_bashrc +++ /dev/null @@ -1,7 +0,0 @@ -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/templates/root_clone.sh b/templates/root_clone.sh deleted file mode 100644 index 7f92c5f..0000000 --- a/templates/root_clone.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/sh - -REPO="$1" -TARGET="$2" - -set -eux - -# Exits with 100 if the directory was changed - -if [ ! -d "$TARGET" ]; then - git clone --depth=1 --branch main --single-branch "$REPO" "$TARGET" - exit 100 -fi - -cd "$TARGET" -before=$(git rev-parse HEAD) -git fetch --depth=1 --prune origin main -git reset --hard origin/main -after=$(git rev-parse HEAD) - -if [ "$before" != "$after" ]; then exit 100; fi diff --git a/templates/root_ssh_config.j2 b/templates/root_ssh_config.j2 deleted file mode 100644 index 96f78f2..0000000 --- a/templates/root_ssh_config.j2 +++ /dev/null @@ -1,4 +0,0 @@ -Host backup - HostName {{ backup_ssh_host }} - User {{ backup_ssh_user }} - IdentityFile /root/.ssh/backup diff --git a/templates/usr_local_bin_backup.sh.j2 b/templates/usr_local_bin_backup.sh.j2 deleted file mode 100644 index f6a0e2c..0000000 --- a/templates/usr_local_bin_backup.sh.j2 +++ /dev/null @@ -1,365 +0,0 @@ -{% raw %}#!/usr/bin/env bash -set -euo pipefail - -# ===== CONFIG (filled by Ansible) ===== -KEEP_LOCAL=30 -KEEP_REMOTE=10 -KEYFILE="/root/.ssh/backup" -HOST_DIR="backup" -DATASET="{% endraw %}{{ backup_zfs_dataset }}{% raw %}" -BACKUP_EXCLUDE_PROP="{% endraw %}{{ backup_zfs_exclude_property | default('com.pursotin:backup') }}{% raw %}" -USER="{% endraw %}{{ backup_ssh_user }}{% raw %}" -HOST="{% endraw %}{{ backup_ssh_host }}{% raw %}" -EMAIL_TO=root -# ===================================== - -# ----- Globals for notification ----- -STARTED_AT="$(date '+%Y-%m-%dT%H:%M:%S%z')" -FINISHED_AT="" -MESSAGE_LOG="" -BACKUP_NAME="" # e.g. zroot@2025-09-30-12-00-00-0300.enc -BACKUP_SIZE_BYTES="" # numeric bytes - -# ----- Helpers ----- -die() { echo "Error: $*" >&2; exit 1; } - -require_cmds() { - local cmds=("$@") - for c in "${cmds[@]}"; do command -v "$c" >/dev/null 2>&1 || die "Missing command: $c"; done -} - -log_note() { - # Echo to console and append to message buffer - local msg="$1" - echo "$msg" - MESSAGE_LOG+="$msg"$'\n' -} - -timestamp() { - # Replace '+' with '-' so timezone is filename-safe and lexicographically sortable within TZ. - date +%Y-%m-%d-%H-%M-%S%z | tr '+' '-' -} - -humanize_bytes() { - local bytes="$1" - if [[ "${bytes}" =~ ^[0-9]+$ ]]; then - awk -v b="${bytes}" ' - BEGIN { - unit_count = split("B KiB MiB GiB TiB PiB EiB", units, " ") - i = 1 - while (b >= 1024 && i < unit_count) { - b = b / 1024 - i++ - } - if (i == 1) { - printf "%.0f %s\n", b, units[i] - } else { - printf "%.2f %s\n", b, units[i] - } - } - ' - else - echo "unknown" - fi -} - -latest_snapshot_for_dataset() { - # Latest snapshot on the TOP dataset only (newest first). Returns e.g. zroot@2025-09-30-... - zfs list -t snapshot -o name -S creation "$DATASET" 2>/dev/null \ - | awk 'NR==2{print; exit}' -} - -list_top_snapshots_newest_first() { - # Only list snapshots on the top dataset (not children), newest first. - zfs list -t snapshot -o name -S creation "$DATASET" 2>/dev/null \ - | awk 'NR>1{print $1}' -} - -list_excluded_datasets() { - # List datasets (including inherited values) where backup property is explicitly false. - zfs get -r -H -o name,value "${BACKUP_EXCLUDE_PROP}" "${DATASET}" 2>/dev/null \ - | awk 'tolower($2)=="false"{print $1}' -} - -list_remote_backups_sorted() { - # We store files under ${HOST_DIR}/<dataset>@<timestamp>.enc - echo "ls ${HOST_DIR}" \ - | sftp -q -i "${KEYFILE}" "${USER}@${HOST}" 2>/dev/null \ - | tail -n +2 | sort -} - -# Upload a local file to the storage box path "${HOST_DIR}/<remote_name>" -sftp_put() { - local local_file="$1" - local remote_name="$2" # just the filename, no directory - sftp -i "${KEYFILE}" "${USER}@${HOST}" <<EOF -put ${local_file} ${HOST_DIR}/${remote_name} -EOF -} - -# ----- Subcommands ----- -cmd_init_remote() { - echo "" - echo "[init-remote]" - require_cmds sftp - - echo "Checking for remote directory: ${HOST_DIR}" - if echo "ls ${HOST_DIR}" | sftp -i "${KEYFILE}" "${USER}@${HOST}" >/dev/null 2>&1; then - echo "Remote directory already exists: ${HOST_DIR}" - log_note "init-remote: verified remote directory '${HOST_DIR}'" - return 0 - fi - - echo "Creating remote directory: ${HOST_DIR}" - set +e - sftp -i "${KEYFILE}" "${USER}@${HOST}" <<EOF -mkdir ${HOST_DIR} -EOF - rc=$? - set -e - if [[ $rc -ne 0 ]]; then - die "Failed to create remote directory ${HOST_DIR}" - fi - - # Re-check - if echo "ls ${HOST_DIR}" | sftp -q -i "${KEYFILE}" "${USER}@${HOST}" >/dev/null 2>&1; then - echo "Remote directory created: ${HOST_DIR}" - log_note "init-remote: created remote directory '${HOST_DIR}'" - else - die "Remote directory ${HOST_DIR} not found after creation" - fi -} - -cmd_snapshot() { - echo "" - echo "[snapshot]" - require_cmds zfs date tr - local ts snap - ts="$(timestamp)" - snap="${DATASET}@${ts}" - echo "Taking recursive snapshot \"${snap}\"" - (set -x; zfs snapshot -r "${snap}") - echo "Recursive snapshot \"${snap}\" created" - log_note "snapshot: created recursive snapshot '${snap}'" -} - -cmd_send_to_remote() { - echo "" - echo "[send-to-remote]" - require_cmds zfs age sftp mktemp stat awk sort - - local snap base target tmp size_bytes ds parent skip - local -a send_args excluded_raw excluded - snap="$(latest_snapshot_for_dataset)" - [[ -n "${snap}" ]] || die "No snapshot found to send. Run 'snapshot' first or ensure the dataset has snapshots." - - base="$(basename "${snap}")" # e.g., zroot@2025-09-30-12-00-00-0300 - target="${base}.enc" - tmp="$(mktemp -t backup_send.XXXXXX)" - trap 'rm -f "${tmp}"' EXIT - - send_args=(-Rvc) - mapfile -t excluded_raw < <(list_excluded_datasets | sort) - excluded=() - for ds in "${excluded_raw[@]}"; do - skip=0 - [[ -n "${ds}" ]] || continue - for parent in "${excluded[@]}"; do - if [[ "${ds}" == "${parent}" || "${ds}" == "${parent}/"* ]]; then - skip=1 - break - fi - done - [[ "${skip}" -eq 1 ]] || excluded+=("${ds}") - done - - if [[ "${#excluded[@]}" -gt 0 ]]; then - echo "Excluding datasets where ${BACKUP_EXCLUDE_PROP}=false:" - for ds in "${excluded[@]}"; do - if [[ "${ds}" == "${DATASET}" ]]; then - die "Top dataset ${DATASET} is excluded via ${BACKUP_EXCLUDE_PROP}=false; refusing to create an empty backup stream." - fi - echo " - ${ds}" - send_args+=(-X "${ds}") - done - log_note "send-to-remote: excluded ${#excluded[@]} dataset(s) using ${BACKUP_EXCLUDE_PROP}=false" - fi - - echo "Creating encrypted replication stream to temp file: ${tmp}" - # -R: recursive hierarchy, -v: progress to stderr, -c: send compressed (keeps on-disk compression) - (set -x; zfs send "${send_args[@]}" "${snap}" | age -e -i "${KEYFILE}" > "${tmp}") - - size_bytes="$(stat -f %z "${tmp}" 2>/dev/null || stat -c %s "${tmp}" 2>/dev/null || echo "unknown")" - echo "Local stream size: ${size_bytes} bytes" - - echo "Uploading via SFTP to ${HOST}:${HOST_DIR}/${target}" - if sftp_put "${tmp}" "${target}"; then - echo "Upload complete" - else - die "SFTP upload failed (does the remote directory '${HOST_DIR}' exist? Run 'init-remote')" - fi - - # Set globals for notify() - BACKUP_NAME="${target}" - BACKUP_SIZE_BYTES="${size_bytes}" - - log_note "send-to-remote: uploaded '${snap}' as '${target}' (${size_bytes} bytes) to ${HOST}:${HOST_DIR}" - rm -f "${tmp}" - trap - EXIT -} - -cmd_prune_remote() { - echo "" - echo "[prune-remote]" - require_cmds sftp awk sort wc - - echo "Fetching remote backup listing from sftp://${HOST}/${HOST_DIR}" - BACKUPS="$(list_remote_backups_sorted || true)" - mapfile -t BACKUP_ARR < <(printf "%s\n" "${BACKUPS}") - local count="${#BACKUP_ARR[@]}" - - if [[ "${count}" -le "${KEEP_REMOTE}" ]]; then - echo "Remote backups (${count}) <= KEEP_REMOTE (${KEEP_REMOTE}); nothing to prune." - log_note "prune-remote: kept ${count} (<= ${KEEP_REMOTE}); no deletions" - return 0 - fi - - echo "Pruning remote backups, keeping latest ${KEEP_REMOTE} (will delete $(("${count}" - "${KEEP_REMOTE}")))" - local to_delete_count=$((count - KEEP_REMOTE)) - local deleted=0 - for ((i=0; i<to_delete_count; i++)); do - b="${BACKUP_ARR[$i]}" - [[ -n "${b}" ]] || continue - echo "Deleting remote: ${b}" - set +e - echo "rm ${b}" | sftp -i "${KEYFILE}" "${USER}@${HOST}" - rc=$? - set -e - if [[ $rc -ne 0 ]]; then - die "Failed to delete remote file: ${b}" - fi - deleted=$((deleted+1)) - done - echo "Remote prune done. Deleted: ${deleted}" - log_note "prune-remote: deleted ${deleted}, kept ${KEEP_REMOTE}" -} - -cmd_prune_local() { - echo "" - echo "[prune-local]" - require_cmds zfs awk - - echo "Pruning local snapshots on dataset: ${DATASET}; keeping latest ${KEEP_LOCAL}" - mapfile -t snaps < <(list_top_snapshots_newest_first) - local total="${#snaps[@]}" - if [[ "${total}" -le "${KEEP_LOCAL}" ]]; then - echo "Local snapshots (${total}) <= KEEP_LOCAL (${KEEP_LOCAL}); nothing to prune." - log_note "prune-local: kept ${total} (<= ${KEEP_LOCAL}); no deletions" - return 0 - fi - - local deleted=0 - # List is newest-first; skip first KEEP_LOCAL and destroy the rest RECURSIVELY across the tree - for ((i=KEEP_LOCAL; i<total; i++)); do - s="${snaps[$i]}" # e.g., zroot@2025-09-30-... - echo "Destroying recursive snapshot: ${s}" - (set -x; zfs destroy -r "${s}") - deleted=$((deleted+1)) - done - echo "Local prune done. Deleted: ${deleted}" - log_note "prune-local: deleted ${deleted}, kept ${KEEP_LOCAL}" -} - -cmd_notify() { - echo "" - echo "[notify]" - require_cmds mail date awk - local backup_size_human - FINISHED_AT="$(date '+%Y-%m-%dT%H:%M:%S%z')" - - # Derive snapshot name if not set yet (best-effort) - if [[ -z "${BACKUP_NAME}" ]]; then - # Try to infer from latest snapshot - latest="$(latest_snapshot_for_dataset || true)" - if [[ -n "${latest}" ]]; then - BACKUP_NAME="$(basename "${latest}").enc" - else - BACKUP_NAME="unknown" - fi - fi - - backup_size_human="$(humanize_bytes "${BACKUP_SIZE_BYTES:-unknown}")" - - # Compose subject and body - local subject="Backup completed: ${BACKUP_NAME}" - local body="" - body+="Backup run summary"$'\n' - body+="Started: ${STARTED_AT}"$'\n' - body+="Finished: ${FINISHED_AT}"$'\n' - body+="Dataset: ${DATASET}"$'\n' - body+="Remote: ${USER}@${HOST}:${HOST_DIR}"$'\n' - body+="Name: ${BACKUP_NAME}"$'\n' - body+="Size: ${backup_size_human} (${BACKUP_SIZE_BYTES:-unknown} bytes)"$'\n' - body+=$'\n' - body+="Steps:"$'\n' - body+="${MESSAGE_LOG:-<no steps recorded>}"$'\n' - - echo "Sending notification email..." - printf "%s\n" "$body" | mail -s "$subject" "${EMAIL_TO}" - log_note "notify: email sent to ${EMAIL_TO}" -} - -usage() { - cat <<EOF -Usage: $(basename "$0") [subcommand [subcommand ...]] - -Subcommands (executed in order): - init-remote Create the remote backup directory (\$HOST_DIR). Succeeds if it already exists. - snapshot Create a new **recursive** ZFS snapshot for \$DATASET - send-to-remote Create encrypted replication stream to a temp file, then upload via SFTP - prune-remote Keep latest \$KEEP_REMOTE backups on remote (default ${KEEP_REMOTE}) - prune-local Keep latest \$KEEP_LOCAL local snapshots on the top dataset (default ${KEEP_LOCAL}) - notify Send notification email (includes steps, timestamps, backup name & size) - -Notes: - - Run 'init-remote' once before the first upload, or anytime after changing \$HOST_DIR. - - Local pruning destroys older snapshots **recursively** to maintain consistency across descendants. - - Set \${BACKUP_EXCLUDE_PROP}=false on a dataset to exclude it (and inherited descendants) from send-to-remote. - Example: zfs set com.pursotin:backup=false zroot/some/heavy-dataset - -Examples: - $(basename "$0") init-remote snapshot send-to-remote prune-remote prune-local notify -EOF -} - -# ----- Main ----- -main() { - if [[ $# -eq 0 ]]; then - usage - exit 1 - fi - - # sanity - [[ -n "${DATASET}" ]] || die "DATASET not set" - [[ -n "${USER}" && -n "${HOST}" ]] || die "USER/HOST not set" - [[ -r "${KEYFILE}" ]] || die "KEYFILE not readable: ${KEYFILE}" - - while [[ $# -gt 0 ]]; do - case "$1" in - init-remote) cmd_init_remote ;; - snapshot) cmd_snapshot ;; - send-to-remote) cmd_send_to_remote ;; - prune-remote) cmd_prune_remote ;; - prune-local) cmd_prune_local ;; - notify) cmd_notify ;; - -h|--help|help) usage; exit 0 ;; - *) die "Unknown subcommand: $1" ;; - esac - shift - done - - echo "Done." -} - -main "$@" -{% endraw %} diff --git a/templates/usr_local_bin_logto.sh.j2 b/templates/usr_local_bin_logto.sh.j2 deleted file mode 100644 index 9bb5aa1..0000000 --- a/templates/usr_local_bin_logto.sh.j2 +++ /dev/null @@ -1,47 +0,0 @@ -#!/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/templates/usr_local_bin_pylogsentinel-batch-email.sh.j2 b/templates/usr_local_bin_pylogsentinel-batch-email.sh.j2 deleted file mode 100644 index d28efd9..0000000 --- a/templates/usr_local_bin_pylogsentinel-batch-email.sh.j2 +++ /dev/null @@ -1,14 +0,0 @@ -#!/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/templates/usr_local_bin_safepf.sh b/templates/usr_local_bin_safepf.sh deleted file mode 100644 index 1b6efee..0000000 --- a/templates/usr_local_bin_safepf.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/sh - -set -eu - -cmd="pfctl -f /etc/pf.conf" -read -p "Run this command (y/n)? $cmd " answer -if [ "$answer" = "y" ]; then - (set -x; $cmd) -else - exit 0 -fi - -cmd="service pf restart" -read -p "Run this command (y/n)? $cmd " answer -if [ "$answer" = "y" ]; then - (set -x; $cmd) -else - exit 0 -fi - -timeout=60 -echo "Running safety timeout ($timeout seconds). Press CTRL-C if everything is working." -while [ $timeout -gt 0 ] -do - sleep 1 - timeout=$((timeout - 1)) - echo -n "." -done - -echo "Timeout reached. Enabling empty pf rules" - -set -x -mv /etc/pf.conf /etc/pf.conf.locked_out -echo "" > /etc/pf.conf -pfctl -f /etc/pf.conf -service pf restart diff --git a/templates/usr_local_etc_pylogsentinel.conf.j2 b/templates/usr_local_etc_pylogsentinel.conf.j2 deleted file mode 100644 index cb80742..0000000 --- a/templates/usr_local_etc_pylogsentinel.conf.j2 +++ /dev/null @@ -1,22 +0,0 @@ -[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/templates/usr_local_etc_rc.d_zpool_gpt_labels.j2 b/templates/usr_local_etc_rc.d_zpool_gpt_labels.j2 deleted file mode 100644 index 36bc715..0000000 --- a/templates/usr_local_etc_rc.d_zpool_gpt_labels.j2 +++ /dev/null @@ -1,33 +0,0 @@ -#!/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/templates/usr_local_etc_smartd.conf.j2 b/templates/usr_local_etc_smartd.conf.j2 deleted file mode 100644 index bdcbfd0..0000000 --- a/templates/usr_local_etc_smartd.conf.j2 +++ /dev/null @@ -1,4 +0,0 @@ -/dev/nvme0 -a -d nvme -/dev/nvme1 -a -d nvme -/dev/nvme2 -a -d nvme -/dev/nvme3 -a -d nvme |
