Fa cinquanta-dues lliçons que administres srv-tramontana. Saps muntar un servidor intermediari invers amb TLS, ajustar PostgreSQL, xifrar un volum, endurir una unitat de systemd i reconstruir la màquina sencera amb Ansible en cinquanta minuts. I hi ha un dubte raonable que gairebé tothom té a aquestes altures: quant d'això és «cosa de servidors d'empresa» i quant s'aplica de debò a qualsevol lloc?

Aquesta lliçó respon a aquella pregunta construint una cosa que és teva: un servidor multimèdia per a casa teva. Muntaràs Jellyfin sobre el teu propi maquinari, amb emmagatzematge redundant, acceleració per maquinari per a la transcodificació, comparticions per als dispositius de la família, accés des de fora i còpies de seguretat. I descobriràs que és exactament la mateixa feina: els mateixos UUID a fstab, la mateixa clau a keyrings, la mateixa unitat endurida, el mateix smartctl, el mateix Ansible.

Amb dues diferències que a casa importen i al centre de dades no: el consum elèctric i el soroll. I amb un advertiment legal que convé llegir abans de començar.

Contingut

  1. Advertiment legal: quin contingut és legítim
  2. Objectiu, requisits i decisions de disseny
  3. Triar el maquinari
  4. Emmagatzematge: RAID, Btrfs i la veritat sobre les còpies
  5. Jellyfin enfront de Plex i Emby
  6. Instal·lació i unitat de systemd endurida
  7. Acceleració per maquinari per a la transcodificació
  8. Organització de la biblioteca i noms de fitxer
  9. Compartir: Samba per a tot, NFS per a Linux
  10. Accés des de fora de casa: tres opcions i el seu risc
  11. Descàrregues i automatització
  12. Còpies de seguretat de la biblioteca i de la configuració
  13. Consum, soroll i temperatura
  14. Automatització amb Ansible
  15. Operació: actualitzacions, salut dels discos i què fer quan un falla

Advertiment legal: quin contingut és legítim

Va primer perquè és el primer.

Un servidor multimèdia és una eina neutra: serveix els fitxers que tu li dones. El que en determina la legalitat és l'origen del contingut, i a Espanya la situació és raonablement clara:

Contingut Situació
Fotos i vídeos que has gravat tu Teu, sense cap dubte
Còpia d'un DVD o Blu-ray que has comprat Zona grisa: la còpia privada existeix, però saltar-se la protecció anticòpia està prohibit
Música de CD que has comprat, extreta Còpia privada, generalment acceptada
Contingut comprat a botigues digitals sense DRM Teu, segons la llicència
Obres en domini públic o amb llicència lliure Legítim
Descàrregues de xarxes P2P d'obres protegides No és legítim
Contingut d'una subscripció, descarregat saltant-se el DRM No és legítim

Aquest curs no cobreix en cap moment l'obtenció de contingut protegit per drets d'autor. Tot el que segueix pressuposa que la biblioteca conté material propi, adquirit legalment, o lliure. Si tens 4 TB de vídeos familiars, discos comprats i cinema clàssic en domini públic, aquest projecte és exactament per a tu.

I una nota addicional: si comparteixes el servidor amb persones de fora de la teva unitat familiar, encara que sigui amb contingut comprat, estàs fent comunicació pública, que és una cosa diferent de la còpia privada.

Objectiu, requisits i decisions de disseny

Objectiu. Un servidor domèstic que serveixi la biblioteca familiar a televisors, mòbils, tauletes i portàtils dins de casa; accessible des de fora de manera segura; amb emmagatzematge tolerant a la fallada d'un disc; amb còpies del que és insubstituïble; i amb un consum elèctric que no faci mal.

Decisions de disseny, preses abans de comprar res:

Decisió Elecció Per què
Sistema operatiu Ubuntu Server 24.04 LTS El mateix del curs: tot el que has après s'hi aplica
Interfície gràfica Cap Consumeix RAM i afegeix superfície d'atac per a res
Servidor multimèdia Jellyfin Lliure, sense compte, sense telemetria (apartat 5)
Emmagatzematge Btrfs RAID 1 sobre dos discos Sumes de verificació, instantànies i ampliació flexible
Accés des de fora VPN (08-04) Zero ports exposats
Compartició interna Samba, i NFS només si hi ha clients Linux Compatibilitat universal
Gestió Ansible Reconstruïble com srv-tramontana

I una decisió que convé prendre explícitament: aquest servidor és el segon laboratori. En ser teu i no crític per a ningú, és on pots provar coses que no provaries en producció. Aquesta és una de les millors raons per muntar-lo.

Anomenarem l'equip srv-casa, amb IP fixa 192.168.1.20 a la xarxa domèstica.

Triar el maquinari

Opció Preu orientatiu Consum Transcodificació Emmagatzematge Soroll
Raspberry Pi 5 (8 GB) 80-120 € 4-8 W Molt limitada; sense descodificació H.265 per maquinari USB, sense SATA natiu Silenciosa
Mini PC amb Intel N100 130-200 € 6-15 W Excel·lent: Quick Sync modern, AV1 1-2 M.2 + SATA Molt baix
NAS comercial (Synology, QNAP) 300-700 € 15-30 W Variable segons el model 2-4 badies Baix
PC reaprofitat 0 € 40-90 W Depèn de la gràfica Moltes badies Alt
Servidor de segona mà 100-300 € 60-150 W Sense GPU normalment Moltes badies Molt alt

La recomanació és el mini PC amb Intel N100 o similar, i per una raó que es veu a la taula de l'apartat 13: el consum. Un servidor que està encès 24 hores al dia durant un any consumeix 8.760 hores de la seva potència mitjana. La diferència entre 10 W i 60 W són 438 kWh a l'any, uns 105 € a Espanya a 0,24 €/kWh. En tres anys, la diferència d'electricitat paga el mini PC sencer i encara sobra.

Sobre la Raspberry Pi 5, que és l'opció més popular i mereix un matís honest: és magnífica per a una biblioteca de música, fotos o vídeo que els clients reprodueixin directament sense conversió. Però la Pi 5 va eliminar el descodificador H.265 per maquinari que tenia la Pi 4, i no té codificador de vídeo. Si algun client necessita transcodificar —un televisor vell, un mòbil per dades, un format no admès— la Pi ho farà per programari, amb un resultat que va de lent a inviable. Si la teva biblioteca és H.264 i els teus clients són moderns, la Pi va perfecta; si tens material H.265 4K i un televisor antic, no.

Sobre reaprofitar un PC antic: és gratis i és la pitjor opció a mitjà termini. Consumeix molt, fa soroll, els seus ventiladors tenen deu anys i la seva font d'alimentació és el que més falla en un equip encès permanentment. Com a laboratori per aprendre, perfecte. Com a servidor permanent, surt car.

Sobre la RAM: 4 GB basten per a Jellyfin servint dos o tres clients. 8 GB donen folgança per a la memòria cau del sistema de fitxers, que és el que fa que la biblioteca es navegui amb fluïdesa.

Emmagatzematge: RAID, Btrfs i la veritat sobre les còpies

Quant espai cal de debò

Contingut Mida típica
Cançó en FLAC 30-50 MB
Àlbum complet en FLAC 400-600 MB
Foto de mòbil modern 3-8 MB
Foto RAW de càmera 25-45 MB
Episodi de sèrie 1080p H.264 1,2-2,5 GB
Episodi de sèrie 1080p H.265 400-900 MB
Pel·lícula 1080p H.264 4-12 GB
Pel·lícula 1080p H.265 2-5 GB
Pel·lícula 4K HDR 25-60 GB
Còpia íntegra d'un Blu-ray 25-45 GB

Una biblioteca familiar realista —20 anys de fotos, la música comprada, un parell de centenars de pel·lícules i algunes sèries— ronda els 4-8 TB. La regla pràctica: calcula el que creus que necessites i duplica-ho, perquè el 4K i les fotos RAW creixen més ràpid del que la intuïció suggereix.

Per què aquí sí que convé la redundància

A 05-04 vas muntar LVM sense RAID, perquè srv-tramontana és una màquina virtual l'emmagatzematge de la qual ja és redundant a l'amfitrió i perquè el seu valor és a les còpies, no al disc. A casa la situació és la contrària: els discos són físics i són teus, i hi ha contingut insubstituïble —les fotos familiars— que no es pot tornar a descarregar ni comprar.

Nivell Discos Capacitat útil Tolera Comentari
RAID 0 2+ 100 % Res Duplica el risc. A casa no
RAID 1 2 50 % 1 disc El recomanat: simple i suficient
RAID 5 3+ (n−1)/n 1 disc Reconstrucció llarga i arriscada amb discos grans
RAID 6 4+ (n−2)/n 2 discos Per a 6+ discos
RAID 10 4+ 50 % 1 per parella Ràpid, car en discos

RAID 5 amb discos de 8 TB o més mereix un avís. Reconstruir el conjunt després de canviar un disc pot trigar més d'un dia, durant el qual els altres discos treballen al límit llegint cada sector. És precisament el moment en què un altre disc té més probabilitat de fallar, i si falla, es perd tot. Amb dos discos i RAID 1, la reconstrucció és una còpia senzilla i el risc és molt menor.

mdadm enfront de Btrfs i ZFS

mdadm + ext4 Btrfs ZFS
Al nucli d'Ubuntu Sí Sí Sí (mòdul DKMS)
Detecta corrupció silenciosa No Sí: suma de verificació de dades i metadades Sí
La repara automàticament No Sí, amb RAID 1 Sí
Instantànies Amb LVM Natives, instantànies Natives
Afegir un disc d'una altra mida No Sí Difícil
Compressió transparent No Sí (zstd) Sí
RAID 5/6 Sòlid NO FER-LO SERVIR: encara es considera inestable Sòlid (RAIDZ)
Consum de RAM Baix Baix Alt: ~1 GB per TB amb deduplicació
Llicència GPL GPL CDDL: no es distribueix dins del nucli

L'elecció és Btrfs en RAID 1, i l'argument decisiu són les sumes de verificació. Un RAID clàssic protegeix de la fallada d'un disc sencer, que és sorollosa i evident. No protegeix de la corrupció silenciosa: un bit que canvia al disc per degradació magnètica, un error de microprogramari, un cable defectuós. mdadm no ho pot detectar —té dues còpies diferents i no sap quina és la bona—, i aquell bit corromput es propaga a les teves còpies de seguretat. Btrfs guarda una suma de verificació de cada bloc: detecta l'error, sap que l'altra còpia és correcta i la repara.

Per a fotos familiars que estaran trenta anys en un disc, això no és un detall tècnic.

$ sudo apt install btrfs-progs smartmontools

# 1. Revisar l estat dels discos ABANS de fer-los servir, fins i tot nous
$ sudo smartctl -a /dev/sda | grep -E 'Model|Power_On_Hours|Reallocated'
Device Model:     WDC WD80EFPX-68C4ZN0
  9 Power_On_Hours          0x0032   100   100   000    Old_age   Always  -  4
  5 Reallocated_Sector_Ct   0x0033   100   100   140    Pre-fail  Always  -  0

