/
/
/
This repo is destined for my server automations and setup.
1---
2# ============================================================================
3# D&D Table Playbook
4# ============================================================================
5#
6# Deploys the projector display path, camera capture path and ceiling wash
7# on the table Pi.
8#
9# PREREQUISITES:
10# - Raspberry Pi OS Bookworm 64-bit **Lite** (not Desktop: kmssink cannot take
11# DRM master while a compositor holds it; and Ubuntu's mainline kernel does
12# not reliably ship the bcm2835-codec nodes this depends on)
13# - Raspberry Pi 4 (the Pi 5 has no hardware H.264 encoder)
14# - SSH access with sudo privileges for ansible_user
15# - MQTT broker accessible on the network
16# - Projector connected over HDMI
17# - Images published to Harbor by Forgejo CI
18# - Camera Module 3 (optional; set dnd_table_camera_enabled=true once fitted)
19#
20# USAGE:
21# ansible-playbook dnd-table.yml
22# ansible-playbook dnd-table.yml --tags dnd # skip base system roles
23# ansible-playbook dnd-table.yml --tags validation --check
24#
25# > â ï¸ `--tags wash` does NOT deploy the wash container. It selects the wash's
26# > PREREQUISITES only -- the GPIO and I2C device checks, the group IDs, the
27# > boot-config parameters. The tasks that render the compose file and bring
28# > the services up live in `table.yml` and carry `[dnd, table]`, because a
29# > wash deploy and a table deploy recreate the same compose project.
30# >
31# > **Use `--tags dnd` to actually deploy the wash.** `--tags wash` on its own
32# > reports a clean run having changed no container, which reads exactly like a
33# > successful deploy and is not one.
34#
35# ============================================================================
36
37- name: "D&D Table Complete Setup"
38 hosts: dnd-table
39 become: true
40 gather_facts: true
41
42 pre_tasks:
43 - name: Verify table system requirements
44 ansible.builtin.assert:
45 that:
46 - ansible_distribution in ["Debian", "Ubuntu"]
47 - ansible_architecture in ["aarch64", "armv7l"]
48 - ansible_memtotal_mb >= 900
49 - ansible_processor_vcpus >= 2
50 fail_msg: |
51 Table system requirements not met:
52 - Requires Debian or Ubuntu on ARM
53 - Minimum 1GB RAM (found {{ ansible_memtotal_mb }}MB)
54 - Minimum 2 CPU cores (found {{ ansible_processor_vcpus }})
55 success_msg: "Table system requirements validated successfully"
56 tags: always
57
58 # Not fatal here: the codec assertions in the role are the real gate. This
59 # names the likely cause early, because the distribution explains most of
60 # the failures this deployment can have.
61 - name: Warn when not running Raspberry Pi OS
62 ansible.builtin.debug:
63 msg: |
64 WARNING: {{ ansible_distribution }} {{ ansible_distribution_version }} detected.
65
66 The hardware codec nodes (/dev/video10, /dev/video11) come from the
67 bcm2835-codec driver in the downstream Raspberry Pi kernel. Ubuntu's
68 mainline kernel frequently does not expose them, and the role will
69 fail its codec assertions below if so.
70 when: ansible_distribution != "Debian"
71 tags: always
72
73 - name: Display table deployment information
74 ansible.builtin.debug:
75 msg: |
76 ============================================================================
77 D&D Table Installation Starting
78 ============================================================================
79 Target Host: {{ inventory_hostname }}
80 Target IP: {{ ansible_default_ipv4.address }}
81 OS: {{ ansible_distribution }} {{ ansible_distribution_version }}
82 Architecture: {{ ansible_architecture }}
83
84 Display path: {{ 'enabled' if dnd_table_display_enabled else 'disabled' }}
85 {{ dnd_table_width }}x{{ dnd_table_height }}@{{ dnd_table_fps }}, RTP in on :{{ dnd_table_rtp_port }}
86 Camera path: {{ 'enabled' if dnd_table_camera_enabled else 'disabled (no module fitted)' }}
87 -> {{ dnd_table_runner_host }}:{{ dnd_table_camera_rtp_port }}
88 Ceiling wash: {{ 'enabled' if dnd_wash_enabled else 'disabled' }}
89 GPIO r={{ dnd_wash_pin_r }} g={{ dnd_wash_pin_g }} b={{ dnd_wash_pin_b }} w={{ dnd_wash_pin_w }}
90
91 Images:
92 {{ dnd_table_image }}
93 {{ dnd_wash_image }}
94
95 MQTT: {{ mqtt_host }}:{{ mqtt_port }}
96 ============================================================================
97 tags: always
98
99 # ============================================================================
100 # ROLE EXECUTION ORDER
101 # ============================================================================
102
103 roles:
104 # 1. SYSTEM SETUP - Basic system configuration and packages
105 - role: system
106 tags: [system, setup]
107
108 # 2. DOCKER INSTALLATION - Install Docker using geerlingguy.docker
109 - role: geerlingguy.docker
110 tags: [docker, setup]
111
112 # 3. USER MANAGEMENT - Create user with proper groups
113 - role: user
114 tags: [user, setup]
115
116 # 4. DOCKER FRAMEWORK - Setup Docker directory structure
117 - role: docker-framework
118 tags: [docker, framework]
119
120 # 5. SECURITY HARDENING - Apply security settings
121 - role: geerlingguy.security
122 tags: [security, hardening]
123
124 # 6. TABLE - Projector, camera and ceiling wash
125 #
126 # **`wash` is deliberately NOT in this list.** A tag on a role applies to
127 # every task inside it, and it is additive: the per-task `tags: [display]`
128 # inside the role widen the selection rather than narrowing it. With `wash`
129 # here, `--tags wash` selected the whole role including the projector
130 # assertion, so deploying the ceiling wash failed whenever the projector
131 # happened to be switched off -- which it legitimately is between sessions.
132 #
133 # The wash shares no hardware with the display path (separate container,
134 # separate service, I2C rather than HDMI), so that coupling was incidental.
135 # The wash tasks carry their own `wash` tag inside the role and are
136 # selected by it directly.
137 - role: dnd-table
138 tags: [dnd, table]
139
140 # 7. MONITORING - System monitoring
141 - role: monitoring
142 tags: [monitoring, glances]
143
144 # ============================================================================
145 # POST-INSTALLATION TASKS
146 # ============================================================================
147
148 post_tasks:
149 - name: Verify Docker containers are running
150 ansible.builtin.command: docker ps --format "table {% raw %}{{.Names}}\t{{.Status}}{% endraw %}"
151 register: docker_status
152 changed_when: false
153 tags: [verification, docker]
154
155 - name: Display deployment completion summary
156 ansible.builtin.debug:
157 msg: |
158 ============================================================================
159 D&D Table Installation Complete
160 ============================================================================
161
162 Host: {{ inventory_hostname }}
163 IP Address: {{ ansible_default_ipv4.address }}
164 OS: {{ ansible_distribution }} {{ ansible_distribution_version }}
165
166 Docker Containers:
167 {{ docker_status.stdout | default('Skipped') | indent(10) }}
168
169 Verify the display path -- from the runner:
170 gst-launch-1.0 videotestsrc pattern=smpte is-live=true \
171 ! video/x-raw,width={{ dnd_table_width }},height={{ dnd_table_height }},framerate={{ dnd_table_fps }}/1 \
172 ! timeoverlay ! videoconvert \
173 ! x264enc tune=zerolatency speed-preset=ultrafast key-int-max={{ dnd_table_fps }} \
174 ! h264parse config-interval=1 ! rtph264pay pt=96 config-interval=1 \
175 ! udpsink host={{ ansible_default_ipv4.address }} port={{ dnd_table_rtp_port }} sync=false
176 {% if dnd_wash_enabled %}
177
178 Verify the ceiling wash:
179 mosquitto_pub -h {{ mqtt_host }} -t {{ dnd_wash_base_topic }}/set -m '{"scene":"torchlight"}'
180 mosquitto_sub -h {{ mqtt_host }} -t '{{ dnd_wash_base_topic }}/#' -v
181 {% endif %}
182
183 Useful commands:
184 docker logs -f dnd-table
185 docker logs -f dnd-wash
186 ============================================================================
187 tags: always
188