aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJan Tuomi <jan@jantuomi.fi>2026-08-13 22:18:47 +0300
committerJan Tuomi <jan@jantuomi.fi>2026-08-13 22:18:47 +0300
commit22a76436ca9acced00c517b57ae2f2b73a197de0 (patch)
tree9f849d28dbe2cb98c0d7c3505b8c0b6476985182
parent40596977c2191247bd602170f6a63958c59697f7 (diff)
Update immich jail
-rw-r--r--roles/jail/templates/jail_conf.j23
-rw-r--r--roles/jails/20_immich/defaults/main.yml1
-rw-r--r--roles/jails/20_immich/tasks/main.yml45
-rw-r--r--roles/jails/20_immich/templates/jail.conf.j219
-rw-r--r--roles/jails/20_immich/templates/usr_local_bin_extract_image.sh47
5 files changed, 36 insertions, 79 deletions
diff --git a/roles/jail/templates/jail_conf.j2 b/roles/jail/templates/jail_conf.j2
index d8e0667..25d12bb 100644
--- a/roles/jail/templates/jail_conf.j2
+++ b/roles/jail/templates/jail_conf.j2
@@ -3,10 +3,11 @@
exec.clean;
allow.raw_sockets;
- mount.devfs; # Mount /dev according to devfs_ruleset
+ mount.devfs; # Mount /dev according to devfs_ruleset
devfs_ruleset = {{ jail.devfs_ruleset }};
enforce_statfs = 1; # Let jail see own mounts
+
{% for opt in jail.options %}
{{ opt }};
{% endfor %}
diff --git a/roles/jails/20_immich/defaults/main.yml b/roles/jails/20_immich/defaults/main.yml
index 6cb9390..97f4a13 100644
--- a/roles/jails/20_immich/defaults/main.yml
+++ b/roles/jails/20_immich/defaults/main.yml
@@ -26,5 +26,6 @@ nullfs:
pkg:
- skopeo
+ # umoci is a statically linked Linux binary, fetched by setup task
services: []
diff --git a/roles/jails/20_immich/tasks/main.yml b/roles/jails/20_immich/tasks/main.yml
index cac290e..bcd8258 100644
--- a/roles/jails/20_immich/tasks/main.yml
+++ b/roles/jails/20_immich/tasks/main.yml
@@ -26,16 +26,20 @@
nullfs: "{{ nullfs | default([]) | normalize_nullfs }}"
delegate_to: "{{ jail_delegate_host }}"
-# Deploy extraction script
-- name: Deploy extract-image script
- template:
- src: "{{ jail_role_dir }}/templates/usr_local_bin_extract_image.sh"
- dest: /usr/local/bin/extract-image.sh
- owner: root
- group: wheel
- mode: "0755"
+# Fetch umoci Linux binary (statically linked, runs via Linux compat)
+- name: Check if umoci is installed
+ stat:
+ path: /usr/local/bin/umoci
+ register: _umoci_stat
+
+- name: Fetch umoci Linux binary
+ shell: |
+ fetch -o /tmp/umoci https://github.com/opencontainers/umoci/releases/latest/download/umoci.linux.amd64
+ install -m 755 /tmp/umoci /usr/local/bin/umoci
+ rm /tmp/umoci
+ when: not _umoci_stat.stat.exists
-# Create image directory
+# Create /image directory
- name: Create /image directory
file:
path: /image
@@ -54,7 +58,15 @@
register: _images_extracted
- name: Extract OCI images
- shell: "/usr/local/bin/extract-image.sh {{ item.item.image }} {{ item.item.name }}"
+ shell: |
+ TMPDIR="/tmp/oci_extract_{{ item.item.name }}"
+ rm -rf "$TMPDIR"
+ mkdir -p "$TMPDIR"
+ skopeo copy --override-os linux "docker://{{ item.item.image }}" "oci:$TMPDIR/image"
+ umoci unpack --image "$TMPDIR/image" "$TMPDIR/unpacked"
+ rm -rf "/image/{{ item.item.name }}"
+ mv "$TMPDIR/unpacked/rootfs" "/image/{{ item.item.name }}"
+ rm -rf "$TMPDIR"
loop: "{{ _images_extracted.results }}"
loop_control:
label: "{{ item.item.name }}"
@@ -72,19 +84,6 @@
label: "{{ item[0].name }}:{{ item[1].dst }}"
when: "item[1].dst.startswith('/image/' + item[0].name)"
-# Create mount points in each rootfs
-- name: Create base mount points in image rootfs
- file:
- path: "/image/{{ item[0].name }}/{{ item[1] }}"
- state: directory
- owner: root
- group: wheel
- mode: "0755"
- loop: "{{ oci_images | product(['proc', 'sys', 'dev', 'tmp', 'run']) | list }}"
- loop_control:
- label: "{{ item[0].name }}/{{ item[1] }}"
-
-# Deploy resolv.conf into each rootfs
- name: Deploy resolv.conf into image rootfs
copy:
content: "nameserver {{ lan_ipv4_gateway }}\n"
diff --git a/roles/jails/20_immich/templates/jail.conf.j2 b/roles/jails/20_immich/templates/jail.conf.j2
index 47a0454..6486677 100644
--- a/roles/jails/20_immich/templates/jail.conf.j2
+++ b/roles/jails/20_immich/templates/jail.conf.j2
@@ -1,11 +1,14 @@
+{% set devfs_ruleset = 4 -%}
{{ jail.name }} {
vnet;
exec.clean;
allow.raw_sockets;
- mount.devfs;
- enforce_statfs = 1;
- devfs_ruleset = 4;
+ mount.devfs; # Mount /dev according to devfs_ruleset
+ devfs_ruleset = {{ devfs_ruleset }};
+
+ enforce_statfs = 1; # Let jail see own mounts
+
host.hostname = "${name}";
path = "/usr/local/jails/containers/${name}";
@@ -25,10 +28,10 @@
{% for img in oci_images %}
# Mounts for {{ img.name }} rootfs
- mount += "linprocfs $path/image/{{ img.name }}/proc linprocfs rw 0 0";
- mount += "linsysfs $path/image/{{ img.name }}/sys linsysfs rw 0 0";
- mount += "devfs $path/image/{{ img.name }}/dev devfs rw 0 0";
- mount += "tmpfs $path/image/{{ img.name }}/tmp tmpfs rw 0 0";
- mount += "tmpfs $path/image/{{ img.name }}/run tmpfs rw 0 0";
+ mount += "devfs $path/image/{{ img.name }}/dev devfs rw,ruleset={{ devfs_ruleset }} 0 0";
+ mount += "linprocfs $path/image/{{ img.name }}/proc linprocfs rw 0 0";
+ mount += "linsysfs $path/image/{{ img.name }}/sys linsysfs rw 0 0";
+ mount += "tmpfs $path/image/{{ img.name }}/tmp tmpfs rw 0 0";
+ mount += "tmpfs $path/image/{{ img.name }}/run tmpfs rw 0 0";
{% endfor %}
}
diff --git a/roles/jails/20_immich/templates/usr_local_bin_extract_image.sh b/roles/jails/20_immich/templates/usr_local_bin_extract_image.sh
deleted file mode 100644
index e8226dd..0000000
--- a/roles/jails/20_immich/templates/usr_local_bin_extract_image.sh
+++ /dev/null
@@ -1,47 +0,0 @@
-#!/bin/sh
-# Extract a Docker image to a directory under /image/
-# Usage: extract-image.sh <image-ref> <name>
-# Example: extract-image.sh ghcr.io/immich-app/immich-server:v2.4.1 immich-server
-
-set -eu
-
-IMAGE="$1"
-NAME="$2"
-IMAGE_DIR="/image"
-TARGET="${IMAGE_DIR}/${NAME}"
-TMP_DIR="/tmp/oci-${NAME}"
-
-echo "Extracting ${IMAGE} to ${TARGET}..."
-
-# Clean up any previous extraction attempt
-rm -rf "${TMP_DIR}"
-mkdir -p "${TMP_DIR}"
-
-# Pull image layers
-skopeo copy --override-os linux "docker://${IMAGE}" "dir:${TMP_DIR}"
-
-# Extract all layers in order into new rootfs
-rm -rf "${TARGET}.new"
-mkdir -p "${TARGET}.new"
-
-# Parse layer digests from manifest and extract each layer
-grep -o '"sha256:[a-f0-9]*"' "${TMP_DIR}/manifest.json" | \
- sed 's/"//g; s/sha256://' | \
- while read hash; do
- if [ -f "${TMP_DIR}/${hash}" ]; then
- echo " Extracting layer ${hash}..."
- tar -xzf "${TMP_DIR}/${hash}" -C "${TARGET}.new" 2>/dev/null || \
- tar -xf "${TMP_DIR}/${hash}" -C "${TARGET}.new" 2>/dev/null || true
- fi
- done
-
-# Swap in the new rootfs
-if [ -d "${TARGET}" ]; then
- mv "${TARGET}" "${TARGET}.old"
-fi
-mv "${TARGET}.new" "${TARGET}"
-
-# Cleanup
-rm -rf "${TMP_DIR}" "${TARGET}.old"
-
-echo "Done: ${TARGET}"