Contenerizando a Jellyfin en un Jail

La idea de usar miniDLNA se queda muy muy corta
  1. La interface que nos ofrece muy poco intuitiva.

  2. Cada rescan reiniciaba las películas en plena reproducción. 🤢

  3. El protocolo DLNA no es compatible con todos los Smart-TV. 🤮

El tema central será Jellyfin, pero se necesita tocar varios puntos primero.

Resulta que freeBSD tiene algo muy potente, siendo nativo de el llamado jails (prisiones, cárcel) para contenerizar nuestras aplicaciones, muy parecido a docker, pero más maduro, dando un método de contenerización lijera, a veces llamado virtualización a nivel de OS(Sistema operativo), un jail normalmente contiene un sistema operativo completo en una zona de usuario que corre sobre el Sistema FreeBSD.

Los jails, no tienen su propio kernel, sino, que corren en una porción del kernel del host, el host puede controlar el jail, por medio de ciertos comandos sin entrar en el jail, o correr procesos dentro del jail si se prefiere.

La cuenta root del jail puede controlar todo dentro del jail, pero no fuera de el, cada uno de ellos posee una dirección ip dedicada.

Procesos del Jail o JID

xigmanas: ~# jls (1)
   JID  IP Address      Hostname                      Path
     1                  adguardhome                   /mnt/pool/extensions/bastille/jails/adguardhome/root
     2  192.168.1.248   jdownloader                   /mnt/pool/extensions/bastille/jails/jdownloader/root
     4                  nextcloud                     /mnt/pool/extensions/bastille/jails/nextcloud/root
     5                  reverse-proxy                 /mnt/pool/extensions/bastille/jails/reverse-proxy/root
     6                  jellyfin                      /mnt/pool/extensions/bastille/jails/jellyfin/root
1 Con el comando jls visualizamos nuestros jails, el JID es como un process ID, cada JID cambia una vez que el jail inicie.

Usando Bastille

35433989?s=200&v=4

Bastille para administrar nuestros jails con simples comandos, este lo instale desde la interface del xigmanas, no me toco probar desde la consola, pero si fue necesario tener primero a OBI(one button installer). para instalarlo desde ese plugin.

ObiConBastille

Subcommands

La lista de comandos de bastille es un poco variada subcommands.

Podemos instalar todo nuestros jails via web o por consola

todosLosJails

Jails.conf, fstab

Cada Jail tiene 2 ficheros importantes, por ejemplo:

  • jail.conf

  • fstab

Más info al respecto jail-config

Las rutas de ambos ficheros, estan el dataset donde esta bastille, en mi caso llamado pool

/mnt/pool/extensions/bastille/jails/jdownloader/jail.conf
/mnt/pool/extensions/bastille/jails/jdownloader/fstab

Si queremos entrar dentro de un jail usariamos el siguiente comando.

bastille console Jdownloader (1)
1 comando que permite entrar al jail llamado Jdownloader

Dirección ip del jail

Si queremos asignar una dirección ip a nuestro jail bien sea estatica o dinámica, nos vamos al etc/rc.conf

Donde tenemos la ruta siguiente de dicho fichero

/mnt/pool/extensions/bastille/jails/jellyfin-clone/root/etc/rc.conf
Una de las dos líneas se usara, estática o dinámica.
syslogd_flags="-ss"
sendmail_enable="NO"
sendmail_submit_enable="NO"
sendmail_outbound_enable="NO"
sendmail_msp_queue_enable="NO"
cron_flags="-J 60"
ifconfig_e0b_bastille4_name="vnet0"
ifconfig_vnet0="inet 192.168.1.247/24" (1)
#ifconfig_vnet0="SYNCDHCP" (2)
defaultrouter="192.168.1.1"
jellyfinserver_enable="TRUE"
jellyfinserver_user="jellyfin"
jellyfinserver_group="jellyfin"
1 Se asigna a este jail una dirección ip estática dentro del pool DHCP de mi red local, con subnet /24
2 Se usa SYNCDHCP y esto asignará una dirección ip dinámica, justo como lo comenta aquí el código fuente

El código del enlace anterior.

# If 0.0.0.0 set DHCP, else set static IP address
if [ "${IP}" == "0.0.0.0" ]; then
    sysrc -f "${bastille_jail_rc_conf}" ifconfig_vnet0="SYNCDHCP"
else
    sysrc -f "${bastille_jail_rc_conf}" ifconfig_vnet0="inet ${IP}"
fi

Bug al clonar jail con bastille

El bug no es nada complejo de resolver, solo que se debe fixear la vnet correctamente, solo cambiar y ya, porque cuando clona, copia la interface del jail original sin escribir la nueva interface del jail clonado entonces:

