aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJan Tuomi <jan@jantuomi.fi>2025-10-06 00:06:54 +0300
committerJan Tuomi <jan@jantuomi.fi>2025-10-06 00:06:54 +0300
commitae9be6373428471dd4c95f64741b990638c272c0 (patch)
tree66211764ac0d8c5484e119b7b3f23ad56ebc5ea4
parentffe2c8d0accb5c33c98408dfb1039d7387ddffd1 (diff)
Work on networking
-rw-r--r--playbook.yml4
-rw-r--r--tasks/jail_ingress.yml53
-rw-r--r--tasks/jail_single.yml3
-rw-r--r--tasks/network.yml17
-rw-r--r--templates/etc_jail.conf.j28
-rw-r--r--templates/etc_resolv.conf.j21
-rw-r--r--templates/usr_local_bin_hetzner_ddns.sh.j266
-rw-r--r--templates/usr_local_etc_do_dyndns_auth.j21
-rw-r--r--templates/usr_local_etc_hetzner_auth.j21
-rw-r--r--templates/usr_local_etc_nginx_nginx.conf.j213
-rw-r--r--templates/usr_local_etc_rc.d_hetzner_ddns.j248
11 files changed, 178 insertions, 37 deletions
diff --git a/playbook.yml b/playbook.yml
index d8de148..fd9d2ad 100644
--- a/playbook.yml
+++ b/playbook.yml
@@ -19,7 +19,9 @@
smtp_port: "{{ lookup('env', 'SMTP_PORT') }}"
dma_mail_hostname: "{{ lookup('env', 'DMA_MAILNAME') }}"
dma_to_address: "{{ lookup('env', 'DMA_TO_ADDRESS') }}"
- do_api_token: "{{ lookup('env', 'DO_API_TOKEN') }}"
+ hetzner_zone_id: "{{ lookup('env', 'HETZNER_ZONE_ID') }}"
+ hetzner_record_id: "{{ lookup('env', 'HETZNER_RECORD_ID') }}"
+ hetzner_pat: "{{ lookup('env', 'HETZNER_PAT') }}"
backup_ssh_host: "{{ lookup('env', 'BACKUP_SSH_HOST') }}"
backup_ssh_user: "{{ lookup('env', 'BACKUP_SSH_USER') }}"
backup_ssh_privkey_file: "{{ lookup('env', 'BACKUP_SSH_PRIVKEY_FILE') }}"
diff --git a/tasks/jail_ingress.yml b/tasks/jail_ingress.yml
index 1a87722..d6db2f5 100644
--- a/tasks/jail_ingress.yml
+++ b/tasks/jail_ingress.yml
@@ -2,17 +2,9 @@
loop:
- { jail: ingress, package: nginx }
- { jail: ingress, package: py311-certbot }
+ - { jail: ingress, package: py311-certbot-nginx }
include_tasks: pkg_jail_install.yml
-- name: Configure nginx.conf
- template:
- src: usr_local_etc_nginx_nginx.conf.j2
- dest: /usr/local/jails/containers/ingress/usr/local/etc/nginx/nginx.conf
- owner: root
- group: wheel
- mode: "0644"
- register: nginx_conf
-
- name: Create static sites log dir
file:
path: /usr/local/jails/containers/ingress/var/log/nginx/{{ item }}
@@ -41,9 +33,19 @@
loop: "{{ static_sites | map(attribute='site') | unique }}"
#- name: Generate LetsEncrypt certs
-# shell: jexec ingress certbot certonly --standalone -n -m jan@jantuomi.fi --agree-tos -d {{ item }}
+# shell: jexec ingress certbot certonly --nginx -n -m jan@jantuomi.fi --agree-tos -d {{ item }}
# loop: "{{ static_sites | selectattr('tls') | map(attribute='host') }}"
-# when: nginx_conf.changed
+
+# It's important to run this after generating certs, because nginx.conf refers to files
+# generated by certbot. Certbot will fail validation if nginx.conf is configured too early.
+- name: Configure nginx.conf
+ template:
+ src: usr_local_etc_nginx_nginx.conf.j2
+ dest: /usr/local/jails/containers/ingress/usr/local/etc/nginx/nginx.conf
+ owner: root
+ group: wheel
+ mode: "0644"
+ register: nginx_conf
- name: Copy over static HTML
ansible.posix.synchronize:
@@ -62,3 +64,32 @@
service -j ingress nginx enable
service -j ingress nginx onestart
when: ingress_nginx_enabled.rc != 0
+
+- name: Configure hetzner-ddns
+ template:
+ src: usr_local_etc_rc.d_hetzner_ddns.j2
+ dest: /usr/local/jails/containers/ingress/usr/local/etc/rc.d/hetzner_ddns
+ owner: root
+ group: wheel
+ mode: "0755"
+
+- name: Install hetzner-ddns script
+ template:
+ src: usr_local_bin_hetzner_ddns.sh.j2
+ dest: /usr/local/jails/containers/ingress/usr/local/bin/hetzner_ddns.sh
+ owner: root
+ group: wheel
+ mode: "0755"
+
+- name: Set up hetzner_auth
+ template:
+ src: usr_local_etc_hetzner_auth.j2
+ dest: /usr/local/jails/containers/ingress/usr/local/etc/hetzner_auth
+ owner: root
+ group: wheel
+ mode: "0600"
+
+- name: Restart hetzner-ddns
+ shell: |
+ service -j ingress hetzner_ddns enable
+ service -j ingress hetzner_ddns restart
diff --git a/tasks/jail_single.yml b/tasks/jail_single.yml
index 9c5d1ee..9d539d0 100644
--- a/tasks/jail_single.yml
+++ b/tasks/jail_single.yml
@@ -8,6 +8,9 @@
shell: zfs clone zroot/jails/templates/{{ jail_userland_version }}@base "zroot/jails/containers/{{ jail.name }}"
when: check_jail_directory.rc != 0
+- name: Make resolv.conf immutable
+ shell: chflags schg /usr/local/jails/containers/{{ jail.name }}/etc/resolv.conf
+
- name: "Check if jail {{ jail.name }} is running"
shell: jls -j "{{ jail.name }}"
failed_when: false
diff --git a/tasks/network.yml b/tasks/network.yml
index 15c4494..bf22044 100644
--- a/tasks/network.yml
+++ b/tasks/network.yml
@@ -66,20 +66,3 @@
- name: Reload pf rules
shell: pfctl -f /etc/pf.conf
when: pf_conf.changed
-
-- name: Set up /usr/local/etc/do_dyndns_auth
- template:
- src: usr_local_etc_do_dyndns_auth.j2
- dest: /usr/local/etc/do_dyndns_auth
- owner: root
- group: wheel
- mode: "0600"
-
-- name: Set up /usr/local/bin/do_dyndns.sh
- template:
- src: usr_local_bin_do_dyndns.sh.j2
- dest: /usr/local/bin/do_dyndns.sh
- owner: root
- group: wheel
- mode: "0755"
- register: do_dyndns_sh
diff --git a/templates/etc_jail.conf.j2 b/templates/etc_jail.conf.j2
index 708e5c1..0b8c18b 100644
--- a/templates/etc_jail.conf.j2
+++ b/templates/etc_jail.conf.j2
@@ -12,7 +12,7 @@ exec.prestart += "ifconfig ${epl}a up";
exec.prestart += "ifconfig brlan0 addm ${epl}a";
exec.clean;
exec.start = "/bin/sh /etc/rc";
-exec.poststart = "jexec ${name} ifconfig ${epl}b 192.168.2.${num}/16";
+exec.poststart = "jexec ${name} ifconfig ${epl}b 192.168.2.${num}/16 up";
exec.poststart += "jexec ${name} route add default 192.168.0.1 || echo 'Failed to add default route'";
exec.stop = "/bin/sh /etc/rc.shutdown";
exec.poststop += "ifconfig ${epl}b -vnet $name"; # workaround to bug 238326: move ${epl}b from the jail to the host when stopping jail services
@@ -32,6 +32,10 @@ path = "/usr/local/jails/containers/${name}";
{% for jail in jails %}
{{ jail.name }} {
$num = "{{ jail.num }}";
+ {% if jail.name == "ingress" %}
+ exec.poststart = "jexec ${name} ifconfig ${epl}b 192.168.2.${num}/16 up";
+ exec.poststart = "jexec ${name} ifconfig epw1b up";
+ exec.poststart += "jexec ${name} service dhclient restart epw1b";
+ {% endif %}
}
-
{% endfor %}
diff --git a/templates/etc_resolv.conf.j2 b/templates/etc_resolv.conf.j2
index 29f6bf6..9232f25 100644
--- a/templates/etc_resolv.conf.j2
+++ b/templates/etc_resolv.conf.j2
@@ -1,3 +1,2 @@
search {{ lan_search_domain }}
nameserver {{ lan_ipv4_gateway }}
-nameserver 8.8.8.8
diff --git a/templates/usr_local_bin_hetzner_ddns.sh.j2 b/templates/usr_local_bin_hetzner_ddns.sh.j2
new file mode 100644
index 0000000..aee1016
--- /dev/null
+++ b/templates/usr_local_bin_hetzner_ddns.sh.j2
@@ -0,0 +1,66 @@
+#!/bin/sh
+# Minimal Hetzner DNS updater for pfSense/FreeBSD
+# - Always updates on startup (no pre-check)
+# - Then polls and only updates when IP changes
+# - Keeps last IP only in memory (no files)
+
+# --- CONFIG ---------------------------------------------------------------
+API_TOKEN="$(cat /usr/local/etc/hetzner_auth)"
+ZONE_ID="{{ hetzner_zone_id }}"
+RECORD_ID="{{ hetzner_record_id }}"
+RECORD_NAME=pursotin
+RECORD_TYPE="A"
+IFACE_CMD="ifconfig epw1b"
+
+POLL_INTERVAL=60
+API_BASE="https://dns.hetzner.com/api/v1"
+TTL="300"
+# --------------------------------------------------------------------------
+
+get_ip() {
+ # Expect FreeBSD-style ifconfig output; grab first IPv4 addr
+ # Example: 'inet 192.0.2.3 ...'
+ sh -c "$IFACE_CMD" 2>/dev/null | awk '/inet[[:space:]]/ {print $2; exit}'
+}
+
+update_record() {
+ ip="$1"
+ body=$(printf '{"type":"%s","name":"%s","value":"%s","zone_id":"%s","ttl":%s}' \
+ "$RECORD_TYPE" "$RECORD_NAME" "$ip" "$ZONE_ID" "$TTL")
+
+ http_code=$(
+ curl -sS -o /dev/null -w "%{http_code}" -X PUT \
+ -H "Content-Type: application/json" \
+ -H "Auth-API-Token: ${API_TOKEN}" \
+ --data "$body" \
+ "${API_BASE}/records/${RECORD_ID}"
+ )
+
+ [ "$http_code" = "200" ] || {
+ echo "$(date -u +"%F %T") update failed (HTTP $http_code)" >&2
+ return 1
+ }
+
+ echo "$(date -u +"%F %T") updated ${RECORD_NAME} to ${ip}"
+ return 0
+}
+
+# --- Startup: always update once (no check) --------------------------------
+last_ip=""
+ip="$(get_ip)"
+if [ -n "$ip" ]; then
+ update_record "$ip" && last_ip="$ip"
+else
+ echo "$(date -u +"%F %T") no IPv4 from: ${IFACE_CMD}; will retry..." >&2
+fi
+
+# --- Poll loop: update only on change --------------------------------------
+while :; do
+ ip="$(get_ip)"
+ if [ -n "$ip" ] && [ "$ip" != "$last_ip" ]; then
+ if update_record "$ip"; then
+ last_ip="$ip"
+ fi
+ fi
+ sleep "$POLL_INTERVAL"
+done
diff --git a/templates/usr_local_etc_do_dyndns_auth.j2 b/templates/usr_local_etc_do_dyndns_auth.j2
deleted file mode 100644
index 2a9cbc5..0000000
--- a/templates/usr_local_etc_do_dyndns_auth.j2
+++ /dev/null
@@ -1 +0,0 @@
-{{ do_api_token }}
diff --git a/templates/usr_local_etc_hetzner_auth.j2 b/templates/usr_local_etc_hetzner_auth.j2
new file mode 100644
index 0000000..129dccf
--- /dev/null
+++ b/templates/usr_local_etc_hetzner_auth.j2
@@ -0,0 +1 @@
+{{ hetzner_pat }}
diff --git a/templates/usr_local_etc_nginx_nginx.conf.j2 b/templates/usr_local_etc_nginx_nginx.conf.j2
index 55c55df..cee7878 100644
--- a/templates/usr_local_etc_nginx_nginx.conf.j2
+++ b/templates/usr_local_etc_nginx_nginx.conf.j2
@@ -22,17 +22,23 @@ http {
{% for site in static_sites -%}
{% if site.tls -%}
- {#server {
- listen 443 ssl;
+ server {
server_name {{ site.host }};
-
root /usr/local/www/{{ site.site }};
+
+ listen 443 ssl;
+ listen [::]:443 ssl;
+ http2 on;
+
index index.html;
location / {
try_files $uri $uri.html $uri/ =404;
}
+ # 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;
+
access_log /var/log/nginx/{{ site.site }}/access.log;
error_log /var/log/nginx/{{ site.site }}/error.log;
@@ -41,7 +47,6 @@ http {
include /usr/local/etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /usr/local/etc/letsencrypt/ssl-dhparams.pem;
}
- #}
{% endif -%}
server {
diff --git a/templates/usr_local_etc_rc.d_hetzner_ddns.j2 b/templates/usr_local_etc_rc.d_hetzner_ddns.j2
new file mode 100644
index 0000000..ccbbb8c
--- /dev/null
+++ b/templates/usr_local_etc_rc.d_hetzner_ddns.j2
@@ -0,0 +1,48 @@
+#!/bin/sh
+#
+# PROVIDE: hetzner_ddns
+# REQUIRE: NETWORKING
+# KEYWORD: shutdown
+
+# Enable via:
+# hetzner_ddns_enable="YES"
+#
+# (pfSense: System -> Advanced -> System Tunables or /etc/rc.conf)
+
+. /etc/rc.subr
+
+name="hetzner_ddns"
+rcvar=hetzner_ddns_enable
+
+load_rc_config $name
+
+: ${hetzner_ddns_enable:="NO"}
+: ${hetzner_ddns_user:="root"}
+: ${hetzner_ddns_command:="/usr/local/bin/hetzner-ddns.sh"}
+
+start_cmd="${name}_start"
+stop_cmd="${name}_stop"
+status_cmd="${name}_status"
+
+hetzner_ddns_start() {
+ echo "Starting ${name}..."
+ /usr/sbin/daemon -t "${name}" -u "${hetzner_ddns_user}" ${hetzner_ddns_command}
+}
+
+hetzner_ddns_stop() {
+ echo "Stopping ${name}..."
+ # First try by daemon title, then by command as fallback
+ pkill -f "^daemon: ${name}$" || pkill -f "${hetzner_ddns_command}"
+}
+
+hetzner_ddns_status() {
+ if pgrep -f "^daemon: ${name}$" >/dev/null 2>&1 || pgrep -f "${hetzner_ddns_command}" >/dev/null 2>&1; then
+ echo "${name} is running"
+ return 0
+ else
+ echo "${name} is not running"
+ return 1
+ fi
+}
+
+run_rc_command "$1"