La auditoría de seguridad de 08-03 cambió doce cosas del toolkit, y la lección de optimización cambió otras tantas antes. Ahora mismo, si vigilante.sh empieza a fallar el martes de madrugada, nadie sabe decir qué se tocó, cuándo ni por qué, ni cómo volver a la versión que funcionaba. Esta lección pone ~/veloz-ops/ bajo control de versiones. No es un curso de Git: es Git visto desde la necesidad concreta de un equipo de operaciones que mantiene cinco scripts, una librería y un fichero de configuración con credenciales que no debe subirse jamás.
Contenido
- Por qué
respaldo.sh.bak.old.v2no es control de versiones - Los cinco conceptos que hacen falta
- Poner
~/veloz-ops/bajo control - Ver qué ha cambiado:
git diffygit log - Qué NO se versiona:
.gitignorey la configuración - Si ya subiste un secreto
- Mensajes de commit útiles en operaciones
- Deshacer con criterio
- Ramas para probar sin tocar producción
- Etiquetas y despliegue a la flota
- Remotos y repositorios privados
- Un hook
pre-commitde verdad - Flujo de trabajo mínimo para un equipo pequeño
- Por qué
respaldo.sh.bak.old.v2 no es control de versiones
respaldo.sh.bak.old.v2 no es control de versionesEl patrón es universal: antes de tocar un script se hace una copia con un sufijo. Al cabo de un año el directorio tiene respaldo.sh.bak, respaldo.sh.old, respaldo.sh.v2, respaldo.sh.20260715 y respaldo.sh.BUENO. Y ninguna de esas copias responde a las preguntas que importan: qué cambió exactamente entre dos de ellas, quién lo cambió, cuándo, por qué y cuál es la que corre ahora en srv-veloz-02. Tampoco permiten saber si el cambio del martes y el del jueves son independientes.
Git responde a las cinco preguntas y añade dos capacidades que las copias no dan: probar un cambio en paralelo sin tocar lo que está en producción, y deshacer un cambio concreto sin deshacer los posteriores.
- Los cinco conceptos que hacen falta
| Concepto | Qué es | Analogía |
|---|---|---|
| Repositorio | El directorio con su historia completa, en .git/ |
El archivo del proyecto |
| Área de preparación (staging) | Zona intermedia donde eliges qué entra en el próximo commit | La bandeja de lo que vas a entregar |
| Commit | Una foto del proyecto con autor, fecha y mensaje | Una entrada de bitácora firmada |
| Rama | Una línea de desarrollo independiente | Un borrador paralelo |
| Remoto | Una copia del repositorio en otro sitio | El servidor donde el equipo comparte |
El área de preparación es lo que más confunde al principio y tiene una razón muy práctica: permite haber tocado cuatro ficheros y hacer dos commits distintos, uno con la corrección de seguridad y otro con la mejora de rendimiento. En operaciones eso vale oro, porque permite revertir uno sin el otro.
- Poner
~/veloz-ops/ bajo control
~/veloz-ops/ bajo control$ cd ~/veloz-ops
$ git init
Repositorio Git vacio inicializado en /home/veloz/veloz-ops/.git/
$ git config user.name "Ana Lopez"
$ git config user.email "[email protected]"Antes del primer git add hay que decidir qué no entra, así que lo correcto es escribir el .gitignore primero (apartado 5) y después añadir:
$ git add .gitignore bin/ lib/ etc/veloz-ops.conf.ejemplo
$ git status --short
A .gitignore
A bin/informe-diario.sh
A bin/vigilante.sh
A etc/veloz-ops.conf.ejemplo
A lib/comun.sh
$ git commit -m "Version inicial del toolkit de operaciones de Veloz Envios"
[master (commit-raiz) 4f2a1b8] Version inicial del toolkit de operaciones
8 files changed, 1247 insertions(+)git add mueve cambios al área de preparación; git commit los graba en la historia. En git status --short, A es added, M modificado, D borrado y ?? sin seguimiento. Un detalle que ahorra sorpresas: Git guarda el bit de ejecución, así que los chmod +x de bin/ viajan con el repositorio.
- Ver qué ha cambiado:
git diff y git log
git diff y git log$ git diff # cambios NO preparados (trabajo vs. staging)
$ git diff --staged # cambios preparados (staging vs. ultimo commit)
$ git log --oneline --graph --decorate -4
* 9c14e7a (HEAD -> master, tag: v1.2) Fijar PATH e IFS en comun.sh (auditoria)
* 3b8d201 Sustituir bucle por awk en informe-diario.sh: 3m12s -> 3,9s
* a71f0c4 Anadir --dry-run a respaldo.sh
* 4f2a1b8 Version inicial del toolkitLa distinción entre los dos diff es la que más se pregunta al empezar: el primero enseña lo que aún no has añadido con git add; el segundo, lo que está a punto de entrar en el commit. Revisa siempre git diff --staged antes de escribir el mensaje: es el momento natural para descubrir que has dejado un set -x puesto. Para investigar un fichero concreto, git log -p bin/vigilante.sh muestra su historia línea a línea y git blame bin/vigilante.sh dice qué commit introdujo cada línea: la herramienta que de verdad usarás cuando algo lleve meses roto.
- Qué NO se versiona:
.gitignore y la configuración
.gitignore y la configuraciónEsta es la parte crítica. etc/veloz-ops.conf contiene el token que acabamos de proteger en 08-03; subirlo lo hace visible para todo el equipo, para siempre y en cada clon.
# ~/veloz-ops/.gitignore
etc/veloz-ops.conf # secretos y configuracion local: NUNCA
etc/secretos
*.key
*.pem
.netrc
logs/ # salida regenerable
*.log
*.tmp
datos/ # datos de negocio: van al respaldo, no al repositorio
*.csvEn su lugar se versiona una plantilla que documenta la configuración sin revelar nada:
# etc/veloz-ops.conf.ejemplo <-- este SI se versiona
VELOZ_API_URL="http://localhost:8080"
VELOZ_API_TOKEN="pon-aqui-tu-token" # obtener de Vault, no compartir
VELOZ_RETENCION_DIAS=30Al instalar el toolkit en un servidor nuevo se copia la plantilla, se rellena y se ajustan permisos. Una advertencia importante: .gitignore solo afecta a ficheros sin seguimiento. Si ya hiciste git add etc/veloz-ops.conf, añadirlo al .gitignore no lo saca: hace falta git rm --cached etc/veloz-ops.conf, que lo quita del repositorio y lo deja en el disco.
- Si ya subiste un secreto
Ocurre. El orden de las acciones no es negociable:
- Rota el secreto inmediatamente. Genera uno nuevo e invalida el viejo. Es el único paso que de verdad mitiga; los demás son limpieza.
- Sácalo del repositorio actual:
git rm --cached, añádelo al.gitignore, commit. - Reescribe la historia con
git filter-repo(la herramienta recomendada hoy) o BFG Repo-Cleaner, que eliminan el fichero de todos los commits. - Avisa al equipo: la reescritura cambia todos los identificadores de commit y quien tenga un clon deberá rehacerlo.
Y la advertencia de fondo: reescribir la historia no basta si el repositorio ya se compartió. Cualquiera pudo clonarlo, la plataforma pudo indexarlo, hay copias en caché y en respaldos. Por eso el paso 1 no es opcional ni posterior.
- Mensajes de commit útiles en operaciones
Un mensaje no describe el diff —eso ya lo hace el diff—, sino qué cambió y por qué. En operaciones, el «por qué» suele ser un incidente:
| Mensaje inútil | Mensaje útil |
|---|---|
cambios |
Anadir timeout de 10s a veloz_api_get |
arreglo |
Corregir division por cero en veloz_porcentaje sin envios |
update vigilante.sh |
Subir el umbral de disco al 85% para evitar falsos avisos |
wip |
Fijar PATH e IFS en comun.sh (auditoria de seguridad) |
Convención práctica: primera línea imperativa de menos de 72 caracteres, línea en blanco y cuerpo con el contexto.
$ git commit -m "Reintentar la consulta a veloz-api hasta 3 veces" -m \
"Incidente INC-482: el 2026-07-29 la API tardo 8s en responder tras el
reinicio nocturno y vigilante.sh envio una alerta falsa. Se anaden
reintentos con retroceso exponencial (2s, 4s, 8s) antes de alertar."Dentro de seis meses, ese commit explica por qué existe un bucle de reintentos que a primera vista sobra. Un commit por cambio lógico: no mezcles el refactor de rendimiento con la corrección de seguridad aunque los hayas hecho la misma tarde.
- Deshacer con criterio
Cuatro órdenes para cuatro situaciones. Confundirlas es la principal fuente de trabajo perdido:
| Situación | Orden | Destructivo |
|---|---|---|
| Descartar mis ediciones de un fichero | git restore bin/vigilante.sh |
Sí (pierde lo no guardado) |
He hecho git add de más |
git restore --staged etc/veloz-ops.conf |
No |
| Un commit ya publicado es incorrecto | git revert 3b8d201 |
No (crea un commit inverso) |
| Tirar commits locales aún no publicados | git reset --hard 4f2a1b8 |
Sí, borra historia |
Regla de oro: git revert para lo que ya han visto otros; git reset --hard solo para lo tuyo y no publicado. revert deja constancia de que hubo un cambio y de que se retiró, que en operaciones es información valiosa. Y si el mensaje del último commit está mal, git commit --amend lo corrige mientras no lo hayas subido.
- Ramas para probar sin tocar producción
Quieres reescribir la lógica de umbrales de vigilante.sh, pero ese script corre cada cinco minutos en tres servidores. Una rama te da un espacio paralelo:
$ git switch -c umbrales-dinamicos # crea la rama y cambia a ella
$ git commit -am "Calcular el umbral de disco con la media de 7 dias"
$ git switch master # master sigue intactoCuando el cambio esté probado se integra con git merge umbrales-dinamicos y se borra la rama con git branch -d. Si en el intervalo alguien tocó las mismas líneas en master, Git avisa de un conflicto y marca el fichero:
<<<<<<< HEAD
umbral_disco=85
=======
umbral_disco=$(( media_7dias + 10 ))
>>>>>>> umbrales-dinamicosResolverlo es editar el fichero dejando la versión correcta —a menudo una combinación de ambas—, borrar las tres líneas de marcas y hacer git add y git commit. No hay magia: decide un humano. En scripts de Bash conviene además ejecutar bash -n (05-03) sobre el fichero resuelto antes de confirmar, porque un conflicto mal resuelto deja fácilmente un fi de más.
- Etiquetas y despliegue a la flota
Una etiqueta marca un punto de la historia con un nombre estable, y en operaciones sirve para saber qué versión corre en cada servidor:
El despliegue a srv-veloz-01/02/03 combina esto con lo visto en 07-06:
# A) Cada servidor tiene un clon y se actualiza a la etiqueta
for h in srv-veloz-01 srv-veloz-02 srv-veloz-03; do
ssh -n "$h" 'cd ~/veloz-ops && git fetch --tags && git checkout -q v1.2'
done
# B) Un clon de referencia y rsync a la flota (sin Git en produccion)
for h in srv-veloz-01 srv-veloz-02 srv-veloz-03; do
rsync -a --delete --exclude '.git' --exclude 'etc/veloz-ops.conf' \
~/veloz-ops/ "$h:~/veloz-ops/"
doneLa opción A permite consultar la versión desplegada con git describe en cada máquina; la B evita instalar Git y credenciales en producción. Fíjate en la exclusión de etc/veloz-ops.conf: la configuración es local de cada servidor y no debe sobrescribirse desde el repositorio.
- Remotos y repositorios privados
Un repositorio local no es una copia de seguridad: si se pierde el disco, se pierde la historia.
$ git remote add origin [email protected]:ops/veloz-ops.git
$ git push -u origin master # -u recuerda el destino para los siguientes
$ git pull # trae e integra lo que hayan subido otrosgit pull es internamente fetch más merge; si prefieres ver qué llega antes de integrarlo, git fetch seguido de git log HEAD..origin/master es un hábito excelente en operaciones. Y una advertencia que no sobra: el código de operaciones va en repositorios privados. Aunque no contenga secretos —y no debe—, revela nombres de servidores, rutas internas, umbrales y estructura del sistema; es reconocimiento gratuito para un atacante. Autenticación por clave SSH (07-06), no por contraseña.
- Un hook
pre-commit de verdad
pre-commit de verdadUn hook es un script que Git ejecuta automáticamente en cierto momento. pre-commit corre antes de crear el commit y, si termina con código distinto de 0, lo aborta:
#!/usr/bin/env bash
# .git/hooks/pre-commit - requiere chmod +x
set -uo pipefail
fallos=0
mapfile -t scripts < <(git diff --cached --name-only --diff-filter=ACM |
grep -E '\.(sh|bats)$|^bin/')
(( ${#scripts[@]} == 0 )) && exit 0
for s in "${scripts[@]}"; do
bash -n "$s" || { printf 'sintaxis: %s\n' "$s" >&2; fallos=1; }
shellcheck -x "$s" || fallos=1 # 08-05
done
[[ -d tests ]] && { bats tests/ >/dev/null || fallos=1; } # 08-06
if git diff --cached | grep -qE 'TOKEN=|PASSWORD=|BEGIN (RSA|OPENSSH) PRIVATE KEY'; then
printf 'posible secreto en el commit, revisalo\n' >&2
fallos=1
fi
exit "$fallos"Cuatro barreras: sintaxis con bash -n, análisis estático con ShellCheck, pruebas con Bats y una búsqueda simple de secretos. --diff-filter=ACM limita la comprobación a los ficheros añadidos, copiados o modificados —no tiene sentido analizar uno que se borra— y solo mira los preparados, no todo el árbol. Dos limitaciones que hay que conocer: los hooks viven en .git/hooks/ y no se versionan, así que hay que instalarlos en cada clon (por eso muchos equipos guardan el hook en tools/ y lo enlazan con un script de instalación), y git commit --no-verify los salta. El hook es una red de seguridad para despistes, no un control de acceso.
- Flujo de trabajo mínimo para un equipo pequeño
Para dos o tres personas de operaciones basta con esto: git pull antes de empezar; rama corta para el cambio (git switch -c corregir-timeout-api); commits pequeños con mensajes que citen el incidente; git push -u origin <rama> y petición de revisión a otra persona; fusión a master, etiqueta si es desplegable y despliegue a la flota; borrar la rama.
El penúltimo paso es el más importante de la lista, y no es una herramienta: es una persona. La revisión por otro compañero es el mejor control de calidad que existe, mejor que ShellCheck, que las pruebas y que la disciplina propia. Encuentra lo que las herramientas no ven: que el umbral está mal elegido, que el script asume un directorio que solo existe en tu máquina, que ese rm -rf no está tan acotado como parece. Diez minutos de revisión ahorran una guardia nocturna.
Errores Comunes y Consejos
- Empezar por
git add .sin.gitignore. Es la forma más común de subir un secreto. El.gitignoreva antes del primer commit. - Creer que
.gitignoreretira ficheros ya versionados. No lo hace:git rm --cachedes lo que los saca. - Borrar el secreto y no rotarlo. Sigue en la historia, en los clones y en las cachés.
git reset --hardsobre commits ya publicados. Rompe el repositorio de los demás; para lo publicado,git revert.- Consejo:
git stashpara la interrupción. Guarda el trabajo a medias, atiende la incidencia y recupéralo congit stash pop. - Consejo:
git log -S 'veloz_api_get'busca en qué commit apareció o desapareció una cadena; es la forma rápida de datar un cambio de comportamiento.
Ejercicios
Ejercicio 1. Descubres que etc/veloz-ops.conf, con el token de la API, lleva tres commits versionado en un repositorio compartido con dos compañeros. Enumera en orden las acciones y las órdenes concretas.
Ejercicio 2. Escribe el .gitignore del toolkit justificando cada exclusión, y explica qué se versiona en su lugar.
Ejercicio 3. Has fusionado a master un cambio en vigilante.sh (commit 3b8d201) que provoca alertas falsas, y ya hay dos commits posteriores de otro compañero. ¿Cómo lo deshaces sin perder su trabajo?
Soluciones
Solución 1. Primero, rotar el token en la API e invalidar el antiguo: está en tres clones y en el servidor de Git, y es lo único que corta la exposición real. Después:
$ printf 'etc/veloz-ops.conf\n' >> .gitignore
$ git rm --cached etc/veloz-ops.conf
$ git add .gitignore etc/veloz-ops.conf.ejemplo
$ git commit -m "Dejar de versionar la configuracion con credenciales"
$ git pushLuego reescribir la historia con git filter-repo --path etc/veloz-ops.conf --invert-paths (o BFG), avisando antes a los dos compañeros de que deberán volver a clonar porque todos los identificadores de commit cambian. Y documentar el incidente, recordando que la reescritura no sustituye a la rotación: si el repositorio se compartió, hay que asumir que el token es público.
Solución 2. El del apartado 5. etc/veloz-ops.conf, *.key, *.pem y .netrc contienen credenciales; logs/ y *.log son salida regenerable que además haría crecer el repositorio sin límite; *.tmp son temporales de mktemp; datos/ y *.csv son datos de negocio con información personal (08-03), que pertenecen al respaldo y no al control de versiones. Se versiona etc/veloz-ops.conf.ejemplo porque documenta qué variables hay que definir sin revelar sus valores.
Solución 3. Con git revert, que crea un commit nuevo deshaciendo exactamente los cambios de 3b8d201 y deja intactos los dos posteriores:
git reset --hard sería un error grave: tiraría también los commits del compañero y, al estar ya publicados, obligaría a un push --force que rompería su repositorio. Si el revert produce conflicto porque los commits posteriores tocaron las mismas líneas, se resuelve a mano como en el apartado 9 y se termina con git revert --continue.
Conclusión
Git resuelve para un toolkit de operaciones lo que las copias .bak no resuelven: qué cambió, quién, cuándo y por qué, y cómo volver atrás sin arrastrar lo demás. Con cinco conceptos —repositorio, área de preparación, commit, rama y remoto— basta para trabajar: git init, git add, git commit -m, git status --short, git diff y git diff --staged para revisar antes de confirmar, git log --oneline --graph para leer la historia y git blame para saber quién introdujo esa línea. Lo primero que se escribe no es un commit sino el .gitignore, que deja fuera logs/, temporales, datos y sobre todo etc/veloz-ops.conf con sus credenciales, versionando en su lugar etc/veloz-ops.conf.ejemplo; y si un secreto ya está dentro, el orden es rotar primero, luego git rm --cached y luego git filter-repo o BFG, sabiendo que reescribir la historia no repara nada si el repositorio ya se compartió. Los mensajes describen el cambio y su motivo —el incidente que lo provocó—, con un commit por cambio lógico. Para deshacer hay que elegir bien: git restore descarta ediciones, git restore --staged retira del área de preparación, git revert deshace lo publicado dejando constancia y git reset --hard solo se usa sobre commits propios no publicados. Las ramas (git switch -c) permiten reescribir vigilante.sh sin tocar lo que corre cada cinco minutos, con fusión y resolución manual de conflictos; las etiquetas (git tag -a v1.2) fijan qué versión está desplegada y se combinan con el despliegue a la flota de 07-06, por git checkout en cada servidor o por rsync excluyendo .git y la configuración local. El remoto —privado y con clave SSH— es a la vez copia de seguridad y punto de encuentro. Y el pre-commit automatiza las barreras: bash -n, ShellCheck, Bats y una búsqueda de secretos, con la doble advertencia de que los hooks no se versionan y de que --no-verify los salta. Por encima de todo eso queda el control de calidad más eficaz, que no es una herramienta: que otra persona lea tu cambio antes de que llegue a producción.
El hook pre-commit que acabamos de escribir invoca dos órdenes que aún no conocemos. La lección 08-05 se ocupa de la primera: ShellCheck, el analizador estático que encuentra los errores que este curso te ha enseñado a evitar —y unos cuantos más—, junto con shfmt para el formato uniforme que 08-01 dejó prometido. Recorreremos los avisos más frecuentes conectándolos con la lección donde se explicó el problema, aprenderemos a silenciar un aviso con criterio y no por comodidad, y pasaremos ShellCheck a los cinco scripts y a lib/comun.sh para clasificar y corregir todo lo que aparezca.
Curso de Programación en Bash
Módulo 1: Introducción a Bash
- ¿Qué es Bash?
- Configurando tu Entorno
- Navegación Básica en la Línea de Comandos
- Entendiendo el Shell
- Encontrar Ayuda: man, help y --help
Módulo 2: Comandos Básicos de Bash
- Operaciones con Archivos y Directorios
- Comandos de Procesamiento de Texto
- Permisos y Propiedad de Archivos
- Redirección y Tuberías
- Comodines y Expansión de Rutas
- Historial y Atajos de Teclado
Módulo 3: Fundamentos de Scripting
- Creando y Ejecutando un Script
- Variables y Constantes
- Operadores Básicos
- Sentencias Condicionales
- Argumentos y Entrada del Usuario
- Comillas, Expansión y Sustitución
Módulo 4: Scripting Intermedio
- Bucles en Bash
- Funciones en Bash
- Arrays y Arrays Asociativos
- Manipulación de Cadenas
- La Sentencia case y los Menús Interactivos
- Aritmética y Cálculos Numéricos
Módulo 5: Técnicas Avanzadas de Scripting
- Operaciones Avanzadas con Archivos
- Gestión de Procesos
- Manejo de Errores y Depuración
- Expresiones Regulares
- Entrada/Salida Avanzada: Descriptores y Here-Documents
- Scripts Modulares y Librerías Reutilizables
Módulo 6: Trabajando con Herramientas Externas
Módulo 7: Automatización y Programación
- Trabajos Cron
- Automatizando Tareas
- Scripts de Respaldo y Restauración
- Monitoreo y Registro
- Servicios y Temporizadores con systemd
- Automatización Remota con SSH
Módulo 8: Mejores Prácticas y Optimización
- Escribiendo Código Legible
- Optimizando Scripts en Bash
- Consideraciones de Seguridad
- Control de Versiones con Git
- Análisis Estático con ShellCheck y shfmt
- Pruebas Automatizadas con Bats
- Portabilidad: POSIX sh frente a Bashismos
