fix(deploy): link the pinned Python interpreter before facts need it

group_vars/all/00_general.yml pins ansible_python_interpreter to
/usr/local/bin/python3 for every host. Fact gathering is the first module
run, so nothing inside the play can create that path, and a managed server
that does not already carry it dies at 'Gathering Facts' with rc 127.

The published deploy recipe addresses exactly such a server:
ghcr.io/kevinveenbirkenbach/base-debian:latest carries /usr/bin/python3 and
no /usr/local/bin/python3, so a reader following the documented instructions
on a fresh Debian hits the same wall. The web-app-docs instruction replay is
what surfaced it, across four jobs of run 39040406916.

A pre_task links the discovered python3 into place through
ansible.builtin.script, which needs no interpreter on the target, and the
play then gathers facts explicitly. Dropping the pin instead would change
which interpreter every module runs under on every existing host.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
kevinveenbirkenbachandClaude Opus 5 committed 2026-10-17 19:33:18 +02:00
1 parent a9f2c7209c
commit 87beaced7b
2 files changed
+40

No files matched your search

+11
View File
@@ -15,6 +15,17 @@
COMPOSE_DOCKER_CLI_BUILD: "{{ BUILDKIT_ENABLED | int }}"
INFINITO_RESCUE_DIAGNOSTICS_DIR: "{{ DIR_RESCUE_DIAGNOSTICS }}"
CONTAINER_EXEC_TIMEOUT: "{{ SYS_TIMEOUT_CONTAINER_EXEC }}"
gather_facts: false
pre_tasks:
- name: "🐍 Link the managed Python interpreter before any module needs it"
ansible.builtin.script: scripts/system/python/managed_interpreter.sh
environment: {}
register: managed_interpreter
changed_when: "'linked' in managed_interpreter.stdout"
- name: "🔍 Gather facts"
ansible.builtin.setup:
tasks:
- name: "Load 'constructor' tasks"
include_tasks: "tasks/stages/01_constructor.yml"
+29
View File
@@ -0,0 +1,29 @@
#!/bin/sh
# shellcheck shell=sh
#
# group_vars/all/00_general.yml pins ansible_python_interpreter to
# /usr/local/bin/python3, and fact gathering is the first thing that needs it,
# so nothing inside the play can create it: a bare server fails at 'Gathering
# Facts' with "The module interpreter '/usr/local/bin/python3' was not found",
# rc 127.
#
# Runs through ansible.builtin.script, which needs no python on the target.
#
# Output: 'linked' when it created the symlink, 'present' when it did not.
set -eu
if [ -x /usr/local/bin/python3 ]; then
echo present
exit 0
fi
discovered="$(command -v python3 || true)"
if [ -z "${discovered}" ]; then
echo "FAILURE: no python3 on PATH; a managed server needs one before any module can run" >&2
exit 1
fi
mkdir -p /usr/local/bin
ln -sfn "${discovered}" /usr/local/bin/python3
echo linked