Tenim nou scripts que funcionen, cadascun amb les seves opcions, la seva configuració i la seva manera d'invocar-se. Això no és un producte: és una carpeta. Un company que entra demà no sap quin ha d'executar, l'ajuda està repartida en nou --help diferents, la instal·lació és «copia això i recorda't dels permisos», i ningú no sap quina versió corre a srv-veloz-02. Aquest últim projecte converteix la col·lecció en veloz-ops: una única ordre amb subordres, instal·lable, configurable, versionada, provada, desplegada a la flota i documentada. És el tancament del curs, i el pas que separa «sé programar en Bash» de «he lliurat alguna cosa».
Contingut
- El toolkit vist sencer
- El patró d'ordre única amb subordres
- Reorganització final del repositori
- Configuració unificada i
veloz-ops config - Autocompleció de Bash
- Instal·lació idempotent
- Versionatge semàntic i
veloz-ops version - Qualitat abans de desplegar
- Desplegament a la flota i tornada enrere
- Documentació: README i runbook
- El toolkit vist sencer
flowchart TD
U["veloz-ops (ordre unica)"]
U --> S2[estat] --> L2[libexec/info-sistema.sh]
U --> S3[logs] --> L3[libexec/analitza-logs.sh]
U --> S4[copia] --> L4[libexec/copia.sh]
U --> S5[xarxa] --> L5[libexec/monitor-xarxa.sh]
U --> S6[flota] --> L6[libexec/flota.sh]
L2 & L3 & L4 & L5 & L6 --> LIB["lib/comu.sh<br/>veloz_log · veloz_requereix · veloz_api_get"]
LIB --> CFG["etc/veloz-ops.conf + etc/*.d/"]
L3 --> D1[("/var/log/veloz/*.log")]
L4 --> D2[("/srv/veloz/dades + /copies")]
L5 --> D3(["veloz-api :8080 · flota SSH"])
TMR["systemd timers"] --> U
El diagrama mostra la propietat que busquem: una porta d'entrada, una llibreria comuna, una configuració. Els scripts passen a libexec/ perquè deixen de ser interfície pública; ningú no els invoca directament, els invoca veloz-ops.
- El patró d'ordre única amb subordres
És el patró de git, systemctl o docker, i en Bash s'implementa amb case i funcions (04-05):
#!/usr/bin/env bash
# veloz-ops - punt d'entrada unic del toolkit.
set -euo pipefail
export LC_ALL=C
readonly ARREL=${VELOZ_ARREL:-/opt/veloz-ops}
readonly LIBEXEC=$ARREL/libexec
# shellcheck source=lib/comu.sh
. "$ARREL/lib/comu.sh"
ajuda() {
cat <<'EOF'
Us: veloz-ops <subordre> [opcions]
estat Informacio del sistema (09-01)
logs Analisi de registres (09-02)
copia Copies de seguretat, verificacio i restauracio (09-03)
xarxa Monitoratge de xarxa i servei (09-04)
flota Executa una ordre als tres servidors
config Configuracio efectiva i el seu origen; version; ajuda <sub>
EOF
}
main() {
local sub=${1:-ajuda}; shift || true
case $sub in
estat) exec "$LIBEXEC/info-sistema.sh" "$@" ;;
logs) exec "$LIBEXEC/analitza-logs.sh" "$@" ;;
copia) exec "$LIBEXEC/copia.sh" "$@" ;;
xarxa) exec "$LIBEXEC/monitor-xarxa.sh" "$@" ;;
flota) exec "$LIBEXEC/flota.sh" "$@" ;;
config) mostra_config "$@" ;;
version) printf 'veloz-ops %s\n' "$VERSION" ;;
ajuda|-h|--help) [[ $# -gt 0 ]] && ajuda_de "$1" || ajuda ;;
*) printf 'Subordre desconeguda: %s\n\n' "$sub" >&2; ajuda >&2; exit 2 ;;
esac
}
main "$@"Tres decisions. exec reemplaça el procés en lloc de crear-ne un de fill: estalvia un procés i, més important, el codi de sortida i els senyals arriben a l'script real sense intermediari, així que Ctrl-C sobre veloz-ops copia interromp la còpia de debò. El case és una llista blanca explícita (08-03): construir el camí com a "$LIBEXEC/$sub.sh" seria més curt i permetria a qualsevol executar veloz-ops ../../bin/elquesigui. I la subordre desconeguda surt amb codi 2 i escriu a stderr, seguint el conveni de 05-03.
- Reorganització final del repositori
veloz-ops/ ├── bin/veloz-ops # unic executable al PATH ├── libexec/ # scripts interns, no al PATH │ ├── info-sistema.sh analitza-logs.sh copia.sh monitor-xarxa.sh flota.sh │ └── acces.awk # programes awk separats (09-02) ├── lib/comu.sh # funcions veloz_*, es carrega amb source ├── etc/veloz-ops.conf # 600; mes copia.d/ i monitor-xarxa.d/ ├── systemd/*.service *.timer ├── tests/*.bats ├── docs/runbook.md └── completion/ install.sh verificar.sh README.md CHANGELOG.md
El criteri val la pena enunciar-lo: bin/ és el que l'usuari invoca, libexec/ el que invoca el programa, lib/ el que es carrega amb source i mai no s'executa, etc/ el que s'edita sense desplegar. Aquesta última frase decideix els dubtes: si canviar-ho exigeix revisió de codi, és codi; si és un camí, un llindar o una llista d'objectius, és configuració.
- Configuració unificada i
veloz-ops config
veloz-ops configLa precedència de 05-06 —defecte < fitxer < entorn < opcions— s'implementa en un únic punt de lib/comu.sh, i es fa auditable desant l'origen de cada valor:
declare -A CFG ORIGEN
veloz_config_carrega() {
local f=$1 k v env_var
while IFS='=' read -r k v; do # fitxer: trepitja els defectes
[[ $k == \#* || -z $k ]] && continue
k=${k// /}; v=${v%\"}; v=${v#\"}; CFG[$k]=$v; ORIGEN[$k]="fitxer:$f"
done < "$f"
for k in "${!CFG[@]}"; do # entorn: trepitja el fitxer
env_var=VELOZ_$k
[[ -n ${!env_var:-} ]] && { CFG[$k]=${!env_var}; ORIGEN[$k]=entorn; }
done
}${!env_var} és l'expansió indirecta (03-06): construeix el nom VELOZ_LLINDAR_DISC i llegeix aquesta variable. Desar l'origen al costat del valor costa un array associatiu més i resol la pregunta que consumeix hores en producció: «el llindar és a 95, però el fitxer diu 85, d'on surt?». veloz-ops config ho imprimeix en tres columnes —clau, valor, origen— i per això és una subordre de primera classe i no un echo de depuració.
- Autocompleció de Bash
# completion/veloz-ops.bash -> /etc/bash_completion.d/veloz-ops
_veloz_ops() {
local act=${COMP_WORDS[COMP_CWORD]} opcions='estat logs copia xarxa flota config version ajuda'
(( COMP_CWORD > 1 )) && case ${COMP_WORDS[1]} in
copia) opcions='executa verifica restaura informe --simula' ;;
estat) opcions='--format --seccio --ajuda' ;;
xarxa) opcions='comprova resum directe --objectiu' ;;
esac
mapfile -t COMPREPLY < <(compgen -W "$opcions" -- "$act")
}
complete -F _veloz_ops veloz-opsCOMP_WORDS i COMP_CWORD són les variables que Bash omple en prémer Tab, i compgen -W filtra una llista pel prefix teclejat. No és cosmètica: l'autocompleció és la documentació que la gent llegeix de debò, i evita la meitat dels errors de teclejat en una subordre delicada com restaura.
- Instal·lació idempotent
install.sh ha de poder executar-se cent vegades amb el mateix resultat (07-02):
installa() {
install -d -m 755 "$PREFIX"/{bin,libexec,lib,etc,docs}
install -m 755 bin/veloz-ops "$PREFIX/bin/veloz-ops"
install -m 755 libexec/*.sh "$PREFIX/libexec/"
install -m 644 libexec/*.awk lib/comu.sh -t "$PREFIX/lib/"
[[ -e $PREFIX/etc/veloz-ops.conf ]] ||
install -m 600 etc/veloz-ops.conf.exemple "$PREFIX/etc/veloz-ops.conf"
install -m 644 completion/veloz-ops.bash /etc/bash_completion.d/veloz-ops
ln -sfn "$PREFIX/bin/veloz-ops" /usr/local/bin/veloz-ops
install -m 644 systemd/*.service systemd/*.timer /etc/systemd/system/
systemctl daemon-reload
systemctl enable --now veloz-copia.timer veloz-xarxa.timer
"$PREFIX/bin/veloz-ops" version
}install en lloc de cp + chmod perquè crea el destí i fixa els permisos en una sola operació, i és idempotent per naturalesa; ln -sfn sobreescriu l'enllaç sense queixar-se; systemctl enable --now activa i arrenca, i repetir-ho no fa cap mal. La línia clau és la condicional: la configuració existent no es trepitja mai. Un instal·lador que sobreescriu veloz-ops.conf esborra els llindars que algú va ajustar a les tres de la matinada; per això el repositori versiona veloz-ops.conf.exemple i no el fitxer real (08-04). La desinstal·lació és simètrica: --desinstalla atura i deshabilita els timers, esborra les unitats, fa daemon-reload, elimina l'enllaç i l'arbre, i conserva etc/ i les còpies de seguretat, avisant d'on queden.
- Versionatge semàntic i
veloz-ops version
veloz-ops version| Canvi | Incrementa | Exemple |
|---|---|---|
| S'elimina o es reanomena una subordre o opció; canvia el format d'un fitxer d'estat | MAJOR | --seccio passa a --area |
| Subordre o opció noves, compatibles | MENOR | S'afegeix veloz-ops xarxa resum |
| Correcció sense canvi d'interfície | PEDAÇ | S'arregla el càlcul de la mediana |
La versió surt de Git, no d'una constant que algú s'oblidarà d'actualitzar (08-04):
VERSION=$(git -C "$ARREL" describe --tags --dirty 2>/dev/null || cat "$ARREL/VERSION" 2>/dev/null || echo desconeguda)git describe --tags --dirty dóna v1.4.0 en una etiqueta exacta, v1.4.0-7-gab12cd3 si hi ha set commits a sobre —cosa que revela a l'instant que en aquell servidor hi ha alguna cosa sense publicar— i afegeix -dirty si hi ha canvis sense confirmar, que en producció és una alerta en si mateixa. L'install.sh escriu a més un fitxer VERSION per al desplegament per rsync sense repositori.
- Qualitat abans de desplegar
Un sol script recull totes les comprovacions del mòdul 8:
#!/usr/bin/env bash
# verificar.sh - tot el que ha de passar abans de desplegar.
set -uo pipefail
fallades=0
mapfile -t SCRIPTS < <(find bin libexec lib -type f)
pas() { printf '\n== %s ==\n' "$1"; shift; "$@" || ((fallades++)); }
pas sintaxi bash -n "${SCRIPTS[@]}"
pas shellcheck shellcheck -x -S style "${SCRIPTS[@]}"
pas format shfmt -d -i 2 -ci "${SCRIPTS[@]}"
pas proves bats tests/
pas ajuda bin/veloz-ops ajuda
exit $(( fallades > 0 ))L'ordre va del que és ràpid al que és lent: bash -n triga mil·lisegons i descarta un error de sintaxi abans de gastar trenta segons en Bats. shellcheck -x segueix els source per analitzar també lib/comu.sh (08-05), i -S style puja el llistó per damunt dels errors. shfmt -d no reformata, només mostra la diferència i falla: en CI cal fallar, no arreglar per sorpresa. La bateria de Bats s'amplia a les subordres (08-06):
@test "subordre desconeguda surt amb 2 i escriu a stderr" {
run bin/veloz-ops noexisteix
[ "$status" -eq 2 ]
[[ $output == *"Subordre desconeguda"* ]]
}
@test "copia --simula no crea cap directori" {
local abans; abans=$(find "$BATS_TMPDIR/copies" -type d | wc -l)
run bin/veloz-ops copia executa --simula
[ "$status" -eq 0 ]
[ "$(find "$BATS_TMPDIR/copies" -type d | wc -l)" -eq "$abans" ]
}La segona prova és la més valuosa del toolkit: verifica que el mode simulació no té efectes, que és la promesa en què es recolza qualsevol abans de llançar una còpia amb --delete. El hook pre-commit (08-04) executa les tres primeres etapes i CI la bateria completa a cada branca.
- Desplegament a la flota i tornada enrere
desplega() { # $1 = etiqueta, p. ex. v1.4.0
local etiqueta=$1 host
git tag -l "$etiqueta" | grep -q . || veloz_morir 2 "etiqueta inexistent: $etiqueta"
./verificar.sh || veloz_morir 3 "no es desplega codi que no verifica"
for host in srv-veloz-01 srv-veloz-02 srv-veloz-03; do
printf '\n--- %s ---\n' "$host"
ssh -n "$host" "cd /opt/veloz-ops && git fetch --tags -q &&
git checkout -q $etiqueta && sudo ./install.sh" || return 1
ssh -n "$host" 'veloz-ops version && veloz-ops estat --seccio serveis' || return 1
done
}El desplegament és seqüencial i amb verificació posterior a cada amfitrió: si srv-veloz-01 falla, no es toca el segon, i en el pitjor dels casos queda un servidor malament i dos d'intactes. ssh -n (07-06) impedeix que el bucle es mengi l'entrada estàndard, l'error clàssic que fa que només es desplegui el primer amfitrió. Es desplega una etiqueta, mai una branca: git checkout v1.4.0 dóna exactament el codi que es va provar, i la tornada enrere és la mateixa ordre amb l'etiqueta anterior (desplega v1.3.2). Que revertir sigui idèntic a desplegar és el que fa que algú s'atreveixi a executar-ho a les quatre de la matinada. Amb una excepció per al runbook: revertir el codi és trivial, revertir un canvi de format dels fitxers d'estat o de l'històric no ho és, i per això aquest tipus de canvi incrementa la versió MAJOR.
- Documentació: README i runbook
Són dos documents amb dos lectors diferents i formen part del lliurable. El README.md el llegeix qui arriba nou, en fred: què és, què requereix, com s'instal·la en cinc ordres copiables, les subordres amb un exemple real cadascuna i on és la configuració. Si algú no pot instal·lar-lo i executar veloz-ops estat seguint només el README, el README està malament. El docs/runbook.md el llegeix algú mig adormit amb una alerta al mòbil: s'organitza per alerta, no per script, sense prosa i amb ordres copiables.
## ALERTA: copia amb fallades a srv-veloz-01
1. Confirmar: veloz-ops copia informe
2. Veure el log: journalctl -u veloz-copia.service -n 50
3. Si "sense espai" (codi 5): df -h /copies; revisar RETENCIO_* del perfil
4. Si "verificacio fallida" (codi 4): NO esborris res; restaura de srv-veloz-03
5. Escalar a Operacions si el perfil dades falla dues nits seguides.Un script sense documentació és un script que només pot executar qui el va escriure; i qui el va escriure acabarà de vacances justament la nit que falli.
Errors Habituals i Consells
- Construir el camí de la subordre amb l'argument de l'usuari.
"$LIBEXEC/$1.sh"obre la porta a../. Llista blanca ambcase, sempre. - Instal·ladors que trepitgen la configuració. Versiona
.conf.exemple; el.confreal només es crea si no existeix. - Desplegar una branca en lloc d'una etiqueta.
mainsignifica una cosa diferent cada hora; una etiqueta significa sempre el mateix. - Bucle
for hostambsshsense-n. El primersshconsumeix la llista i la resta de la flota es queda sense desplegar, en silenci. - Documentar per script en comptes de per alerta. A les quatre de la matinada ningú no busca «analitza-logs»; busca el text que li ha arribat al mòbil.
- Consell: executa
veloz-ops configals tres servidors i compara. Les diferències no explicades entre nodes són l'origen de la meitat dels incidents de «en aquest servidor sí que funciona».
Exercicis
- Subordre
doctor. Verifica la salut de la mateixa instal·lació: dependències (jq,rsync,curl,awk), permisos d'etc/(600), timers actius, antiguitat de l'última còpia i de l'últim cicle del monitor. Un símbol per comprovació i codi 0/1/2 global. - Panell de l'estat de la flota. Un
veloz-ops flota estatque reculli el JSON d'estatdels tres servidors en paral·lel i marqui les diferències de versió, kernel i configuració efectiva. - Reptes oberts d'ampliació. Empaqueta el toolkit com a
.debambpostinst/prermi la configuració marcada com aconffiles; afegeix unveloz-ops apique exposi els informes per HTTP decidint què no exposar; unifica un--jsona totes les subordres amb una prova Bats que validi cada sortida; i porta el toolkit a Debian o Alpine anotant cada bashisme que calgui resoldre (08-07).
Solucions
1. doctor no comprova res pel seu compte: reutilitza el que ja està construït.
doctor() {
local fallades=0
chk() { local n=$1; shift
if "$@" >/dev/null 2>&1; then printf ' [OK] %s\n' "$n"
else printf ' [FALLADA] %s\n' "$n"; ((fallades++)); fi; }
chk "dependencies" veloz_requereix jq rsync curl awk
chk "permisos d'etc" test "$(stat -c %a "$ARREL/etc/veloz-ops.conf")" = 600
chk "timer de copia" systemctl is-active --quiet veloz-copia.timer
chk "copia recent" test "$(dies_ultima_copia)" -le 1
(( fallades == 0 )) && return 0 || return 2
}La funció imbricada chk, que rep una ordre i l'executa, evita repetir el mateix if deu vegades; i retornar 0/2 permet que el mateix doctor es vigili des de vigilant.sh amb el conveni de 07-04.
2. flota.sh ja executa en paral·lel, així que n'hi ha prou amb consolidar els JSON amb jq -s:
veloz-ops flota 'veloz-ops estat --format json' | jq -s '
map({host, version, kernel})
| {nodes: ., kernels_diferents: (map(.kernel) | unique | length)}'Detectar la divergència —no llistar els valors— és el que converteix el panell en una eina útil: si kernels_diferents és més gran que 1, hi ha un servidor sense actualitzar.
Conclusió
Vam començar el curs escrivint echo "Hola" en un terminal i l'acabem amb un producte instal·lat en tres servidors: una ordre única amb subordres i ajuda, configuració amb precedència i auditable, autocompleció, instal·lador idempotent i desinstal·lador simètric, versionatge semàntic lligat a les etiquetes de Git, una bateria de proves que corre a cada commit, desplegament per etiqueta amb tornada enrere i documentació escrita per a qui la llegirà de matinada. Cap d'aquestes peces no és Bash avançat: són case, funcions, arrays, printf, source i codis de sortida —això del mòdul 4— aplicats amb el criteri del mòdul 8. Aquest és el missatge del curs sencer: la diferència entre un script i una eina no és al llenguatge, és a la disciplina.
Convé acabar amb honestedat sobre els límits. Bash és insuperable enganxant programes, movent fitxers i automatitzant el sistema; és exactament l'eina correcta per als cinc projectes d'aquest mòdul. Deixa de ser-ho quan apareixen estructures de dades imbricades (més de dos nivells de JSON i jq ja no basta), quan calen proves unitàries de lògica de negoci, concurrència real, aritmètica de coma flotant sostinguda, o quan l'script passa de les mil línies i cada canvi fa por. Si et trobes escrivint un analitzador d'expressions o un client HTTP amb estat en Bash, el senyal és clar: toca Python, Go o el llenguatge que faci servir el teu equip. Saber quan parar forma part de dominar l'eina, i reescriure en un altre llenguatge un script de Bash ben estructurat és senzill precisament perquè estava ben estructurat.
I per on continuar. El manual de Bash (man bash, i sobretot la seva secció d'expansions, que es llegeix sencera un cop a la vida) és l'única font definitiva. La guia d'estil de shell de Google, per discutir decisions amb arguments en comptes de per gustos. ShellCheck com a mestre permanent: cada avís que no entenguis, busca'l pel seu codi i aprendràs alguna cosa que no sabies. I sobretot, el que de debò consolida: automatitza el teu. Agafa aquella tasca que fas a mà cada dilluns, escriu-la com un script amb set -euo pipefail, dóna-li opcions, posa-li un --simula, prova-la amb Bats, puja-la a Git i dispara-la amb un timer. En això consisteix l'ofici, i ja ho saps fer sencer.
Curs de Programació en Bash
Mòdul 1: Introducció a Bash
- Què és Bash?
- Configurar el teu Entorn
- Navegació Bàsica per la Línia d'Ordres
- Entendre el Shell
- Trobar Ajuda: man, help i --help
Mòdul 2: Ordres Bàsiques de Bash
- Operacions amb Fitxers i Directoris
- Ordres de Processament de Text
- Permisos i Propietat dels Fitxers
- Redirecció i Canonades
- Comodins i Expansió de Rutes
- Historial i Dreceres de Teclat
Mòdul 3: Fonaments de Scripting
- Crear i Executar un Script
- Variables i Constants
- Operadors Bàsics
- Sentències Condicionals
- Arguments i Entrada de l'Usuari
- Cometes, Expansió i Substitució
Mòdul 4: Scripting Intermedi
- Bucles en Bash
- Funcions en Bash
- Arrays i Arrays Associatius
- Manipulació de Cadenes
- La Sentència case i els Menús Interactius
- Aritmètica i Càlculs Numèrics
Mòdul 5: Tècniques Avançades de Scripting
- Operacions Avançades amb Fitxers
- Gestió de Processos
- Gestió d'Errors i Depuració
- Expressions Regulars
- Entrada/Sortida Avançada: Descriptors i Here-Documents
- Scripts Modulars i Llibreries Reutilitzables
Mòdul 6: Treballar amb Eines Externes
Mòdul 7: Automatització i Programació
- Tasques Cron
- Automatitzar Tasques
- Scripts de Còpia i Restauració
- Monitoratge i Registre
- Serveis i Temporitzadors amb systemd
- Automatització Remota amb SSH
Mòdul 8: Bones Pràctiques i Optimització
- Escriure Codi Llegible
- Optimitzar Scripts en Bash
- Consideracions de Seguretat
- Control de Versions amb Git
- Anàlisi Estàtica amb ShellCheck i shfmt
- Proves Automatitzades amb Bats
- Portabilitat: POSIX sh enfront de Bashismes
