espejo: push a gitea + GitHub privado, y script para reponerlo tras un clon
git.tawasuyu.net resuelve a gioser, marcado como FIJO y SIN BACKUP. Sin espejo, las dos únicas
copias del repo eran el laptop —que ya se corrompió una vez por un corte sucio, el 2026-07-22— y
esa. Ahora hay una tercera, en otro proveedor y PRIVADA.
Se implementa con `git remote set-url --add --push origin` en vez de un remoto `github` aparte,
para que TODO `git push origin main` que ya existe en los scripts (cosecha-cron.sh, el latido)
espeje solo, sin tocar un script. Si GitHub falla, el push devuelve != 0 aunque gitea haya
aceptado; cosecha-cron.sh ya lo tolera ("push falló, reintenta próximo ciclo") ⇒ el latido no se
rompe por eso.
La credencial va por HTTPS con el token de `gh`, no por SSH: las tres claves SSH del laptop
(github5, key25, sergiogithub) son DEPLOY KEYS de repos ajenos y dan "Repository not found" contra
este. El bloque `Host github.com` del ~/.ssh/config además no tiene `IdentitiesOnly`, así que ssh
ofrece todas y gana una deploy key.
El script existe porque las dos cosas que configura viven en `.git/config`, que NO se versiona: se
perderían exactamente en el escenario para el que se pusieron (el laptop muere, clonás de nuevo).
Incluye también el `core.fsync` del mismo incidente. Idempotente (los pushurl son acumulativos, así
que los reconstruye en vez de añadir) y con `--check` para auditar sin tocar nada.
Verificado: los dos remotos en el mismo commit, GitHub reporta PRIVATE, y correrlo tres veces deja
2 pushurl, no 6.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Semántico sayk'uy
Mana ima sutichaypas t'ikrakurqanchu: qillqalla, yachachiylla utaq willaylla t'ikrakurqan.
1 qillqakuna · +88 −0
- scripts/espejo-setup.sh +88 −0
diff --git a/scripts/espejo-setup.sh b/scripts/espejo-setup.sh
new file mode 100755
index 00000000..6941df7d
--- /dev/null
+++ b/scripts/espejo-setup.sh
@@ -0,0 +1,88 @@
+#!/usr/bin/env bash
+# espejo-setup.sh — deja este clon con espejo doble y con git endurecido contra cortes sucios.
+#
+# POR QUÉ EXISTE: las dos cosas que configura viven en `.git/config`, que **no se versiona**. Es
+# decir: se pierden exactamente en el escenario para el que se pusieron (el laptop muere, clonás de
+# nuevo). Este script las vuelve a poner en un paso, y es idempotente, así que se puede correr sobre
+# un clon ya configurado sin duplicar nada.
+#
+# QUÉ CONFIGURA
+#
+# 1. PUSH A LOS DOS REMOTOS. `origin` fetchea de gitea (canónico) pero empuja a gitea Y a GitHub.
+# Se hace con `--add --push` en vez de un remoto `github` aparte para que TODO `git push origin
+# main` que ya existe en los scripts (p.ej. cosecha-cron.sh) espeje solo, sin tocar un script.
+# Motivo del espejo (2026-07-22): git.tawasuyu.net resuelve a gioser, marcado como FIJO y SIN
+# BACKUP. Sin GitHub, las dos únicas copias eran el laptop —que ya se corrompió una vez por un
+# corte sucio— y esa. El espejo es PRIVADO.
+#
+# OJO con la credencial: las claves SSH del laptop son todas DEPLOY KEYS de otros repos (dan
+# "Repository not found" contra este). Por eso el remoto de GitHub va por HTTPS con el token de
+# `gh` como credential helper. Si `gh auth status` no está logueado, esto no va a empujar.
+#
+# Si GitHub está caído o el token venció, `git push origin main` devuelve != 0 aunque gitea haya
+# aceptado. cosecha-cron.sh ya lo tolera (loguea "push falló, reintenta próximo ciclo"), así que
+# el latido no se rompe por eso.
+#
+# 2. GIT CON fsync. git 2.55 NO fsyncea los loose objects por defecto. Con un kernel rc que corta
+# sucio, ext4 deja el inode con su mtime y el contenido en cero: el 2026-07-22 eso dejó 6 objects
+# vacíos + refs + reflog en NULs y el repo con "fatal: bad object HEAD". Se recuperó entero sólo
+# porque el push a gitea había llegado. Con esto, no vuelve a pasar por esa vía.
+#
+# Uso: scripts/espejo-setup.sh [--check]
+set -uo pipefail
+
+GITEA="${GITEA:-ssh://gitea@git.tawasuyu.net:2345/sergio/hammer.git}"
+GITHUB="${GITHUB:-https://github.com/sergiovelasquezzeballos/hammer.git}"
+ROOT="$(cd "$(dirname "$0")/.." && pwd)"
+cd "$ROOT"
+
+CHECK=0
+[ "${1:-}" = "--check" ] && CHECK=1
+
+ok() { printf ' ✓ %s\n' "$1"; }
+warn() { printf ' ⚠ %s\n' "$1"; }
+
+echo "espejo-setup en $ROOT"
+
+# --- 1. push a los dos ---
+actuales="$(git remote get-url --push --all origin 2>/dev/null || true)"
+falta=0
+printf '%s\n' "$actuales" | grep -qF "$GITEA" || falta=1
+printf '%s\n' "$actuales" | grep -qF "$GITHUB" || falta=1
+
+if [ "$falta" = "0" ]; then
+ ok "origin ya empuja a gitea y a GitHub"
+elif [ "$CHECK" = "1" ]; then
+ warn "origin NO empuja a los dos (corré sin --check para arreglarlo)"
+else
+ # Reconstruir de cero: los pushurl son acumulativos y re-correr sin limpiar duplicaría.
+ git config --unset-all remote.origin.pushurl 2>/dev/null || true
+ git remote set-url --add --push origin "$GITEA"
+ git remote set-url --add --push origin "$GITHUB"
+ ok "origin empuja ahora a gitea y a GitHub"
+fi
+
+# --- 2. fsync ---
+FSYNC="loose-object,pack,pack-metadata,commit-graph,index,reference"
+if [ "$(git config --get core.fsync 2>/dev/null)" = "$FSYNC" ]; then
+ ok "core.fsync ya endurecido"
+elif [ "$CHECK" = "1" ]; then
+ warn "core.fsync SIN endurecer (corré sin --check para arreglarlo)"
+else
+ git config core.fsync "$FSYNC"
+ git config core.fsyncMethod fsync
+ ok "core.fsync endurecido"
+fi
+
+# --- credencial de GitHub: avisar temprano, no fallar al primer push ---
+if command -v gh >/dev/null 2>&1; then
+ if gh auth status >/dev/null 2>&1; then
+ ok "gh autenticado (el push a GitHub va por HTTPS con su token)"
+ else
+ warn "gh NO autenticado ⇒ el push a GitHub va a fallar. Corré: gh auth login && gh auth setup-git"
+ fi
+else
+ warn "no hay gh instalado ⇒ el push a GitHub va a fallar. Instalalo y corré: gh auth login && gh auth setup-git"
+fi
+
+echo "listo. Verificar: git push origin main (debe reportar DOS veces, una por remoto)"