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 /roles/jails | |
| parent | 4715a28fdcd87440400d17154bfa361d99db29cc (diff) | |
Rework
Diffstat (limited to 'roles/jails')
54 files changed, 950 insertions, 0 deletions
diff --git a/roles/jails/01_ingress/defaults/main.yml b/roles/jails/01_ingress/defaults/main.yml new file mode 100644 index 0000000..f6d5aa4 --- /dev/null +++ b/roles/jails/01_ingress/defaults/main.yml @@ -0,0 +1,92 @@ +userland: "15.0-RELEASE" +devfs_ruleset: 6 +no_default_route: true + +ingress_routes: + - { host: jan.systems, jail: homepage } + - { host: jantuomi.fi, redirect: jan.systems } + - { host: aggro.jan.systems, jail: aggro } + - { host: diddle.jan.systems, jail: diddle } + - { host: spliit.jan.systems, jail: spliit } + - { host: freshrss.jan.systems, jail: freshrss } + - { host: irc.jan.systems, jail: irc_thelounge, presets: [websocket] } + - { host: paste.jan.systems, jail: paste } + - { host: leolalla.fi, jail: leolalla_fi } + - { host: immich.jan.systems, ip: 192.168.3.3, port: 2283 } + - { host: plex.jan.systems, jail: plex, port: 32400, presets: [streaming] } + - { host: komga.jan.systems, jail: komga, port: 25600 } + +cert_domains: + - "jan.systems" + - "*.jan.systems" + - "jantuomi.fi" + - "*.jantuomi.fi" + - "leolalla.fi" + - "*.leolalla.fi" + +cert_name: "{{ cert_domains[0] }}" +contact_email: jan@jantuomi.fi + +jail_conf_options: + - "allow.raw_sockets" + +nullfs: + - src: /usr/local/jails/volumes/goaccess_www + dst: /mnt/www_goaccess + +pkg: + - nginx + - py311-certbot + - py311-certbot-nginx + - goaccess + +files: + - src: etc_pf.conf.j2 + dest: /etc/pf.conf + - src: usr_local_etc_nginx_nginx.conf.j2 + dest: /usr/local/etc/nginx/nginx.conf + - src: acme-dns-auth.py + dest: /usr/local/bin/acme-dns-auth.py + mode: "0755" + - src: usr_local_bin_hetzner_ddns.sh.j2 + dest: /usr/local/bin/hetzner_ddns.sh + mode: "0755" + - src: usr_local_etc_hetzner_auth.j2 + dest: /usr/local/etc/hetzner_auth + mode: "0600" + - src: usr_local_bin_gen_goaccess.sh.j2 + dest: /usr/local/bin/gen_goaccess.sh + mode: "0755" + - src: etc_crontab.j2 + dest: /etc/crontab + +services: + - nginx + - pf + +sysctl: + - name: net.inet.ip.forwarding + value: "1" + +sysrc: + - name: gateway_enable + value: "YES" + +nginx_presets: + websocket: + - "proxy_http_version 1.1" + - 'proxy_set_header Connection "Upgrade"' + - "proxy_set_header Upgrade $http_upgrade" + - "proxy_read_timeout 1d" + - "proxy_send_timeout 1d" + - "proxy_buffering off" + - "proxy_request_buffering off" + - "client_max_body_size 100M" + streaming: + - "proxy_http_version 1.1" + - 'proxy_set_header Connection "Upgrade"' + - "proxy_set_header Upgrade $http_upgrade" + - "proxy_redirect off" + - "proxy_buffering off" + - "proxy_read_timeout 3600s" + - "proxy_send_timeout 3600s" diff --git a/roles/jails/01_ingress/tasks/main.yml b/roles/jails/01_ingress/tasks/main.yml new file mode 100644 index 0000000..5b56286 --- /dev/null +++ b/roles/jails/01_ingress/tasks/main.yml @@ -0,0 +1,49 @@ +- name: Set WAN hooks (test) + set_fact: + exec_prestart: + - "ifconfig epw{{ jail_num }}a destroy 2>/dev/null || true" + - "ifconfig epair{{ jail_num }}001 create" + - "ifconfig epair{{ jail_num }}001a name epw{{ jail_num }}a" + - "ifconfig epair{{ jail_num }}001b name epw{{ jail_num }}b" + - "ifconfig brlan0 addm epw{{ jail_num }}a" + exec_poststart: + - "ifconfig epw{{ jail_num }}b vnet {{ jail_name }}" + - "ifconfig epw{{ jail_num }}a up" + - "jexec {{ jail_name }} ifconfig epw{{ jail_num }}b up" + - "jexec {{ jail_name }} ifconfig epw{{ jail_num }}b inet {{ ingress_wan_static }}" + - "jexec {{ jail_name }} route add default {{ lan_ipv4_gateway }}" + exec_poststop: + - "ifconfig epw{{ jail_num }}a destroy 2>/dev/null || true" + when: not is_prod + +- name: Set WAN hooks (prod) + set_fact: + exec_prestart: + - "ifconfig epw{{ jail_num }}a destroy 2>/dev/null || true" + - "ifconfig epair{{ jail_num }}001 create" + - "ifconfig epair{{ jail_num }}001a name epw{{ jail_num }}a" + - "ifconfig epair{{ jail_num }}001b name epw{{ jail_num }}b" + - "ifconfig brwan0 addm epw{{ jail_num }}a" + exec_poststart: + - "ifconfig epw{{ jail_num }}b vnet {{ jail_name }}" + - "ifconfig epw{{ jail_num }}a up" + - "jexec {{ jail_name }} ifconfig epw{{ jail_num }}b up" + - "jexec {{ jail_name }} service dhclient restart epw{{ jail_num }}b" + - "jexec {{ jail_name }} route add 10.6.210.0/24 {{ lan_ipv4_gateway }} || true" + exec_poststop: + - "ifconfig epw{{ jail_num }}a destroy 2>/dev/null || true" + when: is_prod + +- import_role: + name: jail + +- name: Check if TLS certs exist + stat: + path: "/usr/local/etc/letsencrypt/live/{{ cert_name }}" + register: _certbot_certs + when: is_prod + +- name: Pause for manual certbot setup + pause: + prompt: "Run certbot manually in the ingress jail to obtain certs, then press Enter." + when: is_prod and not (_certbot_certs.stat.exists | default(true)) diff --git a/roles/jails/01_ingress/templates/acme-dns-auth.py b/roles/jails/01_ingress/templates/acme-dns-auth.py new file mode 100755 index 0000000..77928e6 --- /dev/null +++ b/roles/jails/01_ingress/templates/acme-dns-auth.py @@ -0,0 +1,170 @@ +#!/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/roles/jails/01_ingress/templates/etc_crontab.j2 b/roles/jails/01_ingress/templates/etc_crontab.j2 new file mode 100644 index 0000000..6879766 --- /dev/null +++ b/roles/jails/01_ingress/templates/etc_crontab.j2 @@ -0,0 +1,18 @@ +# /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/roles/jails/01_ingress/templates/etc_pf.conf.j2 b/roles/jails/01_ingress/templates/etc_pf.conf.j2 new file mode 100644 index 0000000..c0528e1 --- /dev/null +++ b/roles/jails/01_ingress/templates/etc_pf.conf.j2 @@ -0,0 +1,27 @@ +{% 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/roles/jails/01_ingress/templates/usr_local_bin_gen_goaccess.sh.j2 b/roles/jails/01_ingress/templates/usr_local_bin_gen_goaccess.sh.j2 new file mode 100644 index 0000000..2cfc93a --- /dev/null +++ b/roles/jails/01_ingress/templates/usr_local_bin_gen_goaccess.sh.j2 @@ -0,0 +1,8 @@ +#!/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/roles/jails/01_ingress/templates/usr_local_bin_hetzner_ddns.sh.j2 b/roles/jails/01_ingress/templates/usr_local_bin_hetzner_ddns.sh.j2 new file mode 100644 index 0000000..a2f4430 --- /dev/null +++ b/roles/jails/01_ingress/templates/usr_local_bin_hetzner_ddns.sh.j2 @@ -0,0 +1,130 @@ +#!/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/roles/jails/01_ingress/templates/usr_local_etc_hetzner_auth.j2 b/roles/jails/01_ingress/templates/usr_local_etc_hetzner_auth.j2 new file mode 100644 index 0000000..129dccf --- /dev/null +++ b/roles/jails/01_ingress/templates/usr_local_etc_hetzner_auth.j2 @@ -0,0 +1 @@ +{{ hetzner_pat }} diff --git a/roles/jails/01_ingress/templates/usr_local_etc_nginx_nginx.conf.j2 b/roles/jails/01_ingress/templates/usr_local_etc_nginx_nginx.conf.j2 new file mode 100644 index 0000000..3f853c5 --- /dev/null +++ b/roles/jails/01_ingress/templates/usr_local_etc_nginx_nginx.conf.j2 @@ -0,0 +1,123 @@ +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 _; + + location / { + return 404; + } + } + + {% for route in ingress_routes -%} + server { + listen 80; + listen [::]:80; + server_name {{ route.host }}; + +{% if ssl_enabled %} + return 307 https://$host$request_uri; + } + + server { + server_name {{ route.host }}; + http2 on; + + 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; +{% endif %} + + {% if route.jail is defined -%} + {% for jail in jails if jail.name == route.jail -%} + location / { + proxy_pass http://{{ jail_lan_cidr | ipv4_nth(jail.num + jail_lan_offset | int) }}{% 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 route.presets is defined -%} + {% set directives = [] -%} + {% for p in route.presets -%} + {% for d in nginx_presets[p] -%} + {% if d not in directives -%}{% set _ = directives.append(d) -%}{% endif -%} + {% endfor -%} + {% endfor -%} + {% for directive in directives -%} + {{ directive }}; + {% endfor -%} + {% 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/roles/jails/02_postgres/defaults/main.yml b/roles/jails/02_postgres/defaults/main.yml new file mode 100644 index 0000000..045ba4d --- /dev/null +++ b/roles/jails/02_postgres/defaults/main.yml @@ -0,0 +1,9 @@ +userland: "14.3-RELEASE" +devfs_ruleset: 5 + +jail_conf_options: + - "allow.raw_sockets" + - "allow.sysvipc" + +pkg: + - postgresql18-server diff --git a/roles/jails/02_postgres/tasks/main.yml b/roles/jails/02_postgres/tasks/main.yml new file mode 100644 index 0000000..2abdaff --- /dev/null +++ b/roles/jails/02_postgres/tasks/main.yml @@ -0,0 +1,2 @@ +- import_role: + name: jail diff --git a/roles/jails/03_irc_thelounge/defaults/main.yml b/roles/jails/03_irc_thelounge/defaults/main.yml new file mode 100644 index 0000000..697ea79 --- /dev/null +++ b/roles/jails/03_irc_thelounge/defaults/main.yml @@ -0,0 +1 @@ +userland: "14.3-RELEASE" diff --git a/roles/jails/03_irc_thelounge/tasks/main.yml b/roles/jails/03_irc_thelounge/tasks/main.yml new file mode 100644 index 0000000..2abdaff --- /dev/null +++ b/roles/jails/03_irc_thelounge/tasks/main.yml @@ -0,0 +1,2 @@ +- import_role: + name: jail diff --git a/roles/jails/04_taulubot/defaults/main.yml b/roles/jails/04_taulubot/defaults/main.yml new file mode 100644 index 0000000..697ea79 --- /dev/null +++ b/roles/jails/04_taulubot/defaults/main.yml @@ -0,0 +1 @@ +userland: "14.3-RELEASE" diff --git a/roles/jails/04_taulubot/tasks/main.yml b/roles/jails/04_taulubot/tasks/main.yml new file mode 100644 index 0000000..2abdaff --- /dev/null +++ b/roles/jails/04_taulubot/tasks/main.yml @@ -0,0 +1,2 @@ +- import_role: + name: jail diff --git a/roles/jails/05_homepage/defaults/main.yml b/roles/jails/05_homepage/defaults/main.yml new file mode 100644 index 0000000..d48c9fa --- /dev/null +++ b/roles/jails/05_homepage/defaults/main.yml @@ -0,0 +1,18 @@ +userland: "14.3-RELEASE" + +pkg: + - nginx + - rsync + - bash + +files: + - src: usr_local_etc_nginx_nginx.conf.j2 + dest: /usr/local/etc/nginx/nginx.conf + - src: etc_crontab.j2 + dest: /etc/crontab + +dirs: + - /var/www + +services: + - nginx diff --git a/roles/jails/05_homepage/tasks/main.yml b/roles/jails/05_homepage/tasks/main.yml new file mode 100644 index 0000000..2abdaff --- /dev/null +++ b/roles/jails/05_homepage/tasks/main.yml @@ -0,0 +1,2 @@ +- import_role: + name: jail diff --git a/roles/jails/05_homepage/templates/etc_crontab.j2 b/roles/jails/05_homepage/templates/etc_crontab.j2 new file mode 100644 index 0000000..8541f57 --- /dev/null +++ b/roles/jails/05_homepage/templates/etc_crontab.j2 @@ -0,0 +1,9 @@ +# /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/roles/jails/05_homepage/templates/usr_local_etc_nginx_nginx.conf.j2 b/roles/jails/05_homepage/templates/usr_local_etc_nginx_nginx.conf.j2 new file mode 100644 index 0000000..ee45405 --- /dev/null +++ b/roles/jails/05_homepage/templates/usr_local_etc_nginx_nginx.conf.j2 @@ -0,0 +1,55 @@ +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/roles/jails/06_hommabot/defaults/main.yml b/roles/jails/06_hommabot/defaults/main.yml new file mode 100644 index 0000000..d12d40f --- /dev/null +++ b/roles/jails/06_hommabot/defaults/main.yml @@ -0,0 +1,14 @@ +userland: "14.3-RELEASE" + +pkg: + - npm + +files: + - src: root_hommabot_env.j2 + dest: /root/hommabot/.env + mode: "0600" + - src: root_hommabot_deps.sh + dest: /root/hommabot/deps.sh + mode: "0755" + - src: etc_crontab.j2 + dest: /etc/crontab diff --git a/roles/jails/06_hommabot/tasks/main.yml b/roles/jails/06_hommabot/tasks/main.yml new file mode 100644 index 0000000..2abdaff --- /dev/null +++ b/roles/jails/06_hommabot/tasks/main.yml @@ -0,0 +1,2 @@ +- import_role: + name: jail diff --git a/roles/jails/06_hommabot/templates/etc_crontab.j2 b/roles/jails/06_hommabot/templates/etc_crontab.j2 new file mode 100644 index 0000000..556d12a --- /dev/null +++ b/roles/jails/06_hommabot/templates/etc_crontab.j2 @@ -0,0 +1,10 @@ +# /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/roles/jails/06_hommabot/templates/root_hommabot_deps.sh b/roles/jails/06_hommabot/templates/root_hommabot_deps.sh new file mode 100644 index 0000000..25c0e56 --- /dev/null +++ b/roles/jails/06_hommabot/templates/root_hommabot_deps.sh @@ -0,0 +1,14 @@ +#!/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/roles/jails/06_hommabot/templates/root_hommabot_env.j2 b/roles/jails/06_hommabot/templates/root_hommabot_env.j2 new file mode 100644 index 0000000..c66c666 --- /dev/null +++ b/roles/jails/06_hommabot/templates/root_hommabot_env.j2 @@ -0,0 +1,4 @@ +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/roles/jails/07_aggro/defaults/main.yml b/roles/jails/07_aggro/defaults/main.yml new file mode 100644 index 0000000..697ea79 --- /dev/null +++ b/roles/jails/07_aggro/defaults/main.yml @@ -0,0 +1 @@ +userland: "14.3-RELEASE" diff --git a/roles/jails/07_aggro/tasks/main.yml b/roles/jails/07_aggro/tasks/main.yml new file mode 100644 index 0000000..2abdaff --- /dev/null +++ b/roles/jails/07_aggro/tasks/main.yml @@ -0,0 +1,2 @@ +- import_role: + name: jail diff --git a/roles/jails/08_diddle/defaults/main.yml b/roles/jails/08_diddle/defaults/main.yml new file mode 100644 index 0000000..9423bd6 --- /dev/null +++ b/roles/jails/08_diddle/defaults/main.yml @@ -0,0 +1,23 @@ +userland: "14.3-RELEASE" + +pkg: + - python311 + - py311-sqlite3 + - git + +files: + - src: root_clone.sh + dest: /root/clone.sh + mode: "0755" + - src: root_diddle_env.j2 + dest: /root/diddle/.env + mode: "0600" + - src: usr_local_bin_diddle + dest: /usr/local/bin/diddle + mode: "0755" + - src: usr_local_etc_rc.d_diddle + dest: /usr/local/etc/rc.d/diddle + mode: "0755" + +services: + - diddle diff --git a/roles/jails/08_diddle/tasks/main.yml b/roles/jails/08_diddle/tasks/main.yml new file mode 100644 index 0000000..2abdaff --- /dev/null +++ b/roles/jails/08_diddle/tasks/main.yml @@ -0,0 +1,2 @@ +- import_role: + name: jail diff --git a/roles/jails/08_diddle/templates/root_clone.sh b/roles/jails/08_diddle/templates/root_clone.sh new file mode 100644 index 0000000..7f92c5f --- /dev/null +++ b/roles/jails/08_diddle/templates/root_clone.sh @@ -0,0 +1,21 @@ +#!/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/roles/jails/08_diddle/templates/root_diddle_env.j2 b/roles/jails/08_diddle/templates/root_diddle_env.j2 new file mode 100644 index 0000000..ca7db78 --- /dev/null +++ b/roles/jails/08_diddle/templates/root_diddle_env.j2 @@ -0,0 +1,9 @@ +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/roles/jails/08_diddle/templates/usr_local_bin_diddle b/roles/jails/08_diddle/templates/usr_local_bin_diddle new file mode 100644 index 0000000..d07cd5e --- /dev/null +++ b/roles/jails/08_diddle/templates/usr_local_bin_diddle @@ -0,0 +1,17 @@ +#!/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/roles/jails/08_diddle/templates/usr_local_etc_rc.d_diddle b/roles/jails/08_diddle/templates/usr_local_etc_rc.d_diddle new file mode 100644 index 0000000..01deaad --- /dev/null +++ b/roles/jails/08_diddle/templates/usr_local_etc_rc.d_diddle @@ -0,0 +1,43 @@ +#!/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/roles/jails/09_redis/defaults/main.yml b/roles/jails/09_redis/defaults/main.yml new file mode 100644 index 0000000..5416267 --- /dev/null +++ b/roles/jails/09_redis/defaults/main.yml @@ -0,0 +1 @@ +userland: "15.0-RELEASE" diff --git a/roles/jails/09_redis/tasks/main.yml b/roles/jails/09_redis/tasks/main.yml new file mode 100644 index 0000000..2abdaff --- /dev/null +++ b/roles/jails/09_redis/tasks/main.yml @@ -0,0 +1,2 @@ +- import_role: + name: jail diff --git a/roles/jails/10_samba/defaults/main.yml b/roles/jails/10_samba/defaults/main.yml new file mode 100644 index 0000000..99c53e1 --- /dev/null +++ b/roles/jails/10_samba/defaults/main.yml @@ -0,0 +1,5 @@ +userland: "15.0-RELEASE" + +nullfs: + - src: /usr/local/jails/volumes/storage + dst: /mnt/storage diff --git a/roles/jails/10_samba/tasks/main.yml b/roles/jails/10_samba/tasks/main.yml new file mode 100644 index 0000000..2abdaff --- /dev/null +++ b/roles/jails/10_samba/tasks/main.yml @@ -0,0 +1,2 @@ +- import_role: + name: jail diff --git a/roles/jails/11_spliit/defaults/main.yml b/roles/jails/11_spliit/defaults/main.yml new file mode 100644 index 0000000..697ea79 --- /dev/null +++ b/roles/jails/11_spliit/defaults/main.yml @@ -0,0 +1 @@ +userland: "14.3-RELEASE" diff --git a/roles/jails/11_spliit/tasks/main.yml b/roles/jails/11_spliit/tasks/main.yml new file mode 100644 index 0000000..2abdaff --- /dev/null +++ b/roles/jails/11_spliit/tasks/main.yml @@ -0,0 +1,2 @@ +- import_role: + name: jail diff --git a/roles/jails/12_goaccess/defaults/main.yml b/roles/jails/12_goaccess/defaults/main.yml new file mode 100644 index 0000000..0fa6078 --- /dev/null +++ b/roles/jails/12_goaccess/defaults/main.yml @@ -0,0 +1,5 @@ +userland: "15.0-RELEASE" + +nullfs: + - src: /usr/local/jails/volumes/goaccess_www + dst: /var/www/goaccess diff --git a/roles/jails/12_goaccess/tasks/main.yml b/roles/jails/12_goaccess/tasks/main.yml new file mode 100644 index 0000000..2abdaff --- /dev/null +++ b/roles/jails/12_goaccess/tasks/main.yml @@ -0,0 +1,2 @@ +- import_role: + name: jail diff --git a/roles/jails/13_plex/defaults/main.yml b/roles/jails/13_plex/defaults/main.yml new file mode 100644 index 0000000..91ea619 --- /dev/null +++ b/roles/jails/13_plex/defaults/main.yml @@ -0,0 +1,5 @@ +userland: "15.0-RELEASE" + +nullfs: + - src: /usr/local/jails/volumes/storage/media + dst: /mnt/media diff --git a/roles/jails/13_plex/tasks/main.yml b/roles/jails/13_plex/tasks/main.yml new file mode 100644 index 0000000..2abdaff --- /dev/null +++ b/roles/jails/13_plex/tasks/main.yml @@ -0,0 +1,2 @@ +- import_role: + name: jail diff --git a/roles/jails/14_freshrss/defaults/main.yml b/roles/jails/14_freshrss/defaults/main.yml new file mode 100644 index 0000000..697ea79 --- /dev/null +++ b/roles/jails/14_freshrss/defaults/main.yml @@ -0,0 +1 @@ +userland: "14.3-RELEASE" diff --git a/roles/jails/14_freshrss/tasks/main.yml b/roles/jails/14_freshrss/tasks/main.yml new file mode 100644 index 0000000..2abdaff --- /dev/null +++ b/roles/jails/14_freshrss/tasks/main.yml @@ -0,0 +1,2 @@ +- import_role: + name: jail diff --git a/roles/jails/15_paste/defaults/main.yml b/roles/jails/15_paste/defaults/main.yml new file mode 100644 index 0000000..697ea79 --- /dev/null +++ b/roles/jails/15_paste/defaults/main.yml @@ -0,0 +1 @@ +userland: "14.3-RELEASE" diff --git a/roles/jails/15_paste/tasks/main.yml b/roles/jails/15_paste/tasks/main.yml new file mode 100644 index 0000000..2abdaff --- /dev/null +++ b/roles/jails/15_paste/tasks/main.yml @@ -0,0 +1,2 @@ +- import_role: + name: jail diff --git a/roles/jails/16_dl/defaults/main.yml b/roles/jails/16_dl/defaults/main.yml new file mode 100644 index 0000000..c296953 --- /dev/null +++ b/roles/jails/16_dl/defaults/main.yml @@ -0,0 +1,11 @@ +userland: "15.0-RELEASE" +devfs_ruleset: 4 +no_default_route: true + +jail_conf_options: + - "allow.raw_sockets" + - "allow.mlock" + +nullfs: + - src: /usr/local/jails/volumes/storage + dst: /mnt/storage diff --git a/roles/jails/16_dl/tasks/main.yml b/roles/jails/16_dl/tasks/main.yml new file mode 100644 index 0000000..2abdaff --- /dev/null +++ b/roles/jails/16_dl/tasks/main.yml @@ -0,0 +1,2 @@ +- import_role: + name: jail diff --git a/roles/jails/17_syncthing/defaults/main.yml b/roles/jails/17_syncthing/defaults/main.yml new file mode 100644 index 0000000..a2d7d8f --- /dev/null +++ b/roles/jails/17_syncthing/defaults/main.yml @@ -0,0 +1,11 @@ +userland: "15.0-RELEASE" + +nullfs: + - src: /usr/local/jails/volumes/storage/docs + dst: /mnt/docs + - src: /usr/local/jails/volumes/storage/vault + dst: /mnt/vault + - src: /usr/local/jails/volumes/storage/jan-systems-2025-content + dst: /mnt/jan-systems-2025-content + - src: /usr/local/jails/volumes/storage/projects-ableton + dst: /mnt/projects-ableton diff --git a/roles/jails/17_syncthing/tasks/main.yml b/roles/jails/17_syncthing/tasks/main.yml new file mode 100644 index 0000000..2abdaff --- /dev/null +++ b/roles/jails/17_syncthing/tasks/main.yml @@ -0,0 +1,2 @@ +- import_role: + name: jail diff --git a/roles/jails/18_komga/defaults/main.yml b/roles/jails/18_komga/defaults/main.yml new file mode 100644 index 0000000..d69a731 --- /dev/null +++ b/roles/jails/18_komga/defaults/main.yml @@ -0,0 +1,5 @@ +userland: "15.0-RELEASE" + +nullfs: + - src: /usr/local/jails/volumes/storage/media/manga + dst: /mnt/manga diff --git a/roles/jails/18_komga/tasks/main.yml b/roles/jails/18_komga/tasks/main.yml new file mode 100644 index 0000000..2abdaff --- /dev/null +++ b/roles/jails/18_komga/tasks/main.yml @@ -0,0 +1,2 @@ +- import_role: + name: jail diff --git a/roles/jails/19_leolalla_fi/defaults/main.yml b/roles/jails/19_leolalla_fi/defaults/main.yml new file mode 100644 index 0000000..5416267 --- /dev/null +++ b/roles/jails/19_leolalla_fi/defaults/main.yml @@ -0,0 +1 @@ +userland: "15.0-RELEASE" diff --git a/roles/jails/19_leolalla_fi/tasks/main.yml b/roles/jails/19_leolalla_fi/tasks/main.yml new file mode 100644 index 0000000..2abdaff --- /dev/null +++ b/roles/jails/19_leolalla_fi/tasks/main.yml @@ -0,0 +1,2 @@ +- import_role: + name: jail |