En nuestro jails.conf

jellyfin-clone {
  devfs_ruleset = 13;
  enforce_statfs = 2;
  exec.clean;
  exec.consolelog = /mnt/pool/extensions/bastille/logs/jellyfin-clone_console.log;
  exec.start = '/bin/sh /etc/rc';
  exec.stop = '/bin/sh /etc/rc.shutdown';
  host.hostname = jellyfin-clone;
  mount.devfs;
  mount.fstab = /mnt/pool/extensions/bastille/jails/jellyfin-clone/fstab;
  path = /mnt/pool/extensions/bastille/jails/jellyfin-clone/root;
  securelevel = 2;

  vnet;
  vnet.interface = e0b_bastille5; (1)
  exec.prestart += "jib addm bastille5 igb0"; (2)
  exec.prestart += "ifconfig e0a_bastille5 description \"vnet host interface for Bastille jail jellyfin-clone\""; (3)
  exec.poststop += "jib destroy bastille5"; (4)
  allow.mlock;
  allow.raw_sockets;
}
1 Nuestra inteface es e0b_bastille5, y debe estar también en las siguentes líneas.
2 Esta línea la cambie porque antes estaba la interface bastille1 del jail original
3 Se usa bastille5
4 Se usa bastille5 también.

Jail no arranca?

Al clonar, por lo visto el fstab toca actualizar los nombres correctos al nuevo nombre del jail.
xigmanas: ~# bastille start jellyfin-clone (1)
[jellyfin-clone]:
(2)
mount_nullfs: /mnt/pool/extensions/bastille/jails/jellyfin/root/mnt/series: Resource deadlock avoided
jail: jellyfin-clone: /sbin/mount -t nullfs -o rw /mnt/pool_2/series /mnt/pool/extensions/bastille/jails/jellyfin/root/mnt/series: failed

xigmanas: ~# bastille start jellyfin-clone (3)
[jellyfin-clone]:
e0a_bastille5
e0b_bastille5
jellyfin-clone: created

xigmanas: ~# bastille console jellyfin-clone
[jellyfin-clone]:
root@jellyfin-clone:~ # ping google.com
PING google.com (142.250.201.78): 56 data bytes
64 bytes from 142.250.201.78: icmp_seq=0 ttl=116 time=5.877 ms
64 bytes from 142.250.201.78: icmp_seq=1 ttl=116 time=8.838 ms
64 bytes from 142.250.201.78: icmp_seq=2 ttl=116 time=8.711 ms
^C
--- google.com ping statistics ---
3 packets transmitted, 3 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 5.877/7.809/8.838/1.367 ms
1 Iniciamos nuestro jail y vemos que en la consola arroja un error justo en el fstab.
2 El jail clonado tiene un nombre incorrecto apuntando al original jellyfin, entonces lo cambiamos al nuevo nombre que es jellyfin-clone y listo
3 Luego de correjir el fstab todo bien.

Instalando Jdownloader

jdownloader

Desde hace muchos años, uso Jdownloader y de verdad que siempre me vino bien para reconexión automática, reanudar/continuar las descargas, descompresión automatica, conectarme a otros servidores como mega etc…​

Instalar jdk

Viene bien la openjdk11

xigmanas: ~# bastille console jdownloader (1)
root@jdownloader:~ # pkg install openjdk11 (2)
1 Entramos al jail jdownloader
2 Instalamos la open jdk.
root@jdownloader:~ # java -version (1)
openjdk version "11.0.12" 2021-07-20
OpenJDK Runtime Environment (build 11.0.12+7-1)
OpenJDK 64-Bit Server VM (build 11.0.12+7-1, mixed mode)
1 Mostramos la versión de java actual.

Descargar megatools e iniciar Jdownloader

Megatools y url del .jar de Jdownloader
  1. Vamos a instalar megatools, para hacer descargas desde mega.nz

  2. Buscamos la versión other de aqui para el .jar

root@jdownloader:~ # pkg install megatools (1)
root@jdownloader:~/java # megadl https://mega.nz/file/2EkgUSga#Pjau9db2bBDES-ih4iWYlHfwC0t-444eFfm0SQegqRA (2)
Downloaded JDownloader.jar
root@jdownloader:~/java # ls -la
total 4134
drwxr-xr-x   2 root  wheel        3 Jul  4 16:32 .
drwxr-x---  19 root  wheel       37 Jul  4 16:32 ..
-rw-r--r--   1 root  wheel  4411735 Jul  4 16:32 JDownloader.jar
1 Instalamos megatools
2 Comando megadl para descargar desde mega
java -jar JDownloader.jar -norestart (1)
1 Este comando permite iniciar Jdownloader pero sin interface grafica, es decir modo consola, y muchas veces, requiere ejecutarlo nuevamente si nos sale la imagen de abajo.