# 2. Crear el sistema de fitxers en RAID 1 (dades I metadades)
$ sudo mkfs.btrfs -L multimedia -d raid1 -m raid1 /dev/sda /dev/sdb
Label:              multimedia
UUID:               8f3a2c11-4d5e-4a91-b7c2-1e9d0f8a3b45
Number of devices:  2
Data,RAID1:  8.00TiB
Metadata,RAID1: 8.00GiB

# 3. Muntar per UUID, mai per /dev/sdX (05-04)
$ sudo mkdir -p /srv/multimedia
$ blkid /dev/sda | grep -oP 'UUID="\K[^"]+'
8f3a2c11-4d5e-4a91-b7c2-1e9d0f8a3b45
# /etc/fstab
# compress=zstd:3  -> comprimeix el que es comprimible; el video ja
#                     comprimit el detecta i el salta, aixi que no costa res
# noatime          -> no escriure la data d acces a cada lectura:
#                     menys escriptures i mes vida del disc
# nofail           -> SI EL DISC NO HI ES, EL SISTEMA ARRENCA IGUALMENT.
#                     Sense aixo, un disc desconnectat deixa l equip en
#                     mode emergencia, sense xarxa i sense SSH: cal anar-hi
#                     amb teclat i monitor. A casa, imprescindible.
# x-systemd.device-timeout=10 -> no esperar 90 s un disc absent
UUID=8f3a2c11-4d5e-4a91-b7c2-1e9d0f8a3b45  /srv/multimedia  btrfs \
  defaults,compress=zstd:3,noatime,nofail,x-systemd.device-timeout=10  0  0
$ sudo mount -a          # SEMPRE despres de tocar fstab (convencio del curs)
$ findmnt /srv/multimedia
TARGET          SOURCE    FSTYPE OPTIONS
/srv/multimedia /dev/sda  btrfs  rw,noatime,compress=zstd:3,nofail

$ sudo btrfs filesystem usage /srv/multimedia
Overall:
    Device size:                  14.55TiB
    Device allocated:              2.02TiB
    Used:                          2.01TiB
    Free (estimated):              6.26TiB      (min: 6.26TiB)
Data,RAID1: Size:1.01TiB, Used:1.00TiB
   /dev/sda	 1.01TiB
   /dev/sdb	 1.01TiB

Fixa't que Device size mostra 14,55 TiB (els dos discos) però Free n'estima 6,26 TiB: en RAID 1 cada bloc s'escriu dues vegades. df -h dona xifres confuses amb Btrfs; fes servir sempre btrfs filesystem usage.

La depuració de dades: l'operació que justifica Btrfs

# Verifica TOTES les sumes de verificacio i repara el que pot amb l altra copia
$ sudo btrfs scrub start -B /srv/multimedia
scrub done for 8f3a2c11-4d5e-4a91-b7c2-1e9d0f8a3b45
	Total to scrub: 2.01TiB
	Rate: 178.42MiB/s
	Error summary: csum=2
	  Corrected: 2
	  Uncorrectable: 0

Corrected: 2. Dos blocs estaven corruptes en un disc i Btrfs els ha reparat amb la còpia bona de l'altre. Amb mdadm aquells dos blocs haurien continuat allà, corruptes, i probablement haurien acabat a les còpies de seguretat. Aquest és l'argument sencer, en una línia de sortida.

La depuració es programa mensualment amb un temporitzador, seguint el patró de 05-05:

# /etc/systemd/system/btrfs-scrub.timer
[Unit]
Description=Depuracio mensual de Btrfs a /srv/multimedia

[Timer]
OnCalendar=Sun *-*-01..07 03:00:00   # primer diumenge de cada mes
Persistent=true
RandomizedDelaySec=1h

[Install]
WantedBy=timers.target
# /etc/systemd/system/btrfs-scrub.service
[Unit]
Description=Depuracio de dades de Btrfs

[Service]
Type=oneshot
ExecStart=/usr/bin/btrfs scrub start -B -c idle -n 5 /srv/multimedia
# -c idle -n 5: prioritat d E/S minima, per no molestar si algu esta
# veient una pel·licula a les 3 de la matinada
Nice=19
IOSchedulingClass=idle

El RAID no és una còpia de seguretat

Cal dir-ho amb totes les lletres, perquè és l'error domèstic més car:

El RAID protegeix de la fallada d'un disc. No protegeix de res més.

Amenaça El RAID 1 en protegeix?
S'espatlla un disc Sí
Corrupció silenciosa d'un bloc Sí, amb Btrfs; no amb mdadm
Esborres una carpeta per error No: s'esborra dels dos discos
Un xifrador de fitxers ataca l'equip No: xifra els dos discos
Falla la font d'alimentació i s'emporta els discos No
Sobretensió, incendi, inundació, robatori No
Actualització que corromp el sistema de fitxers No

De les set files, el RAID en cobreix una i mitja. Per això existeix l'apartat 12.

Jellyfin enfront de Plex i Emby

Jellyfin Plex Emby
Llicència GPL, lliure Propietària Propietària (va ser lliure)
Requereix compte al núvol No Sí, fins i tot per a ús local Opcional
Telemetria Cap Sí, i hi ha hagut polèmiques Sí
Funcions de pagament Cap: tot inclòs Plex Pass per a maquinari, mòbil, saltar introduccions Emby Premiere
Acceleració per maquinari Gratuïta Requereix subscripció Requereix subscripció
Aplicacions de TV Android TV, webOS, Tizen, Roku, Kodi Més i més polides Intermedi
Funciona si Internet cau Sí, completament Pot fallar l'autenticació Sí
Maduresa de la interfície Bona, una mica més aspra Excel·lent Bona
Metadades automàtiques Sí Sí, una mica millors Sí

L'elecció és Jellyfin, i hi ha tres raons que pesen més que la interfície una mica menys polida:

  1. No depèn de ningú. Plex exigeix un compte al seu núvol fins i tot per veure una pel·lícula des del menjador. El dia que Plex canviï de model de negoci, pugi preus o tanqui —coses que han passat amb productes d'aquest tipus— la teva biblioteca continua funcionant amb Jellyfin exactament igual. És la definició pràctica de per què el programari lliure importa, i aquesta lliçó és un bon lloc per comprovar-ho.
  2. L'acceleració per maquinari és gratuïta. A Plex i Emby és de pagament, i és precisament la funció que decideix si el servidor serveix o no serveix.
  3. Sense telemetria. El que veu la teva família a casa no surt de casa.

Instal·lació i unitat de systemd endurida

Dipòsit oficial amb la clau a keyrings, exactament com a 05-03 — res d'apt-key, que està obsolet, ni de canalitzar un script cap a bash:

$ sudo install -m 0755 -d /etc/apt/keyrings
$ curl -fsSL https://repo.jellyfin.org/jellyfin_team.gpg.key | \
      sudo gpg --dearmor -o /etc/apt/keyrings/jellyfin.gpg
$ sudo chmod 0644 /etc/apt/keyrings/jellyfin.gpg

$ cat <<EOF | sudo tee /etc/apt/sources.list.d/jellyfin.sources
Types: deb
URIs: https://repo.jellyfin.org/ubuntu
Suites: noble
Components: main
Architectures: amd64
Signed-By: /etc/apt/keyrings/jellyfin.gpg
EOF

$ sudo apt update && sudo apt install jellyfin
$ systemctl is-active jellyfin
active

Comprovació obligatòria que la clau és la que diu ser, abans de confiar en el dipòsit:

$ gpg --show-keys /etc/apt/keyrings/jellyfin.gpg | head -2
pub   rsa4096 2018-11-08 [SC]
      04B7DA6B1FE10D5E76D9D5A6F5B2F0D5A8E5D6C3

Aquella empremta es contrasta amb la publicada a la documentació oficial. És un pas que gairebé tothom es salta i que és l'única defensa real davant d'un dipòsit compromès.

La unitat endurida

El paquet porta una unitat funcional però poc restrictiva. Seguint la convenció de drop-ins del curs, no s'edita: es complementa.

$ sudo systemctl edit jellyfin
# /etc/systemd/system/jellyfin.service.d/override.conf
[Service]
# --- Aillament del sistema de fitxers ---
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
# Nomes aquestes rutes son escrivibles
ReadWritePaths=/var/lib/jellyfin /var/log/jellyfin /var/cache/jellyfin /etc/jellyfin
# La biblioteca, NOMES LECTURA: Jellyfin no te cap rao per poder
# esborrar les teves pel·licules. Es la restriccio mes important de totes.
ReadOnlyPaths=/srv/multimedia

# --- Privilegis ---
NoNewPrivileges=true
PrivateDevices=false            # false: necessita /dev/dri per a la GPU
DeviceAllow=/dev/dri/renderD128 rw
DeviceAllow=/dev/dri/card0 rw
CapabilityBoundingSet=
AmbientCapabilities=
RestrictSUIDSGID=true

# --- Nucli i memoria ---
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectKernelLogs=true
ProtectControlGroups=true
ProtectProc=invisible
MemoryDenyWriteExecute=false    # .NET fa servir compilacio JIT: no es pot
LockPersonality=true
RestrictRealtime=true
RestrictNamespaces=true

# --- Xarxa ---
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK
IPAddressAllow=localhost 192.168.1.0/24 10.8.0.0/24
IPAddressDeny=any

# --- Crides al sistema ---
SystemCallFilter=@system-service
SystemCallFilter=~@privileged @resources @obsolete
SystemCallArchitectures=native

# --- Limits de recursos (cgroups, 07-05) ---
# Evita que una transcodificacio descontrolada bloquegi l equip
CPUQuota=350%
MemoryMax=3G
IOWeight=50
$ sudo systemctl daemon-reload && sudo systemctl restart jellyfin
$ systemd-analyze security jellyfin
→ Overall exposure level for jellyfin.service: 2.4 OK

De 8,2 (perillós) a 2,4, en la mateixa línia que l'1,6 de tramontana.service. No baixa més per dos motius legítims: necessita accés a /dev/dri per a la GPU i .NET requereix memòria executable per al seu compilador JIT.

ReadOnlyPaths=/srv/multimedia mereix un paràgraf. Jellyfin llegeix la biblioteca; no l'escriu. Amb aquesta línia, ni una fallada de l'aplicació ni una vulnerabilitat remota poden esborrar vint anys de fotos. És una defensa d'una línia amb un valor enorme, i és exactament el mateix raonament que vas aplicar a tramontana.service a 05-05.

I la comprovació que funciona:

$ sudo -u jellyfin touch /srv/multimedia/prova
touch: cannot touch '/srv/multimedia/prova': Read-only file system

Acceleració per maquinari per a la transcodificació

Què és transcodificar i per què importa tant

Quan el client pot reproduir el fitxer tal com està, Jellyfin fa reproducció directa: envia els bytes sense tocar-los, amb un cost de CPU gairebé nul. Quan no pot —perquè el còdec, la resolució, el contenidor o l'amplada de banda no li serveixen—, Jellyfin transcodifica: descodifica el vídeo i el torna a codificar al vol.

