From eb2dd373857b7d4c58ff6e7b9f0c4a706f5bced9 Mon Sep 17 00:00:00 2001 From: Jason Rothstein Date: Mon, 7 Sep 2026 23:35:05 -0500 Subject: [PATCH] feat(ensure_apache): synchronize website content from local temp staging Replace remote git checkouts on target web nodes with an orchestrated local staging and rsync deployment pipeline. Recent OpenSSH updates in Fedora dropped agent forwarding into remote sessions, breaking remote git fetch operations. Furthermore, pulling directly from Git on production servers exposed .git repositories in public webroots and required target servers to hold Git credentials. - Create an ephemeral, unguessable local staging directory (0700) on localhost via ansible.builtin.tempfile scoped by inventory_hostname. - Stage checkouts using repo hash prefixes to prevent collision when public and private servers share identical FQDNs. - Execute shallow checkouts (depth: 1) on the control node using local credentials. - Push website content to /srv/http/{fqdn}/ via ansible.posix.synchronize with checksum delta comparisons and automated cleanup (--delete). - Introduce ensure_apache_rsync_excludes in role defaults to strip .git*, .env*, CI/CD pipelines (.gitea, .github), editor swap files, and OS metadata while preserving .htaccess and /.well-known/. - Enforce httpd_sys_content_t SELinux context recursively across webroots. - Guarantee staging directory cleanup on localhost via an always block. --- roles/ensure_apache/defaults/main.yml | 12 ++++ roles/ensure_apache/tasks/main.yml | 85 ++++++++++++++++++++++----- 2 files changed, 82 insertions(+), 15 deletions(-) diff --git a/roles/ensure_apache/defaults/main.yml b/roles/ensure_apache/defaults/main.yml index d559906..40c9653 100644 --- a/roles/ensure_apache/defaults/main.yml +++ b/roles/ensure_apache/defaults/main.yml @@ -2,3 +2,15 @@ # defaults file for ensure_apache lets_encrypt_admin: root@example.com lets_encrypt_url: https://acme-v02.api.letsencrypt.org/directory +ensure_apache_rsync_excludes: + - --exclude=*.bak + - --exclude=*.swo + - --exclude=*.swp + - --exclude=*~ + - --exclude=.DS_Store + - --exclude=.env* + - --exclude=.git* + - --exclude=.gitea + - --exclude=.github + - --exclude=.gitlab-ci.yml + - --exclude=Thumbs.db diff --git a/roles/ensure_apache/tasks/main.yml b/roles/ensure_apache/tasks/main.yml index 158d69a..357352b 100644 --- a/roles/ensure_apache/tasks/main.yml +++ b/roles/ensure_apache/tasks/main.yml @@ -187,25 +187,80 @@ - Ensure_Apache.Service_Facts - Ensure_Apache.Service_Reload - Ensure_Apache.Service_Reload_Config -- name: Ensure Website Content From Git Repos +- name: Synchronize Website Content From Git Repositories when: - ensure_apache is defined - http_vhost is defined - http_vhost is iterable - - item.fqdn is defined - - item.repo is defined - ansible.builtin.git: - accept_hostkey: 'yes' - dest: /srv/http/{{ item.fqdn }} - repo: '{{ item.repo }}' - loop: '{{ http_vhost }}' - loop_control: - label: /srv/http/{{ item.fqdn }} will be populated... - notify: - - Ensure_Apache.Package_Facts - - Ensure_Apache.Service_Facts - - Ensure_Apache.Service_Reload - - Ensure_Apache.Service_Reload_Config + - http_vhost | selectattr('repo', 'defined') | list | length > 0 + block: + - name: Create Temporary Local Staging Directory + become: false + delegate_to: localhost + ansible.builtin.tempfile: + prefix: 'ansible_web_{{ inventory_hostname }}_' + state: directory + register: ensure_apache_local_tempdir + + - name: Ensure Website Content Cloned Locally # noqa: latest[git] + when: + - item.fqdn is defined + - item.repo is defined + become: false + delegate_to: localhost + ansible.builtin.git: + accept_hostkey: true + depth: 1 + dest: '{{ ensure_apache_local_tempdir.path }}/{{ (item.repo | hash("sha256"))[:8] }}_{{ item.fqdn }}' + repo: '{{ item.repo }}' + version: '{{ item.version | default("HEAD") }}' + loop: '{{ http_vhost }}' + loop_control: + label: '{{ item.fqdn }} local clone' + + - name: Ensure Website Content Synchronized To Target + when: + - item.fqdn is defined + - item.repo is defined + ansible.posix.synchronize: + archive: true + checksum: true + delete: true + dest: '/srv/http/{{ item.fqdn }}/' + rsync_opts: '{{ item.rsync_opts | default(ensure_apache_rsync_excludes) }}' + src: '{{ ensure_apache_local_tempdir.path }}/{{ (item.repo | hash("sha256"))[:8] }}_{{ item.fqdn }}/' + loop: '{{ http_vhost }}' + loop_control: + label: '/srv/http/{{ item.fqdn }} will be synchronized' + notify: + - Ensure_Apache.Service_Reload + - Ensure_Apache.Service_Reload_Config + + - name: Ensure SELinux Context On Web Content + when: + - item.fqdn is defined + - item.repo is defined + ansible.builtin.file: + dest: '/srv/http/{{ item.fqdn }}' + group: root + mode: '0755' + owner: root + recurse: true + setype: httpd_sys_content_t + loop: '{{ http_vhost }}' + loop_control: + label: '/srv/http/{{ item.fqdn }} SELinux context' + + always: + - name: Remove Temporary Local Staging Directory + when: + - ensure_apache_local_tempdir is defined + - ensure_apache_local_tempdir.path is defined + become: false + delegate_to: localhost + ansible.builtin.file: + path: '{{ ensure_apache_local_tempdir.path }}' + state: absent - name: Ensure Services when: - ensure_apache is defined