Cfg - Certificados SSL Let’s Encrypt


Índice:

   Objetivo

   Instalación de Certbot y el plugin certbot_dns_duckdns

      Instalación de las herramientas necesarias

      Configuración de Certbot

   Obtener los certificados

   Instalar los certificados en OMV 8

   Renovación automática de los certificados

      Renovar los certificados en OpenMediaVault 8

      Script renovación certificados OMV 8

            Script: /etc/letsencrypt/renewal-hooks/deploy/script_actualiza_omv.sh

            Script: /etc/letsencrypt/renewal-omv/update_cert_omv.sh


Objetivo  ⮴  

Obtener y mantener renovado un certificado Let's Ecncrypt para un dominio propor-

cionado por Duckdns desde un equipo doméstico con OpenMediaVault 8 conectado a

internet a través de un router. La tarea de renovación también incluye la actua-

lización de los certificados en OMV 8.


Se usará Certbot usando como método de valildación del domino DNS-01 que no re-

quiere como el método HTTP-01 redireccionar puertos en el router y el plugin DNS

certbot_dns_duckdns.


Enlaces

DNS plugins Certbot


Instalación de Certbot y el plugin certbot_dns_duckdns  ⮴  

En OVM 8 el administrador de paquetes del sistema está administrando Python

packages, lo que al intentar instalar el plugin certbot_dns_duckdns genera el

error externally-managed-environment, por ello se recurrira a un entrono para

realizar la instalación y posterior uso.


Instalación de las herramientas necesarias  ⮴  

Instalar paquetes:

      apt install certbot

      apt install python3-pip

      apt install python3-venv


Crear directorios en /etc/letsencrypt

      mkdir /etc/letsencrypt/venv-ip

      mkdir /etc/letsencrypt/credentials


El primero para el entrono de python en el que instalará certbot_dns_duckdns.

El segundo en el que se almacenerán los archivos de credenciales que usará

certbot para crear/renovar los certificados.


Crear el entorno y acceder a él

      python3 -m venv /etc/letsencrypt/venv-ip

      source /etc/letsencrypt/venv-ip/bin/activate


Instalar certbot_dns_duckdns y comprobar que está disponible

      pip install certbot_dns_duckdns

      certbot plugins


Configuración de Certbot  ⮴  

Certbot se puede usar manual o automáticamente, esto último lo hará el servi-

cio automático que renueva los certificados, pero en ambos casos ha de hacer-

lo en el entorno creado para tener acceso a certbot_dns_duckdns.


Manualmente Certbot puede ser llamado de dos formas:

  1. Si se está dentro del entrono que se ha creado: certbot certonly ...

  2. Si no se está: /etc/letsencrypt/venv-ip/bin/certbot certonly ...


La tarea automática1 de renovación necesita para realicese correctamente que se

sobreescriba la entrada ExecStart del Certbot.service de tal forma que la llama-

da que realice la haga desde el entrono -2ª opción anterior-, para ello:


# Editar

      systemctl edit certbot.service


# Añadir

      # Sobreescribir para que cerbot renew se ejecute desde el entono donde       

      # se ha instalado el plugin certbot_dns_duckdns

      [Service]

      ExecStart=

      ExecStart=/etc/letsencrypt/venv-ip/bin/certbot -q renew --no-random-sleep-on-renew


# Verificar

      systemctl daemon-reload

      systemd-analyze verify certbot.service

      systemctl cat certbot.service


# Resultado de sudo systemctl cat certbot.service


      ### Editing /etc/systemd/system/certbot.service.d/override.conf

      ### Anything between here and the comment below will become the contents of the drop-in file


      [Service]

      ExecStart=

      ExecStart=/etc/letsencrypt/venv-ip/bin/certbot -q renew --no-random-sleep-on-renew


      ### Edits below this comment will be discarded



      ### /usr/lib/systemd/system/certbot.service

      # [Unit]

      # Description=Certbot

      # Documentation=file:///usr/share/doc/python-certbot-doc/html/index.html

      # Documentation=https://certbot.eff.org/docs

      # [Service]

      # Type=oneshot

      # ExecStart=/usr/bin/certbot -q renew --no-random-sleep-on-renew

      # PrivateTmp=true



Obtener los certificados  ⮴  

En primer lugar se crea el archivo que se usará como credenciales en duckdns

para obtener los certificados

    mkdir /etc/letsencrypt/credentials

    cd /etc/letsencrypt/credentials

    nano cfg_lozjim


El archivo sólo contendrá una línea:

    dns_duckdns_token = [token asignado al crear el dominio]


Tras crear el archivo dar acceso exclusivamente al root

    chmod 600 /etc/letsencrypt/credentials/cfg_lozjim


Obtener los certificados:


Detalle del comando:


Se puede añdir --dry-run> para hacer simulaciones.


Certbot genera los certificados compuestos por los siguientes archivos:


Instalar los certificados en OMV 8  ⮴  

La instalación en OMV de los certificados consiste en copiar el contenido de dos