Transcodificar per programari una pel·lícula 1080p H.265 a H.264 consumeix diversos nuclis al 100 % en temps real. En un mini PC de dos nuclis, això significa que un sol espectador satura la màquina i el vídeo s'entretalla. Amb acceleració per maquinari, la mateixa feina la fa un bloc dedicat del xip amb un cost de CPU del 5 %, i es poden servir tres o quatre fluxos alhora.

És la diferència entre un servidor que funciona i un que no. Per això cal comprovar el maquinari abans de comprar-lo.

Tecnologia Fabricant Qualitat Notes
Quick Sync (VAAPI/QSV) Intel iGPU Molt bona La millor relació consum/rendiment
NVENC/NVDEC NVIDIA Molt bona Límit de fluxos simultanis a les de consum
VAAPI sobre AMD AMD Bona Compatibilitat una mica més irregular
V4L2 M2M Raspberry Pi 4 Acceptable No a la Pi 5
Programari (libx264) CPU Millor qualitat Inviable en temps real en equips modestos

Comprovar si l'equip la té

$ sudo apt install vainfo intel-media-va-driver-non-free

# 1. Existeix el dispositiu de renderitzat?
$ ls -l /dev/dri/
crw-rw---- 1 root video  226,   0 Aug 18 09:02 card0
crw-rw---- 1 root render 226, 128 Aug 18 09:02 renderD128

# 2. Que sap fer?
$ vainfo --display drm --device /dev/dri/renderD128 2>/dev/null | \
      grep -E 'Driver version|VAProfileH264High|VAProfileHEVCMain|VAProfileAV1'
vainfo: Driver version: Intel iHD driver for Intel(R) Gen Graphics - 24.1.0
      VAProfileH264High               : VAEntrypointVLD        (descodifica)
      VAProfileH264High               : VAEntrypointEncSliceLP (codifica)
      VAProfileHEVCMain               : VAEntrypointVLD
      VAProfileHEVCMain10             : VAEntrypointVLD
      VAProfileAV1Profile0            : VAEntrypointVLD

Com es llegeix: VAEntrypointVLD significa descodificació per maquinari; VAEntrypointEncSlice significa codificació. Necessites totes dues per transcodificar de debò. Aquest xip descodifica H.264, H.265 (inclòs 10 bits) i AV1, i codifica H.264: perfecte per al que cal.

renderD128 pertany al grup render, així que l'usuari del servei hi ha de ser:

$ sudo usermod -aG render,video jellyfin
$ id jellyfin
uid=996(jellyfin) gid=996(jellyfin) groups=996(jellyfin),44(video),993(render)
$ sudo systemctl restart jellyfin

I això connecta amb la unitat endurida: DeviceAllow=/dev/dri/renderD128 rw és el que permet que el servei, amb PrivateDevices restringit, continuï veient la GPU. Sense aquella línia, la pertinença al grup no serveix de res — un detall que provoca hores de desconcert.

Configurar i verificar

Al tauler de Jellyfin: Tauler > Reproducció > Acceleració per maquinari = VAAPI, dispositiu /dev/dri/renderD128, activant la descodificació d'H.264, HEVC, HEVC 10 bits i VP9, i marcant «Habilita la descodificació per maquinari millorada» i «Permet la codificació en format HEVC».

La verificació real es fa reproduint alguna cosa que forci la conversió:

# Amb una reproduccio en curs que estigui transcodificant
$ ps -eo pcpu,comm | grep ffmpeg
  6.2 ffmpeg

$ sudo intel_gpu_top -s 1000 | head -8
 Freq MHz    IRQ RC6     Power W        IMC MiB/s
 req  act        %       gpu   pkg     rd     wr
 850  842   1284  12    3.21  8.94   412.1  188.4

 ENGINES     BUSY                     MI_SEM MI_WAIT
 Render/3D   4.12% |█                |      0%      0%
 Video       78.44% |███████████████ |      0%      0%
 VideoEnhance 2.10% |                |      0%      0%

Video 78,44 % amb ffmpeg al 6,2 % de CPU: la GPU està fent la feina. Si ffmpeg aparegués al 190 % de CPU i el motor de vídeo a 0 %, l'acceleració no estaria funcionant tot i estar marcada al tauler.

La comparació, mesurada:

Per programari Amb VAAPI
CPU d'ffmpeg (1080p H.265→H.264) 185 % 6 %
Fluxos simultanis possibles 1, amb talls 3-4
Consum elèctric durant la conversió +35 W +7 W
Qualitat visual a igual taxa de bits Una mica millor Lleugerament inferior

I la millor optimització: no transcodificar

Tot l'anterior és la xarxa de seguretat. L'estratègia correcta és que la conversió no calgui:

  • Guarda el material en H.264 en contenidor MP4 si els teus clients són variats: és el que reprodueix tot directament.
  • Fes servir H.265 només si tots els teus dispositius l'admeten; estalvia la meitat d'espai.
  • Els subtítols incrustats forcen la transcodificació encara que el vídeo sigui compatible: els formats gràfics (PGS de Blu-ray) obliguen a «cremar-los» sobre la imatge. Els subtítols en .srt externs s'envien a part i no forcen res. És la causa número u de conversions inesperades.
  • L'àudio sol ser el culpable ocult: un DTS-HD que el televisor no entén obliga a convertir la pista d'àudio, encara que el vídeo passi directe.

Organització de la biblioteca i noms de fitxer

Jellyfin identifica el contingut consultant bases de dades públiques de metadades, i per encertar necessita noms que pugui interpretar. Amb l'estructura correcta, el reconeixement ronda el 99 %; amb noms lliures, passes hores corregint a mà.

/srv/multimedia/
├── pellicules/
│   ├── La Ciutat Cremada (1976)/
│   │   ├── La Ciutat Cremada (1976) - 1080p.mkv
│   │   ├── La Ciutat Cremada (1976).ca.srt
│   │   └── poster.jpg
│   └── Metropolis (1927)/
│       └── Metropolis (1927) - 1080p.mkv
├── series/
│   └── Nom de la Serie (2019)/
│       ├── Season 01/
│       │   ├── Nom de la Serie S01E01.mkv
│       │   └── Nom de la Serie S01E02.mkv
│       └── Season 02/
│           └── Nom de la Serie S02E01.mkv
├── musica/
│   └── Artista/
│       └── Album (2004)/
│           ├── 01 - Primer tema.flac
│           └── 02 - Segon tema.flac
└── fotos/
    └── 2026/
        └── 2026-07 Vacances Pirineus/
Regla Exemple correcte Exemple problemàtic
Una carpeta per pel·lícula, amb any La Ciutat Cremada (1976)/ pellicules/ciutat.mkv
Any entre parèntesis Metropolis (1927) Metropolis 1927
Sèries amb SxxExx Serie S01E03.mkv Serie 1x3.mkv
Carpetes Season NN Season 01/ Temporada 1/
Subtítols amb codi d'idioma pellicula.ca.srt subs.srt
Música: carpeta per artista i àlbum Artista/Album (2004)/ Tot en un directori

L'any és el més important: distingeix entre versions noves i pel·lícules amb el mateix títol, que és l'origen de la majoria de les identificacions errònies.

I una recomanació pràctica: no reanomenis a mà. filebot (comercial) o tinyMediaManager (gratuït) fan la feina. O, amb les eines del Mòdul 3:

