---
- name: π Detect container environment (systemd-detect-virt)
ansible.builtin.command: systemd-detect-virt --container
register: systemd_detect_virt_container
changed_when: false
failed_when: false
- name: π Normalize DOCKER_IN_CONTAINER from runtime detection
ansible.builtin.set_fact:
DOCKER_IN_CONTAINER: "{{ (systemd_detect_virt_container.rc | default(1) | int) == 0 }}"
- name: "π Get apt ready before the first apt task"
ansible.builtin.include_tasks: "./tasks/utils/apt/ready.yml"
when: ansible_facts['os_family'] == 'Debian'
- name: π Check DNS resolution for test domain
ansible.builtin.command: "getent hosts {{ DOMAIN_TEST }}"
register: dns_check
changed_when: false
failed_when: dns_check.rc != 0
when:
- MODE_ASSERT | bool
- not (DOMAIN_PRIMARY is search('\.onion$'))
- name: π Merge variables
block:
- name: π Merge networks definitions
ansible.builtin.set_fact:
networks: "{{ defaults_networks | combine(networks | default({}, true), recursive=True) }}"
- name: π§ Render the applications SPOT once for the merge block
applications_carrier: {}
- name: "β
Sanity check: all group_names must exist in applications"
ansible.builtin.assert:
that:
- group_name in lookup('applications')
fail_msg: "Group '{{ group_name }}' has no entry in 'applications'"
success_msg: "Group '{{ group_name }}' is defined in 'applications'"
loop: "{{ group_names }}"
loop_control:
loop_var: group_name
when: MODE_ASSERT | bool
- name: π Merge current play applications
ansible.builtin.set_fact:
CURRENT_PLAY_APPLICATIONS: "{{ lookup('applications_current_play') }}"
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
- name: π Merge current play domain definitions
ansible.builtin.set_fact:
CURRENT_PLAY_DOMAINS: >-
{{
lookup('applications')
| canonical_domains_map(
DOMAIN_PRIMARY,
recursive=True,
roles_base_dir=(lookup('path_absolute', 'roles')),
seed=(CURRENT_PLAY_APPLICATIONS | dict2items | map(attribute='key') | list)
)
}}
- name: π§ Set CURRENT_PLAY_REDIRECT_DOMAINS
ansible.builtin.set_fact:
CURRENT_PLAY_REDIRECT_DOMAINS: "{{ lookup('current_play_redirect_domains') }}"
- name: π§ Set current play all domains incl. www redirect if enabled
ansible.builtin.set_fact:
CURRENT_PLAY_DOMAINS_ALL: >-
{{
(
CURRENT_PLAY_DOMAINS
| combine(
CURRENT_PLAY_REDIRECT_DOMAINS | default([])
| items2dict(key_name='source', value_name='source'),
recursive=True
)
)
| generate_all_domains(WWW_REDIRECT_ENABLED | bool)
}}
- name: β
Assert OIDC resolves lazily (inventory overrides live in OIDC_OVERRIDE)
ansible.builtin.assert:
that:
- OIDC.CLIENT.DISCOVERY_DOCUMENT is defined
fail_msg: >-
OIDC does not resolve to the full defaults tree. An inventory most
likely still defines 'OIDC:' at host level, shadowing the lazy
group_var β migrate that block to 'OIDC_OVERRIDE:' so the issuer can
family-align to each consuming role.
quiet: true
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
- name: π Merge design configuration
ansible.builtin.set_fact:
design: "{{ defaults_design | combine(design | default({}, true), recursive=True) }}"
- name: π Merge service_provider configuration
ansible.builtin.set_fact:
service_provider: "{{ defaults_service_provider | combine(service_provider | default({}, true), recursive=True) }}"
- name: π§ Drop the merge-block carrier so later stages re-render
applications_carrier:
clear: true
- name: "πΎ Persist {{ SOFTWARE_NAME }} version"
ansible.builtin.include_role:
name: sys-version
- name: π¬ init root user
ansible.builtin.include_role:
name: user-root
- name: "π₯Ύ Bootstrap svc-net-wireguard"
when: "'svc-net-wireguard' | application_allowed(group_names, lookup('deployment').whitelist)"
block:
- name: π§ Carry the applications SPOT through the mesh bootstrap
applications_carrier: {}
- name: "π Load svc-net-wireguard"
ansible.builtin.include_role:
name: svc-net-wireguard
- name: π§ Drop the mesh-bootstrap carrier so later stages re-render
applications_carrier:
clear: true
- name: π update device
ansible.builtin.include_role:
name: update
- name: "β
Ensure correct timezone is '{{ HOST_TIMEZONE }}'"
community.general.timezone:
name: "{{ HOST_TIMEZONE }}"
when: not DOCKER_IN_CONTAINER | bool
- name: π§Ή System Cleanup
ansible.builtin.include_role:
name: sys-cleanup
when:
- MODE_CLEANUP | bool
- DOCKER_IN_CONTAINER | bool
- name: "π₯οΈ Auto-place single-host shared services on first manager (swarm mode)"
ansible.builtin.add_host:
name: "{{ (groups['svc-swarm-manager'] | default([]))[0] }}"
groups: "{{ role_id }}"
loop: "{{ lookup('roles_with_placement', 'manager') }}"
loop_control:
loop_var: role_id
label: "{{ role_id }}"
when:
- (groups['svc-swarm-manager'] | default([]) | length) > 0
- role_id in groups
- (groups[role_id] | default([]) | length) == 0
run_once: true
changed_when: false
- name: "π§ auto-place svc-prx-openresty (swarm manager, first compose host) for vhosts"
ansible.builtin.add_host:
name: "{{ (groups['svc-swarm-manager'] | default([]))[0] | default(ansible_play_hosts | first) }}"
groups: svc-prx-openresty
vars:
_web_groups: >-
{{
groups
| dict2items
| selectattr('key', 'match', '^web-')
| rejectattr('value', 'equalto', [])
| list
}}
when:
- (groups['svc-prx-openresty'] | default([]) | length) == 0
- (_web_groups | length) > 0
run_once: true
changed_when: false
- name: π§ Render the applications SPOT once for the bootstrap section
applications_carrier: {}
when: DEPLOYMENT_MODE == 'swarm'
- name: "π₯Ύ Bootstrap svc-swarm-node"
ansible.builtin.include_role:
name: svc-swarm-node
when: "'svc-swarm-node' | application_allowed(group_names, lookup('deployment').whitelist)"
- name: "π Open service-loader preload context BEFORE Bootstrap"
ansible.builtin.set_fact:
service_loader_post_run: []
service_loader_preloading: true
changed_when: false
- name: "π₯Ύ Bootstrap NFS clients + registry + sandbox runtimes (MUST precede Preload)"
ansible.builtin.include_tasks: ./tasks/utils/setup/group.yml
loop:
- svc-storage
- svc-registry
- svc-virt
loop_control:
loop_var: role_group
label: "{{ role_group }}"
- name: π Include Token Store
ansible.builtin.include_role:
name: sys-token-store
when: run_once_sys_token_store is not defined
- name: π§ Render the applications SPOT once and carry it into every task's worker
applications_carrier: {}
- name: π οΈ Preload shared services
ansible.builtin.include_role:
name: sys-service-loader
- name: "π Close the service-loader preload context"
ansible.builtin.set_fact:
service_loader_preloading: false
changed_when: false
- name: "π§© Load base roles"
ansible.builtin.include_tasks: ./tasks/utils/setup/group.yml
loop: "{{ lookup('stage_groups', 'constructor') }}"
loop_control:
loop_var: role_group
label: "{{ role_group }}"
- name: π¦ Install inventory-defined packages
package_install:
id: "{{ PACKAGES }}"
when: PACKAGES | default([]) | length > 0