#!/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 # Cargar configuración si existe 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 if [[ -f "${HOME}/.config/mxos/mirrors.env" ]]; then # shellcheck source=/dev/null source "${HOME}/.config/mxos/mirrors.env" fi CF_ZONE_ID="${CF_ZONE_ID:-2bd35b00a854bc77cfd38009f977ea3c}" MIRRORS_DNSLINK_RECORDS="${MIRRORS_DNSLINK_RECORDS:-_dnslink.mirrors.mx-os.mx}" resolve_cf_token() { if [[ -n "${CF_API_TOKEN:-}" ]]; then echo "${CF_API_TOKEN}" return 0 fi if command -v gopass >/dev/null 2>&1; then local gopass_key="${CF_GOPASS_KEY:-cloudflare/api-token}" local token token="$(gopass show -f "${gopass_key}" 2>/dev/null | awk '/^API Token:/ {print $3}' | tr -d '[:space:]')" if [[ -n "${token}" ]]; then echo "${token}" return 0 fi fi echo "Error: No se encontró token de Cloudflare en CF_API_TOKEN ni en GoPass." >&2 return 1 } update_or_create_dnslink() { local token="$1" local record_name="$2" local cid="$3" local dnslink_content="dnslink=/ipfs/${cid}" echo "==> Actualizando registro DNSLink para ${record_name}..." # Buscar si el registro ya existe local search_response search_response="$(curl -4 --max-time 15 -s -X GET \ "https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/dns_records?type=TXT&name=${record_name}" \ -H "Authorization: Bearer ${token}" \ -H "Content-Type: application/json")" local record_id record_id="$(echo "${search_response}" | jq -r '.result[0].id // empty')" if [[ -n "${record_id}" ]]; then local update_response update_response="$(curl -4 --max-time 15 -s -X PUT \ "https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/dns_records/${record_id}" \ -H "Authorization: Bearer ${token}" \ -H "Content-Type: application/json" \ -d "{\"type\":\"TXT\",\"name\":\"${record_name}\",\"content\":\"${dnslink_content}\",\"ttl\":300}")" if echo "${update_response}" | jq -e '.success' >/dev/null 2>&1; then echo " Registro ${record_name} actualizado exitosamente -> ${dnslink_content}" else echo "Error actualizando registro ${record_name}:" >&2 echo "${update_response}" | jq .errors >&2 return 1 fi else local create_response create_response="$(curl -4 --max-time 15 -s -X POST \ "https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/dns_records" \ -H "Authorization: Bearer ${token}" \ -H "Content-Type: application/json" \ -d "{\"type\":\"TXT\",\"name\":\"${record_name}\",\"content\":\"${dnslink_content}\",\"ttl\":300}")" if echo "${create_response}" | jq -e '.success' >/dev/null 2>&1; then echo " Registro ${record_name} creado exitosamente -> ${dnslink_content}" else echo "Error creando registro ${record_name}:" >&2 echo "${create_response}" | jq .errors >&2 return 1 fi fi } validate_root_cid() { local cid="$1" local required_paths=( "index.html" "README.html" "modules.json" "bitacora-cids.json" "centos-stream/10-stream/BaseOS" "fedora/linux" ) echo "==> Validando integridad estructural de /ipfs/${cid} antes de publicar..." for rel_path in "${required_paths[@]}"; do if ! timeout 10s ipfs files stat "/ipfs/${cid}/${rel_path}" >/dev/null 2>&1; then echo "Error de integridad: La ruta requerida '/ipfs/${cid}/${rel_path}' no existe en el DAG o excedió el tiempo límite." >&2 echo "Abortando actualización de DNSLink para prevenir fallas en producción." >&2 return 1 fi done echo " Integridad estructural 100% verificada." } main() { local target_cid="${1:-}" if [[ -z "${target_cid}" ]]; then if [[ ! -t 0 ]]; then read -r target_cid fi fi if [[ -z "${target_cid}" ]]; then echo "Uso: $0 " >&2 echo " O pase el CID por tubería estándar: echo \$CID | $0" >&2 exit 1 fi # Limpiar prefijo si viene con /ipfs/ target_cid="${target_cid#/ipfs/}" # Validación estricta de sintaxis de CID if [[ ! "${target_cid}" =~ ^(Qm[1-9A-HJ-NP-Za-km-z]{44}|bafy[a-z0-9]{55,})$ ]]; then echo "Error: El CID provisto no tiene un formato canónico válido: ${target_cid}" >&2 exit 1 fi # Validación de pre-vuelo antes de tocar registros públicos validate_root_cid "${target_cid}" local token token="$(resolve_cf_token)" local records_to_update=() if [[ -n "${MIRRORS_DNSLINK_RECORDS:-}" ]]; then IFS=' ' read -r -a records_to_update <<< "${MIRRORS_DNSLINK_RECORDS}" elif [[ -n "${MIRRORS_DNSLINK_RECORD:-}" ]]; then records_to_update+=("${MIRRORS_DNSLINK_RECORD}") else records_to_update+=("_dnslink.mirrors.mx-os.mx") fi for rec in "${records_to_update[@]}"; do update_or_create_dnslink "${token}" "${rec}" "${target_cid}" done echo "==> Verificando resolución DNSLink canónica..." if command -v ipfs >/dev/null 2>&1; then local resolved resolved="$(ipfs resolve "/ipns/${MIRRORS_DOMAIN}" 2>/dev/null || true)" echo " IPFS resolve /ipns/${MIRRORS_DOMAIN} -> ${resolved:-pendiente de propagación}" fi local desc="${2:-Publicación en producción}" if [[ -x "${SCRIPT_DIR}/mxos-bitacora.bash" ]]; then "${SCRIPT_DIR}/mxos-bitacora.bash" add "${target_cid}" "${desc}" || true fi } main "$@"