# Veure que faria abans de fer-ho (convencio --dry-run del curs)
$ for f in /srv/multimedia/entrada/*.mkv; do
      nou="$(basename "$f" | sed -E 's/\.(1080p|720p|x264|WEB-DL)//gI; s/\./ /g')"
      printf '%s\n  -> %s\n' "$f" "$nou"
  done

Permisos, seguint el model de grups de 05-01:

$ sudo groupadd -f multimedia
$ sudo usermod -aG multimedia jellyfin
$ sudo usermod -aG multimedia "$USER"
$ sudo chown -R root:multimedia /srv/multimedia
$ sudo find /srv/multimedia -type d -exec chmod 2775 {} +   # SGID: hereta grup
$ sudo find /srv/multimedia -type f -exec chmod 0664 {} +

El bit SGID als directoris (el 2 de 2775) fa que tot el que es crea a dins hereti el grup multimedia, sense dependre de l'umask de qui ho creï. És el mateix mecanisme de 05-02.

Compartir: Samba per a tot, NFS per a Linux

Jellyfin serveix la reproducció, però també cal accés als fitxers: copiar fotos des del mòbil, afegir contingut des del portàtil, fer còpies.

Samba (SMB) NFS
Clients Windows, macOS, Linux, Android, iOS, TV Linux i Unix, principalment
Autenticació Usuari i contrasenya propis Per IP de la màquina (NFSv3) o Kerberos (v4)
Rendiment a la LAN Molt bo Una mica millor, menys latència
Permisos Unix Emulats Natius
Configuració Mitjana Simple, si et fies de la xarxa
Xifratge Sí, SMB3 Només amb Kerberos o sobre VPN
Ús domèstic L'opció per defecte Per muntar en altres Linux

La recomanació: Samba com a base, perquè un mòbil, un televisor i un Windows el parlen sense instal·lar res, i NFS només si tens clients Linux que hagin de muntar la biblioteca permanentment.

# /etc/samba/smb.conf (despres de copiar l original a .bak-2026-08-18)
[global]
    workgroup = CASA
    server string = Servidor multimedia
    security = user
    map to guest = never                  # RES de convidats

    # SMB1 esta trencat per disseny (WannaCry es va propagar per aqui).
    # Minim SMB2; SMB3 aporta xifratge.
    server min protocol = SMB2
    client min protocol = SMB2
    server smb encrypt = desired

    # Escoltar NOMES a la LAN i la VPN, mai a totes les interficies
    interfaces = 127.0.0.1 192.168.1.0/24 10.8.0.0/24
    bind interfaces only = yes
    hosts allow = 127.0.0.1 192.168.1.0/24 10.8.0.0/24
    hosts deny = 0.0.0.0/0

    # Sense impressores: elimina superficie d atac i soroll al registre
    load printers = no
    printing = bsd
    printcap name = /dev/null
    disable spoolss = yes

    log level = 1
    log file = /var/log/samba/log.%m
    max log size = 1000

[multimedia]
    path = /srv/multimedia
    comment = Biblioteca familiar
    browseable = yes
    read only = yes                       # per defecte, NOMES LECTURA
    # Nomes aquests usuaris poden escriure
    write list = @multimedia
    valid users = @multimedia
    create mask = 0664
    directory mask = 2775
    force group = multimedia
    vfs objects = recycle                 # paperera: salva d esborrats
    recycle:repository = .paperera
    recycle:keeptree = yes
    recycle:versions = yes

[fotos]
    path = /srv/multimedia/fotos
    valid users = @multimedia
    read only = no
    create mask = 0664
    directory mask = 2775
    force group = multimedia
$ sudo apt install samba
$ testparm -s >/dev/null && echo "configuracio valida"   # el nginx -t de Samba
configuracio valida

# Els usuaris de Samba son INDEPENDENTS dels del sistema, encara que
# el nom coincideixi. Cal crear-los explicitament.
$ sudo smbpasswd -a marta
New SMB password: ********
$ sudo smbpasswd -e marta

$ sudo systemctl restart smbd nmbd
$ smbclient -L //192.168.1.20 -U marta
	Sharename       Type      Comment
	---------       ----      -------
	multimedia      Disk      Biblioteca familiar
	fotos           Disk

Tres decisions defensables d'aquella configuració: map to guest = never (l'accés anònim és la causa de la majoria dels incidents amb Samba), read only = yes per defecte amb write list explícit, i la paperera recycle, que ha salvat més fotos familiars que cap altra opció d'aquest fitxer.

I NFS, per al portàtil Linux:

$ sudo apt install nfs-kernel-server
$ echo '/srv/multimedia 192.168.1.0/24(ro,sync,no_subtree_check,root_squash)' | \
      sudo tee -a /etc/exports
$ sudo exportfs -ra
$ sudo exportfs -v
/srv/multimedia   192.168.1.0/24(ro,sync,no_subtree_check,root_squash)

root_squash converteix el root del client en nobody: sense això, qualsevol amb root en un portàtil de la xarxa tindria root sobre els teus fitxers. I ro, perquè per escriure ja hi ha Samba amb autenticació real.

I el tallafocs, seguint 06-03 — només la LAN:

$ sudo ufw allow from 192.168.1.0/24 to any app Samba
$ sudo ufw allow from 192.168.1.0/24 to any port 8096 proto tcp comment 'Jellyfin'
$ sudo ufw allow from 192.168.1.0/24 to any port 2049 proto tcp comment 'NFS'
$ sudo ufw status | head -6
Status: active
To                         Action      From
--                         ------      ----
Samba                      ALLOW       192.168.1.0/24
8096/tcp (Jellyfin)        ALLOW       192.168.1.0/24
2049/tcp (NFS)             ALLOW       192.168.1.0/24

Accés des de fora de casa: tres opcions i el seu risc

Vols veure la teva biblioteca des d'un hotel. Hi ha tres maneres, i no són equivalents.

Exposar amb servidor intermediari invers VPN Túnel invers
Ports oberts al router 80 i 443 Un d'UDP Cap
Superfície d'atac Jellyfin, exposat a Internet El túnel, sense port TCP Depèn del proveïdor
Requereix IP pública Sí Sí No: funciona amb CGNAT
Configurar a cada dispositiu No Sí, una vegada Depèn
Una fallada de Jellyfin et compromet Sí No: no és abastable Sí
Accés a altres serveis de casa Només el publicat Tots Només el publicat
Complexitat Mitjana (08-01) Mitjana (08-04) Baixa
Dependència de tercers Cap Cap Alta

La recomanació és la VPN, i és la lliçó següent. El raonament:

Exposar Jellyfin a Internet significa que qualsevol vulnerabilitat de Jellyfin és una vulnerabilitat de casa teva. És una aplicació gran, amb autenticació pròpia i un historial de fallades de seguretat com qualsevol programari de la seva mida. Els escàners automàtics troben un port 443 obert en qüestió d'hores. Amb la VPN, un atacant que no tingui la teva clau privada no pot ni tan sols comprovar si Jellyfin existeix.

I hi ha un argument addicional que sol decidir la qüestió: amb la VPN accedeixes també a Samba, al tauler del router i a qualsevol altra cosa que muntis després, sense publicar res de nou.

Si tot i així decideixes exposar-lo —hi ha un cas legítim: compartir amb familiars que no instal·laran una VPN—, el mínim innegociable és:

# /etc/nginx/sites-available/jellyfin — nomes si decideixes exposar-lo
server {
    listen 443 ssl;
    http2 on;
    server_name multimedia.elmeudomini.example;

    ssl_certificate     /etc/letsencrypt/live/multimedia.elmeudomini.example/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/multimedia.elmeudomini.example/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;

    add_header Strict-Transport-Security "max-age=63072000" always;
    add_header X-Content-Type-Options "nosniff" always;

    # Limit de taxa contra forca bruta sobre el formulari d acces
    limit_req zone=login burst=5 nodelay;
    client_max_body_size 20M;

    location / {
        proxy_pass http://127.0.0.1:8096;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        # WebSocket: Jellyfin el fa servir per a l estat de reproduccio
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        # Sense memoria intermedia: es video en flux
        proxy_buffering off;
    }
}

És literalment la configuració de 08-01 amb dos afegits —WebSocket i proxy_buffering off— i demostra el punt d'aquesta lliçó: la feina és la mateixa. A més, fail2ban amb un filtre per al registre de Jellyfin, i contrasenyes fortes per a tots els comptes, inclòs el del nen.

Descàrregues i automatització

Breument, perquè és una àrea on la tècnica i la legalitat es creuen.

Existeix un ecosistema conegut com a arr —Sonarr, Radarr, Lidarr, Prowlarr— que automatitza la cerca, descàrrega i organització de contingut. Es desplega habitualment amb Docker Compose, amb el que saps de 07-05:

# ~/multimedia/compose.yml (fragment il·lustratiu)
services:
  sonarr:
    image: lscr.io/linuxserver/sonarr:latest
    environment:
      - PUID=1000
      - PGID=1002          # grup multimedia
      - TZ=Europe/Madrid
    volumes:
      - ./sonarr-config:/config
      - /srv/multimedia/series:/series
    ports:
      - "127.0.0.1:8989:8989"   # NOMES localhost: s hi arriba per VPN
    restart: unless-stopped

Dues observacions aplicables independentment de l'ús:

  • ports: "127.0.0.1:8989:8989" és el detall crític. Docker crea les seves pròpies regles d'iptables i se salta ufw, com vas veure a 07-05: publicar 8989:8989 a seques exposa el port a tota la xarxa encara que ufw digui el contrari. Especificar l'adreça ho evita.
  • Aquestes eines són perfectament legítimes per gestionar contingut propi, substituir un fitxer malmès per la teva pròpia còpia de seguretat, o descarregar material amb llicència lliure. El seu ús per obtenir obres protegides no ho és, i aquest curs no cobreix aquella part.

Un ús domèstic útil i sense ambigüitat: la còpia automàtica de les fotos del mòbil. Immich o Nextcloud, en Docker, substitueixen el servei de fotos al núvol per un de propi, i són potser el millor argument per tenir un servidor a casa.

Còpies de seguretat de la biblioteca i de la configuració

Aquí s'aplica exactament 05-08, i cal fer una distinció que estalvia diners:

Contingut Insubstituïble Estratègia
Fotos i vídeos familiars Sí, absolutament 3-2-1 complet, fora de casa
Documents escanejats Sí 3-2-1 complet
Configuració de Jellyfin (usuaris, vistos, llistes) Costosa de refer Còpia diària, petita
Música extreta de CD propis Recuperable amb esforç Còpia local; opcional fora
Pel·lícules de discos propis Recuperable amb molt d'esforç Només RAID; còpia si sobra espai

Copiar 6 TB de pel·lícules al núvol costa diners cada mes i no aporta gran cosa, perquè els discos originals continuen a la prestatgeria. Copiar 400 GB de fotos familiars fora de casa és innegociable, i costa poc. Distingir-ho és la decisió que fa el projecte sostenible.

#!/usr/bin/env bash
#
# copia_casa.sh - Copies del servidor multimedia domestic
#
# Estrategia:
#   - Insubstituible (fotos, documents) -> restic fora de casa
#   - Configuracio de Jellyfin          -> restic, diari
#   - Pel·licules i musica              -> instantania Btrfs local
#
set -euo pipefail

readonly SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/lib/comuns.sh"

readonly CASA_MULTIMEDIA="${CASA_MULTIMEDIA:-/srv/multimedia}"
readonly CASA_INSTANTANIES="${CASA_INSTANTANIES:-/srv/multimedia/.instantanies}"
export RESTIC_REPOSITORY="${RESTIC_REPOSITORY:-b2:elmeudomini-multimedia:/}"

umask 077

instantania_local() {
    local nom="${CASA_INSTANTANIES}/$(date +%F)"
    [[ -d "$nom" ]] && { log "la instantania d avui ja existeix"; return 0; }
    # Instantania de nomes lectura: no ocupa espai fins que alguna cosa canvia
    btrfs subvolume snapshot -r "$CASA_MULTIMEDIA" "$nom" \
        || morir 73 "no s ha pogut crear la instantania"
    log "instantania creada: $nom"

    # Retencio: conservar 7 diaries
    local sobrants
    mapfile -t sobrants < <(find "$CASA_INSTANTANIES" -maxdepth 1 -type d -name '20*' \
        | sort -r | tail -n +8)
    local s
    for s in "${sobrants[@]:-}"; do
        [[ -n "$s" ]] || continue
        btrfs subvolume delete "$s" && log "instantania antiga eliminada: $s"
    done
}

copia_externa() {
    requereix_comanda restic
    RESTIC_PASSWORD="$(pass casa/restic)" \
    restic backup \
        "${CASA_MULTIMEDIA}/fotos" \
        "${CASA_MULTIMEDIA}/documents" \
        /var/lib/jellyfin \
        /etc/jellyfin \
        --exclude-caches \
        --exclude '*/metadata/*' \
        --tag casa \
        || morir 74 "restic ha fallat"

    RESTIC_PASSWORD="$(pass casa/restic)" \
    restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 12 --prune
}

verificar() {
    # Una copia no verificada no es una copia (05-08)
    RESTIC_PASSWORD="$(pass casa/restic)" \
    restic check --read-data-subset=2% || morir 74 "la verificacio de restic ha fallat"
    log "verificacio correcta"
}

main() {
    requereix_comanda btrfs
    instantania_local
    copia_externa
    verificar
    log "copia completada"
}

main "$@"

Les instantànies de Btrfs mereixen una explicació, perquè són la protecció contra l'error humà que el RAID no dona. Una instantània és una còpia instantània de l'estat actual que no ocupa espai: comparteix tots els blocs amb l'original i només divergeix quan alguna cosa canvia. Si demà esborres una carpeta de fotos per error, és a /srv/multimedia/.instantanies/2026-08-18/ sense haver costat ni un byte.

$ sudo btrfs subvolume list /srv/multimedia
ID 258 gen 4412 top level 5 path .instantanies/2026-08-16
ID 259 gen 4488 top level 5 path .instantanies/2026-08-17
ID 260 gen 4501 top level 5 path .instantanies/2026-08-18

# Recuperar alguna cosa esborrada per error: es una simple copia
$ sudo cp -a /srv/multimedia/.instantanies/2026-08-17/fotos/2026-07 /srv/multimedia/fotos/

I l'advertiment: una instantània no és una còpia de seguretat. Viu als mateixos discos. Protegeix de l'error humà, no de la fallada del maquinari ni de l'incendi. Per això l'script fa les dues coses.

Consum, soroll i temperatura

Tres factors que en un centre de dades són problema d'algú altre i al menjador de casa teva són problema teu.

El cost elèctric anual

Cost anual = Potencia mitjana (W) x 8760 h / 1000 x preu del kWh
Equip Potència mitjana kWh/any Cost a 0,24 €/kWh
Raspberry Pi 5 + 1 SSD 7 W 61 15 €
Mini PC N100 + 2 HDD (parats) 13 W 114 27 €
Mini PC N100 + 2 HDD (actius) 24 W 210 50 €
NAS de 4 badies 28 W 245 59 €
PC reaprofitat 65 W 569 137 €
Servidor de segona mà 110 W 964 231 €

La diferència entre el mini PC i el PC reaprofitat són 110 € a l'any. Aquest és l'argument de l'apartat 3 convertit en diners.

$ sudo apt install powertop lm-sensors
$ sudo sensors-detect --auto >/dev/null

# Calibrar primer (triga uns minuts i mesura el consum de cada estat)
$ sudo powertop --calibrate
$ sudo powertop --auto-tune       # aplica els ajustos recomanats

$ sudo powertop --time=60 --csv=/tmp/energia.csv >/dev/null
$ grep -A6 'Power est' /tmp/energia.csv | head -8
The battery reports a discharge rate of 12.4 W
Usage       Events/s   Category       Description
  6.2 ms/s     14.1     Process        ffmpeg
  1.1 ms/s      8.4     Process        jellyfin

powertop --auto-tune activa l'estalvi d'energia d'USB, PCIe i SATA. Un avís: pot desactivar ports USB que estiguin en ús ocasional. Per fer-ho persistent, un servei de systemd, no un rc.local:

# /etc/systemd/system/powertop.service
[Unit]
Description=Ajustos d estalvi energetic de PowerTOP
After=multi-user.target

[Service]
Type=oneshot
ExecStart=/usr/sbin/powertop --auto-tune
RemainAfterExit=true

[Install]
WantedBy=multi-user.target

Aturar els discos quan no es fan servir

Un HDD de 3,5 polzades consumeix 6-9 W girant i 0,5 W parat. Amb dos discos, aturar-los quan ningú no mira res estalvia uns 30 € a l'any — i, sobretot, redueix el soroll de fons.

$ sudo apt install hdparm

# -B 127: gestio d energia agressiva pero SENSE aparcar el capcal a cada
#         moment (els valors < 128 permeten l aturada; 254 la desactiva)
# -S 120: aturar despres de 120 x 5 s = 10 minuts d inactivitat
$ sudo hdparm -B 127 -S 120 /dev/sda
# /etc/hdparm.conf — persistent entre reinicis
/dev/disk/by-id/ata-WDC_WD80EFPX-68C4ZN0_WD-CA0J1234 {
    apm = 127
    spindown_time = 120
}

El compromís cal entendre'l: cada arrencada des de parat desgasta el disc més que diverses hores girant, i afegeix 5-8 segons d'espera en obrir la biblioteca. Amb un temps d'aturada de 10 minuts i ús normal a les tardes, el balanç és favorable. Amb 5 minuts i algú que navega la biblioteca a estones, el disc arrenca i s'atura vint vegades per nit, i això n'escurça la vida. No baixis mai de 10 minuts.

Es fa servir /dev/disk/by-id/ i no /dev/sda per la mateixa raó que els UUID a fstab: els noms sdX canvien d'ordre entre arrencades.

Temperatura

$ sensors
coretemp-isa-0000
Package id 0:  +42.0°C  (high = +100.0°C, crit = +100.0°C)

$ sudo smartctl -A /dev/sda | grep -i temperature
194 Temperature_Celsius     0x0022   118   105   000    Old_age   Always  -  38
Component Ideal Acceptable Preocupant
CPU en repòs < 45 °C < 60 °C > 75 °C
CPU transcodificant < 70 °C < 85 °C > 90 °C
HDD 30-40 °C 25-45 °C > 50 °C o < 20 °C
SSD NVMe < 55 °C < 70 °C > 75 °C

Els estudis de fiabilitat a gran escala són clars: els discos durs duren menys per sobre de 45 °C i també per sota de 20 °C. En un armari tancat del menjador, sense ventilació, dos discos superen els 50 °C amb facilitat. Un ventilador de 120 mm a poques revolucions és pràcticament inaudible i baixa 8-10 °C.

Automatització amb Ansible

# ~/casa-infra/roles/multimedia/defaults/main.yml
---
multimedia_punt_muntatge: /srv/multimedia
multimedia_uuid: 8f3a2c11-4d5e-4a91-b7c2-1e9d0f8a3b45
multimedia_grup: multimedia
multimedia_gid: 1002
multimedia_xarxa_lan: 192.168.1.0/24
multimedia_xarxa_vpn: 10.8.0.0/24
multimedia_usuaris_samba: [marta, luis]
multimedia_accelerar_hw: true
multimedia_dispositiu_gpu: /dev/dri/renderD128
multimedia_hdparm_spindown: 120
multimedia_subdirectoris: [pellicules, series, musica, fotos, documents]
# ~/casa-infra/roles/multimedia/tasks/main.yml
---
- name: Instal·lar paquets base
  ansible.builtin.apt:
    name:
      - btrfs-progs
      - smartmontools
      - samba
      - hdparm
      - powertop
      - restic
    state: present
    update_cache: true
  tags: [paquets]

- name: Paquets d acceleracio per maquinari
  ansible.builtin.apt:
    name: [vainfo, intel-media-va-driver-non-free]
    state: present
  when: multimedia_accelerar_hw | bool
  tags: [gpu]

- name: Clau del diposit de Jellyfin a keyrings
  ansible.builtin.get_url:
    url: https://repo.jellyfin.org/jellyfin_team.gpg.key
    dest: /etc/apt/keyrings/jellyfin.asc
    mode: '0644'
  tags: [paquets]

- name: Diposit de Jellyfin
  ansible.builtin.deb822_repository:
    name: jellyfin
    types: deb
    uris: https://repo.jellyfin.org/ubuntu
    suites: "{{ ansible_distribution_release }}"
    components: main
    architectures: amd64
    signed_by: /etc/apt/keyrings/jellyfin.asc
  register: repo_jellyfin
  tags: [paquets]

- name: Instal·lar Jellyfin
  ansible.builtin.apt:
    name: jellyfin
    state: present
    update_cache: "{{ repo_jellyfin.changed }}"
  tags: [paquets]

# --- Emmagatzematge ---
- name: Comprovar que el sistema de fitxers existeix abans de muntar-lo
  ansible.builtin.command: "blkid -U {{ multimedia_uuid }}"
  register: blk
  changed_when: false
  failed_when: blk.rc != 0

- name: Muntar la biblioteca per UUID amb nofail
  ansible.posix.mount:
    path: "{{ multimedia_punt_muntatge }}"
    src: "UUID={{ multimedia_uuid }}"
    fstype: btrfs
    opts: defaults,compress=zstd:3,noatime,nofail,x-systemd.device-timeout=10
    state: mounted

- name: Grup de la biblioteca
  ansible.builtin.group:
    name: "{{ multimedia_grup }}"
    gid: "{{ multimedia_gid }}"
    state: present

- name: Estructura de directoris amb SGID
  ansible.builtin.file:
    path: "{{ multimedia_punt_muntatge }}/{{ item }}"
    state: directory
    owner: root
    group: "{{ multimedia_grup }}"
    mode: '2775'
  loop: "{{ multimedia_subdirectoris }}"

- name: Afegir jellyfin als grups necessaris
  ansible.builtin.user:
    name: jellyfin
    groups: "{{ [multimedia_grup] + (['render', 'video'] if multimedia_accelerar_hw else []) }}"
    append: true
  notify: Reiniciar jellyfin

- name: Drop-in d enduriment de jellyfin
  ansible.builtin.template:
    src: jellyfin-override.conf.j2
    dest: /etc/systemd/system/jellyfin.service.d/override.conf
    owner: root
    group: root
    mode: '0644'
  notify:
    - Recarregar systemd
    - Reiniciar jellyfin

- name: Configuracio de Samba
  ansible.builtin.template:
    src: smb.conf.j2
    dest: /etc/samba/smb.conf
    owner: root
    group: root
    mode: '0644'
    backup: true
    validate: 'testparm -s %s'    # el nginx -t de Samba (08-01)
  notify: Reiniciar samba

- name: Tallafocs: nomes LAN i VPN
  community.general.ufw:
    rule: allow
    src: "{{ item.0 }}"
    port: "{{ item.1 }}"
    proto: tcp
  loop: "{{ [multimedia_xarxa_lan, multimedia_xarxa_vpn] | product(['445', '8096']) | list }}"
  tags: [firewall]

- name: Temporitzadors de depuracio, SMART i copia
  ansible.builtin.copy:
    src: "{{ item }}"
    dest: "/etc/systemd/system/{{ item }}"
    mode: '0644'
  loop:
    - btrfs-scrub.service
    - btrfs-scrub.timer
    - copia-casa.service
    - copia-casa.timer
  notify: Recarregar systemd

- name: Activar els temporitzadors
  ansible.builtin.systemd:
    name: "{{ item }}"
    enabled: true
    state: started
    daemon_reload: true
  loop: [btrfs-scrub.timer, copia-casa.timer, smartd.service]

# --- Verificacio ---
- name: Verificar que l acceleracio per maquinari es visible
  ansible.builtin.command: "vainfo --display drm --device {{ multimedia_dispositiu_gpu }}"
  register: va
  changed_when: false
  failed_when: "'VAProfileH264High' not in va.stdout"
  when: multimedia_accelerar_hw | bool
  tags: [verificar]

- name: Verificar que Jellyfin respon
  ansible.builtin.uri:
    url: "http://127.0.0.1:8096/health"
    status_code: 200
  retries: 5
  delay: 3
  tags: [verificar]

Aquell validate: 'testparm -s %s' és el mateix patró que nginx -t -c %s a 08-01, i protegeix del mateix problema: una configuració invàlida que impedeix arrencar el servei. El patró es repeteix perquè és correcte, no perquè sigui una casualitat.

Operació: actualitzacions, salut dels discos i què fer quan un falla

Actualitzacions

# Actualitzacions de seguretat desateses (06-06)
$ sudo apt install unattended-upgrades
$ sudo dpkg-reconfigure -plow unattended-upgrades

Amb una diferència respecte a srv-tramontana: aquí sí que convé permetre el reinici automàtic, perquè no hi ha finestra de manteniment que negociar ni ningú a qui avisar.

# /etc/apt/apt.conf.d/50unattended-upgrades
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "05:30";
Unattended-Upgrade::Automatic-Reboot-WithUsers "false";

Automatic-Reboot-WithUsers "false" evita reiniciar si hi ha algú connectat — però no detecta algú que estigui veient una pel·lícula, així que les 05:30 són una elecció deliberada.

Jellyfin s'actualitza amb apt com qualsevol altre paquet. Abans d'una actualització major, instantània:

$ sudo btrfs subvolume snapshot -r /var/lib/jellyfin \
      /srv/multimedia/.instantanies/jellyfin-pre-$(date +%F)

Salut dels discos

$ sudo systemctl enable --now smartd

# Prova curta setmanal, llarga mensual, i avis per correu
$ sudo tee /etc/smartd.conf <<'EOF'
DEVICESCAN -a -o on -S on -n standby,q \
  -s (S/../.././02|L/../(01|15)/./03) \
  -W 4,45,50 \
  -m root -M exec /usr/share/smartmontools/smartd-runner
EOF
$ sudo systemctl restart smartd
Paràmetre Significat
-n standby,q No despertar un disc aturat per comprovar-lo
-s (S/../.././02|L/../(01|15)/./03) Prova curta diària a les 02:00; llarga els dies 1 i 15 a les 03:00
-W 4,45,50 Avisar si puja 4 °C de cop, si passa de 45 °C, crític a 50 °C

Els atributs que de debò prediuen una fallada, segons els estudis de fiabilitat a gran escala:

$ sudo smartctl -A /dev/sda | \
      awk '$1 ~ /^(5|187|188|197|198)$/ {printf "%-28s %s\n", $2, $10}'
Reallocated_Sector_Ct        0
Reported_Uncorrect           0
Command_Timeout              0
Current_Pending_Sector       0
Offline_Uncorrectable        0
Atribut Què indica Llindar d'acció
5 Reallocated_Sector_Ct Sectors malmesos reassignats > 0: vigilar. Creixent: canviar
197 Current_Pending_Sector Sectors sospitosos sense reassignar > 0: canviar aviat
198 Offline_Uncorrectable Sectors il·legibles > 0: canviar ja
187 Reported_Uncorrect Errors no corregibles > 0: vigilar
188 Command_Timeout Ordres esgotades Sol ser el cable, no el disc

Un disc amb Current_Pending_Sector creixent s'està morint, encara que SMART digui PASSED. El veredicte global de SMART és notòriament optimista: mira els atributs, no el resum.

Quan un disc falla

# El simptoma: errors al journal
$ sudo journalctl -k --since today | grep -iE 'ata[0-9]|I/O error|medium error'
kernel: ata2.00: exception Emask 0x0 SAct 0x0 SErr 0x0 action 0x0
kernel: blk_update_request: I/O error, dev sdb, sector 1928374656

$ sudo btrfs device stats /srv/multimedia
[/dev/sda].write_io_errs    0
[/dev/sda].read_io_errs     0
[/dev/sda].corruption_errs  0
[/dev/sdb].write_io_errs    142
[/dev/sdb].read_io_errs     2891
[/dev/sdb].corruption_errs  18

Procediment de substitució amb Btrfs RAID 1:

# 1. NO desmuntar ni apagar. Amb RAID 1 el sistema continua funcionant.
#    Primer, comprovar que hi ha copia del que es insubstituible.
$ RESTIC_PASSWORD=$(pass casa/restic) restic snapshots --tag casa | tail -3

# 2. Si el disc ENCARA respon: reemplacament en calent (l ideal).
#    Btrfs copia del disc vell el que pot i del mirall el que no.
$ sudo btrfs replace start -f /dev/sdb /dev/sdc /srv/multimedia
$ sudo btrfs replace status /srv/multimedia
0.8% done, 0 write errs, 0 uncorr. read errs

# 3. Si el disc JA NO respon: muntar en mode degradat i afegir-ne un de nou
$ sudo mount -o degraded,compress=zstd:3 UUID=8f3a2c11-... /srv/multimedia
$ sudo btrfs device add /dev/sdc /srv/multimedia
$ sudo btrfs device remove missing /srv/multimedia
$ sudo btrfs balance start -dconvert=raid1 -mconvert=raid1 /srv/multimedia

# 4. Verificar que s ha restablert la redundancia
$ sudo btrfs filesystem df /srv/multimedia
Data, RAID1: total=1.01TiB, used=1.00TiB
Metadata, RAID1: total=8.00GiB, used=6.12GiB

# 5. Depuracio completa per confirmar que tot esta integre
$ sudo btrfs scrub start -B /srv/multimedia

Tres avisos sobre aquest procediment:

  • btrfs replace és preferible a add + remove: és més ràpid i manté la redundància durant més temps del procés.
  • El mode degradat té un límit: amb RAID 1 de dos discos, una segona fallada durant la reconstrucció ho perd tot. És el moment de més risc del cicle de vida del conjunt, i la raó per la qual les còpies externes existeixen.
  • Substitueix el disc quan SMART avisa, no quan falla. Un reemplaçament en calent sobre un disc que encara llegeix és un tràmit; una reconstrucció des d'un disc mort és un risc.

La rutina domèstica

Freqüència Tasca Eina
Automàtic Actualitzacions de seguretat unattended-upgrades
Automàtic Còpia diària del que és insubstituïble copia_casa.sh + temporitzador
Automàtic Instantània diària Btrfs
Diari Prova SMART curta smartd
Setmanal (2 min) Revisar avisos i espai btrfs filesystem usage
Mensual (10 min) Depuració de Btrfs i prova SMART llarga Temporitzadors
Trimestral (20 min) Restaurar un fitxer de la còpia restic restore
Anual Revisar pols, ventiladors i temperatures Físicament

La fila trimestral és la mateixa de 08-02 i per la mateixa raó: una còpia que no s'ha restaurat mai és un fitxer del qual suposem coses. A casa s'incompleix encara més que a la feina.

Errors Comuns i Consells

  • Creure que el RAID és una còpia de seguretat. Protegeix de la fallada d'un disc i de res més: ni d'esborrats, ni de xifradors, ni d'incendis.
  • Fer servir Btrfs en RAID 5 o 6. Encara es consideren inestables. RAID 1, o mdadm si necessites paritat.
  • Muntar sense nofail. Un disc desconnectat deixa l'equip en mode emergència, sense xarxa ni SSH, i cal anar-hi amb teclat i monitor.
  • Muntar per /dev/sdX. L'ordre canvia entre arrencades. Sempre UUID.
  • No comprovar l'acceleració per maquinari abans de comprar. És el que decideix si el servidor serveix. La Pi 5 no té descodificador H.265.
  • Oblidar DeviceAllow a la unitat endurida. L'usuari és al grup render i tot i així no veu la GPU, i el desconcert dura hores.
  • No posar ReadOnlyPaths sobre la biblioteca. És una línia que impedeix que una fallada de l'aplicació esborri vint anys de fotos.
  • Permetre convidats a Samba. map to guest = never. L'accés anònim és la causa de la majoria dels incidents.
  • Deixar SMB1 actiu. Està trencat per disseny; és per on es va propagar WannaCry.
  • NFS sense root_squash. Qualsevol amb root en un portàtil de la xarxa té root sobre els teus fitxers.
  • Publicar ports amb Docker creient que ufw els tapa. Docker escriu les seves pròpies regles i se les salta. Fes servir 127.0.0.1:port:port.
  • Exposar Jellyfin directament a Internet. Una vulnerabilitat seva es converteix en una vulnerabilitat de casa teva. Fes servir la VPN de 08-04.
  • Posar el temps d'aturada dels discos per sota de 10 minuts. Arrenquen i s'aturen vint vegades per nit, i això els mata abans.
  • Fiar-se del veredicte PASSED de SMART. És optimista. Mira els atributs 5, 197 i 198.
  • Ficar el servidor en un armari tancat. Per sobre de 45 °C els discos duren significativament menys.
  • Copiar 6 TB de pel·lícules al núvol. Costa diners cada mes i els discos originals continuen a la prestatgeria. Copia el que és insubstituïble.
  • Consell de mètode. Tracta aquest servidor amb el mateix rigor que el de la feina —Ansible, còpies verificades, unitats endurides— i hi guanyaràs dues coses: un servidor que dura, i un laboratori on practicar sense risc.

Exercicis

Exercici 1

Un familiar es queixa que les pel·lícules «es tallen» al televisor del menjador, però es veuen perfectament a la tauleta. Diagnostica el problema de principi a fi i resol-lo.

Exercici 2

Dissenya l'estratègia de còpies completa per a un servidor domèstic amb 6 TB ocupats: 400 GB de fotos i documents, 800 GB de música extreta de CD propis i 4,8 TB de pel·lícules de discos comprats. Justifica cada decisió amb el seu cost.

Exercici 3

Escriu un script revisio_casa.sh amb les convencions del curs que comprovi la salut del servidor domèstic i retorni 0, 1 o 2.

Solucions

Solució 1

Diagnòstic pas a pas. La dada de partida és valuosa: el mateix contingut va bé en un dispositiu i malament en un altre, per tant el problema no és al fitxer ni al disc, sinó a la relació entre el fitxer i aquell client concret.

# 1. Esta transcodificant? El tauler de Jellyfin ho diu, i tambe aixo:
$ ps -eo pid,pcpu,args | grep '[f]fmpeg' | head -1
  8814 191.2 /usr/lib/jellyfin-ffmpeg/ffmpeg -i /srv/multimedia/pellicules/...
    -c:v libx264 -preset veryfast -b:v 3000000 ...

191,2 % de CPU i -c:v libx264: està transcodificant per programari, saturant els dos nuclis. Aquí hi ha la causa immediata dels talls.

# 2. Per que transcodifica? Analitzar el fitxer
$ ffprobe -v error -show_entries stream=index,codec_type,codec_name,width,height,channels \
      -of csv=p=0 "/srv/multimedia/pellicules/Pellicula (2019)/Pellicula (2019).mkv"
0,video,hevc,3840,2160
1,audio,dts,8
2,subtitle,hdmv_pgs_subtitle

# 3. Que admet el televisor? Al registre de Jellyfin, despres de reproduir:
$ sudo journalctl -u jellyfin --since "10 min ago" | grep -i 'transcod\|reason'
[INF] Transcoding reason: VideoCodecNotSupported, AudioCodecNotSupported,
      SubtitleCodecNotSupported

Tres motius acumulats, i cal atacar-los per separat:

Motiu Detall Solució
VideoCodecNotSupported HEVC 4K; el televisor només fa H.264 1080p Acceleració per maquinari o segona versió
AudioCodecNotSupported DTS 7.1; el televisor fa AC3 5.1 Transcodificar només l'àudio (barat)
SubtitleCodecNotSupported PGS: subtítol gràfic que cal cremar sobre la imatge Extreure'l a .srt extern

I la comprovació que descarta l'altre sospitós habitual:

# 4. Es la xarxa? Wi-Fi del menjador enfront de cable
$ iperf3 -c 192.168.1.45 -t 10 -R
[  5]   0.00-10.00  sec  58.2 MBytes  48.8 Mbits/sec

48,8 Mbit/s basten per a un flux de 1080p (uns 10 Mbit/s) però no per a 4K sense convertir (40-80 Mbit/s). Així que la xarxa és un factor secundari real: encara que arreglèssim els còdecs, el 4K per aquell Wi-Fi aniria just.

Resolució, en quatre mesures ordenades per relació benefici/esforç:

# --- MESURA 1: activar l acceleracio per maquinari (impacte immediat) ---
$ vainfo --display drm --device /dev/dri/renderD128 | grep -c EncSlice
6
$ sudo usermod -aG render,video jellyfin
$ sudo systemctl restart jellyfin
# I al tauler: Reproduccio > VAAPI, /dev/dri/renderD128,
# marcant HEVC, HEVC 10 bits, VP9 i "codificacio HEVC"

# Verificar l efecte
$ ps -eo pcpu,args | grep '[f]fmpeg' | awk '{print $1}'
5.8
$ sudo intel_gpu_top -s 1000 | grep Video
 Video      74.12% |██████████████   |

De 191 % de CPU a 5,8 %. Els talls desapareixen immediatament.

# --- MESURA 2: extreure els subtitols a un fitxer extern ---
# Els PGS grafics forcen la transcodificacio de VIDEO encara que el
# codec sigui compatible: cal "cremar-los" sobre la imatge. Els .srt
# s envien a part i el client els dibuixa.
$ ffmpeg -i "Pellicula (2019).mkv" -map 0:s:0 -c:s srt "Pellicula (2019).ca.srt"
# --- MESURA 3: convertir nomes l audio problematic, sense tocar el video ---
# -c:v copy no recodifica el video: son segons, no hores, i sense
# cap perdua de qualitat d imatge.
$ ffmpeg -i entrada.mkv -c:v copy -c:s copy \
      -c:a ac3 -b:a 640k -ac 6 sortida.mkv
# --- MESURA 4: cable Ethernet o PLC fins al menjador ---
$ iperf3 -c 192.168.1.45 -t 10 -R
[  5]   0.00-10.00  sec  1.09 GBytes  938 Mbits/sec

El resultat, mesurat abans i després:

Mètrica Abans Després
CPU d'ffmpeg 191 % 5,8 %
Talls en 30 min de reproducció 14 0
Fluxos simultanis possibles 1, amb talls 3-4
Consum durant la reproducció 48 W 17 W
Reproducció directa No Sí, si hi ha versió 1080p H.264

I la lliçó de fons, que va més enllà d'aquest cas: la millor transcodificació és la que no passa. Per a una biblioteca amb clients heterogenis, l'estratègia definitiva és guardar dues versions del material important —l'original 4K HEVC per al televisor modern i una versió 1080p H.264 amb àudio AC3 per a tota la resta—, que Jellyfin ofereix automàticament. Costa un 30 % més d'espai i elimina la conversió per complet.

Solució 2

El principi que organitza tota l'estratègia: no tot el contingut val el mateix. Copiar-ho tot amb el mateix criteri és car i, paradoxalment, sol acabar en què no es copia res.

Categoria Volum Es pot recuperar? Cost de recuperar-ho Valor
Fotos i documents 400 GB Mai Infinit Màxim
Música de CD propis 800 GB Sí, extraient-la de nou ~40 h de feina Mitjà
Pel·lícules de discos propis 4,8 TB Sí, copiant-los de nou ~200 h de feina Baix
Configuració de Jellyfin 2 GB Sí, reconfigurant ~4 h Mitjà

Estratègia per nivells, aplicant 3-2-1 on toca:

NIVELL 1 - Insubstituible (402 GB): fotos, documents, configuracio
  Copia 1: Btrfs RAID 1 local (l original)
  Copia 2: disc extern USB xifrat, mensual, en un calaix
  Copia 3: restic al nuvol, diaria, xifrada d extrem a extrem
  -> 3 copies, 2 suports, 1 fora de casa. 3-2-1 complet.

NIVELL 2 - Recuperable amb esforc (800 GB): musica
  Copia 1: Btrfs RAID 1 local
  Copia 2: el mateix disc extern USB, mensual
  -> Sense copia al nuvol: 800 GB al mes no compensen 40 h de feina.

NIVELL 3 - Recuperable (4,8 TB): pel·licules
  Copia 1: Btrfs RAID 1 local
  Copia 2: els discos originals a la prestatgeria (ja la tens)
  -> Sense copia addicional. El RAID cobreix la fallada d un disc i els
     originals cobreixen la resta.

El càlcul econòmic, que és l'argument:

Estratègia Volum al núvol Cost anual (a 0,005 €/GB/mes) Comentari
Copiar-ho tot 6.000 GB 360 €/any Insostenible a casa
Només el nivell 1 402 GB 24 €/any Recomanat
Res al núvol 0 GB 0 € Un incendi s'ho emporta tot

Vint-i-quatre euros a l'any protegeixen el que és irreemplaçable. Tres-cents seixanta protegirien a més unes pel·lícules que són a la prestatgeria. La diferència entre totes dues xifres és la raó per la qual molta gent acaba sense cap còpia: intenten copiar-ho tot, veuen el preu i abandonen.

Implementació:

# --- Nivell 1: restic al nuvol, diari ---
$ export RESTIC_REPOSITORY="b2:elmeudomini-multimedia:/"
$ restic init                    # una sola vegada

# La frase de pas, a pass (06-05). Si es perd, la copia es
# IL·LEGIBLE: no hi ha recuperacio possible. Va tambe en paper, a casa
# d un familiar, dins d un sobre tancat.
$ pass generate casa/restic 40
# --- Nivells 1 i 2: disc extern USB xifrat, mensual ---
# LUKS (06-05): si perds el disc, les dades no son llegibles
$ sudo cryptsetup luksFormat /dev/sdd1
$ sudo cryptsetup open /dev/sdd1 copia-externa
$ sudo mkfs.btrfs -L copia /dev/mapper/copia-externa

# Copia mensual. --delete es fa servir AMB CURA: si l origen esta
# desmuntat, rsync esborraria el desti sencer. La comprovacio previa
# no es opcional.
$ mountpoint -q /srv/multimedia || { echo "origen no muntat; avortant"; exit 1; }
$ sudo rsync -aHAX --delete --info=progress2 \
      /srv/multimedia/{fotos,documents,musica}/ /mnt/copia/
$ sudo umount /mnt/copia && sudo cryptsetup close copia-externa

Verificació, sense la qual res de tot l'anterior compta:

# Automatic, setmanal
$ restic check --read-data-subset=5%

# Manual, trimestral: restaurar de debo un fitxer i OBRIR-LO
$ restic restore latest --include '/srv/multimedia/fotos/2019' --target /tmp/prova
$ sha256sum /tmp/prova/srv/multimedia/fotos/2019/IMG_4412.jpg \
            /srv/multimedia/fotos/2019/IMG_4412.jpg
a3f1...  /tmp/prova/srv/multimedia/fotos/2019/IMG_4412.jpg
a3f1...  /srv/multimedia/fotos/2019/IMG_4412.jpg

La taula de recuperació, que és el que cal tenir escrit i guardat fora del servidor:

Escenari Què es perd D'on es recupera Temps
Falla un disc Res RAID 1: reemplaçament en calent 6-12 h de reconstrucció
Esborres una carpeta Res Instantània Btrfs del dia 2 minuts
Es corromp un fitxer Res Btrfs el repara sol a la depuració Automàtic
Un xifrador ataca l'equip Nivell 3 Núvol (nivell 1) + USB (1 i 2) + discos originals 2-3 dies
Incendi o robatori Nivells 2 i 3 Núvol: només el nivell 1 1-2 dies per a 400 GB

Aquella última fila és el moment de la veritat de tota l'estratègia, i és honesta: en un incendi es perden les pel·lícules i la música. S'accepta conscientment, perquè els discos originals probablement també cremin i el cost d'evitar-ho són 336 € a l'any. El que és important —les fotos de la família— sobreviu, que és exactament per al que es va dissenyar.

Solució 3

#!/usr/bin/env bash
#
# revisio_casa.sh - Revisio de salut del servidor multimedia domestic
#
# Codis de sortida:
#   0 = correcte   1 = avis   2 = critic
#
# Pensat per executar-se des d un temporitzador diari, amb silenci si tot
# va be: nomes un estat != 0 produeix notificacio.
#
set -euo pipefail

readonly SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/lib/comuns.sh"

readonly CASA_MULTIMEDIA="${CASA_MULTIMEDIA:-/srv/multimedia}"
readonly CASA_DISCS="${CASA_DISCS:-/dev/sda /dev/sdb}"
readonly CASA_LLINDAR_ESPAI="${CASA_LLINDAR_ESPAI:-85}"
readonly CASA_LLINDAR_TEMP="${CASA_LLINDAR_TEMP:-45}"
readonly CASA_LLINDAR_COPIA_H="${CASA_LLINDAR_COPIA_H:-36}"
readonly CASA_LLINDAR_SCRUB_D="${CASA_LLINDAR_SCRUB_D:-45}"

umask 027
estat_global=0

registrar() {
    local nivell="$1" msg="$2"
    case "$nivell" in
        ok)     log "OK      $msg" ;;
        avis)   error "AVIS    $msg"; (( estat_global < 1 )) && estat_global=1 ;;
        critic) error "CRITIC  $msg"; estat_global=2 ;;
    esac
    return 0
}

comprovar_muntatge() {
    if ! mountpoint -q "$CASA_MULTIMEDIA"; then
        registrar critic "$CASA_MULTIMEDIA NO esta muntat"
        return 0
    fi
    registrar ok "biblioteca muntada"

    # Amb 'nofail', un disc absent NO impedeix arrencar: cal comprovar
    # explicitament que hi son els dos dispositius.
    local n
    n="$(btrfs filesystem show "$CASA_MULTIMEDIA" | grep -c '^\s*devid')"
    if (( n < 2 )); then
        registrar critic "nomes $n dispositiu(s): el RAID 1 esta DEGRADAT"
    else
        registrar ok "RAID 1 amb $n dispositius"
    fi
}

comprovar_espai() {
    mountpoint -q "$CASA_MULTIMEDIA" || return 0
    # 'df' menteix amb Btrfs en RAID: es fa servir la sortida propia de btrfs
    local lliure_b total_b usat_pct
    lliure_b="$(btrfs filesystem usage -b "$CASA_MULTIMEDIA" | \
        awk '/Free \(estimated\)/{gsub(/[^0-9]/,"",$3); print $3}')"
    total_b="$(btrfs filesystem usage -b "$CASA_MULTIMEDIA" | \
        awk '/Device size/{gsub(/[^0-9]/,"",$3); print $3/2}')"
    usat_pct=$(( 100 - (lliure_b * 100 / total_b) ))

    if (( usat_pct >= 95 )); then
        registrar critic "biblioteca al ${usat_pct}% ($(formatar_bytes "$lliure_b") lliures)"
    elif (( usat_pct >= CASA_LLINDAR_ESPAI )); then
        registrar avis "biblioteca al ${usat_pct}%"
    else
        registrar ok "espai al ${usat_pct}% ($(formatar_bytes "$lliure_b") lliures)"
    fi
}

comprovar_errors_btrfs() {
    mountpoint -q "$CASA_MULTIMEDIA" || return 0
    local total
    total="$(btrfs device stats "$CASA_MULTIMEDIA" | awk '{s+=$2} END {print s+0}')"
    if (( total > 0 )); then
        registrar critic "btrfs acumula $total errors d E/S o corrupcio"
        btrfs device stats "$CASA_MULTIMEDIA" | awk '$2>0 {print "         " $0}' >&2
    else
        registrar ok "sense errors d E/S ni corrupcio"
    fi
}

comprovar_scrub() {
    mountpoint -q "$CASA_MULTIMEDIA" || return 0
    local data dies
    data="$(btrfs scrub status "$CASA_MULTIMEDIA" | \
        awk -F': ' '/Scrub started/{print $2}')" || true
    if [[ -z "${data:-}" ]]; then
        registrar avis "no consta cap depuracio executada"
        return 0
    fi
    dies=$(( ( $(date +%s) - $(date -d "$data" +%s) ) / 86400 ))
    if (( dies > CASA_LLINDAR_SCRUB_D )); then
        registrar avis "ultima depuracio fa $dies dies"
    else
        registrar ok "ultima depuracio fa $dies dies"
    fi
}

comprovar_discs() {
    requereix_comanda smartctl
    local d
    for d in $CASA_DISCS; do
        [[ -b "$d" ]] || { registrar critic "$d no existeix"; continue; }

        # -n standby: NO despertar un disc aturat nomes per mirar-lo
        local sortida
        sortida="$(smartctl -A -H -n standby "$d" 2>/dev/null)" || true
        if grep -q 'STANDBY' <<<"$sortida"; then
            registrar ok "$d en repos (no es desperta)"
            continue
        fi

        # Els atributs que prediuen fallades, no el veredicte global
        local realloc pendents illegibles temp
        realloc="$(awk '$1==5   {print $10+0}' <<<"$sortida")"
        pendents="$(awk '$1==197 {print $10+0}' <<<"$sortida")"
        illegibles="$(awk '$1==198 {print $10+0}' <<<"$sortida")"
        temp="$(awk '$1==194 {print $10+0}' <<<"$sortida")"

        if (( ${illegibles:-0} > 0 || ${pendents:-0} > 0 )); then
            registrar critic "$d: $pendents pendents, $illegibles illegibles: SUBSTITUIR"
        elif (( ${realloc:-0} > 0 )); then
            registrar avis "$d: $realloc sectors reassignats: vigilar"
        else
            registrar ok "$d sense sectors malmesos"
        fi

        if (( ${temp:-0} >= 50 )); then
            registrar critic "$d a ${temp}C"
        elif (( ${temp:-0} >= CASA_LLINDAR_TEMP )); then
            registrar avis "$d a ${temp}C (ideal < 45)"
        fi
    done
}

comprovar_serveis() {
    local s
    for s in jellyfin smbd smartd; do
        systemctl is-active --quiet "$s" \
            && registrar ok "$s actiu" \
            || registrar critic "$s NO esta actiu"
    done

    # Que respongui, no nomes que el proces existeixi
    if curl -sf -m 5 -o /dev/null http://127.0.0.1:8096/health; then
        registrar ok "jellyfin respon"
    else
        registrar critic "jellyfin no respon al 8096"
    fi
}

comprovar_copies() {
    local marca=/var/lib/copia-casa/ultima-correcta
    if [[ ! -f "$marca" ]]; then
        registrar critic "no hi ha constancia de cap copia correcta"
        return 0
    fi
    local hores
    hores=$(( ( $(date +%s) - $(stat -c %Y "$marca") ) / 3600 ))
    if (( hores > CASA_LLINDAR_COPIA_H * 2 )); then
        registrar critic "ultima copia correcta fa ${hores} h"
    elif (( hores > CASA_LLINDAR_COPIA_H )); then
        registrar avis "ultima copia correcta fa ${hores} h"
    else
        registrar ok "ultima copia correcta fa ${hores} h"
    fi
}

comprovar_acceleracio() {
    [[ -e /dev/dri/renderD128 ]] || { registrar avis "sense GPU accessible"; return 0; }
    if vainfo --display drm --device /dev/dri/renderD128 2>/dev/null | \
            grep -q VAEntrypointEncSlice; then
        registrar ok "acceleracio per maquinari disponible"
    else
        registrar avis "la GPU no exposa codificacio per maquinari"
    fi
}

main() {
    requereix_comanda btrfs
    requereix_comanda curl

    comprovar_muntatge
    comprovar_espai
    comprovar_errors_btrfs
    comprovar_scrub
    comprovar_discs
    comprovar_serveis
    comprovar_copies
    comprovar_acceleracio

    case "$estat_global" in
        0) log "servidor multimedia correcte" ;;
        1) error "revisio amb AVISOS" ;;
        2) error "revisio en estat CRITIC" ;;
    esac
    return "$estat_global"
}

main "$@"
$ shellcheck ~/scripts/revisio_casa.sh && echo "sense avisos"
sense avisos

$ ~/scripts/revisio_casa.sh; echo "estat: $?"
[2026-08-18 08:00:03] OK      biblioteca muntada
[2026-08-18 08:00:03] OK      RAID 1 amb 2 dispositius
[2026-08-18 08:00:03] OK      espai al 28% (6,3 TiB lliures)
[2026-08-18 08:00:04] OK      sense errors d E/S ni corrupcio
[2026-08-18 08:00:04] OK      ultima depuracio fa 12 dies
[2026-08-18 08:00:04] OK      /dev/sda en repos (no es desperta)
[2026-08-18 08:00:04] OK      /dev/sdb en repos (no es desperta)
[2026-08-18 08:00:05] OK      jellyfin actiu
[2026-08-18 08:00:05] OK      smbd actiu
[2026-08-18 08:00:05] OK      smartd actiu
[2026-08-18 08:00:05] OK      jellyfin respon
[2026-08-18 08:00:05] OK      ultima copia correcta fa 6 h
[2026-08-18 08:00:06] OK      acceleracio per maquinari disponible
[2026-08-18 08:00:06] servidor multimedia correcte
estat: 0

Cinc decisions de disseny que mereixen justificació:

  1. -n standby a smartctl. Sense aquella opció, la revisió diària desperta els discos cada matí, anul·lant l'estalvi d'energia de l'apartat 13 i afegint un cicle d'arrencada diari a cada disc. Un script de vigilància que degrada el que vigila és un mal script.
  2. Comprovar el nombre de dispositius, no només el muntatge. Amb nofail, un disc desconnectat no impedeix arrencar i el sistema funciona amb normalitat, degradat i sense redundància. És una fallada silenciosa: sense aquesta comprovació, te n'assabentes el dia que falla el segon.
  3. No fer servir df amb Btrfs en RAID. Dona xifres enganyoses perquè no entén que cada bloc s'escriu dues vegades. Es fa servir btrfs filesystem usage -b.
  4. Els atributs SMART, no el veredicte. smartctl -H diu PASSED en discos que fallaran la setmana vinent. Els atributs 5, 197 i 198 són els que prediuen.
  5. Una marca de fitxer per a les còpies, escrita per copia_casa.sh només després de verificar. Comprovar que el temporitzador es va executar no val: es va poder executar i fallar. El que importa és que hi va haver una còpia correcta.
# /etc/systemd/system/revisio-casa.timer
[Unit]
Description=Revisio diaria del servidor multimedia

[Timer]
OnCalendar=*-*-* 08:00:00
Persistent=true

[Install]
WantedBy=timers.target
# /etc/systemd/system/revisio-casa.service
[Unit]
Description=Revisio de salut del servidor multimedia
# Notificar NOMES si falla: silenci si tot va be
OnFailure=notificar-casa@%n.service

[Service]
Type=oneshot
User=operador
ExecStart=/home/operador/scripts/revisio_casa.sh

Amb Type=oneshot i sense redireccions, la sortida va al journal i només un codi diferent de zero dispara OnFailure. És la convenció de «silenci si tot va bé» del curs, i a casa importa encara més: un correu diari que diu «tot bé» es deixa de llegir en dues setmanes, i amb ell es deixen de llegir els que sí que importen. És la fatiga d'alertes de 08-06, en versió domèstica.

Conclusió

Has construït un servidor complet des de zero, i pel camí has comprovat la resposta a la pregunta amb què començava la lliçó: no hi havia res específic de «servidors d'empresa». Vas muntar per UUID amb nofail perquè a 05-04 vas aprendre que un disc absent et pot deixar sense accés remot. Vas posar la clau del dipòsit a keyrings perquè a 05-03 vas aprendre que apt-key està obsolet. Vas endurir la unitat de systemd fins a baixar l'exposició a 2,4, amb un ReadOnlyPaths sobre la biblioteca que impedeix que una fallada de l'aplicació esborri vint anys de fotos. Vas validar la configuració de Samba amb testparm -s abans d'aplicar-la, que és el mateix nginx -t de 08-01 amb un altre nom. I ho vas deixar tot a Ansible, reconstruïble com srv-tramontana.

Has pres decisions justificades en lloc de copiar receptes. Btrfs en RAID 1 en comptes de mdadm, perquè les sumes de verificació detecten i reparen la corrupció silenciosa que un RAID clàssic propaga a les còpies — i ho has vist funcionar en una depuració que va corregir dos blocs. Jellyfin en comptes de Plex, perquè no depèn del núvol de ningú i la seva acceleració per maquinari no és de pagament. La VPN en comptes d'exposar el servei a Internet, perquè una vulnerabilitat de Jellyfin no ha de ser una vulnerabilitat de casa teva. I una estratègia de còpies per nivells que protegeix el que és insubstituïble per 24 € a l'any en lloc d'intentar protegir-ho tot per 360 i acabar sense protegir res.

També has après dues coses que la feina no t'ensenya. Que el consum elèctric és una decisió d'arquitectura: 110 € a l'any de diferència entre un mini PC i un PC reaprofitat, que en tres anys paga l'equip sencer. I que el RAID cobreix exactament una amenaça de les set de la taula, mentre que les instantànies de Btrfs, les còpies verificades i els discos originals a la prestatgeria cobreixen les altres sis. Amb la frase que cal endur-se: el RAID protegeix de la fallada d'un disc i de res més.

A 08-04 muntes el servidor VPN amb WireGuard que aquesta lliçó ha recomanat dues vegades. Entendràs per què WireGuard és una interfície de xarxa i no un dimoni, per què AllowedIPs és encaminament i control d'accés alhora —el concepte que més es malinterpreta de tota l'eina—, i com decidir entre túnel dividit i túnel complet amb criteri en lloc de per costum. En acabar tindràs accés a la xarxa interna de Tramontana des de qualsevol lloc sense exposar ni un sol port TCP, podràs tancar PostgreSQL i el tauler d'estadístiques a l'exterior perquè ja no caldran oberts, i de passada la teva biblioteca de casa serà abastable des de l'hotel amb un sol port UDP obert al router. I veuràs per què una VPN, malgrat tot el que té de bo, no converteix una xarxa interna en una xarxa segura.

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