aboutsummaryrefslogtreecommitdiffstats
path: root/roles/jail
diff options
context:
space:
mode:
authorJan Tuomi <jan@jantuomi.fi>2026-08-04 12:00:39 +0300
committerJan Tuomi <jan@jantuomi.fi>2026-08-04 12:00:39 +0300
commitd38fe54ab035f8e3248b8e20a58abd89861833bf (patch)
treefcc78428a726799568a1ae2253a16590e883c816 /roles/jail
parent017b29539749da3c62202879d757a45ffc71208c (diff)
Simplify jail configuration
Diffstat (limited to 'roles/jail')
-rw-r--r--roles/jail/tasks/jail_launch.yml7
-rw-r--r--roles/jail/tasks/jail_setup.yml142
-rw-r--r--roles/jail/templates/jail_conf.j232
-rw-r--r--roles/jail/templates/usr_local_bin_jail_net.j2127
4 files changed, 175 insertions, 133 deletions
diff --git a/roles/jail/tasks/jail_launch.yml b/roles/jail/tasks/jail_launch.yml
index 31ccdf2..803290c 100644
--- a/roles/jail/tasks/jail_launch.yml
+++ b/roles/jail/tasks/jail_launch.yml
@@ -1,5 +1,6 @@
- name: Start services
- service:
- name: "{{ item }}"
- state: started
+ shell: "service {{ item }} start || service {{ item }} status"
loop: "{{ services | default([]) }}"
+ register: _svc_start
+ changed_when: "'already running' not in _svc_start.stdout and 'is running' not in _svc_start.stdout"
+ failed_when: "_svc_start.rc != 0 and 'already running' not in _svc_start.stdout and 'is running' not in _svc_start.stdout"
diff --git a/roles/jail/tasks/jail_setup.yml b/roles/jail/tasks/jail_setup.yml
index f50562a..2c8581d 100644
--- a/roles/jail/tasks/jail_setup.yml
+++ b/roles/jail/tasks/jail_setup.yml
@@ -1,4 +1,11 @@
# Host-side setup (runs on the jail host via SSH)
+
+# Normalize zfs and nullfs entries: expand short names to full paths
+- name: "Normalize zfs and nullfs entries for {{ jail_name }}"
+ set_fact:
+ _zfs: "{{ zfs | default([]) | normalize_zfs }}"
+ _nullfs: "{{ nullfs | default([]) | normalize_nullfs }}"
+
- name: "Check if {{ jail_name }} container exists"
shell: "zfs list -o name | grep -Fxq 'zroot/jails/containers/{{ jail_name }}'"
failed_when: false
@@ -13,102 +20,6 @@
when: jail_exists.rc == 0
delegate_to: "{{ jail_delegate_host }}"
-# ZFS volume migration
-- name: "Check ZFS volume state for {{ jail_name }}"
- shell: |
- if zfs list {{ item.name }} >/dev/null 2>&1; then
- echo "exists"
- elif [ -d "/usr/local/jails/containers/{{ jail_name }}{{ item.mountpoint }}" ] && \
- [ "$(ls -A /usr/local/jails/containers/{{ jail_name }}{{ item.mountpoint }})" ]; then
- echo "migrate"
- else
- echo "empty"
- fi
- loop: "{{ zfs | default([]) }}"
- loop_control:
- label: "{{ item.name }}"
- register: _zfs_volume_state
- changed_when: false
- delegate_to: "{{ jail_delegate_host }}"
-
-- name: "*** VOLUME MIGRATION REQUIRED: {{ jail_name }} ***"
- pause:
- prompt: |
-
- ════════════════════════════════════════════════════════════════
- VOLUME MIGRATION: {{ jail_name }}
- ════════════════════════════════════════════════════════════════
- The following paths contain data that will be migrated
- to new ZFS volumes:
- {% for result in _zfs_volume_state.results | default([]) if result.stdout == 'migrate' %}
- - {{ result.item.mountpoint }} → {{ result.item.name }}
- {% endfor %}
-
- This will:
- 1. Create ZFS volumes (staged)
- 2. Stop the jail
- 3. Copy existing data into the volumes
- ════════════════════════════════════════════════════════════════
-
- Press Enter to continue or Ctrl+C to abort
- when: (_zfs_volume_state.results | default([]) | selectattr('stdout', 'equalto', 'migrate') | list | length) > 0
-
-- name: "Create new ZFS volumes (staged) for {{ jail_name }}"
- community.general.zfs:
- name: "{{ item.item.name }}"
- state: present
- extra_zfs_properties:
- mountpoint: "/usr/local/jails/volumes/.staging/{{ jail_name }}{{ item.item.mountpoint }}"
- loop: "{{ _zfs_volume_state.results | default([]) }}"
- loop_control:
- label: "{{ item.item.name }}"
- when: item.stdout == "migrate"
- delegate_to: "{{ jail_delegate_host }}"
-
-- name: "Create staging mount points for {{ jail_name }}"
- file:
- path: "/usr/local/jails/volumes/.staging/{{ jail_name }}{{ item.item.mountpoint }}"
- state: directory
- loop: "{{ _zfs_volume_state.results | default([]) }}"
- loop_control:
- label: "{{ item.item.name }}"
- when: item.stdout == "migrate"
- delegate_to: "{{ jail_delegate_host }}"
-
-- name: "Mount staged volumes for {{ jail_name }}"
- shell: "zfs mount {{ item.item.name }} || true"
- loop: "{{ _zfs_volume_state.results | default([]) }}"
- loop_control:
- label: "{{ item.item.name }}"
- when: item.stdout == "migrate"
- changed_when: false
- delegate_to: "{{ jail_delegate_host }}"
-
-- name: "Stop {{ jail_name }} jail for volume migration"
- shell: "service jail stop {{ jail_name }} || true"
- when: (_zfs_volume_state.results | default([]) | selectattr('stdout', 'equalto', 'migrate') | list | length) > 0
- delegate_to: "{{ jail_delegate_host }}"
-
-- name: "Copy data into staged volumes for {{ jail_name }}"
- shell: |
- cp -a "/usr/local/jails/containers/{{ jail_name }}{{ item.item.mountpoint }}/." \
- "/usr/local/jails/volumes/.staging/{{ jail_name }}{{ item.item.mountpoint }}/"
- loop: "{{ _zfs_volume_state.results | default([]) }}"
- loop_control:
- label: "{{ item.item.name }}"
- when: item.stdout == "migrate"
- delegate_to: "{{ jail_delegate_host }}"
-
-- name: "Set final mountpoint on migrated volumes for {{ jail_name }}"
- shell: |
- zfs unmount {{ item.item.name }} || true
- zfs set mountpoint="/usr/local/jails/containers/{{ jail_name }}{{ item.item.mountpoint }}" {{ item.item.name }}
- loop: "{{ _zfs_volume_state.results | default([]) }}"
- loop_control:
- label: "{{ item.item.name }}"
- when: item.stdout == "migrate"
- delegate_to: "{{ jail_delegate_host }}"
-
# Userland migration
- name: "*** USERLAND MIGRATION REQUIRED: {{ jail_name }} ***"
pause:
@@ -168,28 +79,41 @@
community.general.zfs:
name: "{{ item.name }}"
state: present
- extra_zfs_properties:
- mountpoint: "/usr/local/jails/containers/{{ jail_name }}{{ item.mountpoint }}"
- loop: "{{ zfs | default([]) }}"
+ loop: "{{ _zfs }}"
loop_control:
label: "{{ item.name }}"
delegate_to: "{{ jail_delegate_host }}"
-- name: "Create mount points for ZFS volumes in {{ jail_name }}"
+- name: "Inherit mountpoint on ZFS volumes for {{ jail_name }}"
+ shell: |
+ source=$(zfs get -H -o source mountpoint {{ item.name }})
+ if [ "$source" != "inherited" ] && [ "$source" != "received" ]; then
+ zfs unmount {{ item.name }} 2>/dev/null
+ zfs inherit mountpoint {{ item.name }}
+ echo "changed"
+ fi
+ loop: "{{ _zfs }}"
+ loop_control:
+ label: "{{ item.name }}"
+ register: _zfs_inherit
+ changed_when: "'changed' in _zfs_inherit.stdout"
+ delegate_to: "{{ jail_delegate_host }}"
+
+- name: "Set ownership on ZFS volume host mountpoints for {{ jail_name }}"
file:
- path: "/usr/local/jails/containers/{{ jail_name }}{{ item.mountpoint }}"
+ path: "/usr/local/jails/volumes/{{ item.name | basename }}"
state: directory
owner: "{{ item.owner | default('root') }}"
group: "{{ item.group | default('wheel') }}"
mode: "{{ item.mode | default('0755') }}"
- loop: "{{ zfs | default([]) }}"
+ loop: "{{ _zfs }}"
loop_control:
- label: "{{ item.mountpoint }}"
+ label: "{{ item.name }}"
delegate_to: "{{ jail_delegate_host }}"
- name: "Mount ZFS volumes for {{ jail_name }}"
shell: "zfs mount {{ item.name }} || true"
- loop: "{{ zfs | default([]) }}"
+ loop: "{{ _zfs }}"
loop_control:
label: "{{ item.name }}"
changed_when: false
@@ -210,7 +134,7 @@
file:
path: "{{ item.src }}"
state: directory
- loop: "{{ nullfs | default([]) }}"
+ loop: "{{ _nullfs }}"
loop_control:
label: "{{ item.src }}"
delegate_to: "{{ jail_delegate_host }}"
@@ -222,7 +146,7 @@
owner: root
group: wheel
mode: "0755"
- loop: "{{ nullfs | default([]) }}"
+ loop: "{{ _nullfs }}"
loop_control:
label: "{{ item.dst }}"
delegate_to: "{{ jail_delegate_host }}"
@@ -238,17 +162,15 @@
jail:
name: "{{ jail_name }}"
num: "{{ jail_num }}"
- ip: "{{ jail_lan_cidr | ipv4_nth_cidr(jail_num | int + jail_lan_offset | int) }}"
devfs_ruleset: "{{ devfs_ruleset | default(4) }}"
options: "{{ jail_conf_options | default([]) }}"
- default_route: "{{ not no_default_route | default(false) }}"
exec_prestart: "{{ exec_prestart | default([]) }}"
exec_start: "{{ exec_start | default([]) }}"
exec_poststart: "{{ exec_poststart | default([]) }}"
exec_prestop: "{{ exec_prestop | default([]) }}"
exec_stop: "{{ exec_stop | default([]) }}"
exec_poststop: "{{ exec_poststop | default([]) }}"
- mounts: "{{ nullfs | default([]) }}"
+ nullfs: "{{ _nullfs }}"
delegate_to: "{{ jail_delegate_host }}"
- name: "Start {{ jail_name }} jail"
@@ -289,7 +211,7 @@
changed_when: "'Number of packages to be installed' in pkg_result.stdout"
- name: Install packages from poudriere
- shell: "pkg install -y -r poudriere {{ pkg_custom | join(' ') }}"
+ shell: "timeout 120 pkg install -y -r poudriere {{ pkg_custom | join(' ') }}"
when: pkg_custom is defined and pkg_custom | length > 0
register: pkg_custom_result
changed_when: "'Installing' in pkg_custom_result.stdout"
diff --git a/roles/jail/templates/jail_conf.j2 b/roles/jail/templates/jail_conf.j2
index 2a0ea27..aa88049 100644
--- a/roles/jail/templates/jail_conf.j2
+++ b/roles/jail/templates/jail_conf.j2
@@ -9,28 +9,19 @@
{% endfor %}
devfs_ruleset = {{ jail.devfs_ruleset }};
- host.hostname = "{{ jail.name }}";
+ host.hostname = "${name}";
path = "/usr/local/jails/containers/${name}";
exec.start = "/bin/sh /etc/rc";
exec.stop = "/bin/sh /etc/rc.shutdown";
- # LAN epair
- exec.prestart += "ifconfig epl{{ jail.num }}a destroy 2>/dev/null || true";
- exec.prestart += "ifconfig epair{{ jail.num }}000 create";
- 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 += "ifconfig epl{{ jail.num }}a up";
- exec.poststart += "jexec ${name} ifconfig epl{{ jail.num }}b up";
- exec.poststart += "jexec ${name} ifconfig epl{{ jail.num }}b {{ jail.ip }}";
- exec.poststart += "jexec ${name} route delete default || true";
-{% if jail.default_route %}
- exec.poststart += "jexec ${name} route add default {{ ingress_ip }} || true";
-{% endif %}
- exec.poststop += "ifconfig epl{{ jail.num }}a destroy 2>/dev/null || true";
+ # Networking
+ exec.prestart += "jail_net prestart {{ jail.num }} ${name}";
+ exec.poststart += "jail_net poststart {{ jail.num }} ${name}";
+ exec.prestop += "jail_net prestop {{ jail.num }} ${name}";
+ exec.poststop += "jail_net poststop {{ jail.num }} ${name}";
+
+ # Custom hooks
{% for cmd in jail.exec_prestart %}
exec.prestart += "{{ cmd }}";
{% endfor %}
@@ -49,8 +40,9 @@
{% for cmd in jail.exec_poststop %}
exec.poststop += "{{ cmd }}";
{% endfor %}
-{% for mount in jail.mounts %}
- exec.prestart += "mount -t nullfs {{ mount.src }} /usr/local/jails/containers/{{ jail.name }}{{ mount.dst }} || true";
- exec.poststop += "umount /usr/local/jails/containers/{{ jail.name }}{{ mount.dst }} || true";
+
+ # nullfs mounts
+{% for mount in jail.nullfs %}
+ mount += "{{ mount.src }} ${path}{{ mount.dst }} nullfs {{ mount.mode | default('rw') }} 0 0";
{% endfor %}
}
diff --git a/roles/jail/templates/usr_local_bin_jail_net.j2 b/roles/jail/templates/usr_local_bin_jail_net.j2
new file mode 100644
index 0000000..e9873a3
--- /dev/null
+++ b/roles/jail/templates/usr_local_bin_jail_net.j2
@@ -0,0 +1,127 @@
+#!/bin/sh
+# jail_net - manage jail LAN networking lifecycle
+# Usage: jail_net <prestart|poststart|prestop|poststop> <jail_num> <jail_name>
+
+set -e
+
+STAGE="$1"
+NUM="$2"
+NAME="$3"
+
+BRIDGE="brlan0"
+LAN_BASE="{{ jail_lan_cidr | ipv4_host }}"
+LAN_PREFIX="{{ jail_lan_cidr | ipv4_prefixlen }}"
+LAN_OFFSET="{{ jail_lan_offset }}"
+DEFAULT_ROUTE="{{ jail_lan_cidr | ipv4_nth(1 + jail_lan_offset | int) }}"
+
+# Compute jail IP from jail_num + offset
+_nth=$(( LAN_OFFSET + NUM ))
+_a=$(echo "$LAN_BASE" | cut -d. -f1)
+_b=$(echo "$LAN_BASE" | cut -d. -f2)
+_c=$(echo "$LAN_BASE" | cut -d. -f3)
+_d=$(echo "$LAN_BASE" | cut -d. -f4)
+_total=$(( (_a << 24) + (_b << 16) + (_c << 8) + _d + _nth ))
+IP="$(( (_total >> 24) & 255 )).$(( (_total >> 16) & 255 )).$(( (_total >> 8) & 255 )).$(( _total & 255 ))/${LAN_PREFIX}"
+
+# --- Cleanup functions ---
+
+_cleanup_lan_prestart() {
+ echo "jail_net: prestart failed for ${NAME}, cleaning up LAN interfaces" >&2
+ ifconfig epair${NUM}000a destroy 2>/dev/null || true
+ ifconfig epl${NUM}a destroy 2>/dev/null || true
+}
+
+_cleanup_wan_prestart() {
+ echo "jail_net: prestart failed for ${NAME}, cleaning up WAN interfaces" >&2
+ ifconfig epair${NUM}001a destroy 2>/dev/null || true
+ ifconfig epw${NUM}a destroy 2>/dev/null || true
+}
+
+_cleanup_poststart() {
+ echo "jail_net: poststart failed for ${NAME}" >&2
+ ifconfig epl${NUM}a destroy 2>/dev/null || true
+ if [ "$NUM" -eq 1 ]; then
+ ifconfig epw${NUM}a destroy 2>/dev/null || true
+ fi
+}
+
+# --- Stage functions ---
+
+_prestart() {
+ trap '_cleanup_lan_prestart' EXIT
+
+ ifconfig epl${NUM}a destroy 2>/dev/null || true
+ ifconfig epair${NUM}000 create
+ ifconfig epair${NUM}000a name epl${NUM}a
+ ifconfig epair${NUM}000b name epl${NUM}b
+ ifconfig epl${NUM}b ether random
+ ifconfig ${BRIDGE} addm epl${NUM}a
+
+ if [ "$NUM" -eq 1 ]; then
+ trap '_cleanup_lan_prestart; _cleanup_wan_prestart' EXIT
+ ifconfig epw${NUM}a destroy 2>/dev/null || true
+ ifconfig epair${NUM}001 create
+ ifconfig epair${NUM}001a name epw${NUM}a
+ ifconfig epair${NUM}001b name epw${NUM}b
+{% if is_prod %}
+ ifconfig brwan0 addm epw${NUM}a
+{% else %}
+ ifconfig brlan0 addm epw${NUM}a
+{% endif %}
+ fi
+
+ trap - EXIT
+}
+
+_poststart() {
+ trap '_cleanup_poststart' EXIT
+
+ ifconfig epl${NUM}b vnet ${NAME}
+ ifconfig epl${NUM}a up
+ jexec ${NAME} ifconfig epl${NUM}b up
+ jexec ${NAME} ifconfig epl${NUM}b ${IP}
+ jexec ${NAME} route delete default || true
+ if [ "$NUM" -ne 1 ]; then
+ jexec ${NAME} route add default ${DEFAULT_ROUTE} || true
+ fi
+
+ if [ "$NUM" -eq 1 ]; then
+ ifconfig epw${NUM}b vnet ${NAME}
+ ifconfig epw${NUM}a up
+ jexec ${NAME} ifconfig epw${NUM}b up
+{% if is_prod %}
+ jexec ${NAME} dhclient epw${NUM}b
+ jexec ${NAME} route add 10.6.210.0/24 {{ lan_ipv4_gateway }} || true
+{% else %}
+ jexec ${NAME} ifconfig epw${NUM}b inet {{ ingress_wan_static }}
+ jexec ${NAME} route add default {{ lan_ipv4_gateway }}
+{% endif %}
+ fi
+
+ trap - EXIT
+}
+
+_prestop() {
+ :
+}
+
+_poststop() {
+ set +e
+ ifconfig epl${NUM}a destroy 2>/dev/null
+ if [ "$NUM" -eq 1 ]; then
+ ifconfig epw${NUM}a destroy 2>/dev/null
+ fi
+}
+
+# --- Dispatch ---
+
+case "$STAGE" in
+ prestart) _prestart ;;
+ poststart) _poststart ;;
+ prestop) _prestop ;;
+ poststop) _poststop ;;
+ *)
+ echo "Usage: jail_net <prestart|poststart|prestop|poststop> <num> <name>" >&2
+ exit 1
+ ;;
+esac