Durant tot el mòdul has treballat amb una suposició implícita: que cada nom correspon a un fitxer i cada fitxer té un nom. Aquesta suposició és falsa, i entendre per què és el que separa qui fa servir Linux de qui el comprèn.

En un sistema de fitxers Unix, el nom i el contingut són coses separades. El contingut viu en una estructura anomenada inode; el nom és simplement una entrada en un directori que apunta a aquest inode. Res no impedeix que dues entrades apuntin al mateix lloc, ni que existeixi un fitxer el contingut del qual sigui la ruta d'un altre. D'aquí surten els dos tipus d'enllaços d'aquesta lliçó.

No és teoria de sistemes de fitxers per curiositat. Els enllaços són per tot arreu en un sistema Linux: /bin és un enllaç, el teu python3 és un enllaç, i el sistema amb què Debian i Ubuntu decideixen quina versió d'una eina es fa servir està construït sobre enllaços. I al final de la lliçó dissenyaràs el patró amb què Tramontana Reserves convertirà un desplegament —i una marxa enrere— en un canvi d'enllaç instantani, sense finestra de servei caigut.

Contingut

  1. Què és un inode
  2. Veure l'inode i el comptador d'enllaços
  3. Enllaços durs amb ln
  4. Les dues limitacions dels enllaços durs
  5. Enllaços simbòlics amb ln -s
  6. Absoluts enfront de relatius
  7. Enllaços trencats
  8. Quines ordres segueixen l'enllaç i quines no
  9. Dur enfront de simbòlic: la taula
  10. Enllaços en un sistema Linux real
  11. update-alternatives
  12. El patró de desplegament de Tramontana

  1. Què és un inode

Quan deses un fitxer, el sistema de fitxers crea tres coses diferents:

  1. Els blocs de dades: el contingut en si, repartit pel disc.
  2. L'inode: una estructura amb totes les metadades del fitxer —permisos, propietari, grup, mida, marques de temps, comptador d'enllaços i les adreces dels blocs de dades—. Cada inode té un número únic dins del seu sistema de fitxers.
  3. L'entrada de directori: un parell (nom, número d'inode) emmagatzemat al directori.

Fixa't en el que no conté l'inode: el nom del fitxer. El nom no és una propietat del fitxer; és una propietat del directori que el conté.

flowchart LR
    subgraph DIR["Directori /home/operador/dades"]
        E1["'cases.txt' -> 262149"]
        E2["'reserves.csv' -> 262151"]
    end
    subgraph INO["Taula d'inodes"]
        I1["Inode 262149<br/>permisos, propietari, dates<br/>enllaços: 1<br/>punters a blocs"]
        I2["Inode 262151<br/>permisos, propietari, dates<br/>enllaços: 1<br/>punters a blocs"]
    end
    subgraph BLQ["Blocs de dades"]
        B1["mas-figueres;Mas Figueres..."]
        B2["id;data;casa;hoste..."]
    end
    E1 --> I1 --> B1
    E2 --> I2 --> B2

Un directori, vist així, no conté fitxers: conté una llista de noms amb números d'inode. És un índex.

Aquesta arquitectura explica de cop diverses coses que fins ara eren fets solts:

Fet Explicació per l'inode
mv dins del mateix disc és instantani Només canvia l'entrada de directori; l'inode i els blocs no es toquen
. té el mateix número que el seu directori Són dos noms per al mateix inode (ho vas comprovar a 02-03)
El nom no és a stat Perquè no és a l'inode
Pots esborrar un fitxer que un altre procés està llegint Es treu el nom, però l'inode sobreviu mentre algú el tingui obert
df -i es pot esgotar amb espai lliure Els inodes són un recurs finit, fixat en formatar

Aquest penúltim punt és l'explicació definitiva del misteri de la lliçó 02-03: esborres un registre de 2 GB, du deixa de veure'l, però df continua dient que l'espai està ocupat. El nom ha desaparegut; l'inode continua viu perquè el procés el té obert, i amb ell els seus blocs. Fins que el procés no tanqui el fitxer —o el reiniciïs— no s'allibera res.

  1. Veure l'inode i el comptador d'enllaços

Dues eines que ja coneixes, ara amb la mirada posada als enllaços.

operador@srv-tramontana:~$ ls -li dades/
total 8
262149 -rw-r----- 1 operador operador  418 Aug 17 19:40 cases.txt
262151 -rw-r--r-- 1 operador operador 1204 Aug 18 07:55 reserves.csv

La primera columna amb -i és el número d'inode. La columna que va just després dels permisos —l'1— és el comptador d'enllaços: quants noms apunten a aquest inode.

stat ho diu amb totes les lletres:

operador@srv-tramontana:~$ stat dades/cases.txt
  File: dades/cases.txt
  Size: 418       	Blocks: 8          IO Block: 4096   regular file
Device: 8,2	Inode: 262149      Links: 1
Access: (0640/-rw-r-----)  Uid: ( 1001/operador)   Gid: ( 1001/operador)

Inode: 262149 i Links: 1. Aquest comptador és el que governa quan s'allibera l'espai d'un fitxer, com veuràs a l'apartat següent.

I ara s'entén el comportament dels directoris que va quedar pendent a la lliçó 02-03:

operador@srv-tramontana:~$ ls -ldi /opt/tramontana/app
131074 drwxr-xr-x 4 root root 4096 Aug 18 08:30 /opt/tramontana/app

Comptador 4, no 1. Els quatre noms que apunten a aquest inode són:

  1. L'entrada app dins de /opt/tramontana.
  2. L'entrada . dins de /opt/tramontana/app.
  3. L'entrada .. dins de /opt/tramontana/app/plantilles.
  4. L'entrada .. dins d'un altre subdirectori.

D'aquí la fórmula: enllaços d'un directori = 2 + nombre de subdirectoris. Un directori sense subdirectoris en té 2; un amb set subdirectoris en té 9.

Un número d'inode només té sentit dins del seu sistema de fitxers. Dos fitxers en discos diferents poden tenir el mateix número sense cap relació. Per això, per comparar, cal mirar també el dispositiu:

operador@srv-tramontana:~$ stat -c '%d:%i  %n' dades/cases.txt /boot/vmlinuz
2049:262149  dades/cases.txt
2049:131331  /boot/vmlinuz

El parell dispositiu:inode sí que identifica un fitxer de manera única a tota la màquina.

  1. Enllaços durs amb ln

Un enllaç dur és, senzillament, una altra entrada de directori que apunta al mateix inode. No és una còpia ni un accés directe: és un altre nom igual de legítim per al mateix fitxer.

operador@srv-tramontana:~$ ln dades/cases.txt dades/allotjaments.txt

operador@srv-tramontana:~$ ls -li dades/
total 12
262149 -rw-r----- 2 operador operador  418 Aug 17 19:40 allotjaments.txt
262149 -rw-r----- 2 operador operador  418 Aug 17 19:40 cases.txt
262151 -rw-r--r-- 1 operador operador 1204 Aug 18 07:55 reserves.csv

Tres coses per confirmar en aquesta sortida:

  1. Mateix inode (262149) a les dues primeres línies.
  2. El comptador ha pujat a 2 a totes dues: hi ha dos noms apuntant-hi.
  3. La mida es mostra dues vegades (418 i 418), però el fitxer ocupa 418 bytes en total, no 836. ls mostra la mida de l'inode, i l'inode és el mateix.