jdownloaderRestart

Al final para conectarnos a el, lo haríamos mediante MyJDownloader este existe en versión app para Android, IOs no lo se, y un plugin para Google Chrome MyJDownloader Browser Extension

Jdownloader con autoboot

Este proceso de autoboot, podemos hacerlo con otra aplicación, asi como hace git automaticamente que también crea su propio daemon, con adguardhome aplique el mismo procedimiento.

El paso anterior con el parametro -norestart, no merece la pena por, dado que a veces, el jdownloader con alguna actualización pendiente requiere que se ejecute de nuevo.

Además si nuestro NAS lo apagamos, nuestro jail no iniciara al Jdownloader por lo comentado anteriormente, entonces, en nuestro jail de Jdownloader nos creamos un pequeño fichero de configuración.

Creando fichero en etc/rc.d

El contenido del fichero que permitirá que nuestro Jdownloader arranque en cada inicio es este:

root@jdownloader:/etc/rc.d # touch jdownloader (1)
root@jdownloader:/etc/rc.d # chmod +x jdownloader (2)
root@jdownloader:/etc/rc.d # nano jdownloader (3)
1 Creamos un fichero llamado jdownloader en esa ruta
2 Establecemos permisos de ejecución
3 Editamos con nano o el que sea 😆
#!/bin/sh

. /etc/rc.subr

name=jd2
rcvar=jd2_enable (1)

start_cmd="${name}_start"
stop_cmd="${name}_stop"

load_rc_config $name

jd2_start()
{
#!/bin/bash
echo "starting JDownloader2..."
umask 000
cd /jdownloader/
/usr/local/bin/java -Djava.awt.headless=true -jar /root/JDownloader.jar >/dev/null 2>/dev/null & (2)
}

jd2_stop()
{
#!/bin/bash
echo "killing java (and JDownloader2)..."
pgrep java | xargs kill
}

jd2_restart()
{
#!/bin/bash
echo "killing java (and JDownloader2)..."
pgrep java | xargs kill
umask 000
cd /jdownloader/
echo "starting JDownloader2..."
/usr/local/bin/java -Djava.awt.headless=true -jar /root/JDownloader.jar >/dev/null 2>/dev/null &
}

run_rc_command "$1"
1 Es el nombre de nuestro daemon j2d podemos llamarle como queramos
2 Esto inicia nuestro .jar y crea un PID del proceso/daemon

Ruta /etc/rc.conf

Necesitamos ahora ir al /etc/rc.conf de nuestro jail y añadir la siguiente líneas, para habilitar nuestro daemon de JDownloader en cada inicio del jail.

syslogd_flags="-ss"
sendmail_enable="NO"
sendmail_submit_enable="NO"
sendmail_outbound_enable="NO"
sendmail_msp_queue_enable="NO"
cron_flags="-J 60"
ifconfig_e0b_bastille4_name="vnet0"
ifconfig_vnet0="inet 192.168.1.248/24"
defaultrouter="192.168.1.1"
jd2_enable="YES" (1)
1 Habilitando el daemon en el boot del jail
last pid: 15419;  load averages:  0.00,  0.00,  0.00                                          up 0+04:58:26  16:37:58
6 processes:   1 running, 5 sleeping
CPU:  0.0% user,  0.0% nice,  0.0% system,  0.0% interrupt,  100% idle
Mem: 461M Active, 1103M Inact, 8597M Wired, 21G Free
ARC: 5622M Total, 505M MFU, 5054M MRU, 96K Anon, 17M Header, 46M Other
     4607M Compressed, 5326M Uncompressed, 1.16:1 Ratio
Swap: 2048M Total, 2048M Free

  PID USERNAME    THR PRI NICE   SIZE    RES STATE    C   TIME    WCPU COMMAND
 5271 root         56  52    0    10G   662M uwait   11   4:10   0.72% java (1)
15419 root          1  20    0    14M  3684K CPU21   21   0:00   0.01% top
 5593 root          1  20    0    13M  2584K nanslp  10   0:00   0.00% cron
 5548 root          1  20    0    13M  2772K select  12   0:00   0.00% syslogd
15261 root          1  20    0    14M  4108K pause    2   0:00   0.00% csh
15260 root          1  52    0    13M  3096K wait     6   0:00   0.00% login
1 daemon de jdownloader activo 🔥
Es importante ejecutar nuevamente el .jar para añadir nuestra autenticación con email y password como en la imagen de abajo.

login jdownloader

En caso de reiniciar el jail por nuestra parte bastaría con un

xigmanas: /# bastille restart jdownloader (1)
[jdownloader]:
jdownloader: removed

