#!/usr/bin/bash set -euo pipefail IFS=$'\n\t' SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" if [[ -d "${SCRIPT_DIR}/../../.git" ]] || [[ -f "${SCRIPT_DIR}/../../nodo/config/etc/mirrors.env" ]]; then REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" else REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" fi if [[ -f "${REPO_ROOT}/nodo/config/etc/mirrors.env" ]]; then # shellcheck source=/dev/null source "${REPO_ROOT}/nodo/config/etc/mirrors.env" elif [[ -f "${REPO_ROOT}/config/etc/mirrors.env" ]]; then # shellcheck source=/dev/null source "${REPO_ROOT}/config/etc/mirrors.env" elif [[ -f "${REPO_ROOT}/etc/mirrors.env" ]]; then # shellcheck source=/dev/null source "${REPO_ROOT}/etc/mirrors.env" fi STAGING_DIR="${MIRRORS_STAGING_DIR:-$HOME/ipfs/staging}" LOG_DIR="${MIRRORS_LOG_DIR:-$HOME/ipfs/logs}" mkdir -p "${STAGING_DIR}" "${LOG_DIR}" LOCK_FILE="${MIRRORS_LOCK_FILE:-/tmp/mxos-repo-sync.lock}" release_intelligent_lock() { local exit_code=$? if [[ -f "${LOCK_FILE}" ]]; then local recorded_pid="" recorded_pid="$(head -n 1 "${LOCK_FILE}" 2>/dev/null | tr -dc '0-9' || true)" if [[ "${recorded_pid}" == "$$" ]]; then rm -f "${LOCK_FILE}" fi fi exit "${exit_code}" } acquire_intelligent_lock() { local lock_dir lock_dir="$(dirname "${LOCK_FILE}")" mkdir -p "${lock_dir}" # 1. Si existe el candado, evaluar si el proceso registrado sigue vivo en el sistema if [[ -f "${LOCK_FILE}" ]]; then local lock_pid="" lock_pid="$(head -n 1 "${LOCK_FILE}" 2>/dev/null | tr -dc '0-9' || true)" if [[ -n "${lock_pid}" ]]; then if kill -0 "${lock_pid}" 2>/dev/null; then local cmdline="" cmdline="$(tr '\0' ' ' < "/proc/${lock_pid}/cmdline" 2>/dev/null || true)" if [[ "${cmdline}" =~ mxos|reposync|dnf5|rsync|bash ]]; then echo "[$(date -u +"%Y-%m-%dT%H:%M:%SZ")] AVISO: Sincronización activa detectada (PID: ${lock_pid}). Omitiendo ejecución del temporizador." exit 0 fi fi fi # Proceso muerto o candado huerfano: se limpia automáticamente echo "[$(date -u +"%Y-%m-%dT%H:%M:%SZ")] INFO: Candado huérfano detectado (PID ${lock_pid:-desconocido} inactivo). Limpiando candado..." rm -f "${LOCK_FILE}" fi # 2. Comprobar si hay una sesión tmux 'mxos-sync' activa ejecutando sincronización if [[ -z "${TMUX:-}" ]] && tmux has-session -t "mxos-sync" 2>/dev/null; then local tmux_pane_pid="" tmux_pane_pid="$(tmux list-panes -t "mxos-sync" -F "#{pane_pid}" 2>/dev/null | head -n 1 || true)" if [[ -n "${tmux_pane_pid}" ]] && kill -0 "${tmux_pane_pid}" 2>/dev/null; then echo "[$(date -u +"%Y-%m-%dT%H:%M:%SZ")] AVISO: La sesión persistente tmux 'mxos-sync' está activa (PID: ${tmux_pane_pid}). Omitiendo ejecución programada." cat << EOF > "${LOCK_FILE}" ${tmux_pane_pid} STARTED=$(date -u +"%Y-%m-%dT%H:%M:%SZ") COMMAND=tmux:mxos-sync EOF exit 0 fi fi # 3. Adquirir candado exclusivo mediante flock a nivel de kernel exec 200>"${LOCK_FILE}" if ! flock -n 200; then local active_pid="" if [[ -s "${LOCK_FILE}" ]]; then active_pid="$(head -n 1 "${LOCK_FILE}" 2>/dev/null | tr -dc '0-9' || true)" fi echo "[$(date -u +"%Y-%m-%dT%H:%M:%SZ")] AVISO: Sincronización bloqueada por otro proceso activo (PID: ${active_pid:-activo}). Omitiendo." exit 0 fi # 4. Registrar metadatos del proceso titular cat << EOF > "${LOCK_FILE}" $$ STARTED=$(date -u +"%Y-%m-%dT%H:%M:%SZ") COMMAND=$0 EOF # 5. Registrar trampa para liberar el candado al salir trap 'release_intelligent_lock' EXIT INT TERM HUP } acquire_intelligent_lock LOG_FILE="${LOG_DIR}/repo-sync-$(date +%Y%m%d-%H%M%S).log" log() { local timestamp timestamp="$(date -u +"%Y-%m-%dT%H:%M:%SZ")" echo "[${timestamp}] $*" | tee -a "${LOG_FILE}" } sync_fedora_iso() { local f43_dest="${STAGING_DIR}/fedora/iso/43" local f44_dest="${STAGING_DIR}/fedora/iso/44" local f45_dest="${STAGING_DIR}/fedora/iso/45" mkdir -p "${f43_dest}" "${f44_dest}" "${f45_dest}" log "INFO: [1/6] Sincronizando Fedora 43 Server ISOs y firmas..." rsync -avP --contimeout=30 --timeout=60 \ --include="Fedora-Server-netinst-x86_64-43*.iso" \ --include="*CHECKSUM*" \ --include="*.manifest" \ --exclude="*" \ rsync://dl.fedoraproject.org/fedora-linux-releases/43/Server/x86_64/iso/ \ "${f43_dest}/" 2>&1 | tee -a "${LOG_FILE}" log "INFO: [2/6] Sincronizando Fedora 44 Server ISOs y firmas..." rsync -avP --contimeout=30 --timeout=60 \ --include="Fedora-Server-netinst-x86_64-44*.iso" \ --include="*CHECKSUM*" \ --include="*.manifest" \ --exclude="*" \ rsync://dl.fedoraproject.org/fedora-linux-releases/44/Server/x86_64/iso/ \ "${f44_dest}/" 2>&1 | tee -a "${LOG_FILE}" log "INFO: [3/6] Sincronizando Fedora 45 Server ISOs y firmas..." rsync -avP --contimeout=30 --timeout=60 \ --include="Fedora-Server-netinst-x86_64-45*.iso" \ --include="*CHECKSUM*" \ --include="*.manifest" \ --exclude="*" \ rsync://dl.fedoraproject.org/fedora-linux-development/45/Server/x86_64/iso/ \ "${f45_dest}/" 2>&1 | tee -a "${LOG_FILE}" } sync_centos_baseos() { local dest="${STAGING_DIR}/centos-stream/10-stream/BaseOS/x86_64/os" mkdir -p "${dest}" log "INFO: [4/9] Sincronizando CentOS Stream 10 BaseOS (paquetes y metadatos)..." dnf5 --setopt=max_parallel_downloads="${MAX_PARALLEL_DOWNLOADS:-10}" \ --repofrompath="c10s-baseos,https://mirror.stream.centos.org/10-stream/BaseOS/x86_64/os/" \ --repo="c10s-baseos" reposync \ --download-metadata \ --norepopath \ --destdir="${dest}" \ --delete 2>&1 | tee -a "${LOG_FILE}" } sync_centos_appstream() { local dest="${STAGING_DIR}/centos-stream/10-stream/AppStream/x86_64/os" mkdir -p "${dest}" log "INFO: [5/9] Sincronizando CentOS Stream 10 AppStream (paquetes y metadatos)..." dnf5 --setopt=max_parallel_downloads="${MAX_PARALLEL_DOWNLOADS:-10}" \ --repofrompath="c10s-appstream,https://mirror.stream.centos.org/10-stream/AppStream/x86_64/os/" \ --repo="c10s-appstream" reposync \ --download-metadata \ --norepopath \ --destdir="${dest}" \ --delete 2>&1 | tee -a "${LOG_FILE}" } sync_centos_crb() { local dest="${STAGING_DIR}/centos-stream/10-stream/CRB/x86_64/os" mkdir -p "${dest}" log "INFO: [6/9] Sincronizando CentOS Stream 10 CRB (paquetes y metadatos)..." dnf5 --setopt=max_parallel_downloads="${MAX_PARALLEL_DOWNLOADS:-10}" \ --repofrompath="c10s-crb,https://mirror.stream.centos.org/10-stream/CRB/x86_64/os/" \ --repo="c10s-crb" reposync \ --download-metadata \ --norepopath \ --destdir="${dest}" \ --delete 2>&1 | tee -a "${LOG_FILE}" } sync_centos_ha() { local dest="${STAGING_DIR}/centos-stream/10-stream/HighAvailability/x86_64/os" mkdir -p "${dest}" log "INFO: [7/9] Sincronizando CentOS Stream 10 HighAvailability..." dnf5 --setopt=max_parallel_downloads="${MAX_PARALLEL_DOWNLOADS:-10}" \ --repofrompath="c10s-ha,https://mirror.stream.centos.org/10-stream/HighAvailability/x86_64/os/" \ --repo="c10s-ha" reposync \ --download-metadata \ --norepopath \ --destdir="${dest}" \ --delete 2>&1 | tee -a "${LOG_FILE}" } sync_centos_nfv() { local dest="${STAGING_DIR}/centos-stream/10-stream/NFV/x86_64/os" mkdir -p "${dest}" log "INFO: [8/9] Sincronizando CentOS Stream 10 NFV..." dnf5 --setopt=max_parallel_downloads="${MAX_PARALLEL_DOWNLOADS:-10}" \ --repofrompath="c10s-nfv,https://mirror.stream.centos.org/10-stream/NFV/x86_64/os/" \ --repo="c10s-nfv" reposync \ --download-metadata \ --norepopath \ --destdir="${dest}" \ --delete 2>&1 | tee -a "${LOG_FILE}" } sync_centos_rt() { local dest="${STAGING_DIR}/centos-stream/10-stream/RT/x86_64/os" mkdir -p "${dest}" log "INFO: [9/9a] Sincronizando CentOS Stream 10 RT..." dnf5 --setopt=max_parallel_downloads="${MAX_PARALLEL_DOWNLOADS:-10}" \ --repofrompath="c10s-rt,https://mirror.stream.centos.org/10-stream/RT/x86_64/os/" \ --repo="c10s-rt" reposync \ --download-metadata \ --norepopath \ --destdir="${dest}" \ --delete 2>&1 | tee -a "${LOG_FILE}" } sync_centos_extras() { local dest="${STAGING_DIR}/centos-stream/SIGs/10-stream/extras/x86_64/extras-common" mkdir -p "${dest}" log "INFO: [9/9b] Sincronizando CentOS Stream 10 extras-common..." dnf5 --setopt=max_parallel_downloads="${MAX_PARALLEL_DOWNLOADS:-10}" \ --repofrompath="c10s-extras,https://mirror.stream.centos.org/SIGs/10-stream/extras/x86_64/extras-common/" \ --repo="c10s-extras" reposync \ --download-metadata \ --norepopath \ --destdir="${dest}" \ --delete 2>&1 | tee -a "${LOG_FILE}" } sync_fedora_everything() { local f43_dest="${STAGING_DIR}/fedora/releases/43/Everything/x86_64/os" local f44_dest="${STAGING_DIR}/fedora/releases/44/Everything/x86_64/os" local f45_dest="${STAGING_DIR}/fedora/releases/45/Everything/x86_64/os" mkdir -p "${f43_dest}" "${f44_dest}" "${f45_dest}" log "INFO: [6/6a] Sincronizando Fedora 43 Everything (paquetes y metadatos)..." dnf5 --setopt=max_parallel_downloads="${MAX_PARALLEL_DOWNLOADS:-10}" \ --repofrompath="f43-everything,https://dl.fedoraproject.org/pub/fedora/linux/releases/43/Everything/x86_64/os/" \ --repo="f43-everything" reposync \ --download-metadata \ --norepopath \ --destdir="${f43_dest}" \ --delete 2>&1 | tee -a "${LOG_FILE}" log "INFO: [6/6b] Sincronizando Fedora 44 Everything (paquetes y metadatos)..." dnf5 --setopt=max_parallel_downloads="${MAX_PARALLEL_DOWNLOADS:-10}" \ --repofrompath="f44-everything,https://dl.fedoraproject.org/pub/fedora/linux/releases/44/Everything/x86_64/os/" \ --repo="f44-everything" reposync \ --download-metadata \ --norepopath \ --destdir="${f44_dest}" \ --delete 2>&1 | tee -a "${LOG_FILE}" log "INFO: [6/6c] Sincronizando Fedora 45 Everything (paquetes y metadatos)..." dnf5 --setopt=max_parallel_downloads="${MAX_PARALLEL_DOWNLOADS:-10}" \ --repofrompath="f45-everything,https://dl.fedoraproject.org/pub/fedora/linux/development/45/Everything/x86_64/os/" \ --repo="f45-everything" reposync \ --download-metadata \ --norepopath \ --destdir="${f45_dest}" \ --delete 2>&1 | tee -a "${LOG_FILE}" } main() { log "==========================================================" log " Inicio de Sincronización Completa de Espejos MxOS" log " Destino: ${STAGING_DIR}" log " Log: ${LOG_FILE}" log "==========================================================" sync_fedora_iso sync_centos_baseos sync_centos_appstream sync_centos_crb sync_centos_ha sync_centos_nfv sync_centos_rt sync_centos_extras sync_fedora_everything log "INFO: Compilando portales HTML..." "${SCRIPT_DIR}/mxos-build-html.bash" >/dev/null 2>&1 || true log "INFO: Asegurando documentos de índice en la raíz de staging..." rm -rf "${STAGING_DIR}/README.rst" \ "${STAGING_DIR}/logo.svg" \ "${STAGING_DIR}/guardianes.json" \ "${STAGING_DIR:?}/bin" \ "${STAGING_DIR}/config" \ "${STAGING_DIR:?}/etc" \ "${STAGING_DIR}/systemd" 2>/dev/null || true for doc in README.html index.html; do if [[ -f "${REPO_ROOT}/${doc}" ]]; then cp -f "${REPO_ROOT}/${doc}" "${STAGING_DIR}/${doc}" fi done # Asegurar que el tablero Vigía esté sincronizado en la raíz del catálogo if [[ -d "${REPO_ROOT}/vigia" ]]; then log "INFO: Sincronizando observatorio Vigía hacia staging..." mkdir -p "${STAGING_DIR}/vigia" cp -rf "${REPO_ROOT}/vigia/"* "${STAGING_DIR}/vigia/" fi log "INFO: Midiendo tamaños finales en staging..." "${SCRIPT_DIR}/mxos-sizes.bash" | tee -a "${LOG_FILE}" log "INFO: Ejecutando ingesta UnixFS en Kubo..." local root_cid root_cid="$(ipfs add -r --nocopy --cid-version=1 --hash=sha2-256 -Q "${STAGING_DIR}")" log "INFO: Nuevo CID raíz: ${root_cid}" ipfs pin add "${root_cid}" >/dev/null 2>&1 || true log "INFO: Actualizando DNSLink en Cloudflare..." "${SCRIPT_DIR}/mxos-dnslink-update.bash" "${root_cid}" 2>&1 | tee -a "${LOG_FILE}" log "INFO: Publicando en IPNS con llave Ed25519 (mxos-mirrors)..." "${SCRIPT_DIR}/mxos-ipns-publish.bash" "${root_cid}" 2>&1 | tee -a "${LOG_FILE}" || true log "INFO: Replicando DAG hacia web0.mx-os.mx..." "${SCRIPT_DIR}/mxos-mirror.bash" replicate "${root_cid}" 2>&1 | tee -a "${LOG_FILE}" || true log "==========================================================" log " Sincronización completada exitosamente." log " CID activo: ${root_cid}" log "==========================================================" } main "$@"