de los archivos obtenidos.


En Sistema - Certificados - + (Añdir) - Importar


Hay que seleccionar el archivo que contenga:


Referencia


Renovación automática de los certificados  ⮴  

La instalación de Certbot preconfigura una tarea para actualizar los certifica-

dos que sean instalados por la aplicación. Los usuarios del plugin Manual deben

tener en cuenta que los certificados --manual no se renovarán automáticamente,

a menos que se combinen con scripts de gancho de autenticación.


Referencia


Se puede consultar con el comando:

  systemctl list-timers


Debería generar una salida con las tareas programadas y entre ellas

  NEXT                             Mon 2026-07-27 07:11:39 CEST

  LEFT LAST                        11h Sun 2026-07-26 18:51:05 CEST

  PASSED UNIT                      31min ago certbot.timer

  ACTIVATES                        certbot.service


El estado de la programación se puede ver:

  systemctl status certbot.timer


  ● certbot.timer - Run certbot twice daily

      Loaded: loaded (/usr/lib/systemd/system/certbot.timer; enabled; preset: enabled)

      Active: active (waiting) since Sun 2026-07-26 16:49:24 CEST; 4h 6min ago

  Invocation: 88cae7e70f194398b9e4a4693cd41126

      Trigger: Mon 2026-07-27 07:11:39 CEST; 10h left

    Triggers: ● certbot.service


  Jul 26 16:49:24 homenas systemd[1]: Started certbot.timer - Run certbot twice daily.


Una vez obtenidos los certificados, esta tarea se encargará de comprobar la necesi-

dad de renovar los certificados de los dominios que se hayan obtenido con certbot

y los renovará en caso necesario. Sin embargo, la renovación no actualizará los cer-

tificados en OMV 8.


Renovar los certificados en OpenMediaVault 8  ⮴  

Al renovar certificados Certbot ejecutar -si existen- unos scripts que pueden

ser usados para realizar tareas adicionales. Por ejemplo, para actualizar los

certificdos en OMV8.


Los scripts de certbot renew:


En post y prev puede haber varios scripts, se ejecutan en orden por lo que

convien nombrarlos como 01_xxx.sh, 02_xxx.sh ...


Durante el proceso Certbot también asigna valores a unas variables de entrono

que podrán ser usadas por estos scripts.


La variable RENEWED_LINEAGE sólo estará disponible cuando se ha realizado

correctamente la renovación.


Script renovación certificados OMV 8  ⮴  

Este script se encontrará en /etc/letsencrypt/renewal-hooks/deploy y se ejecu-

tará cada vez que certbot renueve con exito un certificado. Partiendo de la variable

RENEWED_LINEAGE obtiene los datos necesarios del dominio que se ha actualizado

para lanzar otro script genérico que realiza la tarea con los datos proporcionados.