[jdownloader]:
e0a_bastille4
e0b_bastille4
jdownloader: created

xigmanas: /# jexec -l jdownloader top (2)
last pid: 16487;  load averages:  0.16,  0.10,  0.04
4 processes:   1 running, 3 sleeping
CPU:  0.3% user,  0.0% nice,  0.0% system,  0.0% interrupt, 99.7% idle
Mem: 295M Active, 962M Inact, 8604M Wired, 21G Free
ARC: 5636M Total, 566M MFU, 5007M MRU, 96K Anon, 17M Header, 47M Other
     4611M Compressed, 5333M Uncompressed, 1.16:1 Ratio
Swap: 2048M Total, 2048M Free

  PID USERNAME    THR PRI NICE   SIZE    RES STATE    C   TIME    WCPU COMMAND
16135 root         50  52    0    10G   316M uwait   21   0:24   2.40% java (3)
16487 root          1  20    0    14M  3580K CPU4     4   0:00   0.05% top
16411 root          1  20    0    13M  2772K select  15   0:00   0.00% syslogd
16459 root          1  20    0    13M  2604K nanslp  10   0:00   0.00% cron
1 reiniciando el jail y todo bien
2 Mirando que el proceso java dentro del jail este activo
3 Proceso activo

Actualizar desde MyJDownloader

En la interface web, en settings podemos actualizarle, pero como tenemos autoboot él se reiniciará automáticamente.

jdownloaderUpdateAndRestart

Esta actualización la realize por gusto, en realidad es opcional, sin hacerla me permitía usar jdownloader tranquilamente.

Ruta de descarga

Aquí en settings también podemos cambiar la ruta de descarga, el directorio debe existir en nuestro jail

carpetaDownloads


Instalando Jellyfin

Esto no es Netflix logo, nosotros nos corresponde hacer todo el trabajo de guardar las películas en nuestro NAS, y desde el Jellyfin, acceder a ese directorio, pool/dataset donde las tengamos, para poder verlas 😁

45698031?s=200&v=4

Jellyfin es un fork de emby, como plex, pero opensource, como muchas opciones para tener nuestras películas en nuestro NAS organizadas, editar subtítulos, control de usuarios hasta control parental, añadir musica, tiene un Api para aplicaciones externas, extensiones, la versión que usare es 10.7.7 es un poco vieja, pero viene bastante bien, muy superior al miniDLNA 😅.

Pasos básicos de instalación más repositorio de inspiración

Configuración del jails.conf

Para jellyfin es importante añadir las últimas 2 líneas:

jellyfin {
  devfs_ruleset = 13;
  enforce_statfs = 2;
  exec.clean;
  exec.consolelog = /mnt/pool/extensions/bastille/logs/jellyfin_console.log;
  exec.start = '/bin/sh /etc/rc';
  exec.stop = '/bin/sh /etc/rc.shutdown';
  host.hostname = jellyfin;
  mount.devfs;
  mount.fstab = /mnt/pool/extensions/bastille/jails/jellyfin/fstab;
  path = /mnt/pool/extensions/bastille/jails/jellyfin/root;
  securelevel = 2;

  vnet;
  vnet.interface = e0b_bastille0;
  exec.prestart += "jib addm bastille0 igb0";
  exec.prestart += "ifconfig e0a_bastille0 description \"vnet host interface for Bastille jail jellyfin\"";
  exec.poststop += "jib destroy bastille0";
  allow.mlock; (1)
  allow.raw_sockets; (2)
}
1 Relacionado con la memoria del jail.
2 Habilita utilidades como ping y traceroute dentro del jail.
La dirección ip a usar es la que le asignamos al jail y puerto por defecto 8096

peliculasJellyFin

Error al reinstalar Jellyfin

Me paso muchas veces que al querer reinstalar jellyfin, no podía hacerlo por tener un UID repetido, en realidad por usuarios/grupos.
The service file uses daemon to restart jellyfinserver if it crashes.
The service file will also change the permissions so that the updater works.
If this behavior is unwanted you will need to edit the RC file manually and
remove the daemon and/or the permissions changes.

If you are running this in a jail please set "allow_mlock=1" or similar
for this jail otherwise the program will fail to start.

dotNET does not work well inside jails that are missing either a) VNET or
one of b) ip6=inherit c) ip6=new. The service file will try workaround any
user misconfiguration but is not perfect.