Aquest últim punt té una conseqüència pràctica: du compta l'espai una sola vegada si troba els dos noms al mateix recorregut, però el pot comptar dues vegades si són en recorreguts diferents. És una font clàssica de descaires en calcular la mida de còpies de seguretat amb enllaços durs.

No hi ha original ni còpia. Els dos noms són exactament equivalents; el sistema no guarda quin es va crear primer. Modificar-ne un modifica l'altre, perquè són el mateix fitxer:

operador@srv-tramontana:~$ echo "el-moli;El Molí;Ripollès;5" >> dades/allotjaments.txt
operador@srv-tramontana:~$ tail -n 1 dades/cases.txt
el-moli;El Molí;Ripollès;5

I el mateix amb les metadades: chmod sobre un canvia els permisos de l'altre, perquè els permisos són a l'inode.

Què passa en esborrar

Aquí hi ha el comportament que cal entendre bé:

operador@srv-tramontana:~$ rm dades/cases.txt

operador@srv-tramontana:~$ ls -li dades/
262149 -rw-r----- 1 operador operador  446 Aug 18 13:02 allotjaments.txt
262151 -rw-r--r-- 1 operador operador 1204 Aug 18 07:55 reserves.csv

operador@srv-tramontana:~$ cat dades/allotjaments.txt
mas-figueres;Mas Figueres;Girona;6
...
el-moli;El Molí;Ripollès;5

El contingut continua intacte. rm no esborra fitxers: decrementa el comptador d'enllaços i elimina el nom. L'inode i els seus blocs només s'alliberen quan passen dues coses alhora:

  • El comptador d'enllaços arriba a 0.
  • Cap procés no té el fitxer obert.

Per això l'ordre de baix nivell es diu unlink(), no delete(). rm és literalment «desenllaçar».

Els tres desenllaços possibles d'un rm:

Situació després de decrementar el comptador Què passa amb les dades
Comptador > 0 Continuen accessibles pels altres noms. No s'allibera res
Comptador = 0 i ningú no el té obert S'alliberen l'inode i els blocs. El fitxer desapareix
Comptador = 0 però algun procés el té obert Continuen al disc, sense nom però vius: du no els veu, df sí que els compta

La tercera fila és la del misteri de l'espai que no s'allibera. I també és un mecanisme que es fa servir expressament: un programa que necessita un fitxer temporal el pot crear, obrir i esborrar immediatament. El fitxer continua funcionant mentre el programa el tingui obert, i desapareix automàticament quan el programa acaba, fins i tot si es tanca de manera anormal. No cal netejar res.

  1. Les dues limitacions dels enllaços durs

No poden creuar sistemes de fitxers

operador@srv-tramontana:~$ ln dades/allotjaments.txt /boot/prova.txt
ln: failed to create hard link '/boot/prova.txt' => 'dades/allotjaments.txt':
 Invalid cross-device link

El perquè: un enllaç dur és una entrada (nom, número d'inode). El número d'inode només té significat dins del seu sistema de fitxers: l'inode 262149 de /dev/sda2 no té res a veure amb l'inode 262149 de /dev/sda1. Una entrada en un directori de /boot que digués «262149» apuntaria a l'inode 262149 de /boot, que és un fitxer completament diferent.

No és una restricció arbitrària: és que la referència no significa res fora del seu àmbit.

Com saber si dues rutes són al mateix sistema de fitxers, abans d'intentar-ho:

operador@srv-tramontana:~$ df --output=source /home/operador /opt/tramontana /boot
Filesystem
/dev/sda2
/dev/sda2
/dev/sda1

/home i /opt comparteixen /dev/sda2: s'hi pot enllaçar entre ells. /boot és a /dev/sda1: no.

No es poden fer a directoris

operador@srv-tramontana:~$ ln /opt/tramontana/app /tmp/enllac-app
ln: /opt/tramontana/app: hard link not allowed for directory

Ni tan sols root pot fer-ho a Linux. El perquè és més interessant:

L'arbre de directoris d'Unix és, per disseny, un graf acíclic: es pot baixar i pujar, però no fer voltes en cercle. Si poguessis crear un enllaç dur a un directori, podries construir un bucle:

/a/b/c/  ->  enllaç dur a  /a

Conseqüències immediates:

  • find, du, tar, rsync i qualsevol recorregut recursiu entrarien en un bucle infinit, perquè no hi hauria manera de detectar que ja hi han passat sense portar registre de cada inode visitat.
  • El comptador d'enllaços deixaria de servir per saber quan alliberar l'espai: un cicle de directoris que es referencien mútuament tindria comptadors majors que zero encara que ningú no hi pogués arribar des de l'arrel. Serien escombraries inabastables i impossibles de recollir.
  • .. deixaria de tenir sentit únic: un directori amb dos pares no pot tenir un sol ...

Les entrades . i .. són l'excepció: són enllaços durs a directoris, creats pel nucli, i per això els comptadors dels directoris valen més d'1. Però estan controlats i la seva topologia és coneguda, així que no trenquen res.

Totes dues limitacions desapareixen amb l'altre tipus d'enllaç.

  1. Enllaços simbòlics amb ln -s

Un enllaç simbòlic (o symlink, o enllaç tou) és un fitxer de ple dret, amb el seu propi inode, el contingut del qual és una ruta de text. Quan alguna cosa l'obre, el nucli llegeix aquesta ruta i redirigeix l'operació.

operador@srv-tramontana:~$ ln -s /home/operador/dades/reserves.csv ~/reserves-actual.csv

operador@srv-tramontana:~$ ls -li ~/reserves-actual.csv
262180 lrwxrwxrwx 1 operador operador 36 Aug 18 13:20 reserves-actual.csv -> /home/operador/dades/reserves.csv

Tot el que cal llegir-hi:

Element Què significa
262180 Inode propi i diferent del destí (262151)
l inicial Tipus enllaç simbòlic, la l que vas veure al Mòdul 1
rwxrwxrwx Els permisos de l'enllaç són sempre aquests i no signifiquen res: els que manen són els del destí
1 Comptador d'enllaços del mateix enllaç
36 La mida és la longitud de la ruta, 36 caràcters
-> /home/... La ruta que conté

Aquesta mida confirma què és un symlink: un fitxer el contingut del qual és una cadena de text.

En fer-lo servir, tot funciona com si fos el destí:

operador@srv-tramontana:~$ head -n 2 ~/reserves-actual.csv
id;data;casa;hoste;nits;import
1001;2026-07-03;mas-figueres;Nuria Prat;4;620.00

I les dues limitacions de l'enllaç dur no existeixen:

operador@srv-tramontana:~$ ln -s /boot/vmlinuz ~/nucli-actual
operador@srv-tramontana:~$ ls -l ~/nucli-actual
lrwxrwxrwx 1 operador operador 13 Aug 18 13:24 nucli-actual -> /boot/vmlinuz

operador@srv-tramontana:~$ ln -s /opt/tramontana/app ~/app-produccio
operador@srv-tramontana:~$ ls -ld ~/app-produccio
lrwxrwxrwx 1 operador operador 19 Aug 18 13:25 app-produccio -> /opt/tramontana/app

Creua sistemes de fitxers i apunta a directoris sense problema, perquè no referencia un inode: referencia una ruta, i una ruta és text que es resol en el moment de fer-la servir.

Un enllaç a un directori es comporta com el directori:

operador@srv-tramontana:~$ ls ~/app-produccio/
executable  plantilles  version.txt

operador@srv-tramontana:~$ cd ~/app-produccio
operador@srv-tramontana:~/app-produccio$ pwd
/home/operador/app-produccio
operador@srv-tramontana:~/app-produccio$ pwd -P
/opt/tramontana/app

Aquí tens l'explicació de pwd -P que va quedar pendent a la lliçó 02-03: pwd mostra la ruta lògica, la que vas fer servir per arribar-hi; pwd -P la física, resolent els enllaços.

Opcions de ln que convé conèixer:

Opció Què fa
-s Crea un enllaç simbòlic (sense ella, dur)
-f Força: reemplaça l'enllaç si ja existeix
-n Tracta un enllaç a directori existent com a fitxer, no hi entra
-r Crea l'enllaç amb una ruta relativa calculada automàticament
-v Informa del que fa
-T Tracta el destí sempre com un nom, mai com un directori

La combinació -sfn és la del desplegament i la veuràs en acció a l'apartat 12. Sense -n, reemplaçar un enllaç que apunta a un directori crea l'enllaç nou dins d'aquest directori en comptes de substituir-lo, que és un error molt fàcil de cometre.

  1. Absoluts enfront de relatius

El contingut d'un enllaç simbòlic és una ruta, i aquesta ruta pot ser dels dos tipus.

operador@srv-tramontana:~$ cd /opt/tramontana

# Absolut
operador@srv-tramontana:/opt/tramontana$ sudo ln -s /opt/tramontana/releases/3.2.1 app-abs

# Relatiu
operador@srv-tramontana:/opt/tramontana$ sudo ln -s releases/3.2.1 app-rel

operador@srv-tramontana:/opt/tramontana$ ls -l app-*
lrwxrwxrwx 1 root root 30 Aug 18 13:40 app-abs -> /opt/tramontana/releases/3.2.1
lrwxrwxrwx 1 root root 16 Aug 18 13:40 app-rel -> releases/3.2.1

Tots dos funcionen igual ara mateix. Es diferencien en què passa quan alguna cosa es mou.

Un enllaç relatiu es resol des del directori on és l'enllaç, no des d'on siguis tu:

operador@srv-tramontana:~$ ls /opt/tramontana/app-rel/
executable  plantilles  version.txt

Funciona des del teu home perquè releases/3.2.1 es resol relatiu a /opt/tramontana/, que és on viu l'enllaç.

Ara, la prova que els distingeix: copiar tot l'arbre a un altre lloc.

operador@srv-tramontana:~$ sudo cp -a /opt/tramontana /srv/tramontana/copia-completa

operador@srv-tramontana:~$ ls -l /srv/tramontana/copia-completa/app-*
lrwxrwxrwx 1 root root 30 Aug 18 13:40 app-abs -> /opt/tramontana/releases/3.2.1
lrwxrwxrwx 1 root root 16 Aug 18 13:40 app-rel -> releases/3.2.1
  • app-abs continua apuntant a /opt/tramontana/..., és a dir, a l'arbre original. La còpia no és autocontinguda: depèn de l'original. Si esborres /opt/tramontana, la còpia queda trencada.
  • app-rel apunta a releases/3.2.1 dins de la còpia. L'arbre copiat és autònom i coherent.
Criteri Absolut Relatiu
Sobreviu a moure l'enllaç No Sí, si es mou tot el conjunt
Sobreviu a moure el destí No No
Copiar l'arbre complet Es trenca la independència Continua coherent
Funciona dins d'un chroot o contenidor No (l'arrel canvia) Sí
Llegibilitat en fer ls -l Molt clara Cal calcular
Robust davant d'un muntatge en un altre punt No Sí

