Secret and Dynamic Config Generation
Use when generating secrets/keys during Ansible runs, managing dynamic configuration, or implementing per-run generated values with persistence.
Rules
- ALWAYS use
env_generated_path- NEVER hardcode filename - ALWAYS use
delegate_to: localhostwhen writing generated files - ALWAYS use
create: true- file may not exist on first run - ALWAYS use
mode: "0600"- file contains private keys - NEVER use
copyortemplate- they overwrite entire file - ALWAYS provide sensible defaults when reading generated values
- NEVER fail if generated file is missing - degrade to defaults
- NEVER put dynamic/computed values in group_vars/all.yml as constants
Recent Variable Management Lessons:
9. ALWAYS provide default values for hardware-dependent variables (igpu_render_device: "/dev/dri/renderD128")
10. ALWAYS use | default('value', true) for environment lookups to prevent Jinja2 template failures
11. ALWAYS test with missing generated env files - test scenarios don't always have complete environment setup
12. ALWAYS validate network configuration computation works with default gateway values
Patterns
Writing generated values:
- name: Write WireGuard key to generated env file
ansible.builtin.blockinfile:
path: "{{ env_generated_path }}"
create: true
mode: "0600"
marker: "# {mark} WireGuard Keys (auto-generated)"
block: |
WG_PRIVATE_KEY={{ wg_private_key.stdout }}
WG_PUBLIC_KEY={{ wg_public_key.stdout }}
delegate_to: localhost
Reading generated values:
- name: Read WireGuard private key from generated env
ansible.builtin.set_fact:
_wg_private_key: >-
{{ lookup('pipe', 'grep "^WG_PRIVATE_KEY=" ' + env_generated_path + ' 2>/dev/null | cut -d= -f2')
| default('', true) }}
Path resolution:
# In group_vars/all.yml
env_generated_path: >-
{{ project_root }}/{{
'test.env.generated'
if lookup('env', 'MOLECULE_PROJECT_DIRECTORY') | length > 0
else '.env.generated' }}
Variable scoping table:
# Static constants (group_vars/all.yml)
openwrt_vm_id: 100
# Operator secrets (.env/test.env)
HOME_API_TOKEN: "secret"
# Auto-generated secrets (env_generated_path)
WG_PRIVATE_KEY: "generated"
# Dynamic runtime config (env_generated_path)
LAN_GATEWAY: "10.10.10.1"
# Per-host overrides (host_vars/<host>.yml)
ansible_host: "192.168.86.201"
Verify requirement
Every section written to the generated env file SHOULD have a corresponding assertion in molecule/default/verify.yml. This ensures the generated values are actually produced during converge and are accessible in subsequent plays.
Controller VPN key flow
The controller (build/test machine) gets its own WireGuard keypair for the VPN tunnel to the hub. The key flow spans two phases:
Key generation (
prepare.ymlor first converge): Controller private key generated withwg genkey, public key derived withwg pubkey. Both written toenv_generated_pathunder theController WireGuard Keysmarker block:WIREGUARD_PRIVATE_KEY_CONTROLLERandWIREGUARD_PUBLIC_KEY_CONTROLLER.Hub pubkey availability (
wireguard_configureplay): The hub (home WireGuard container) generates its keypair and writesWIREGUARD_HUB_PUBLIC_KEYtoenv_generated_path. The controller VPN play reads this vialookup('env', 'WIREGUARD_HUB_PUBLIC_KEY').Controller VPN setup (site.yml, after wireguard_configure): Reads both keys from env, templates
/etc/wireguard/wg0.conf, and brings upwg0. Skips gracefully if either key is empty (first run before wireguard_configure has produced the hub key).Endpoint resolution:
WIREGUARD_SERVER_ENDPOINTis computed from the hub's reachable IP + port 51820. Written toenv_generated_pathby the WireGuard configure role.
The build machine VPN play uses explicit sudo (not become: true).
The NOPASSWD sudoers entry for wg-quick, wg, and install is already
installed by setup.sh. This is a solved problem — no manual steps needed.
Anti-patterns
NEVER explain what secrets are in secret generation rules
NEVER write the same section from two different roles
NEVER use bare relative paths - they break with molecule scenarios
NEVER hardcode test.env.generated vs .env.generated decisions
NEVER use become: true on localhost — use explicit sudo. NOPASSWD sudoers is already installed by setup.sh