root@jailvnet2:~ # pw user add jellyfin -c jellyfin -u 710 -d /nonexistent -s /usr/bin/nologin
pw: uid `710' has already been allocated (1)

root@jailvnet2:~ # ln -s /usr/local/lib/libsqlite3.so /usr/local/lib/libe_sqlite3

root@jailvnet2:~ # sysrc jellyfinserver_enable=TRUE
jellyfinserver_enable:  -> TRUE
root@jailvnet2:~ # sysrc jellyfinserver_user=jellyfin
jellyfinserver_user:  -> jellyfin

root@jailvnet2:~ # sysrc jellyfinserver_group=jellyfin
jellyfinserver_group:  -> jellyfin

root@jailvnet2:~ # service jellyfinserver start
install: unknown group jellyfin
install: unknown group jellyfin
Starting jellyfinserver.
su: unknown login: jellyfin
/usr/local/etc/rc.d/jellyfinserver: WARNING: failed to start jellyfinserver (2)
root@jailvnet2:~ #
1 El UID 710 estaba repetido, pareciendo que otro usuario lo tiene, entonces lo que hariamos es colocar otro.
2 Al intentar iniciar el servidor jellyfin tenemos este error.

Lo fixeamos con lo siguiente

pw user add jellyfin -c jellyfin -u 715 -d /nonexistent -s /usr/bin/nologin (1)
1 Usamos ahora UID 715, para fixear el error.
root@jailvnet2:~ # service jellyfinserver start
Starting jellyfinserver. (1)
1 Con este solo mensaje, indica que todo esta bien, ahora podemos acceder con la dirección ip del jail mas el puerto 8096

Actualizando Jellyfin

Es buena idea tener un backup o clonar el jail en casos como estos, bastille lo permite, vía consola o web.
root@jellyfin:~ # service jellyfinserver stop (1)
Stopping jellyfinserver.
root@jellyfin:~ # fetch https://github.com/Thefrank/jellyfin-server-freebsd/releases/download/v10.8.1/jellyfinserver-10.8.1.pkg (2)
jellyfinserver-10.8.1.pkg                               58 MB 7305 kBps    08s
root@jellyfin:~ # ls
.cshrc				.k5login			.shrc
.history			.login				jellyfinserver-10.7.7.pkg
.hushlogin			.profile			jellyfinserver-10.8.1.pkg
root@jellyfin:~ # pkg install jellyfinserver-10.8.1.pkg (3)
Updating FreeBSD repository catalogue...
[jellyfin] Fetching packagesite.pkg: 100%    6 MiB   3.3MB/s    00:02
Processing entries: 100%
FreeBSD repository update completed. 31614 packages processed.
All repositories are up to date.
New version of pkg detected; it needs to be installed first.
The following 1 package(s) will be affected (of 0 checked):

Installed packages to be UPGRADED:
	pkg: 1.17.5 -> 1.18.3 (4)

Number of packages to be upgraded: 1

8 MiB to be downloaded.

Proceed with this action? [y/N]: y (5)
[jellyfin] [1/1] Fetching pkg-1.18.3.pkg: 100%    8 MiB   2.2MB/s    00:04
Checking integrity... done (0 conflicting)
[jellyfin] [1/1] Upgrading pkg from 1.17.5 to 1.18.3...
[jellyfin] [1/1] Extracting pkg-1.18.3: 100%
Updating FreeBSD repository catalogue...
FreeBSD repository is up to date.
All repositories are up to date.
The following 2 package(s) will be affected (of 0 checked):

New packages to be INSTALLED:
	jellyfinserver: 10.8.1
	krb5-120: 1.20

Number of packages to be installed: 2

The process will require 135 MiB more space.
1 MiB to be downloaded.

Proceed with this action? [y/N]: y (6)
[jellyfin] [1/2] Fetching krb5-120-1.20.pkg: 100%    1 MiB   1.2MB/s    00:01
Checking integrity... done (0 conflicting)
[jellyfin] [2/2] Installing jellyfinserver-10.8.1...
===> Creating groups.
Using existing group 'jellyfinserver'.
===> Creating users
Using existing user 'jellyfinserver'.
[jellyfin] Extracting jellyfinserver-10.8.1: 100%
[jellyfin] [1/2] Installing krb5-120-1.20...
[jellyfin] [1/2] Extracting krb5-120-1.20: 100%
=====
Message from jellyfinserver-10.8.1:

--
jellyfinserver relies on Microsoft dotNET5+ SDK to be built
Microsoft does not have an official version of dotNET for FreeBSD

This package was built with an UNOFFICIAL UNSUPPORTED version of dotNET
If this is something that you do not want, remove this package with
"pkg remove jellyfinserver"

This package installs a service file.
Enable it with "sysrc jellyfinserver_enable=TRUE"
Start it with "service jellyfinserver start".

The service file uses daemon to restart jellyfinserver if it crashes.
The service file will also change the permissions so that the updater works.
If this behavior is unwanted you will need to edit the RC file manually and
remove the daemon and/or the permissions changes.

If you are running this in a jail please set "allow_mlock=1" or similar
for this jail otherwise the program will fail to start.

dotNET does not work well inside jails that are missing either a) VNET or
b) ip6=inherit. The service file will try workaround any user misconfiguration
but is not perfect.
root@jellyfin:~ # service jellyfinserver start (7)
Starting jellyfinserver.
1 Paramos el servidor jellyfin
2 fetch a la nueva versión de jellyfin
3 Se instala el .pkg versión 10.8.1 de jellyfin
4 Se muestra la versión que se actualizara.
5 Yes
6 Yes
7 Iniciamos el servidor jellyfin

Tenemos la actualización lista

version10.8.1.jellyfin

Registros / Logs

Esta parte nos permite visualizar nuestros logs, muy util.

logsJellyfin

Puede suceder, que las imagenes de las películas no sean las correctas, pero tampoco puedan cambiarse correctamente desde la interface web, pero puede ser falta de permisos de escritura en el NAS gracias a los logs se puede verificar eso.
[2022-05-08 15:57:59.602 +00:00] [ERR] [18] MediaBrowser.Providers.Manager.ProviderManager: UnauthorizedAccessException - Access to path "/mnt/movies_1/Spiderman un nuevo universo (2018)/folder.jpg" is denied. Will retry saving to "/var/db/jellyfinserver/metadata/library/a4/a45b8287c09ccc439897b6158d8d88bd/poster.jpg"
[2022-05-08 15:57:59.828 +00:00] [ERR] [18] MediaBrowser.Providers.Manager.ProviderManager: UnauthorizedAccessException - Access to path "/mnt/movies_1/Spiderman un nuevo universo (2018)/backdrop.jpg" is denied. Will retry saving to "/var/db/jellyfinserver/metadata/library/a4/a45b8287c09ccc439897b6158d8d88bd/backdrop.jpg"
[2022-05-08 15:58:02.249 +00:00] [ERR] [24] MediaBrowser.Providers.Manager.ProviderManager: Error in metadata saver
(1)
System.UnauthorizedAccessException: Access to the path '/mnt/movies_1/Spiderman un nuevo universo (2018)/Spider-Man Un nuevo universo (2018).nfo' is denied.
 ---> System.IO.IOException: Permission denied
   --- End of inner exception stack trace ---
   at Interop.ThrowExceptionForIoErrno(ErrorInfo errorInfo, String path, Boolean isDirectory, Func`2 errorRewriter)
   at Microsoft.Win32.SafeHandles.SafeFileHandle.Open(String path, OpenFlags flags, Int32 mode)
   at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options)
   at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share)
   at MediaBrowser.XbmcMetadata.Savers.BaseNfoSaver.SaveToFile(Stream stream, String path)
   at MediaBrowser.XbmcMetadata.Savers.BaseNfoSaver.Save(BaseItem item, CancellationToken cancellationToken)
   at MediaBrowser.Providers.Manager.ProviderManager.SaveMetadata(BaseItem item, ItemUpdateType updateType, IEnumerable`1 savers)
1 Error a la hora de escribir en el directorio, entonces, no se podrian editar las portadas y demás.

Generalmente se cambian esas imagenes de aquí, pero el dataset debe tener los permisos correctos.

besoDragon

OpenSubtitles

catalogoPluginOpenSubtitles

Este es un plugin para subtítulos muy útil, en caso de tener una película con caracteres raros en las traducciones.

Necesitamos crearnos un usuario para tener acceso al api, dicho usuario lo introduciremos en nuestro server jellyfin

userPassOpenSubtitles

Estatus del api OpenSubtitles

Esta url permite ver el estado del api de OpenSubtitles con pequeñas metricas.

Buscando subtítulos

En caso de un película vieja, y queramos subtítulos, en la esquina derecha, tenemos 3 puntos verticales, luego en "Editar subtítulos", que nos mostrara la modal siguente:

Por defecto tenemos

  • Subtitulos Latino - Es

  • Pero queremos Inglés por ejemplo

busquedaSubtitulos

Metadatos con MKVToolNix

mkvtoolnix logo

Muchos archivos por ahí (.mp4 .mkv etc), tendran metadatos, e incluso llegan a ser un poco molestos.

En el caso de archivos .mkv viene muy bien el MKVToolNix vía GUI, es muy rápido, importamos nuestra película y editamos el metadato que queramos…​

Lo mejor es editar el fichero en local, si editamos el video directamente en el directorio del NAS, tardaria muchisimo (aún en LAN), podemos descargar la película desde el mismo jellyfin a nuestra pc editar y luego subirla.

mkvtoolnixProperties

En el menu donde "multiplexador" tenemos varias cosas:

  • Codec

    • Cada item corresponde a una parte que podemos editar.

  • Propiedades

    • Podemos cambiar por ejemplo "Nombre de pista".

  • Pestaña/tab Salida

    • General → Título del archivo: escribimos el nombre del video(o simplemente borrar el texto original que no queramos), en ciertos reproductores, este nombre se mostrara en el inicio de la película.

  • Archivo de salida:

    • Este será el nombre del fichero en realidad puede ser cualquiera, no es un metadato, se puede editar como cualquier archivo.

  • Iniciar multiplexado

    • Luego de tener todo listo, clickeamos ese botón.

iniciarMultiplexado


Amazon fire stick 4k

em>

El Amazon fire stick Nos permite instalar aplicaciones en el, basado en Android tv, nos quitamos esa molestia que nos pasa con ciertos smart-tv a la hora de instalar una app, evitando usar el protocolo DLNA que es más inestable de lo que parece.

En el tendremos tranquilamente nuestro cliente jellyfin para acceder a nuestro servidor jellyfin con una configuración previa pero sencilla.

Con conexión HDMI, si lo llevamos a un lugar remoto, lo conectamos y accedemos a nuestro NAS tanto local, o remotamente con ciertos cambios, como tener un dns dinámico.

amazonfireStickMasVisual

Dimensiones 99 x 30 x 14 mm (solo el dispositivo) 108 x 30 x 14 mm (incluyendo el conector)

Peso

53,6

Procesador

Quad-Core de 1,7 GHz

GPU

IMG GE8300

Almacenamiento

8 GB

Wifi

Wifi de doble banda y doble antena (MIMO) que permite una transferencia de datos más rápida y pérdida de conexión menor que con el wifi estándar. Compatible con redes wifi 802.11a/b/g/n/ac.

Bluetooth

Bluetooth 5.0 y LE. Se puede vincular con altavoces, auriculares, mandos de videojuegos y otros dispositivos Bluetooth compatibles.


Reverse proxy para acceso remoto

NGINX Part of F5 horiz black type 1

Ian me trajo recuerdos con una cosa interesante, para mejor administración de los certificados SSL con un server nginx.

Nuevamente nos creamos un Jail para instalar nuestro servidor nginx y redireccionar las peticiones http que queramos que pasen por aquí.

/usr/local/etc/nginx/nginx.conf (1)
1 Ruta de configuración del proxy nginx

En caso de editar el fichero nginx.conf

service nginx reload (1)
1 Para reiniciar el servidor nginx

Redirección a Https

Por lo visto si accedemos con http a nuestros servidores con nginx por defecto es posible entrar normal, y eso no lo queremos, podemos hacer una redirección a https, pero debemos ajustar nuestro nginx.conf

Fue necesario para esto, abrir el puerto 80 en el router con la ip del reverse-proxy, porque sin eso no funciona.
server {
    listen       443 ssl; (1)
    server_name localhost nuestroDominio.duckdns.org;
    ssl_certificate      /usr/local/etc/letsencrypt/live/dominio/fullchain.pem;
    ssl_certificate_key  /usr/local/etc/letsencrypt/live/dominio/privkey.pem;
    ssl_session_cache    shared:SSL:1m;
    ssl_session_timeout  5m;
    ssl_ciphers  HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers  on;
    location / {
      proxy_pass http://192.168.1.130:8096; (2)
    }
    # HSTS (ngx_http_headers_module is required) (63072000 seconds)
    add_header Strict-Transport-Security "max-age=63072000 includeSubDomains; preload" always;
    #error_page  404              /404.html;
    # redirect server error pages to the static page /50x.html
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/local/www/nginx-dist;
    }
    set $https_redirect 0;
    #if request came from port 80/http
    if ($server_port = 80) {
        set $https_redirect 1;
    }
    # or if the requested host came with www
    if ($host ~ '^www\.') {
        set $https_redirect 1;
    }
    #then it will redirects
    if ($https_redirect = 1) {
        return 301 https://$server_name$request_uri;
    }
}
server { (3)
    listen 80 default_server;
    server_name _;
    return 301 https://$host$request_uri;
}
1 Config para el https
2 Nuestro server en local de jellyfin
3 Confi necesaria de http

Como renovar el certificado SSL de letsencrypt con certbot?

certbot logo

Podemos usar certbot con instrucciones para freeBSD los certificados que hemos creado con letsencrypt, estos se vencen a los 90 días.

root@reverse-proxy:/usr/local/etc/nginx # certbot renew (1)
root@reverse-proxy:/usr/local/etc/nginx # certbot renew --dry-run (2)
1 Para renovar los certificados.
2 Sirve para testear la renovación automática, sin generar los certificados.

Pero obtenemos este error

Saving debug log to /var/log/letsencrypt/letsencrypt.log (1)

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /usr/local/etc/letsencrypt/renewal/mydomain.bla.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Renewing an existing certificate for mydomain.bla

Certbot failed to authenticate some domains (authenticator: standalone). The Certificate Authority reported these problems:
  Domain: mydomain.bla
  Type:   unauthorized
  Detail: ip: Invalid response from http://mydomain.bla/.well-known/acme-challenge/e5P1zpHbQUyQ6R28jew3Z9rRxpMPzUdsLE7Gdlp5ZqI: 404
(2)
Hint: The Certificate Authority failed to download the challenge files from the temporary standalone webserver started by Certbot on port 80. Ensure that the listed domains point to this machine and that it can accept inbound connections from the internet.

Failed to renew certificate mydomain.bla with error: Some challenges have failed.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
All renewals failed. The following certificates could not be renewed:
  /usr/local/etc/letsencrypt/live/mydomain.bla/fullchain.pem (failure)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1 renew failure(s), 0 parse failure(s)
Ask for help or search for solutions at https://community.letsencrypt.org. See the logfile /var/log/letsencrypt/letsencrypt.log or re-run Certbot with -v for more details.
1 Ruta del log generado, y que debemos inspeccionar en caso de algo raro.
2 Por lo visto esta estrechamente relacionado con nuestro error, y el puerto 80 bloqueado

Si investigamos mas con el comando certbot renew -v tenemos que se usa el:

http-01 challenge for xxx.xxx.net pero justo este no nos sirve.

Necesitamos usar otro challenge que nos permita hacer esto, nuestro proveedor nos bloquea el puerto 80 😡.

Usando DNS dinámico con DuckDNS

ducky icon small

Anteriormente me valia con no-ip para mi ip dinámica, pero con este dns-01-challenge necesito escribir los record TXT en el, y no me va, porque habría que pagar, prefiero darle ese dinero a los pobres.

Ahora con duckdns me da todo, gratis, y un api-rest para actualizar el record TXT, una vez que introduzca el comando necesario con certbot:

certbot certonly --manual --preferred-challenges dns -d miDominio.duckdns.org (1)
1 Mi dominio
root@reverse-proxy:/logs-letsencrypt # certbot certonly --manual --preferred-challenges dns -d foo.duckdns.org
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Certificate not yet due for renewal

You have an existing certificate that has exactly the same domains or certificate name you requested and isn't close to expiry.
(ref: /usr/local/etc/letsencrypt/renewal/foo.duckdns.org.conf)

What would you like to do?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: Keep the existing certificate for now (1)
2: Renew & replace the certificate (may be subject to CA rate limits)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate number [1-2] then [enter] (press 'c' to cancel):
1 La opción (1) me tocará ya que tengo el certificado vigente.

En caso de tener que renovar el certificado los pasos para actualizar el record TXT es aquí.

root@reverse-proxy:/logs-letsencrypt # certbot certonly --manual --preferred-challenges dns -d mySubName
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Requesting a certificate for bar.duckdns.org

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please deploy a DNS TXT record under the name:

_acme-challenge.bar.duckdns.org.

with the following value:

L9---------------------------------------(1)

Before continuing, verify the TXT record has been deployed. Depending on the DNS
provider, this may take some time, from a few seconds to multiple minutes. You can
check if it has finished deploying with aid of online tools, such as the Google
Admin Toolbox: https://toolbox.googleapps.com/apps/dig/#TXT/_acme-challenge.bar.duckdns.org.
Look for one or more bolded line(s) below the line ';ANSWER'. It should show the
value(s) you've just added.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1 Token generado de 43 caracteres para nuestro record TXT, para usar en los query-parameters del endpoint ofrecido por duck dns, es el paso siguiente.

Invocando al endpoint para actualizar certificado

https://www.duckdns.org/update?domains=mySubName&token=myTokenDeDuckDns&txt=tokenGenerado&verbose=true (1)
1 Insertamos nuestro token de duckdns (el que nos genera la web) en el parametro token, también nuestro token generado en el paso anterior para usarle en el parametro txt

Una vez que setemos la nueva ruta de nuestros certificados fullchain.pem y privkey.pem nos bastaría con reiniciar nuestro server nginx

root@reverse-proxy:/servernginx # service nginx reload (1)
Performing sanity check on nginx configuration:
nginx: the configuration file /usr/local/etc/nginx/nginx.conf syntax is ok
nginx: configuration file /usr/local/etc/nginx/nginx.conf test is successful
1 reiniciando el nginx server.

Nuestro certificado activo

certificadoValidaDuckDNS