Criteri d'ús:

  • Relatius dins d'un mateix arbre d'aplicació (/opt/tramontana/app → releases/...), perquè el conjunt es copia, se'n fa còpia de seguretat i es restaura com una unitat.
  • Absoluts quan el destí és en una altra part del sistema i no té relació estructural amb l'enllaç (per exemple, un enllaç al teu home cap a /var/log/tramontana).

ln -r calcula el relatiu per tu:

operador@srv-tramontana:~$ cd /opt/tramontana
operador@srv-tramontana:/opt/tramontana$ sudo ln -sr /opt/tramontana/releases/3.2.1 app-auto
operador@srv-tramontana:/opt/tramontana$ ls -l app-auto
lrwxrwxrwx 1 root root 16 Aug 18 13:47 app-auto -> releases/3.2.1

Li dones rutes absolutes, que són fàcils d'escriure amb Tab sense equivocar-se, i ell genera l'enllaç relatiu correcte. És la millor de les dues opcions.

  1. Enllaços trencats

Com que un symlink guarda una ruta i no una referència a l'inode, res no garanteix que el destí existeixi. Pots crear un enllaç a alguna cosa que encara no existeix, i el destí pot desaparèixer després.

operador@srv-tramontana:~$ ln -s /opt/tramontana/releases/9.9.9 ~/futur
operador@srv-tramontana:~$ ls -l ~/futur
lrwxrwxrwx 1 operador operador 33 Aug 18 13:52 futur -> /opt/tramontana/releases/9.9.9

operador@srv-tramontana:~$ cat ~/futur
cat: /home/operador/futur: No such file or directory

ls -l mostra l'enllaç sense queixar-se, perquè l'enllaç existeix perfectament: és el destí el que no. Només en intentar fer-lo servir apareix l'error, i el missatge esmenta el nom de l'enllaç, cosa que confon: sembla que no existeix ~/futur quan ~/futur és allà.

Amb color activat, ls mostra els enllaços trencats en vermell parpellejant, que és difícil de passar per alt. Per detectar-los de manera fiable:

# Un enllaç concret
operador@srv-tramontana:~$ ls -lL ~/futur
ls: cannot access '/home/operador/futur': No such file or directory

# Tots els enllaços trencats d'un arbre
operador@srv-tramontana:~$ find ~ -xtype l
/home/operador/futur

# Amb més context
operador@srv-tramontana:~$ find ~ -xtype l -exec ls -l {} \;
lrwxrwxrwx 1 operador operador 33 Aug 18 13:52 /home/operador/futur -> /opt/tramontana/releases/9.9.9

find -xtype l significa «elements que, en resoldre l'enllaç, són de tipus enllaç», cosa que només passa si l'enllaç no es pot resoldre. find s'estudia a fons a la lliçó 03-03; aquesta invocació ja la pots adoptar com a recepta.

I readlink és l'eina específica per inspeccionar enllaços:

operador@srv-tramontana:~$ readlink ~/futur
/opt/tramontana/releases/9.9.9

operador@srv-tramontana:~$ readlink -f ~/reserves-actual.csv
/home/operador/dades/reserves.csv

operador@srv-tramontana:~$ readlink -e ~/futur
operador@srv-tramontana:~$ echo $?
1
Opció Què fa
(cap) Mostra el contingut literal de l'enllaç
-f Resol tota la cadena d'enllaços; no falla si l'últim no existeix
-e Com -f, però falla si el destí no existeix

readlink -e és el verificador: codi 0 si l'enllaç porta a alguna cosa real, diferent de 0 si està trencat. És exactament el que faràs servir a l'script de desplegament per comprovar que l'enllaç app apunta a un release existent abans d'arrencar el servei.

Un enllaç trencat no sempre és un error. És una situació normal quan el destí es crearà més tard, o quan apunta a un dispositiu extraïble que ara no està muntat. El que no hi ha d'haver són enllaços trencats que ningú no sap per què hi són.

  1. Quines ordres segueixen l'enllaç i quines no

Aquesta és la part que provoca més errors a la pràctica: cada ordre decideix pel seu compte si actua sobre l'enllaç o sobre el seu destí.

