Encoding: Pipeline-Konten, NFS-Mount, Layout und Handover-Simulation umgesetzt und verifiziert.
- workload_accounts: gn-media (GID 970), gn-download (983), gn-dashboard (984); gn-media als primäre Gruppe der Pipeline-Konten — AUTH_SYS ueber NFSv4 wirkte serverseitig nicht mit Supplementary-Gruppen (Betriebsbefund 30.10.2026); Sudoers des Deploy-Kontos auf (ALL:ALL) verbreitert (become_user) - video_encoding: NFS-Client, Mount-Unit und Pipeline-Layout auf dem Export (10.0.0.2:/media/datenhdd3/Encoding -> /var/lib/ghostnet-pipeline, ausserhalb der Homes); Layout-Anlage als Dienstkonten wegen root_squash; Verifikation der Verzeichnisrechte als gn-encoding (2777/2770/0750/2750, Gruppe 970) - pve_media_cron: Legacy-Crontab (chown root:root + chmod -R 0777 auf allen Medienplatten, alle 10 min) entfernt — haette jedes echte Berechtigungsmodell plattgewalzt; Backup unter /var/backups/ghostnet/pve/; verify-production prueft das Fehlen ab - verify-pipeline-handover.yml: Handover-Simulation über alle Identitaeten (CT-101-.dlc-Drop -> gn-download -> queue -> gn-encoding -> output/state -> gn-dashboard); einmal vollstaendig bestanden (30.10.2026) - Doku: GNENC-000/001/002, GNAI-017 (6.10/6.11), GNMON-001 auf Stand gebracht Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
@@ -7,11 +7,17 @@ area of the LLM-Server host root; the shared host baseline lives in
|
||||
|
||||
`playbooks/video-encoding.yml` applies the `video_encoding` role: it verifies
|
||||
the locked `gn-encoding` account, enforces CPU-only device isolation, manages
|
||||
the encoding directories and proves the separate rootless Podman store. No
|
||||
encoder image, Quadlet, timer or schedule is defined yet; those are open
|
||||
decisions in
|
||||
the pipeline NFS mount (`10.0.0.2:/media/datenhdd3/Encoding` at
|
||||
`/var/lib/ghostnet-pipeline`) and creates and verifies the pipeline
|
||||
directory layout on the export as the service accounts. No encoder image,
|
||||
Quadlet, timer or schedule is defined yet; those are open decisions in
|
||||
[`../../../docs/platform/llm-server/encoding/`](../../../docs/platform/llm-server/encoding/GNENC-000-DOC_Index.md).
|
||||
|
||||
`playbooks/verify-pipeline-handover.yml` exercises the file handover between
|
||||
the pipeline identities (GNENC-002 sections 5 and 6) on the mounted export;
|
||||
apply `video-encoding.yml` first. The SMB write path from CT-101 (guest root)
|
||||
is simulated separately on CT-101 (GNAI-017 section 6.10).
|
||||
|
||||
## Control-node setup
|
||||
|
||||
Use the shared virtual environment at the repository root (see
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
---
|
||||
# Exercises the GNENC-002 file handover between the pipeline identities on the
|
||||
# mounted NFS export: .dlc drop -> gn-download ingest -> jd/ -> queue/ ->
|
||||
# gn-encoding pick-up -> work/ -> output/ -> state/ -> gn-dashboard read.
|
||||
# Apply playbooks/video-encoding.yml first. The output test release is left in
|
||||
# place for the CT-101 SMB pickup simulation and removed on the next run.
|
||||
- name: Simulate the encoding pipeline file handover (GNENC-002 sections 5 and 6)
|
||||
hosts: llm_servers
|
||||
become: true
|
||||
gather_facts: false
|
||||
|
||||
vars:
|
||||
handover_profile: "AV1 MKV 1080p30 RF25"
|
||||
handover_release: handover-test
|
||||
handover_mount: /var/lib/ghostnet-pipeline
|
||||
handover_dlc: "{{ handover_mount }}/input/{{ handover_profile }}/{{ handover_release }}.dlc"
|
||||
handover_dlc_done: "{{ handover_mount }}/jd/dlc_done/{{ handover_release }}.dlc"
|
||||
handover_jd_release: "{{ handover_mount }}/jd/{{ handover_release }}"
|
||||
handover_queue_release: "{{ handover_mount }}/queue/{{ handover_profile }}/{{ handover_release }}"
|
||||
handover_work_release: "{{ handover_mount }}/work/{{ handover_release }}"
|
||||
handover_output_release: "{{ handover_mount }}/output/{{ handover_profile }}/{{ handover_release }}"
|
||||
handover_output_file: "{{ handover_output_release }}/video.test.mkv"
|
||||
handover_jobs: "{{ handover_mount }}/state/jobs"
|
||||
handover_tmp: "{{ handover_mount }}/state/tmp"
|
||||
handover_job_file: "{{ handover_jobs }}/handover-test.json"
|
||||
|
||||
tasks:
|
||||
- name: Require the pipeline mount to be active
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- /usr/bin/systemctl
|
||||
- is-active
|
||||
- var-lib-ghostnet\x2dpipeline.mount
|
||||
changed_when: false
|
||||
register: handover_mount_status
|
||||
failed_when: handover_mount_status.stdout != "active"
|
||||
|
||||
- name: Stat the handover directories
|
||||
ansible.builtin.stat:
|
||||
path: "{{ item }}"
|
||||
loop:
|
||||
- "{{ handover_mount }}/input/{{ handover_profile }}"
|
||||
- "{{ handover_queue_release }}"
|
||||
- "{{ handover_output_release }}"
|
||||
- "{{ handover_mount }}/jd"
|
||||
- "{{ handover_mount }}/work"
|
||||
- "{{ handover_jobs }}"
|
||||
- "{{ handover_tmp }}"
|
||||
register: handover_dir_stats
|
||||
|
||||
- name: Require the pipeline layout to be present
|
||||
ansible.builtin.assert:
|
||||
that: >-
|
||||
handover_dir_stats.results | map(attribute='stat.isdir')
|
||||
| select('equalto', false) | list | length == 0
|
||||
fail_msg: >-
|
||||
The pipeline layout is missing; apply playbooks/video-encoding.yml
|
||||
first (GNENC-002 section 4).
|
||||
|
||||
- name: Clean up leftovers from previous runs (gn-download side)
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
rm -f "{{ handover_dlc }}" "{{ handover_dlc_done }}"
|
||||
rm -rf "{{ handover_jd_release }}" "{{ handover_queue_release }}"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
become_user: gn-download
|
||||
environment:
|
||||
HOME: /var/lib/ghostnet-download
|
||||
changed_when: true
|
||||
|
||||
- name: Clean up leftovers from previous runs (gn-encoding side)
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
rm -rf "{{ handover_work_release }}" "{{ handover_output_release }}"
|
||||
rm -f "{{ handover_job_file }}"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
become_user: gn-encoding
|
||||
environment:
|
||||
HOME: /var/lib/ghostnet-encoding
|
||||
changed_when: true
|
||||
|
||||
- name: Probe the .dlc drop
|
||||
ansible.builtin.stat:
|
||||
path: "{{ handover_dlc }}"
|
||||
register: handover_dlc_probe
|
||||
|
||||
- name: Provide the .dlc drop as gn-encoding when no external SMB simulation file exists
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
printf 'dlc-handover-test\n' > "{{ handover_dlc }}"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
become_user: gn-encoding
|
||||
environment:
|
||||
HOME: /var/lib/ghostnet-encoding
|
||||
when: not handover_dlc_probe.stat.exists
|
||||
|
||||
- name: Ingest the .dlc as gn-download (read and move to jd/dlc_done)
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
cat "{{ handover_dlc }}" > /dev/null
|
||||
mv "{{ handover_dlc }}" "{{ handover_dlc_done }}"
|
||||
[ -f "{{ handover_dlc_done }}" ]
|
||||
args:
|
||||
executable: /bin/bash
|
||||
become_user: gn-download
|
||||
environment:
|
||||
HOME: /var/lib/ghostnet-download
|
||||
changed_when: true
|
||||
|
||||
- name: Simulate the jDownloader2 download in jd/
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
mkdir "{{ handover_jd_release }}"
|
||||
dd if=/dev/zero of="{{ handover_jd_release }}/video.test" bs=1M count=1 status=none
|
||||
args:
|
||||
executable: /bin/bash
|
||||
become_user: gn-download
|
||||
environment:
|
||||
HOME: /var/lib/ghostnet-download
|
||||
changed_when: true
|
||||
|
||||
- name: Hand the release over to the queue (atomic move)
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
mv "{{ handover_jd_release }}" "{{ handover_queue_release }}"
|
||||
[ -f "{{ handover_queue_release }}/video.test" ]
|
||||
args:
|
||||
executable: /bin/bash
|
||||
become_user: gn-download
|
||||
environment:
|
||||
HOME: /var/lib/ghostnet-download
|
||||
changed_when: true
|
||||
|
||||
- name: Pick up, encode and hand over to output as gn-encoding
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
cat "{{ handover_queue_release }}/video.test" > /dev/null
|
||||
mkdir "{{ handover_work_release }}"
|
||||
dd if=/dev/zero of="{{ handover_work_release }}/video.test.mkv" bs=1M count=1 status=none
|
||||
mv "{{ handover_work_release }}/video.test.mkv" "{{ handover_output_file }}"
|
||||
rm "{{ handover_queue_release }}/video.test"
|
||||
rmdir "{{ handover_queue_release }}" "{{ handover_work_release }}"
|
||||
[ -f "{{ handover_output_file }}" ]
|
||||
args:
|
||||
executable: /bin/bash
|
||||
become_user: gn-encoding
|
||||
environment:
|
||||
HOME: /var/lib/ghostnet-encoding
|
||||
changed_when: true
|
||||
|
||||
- name: Write the state JSON as gn-encoding (atomic via state/tmp)
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
printf '{"job_id":"handover-test","status":"done"}\n' > "{{ handover_tmp }}/handover-test.json"
|
||||
mv "{{ handover_tmp }}/handover-test.json" "{{ handover_job_file }}"
|
||||
[ -f "{{ handover_job_file }}" ]
|
||||
args:
|
||||
executable: /bin/bash
|
||||
become_user: gn-encoding
|
||||
environment:
|
||||
HOME: /var/lib/ghostnet-encoding
|
||||
changed_when: true
|
||||
|
||||
- name: Read the state JSON as gn-dashboard
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
grep -q '"status":"done"' "{{ handover_job_file }}"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
become_user: gn-dashboard
|
||||
environment:
|
||||
HOME: /var/lib/ghostnet-dashboard
|
||||
changed_when: false
|
||||
|
||||
- name: Clean up the state test object
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
rm -f "{{ handover_job_file }}"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
become_user: gn-encoding
|
||||
environment:
|
||||
HOME: /var/lib/ghostnet-encoding
|
||||
changed_when: true
|
||||
|
||||
- name: Stat the handed-over output file
|
||||
ansible.builtin.stat:
|
||||
path: "{{ handover_output_file }}"
|
||||
register: handover_output_stats
|
||||
|
||||
- name: Verify the output file identity
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- handover_output_stats.stat.exists
|
||||
- handover_output_stats.stat.uid | int == 982
|
||||
- handover_output_stats.stat.gid | int == 970
|
||||
fail_msg: >-
|
||||
The output file must exist as 982:970 on the export; found
|
||||
{{ handover_output_stats.stat.uid }}:{{ handover_output_stats.stat.gid }}.
|
||||
success_msg: >-
|
||||
Handover simulation complete; the output test release remains for the
|
||||
CT-101 SMB pickup simulation and is removed on the next run.
|
||||
@@ -6,17 +6,32 @@ baseline for the locked `gn-encoding` service account (UID/GID 982) on
|
||||
|
||||
It verifies the account identity, the locked password and the deterministic
|
||||
subordinate ID ranges, enforces the absence of GPU device groups, manages the
|
||||
root-owned configuration directory, the input/output/work data directories and
|
||||
the administrator-managed rootless Quadlet directory, and proves that the
|
||||
account cannot read `/dev/kfd` or any DRM render node and uses its own rootless
|
||||
Podman store.
|
||||
root-owned configuration directory and the administrator-managed rootless
|
||||
Quadlet directory, and proves that the account cannot read `/dev/kfd` or any
|
||||
DRM render node and uses its own rootless Podman store.
|
||||
|
||||
Pipeline storage comes from the PVE-host NFS export
|
||||
(`10.0.0.2:/media/datenhdd3/Encoding`, GNAI-017/GNAI-019), mounted at
|
||||
`/var/lib/ghostnet-encoding/pipeline` inside the account home — the home
|
||||
itself is not the mountpoint, so the rootless Podman store below it stays
|
||||
visible. The role installs the NFS client, manages the systemd mount unit and
|
||||
creates the pipeline directory layout (`input/`, `jd/`, `queue/`, `work/`,
|
||||
`output/`, `state/`, one subdirectory per HandBrake profile) as the service
|
||||
accounts over NFS, because `root_squash` prevents root-owned creation. The
|
||||
layout ownership and modes (2777 on the SMB-facing trees, 2770/0750/2750
|
||||
elsewhere, group `gn-media` GID 970) are verified after convergence
|
||||
(GNENC-002 section 4).
|
||||
|
||||
While no encoder image is selected (`video_encoding_image` empty), the role
|
||||
additionally guards that no Quadlet file and no container exists for the
|
||||
account. Encoder image, schedule, resource limits, queue handling and health
|
||||
checks are open decisions in
|
||||
`docs/platform/llm-server/encoding/GNENC-001` and are not assumed here.
|
||||
account. Encoder image, schedule, resource limits and health checks remain
|
||||
gated by the open decisions in `docs/platform/llm-server/encoding/GNENC-001`
|
||||
and are not assumed here.
|
||||
|
||||
The account itself is created by the shared `workload_accounts` role in the
|
||||
host project (`llm-server/host/ansible`); this role requires the host baseline
|
||||
to be applied first.
|
||||
host project (`llm-server/host/ansible`), together with `gn-download` (983),
|
||||
`gn-dashboard` (984) and the shared group `gn-media` (GID 970); this role
|
||||
requires the host baseline to be applied first.
|
||||
|
||||
The file handover between the pipeline identities is exercised by
|
||||
`playbooks/verify-pipeline-handover.yml`.
|
||||
|
||||
@@ -8,6 +8,10 @@ video_encoding_account:
|
||||
name: gn-encoding
|
||||
uid: 982
|
||||
gid: 982
|
||||
# Primary login group is gn-media (GID 970): AUTH_SYS over NFSv4 did not
|
||||
# honour the supplementary group membership server-side, so the pipeline
|
||||
# identities carry gn-media as their primary group (verified 2026-10-30).
|
||||
primary_gid: 970
|
||||
home: /var/lib/ghostnet-encoding
|
||||
config_directory: /etc/ghostnet-encoding
|
||||
shell: /usr/sbin/nologin
|
||||
@@ -15,18 +19,46 @@ video_encoding_account:
|
||||
|
||||
video_encoding_subid_count: 65536
|
||||
|
||||
video_encoding_directories:
|
||||
- path: /var/lib/ghostnet-encoding/input
|
||||
mode: "0750"
|
||||
- path: /var/lib/ghostnet-encoding/output
|
||||
mode: "0750"
|
||||
- path: /var/lib/ghostnet-encoding/work
|
||||
mode: "0750"
|
||||
# Pipeline storage on the PVE-host NFS export (GNENC-001/002, GNAI-017).
|
||||
# The mount lives OUTSIDE the service homes (the gn-encoding home is 0750 and
|
||||
# must stay traversable only for its owner; gn-download and gn-dashboard need
|
||||
# to reach the pipeline root without touching any account home).
|
||||
video_encoding_nfs_server: 10.0.0.2
|
||||
video_encoding_nfs_export: /media/datenhdd3/Encoding
|
||||
video_encoding_pipeline_mount: /var/lib/ghostnet-pipeline
|
||||
video_encoding_pipeline_mount_unit: var-lib-ghostnet\x2dpipeline.mount
|
||||
video_encoding_pipeline_mount_options: nfsvers=4.2,hard,_netdev,noatime
|
||||
video_encoding_pipeline_nfs_client_package: nfs-common
|
||||
|
||||
# Export root ownership on the PVE host (GNAI-017 section 4.1):
|
||||
# 100000 = CT-101 guest root (SMB share writes), 970 = gn-media (pipeline).
|
||||
video_encoding_export_root_uid: 100000
|
||||
video_encoding_export_root_gid: 970
|
||||
video_encoding_export_root_mode: "2775"
|
||||
|
||||
# Shared pipeline group of gn-encoding (982), gn-download (983) and
|
||||
# gn-dashboard (984); created by the workload_accounts role.
|
||||
video_encoding_media_group: gn-media
|
||||
video_encoding_media_gid: 970
|
||||
|
||||
# Declared HandBrake profiles (must match the preset files under
|
||||
# llm-server/encoding/profiles/; verified by the role, one input/, queue/
|
||||
# and output/ subdirectory per profile).
|
||||
video_encoding_profile_names:
|
||||
- "AV1 MKV 1080p30 Anime RF34"
|
||||
- "AV1 MKV 1080p30 RF25"
|
||||
- "AV1 MKV 2160p60 4K RF23"
|
||||
|
||||
# Superseded local directories of the pre-NFS baseline (GNENC-001 section 6);
|
||||
# removed when empty.
|
||||
video_encoding_legacy_local_directories:
|
||||
- /var/lib/ghostnet-encoding/input
|
||||
- /var/lib/ghostnet-encoding/output
|
||||
- /var/lib/ghostnet-encoding/work
|
||||
|
||||
video_encoding_quadlet_user_directory: /etc/containers/systemd/users
|
||||
|
||||
# Open decisions from docs/platform/llm-server/encoding/GNENC-001 section 7
|
||||
# stay unset on purpose. Until an encoder image, a schedule and resource limits
|
||||
# are decided, the role only manages the shared encoding baseline and guards
|
||||
# that no workload is defined.
|
||||
# are decided, the role additionally guards that no workload is defined.
|
||||
video_encoding_image: ""
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
video_encoding_account.uid | int
|
||||
- >-
|
||||
video_encoding_account_entry.stdout.split(":")[3] | int ==
|
||||
video_encoding_account.gid | int
|
||||
video_encoding_account.primary_gid | int
|
||||
- >-
|
||||
video_encoding_account_entry.stdout.split(":")[5] ==
|
||||
video_encoding_account.home
|
||||
@@ -146,17 +146,264 @@
|
||||
group: "{{ video_encoding_account.gid }}"
|
||||
mode: "0750"
|
||||
|
||||
- name: Manage the encoding data directories
|
||||
- name: Remove the superseded local pipeline directories when empty
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
changed=0
|
||||
for dir in {{ video_encoding_legacy_local_directories | map('quote') | join(' ') }}; do
|
||||
if [ -d "$dir" ]; then
|
||||
if [ -n "$(find "$dir" -mindepth 1 -print -quit)" ]; then
|
||||
echo "Refusing to remove non-empty legacy directory: $dir"
|
||||
exit 1
|
||||
fi
|
||||
rmdir "$dir"
|
||||
changed=1
|
||||
fi
|
||||
done
|
||||
[ "$changed" = 0 ] || echo CHANGED
|
||||
args:
|
||||
executable: /bin/bash
|
||||
register: video_encoding_legacy_cleanup
|
||||
changed_when: "'CHANGED' in video_encoding_legacy_cleanup.stdout"
|
||||
|
||||
- name: Install the NFS client
|
||||
ansible.builtin.apt:
|
||||
name: "{{ video_encoding_pipeline_nfs_client_package }}"
|
||||
state: present
|
||||
update_cache: true
|
||||
cache_valid_time: 3600
|
||||
install_recommends: false
|
||||
|
||||
- name: Probe whether the pipeline mount is already active
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- /usr/bin/findmnt
|
||||
- --mountpoint
|
||||
- "{{ video_encoding_pipeline_mount }}"
|
||||
check_mode: false
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
register: video_encoding_pipeline_mountpoint_probe
|
||||
|
||||
- name: Create the pipeline mountpoint outside the service homes
|
||||
ansible.builtin.file:
|
||||
path: "{{ video_encoding_directory.path }}"
|
||||
path: "{{ video_encoding_pipeline_mount }}"
|
||||
state: directory
|
||||
owner: "{{ video_encoding_account.uid }}"
|
||||
group: "{{ video_encoding_account.gid }}"
|
||||
mode: "{{ video_encoding_directory.mode }}"
|
||||
loop: "{{ video_encoding_directories }}"
|
||||
loop_control:
|
||||
loop_var: video_encoding_directory
|
||||
label: "{{ video_encoding_directory.path }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
when: video_encoding_pipeline_mountpoint_probe.rc != 0
|
||||
|
||||
- name: Manage the pipeline NFS mount unit
|
||||
ansible.builtin.copy:
|
||||
content: |
|
||||
[Unit]
|
||||
Description=Ghostnet encoding pipeline NFS export (GNENC-002)
|
||||
Requires=network-online.target
|
||||
After=network-online.target
|
||||
|
||||
[Mount]
|
||||
What={{ video_encoding_nfs_server }}:{{ video_encoding_nfs_export }}
|
||||
Where={{ video_encoding_pipeline_mount }}
|
||||
Type=nfs4
|
||||
Options={{ video_encoding_pipeline_mount_options }}
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
dest: "/etc/systemd/system/{{ video_encoding_pipeline_mount_unit }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
|
||||
- name: Reload systemd for the pipeline mount unit
|
||||
ansible.builtin.systemd_service:
|
||||
daemon_reload: true
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: Enable and mount the pipeline NFS export
|
||||
ansible.builtin.systemd_service:
|
||||
name: "{{ video_encoding_pipeline_mount_unit }}"
|
||||
enabled: true
|
||||
state: started
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: Verify the pipeline mount is active
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- /usr/bin/systemctl
|
||||
- is-active
|
||||
- "{{ video_encoding_pipeline_mount_unit }}"
|
||||
check_mode: false
|
||||
changed_when: false
|
||||
register: video_encoding_pipeline_mount_status
|
||||
failed_when: video_encoding_pipeline_mount_status.stdout != "active"
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: Stat the mounted export root
|
||||
ansible.builtin.stat:
|
||||
path: "{{ video_encoding_pipeline_mount }}"
|
||||
register: video_encoding_export_root
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: Verify the mounted export root ownership and mode
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- video_encoding_export_root.stat.isdir
|
||||
- video_encoding_export_root.stat.uid == video_encoding_export_root_uid | int
|
||||
- video_encoding_export_root.stat.gid == video_encoding_export_root_gid | int
|
||||
- >-
|
||||
'%04o' % (video_encoding_export_root.stat.mode | int(0, 8)) ==
|
||||
video_encoding_export_root_mode
|
||||
fail_msg: >-
|
||||
The mounted export root must be {{ video_encoding_export_root_uid }}:{{ video_encoding_export_root_gid }}
|
||||
{{ video_encoding_export_root_mode }} (PVE host side, GNAI-017 section 4.1);
|
||||
found {{ video_encoding_export_root.stat.uid }}:{{ video_encoding_export_root.stat.gid }}
|
||||
{{ video_encoding_export_root.stat.mode }}.
|
||||
success_msg: The pipeline export root is mounted with the declared identity.
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: Collect the HandBrake preset files from the profiles directory
|
||||
ansible.builtin.set_fact:
|
||||
video_encoding_profile_files: >-
|
||||
{{ lookup('ansible.builtin.fileglob', role_path ~ '/../../../profiles/*.json', wantlist=True)
|
||||
| map('basename') | map('regex_replace', '\.json$', '') | list }}
|
||||
|
||||
- name: Require the declared profiles to match the versioned preset files
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- video_encoding_profile_names | sort == video_encoding_profile_files | sort
|
||||
fail_msg: >-
|
||||
The declared video_encoding_profile_names do not match the preset files
|
||||
under llm-server/encoding/profiles/ (found:
|
||||
{{ video_encoding_profile_files | join(', ') }}).
|
||||
success_msg: The declared profiles match the versioned preset files.
|
||||
|
||||
- name: Create the gn-download pipeline directories on the export
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
changed=0
|
||||
while read -r mode path; do
|
||||
[ -n "${path:-}" ] || continue
|
||||
if [ ! -d "$path" ]; then
|
||||
install -d -m "$mode" "$path"
|
||||
changed=1
|
||||
elif [ "$(stat -c '%a' "$path")" != "$mode" ]; then
|
||||
chmod "$mode" "$path"
|
||||
changed=1
|
||||
fi
|
||||
done <<'EOF'
|
||||
{% for d in [
|
||||
(video_encoding_pipeline_mount ~ '/input', '2777'),
|
||||
(video_encoding_pipeline_mount ~ '/jd', '2770'),
|
||||
(video_encoding_pipeline_mount ~ '/jd/dlc_done', '2770'),
|
||||
(video_encoding_pipeline_mount ~ '/jd/failed', '2770'),
|
||||
(video_encoding_pipeline_mount ~ '/queue', '2770') ] %}
|
||||
{{ d[1] }} {{ d[0] }}
|
||||
{% endfor %}
|
||||
{% for p in video_encoding_profile_names %}
|
||||
2777 {{ video_encoding_pipeline_mount }}/input/{{ p }}
|
||||
2770 {{ video_encoding_pipeline_mount }}/queue/{{ p }}
|
||||
{% endfor %}
|
||||
EOF
|
||||
[ "$changed" = 0 ] || echo CHANGED
|
||||
args:
|
||||
executable: /bin/bash
|
||||
become: true
|
||||
become_user: gn-download
|
||||
environment:
|
||||
HOME: /var/lib/ghostnet-download
|
||||
register: video_encoding_download_layout
|
||||
changed_when: "'CHANGED' in video_encoding_download_layout.stdout"
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: Create the gn-encoding pipeline directories on the export
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
changed=0
|
||||
while read -r mode path; do
|
||||
[ -n "${path:-}" ] || continue
|
||||
if [ ! -d "$path" ]; then
|
||||
install -d -m "$mode" "$path"
|
||||
changed=1
|
||||
elif [ "$(stat -c '%a' "$path")" != "$mode" ]; then
|
||||
chmod "$mode" "$path"
|
||||
changed=1
|
||||
fi
|
||||
done <<'EOF'
|
||||
{% for d in [
|
||||
(video_encoding_pipeline_mount ~ '/work', '0750'),
|
||||
(video_encoding_pipeline_mount ~ '/output', '2777'),
|
||||
(video_encoding_pipeline_mount ~ '/output/_failed', '2777'),
|
||||
(video_encoding_pipeline_mount ~ '/state', '2750'),
|
||||
(video_encoding_pipeline_mount ~ '/state/jobs', '0750'),
|
||||
(video_encoding_pipeline_mount ~ '/state/tmp', '0750') ] %}
|
||||
{{ d[1] }} {{ d[0] }}
|
||||
{% endfor %}
|
||||
{% for p in video_encoding_profile_names %}
|
||||
2777 {{ video_encoding_pipeline_mount }}/output/{{ p }}
|
||||
{% endfor %}
|
||||
EOF
|
||||
[ "$changed" = 0 ] || echo CHANGED
|
||||
args:
|
||||
executable: /bin/bash
|
||||
become: true
|
||||
become_user: gn-encoding
|
||||
environment:
|
||||
HOME: "{{ video_encoding_account.home }}"
|
||||
register: video_encoding_worker_layout
|
||||
changed_when: "'CHANGED' in video_encoding_worker_layout.stdout"
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: Verify the pipeline directory layout on the export
|
||||
ansible.builtin.shell: |
|
||||
set -uo pipefail
|
||||
failed=0
|
||||
while read -r uid gid mode path; do
|
||||
[ -n "${path:-}" ] || continue
|
||||
if [ ! -d "$path" ]; then
|
||||
echo "MISSING $path"
|
||||
failed=1
|
||||
continue
|
||||
fi
|
||||
read -r auid agid amode <<<"$(stat -c '%u %g %a' "$path")"
|
||||
if [ "$auid" != "$uid" ] || [ "$agid" != "$gid" ] || [ "$amode" != "$mode" ]; then
|
||||
echo "WRONG $path (expected $uid:$gid $mode, found $auid:$agid $amode)"
|
||||
failed=1
|
||||
fi
|
||||
done <<'EOF'
|
||||
{% for d in [
|
||||
(video_encoding_pipeline_mount ~ '/input', '983', '2777'),
|
||||
(video_encoding_pipeline_mount ~ '/jd', '983', '2770'),
|
||||
(video_encoding_pipeline_mount ~ '/jd/dlc_done', '983', '2770'),
|
||||
(video_encoding_pipeline_mount ~ '/jd/failed', '983', '2770'),
|
||||
(video_encoding_pipeline_mount ~ '/queue', '983', '2770'),
|
||||
(video_encoding_pipeline_mount ~ '/work', '982', '0750'),
|
||||
(video_encoding_pipeline_mount ~ '/output', '982', '2777'),
|
||||
(video_encoding_pipeline_mount ~ '/output/_failed', '982', '2777'),
|
||||
(video_encoding_pipeline_mount ~ '/state', '982', '2750'),
|
||||
(video_encoding_pipeline_mount ~ '/state/jobs', '982', '0750'),
|
||||
(video_encoding_pipeline_mount ~ '/state/tmp', '982', '0750') ] %}
|
||||
{{ d[1] }} {{ d[2] }} {{ d[0] }}
|
||||
{% endfor %}
|
||||
{% for p in video_encoding_profile_names %}
|
||||
983 970 2777 {{ video_encoding_pipeline_mount }}/input/{{ p }}
|
||||
983 970 2770 {{ video_encoding_pipeline_mount }}/queue/{{ p }}
|
||||
982 970 2777 {{ video_encoding_pipeline_mount }}/output/{{ p }}
|
||||
{% endfor %}
|
||||
EOF
|
||||
if [ "$failed" = 1 ]; then
|
||||
echo "Pipeline layout verification failed"
|
||||
exit 1
|
||||
fi
|
||||
echo LAYOUT_OK
|
||||
args:
|
||||
executable: /bin/bash
|
||||
become_user: gn-encoding
|
||||
environment:
|
||||
HOME: "{{ video_encoding_account.home }}"
|
||||
changed_when: false
|
||||
register: video_encoding_layout_verify
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: Manage the administrator-managed rootless Quadlet directory
|
||||
ansible.builtin.file:
|
||||
@@ -330,7 +577,8 @@
|
||||
msg:
|
||||
account: "gn-encoding (UID/GID 982)"
|
||||
config_directory: /etc/ghostnet-encoding
|
||||
data_directories: "{{ video_encoding_directories | map(attribute='path') | list }}"
|
||||
pipeline_mount: "{{ video_encoding_nfs_server }}:{{ video_encoding_nfs_export }} -> {{ video_encoding_pipeline_mount }}"
|
||||
pipeline_profiles: "{{ video_encoding_profile_names | default([]) }}"
|
||||
quadlet_directory: /etc/containers/systemd/users/982
|
||||
podman_store: "{{ video_encoding_podman_info.store.graphRoot }}"
|
||||
gpu_access: none (verified)
|
||||
|
||||
Reference in New Issue
Block a user