Tot el que has construït passa en un sol servidor. Però en producció n'hi ha tres —srv-veloz-01, srv-veloz-02 i srv-veloz-03—, i comprovar l'estat de la flota entrant a mà a cadascun no és automatització: és el mateix d'abans amb més finestres de terminal. ssh és l'eina que converteix un script local en una ordre que s'executa en qualsevol màquina, i amb ella el toolkit fa l'últim salt del mòdul. Veuràs SSH des del punt de vista d'un script —bastant diferent del d'una persona escrivint en un terminal—, com autenticar-se sense que ningú teclegi una contrasenya i sense obrir cap forat, i com recórrer una flota en paral·lel sense que un servidor caigut tombi el recorregut.
Contingut
sshdes d'un script: quin shell obtens i quin codi retorna- Autenticació per clau
- Limitar el que una clau automatitzada pot fer
~/.ssh/configi la reutilització de connexió- Les opcions que un script necessita
known_hostsben gestionat- Blocs remots amb here-document
- Enviar scripts i transferir fitxers
- Recórrer la flota, en sèrie i en paral·lel
sudoremot i quan Bash deixa de ser la resposta- Aplicació: neix
flota.sh
ssh des d'un script: quin shell obtens i quin codi retorna
ssh des d'un script: quin shell obtens i quin codi retornaSense ordre, ssh obre una sessió interactiva. Amb ordre (ssh veloz@srv-veloz-02 'uptime') passa una cosa diferent i molt important: s'executa un shell no interactiu i no d'inici de sessió, exactament la mateixa situació que cron (07-01).
Sessió interactiva ssh host |
ssh host ordre |
|
|---|---|---|
Llegeix ~/.bash_profile o ~/.bashrc? |
Sí | No (llevat de BASH_ENV, rar) |
Àlies i funcions del teu perfil? / PATH |
Sí / el del teu perfil | No / el mínim del sistema |
| Hi ha TTY? | Sí | No, llevat de -t |
Comprova-ho amb ssh veloz@srv-veloz-02 'echo "$PATH"': respon /usr/local/bin:/usr/bin:/bin, i no el que veus en entrar a mà. La conseqüència és la mateixa que amb cron, i la solució també: camins absoluts sempre, i si necessites configuració, carrega-la explícitament. Un ssh srv-veloz-02 'estat-servei.sh' fallarà amb «command not found» encara que l'script funcioni perfectament quan hi entres a mà. I com que no hi ha TTY, una ordre remota que pregunti alguna cosa es queda penjada o falla: s'aplica la propietat 1 de 07-02, res interactiu, ara també a l'altre costat del cable. ssh retorna el codi de sortida de l'ordre remota, cosa magnífica: tota la teva lògica d'errors de 05-03 viatja per la xarxa. Però hi ha una excepció crítica: el 255 és el codi que ssh fa servir per als seus propis errors —host inabastable, autenticació rebutjada, clau desconeguda—. Distingir-lo és el que separa «el servidor està caigut» de «el servei del servidor està caigut», dues incidències molt diferents:
sortida=$(ssh -n -o BatchMode=yes veloz@srv-veloz-02 'estat-servei.sh' 2>&1) && codi=0 || codi=$?
case "$codi" in
0) veloz_log_info "srv-veloz-02: OK" ;;
255) veloz_log_error "srv-veloz-02: INACCESSIBLE (${sortida%%$'\n'*})" ;;
*) veloz_log_warn "srv-veloz-02: ha fallat amb codi $codi" ;;
esacL'ambigüitat és teòrica però real: si la teva ordre remota retornés 255 pel seu compte, no ho podries distingir; per això els codis convencionals de 05-03 es queden per sota de 128.
- Autenticació per clau
Un script no pot teclejar una contrasenya; l'autenticació per clau pública ho resol i a més és més segura.
ssh-keygen -t ed25519 -C "veloz-ops a srv-veloz-01" -f ~/.ssh/id_veloz_ops
ssh-copy-id -i ~/.ssh/id_veloz_ops.pub veloz@srv-veloz-02-t ed25519 tria l'algorisme recomanat avui: claus curtes, ràpides i sòlides (RSA de 4096 bits val si el destí és antic). -f dona un nom propi, per no barrejar la clau d'automatització amb la teva personal; es generen id_veloz_ops (privada, mai no surt de la màquina) i id_veloz_ops.pub (pública, la que es copia). ssh-copy-id l'afegeix al ~/.ssh/authorized_keys del destí i arregla els permisos, cosa important perquè SSH és estricte amb ells i falla en silenci si no li agraden: ~/.ssh a 700, authorized_keys i la clau privada a 600, i ~ sense escriptura per a grup ni altres —si no, qualsevol podria reemplaçar .ssh—. La frase de pas és la decisió incòmoda: una clau protegida és més segura, però algú l'ha d'escriure. ssh-agent desa la clau desxifrada a memòria durant la teva sessió (eval "$(ssh-agent)" i ssh-add ~/.ssh/id_veloz_ops), cosa perfecta per a la feina interactiva i inútil per a una tasca de cron a les 03:00, quan no hi ha ni sessió ni agent. Les tasques automàtiques fan servir claus sense frase de pas, i cal ser honest sobre el risc: qui aconsegueixi aquell fitxer té accés a tots els destins on està autoritzada. Com que el risc no es pot evitar, s'acota: permisos 600 i propietari correcte, una clau diferent per finalitat per revocar-la sense afectar la resta, i restriccions al destí.
- Limitar el que una clau automatitzada pot fer
Una entrada d'authorized_keys admet opcions davant de la clau que limiten el que s'hi pot fer. És la diferència entre una clau que dona accés total al servidor i una que només serveix per al que vas dir:
from="10.0.0.11",command="/home/veloz/veloz-ops/bin/despatxador-remot.sh",no-port-forwarding,no-agent-forwarding,no-pty ssh-ed25519 AAAAC3Nz... veloz-ops
from= impedeix fer servir la clau des d'una altra adreça; no-port-forwarding impedeix fer servir el servidor com a túnel cap a la xarxa interna; no-agent-forwarding impedeix aprofitar el teu agent per saltar a altres màquines; no-pty nega el terminal interactiu; i command= obliga a executar sempre aquella ordre, sigui quina sigui la que demani el client. Aquesta última és la més potent i la que més s'oblida: encara que algú robi la clau, l'única cosa que pot fer és llançar aquell script. L'ordre que el client va demanar queda a $SSH_ORIGINAL_COMMAND, cosa que permet un petit despatxador que només accepti el que està previst:
# despatxador-remot.sh — unica ordre permesa per la clau d'operacions
case "${SSH_ORIGINAL_COMMAND:-}" in
estat) exec /home/veloz/veloz-ops/bin/estat-servei.sh ;;
copia) exec /home/veloz/veloz-ops/bin/copia.sh ;;
*) printf 'ordre no permesa\n' >&2; exit 64 ;;
esacEl case amb llista tancada (04-05) és el correcte: enumera el que està permès en lloc de filtrar el que és perillós. I mai no passis $SSH_ORIGINAL_COMMAND a eval ni a un shell; seria regalar l'execució arbitrària que acabes d'evitar (08-03).
~/.ssh/config i la reutilització de connexió
~/.ssh/config i la reutilització de connexióRepetir usuari, port i clau a cada crida és soroll; ~/.ssh/config (permisos 600) ho centralitza.
Host srv-veloz-*
User veloz
IdentityFile ~/.ssh/id_veloz_ops
IdentitiesOnly yes
ConnectTimeout 5
ServerAliveInterval 15
ControlMaster auto
ControlPath ~/.ssh/cm-%r@%h:%p
ControlPersist 60
Host srv-veloz-03
HostName 10.0.2.33
ProxyJump bastion.veloz.exampleHost srv-veloz-* s'aplica a tots els que casin amb el patró, i els blocs més específics s'hi sumen. IdentitiesOnly yes evita que SSH ofereixi totes les teves claus abans de la correcta, cosa que pot provocar «massa intents d'autenticació»; ServerAliveInterval detecta una connexió morta en lloc de deixar-la penjada; i ProxyJump salta a través d'una màquina intermèdia sense túnels a mà. ControlMaster és la joia per als bucles. Amb auto, la primera connexió a un host obre un sòcol de control a ControlPath i les següents reutilitzen aquella connexió en lloc de negociar de nou; ControlPersist 60 manté el sòcol un minut després de l'última. Establir una sessió SSH costa entre 200 i 500 ms, així que un bucle amb deu ordres sobre tres servidors passa de diversos segons a gairebé res:
executar time ssh srv-veloz-02 true dues vegades seguides dona 0m0,412s la primera i 0m0,038s la segona, deu vegades més ràpida perquè no s'ha tornat a autenticar. Un avís: si el sòcol queda orfe, ssh -O exit srv-veloz-02 el tanca.
- Les opcions que un script necessita
| Opció | Què fa | Per què és imprescindible |
|---|---|---|
-n |
Entrada estàndard des de /dev/null |
Mira més avall: és **l'**error clàssic |
-o BatchMode=yes |
No pregunta mai; falla en lloc seu | Sense ella es penja demanant contrasenya |
-o ConnectTimeout=5 |
Màxim per connectar | Un host caigut no pot penjar la tasca |
-o StrictHostKeyChecking=accept-new |
Accepta hosts nous, rebutja canvis | Apartat 6 |
El problema del -n mereix el seu propi paràgraf, perquè desconcerta tothom el primer cop. A while read -r host; do ssh "$host" 'uptime'; done < servidors.txt només es processa el primer servidor, i la raó és que ssh llegeix l'entrada estàndard per enviar-la a l'ordre remota. Dins del bucle, l'entrada estàndard és servidors.txt, així que el primer ssh s'empassa la resta del fitxer i el read de la segona volta no hi troba res: el bucle acaba després d'un sol servidor, sense error i sense explicació. La solució és ssh -n, un idioma que cal memoritzar igual que el while IFS= read -r de 04-01. L'alternativa és fer servir el descriptor 3 (done < servidors.txt 3<&0 i read -u 3), però -n és més simple. I compte: només es posa -n quan no vols alimentar l'ordre remota; si li estàs passant dades per una canonada, no hi ha de ser.
known_hosts ben gestionat
known_hosts ben gestionatLa primera vegada que et connectes a una màquina, SSH pregunta si n'acceptes l'empremta. Aquella pregunta penja un script, i la temptació és -o StrictHostKeyChecking=no, cosa que és una mala idea: desactiva la comprovació que impedeix que un atacant es faci passar pel teu servidor. Que sigui comú no la fa correcta. Les dues maneres correctes:
# a) Acceptar hosts nous, pero seguir rebutjant canvis de clau
ssh -o StrictHostKeyChecking=accept-new -n veloz@srv-veloz-02 'uptime'
# b) Registrar les claus per endavant, en l'aprovisionament
ssh-keyscan -t ed25519 srv-veloz-01 srv-veloz-02 srv-veloz-03 >> ~/.ssh/known_hostsaccept-new és el punt mitjà raonable: no pregunta la primera vegada, però si la clau d'un host canvia —senyal d'un atac o d'una reinstal·lació— falla sorollosament, que és justament el que vols. La (b) és més rigorosa i l'adequada per a un entorn seriós: les claus es recullen un cop, es verifiquen i es distribueixen amb la resta de la configuració. Quan reinstal·lis un servidor i la clau canviï legítimament, ssh-keygen -R srv-veloz-02 esborra l'entrada antiga.
- Blocs remots amb here-document
Per a diverses ordres remotes, ficar-les en una cadena amb ; es torna il·legible; la manera neta fa servir un here-document (05-05) alimentant un shell remot.
ssh -o BatchMode=yes veloz@srv-veloz-02 bash -s <<'EOF'
set -euo pipefail
cd /srv/veloz/dades
printf 'srv-veloz-02: %s enviaments, %s lliures\n' "$(wc -l < enviaments.csv)" "$(df -h --output=avail /srv|tail -1)"
EOFbash -s li diu al Bash remot que llegeixi l'script de la seva entrada estàndard, i el here-document l'hi proporciona. Fixa't que aquí no s'hi posa -n: l'entrada estàndard és precisament el canal per on viatja l'script. I les cometes simples del delimitador són la part crítica d'aquest idioma, perquè decideixen on s'expandeixen les variables. Amb <<'EOF' el text viatja literal i $var s'expandeix al destí; amb <<EOF sense cometes, el Bash local expandeix abans d'enviar. Compara-ho:
data=2026-08-03
ssh srv-veloz-02 bash -s <<'EOF'
echo "remot: $(hostname), data: $data" # -> remot: srv-veloz-02, data:
EOF
ssh srv-veloz-02 bash -s <<EOF
echo "remot: \$(hostname), data: $data" # -> remot: srv-veloz-02, data: 2026-08-03
EOFEn el primer cas, $(hostname) s'executa al destí (correcte) i $data surt buida perquè aquella variable no existeix allà. En el segon, $data l'expandeix el shell local abans d'enviar i \$(hostname) va escapada per executar-se al destí. Barrejar els dos mons produeix errors desconcertants, així que la recomanació és fer servir sempre <<'EOF' i passar els valors locals com a arguments, més segur i més llegible:
ssh srv-veloz-02 bash -s -- "$data" "$llindar" <<'EOF'
set -euo pipefail
data="$1"; llindar="$2"
awk -F, -v f="$data" -v u="$llindar" '$2 ~ f && $5 == "incidencia" { c++ }
END { print (c + 0 >= u ? "ALERTA" : "OK"), c + 0 }' /srv/veloz/dades/enviaments.csv
EOFEls arguments després de -- arriben a l'script remot com a $1, $2… Així no hi ha cap expansió local que s'hi coli, i un valor amb espais o cometes no pot alterar el significat del codi remot: la mateixa defensa contra la injecció que aplicaves amb awk -v (06-01) i jq --arg (06-05).
- Enviar scripts i transferir fitxers
Quan la feina remota passa de vint línies deixa de tenir sentit incrustar-la.
# a) Enviar per l'entrada estandard, sense deixar rastre al desti
ssh -o BatchMode=yes veloz@srv-veloz-02 'bash -s' -- --dry-run < ~/veloz-ops/bin/comprova-node.sh
# b) Copiar, executar i netejar conservant el codi de sortida
scp -q ~/veloz-ops/bin/comprova-node.sh veloz@srv-veloz-02:/tmp/ && ssh -n veloz@srv-veloz-02 \
'bash /tmp/comprova-node.sh; rc=$?; rm -f /tmp/comprova-node.sh; exit $rc'L'opció (a) és elegant i la que convé per defecte: no toca el disc remot, així que no cal netejar res ni queda una versió antiga oblidada. La (b) cal quan l'script ha de persistir; fixa't que desa el codi de sortida abans d'esborrar, per no perdre'l amb el rm. Per a fitxers, scp està bé per a un de sol, però per a directoris rsync és superior en tot —transfereix només el que ha canviat, preserva permisos, permet excloure, es reprèn i accepta --link-dest—:
rsync -az --delete -e 'ssh -o BatchMode=yes -o ConnectTimeout=5' /copies/diaries/ srv-veloz-02:/copies/n01/-e fixa el transport amb totes les opcions de la connexió, i -z sí que té sentit aquí perquè va per xarxa (recorda de 07-03 que en local no aportava res). Això completa la regla 3-2-1: la còpia que surt del servidor. I per fer-ho bé, la clau de la còpia remota es restringeix al destí amb command="rrsync -wo /copies", un embolcall que només permet a rsync escriure sota aquell camí: l'apartat 3 aplicat al cas més freqüent.
- Recórrer la flota, en sèrie i en paral·lel
El requisit central: que un servidor caigut no avorti el recorregut. Amb set -e actiu, un ssh fallit mataria l'script al primer host inaccessible i perdries la informació dels altres dos. L'idioma ordre && codi=0 || codi=$? és el que neutralitza set -e per a aquella ordre i captura el codi alhora (05-03), i a partir d'aquí un case com el de l'apartat 1 distingeix el 255 de la resta. Tres servidors en sèrie, a dos segons cadascun, són sis segons; amb trenta, un minut. En paral·lel, el temps és el del més lent. Reprenent 05-02, amb un advertiment important: si diversos processos escriuen alhora a la mateixa sortida, les línies s'entrebarregen, així que cada branca escriu al seu propi fitxer i es consolida al final. Quatre decisions fan que el recorregut paral·lel de l'apartat 11 funcioni. Cada branca de fons escriu el seu codi i la seva sortida en fitxers separats dins d'un mktemp -d, perquè una subshell no pot retornar valors al pare per variables. El wait || true espera totes i evita que el codi diferent de zero d'una branca avorti l'script. La consolidació recorre la llista de servidors en el seu ordre original, així que l'informe surt determinista encara que els nodes responguin en qualsevol ordre —un informe l'ordre del qual canvia cada dia és incomparable—. I un trap … EXIT neteja el temporal. Per a flotes grans convé limitar la concurrència: xargs -P 10 (05-01) és la manera més simple de no passar de deu connexions simultànies.
sudo remot i quan Bash deixa de ser la resposta
sudo remot i quan Bash deixa de ser la respostaSi l'ordre remota necessita privilegis, el problema és que sudo voldrà una contrasenya i no hi ha cap terminal on escriure-la. La manera correcta és autoritzar una ordre concreta sense contrasenya al destí, amb una línia veloz ALL=(root) NOPASSWD: /usr/bin/systemctl restart veloz-api a /etc/sudoers.d/veloz-ops. Amb això, ssh -n srv-veloz-02 'sudo /usr/bin/systemctl restart veloz-api' funciona sense preguntar. Camí absolut, sense comodins i per a un usuari concret: un NOPASSWD: ALL equival a donar la contrasenya de root a qui robi la clau. L'alternativa sudo -S, que llegeix la contrasenya de l'entrada estàndard, obliga a enviar-la per una canonada, que és justament el que intentàvem evitar. I aquí apareix requiretty: algunes configuracions de sudoers exigeixen un terminal, cosa que fa fallar tot sudo remot no interactiu amb «sorry, you must have a tty to run sudo»; s'arregla amb Defaults:veloz !requiretty al destí. Forçar el TTY amb ssh -t és la solució bruta, perquè a més barreja stdout i stderr i desordena la sortida. Cal saber reconèixer el límit. Amb tres servidors i comprovacions simples, flota.sh és perfecte: no hi ha res per instal·lar, és transparent i l'entén qualsevol. Però si comences a necessitar plantilles de configuració, gestió de paquets, control que l'estat desitjat es compleix, inventaris dinàmics o execució sobre cinquanta màquines, estàs reescrivint malament el que Ansible ja fa bé —i per SSH, sense agent, amb el mateix model mental—; pssh/parallel-ssh és l'esglaó intermedi per llançar la mateixa ordre en molts hosts. El senyal d'alarma és clar: quan el teu script de Bash comença a portar a dins una llista de paquets per instal·lar i plantilles de fitxers de configuració, ha deixat de ser un script d'operacions.
- Aplicació: neix
flota.sh
flota.shL'últim script del toolkit executa estat-servei.sh als tres servidors en paral·lel i consolida el resultat.
#!/usr/bin/env bash
# flota.sh — Estat consolidat de la flota. QUAN: cada 15 min (temporitzador).
# Codis: 0 tota la flota OK | 1 algun node amb problema | 2 algun node inaccessible
set -euo pipefail
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin; export LC_ALL=C
readonly BASE="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
. "$BASE/lib/comu.sh"; [[ -r $BASE/etc/veloz-ops.conf ]] && . "$BASE/etc/veloz-ops.conf"
VELOZ_COMPONENT=flota
readonly -a FLOTA=(${VELOZ_FLOTA:-srv-veloz-01 srv-veloz-02 srv-veloz-03})
readonly REMOT=/home/veloz/veloz-ops/bin/estat-servei.sh
readonly -a SSH_OPC=(-n -o BatchMode=yes -o ConnectTimeout=5 -o ControlMaster=auto
-o StrictHostKeyChecking=accept-new -o ControlPersist=30
-o ControlPath="$HOME/.ssh/cm-%r@%h:%p")
estat_text() { case "$1" in 0) printf OK ;; 255) printf INACCESSIBLE ;; *) printf "FALLADA($1)" ;; esac; }
main() {
veloz_requereix ssh; local tmp host cod pitjor=0
tmp=$(mktemp -d "${TMPDIR:-/tmp}/flota.XXXXXX"); trap 'rm -rf "$tmp"' EXIT
for host in "${FLOTA[@]}"; do
{ local sor c; sor=$(timeout 60s ssh "${SSH_OPC[@]}" "$host" "$REMOT" 2>&1) && c=0 || c=$?
printf '%s\n' "$c" > "$tmp/$host.cod"; printf '%s\n' "$sor" > "$tmp/$host.sor"; } &
done
wait || true
printf '%-14s %-12s %s\n' NODE ESTAT DETALL
for host in "${FLOTA[@]}"; do
cod=$(< "$tmp/$host.cod"); printf '%-14s %-12s %s\n' "$host" \
"$(estat_text "$cod")" "$(head -1 "$tmp/$host.sor")"
case "$cod" in
0) veloz_log_info "$host: OK" ;;
255) veloz_log_error "$host: inaccessible"; pitjor=2 ;;
*) veloz_log_warn "$host: codi $cod"; (( pitjor < 1 )) && pitjor=1 ;;
esac
done
veloz_log_info "FI, pitjor estat: $pitjor"; return "$pitjor"
}
main "$@"NODE ESTAT DETALL srv-veloz-01 OK veloz-api sana, 14 enviaments a la cua srv-veloz-03 INACCESSIBLE ssh: connect to host srv-veloz-03 port 22: timed out
Repassa les peces: les opcions de SSH agrupades en un array per no repetir-les ni perdre l'entrecometat; ControlMaster per reutilitzar la connexió; timeout 60s a més de ConnectTimeout, perquè el primer cobreix l'ordre remota penjada i el segon només la connexió; branques en paral·lel escrivint a fitxers separats; consolidació en ordre fix; i codis que distingeixen els tres desenllaços. Com que retorna 0/1/2, flota.sh encaixa com una comprovació més de vigilant.sh (07-04) o com un .service amb el seu temporitzador (07-05).
Errors Habituals i Consells
- Oblidar el
-nen un bucle. L'sshs'empassa el fitxer d'entrada i només es processa el primer host, sense cap error visible. - Suposar que el destí llegeix el
.bashrc, o fer servirStrictHostKeyChecking=no. El primer dona «command not found» tot i funcionar en entrar a mà (camins absoluts); el segon desactiva l'única defensa contra la suplantació (fes serviraccept-newossh-keyscan). - Sense
BatchMode=yeso senseConnectTimeout. El primer penja l'script esperant una contrasenya que ningú no escriurà; el segon deixa que un host caigut bloquegi el recorregut durant minuts. - Confondre el 255 amb una fallada de l'aplicació. «El servidor no respon» i «el servei del servidor falla» són incidències diferents.
- Delimitador de here-document sense cometes, o diverses branques paral·leles escrivint a la mateixa sortida. El primer expandeix les variables al lloc equivocat; el segon entrebarreja les línies (un fitxer per branca i consolidar). I
NOPASSWD: ALLconverteix una clau robada en accés total: autoritza ordres concretes amb camí absolut. - Consell: prova sempre
ssh -n host 'ordre'a mà abans de ficar-ho en un bucle, i afegeix-hi-vsi alguna cosa no quadra: la traça d'ssh -vdiu exactament quina clau es va oferir, què va rebutjar el servidor i quin fitxer de configuració es va aplicar.
Exercicis
Exercici 1. Aquest bucle només processa el primer servidor i, quan un està caigut, avorta. Troba els quatre problemes i reescriu-lo.
Exercici 2. Escriu veloz_remot per a lib/comu.sh: executa una ordre en un host remot amb totes les opcions d'automatització, distingeix inaccessible de fallada d'aplicació amb codis diferents, i limita la durada total.
Solucions
Solució 1. Els quatre problemes: (a) falta el -n, així que el primer ssh consumeix el fitxer i el bucle acaba; (b) set -e avorta tan bon punt un host falla, i es perden els altres; (c) sense BatchMode ni ConnectTimeout, un host caigut penja el recorregut o demana contrasenya; (d) sense IFS= read -r i sense filtrar línies buides o comentades, un fitxer d'inventari normal trenca el bucle.
while IFS= read -r host; do
[[ -z $host || $host == \#* ]] && continue
if sortida=$(ssh -n -o BatchMode=yes -o ConnectTimeout=5 "$host" 'uptime; df -h /' 2>&1); then
printf '=== %s ===\n%s\n' "$host" "$sortida"
else
printf '=== %s === ERROR (%s): %s\n' "$host" "$?" "${sortida%%$'\n'*}"
fi
done < ~/veloz-ops/etc/flota.txtL'if al voltant de l'ssh és el que permet conviure amb set -e (05-03): una fallada dins de la condició d'un if no avorta l'script. I la sortida es captura en una variable per etiquetar-la amb el nom del host, en lloc de deixar que tres servidors escriguin entrebarrejats.
Solució 2.
# veloz_remot <host> <ordre...>
# Codis: 0 ok | 69 host inaccessible | 124 s'ha exhaurit el temps | altre = de l'ordre remota
veloz_remot() {
local host="${1:?falta el host}" limit="${VELOZ_SSH_LIMIT:-60}" sortida codi; shift
sortida=$(timeout "${limit}s" ssh -n -o BatchMode=yes -o ConnectTimeout=5 \
-o StrictHostKeyChecking=accept-new -o ControlMaster=auto \
-o ControlPath="$HOME/.ssh/cm-%r@%h:%p" "$host" "$@" 2>&1) && codi=0 || codi=$?
case "$codi" in
0) printf '%s\n' "$sortida"; return 0 ;;
124) veloz_log_error "$host: exhaurits els ${limit}s"; return 124 ;;
255) veloz_log_error "$host: inaccessible (${sortida%%$'\n'*})"; return 69 ;;
*) veloz_log_warn "$host: l ordre ha fallat amb codi $codi"
printf '%s\n' "$sortida" >&2; return "$codi" ;;
esac
}El 255 es tradueix a 69 (EX_UNAVAILABLE), el mateix codi que feia servir veloz_api_get a 06-05 per a «el servei no està disponible»: així qui hi crida tracta igual una API caiguda i un servidor caigut, que operativament és el mateix. La resta de codis es propaguen tal qual, de manera que la lògica d'errors de l'script remot arriba intacta al local. I la sortida correcta va a stdout mentre els diagnòstics van a stderr (02-04), així que dades=$(veloz_remot srv-veloz-02 cat /etc/os-release) captura només el que és útil.
Conclusió
ssh host ordre et dona un shell no interactiu i sense perfil: el mateix problema d'entorn que cron, amb la mateixa solució —camins absoluts i configuració carregada explícitament— i sense res interactiu a l'altre costat. Retorna el codi de l'ordre remota, llevat del 255, que són els errors del mateix ssh i s'han de distingir sempre. L'autenticació és per clau (ssh-keygen -t ed25519, ssh-copy-id, permisos 700/600 estrictes); en automatització va sense frase de pas, i com que el risc no es pot eliminar s'acota: una clau per finalitat i restriccions a authorized_keys amb command=, from=, no-port-forwarding i no-pty. Centralitza el que és repetitiu a ~/.ssh/config amb User, IdentityFile, IdentitiesOnly, ProxyJump i sobretot ControlMaster/ControlPersist, que reutilitza la connexió i fa un bucle deu vegades més ràpid. Les quatre opcions imprescindibles: -n —perquè ssh llegeix l'entrada estàndard i dins d'un while read s'empassa el fitxer, processant un sol host sense donar cap error—, -o BatchMode=yes perquè falli en lloc de preguntar, -o ConnectTimeout perquè un host caigut no bloquegi el recorregut, i StrictHostKeyChecking=accept-new (o ssh-keyscan per endavant) en lloc del perillós no. Per a blocs remots, ssh host bash -s <<'EOF' amb cometes al delimitador i els valors locals passats com a arguments després de --: és el que decideix on s'expandeixen les variables i el que impedeix que una dada alteri el codi. Envia scripts per l'entrada estàndard abans que copiar-los, fes servir rsync -az -e ssh en lloc de scp per a directoris —cosa que a més completa la regla 3-2-1 de 07-03—, recorre la flota capturant el codi de cada host perquè un de caigut no avorti els altres, paral·lelitza amb & i wait escrivint a fitxers separats i consolidant en ordre fix, autoritza sudo remot per ordres concretes —mai NOPASSWD: ALL— i reconeix el límit: quan apareguin plantilles i inventaris, Ansible fa millor aquella feina.
Amb flota.sh es tanca el Mòdul 7 i el toolkit de Veloz Envíos. Ja no espera les teves ordres: informe-diari.sh publica el seu informe cada matí encara que el servidor passés la nit apagat, copia.sh copia, verifica, reté i replica fora del servidor, vigilant.sh comprova vuit coses cada cinc minuts i avisa un sol cop per incidència, i flota.sh consulta els tres servidors en paral·lel. I aquí apareix el problema següent, que és d'una altra naturalesa: això ja són uns quants centenars de línies repartides en cinc scripts i una llibreria, escrites al llarg de set mòduls, que executen tasques privilegiades, manegen claus SSH i toquen dades de clients. Funciona, però és mantenible? És segur? Com saps que un canvi no trenca res? El Mòdul 8 hi respon: codi llegible (08-01), optimització del que de debò importa (08-02), seguretat (08-03), control de versions amb Git (08-04), anàlisi estàtica amb ShellCheck i shfmt (08-05), proves automatitzades amb Bats (08-06) i portabilitat enfront de POSIX (08-07). El toolkit ja funciona sol; toca convertir-lo en programari del qual fiar-se.
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