Ordre Comportament per defecte Com canviar-lo
cat, less, editors Segueixen l'enllaç: llegeixen i escriuen el destí —
ls Mostra l'enllaç -L per mostrar el destí
ls -l d'un directori enllaçat Llista el contingut del destí -d per veure l'enllaç
cp Segueix: copia el contingut del destí -P o -d copia l'enllaç com a tal
cp -a No segueix: copia els enllaços com a enllaços -L per seguir-los
mv Mou l'enllaç, no el destí —
rm Esborra l'enllaç, mai el destí —
chmod, chown Segueixen: canvien el destí chown -h canvia l'enllaç
du Compta l'enllaç (uns bytes) -L per comptar el destí
find No segueix -L per seguir
tar Guarda els enllaços com a enllaços -h per guardar el contingut
rsync -a Copia els enllaços com a enllaços -L per copiar el contingut

Els tres casos que cal tenir clars:

rm sobre un enllaç esborra l'enllaç. Sempre. És segur:

operador@srv-tramontana:~$ rm ~/reserves-actual.csv
operador@srv-tramontana:~$ ls dades/reserves.csv
dades/reserves.csv

El destí continua intacte. Aquest és el comportament que la gent tem i que en realitat mai no falla.

cp sense -a desfà els enllaços:

operador@srv-tramontana:~$ ln -s /opt/tramontana/app/version.txt ~/v.txt

operador@srv-tramontana:~$ cp ~/v.txt /tmp/v-copia.txt
operador@srv-tramontana:~$ ls -l /tmp/v-copia.txt
-rw-rw-r-- 1 operador operador 26 Aug 18 14:02 /tmp/v-copia.txt

operador@srv-tramontana:~$ cp -P ~/v.txt /tmp/v-enllac.txt
operador@srv-tramontana:~$ ls -l /tmp/v-enllac.txt
lrwxrwxrwx 1 operador operador 36 Aug 18 14:02 /tmp/v-enllac.txt -> /opt/tramontana/app/version.txt

cp normal va produir un fitxer real amb el contingut; cp -P va produir un enllaç. En copiar un arbre amb enllaços, aquesta diferència decideix si obtens una còpia fidel o una còpia inflada on cada enllaç s'ha convertit en una còpia completa del fitxer.

El parany de la barra final amb enllaços a directoris. Aquest mossega tothom:

operador@srv-tramontana:~$ ln -s /opt/tramontana/app ~/app-enllac

# SENSE barra: el mateix enllaç
operador@srv-tramontana:~$ ls -ld ~/app-enllac
lrwxrwxrwx 1 operador operador 19 Aug 18 14:05 /home/operador/app-enllac -> /opt/tramontana/app

# AMB barra: el DIRECTORI al qual apunta
operador@srv-tramontana:~$ ls -ld ~/app-enllac/
drwxr-xr-x 4 root root 4096 Aug 18 08:30 /home/operador/app-enllac/

La barra final significa «tracta'l com a directori», i això obliga el nucli a resoldre l'enllaç. I ara el perillós:

# Esborra NOMÉS l'enllaç. El destí intacte.
operador@srv-tramontana:~$ rm ~/app-enllac

# Amb barra final: error, perquè rm no esborra directoris sense -r
operador@srv-tramontana:~$ rm ~/app-enllac/
rm: cannot remove '/home/operador/app-enllac/': Is a directory

# AIXÒ SÍ QUE ESBORRA EL CONTINGUT DEL DESTÍ REAL
operador@srv-tramontana:~$ rm -rf ~/app-enllac/

Aquesta última línia, amb -rf i barra final, travessa l'enllaç i esborra el contingut de /opt/tramontana/app. L'aplicació de producció, eliminada per una barra de més.

Regla d'or: no posis mai barra final després del nom d'un enllaç en una ordre destructiva. I en general, abans d'un rm -rf sobre alguna cosa que podria ser un enllaç, comprova:

operador@srv-tramontana:~$ ls -ld ~/app-enllac
operador@srv-tramontana:~$ readlink -f ~/app-enllac
/opt/tramontana/app

  1. Dur enfront de simbòlic: la taula

