--- - name: Install Gitea Actions self-hosted runners hosts: all become: true pre_tasks: - name: Assert host defines a runners matrix ansible.builtin.assert: that: - runners is defined - runners | length > 0 - runner_env is defined fail_msg: "Host {{ inventory_hostname }} is missing host_vars (runners / runner_env)." tasks: - name: Ensure runner service user exists ansible.builtin.user: name: "{{ runner_user }}" shell: /bin/bash create_home: true home: "{{ runner_home }}" # JS actions (actions/checkout@v4, etc.) execute with `node` on the host # executor. Without it act_runner fails: "Cannot find: node in PATH". # git is needed by checkout for its fetch step. - name: Ensure git is present ansible.builtin.apt: name: git state: present update_cache: true - name: Install Node.js {{ node_major_version }}.x (NodeSource) block: # Key is ASCII-armored, so store it as .asc — apt reads .gpg as binary # and .asc as armored; a mismatch fails repo signature verification. - name: Add NodeSource apt key ansible.builtin.get_url: url: https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key dest: /usr/share/keyrings/nodesource.asc mode: "0644" - name: Add NodeSource apt repo ansible.builtin.apt_repository: repo: "deb [signed-by=/usr/share/keyrings/nodesource.asc] https://deb.nodesource.com/node_{{ node_major_version }}.x nodistro main" filename: nodesource - name: Install nodejs ansible.builtin.apt: name: nodejs state: present update_cache: true # Security scanners used by the CI workflows. Pre-installing them (as root) # means the workflow steps find them on PATH and skip their runtime install, # which would otherwise fail writing to /usr/local/bin as the runner user. - name: Check installed Trivy version ansible.builtin.command: trivy --version register: trivy_check changed_when: false failed_when: false - name: Install Trivy {{ trivy_version }} ansible.builtin.shell: | set -o pipefail curl -sSfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh \ | sh -s -- -b /usr/local/bin v{{ trivy_version }} args: executable: /bin/bash when: trivy_version not in (trivy_check.stdout | default('')) - name: Check installed Gitleaks version ansible.builtin.command: gitleaks version register: gitleaks_check changed_when: false failed_when: false - name: Install Gitleaks {{ gitleaks_version }} ansible.builtin.unarchive: src: "https://github.com/gitleaks/gitleaks/releases/download/v{{ gitleaks_version }}/gitleaks_{{ gitleaks_version }}_linux_x64.tar.gz" dest: /usr/local/bin remote_src: true include: - gitleaks mode: "0755" when: gitleaks_version not in (gitleaks_check.stdout | default('')) # Deploy target for each project's compose stack. Owned by the runner so the # deploy job can `cp docker-compose.yml` here; the host-managed .env lives # here too. Basename matches the compose project name, preserving volumes. - name: Ensure app deploy dir exists for each project ansible.builtin.file: path: "{{ app_base_dir }}/{{ item.project }}" state: directory owner: "{{ runner_user }}" group: "{{ runner_user }}" mode: "0755" loop: "{{ runners }}" loop_control: label: "{{ item.project }}" - name: Install runners for each project ansible.builtin.include_tasks: tasks/install_project.yml loop: "{{ runners }}" loop_control: loop_var: project_spec label: "{{ project_spec.project }}" # The build job runs `docker build` on the host, talking to the daemon via # /var/run/docker.sock. Without docker group membership the runner user gets # "permission denied ... unix:///var/run/docker.sock". - name: Add runner user to the docker group ansible.builtin.user: name: "{{ runner_user }}" groups: docker append: true register: runner_docker_group # Group membership is only read at process start, so already-running runner # services must be restarted to gain socket access. - name: Restart runner services to apply docker group membership ansible.builtin.shell: "systemctl restart 'gitea-runner-*.service'" when: runner_docker_group is changed changed_when: true