Script: /etc/letsencrypt/renewal-hooks/deploy/script_actualiza_omv.sh  ⮴  

    #!/bin/bash


    # Nombre:       actualiza_certificado_omv.sh

    # Descripción:  Deploy-hook de Certbot que sincroniza un certificado SSL

    #               renovado con OpenMediaVault 8, actualizando la entrada

    #               existente en la base de datos de configuración de OMV

    #               (conf.system.certificate.ssl) sin crear duplicados.


    # Ubicación:    /etc/letsencrypt/renewal-hooks/deploy/


    # Disparo:      Certbot ejecuta automáticamente este script tras CADA

    #               renovación exitosa de CUALQUIER certificado gestionado

    #               en este sistema (vía certbot renew, manual o por

    #               certbot.timer), con independencia del authenticator

    #               usado (dns-duckdns, standalone, webroot, etc.) y de si

    #               dicho certificado fue emitido con o sin --deploy-hook.


    # Requisitos:

    #   - Debe ejecutarse como root (así lo hace Certbot vía systemd timer).

    #   - Paquete 'jq' instalado (parseo de JSON).

    #   - omv-confdbadm disponible (CLI de configuración de OMV).

    #   - Script auxiliar update_cert.sh en /ruta/a/update_cert.sh, que

    #     realiza el 'write' sobre el UUID existente en OMV.

    #   - El certificado debe existir previamente en OMV (Sistema →

    #     Certificados → SSL).


    # Variables de entorno usadas (inyectadas por Certbot):

    #   RENEWED_LINEAGE   Ruta al directorio live/ del certificado renovado

    #                     (ej. /etc/letsencrypt/live/midominio.duckdns.org)


    # Salida:

    #   exit 0  → sincronización con OMV completada correctamente

    #   exit 1  → error (variable no definida, ficheros no encontrados,

    #             UUID no localizado en OMV, o fallo en update_cert.sh)


    # Fecha:        2026-07-23

    # Versión:      1.0



    set -euo pipefail


    # script que realiza la actualización

    SCRIPT_RENEW_OMV="/etc/letsencrypt/renewal-omv/update_cert_omv.sh"

    # archivo de log

    LOGFILE="/var/log/letsencrypt/renovaciones-omv.log"


    # --- Datos derivados de la variable de entorno que Certbot inyecta ---

    # RENEWED_LINEAGE="/etc/letsencrypt/live/lozjim.duckdns.org"



    _echo(){

        local instante

        instante="$(date '+%Y-%m-%d %H:%M:%S.%3N')"

        echo "[$instante] $1" >> "$LOGFILE"

        echo "[$instante] $1"




    # Comprobar la existencia del archivo

    if [ ! -f "${SCRIPT_RENEW_OMV}" ]; then

        _echo "Error: ${SCRIPT_RENEW_OMV} no existe."

        exit 1

    fi


    # Comprobar que existe la variable y tiene contenido

    if [ -z "${RENEWED_LINEAGE:-}" ]; then

        _echo "Error: RENEWED_LINEAGE no está definida. Este script debe ejecutarse como deploy-hook de Certbot."

        exit 1

    fi


    DOMINIO=$(basename "$RENEWED_LINEAGE")

    CERTIFICADO="$RENEWED_LINEAGE/fullchain.pem"

    KEY="$RENEWED_LINEAGE/privkey.pem"

    COMENTARIO="$DOMINIO - Updated $(date '+%Y-%m-%d %T')"


    # --- Validar que los ficheros existen ---

    if [ ! -f "$CERTIFICADO" ] || [ ! -f "$KEY" ]; then

        _echo "Error: no se encuentran fullchain.pem o privkey.pem en $RENEWED_LINEAGE"

        exit 1

    fi


    # --- Buscar el UUID del certificado en OMV comprobando CN + SAN del propio certificado ---

    UIDD=""

    while IFS=$'\t' read -r uuid cert_b64; do

        cert_pem=$(echo "$cert_b64" | base64 -d 2>/dev/null)

        [ -z "$cert_pem" ] && continue


        nombres=$(echo "$cert_pem" | openssl x509 -noout -text 2>/dev/null | \

            awk '/Subject:/{print} /X509v3 Subject Alternative Name/{getline; print}')


        if echo "$nombres" | grep -q "$DOMINIO"; then

            UIDD="$uuid"

            break

        fi

    done < <(omv-confdbadm read "conf.system.certificate.ssl" | jq -r '.[] | [.uuid, (.certificate | @base64)] | @tsv')



    if [ -z "$UIDD" ]; then

        _echo "Error: No se encontró certificado para $DOMINIO en OMV"

        exit 1

    fi


    # --- Actualizar el certificado en OMV ---

    if ! bash "${SCRIPT_RENEW_OMV}" "$UIDD" "$CERTIFICADO" "$KEY" "$COMENTARIO"; then

        _echo "Error: fallo al actualizar el certificado en OMV para $DOMINIO"

        exit 1

    fi


    _echo "Certificado de $DOMINIO actualizado correctamente en OMV (UUID: $UIDD)"

    exit 0



Script: /etc/letsencrypt/renewal-omv/update_cert_omv.sh  ⮴  

    #!/bin/bash



    # shellcheck disable=SC1091

    . /usr/share/openmediavault/scripts/helper-functions

    . /etc/default/openmediavault


    # archivo de log

    LOGFILE="/var/log/letsencrypt/renovaciones-omv.log"


    _echo(){

        local instante

        instante="$(date '+%Y-%m-%d %H:%M:%S.%3N')"

        echo "[$instante] $1" >> "$LOGFILE"

        echo "[$instante] $1"




    if [[ $(id -u) -ne 0 ]]; then

      _echo "This script must be executed as root or using sudo."

      exit 99

    fi


    uuid="${1}"

    cert="${2}"

    key="${3}"


    if [ -z "${uuid}" ]; then

      xpath="/config/webadmin/sslcertificateref"

      uuid=$(omv_config_get "${xpath}")

    fi


    if ! omv_isuuid "${uuid}"; then

      _echo "Invalid uuid"

      exit 1

    fi


    if [ ! -f "${cert}" ]; then

      _echo "Cert not found"

      exit 2

    fi


    if [ ! -f "${key}" ]; then

      _echo "Key not found"

      exit 3

    fi


    _echo "Cert file :: ${cert}"

    _echo "Key file :: ${key}"


    xpath="/config/system/certificates/sslcertificate[uuid='${uuid}']"

    _echo "xpath :: ${xpath}"

    echo


    if ! omv_config_exists "${xpath}"; then

      _echo "Config for ${uuid} does not exist"

      exit 4

    fi


    _echo "Updating certificate in database ..."

    omv_config_update "${xpath}/certificate" "$(cat ${cert})"


    _echo "Updating private key in database ..."

    omv_config_update "${xpath}/privatekey" "$(cat ${key})"


    if [ -n "${4}" ]; then

      _echo "Updating comment in database ..."

      omv_config_update "${xpath}/comment" "${4}"

    fi


    _echo "Updating certs and nginx..."

    omv-salt deploy run certificates nginx


    systemctl restart nginx