Característica Enllaç dur Enllaç simbòlic
Què és Una altra entrada de directori al mateix inode Un fitxer el contingut del qual és una ruta
Inode El mateix que el destí Propi i diferent
Es crea amb ln origen enllac ln -s origen enllac
A ls -l Indistingible d'un fitxer normal Comença per l, amb ->
Mida La del fitxer La longitud de la ruta
Comptador d'enllaços del destí Augmenta No canvia
Creua sistemes de fitxers No Sí
Apunta a directoris No Sí
Si s'esborra el destí Les dades continuen accessibles L'enllaç queda trencat
Si es mou el destí Continua funcionant Es trenca (tret dels relatius coherents)
Permisos Els de l'inode compartit lrwxrwxrwx, irrellevants
Detectar el destí No es pot saber quin era «l'original» readlink
Pot apuntar a alguna cosa inexistent No Sí
Cost al disc Zero (només l'entrada) Un inode i uns bytes
Ús típic Còpies amb rsync --link-dest, deduplicació Tota la resta

Quan triar cadascun, a la pràctica:

Fes servir enllaços simbòlics per defecte. Són visibles a ls -l, diuen on apunten, creuen sistemes de fitxers, funcionen amb directoris i es poden reemplaçar de manera atòmica. El 95 % dels enllaços que crearàs i que trobaràs en un sistema Linux són simbòlics.

Fes servir enllaços durs en dos casos concrets:

  1. Còpies de seguretat incrementals amb deduplicació. rsync --link-dest crea una còpia completa aparent de cada dia, però els fitxers que no han canviat són enllaços durs als del dia anterior: ocupen zero espai addicional. És la base de les còpies tipus Time Machine i ho veuràs a la lliçó 05-08.
  2. Quan necessites que el fitxer sobrevisqui a l'esborrat del nom original, sense que quedi un enllaç trencat.

  1. Enllaços en un sistema Linux real

Els enllaços no són una curiositat: són estructura del sistema.

La mateixa arrel:

operador@srv-tramontana:~$ ls -l / | grep '^l'
lrwxrwxrwx   1 root root    7 Apr 22  2024 bin -> usr/bin
lrwxrwxrwx   1 root root    7 Apr 22  2024 lib -> usr/lib
lrwxrwxrwx   1 root root    9 Apr 22  2024 lib64 -> usr/lib64
lrwxrwxrwx   1 root root    8 Apr 22  2024 sbin -> usr/sbin

És la reorganització coneguda com a /usr-merge: els binaris es van consolidar sota /usr i es van deixar enllaços a l'arrel perquè els milers de referències existents a /bin/bash continuessin funcionant. Això és el que veies amb la fletxa a tree -L 1 / a la lliçó 02-03.

Fixa't que són relatius (usr/bin, no /usr/bin). Deliberadament: així continuen sent vàlids si l'arbre es munta en un altre punt, per exemple en reparar el sistema des d'un disc de rescat muntat a /mnt.

L'intèrpret de Python:

operador@srv-tramontana:~$ ls -l /usr/bin/python3
lrwxrwxrwx 1 root root 10 Aug  2 12:04 /usr/bin/python3 -> python3.12

python3 és un enllaç a la versió concreta. Actualitzar Python a la 3.13 és, en essència, canviar aquest enllaç: tots els scripts que comencen per #!/usr/bin/python3 passen a fer servir la versió nova sense tocar-ne ni un.

Cadenes d'enllaços:

operador@srv-tramontana:~$ ls -l /usr/bin/vi
lrwxrwxrwx 1 root root 20 Aug 18 09:00 /usr/bin/vi -> /etc/alternatives/vi
operador@srv-tramontana:~$ ls -l /etc/alternatives/vi
lrwxrwxrwx 1 root root 17 Aug 18 09:00 /etc/alternatives/vi -> /usr/bin/vim.basic
operador@srv-tramontana:~$ readlink -f /usr/bin/vi
/usr/bin/vim.basic

Dos salts. readlink -f segueix la cadena sencera fins al final. El nucli permet fins a uns 40 nivells d'imbricació abans de donar Too many levels of symbolic links.

Els serveis de systemd:

operador@srv-tramontana:~$ ls -l /etc/systemd/system/multi-user.target.wants/ | head -n 4
lrwxrwxrwx 1 root root 44 Jul  1 10:22 ssh.service -> /lib/systemd/system/ssh.service
lrwxrwxrwx 1 root root 48 Jul  1 10:22 cron.service -> /lib/systemd/system/cron.service

Habilitar un servei a systemd és literalment crear un enllaç simbòlic, i deshabilitar-lo és esborrar-lo. Quan executis systemctl enable a la lliçó 05-05, el que passarà per sota és un ln -s.

Els registres rotats:

operador@srv-tramontana:~$ ls -l /var/log/tramontana/
-rw-r----- 1 root adm  18K Aug 18 09:14 acces.log
-rw-r----- 1 root adm 2.1K Aug 17 23:59 acces.log.1.gz

Aquí no hi ha enllaços, però convé saber que alguns serveis mantenen current.log com a enllaç al fitxer del dia.

  1. update-alternatives

Debian i Ubuntu resolen amb enllaços un problema concret: diversos paquets que proporcionen la mateixa funció. Quin editor obre sudo editor? Quin java s'executa si n'hi ha tres d'instal·lades? Què és vi si hi ha vim, nvi i elvis?

El mecanisme és una doble capa d'enllaços simbòlics:

/usr/bin/editor  ->  /etc/alternatives/editor  ->  /bin/nano

La primera capa és fixa i és al PATH. La segona, a /etc/alternatives/, és la que es canvia. Així l'administrador no toca mai /usr/bin, que pertany als paquets.

operador@srv-tramontana:~$ update-alternatives --display editor
editor - auto mode
  link best version is /bin/nano
  link currently points to /bin/nano
  link editor is /usr/bin/editor
/bin/nano - priority 40
/usr/bin/vim.basic - priority 30

Canviar-ho:

operador@srv-tramontana:~$ sudo update-alternatives --config editor
There are 2 choices for the alternative editor (providing /usr/bin/editor).

  Selection    Path                Priority   Status
------------------------------------------------------------
* 0            /bin/nano            40        auto mode
  1            /bin/nano            40        manual mode
  2            /usr/bin/vim.basic   30        manual mode

Press <enter> to keep the current choice[*], or type selection number: 2
update-alternatives: using /usr/bin/vim.basic to provide /usr/bin/editor (editor) in manual mode
operador@srv-tramontana:~$ ls -l /etc/alternatives/editor
lrwxrwxrwx 1 root root 18 Aug 18 14:20 /etc/alternatives/editor -> /usr/bin/vim.basic

L'enllaç de la segona capa ha canviat. Ordres principals:

Ordre Què fa
--display <nom> Mostra l'estat i les opcions
--config <nom> Triar interactivament
--list <nom> Només les rutes disponibles
--set <nom> <ruta> Fixar sense interacció (per a scripts)
--auto <nom> Tornar al mode automàtic per prioritat

Per què això importa per a l'EDITOR de la teva sessió: quan executis sudo visudo o crontab -e, l'editor que s'obre el decideix la variable d'entorn EDITOR i, si no està definida, l'alternativa editor. Si no has configurat mai res i t'apareix nano, ara saps per què i com canviar-ho. La variable EDITOR és de la lliçó 03-01.

  1. El patró de desplegament de Tramontana

Aquí és on tot l'anterior es converteix en una decisió d'arquitectura. És el patró que Tramontana Reserves adoptarà i que reapareixerà a la lliçó 04-07 i al Mòdul 8.

El problema

Avui, el desplegament d'una versió nova significaria: aturar el servei, esborrar o moure /opt/tramontana/app, copiar la versió nova, arrencar. Problemes:

  • Finestra d'indisponibilitat proporcional al temps de còpia. Copiar 48 MB triga segons; en producció, copiar una release gran pot trigar minuts.
  • Estat intermedi incoherent: durant la còpia, el directori té mitja versió vella i mitja de nova. Si el servei arrenca en aquell moment, es comporta de manera impredictible.
  • Tornar enrere és un altre desplegament complet, amb la seva pròpia finestra i els seus propis riscos, executat sota pressió perquè alguna cosa ha fallat.
  • La versió anterior ja no hi és, així que tornar enrere requereix recuperar-la d'una còpia.

La solució

Separar el contingut (els releases, cadascun al seu directori immutable) de el punter (un enllaç simbòlic que diu quin està actiu).

/opt/tramontana/
├── releases/
│   ├── 3.1.0/          <- versió anterior, intacta
│   ├── 3.2.0/          <- versió anterior, intacta
│   └── 3.2.1/          <- versió nova
├── app -> releases/3.2.1     <- EL PUNTER
└── shared/
    └── uploads/        <- dades que sobreviuen als desplegaments
flowchart TD
    S["Servei Tramontana<br/>llegeix /opt/tramontana/app"] --> L["app<br/>(enllaç simbòlic)"]
    L -.->|"apunta a"| R321["releases/3.2.1<br/>ACTIVA"]
    R320["releases/3.2.0<br/>en reserva"]
    R310["releases/3.1.0<br/>en reserva"]
    L -.->|"un ln -sfn<br/>i apunta aquí"| R320

El desplegament complet:

# 1. Pujar la versió nova al seu propi directori. El servei continua
#    corrent sobre 3.2.1 i no se n'assabenta de res.
operador@srv-tramontana:~$ sudo mkdir -p /opt/tramontana/releases/3.3.0
operador@srv-tramontana:~$ sudo tar -xzf /srv/tramontana/enviaments/app-3.3.0.tar.gz \
    -C /opt/tramontana/releases/3.3.0 --strip-components=1

# 2. Verificar la versió nova ABANS d'activar-la
operador@srv-tramontana:~$ cat /opt/tramontana/releases/3.3.0/version.txt
Tramontana Reserves 3.3.0
operador@srv-tramontana:~$ ls -l /opt/tramontana/releases/3.3.0/executable
-rwxr-xr-x 1 root root 51203584 Aug 18 14:40 executable

# 3. Deixar constància de què hi havia abans
operador@srv-tramontana:~$ readlink /opt/tramontana/app
releases/3.2.1

# 4. EL DESPLEGAMENT: una sola ordre
operador@srv-tramontana:~$ cd /opt/tramontana
operador@srv-tramontana:/opt/tramontana$ sudo ln -sfn releases/3.3.0 app

# 5. Verificar
operador@srv-tramontana:/opt/tramontana$ ls -l app
lrwxrwxrwx 1 root root 16 Aug 18 14:41 app -> releases/3.3.0
operador@srv-tramontana:/opt/tramontana$ cat app/version.txt
Tramontana Reserves 3.3.0

# 6. Recarregar el servei
operador@srv-tramontana:/opt/tramontana$ sudo systemctl restart tramontana

La marxa enrere:

operador@srv-tramontana:/opt/tramontana$ sudo ln -sfn releases/3.2.1 app
operador@srv-tramontana:/opt/tramontana$ sudo systemctl restart tramontana

Una ordre. Aquest és tot el rollback. Sense copiar res, sense recuperar d'una còpia de seguretat, sense finestra de manteniment. I es pot executar a les tres de la matinada per algú nerviós sense risc d'equivocar-se.

Per què ln -sfn i no una altra cosa

Les tres lletres importen:

  • -s simbòlic, òbviament.
  • -f força el reemplaçament. Sense ella, ln falla perquè app ja existeix.
  • -n és la crítica. Sense -n, ln veu que app és un enllaç a un directori, hi entra i crea l'enllaç allà dins:
# SENSE -n: el desastre silenciós
operador@srv-tramontana:/opt/tramontana$ sudo ln -sf releases/3.3.0 app
operador@srv-tramontana:/opt/tramontana$ ls -l app/
lrwxrwxrwx 1 root root 15 Aug 18 14:45 3.3.0 -> releases/3.3.0
executable  plantilles  version.txt

Ha creat releases/3.2.1/3.3.0 en comptes de canviar app. L'enllaç app continua apuntant a la versió vella, el servei no s'actualitza i a sobre has embrutat el directori del release anterior, que havia de ser immutable. -n sempre.

Per què és «atòmic»

Reemplaçar un enllaç simbòlic es fa, per sota, amb la crida al sistema rename(), que el nucli garanteix com a atòmica: no hi ha cap instant en què app no existeixi o apunti a mitges. Qualsevol procés que obri /opt/tramontana/app en qualsevol moment veurà o la versió vella completa o la nova completa, mai un estat intermedi.

Compara-ho amb l'alternativa ingènua de rm app && ln -s releases/3.3.0 app: entre les dues ordres hi ha una finestra, breu però real, en què app no existeix. Un procés que intenti obrir un fitxer just llavors rebrà un error. Per això el patró és ln -sfn, en una sola ordre.

Avantatges del patró, resumits

Aspecte Desplegament clàssic Patró amb enllaç
Temps d'indisponibilitat El que trigui la còpia El reinici del servei
Estat intermedi Existeix i és incoherent No existeix
Tornar enrere Un altre desplegament complet Una ordre
Versió anterior Sobreescrita Intacta al disc
Verificar abans d'activar No es pot Sí, amb calma
Espai al disc Una còpia N còpies (cal rotar)

La contrapartida és a l'última fila: cal eliminar els releases antics o /opt creixerà sense límit. La política habitual és conservar els tres o cinc últims, i l'script que ho fa l'escriuràs al Mòdul 4.

Un altre detall de l'esquema: el directori shared/. Les dades que han de sobreviure als desplegaments —fitxers pujats pels usuaris, memòries cau persistents— no poden viure dins del release, perquè el release següent no les tindria. Es posen a shared/ i cada release les enllaça:

operador@srv-tramontana:~$ sudo ln -s ../../shared/uploads /opt/tramontana/releases/3.3.0/uploads

Aquest patró, que aquí muntes a mà, és exactament el que implementen les eines de desplegament professionals (Capistrano el va popularitzar, i les estructures de desplegament de molts PaaS el repliquen). L'automatitzaràs amb un script a la lliçó 04-07 i el posaràs en producció al Mòdul 8.

Errors Comuns i Consells

Creure que un enllaç dur és una còpia. En modifiques un i canvies tots dos, perquè són el mateix fitxer.

Creure que esborrar un enllaç dur esborra les dades. Només si era l'últim nom.

Fer ln -sf sense -n sobre un enllaç a directori. Crea l'enllaç a dins en comptes de substituir-lo, silenciosament.

Posar barra final després d'un enllaç en un rm -rf. Travessa l'enllaç i esborra el destí real. L'error més car d'aquesta lliçó.

Copiar arbres amb enllaços fent servir cp -r. Converteix els enllaços en còpies reals i infla el resultat. Fes servir cp -a o rsync -a.

Fer servir enllaços absoluts dins d'un arbre que es copiarà o mourà. La còpia acaba depenent de l'original. ln -sr.

Refiar-se de ls -l per saber si un enllaç funciona. Mostra els trencats igual que els bons. readlink -e o find -xtype l.

Consell: readlink -f abans de qualsevol operació destructiva sobre una ruta que pugui contenir enllaços. Et diu on apunta de debò.

Consell: posa els enllaços relatius dins d'un mateix arbre i absoluts entre arbres diferents. És la regla que evita el 90 % dels enllaços trencats.

Consell: find /ruta -xtype l de tant en tant. Els enllaços trencats són un símptoma: alguna cosa s'ha mogut o s'ha esborrat i ningú no ha actualitzat el que hi apuntava.

Consell: documenta els enllaços estructurals. Un README a /opt/tramontana explicant el patró de releases evita que el pròxim administrador esborri releases/ pensant que sobra.

Exercicis

Exercici 1: comprovar el comportament dels inodes

A /tmp, sense mirar la teoria:

  1. Crea original.txt amb tres línies. Anota'n l'inode i el comptador d'enllaços.
  2. Crea un enllaç dur dur.txt i un enllaç simbòlic tou.txt. Mostra els tres amb ls -li i explica cada columna.
  3. Afegeix una línia a través de dur.txt. Què veuen original.txt i tou.txt?
  4. Esborra original.txt. Què passa amb cadascun dels altres dos? Explica per què en termes d'inodes.
  5. Torna a crear un original.txt nou amb contingut diferent. A què apunta ara tou.txt? I dur.txt?

Exercici 2: muntar el patró de desplegament

Munta a srv-tramontana l'estructura de releases completa:

  1. Crea /opt/tramontana/releases/3.2.1 amb el contingut actual de /opt/tramontana/app.
  2. Substitueix /opt/tramontana/app per un enllaç relatiu a aquest release, sense que el contingut deixi d'estar accessible en cap moment verificable.
  3. Simula el desplegament d'una versió 3.3.0 i activa-la.
  4. Simula que falla i torna a 3.2.1.
  5. Escriu una comprovació d'una línia que verifiqui que l'enllaç apunta a un release que existeix realment.

Exercici 3: l'enllaç d'en Luis

En Luis ha muntat al seu portàtil un directori de treball compartit i et passa això perquè ho repliquis al servidor:

cd /opt/tramontana
sudo rm app
sudo ln -s /home/luis/projectes/tramontana/build app

La Marta et pregunta si ho pot aprovar. Analitza la proposta, enumera els problemes i proposa l'alternativa correcta amb la seva justificació.

Solucions

Solució 1

operador@srv-tramontana:~$ cd /tmp
operador@srv-tramontana:/tmp$ printf 'línia 1\nlínia 2\nlínia 3\n' > original.txt
operador@srv-tramontana:/tmp$ ls -li original.txt
393221 -rw-rw-r-- 1 operador operador 27 Aug 18 15:02 original.txt

Inode 393221, comptador 1: un sol nom hi apunta.

operador@srv-tramontana:/tmp$ ln original.txt dur.txt
operador@srv-tramontana:/tmp$ ln -s original.txt tou.txt
operador@srv-tramontana:/tmp$ ls -li original.txt dur.txt tou.txt
393221 -rw-rw-r-- 2 operador operador 27 Aug 18 15:02 dur.txt
393221 -rw-rw-r-- 2 operador operador 27 Aug 18 15:02 original.txt
393225 lrwxrwxrwx 1 operador operador 12 Aug 18 15:03 tou.txt -> original.txt

Lectura columna a columna:

  • dur.txt i original.txt comparteixen l'inode 393221 i tots dos mostren comptador 2. Són dos noms del mateix fitxer, sense jerarquia entre ells.
  • tou.txt té inode propi (393225), tipus l, permisos lrwxrwxrwx (que no signifiquen res), mida 12 —els caràcters d'original.txt— i la fletxa al destí.
operador@srv-tramontana:/tmp$ echo "línia 4" >> dur.txt
operador@srv-tramontana:/tmp$ cat original.txt
línia 1
línia 2
línia 3
línia 4
operador@srv-tramontana:/tmp$ cat tou.txt
línia 1
línia 2
línia 3
línia 4

Tots tres veuen el mateix. dur.txt i original.txt perquè són el mateix inode; tou.txt perquè es resol a original.txt, que continua existint.

operador@srv-tramontana:/tmp$ rm original.txt
operador@srv-tramontana:/tmp$ ls -li dur.txt tou.txt
393221 -rw-rw-r-- 1 operador operador 36 Aug 18 15:05 dur.txt
393225 lrwxrwxrwx 1 operador operador 12 Aug 18 15:03 tou.txt -> original.txt

operador@srv-tramontana:/tmp$ cat dur.txt
línia 1
línia 2
línia 3
línia 4

operador@srv-tramontana:/tmp$ cat tou.txt
cat: tou.txt: No such file or directory

dur.txt funciona perfectament i el seu comptador ha baixat de 2 a 1. rm va eliminar una entrada de directori i va decrementar el comptador; com que no va arribar a 0, l'inode i les seves dades continuen allà.

tou.txt està trencat. Contenia el text original.txt, i aquest nom ja no existeix al directori. L'enllaç no guardava cap referència a l'inode, només una cadena.

operador@srv-tramontana:/tmp$ echo "contingut completament diferent" > original.txt
operador@srv-tramontana:/tmp$ ls -li original.txt dur.txt tou.txt
393221 -rw-rw-r-- 1 operador operador 36 Aug 18 15:05 dur.txt
393230 -rw-rw-r-- 1 operador operador 32 Aug 18 15:08 original.txt
393225 lrwxrwxrwx 1 operador operador 12 Aug 18 15:03 tou.txt -> original.txt

operador@srv-tramontana:/tmp$ cat tou.txt
contingut completament diferent

operador@srv-tramontana:/tmp$ cat dur.txt
línia 1
...

Aquest últim pas és el que fixa el concepte:

  • tou.txt s'ha «arreglat» sol i ara apunta al fitxer nou, que té un inode diferent (393230). L'enllaç simbòlic es resol per nom, en el moment de fer-lo servir, així que segueix qualsevol fitxer que ocupi aquell nom. Aquesta és la seva virtut i el seu perill: pot acabar apuntant a alguna cosa que no té res a veure amb l'original.
  • dur.txt continua amb el contingut antic a l'inode 393221. Mai no va dependre del nom.

Resum en una frase: l'enllaç dur està lligat al contingut; el simbòlic està lligat al nom.

Solució 2

# 1. Crear el release a partir del contingut actual, preservant-ho tot
operador@srv-tramontana:~$ sudo mkdir -p /opt/tramontana/releases
operador@srv-tramontana:~$ sudo rsync -a /opt/tramontana/app/ /opt/tramontana/releases/3.2.1/

operador@srv-tramontana:~$ sudo diff -rq /opt/tramontana/app /opt/tramontana/releases/3.2.1
operador@srv-tramontana:~$ echo $?
0

rsync -a amb barra final a l'origen copia el contingut, no la carpeta. I diff -rq sense sortida confirma que la còpia és idèntica abans de tocar res.

# 2. Substituir el directori per l'enllaç
operador@srv-tramontana:~$ cd /opt/tramontana

#    Primer reanomenar (no esborrar): si alguna cosa va malament, l'original continua allà
operador@srv-tramontana:/opt/tramontana$ sudo mv app app.directori-original

#    Crear l'enllaç relatiu
operador@srv-tramontana:/opt/tramontana$ sudo ln -sfn releases/3.2.1 app

#    Verificar que el contingut continua accessible per la ruta de sempre
operador@srv-tramontana:/opt/tramontana$ ls -l app
lrwxrwxrwx 1 root root 16 Aug 18 15:20 app -> releases/3.2.1
operador@srv-tramontana:/opt/tramontana$ cat app/version.txt
Tramontana Reserves 3.2.1
operador@srv-tramontana:/opt/tramontana$ ls app/
executable  plantilles  version.txt

#    Només quan tot està verificat, eliminar el directori antic
operador@srv-tramontana:/opt/tramontana$ sudo rm -rI app.directori-original

El detall important és mv en comptes de rm al pas intermedi. Reanomenar és instantani i reversible; esborrar no. Si l'enllaç hagués sortit malament, un mv de tornada ho restaurava tot.

# 3. Desplegar 3.3.0
operador@srv-tramontana:/opt/tramontana$ sudo cp -a releases/3.2.1 releases/3.3.0
operador@srv-tramontana:/opt/tramontana$ echo "Tramontana Reserves 3.3.0" | sudo tee releases/3.3.0/version.txt
Tramontana Reserves 3.3.0

#    Verificar ABANS d'activar
operador@srv-tramontana:/opt/tramontana$ cat releases/3.3.0/version.txt
Tramontana Reserves 3.3.0

#    Anotar l'estat actual per si cal tornar
operador@srv-tramontana:/opt/tramontana$ readlink app
releases/3.2.1

#    Activar
operador@srv-tramontana:/opt/tramontana$ sudo ln -sfn releases/3.3.0 app
operador@srv-tramontana:/opt/tramontana$ cat app/version.txt
Tramontana Reserves 3.3.0
# 4. Marxa enrere
operador@srv-tramontana:/opt/tramontana$ sudo ln -sfn releases/3.2.1 app
operador@srv-tramontana:/opt/tramontana$ cat app/version.txt
Tramontana Reserves 3.2.1

Una ordre i ets a la versió anterior. I releases/3.3.0 continua al disc, així que reintentar el desplegament després de corregir el problema és una altra ordre.

# 5. Comprovació de l'enllaç
operador@srv-tramontana:/opt/tramontana$ readlink -e app > /dev/null && echo "OK: $(readlink app)" || echo "FALLADA: enllaç trencat"
OK: releases/3.2.1

Desglossament: readlink -e resol l'enllaç i falla amb codi diferent de 0 si el destí no existeix. Amb && i || de la lliçó 02-01, la línia informa en tots dos casos. És exactament la comprovació que anirà al principi de l'script de desplegament del Mòdul 4, per no arrencar mai el servei amb l'enllaç apuntant al no-res.

Solució 3

Problemes de la proposta d'en Luis, de més a menys greu:

1. Producció dependria del directori personal d'un desenvolupador. L'enllaç apunta a /home/luis/projectes/tramontana/build. Això significa que l'aplicació en producció s'executa amb el codi que en Luis tingui a la seva carpeta de treball en aquell instant. Cada vegada que en Luis compili, producció canvia. Sense revisió, sense aprovació de la Marta, sense avís. És exactament l'escenari que el procés de desplegament existeix per impedir.

2. Aquest directori no existeix a srv-tramontana. /home/luis és una ruta del portàtil d'en Luis. Al servidor, l'enllaç naixeria trencat:

operador@srv-tramontana:~$ ls -l /opt/tramontana/app
lrwxrwxrwx 1 root root 38 Aug 18 15:40 app -> /home/luis/projectes/tramontana/build
operador@srv-tramontana:~$ readlink -e /opt/tramontana/app
operador@srv-tramontana:~$ echo $?
1

ls -l ho mostra tan tranquil, però el servei no arrencaria. I el missatge d'error seria No such file or directory: /opt/tramontana/app, quan app sí que existeix: és el destí el que no. Diagnosticar això sense conèixer els enllaços costa una bona estona.

3. sudo rm app destrueix l'original abans de tenir res. Si l'enllaç nou surt malament —i sortirà malament—, l'aplicació ja no hi és. No hi ha marxa enrere. Hauria de ser mv, i només esborrar quan el nou estigui verificat.

4. Trenca l'FHS. El Mòdul 1 va justificar /opt/tramontana/app precisament perquè /home/usuari/projecte és el pitjor lloc possible: donar de baixa un empleat tomba el servei. Aquí l'enllaç reintrodueix aquesta dependència per la porta del darrere, amb l'agreujant que a ls -l /opt/tramontana sembla que tot està bé.

5. Problema de permisos i seguretat. El codi de producció quedaria sota el control d'un usuari sense privilegis, que el pot modificar en qualsevol moment sense sudo. Qualsevol que comprometi el compte d'en Luis controla l'aplicació en producció. La lliçó 02-07 et donarà el vocabulari exacte per explicar per què això és greu.

6. No hi ha versionatge ni manera de tornar enrere. No se sap quina versió està desplegada ni com recuperar l'anterior.

Alternativa correcta, que és el patró de l'apartat 12:

# En Luis genera un paquet versionat des del seu entorn de compilació
luis@portatil-luis:~$ tar -czf tramontana-app-3.3.0.tar.gz -C build .
luis@portatil-luis:~$ sha256sum tramontana-app-3.3.0.tar.gz > tramontana-app-3.3.0.tar.gz.sha256

# L'operador el rep, en verifica la integritat i el desplega com a release
operador@srv-tramontana:~$ sha256sum -c /srv/tramontana/enviaments/tramontana-app-3.3.0.tar.gz.sha256
tramontana-app-3.3.0.tar.gz: OK

operador@srv-tramontana:~$ sudo mkdir -p /opt/tramontana/releases/3.3.0
operador@srv-tramontana:~$ sudo tar -xzf /srv/tramontana/enviaments/tramontana-app-3.3.0.tar.gz \
    -C /opt/tramontana/releases/3.3.0

# Verificar abans d'activar
operador@srv-tramontana:~$ cat /opt/tramontana/releases/3.3.0/version.txt
Tramontana Reserves 3.3.0

# Activar amb una sola ordre atòmica
operador@srv-tramontana:~$ cd /opt/tramontana && sudo ln -sfn releases/3.3.0 app
operador@srv-tramontana:/opt/tramontana$ readlink -e app && cat app/version.txt
/opt/tramontana/releases/3.3.0
Tramontana Reserves 3.3.0

Resposta per a la Marta:

No convé aprovar la proposta tal com està. La idea de fons —poder canviar de versió ràpidament— és bona, i de fet és la que implementarem; el problema és l'execució concreta.

Tal com està plantejada, l'aplicació en producció passaria a executar directament el directori de treball del portàtil d'en Luis. Això vol dir tres coses: que qualsevol compilació seva canviaria producció a l'instant i sense avís, que no sabríem en cap moment quina versió està funcionant, i que si en Luis marxa de l'empresa o canvia de portàtil, el servei deixa de funcionar. A més, al servidor aquesta ruta ni tan sols existeix, així que el canvi deixaria l'aplicació caiguda immediatament.

L'alternativa que proposo aconsegueix el mateix objectiu amb garanties: cada versió s'instal·la al seu propi directori numerat dins del servidor, i un punter indica quina està activa. Desplegar és canviar el punter, i tornar a la versió anterior també, en tots dos casos amb una sola ordre i sense copiar res. La versió anterior es manté intacta al servidor, així que la marxa enrere és immediata i segura. I en tot moment es pot consultar quina versió és en producció i qui la va aprovar.

L'hi comento a en Luis: el que necessitem d'ell és el paquet de la versió, no accés directe des del seu equip.

Conclusió

Has desmuntat la suposició amb què vas començar el mòdul: un fitxer no és un nom.

  • Un inode guarda totes les metadades i les adreces dels blocs, menys el nom. Els directoris són índexs de parells (nom, inode).
  • Aquesta arquitectura explica per què mv és instantani, per què . comparteix inode amb el seu directori, per què es pot esborrar un fitxer obert i per què df i du de vegades no quadren.
  • Un enllaç dur és un altre nom per al mateix inode. rm desenllaça: el contingut viu mentre el comptador no arribi a zero i ningú no el tingui obert.
  • Els enllaços durs no creuen sistemes de fitxers perquè el número d'inode no significa res fora del seu, i no apunten a directoris perquè permetrien cicles que trencarien els recorreguts recursius i la recollida d'espai.
  • Un enllaç simbòlic és un fitxer el contingut del qual és una ruta. Creua sistemes de fitxers, apunta a directoris, pot apuntar al que no existeix, i es resol per nom en el moment de fer-lo servir.
  • Relatius dins d'un mateix arbre, absoluts entre arbres diferents, i ln -sr els calcula per tu.
  • Els enllaços trencats no els detecta ls: els detecten readlink -e i find -xtype l.
  • Cada ordre decideix si segueix l'enllaç o no, i els paranys seriosos són cp sense -a, que desfà els enllaços, i la barra final en un rm -rf, que travessa l'enllaç i esborra el destí real.
  • Els enllaços són estructura del sistema: /bin, /usr/bin/python3, /etc/alternatives i habilitar un servei de systemd és crear un enllaç.
  • I Tramontana ja té el seu patró de desplegament: releases/ immutables i un app -> releases/N que converteix publicar i revertir en un ln -sfn atòmic, amb -n perquè sense ell el desplegament falla en silenci.

Queda l'última peça del mòdul, i és la que més conseqüències té. Has vist que /etc/tramontana/app.conf és -rw-r----- i pertany a root:tramontana, que l'executable és -rwxr-xr-x, i que els permisos d'un enllaç simbòlic no signifiquen res. Has fet servir sudo sense preguntar-te del tot per què calia. A la propera lliçó, Permisos i Propietat dels Fitxers, tot això deixa de ser soroll: llegiràs la cadena d'ls -l caràcter a caràcter, entendràs per què x significa coses diferents en un fitxer i en un directori, traduiràs entre notació octal i simbòlica en tots dos sentits, calcularàs què fa la umask, reconeixeràs SUID, SGID i sticky bit quan els vegis, i dissenyaràs els permisos correctes de cada ruta de Tramontana. És la lliçó que separa un servidor que funciona d'un servidor que a més és segur.

Curs de Linux: De Principiant a Administrador de Sistemes

Mòdul 1: Introducció a Linux

Mòdul 2: Comandes Bàsiques de Linux

Mòdul 3: Habilitats Avançades en la Línia de Comandes

Mòdul 4: Scripting en Shell

Mòdul 5: Administració del Sistema

Mòdul 6: Xarxes i Seguretat

Mòdul 7: Temes Avançats

Mòdul 8: Projectes Pràctics

© Copyright 2026. Tots els drets reservats