From 685fe51854c2cc15516a7c6875e86586b9193910 Mon Sep 17 00:00:00 2001 From: vshumin Date: Fri, 5 Jun 2026 18:48:05 +0000 Subject: [PATCH] Add /healthcheck endpoint required by cl-mirrors mirrorservice MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CloudLinux mirrorservice (https://repo.cloudlinux.com/cloudlinux/mirrorlists/cl-mirrors) filters mirrors lacking a fresh /healthcheck endpoint. Mirrors deployed via these playbooks previously registered in the service but never appeared in mirrorlist responses (status 404 on /healthcheck → marked unavailable). This change wires up /healthcheck for all four playbooks (combined-mirror, complete-swng-rsync, specific-version-rsync, yum-reposync): 1. Installs update-healthcheck.sh which writes /var/www/healthcheck.html in the format expected by the mirrorservice (" | Status: OK | YYYY/MM/DD HH:MM:SS") 2. Exposes /healthcheck via nginx (HTTP + HTTPS) as a static alias 3. Adds ExecStartPost to the sync service so timestamp refreshes on every successful sync run 4. Generates initial /healthcheck during playbook run so the file exists before the first sync completes Timestamp staleness threshold is 12h (mirrorservice config); the sync schedule of every 4h keeps it well within the window. --- ansible/combined-mirror/defaults/main.yml | 4 ++++ ansible/combined-mirror/nginx-https.conf.j2 | 6 +++++ ansible/combined-mirror/nginx.conf.j2 | 6 +++++ ansible/combined-mirror/playbook.yml | 20 ++++++++++++++++ .../combined-mirror/swng-mirror.service.j2 | 1 + ansible/combined-mirror/update-healthcheck.sh | 23 +++++++++++++++++++ ansible/complete-swng-rsync/defaults/main.yml | 4 ++++ .../complete-swng-rsync/nginx-https.conf.j2 | 6 +++++ ansible/complete-swng-rsync/nginx.conf.j2 | 12 ++++++++++ ansible/complete-swng-rsync/playbook.yml | 20 ++++++++++++++++ .../swng-mirror.service.j2 | 1 + .../complete-swng-rsync/update-healthcheck.sh | 23 +++++++++++++++++++ .../defaults/main.yml | 4 ++++ .../nginx-https.conf.j2 | 6 +++++ .../nginx.conf.j2 | 6 +++++ .../playbook.yml | 20 ++++++++++++++++ .../swng-version-mirror.service.j2 | 1 + .../update-healthcheck.sh | 23 +++++++++++++++++++ ansible/yum-reposync/defaults/main.yml | 4 ++++ ansible/yum-reposync/nginx-https.conf.j2 | 6 +++++ ansible/yum-reposync/nginx.conf.j2 | 6 +++++ ansible/yum-reposync/playbook.yml | 20 ++++++++++++++++ ansible/yum-reposync/swng-reposync.service.j2 | 1 + ansible/yum-reposync/update-healthcheck.sh | 23 +++++++++++++++++++ 24 files changed, 246 insertions(+) create mode 100755 ansible/combined-mirror/update-healthcheck.sh create mode 100755 ansible/complete-swng-rsync/update-healthcheck.sh create mode 100755 ansible/specific-version-rsync(Recomended)/update-healthcheck.sh create mode 100755 ansible/yum-reposync/update-healthcheck.sh diff --git a/ansible/combined-mirror/defaults/main.yml b/ansible/combined-mirror/defaults/main.yml index 75295be..81881ef 100644 --- a/ansible/combined-mirror/defaults/main.yml +++ b/ansible/combined-mirror/defaults/main.yml @@ -30,3 +30,7 @@ certbot_cron_enabled: true certbot_cron_schedule: minute: 0 hour: 3 + +# /healthcheck endpoint (required by cl-mirrors mirrorservice) +healthcheck_file: /var/www/healthcheck.html +healthcheck_source_name: swng.cloudlinux.com diff --git a/ansible/combined-mirror/nginx-https.conf.j2 b/ansible/combined-mirror/nginx-https.conf.j2 index 118e8e4..c875f19 100644 --- a/ansible/combined-mirror/nginx-https.conf.j2 +++ b/ansible/combined-mirror/nginx-https.conf.j2 @@ -31,6 +31,12 @@ server { autoindex on; } + # Required by cl-mirrors mirrorservice (health-check) + location = /healthcheck { + alias {{ healthcheck_file }}; + default_type text/html; + } + # SWNG repositories location /swng/ { alias {{ swng_mirror_path }}/; diff --git a/ansible/combined-mirror/nginx.conf.j2 b/ansible/combined-mirror/nginx.conf.j2 index 1d9b4fc..a0ed31e 100644 --- a/ansible/combined-mirror/nginx.conf.j2 +++ b/ansible/combined-mirror/nginx.conf.j2 @@ -38,6 +38,12 @@ server { autoindex on; } + # Required by cl-mirrors mirrorservice (health-check) + location = /healthcheck { + alias {{ healthcheck_file }}; + default_type text/html; + } + # SWNG repositories location /swng/ { alias {{ swng_mirror_path }}/; diff --git a/ansible/combined-mirror/playbook.yml b/ansible/combined-mirror/playbook.yml index 185c402..7248686 100644 --- a/ansible/combined-mirror/playbook.yml +++ b/ansible/combined-mirror/playbook.yml @@ -141,6 +141,26 @@ debug: msg: "{{ timer_status.stdout_lines }}" + - name: Install /healthcheck update script (required by cl-mirrors mirrorservice) + ansible.builtin.copy: + src: update-healthcheck.sh + dest: /usr/local/bin/update-healthcheck.sh + mode: '0755' + + - name: Ensure /healthcheck file directory exists + ansible.builtin.file: + path: "{{ healthcheck_file | dirname }}" + state: directory + mode: '0755' + + - name: Generate initial /healthcheck so it is available before first sync completes + ansible.builtin.command: + cmd: /usr/local/bin/update-healthcheck.sh "{{ healthcheck_source_name }}" PENDING + args: + creates: "{{ healthcheck_file }}" + environment: + HEALTHCHECK_FILE: "{{ healthcheck_file }}" + - name: Install Nginx package: name: nginx diff --git a/ansible/combined-mirror/swng-mirror.service.j2 b/ansible/combined-mirror/swng-mirror.service.j2 index da50f5b..e8d944b 100644 --- a/ansible/combined-mirror/swng-mirror.service.j2 +++ b/ansible/combined-mirror/swng-mirror.service.j2 @@ -5,5 +5,6 @@ After=network.target [Service] Type=oneshot ExecStart=/usr/bin/rsync -av --delete {{ swng_rsync_source }} {{ swng_mirror_path }}/ +ExecStartPost=/usr/local/bin/update-healthcheck.sh "{{ healthcheck_source_name }}" OK StandardOutput=append:{{ swng_sync_log }} StandardError=append:{{ swng_sync_log }} diff --git a/ansible/combined-mirror/update-healthcheck.sh b/ansible/combined-mirror/update-healthcheck.sh new file mode 100755 index 0000000..f5932f6 --- /dev/null +++ b/ansible/combined-mirror/update-healthcheck.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# Updates the /healthcheck endpoint timestamp so the CloudLinux cl-mirrors +# mirrorservice can verify this mirror is fresh. +# Called by the sync service unit's ExecStartPost after a successful run. +# +# Required by: https://repo.cloudlinux.com/cloudlinux/mirrorlists/cl-mirrors +# Format: line " | Status: OK | YYYY/MM/DD HH:MM:SS" with timestamp <= 12h old. + +set -eu + +SOURCE_NAME="${1:-swng.cloudlinux.com}" +STATUS="${2:-OK}" +HEALTHCHECK_FILE="${HEALTHCHECK_FILE:-/var/www/healthcheck.html}" +NOW=$(date -u '+%Y/%m/%d %H:%M:%S') + +mkdir -p "$(dirname "$HEALTHCHECK_FILE")" +cat > "$HEALTHCHECK_FILE" < +Last healthcheck update: $NOW UTC
+

Sync status

+$SOURCE_NAME | Status: $STATUS | $NOW
+ +HTML diff --git a/ansible/complete-swng-rsync/defaults/main.yml b/ansible/complete-swng-rsync/defaults/main.yml index a51b85d..1c00c4e 100644 --- a/ansible/complete-swng-rsync/defaults/main.yml +++ b/ansible/complete-swng-rsync/defaults/main.yml @@ -24,3 +24,7 @@ certbot_cron_schedule: minute: 0 hour: 3 + +# /healthcheck endpoint (required by cl-mirrors mirrorservice) +healthcheck_file: /var/www/healthcheck.html +healthcheck_source_name: swng.cloudlinux.com diff --git a/ansible/complete-swng-rsync/nginx-https.conf.j2 b/ansible/complete-swng-rsync/nginx-https.conf.j2 index e1fa07b..92533de 100644 --- a/ansible/complete-swng-rsync/nginx-https.conf.j2 +++ b/ansible/complete-swng-rsync/nginx-https.conf.j2 @@ -25,6 +25,12 @@ server { access_log /var/log/nginx/swng-mirror-https-access.log; error_log /var/log/nginx/swng-mirror-https-error.log; + # Required by cl-mirrors mirrorservice (health-check) + location = /healthcheck { + alias {{ healthcheck_file }}; + default_type text/html; + } + # SWNG repositories location / { try_files $uri $uri/ =404; diff --git a/ansible/complete-swng-rsync/nginx.conf.j2 b/ansible/complete-swng-rsync/nginx.conf.j2 index aaae6db..761fca5 100644 --- a/ansible/complete-swng-rsync/nginx.conf.j2 +++ b/ansible/complete-swng-rsync/nginx.conf.j2 @@ -10,6 +10,12 @@ server { root {{ certbot_webroot | default('/var/www/mirrors/acme') }}; } + # Required by cl-mirrors mirrorservice (health-check) + location = /healthcheck { + alias {{ healthcheck_file }}; + default_type text/html; + } + # Normalize SWNG path (no trailing slash) location = /swng { return 301 https://$server_name/swng/; @@ -37,6 +43,12 @@ server { access_log /var/log/nginx/swng-mirror-access.log; error_log /var/log/nginx/swng-mirror-error.log; + # Required by cl-mirrors mirrorservice (health-check) + location = /healthcheck { + alias {{ healthcheck_file }}; + default_type text/html; + } + # SWNG repositories location / { try_files $uri $uri/ =404; diff --git a/ansible/complete-swng-rsync/playbook.yml b/ansible/complete-swng-rsync/playbook.yml index e45da5c..518340d 100644 --- a/ansible/complete-swng-rsync/playbook.yml +++ b/ansible/complete-swng-rsync/playbook.yml @@ -73,6 +73,26 @@ debug: msg: "{{ timer_status.stdout_lines }}" + - name: Install /healthcheck update script (required by cl-mirrors mirrorservice) + ansible.builtin.copy: + src: update-healthcheck.sh + dest: /usr/local/bin/update-healthcheck.sh + mode: '0755' + + - name: Ensure /healthcheck file directory exists + ansible.builtin.file: + path: "{{ healthcheck_file | dirname }}" + state: directory + mode: '0755' + + - name: Generate initial /healthcheck so it is available before first sync completes + ansible.builtin.command: + cmd: /usr/local/bin/update-healthcheck.sh "{{ healthcheck_source_name }}" PENDING + args: + creates: "{{ healthcheck_file }}" + environment: + HEALTHCHECK_FILE: "{{ healthcheck_file }}" + - name: Install Nginx package: name: nginx diff --git a/ansible/complete-swng-rsync/swng-mirror.service.j2 b/ansible/complete-swng-rsync/swng-mirror.service.j2 index 4ce7768..5389aa1 100644 --- a/ansible/complete-swng-rsync/swng-mirror.service.j2 +++ b/ansible/complete-swng-rsync/swng-mirror.service.j2 @@ -5,5 +5,6 @@ After=network.target [Service] Type=oneshot ExecStart=/usr/bin/rsync -av --delete {{ rsync_source }} {{ swng_mirror_path }}/ +ExecStartPost=/usr/local/bin/update-healthcheck.sh "{{ healthcheck_source_name }}" OK StandardOutput=append:{{ sync_log_file }} StandardError=append:{{ sync_log_file }} diff --git a/ansible/complete-swng-rsync/update-healthcheck.sh b/ansible/complete-swng-rsync/update-healthcheck.sh new file mode 100755 index 0000000..f5932f6 --- /dev/null +++ b/ansible/complete-swng-rsync/update-healthcheck.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# Updates the /healthcheck endpoint timestamp so the CloudLinux cl-mirrors +# mirrorservice can verify this mirror is fresh. +# Called by the sync service unit's ExecStartPost after a successful run. +# +# Required by: https://repo.cloudlinux.com/cloudlinux/mirrorlists/cl-mirrors +# Format: line " | Status: OK | YYYY/MM/DD HH:MM:SS" with timestamp <= 12h old. + +set -eu + +SOURCE_NAME="${1:-swng.cloudlinux.com}" +STATUS="${2:-OK}" +HEALTHCHECK_FILE="${HEALTHCHECK_FILE:-/var/www/healthcheck.html}" +NOW=$(date -u '+%Y/%m/%d %H:%M:%S') + +mkdir -p "$(dirname "$HEALTHCHECK_FILE")" +cat > "$HEALTHCHECK_FILE" < +Last healthcheck update: $NOW UTC
+

Sync status

+$SOURCE_NAME | Status: $STATUS | $NOW
+ +HTML diff --git a/ansible/specific-version-rsync(Recomended)/defaults/main.yml b/ansible/specific-version-rsync(Recomended)/defaults/main.yml index 21ed5bb..b675c41 100644 --- a/ansible/specific-version-rsync(Recomended)/defaults/main.yml +++ b/ansible/specific-version-rsync(Recomended)/defaults/main.yml @@ -25,3 +25,7 @@ certbot_cron_enabled: true certbot_cron_schedule: minute: 0 hour: 3 + +# /healthcheck endpoint (required by cl-mirrors mirrorservice) +healthcheck_file: /var/www/healthcheck.html +healthcheck_source_name: swng.cloudlinux.com diff --git a/ansible/specific-version-rsync(Recomended)/nginx-https.conf.j2 b/ansible/specific-version-rsync(Recomended)/nginx-https.conf.j2 index ad4e26f..e8808bf 100644 --- a/ansible/specific-version-rsync(Recomended)/nginx-https.conf.j2 +++ b/ansible/specific-version-rsync(Recomended)/nginx-https.conf.j2 @@ -26,6 +26,12 @@ server { error_log /var/log/nginx/swng-{{ cloudlinux_version }}-mirror-https-error.log; # SWNG CloudLinux repositories + # Required by cl-mirrors mirrorservice (health-check) + location = /healthcheck { + alias {{ healthcheck_file }}; + default_type text/html; + } + location / { try_files $uri $uri/ =404; } diff --git a/ansible/specific-version-rsync(Recomended)/nginx.conf.j2 b/ansible/specific-version-rsync(Recomended)/nginx.conf.j2 index 229bd76..193fbd9 100644 --- a/ansible/specific-version-rsync(Recomended)/nginx.conf.j2 +++ b/ansible/specific-version-rsync(Recomended)/nginx.conf.j2 @@ -10,6 +10,12 @@ server { root {{ certbot_webroot | default('/var/www/mirrors/acme') }}; } + # Required by cl-mirrors mirrorservice (health-check) + location = /healthcheck { + alias {{ healthcheck_file }}; + default_type text/html; + } + # Normalize SWNG path (no trailing slash) location = /swng { return 301 https://$server_name/swng/; diff --git a/ansible/specific-version-rsync(Recomended)/playbook.yml b/ansible/specific-version-rsync(Recomended)/playbook.yml index 3987d93..0fb5237 100644 --- a/ansible/specific-version-rsync(Recomended)/playbook.yml +++ b/ansible/specific-version-rsync(Recomended)/playbook.yml @@ -77,6 +77,26 @@ debug: msg: "{{ timer_status.stdout_lines }}" + - name: Install /healthcheck update script (required by cl-mirrors mirrorservice) + ansible.builtin.copy: + src: update-healthcheck.sh + dest: /usr/local/bin/update-healthcheck.sh + mode: '0755' + + - name: Ensure /healthcheck file directory exists + ansible.builtin.file: + path: "{{ healthcheck_file | dirname }}" + state: directory + mode: '0755' + + - name: Generate initial /healthcheck so it is available before first sync completes + ansible.builtin.command: + cmd: /usr/local/bin/update-healthcheck.sh "{{ healthcheck_source_name }}" PENDING + args: + creates: "{{ healthcheck_file }}" + environment: + HEALTHCHECK_FILE: "{{ healthcheck_file }}" + - name: Install Nginx package: name: nginx diff --git a/ansible/specific-version-rsync(Recomended)/swng-version-mirror.service.j2 b/ansible/specific-version-rsync(Recomended)/swng-version-mirror.service.j2 index fb10dde..a7cd0c1 100644 --- a/ansible/specific-version-rsync(Recomended)/swng-version-mirror.service.j2 +++ b/ansible/specific-version-rsync(Recomended)/swng-version-mirror.service.j2 @@ -5,5 +5,6 @@ After=network.target [Service] Type=oneshot ExecStart=/usr/bin/rsync -av --delete {{ rsync_source }} {{ swng_mirror_path }}/ +ExecStartPost=/usr/local/bin/update-healthcheck.sh "{{ healthcheck_source_name }}" OK StandardOutput=append:{{ sync_log_file }} StandardError=append:{{ sync_log_file }} diff --git a/ansible/specific-version-rsync(Recomended)/update-healthcheck.sh b/ansible/specific-version-rsync(Recomended)/update-healthcheck.sh new file mode 100755 index 0000000..f5932f6 --- /dev/null +++ b/ansible/specific-version-rsync(Recomended)/update-healthcheck.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# Updates the /healthcheck endpoint timestamp so the CloudLinux cl-mirrors +# mirrorservice can verify this mirror is fresh. +# Called by the sync service unit's ExecStartPost after a successful run. +# +# Required by: https://repo.cloudlinux.com/cloudlinux/mirrorlists/cl-mirrors +# Format: line " | Status: OK | YYYY/MM/DD HH:MM:SS" with timestamp <= 12h old. + +set -eu + +SOURCE_NAME="${1:-swng.cloudlinux.com}" +STATUS="${2:-OK}" +HEALTHCHECK_FILE="${HEALTHCHECK_FILE:-/var/www/healthcheck.html}" +NOW=$(date -u '+%Y/%m/%d %H:%M:%S') + +mkdir -p "$(dirname "$HEALTHCHECK_FILE")" +cat > "$HEALTHCHECK_FILE" < +Last healthcheck update: $NOW UTC
+

Sync status

+$SOURCE_NAME | Status: $STATUS | $NOW
+ +HTML diff --git a/ansible/yum-reposync/defaults/main.yml b/ansible/yum-reposync/defaults/main.yml index 56f9e4d..a112358 100644 --- a/ansible/yum-reposync/defaults/main.yml +++ b/ansible/yum-reposync/defaults/main.yml @@ -41,3 +41,7 @@ swng_repos: # baseurl: https://upstream.cloudlinux.com/swng/8/x86_64/ # module_platform_id: platform:el8 # enabled: true + +# /healthcheck endpoint (required by cl-mirrors mirrorservice) +healthcheck_file: /var/www/healthcheck.html +healthcheck_source_name: swng.cloudlinux.com diff --git a/ansible/yum-reposync/nginx-https.conf.j2 b/ansible/yum-reposync/nginx-https.conf.j2 index 737cf44..edb01fa 100644 --- a/ansible/yum-reposync/nginx-https.conf.j2 +++ b/ansible/yum-reposync/nginx-https.conf.j2 @@ -25,6 +25,12 @@ server { access_log /var/log/nginx/swng-reposync-mirror-https-access.log; error_log /var/log/nginx/swng-reposync-mirror-https-error.log; + # Required by cl-mirrors mirrorservice (health-check) + location = /healthcheck { + alias {{ healthcheck_file }}; + default_type text/html; + } + # SWNG repositories location / { try_files $uri $uri/ =404; diff --git a/ansible/yum-reposync/nginx.conf.j2 b/ansible/yum-reposync/nginx.conf.j2 index 3be75e1..7aae147 100644 --- a/ansible/yum-reposync/nginx.conf.j2 +++ b/ansible/yum-reposync/nginx.conf.j2 @@ -14,6 +14,12 @@ server { access_log /var/log/nginx/swng-reposync-mirror-access.log; error_log /var/log/nginx/swng-reposync-mirror-error.log; + # Required by cl-mirrors mirrorservice (health-check) + location = /healthcheck { + alias {{ healthcheck_file }}; + default_type text/html; + } + # SWNG repositories location / { try_files $uri $uri/ =404; diff --git a/ansible/yum-reposync/playbook.yml b/ansible/yum-reposync/playbook.yml index f6aff91..13c6501 100644 --- a/ansible/yum-reposync/playbook.yml +++ b/ansible/yum-reposync/playbook.yml @@ -104,6 +104,26 @@ debug: msg: "{{ timer_status.stdout_lines }}" + - name: Install /healthcheck update script (required by cl-mirrors mirrorservice) + ansible.builtin.copy: + src: update-healthcheck.sh + dest: /usr/local/bin/update-healthcheck.sh + mode: '0755' + + - name: Ensure /healthcheck file directory exists + ansible.builtin.file: + path: "{{ healthcheck_file | dirname }}" + state: directory + mode: '0755' + + - name: Generate initial /healthcheck so it is available before first sync completes + ansible.builtin.command: + cmd: /usr/local/bin/update-healthcheck.sh "{{ healthcheck_source_name }}" PENDING + args: + creates: "{{ healthcheck_file }}" + environment: + HEALTHCHECK_FILE: "{{ healthcheck_file }}" + - name: Install Nginx package: name: nginx diff --git a/ansible/yum-reposync/swng-reposync.service.j2 b/ansible/yum-reposync/swng-reposync.service.j2 index 2b7eb94..b58159f 100644 --- a/ansible/yum-reposync/swng-reposync.service.j2 +++ b/ansible/yum-reposync/swng-reposync.service.j2 @@ -14,5 +14,6 @@ ExecStart=/usr/bin/reposync -p {{ swng_mirror_path }}/ --repo {{ repo.name }} ExecStartPost=/usr/bin/createrepo {{ swng_mirror_path }}/{{ repo.name }}/ {% endif %} {% endfor %} +ExecStartPost=/usr/local/bin/update-healthcheck.sh "{{ healthcheck_source_name }}" OK StandardOutput=append:{{ sync_log_file }} StandardError=append:{{ sync_log_file }} diff --git a/ansible/yum-reposync/update-healthcheck.sh b/ansible/yum-reposync/update-healthcheck.sh new file mode 100755 index 0000000..f5932f6 --- /dev/null +++ b/ansible/yum-reposync/update-healthcheck.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# Updates the /healthcheck endpoint timestamp so the CloudLinux cl-mirrors +# mirrorservice can verify this mirror is fresh. +# Called by the sync service unit's ExecStartPost after a successful run. +# +# Required by: https://repo.cloudlinux.com/cloudlinux/mirrorlists/cl-mirrors +# Format: line " | Status: OK | YYYY/MM/DD HH:MM:SS" with timestamp <= 12h old. + +set -eu + +SOURCE_NAME="${1:-swng.cloudlinux.com}" +STATUS="${2:-OK}" +HEALTHCHECK_FILE="${HEALTHCHECK_FILE:-/var/www/healthcheck.html}" +NOW=$(date -u '+%Y/%m/%d %H:%M:%S') + +mkdir -p "$(dirname "$HEALTHCHECK_FILE")" +cat > "$HEALTHCHECK_FILE" < +Last healthcheck update: $NOW UTC
+

Sync status

+$SOURCE_NAME | Status: $STATUS | $NOW
+ +HTML