#!/bin/bash ################################################################################################################################# # Version 1.1.0 / 2021-10-17 # # - Option für Ausführung über Docker hinzugefügt # # # # HowTo: # # 1. Variablen für das Arbeitsverzeichnis, die Domainliste und ggf. die Mailadresse anpassen # # 2. das Skript mit dem Parameter create als root aufrufen (die TXT-Records werden erstellt und ausgegeben) # # ./pfad/zu/LE_wildcard.sh create # # 3. manuell die TXT-Records beim Anbieter anpassen # # 4. das Skript ohne Parameter aufrufen, um das Zertifikat zu erstellen / zu erneuern # # ./pfad/zu/LE_wildcard.sh # # ➜ Das Default-Zertifikat des DSM wird erstetzt! # # ➜ Ebenso das Zertifikat aller Anwendungen, für die das Defaultzertifikat konfiguriert wurde! # # # # einige Quellen: # # http://www.stueben.de/lets-encrypt-wildcard/#comment-2208 # # https://www.howtoforge.de/anleitung/erste-schritte-mit-acmesh-lets-encrypt-ssl-client/ # # https://itnext.io/synology-lets-encrypt-dns-01-challenge-acme-sh-and-route53-af491786d262 # # https://vdr.one/how-to-create-a-lets-encrypt-wildcard-certificate-on-a-synology-nas/ # # https://letsencrypt.org/de/docs/rate-limits/ # # https://github.com/acmesh-official/acme.sh/wiki/Run-acme.sh-in-docker # # https://youtu.be/8Y1J-0mi5x4 (automatisch erneuern) # # # ################################################################################################################################# # ----- VARIABLEN ANPASSEN ----- WORKPATH="/volume1/homes//_wildcardcert" DOMAINLIST="-d *.domain1.com -d *.domain2.com -d domain1.com -d domain2.com" # jede Domain durch -d definieren! ACCOUNTMAIL="mail@example.com" ACME_METHOD=acme_docker # über welchen Weg soll acme.sh ausgeführt werden? acme_local oder acme_docker # acme_sh_SOURCE="https://raw.githubusercontent.com/Neilpang/acme.sh/master/acme.sh" acme_sh_SOURCE="https://raw.githubusercontent.com/acmesh-official/acme.sh/master/acme.sh" #-------------------------------------------------- ab hier nichts mehr ändern -------------------------------------------------- # CERTPATH: in diesen Verzeichnissen speichert der DSM die Zertifikate # bisher sind mir 2 Verzeichnisse bekannt: CERTPATH=("/usr/local/etc/certificate/" "/usr/syno/etc/certificate/") # Skript muss als root ausgeführt werden, da sonst kein Zugriff auf die Systemverzeichnisse möglich ist if [ $(whoami) != "root" ]; then echo "WARNUNG: das Skript muss von root ausgeführt werden!" exit 1 fi # Arbeitsordner ggf. erstellen if [ ! -d "$WORKPATH/LE_CERT-home" ]; then mkdir -p "$WORKPATH/LE_CERT-home" fi cd "$WORKPATH" cert_create () { # ----- ZERTIFIKAT VORBEREITEN ----- # create (Token für txt-Record): echo "Zertifikatserstellung vorbereiten:" echo "(folge den nachfolgenden Anweisungen im Output für den DNS txt-Record)" if [ $ACME_METHOD = acme_local ]; then ./acme.sh --force --issue --home "$WORKPATH" --config-home "$WORKPATH" --cert-home "$WORKPATH/LE_CERT-home" --accountemail "${ACCOUNTMAIL}" ${DOMAINLIST} --dns --yes-I-know-dns-manual-mode-enough-go-ahead-please elif [ $ACME_METHOD = acme_docker ]; then # docker help: https://github.com/acmesh-official/acme.sh/wiki/Run-acme.sh-in-docker docker run --rm -it -v "$WORKPATH":/acme.sh neilpang/acme.sh --home "/acme.sh" --issue ${DOMAINLIST} --config-home "/acme.sh" --cert-home "/acme.sh/LE_CERT-home" --accountemail "${ACCOUNTMAIL}" --dns --yes-I-know-dns-manual-mode-enough-go-ahead-please fi exit 0 } if [ $ACME_METHOD = acme_local ]; then echo -n "Download acme.sh ➜ " if [ -f ./acme.sh ]; then echo "ok (bereits vorhanden / Version: $(get_key_value ./acme.sh VER))" else wget -q "$acme_sh_SOURCE" if [ -f ./acme.sh ]; then echo "ok / Version: $(get_key_value ./acme.sh VER)" chmod a+x acme.sh else echo "fehlgeschlagen - das Skript wird beendet!" exit fi fi fi # initiale Zertifikatserstellung nur mit passendem Parameter (create) starten: if [[ $1 = create ]]; then cert_create fi # ----- PFADE FÜR DIE ZERTIFIKATE GENERIEREN ----- CERT_SUB_PATH=$(find "${WORKPATH}/LE_CERT-home" -name "*.key" -type f | head -n1 | sed -e "s%${WORKPATH}/LE_CERT-home%%g" ) # > den Fund könnte man noch mit der Domainliste abgleichen. So könnte man das Skript für mehrere Domains verwenden CERT_SUB_PATH_AB="${CERT_SUB_PATH%.*}" CERT_SUB_PATH_A="$(echo ${CERT_SUB_PATH_AB} | awk -F'/' '{print $2}' )" new_cert="$WORKPATH/LE_CERT-home${CERT_SUB_PATH_AB}.cer" # cert new_privatkey="$WORKPATH/LE_CERT-home${CERT_SUB_PATH_AB}.key" # private-key new_intermediate_cert="$WORKPATH/LE_CERT-home/${CERT_SUB_PATH_A}/ca.cer" # intermediate CA cert new_full_chain_certs="$WORKPATH/LE_CERT-home/${CERT_SUB_PATH_A}/fullchain.cer" # full_chain_certs # ----- ZERTIFIKATE ERSTELLEN / ERNEUERN ----- echo -n "erstelle / erneuere das Zertifikat ➜ " if [ $ACME_METHOD = acme_local ]; then # lokale Installation: acmeLOG=$(./acme.sh --log --debug 2 --force --home "$WORKPATH" --renew ${DOMAINLIST} --config-home "$WORKPATH" --cert-home "$WORKPATH/LE_CERT-home" --accountemail "${ACCOUNTMAIL}" --dns --yes-I-know-dns-manual-mode-enough-go-ahead-please ) elif [ $ACME_METHOD = acme_docker ]; then # docker help: https://github.com/acmesh-official/acme.sh/wiki/Run-acme.sh-in-docker acmeLOG=$(docker run --rm -it -v "$WORKPATH":/acme.sh neilpang/acme.sh --home "/acme.sh" --renew ${DOMAINLIST} --config-home "/acme.sh" --cert-home "/acme.sh/LE_CERT-home" --accountemail "${ACCOUNTMAIL}" --dns --yes-I-know-dns-manual-mode-enough-go-ahead-please ) # --debug und --log stehen im Dockerskript nicht zur Verfügung fi # Zertifikatserneuerung OK?: if [ $? = 0 ]; then if echo "$acmeLOG" | grep -q "Add the following TXT record|Please add the TXT records to the domains" ; then echo "! ! ! ACHTUNG ! ! ! TXT-Record muss erneuert werden" echo "folge den nachstehenden Instruktionen und führe anschließend das Skript erneut aus:" echo "$acmeLOG" | sed -e "s/^/ /g" exit 0 fi echo "ok" echo -e else echo "FEHLER! Skript wird beendet" echo "acme.sh - LOG:" echo "$acmeLOG" | sed -e "s/^/ /g" exit 1 fi # ----- BACKUP & COPY CERTIFICATE ----- copy_cert () { # diese Funktion # ➜ identifiziert das Default-Zertifikat # ➜ prüft die INFO-Datei, für welche Anwendung das Default-Zertifikat genutzt wird # ➜ erstellt ein Backup der alten Zertifikatsdateien # ➜ ersetzt die alten Zertifikatsdateien durch die erneuerten # ➜ überträgt die Rechte und Besitzer von den alten Zertifikatsdateien auf die neuen # ➜ startet im Abschluss den Webserver neu copy_cert_sub () { # Unterfunktion, da das Default-Zertifikat / Importpfad nicht durch die INFO abgedeckt wird if [ -d "${TARGETPATH}" ]; then BACKUPPATH="${BACKUPMAINPATH}/${SUBSCRIBER}" echo " Speicherpfad: ➜ $TARGETPATH" echo -n " Backup des alten Zertifikats ➜ " if [ ! -d "${BACKUPPATH}" ]; then mkdir -p "${BACKUPPATH}" fi # erstelle ein Backup für das aktuelle Verzeichnis rsync -aHcxv "${TARGETPATH}" "${BACKUPPATH}" 2>&1 >/dev/null if [ $? = 0 ]; then echo "ok" else echo " Backup fehlgeschlagen! Nächster Ordner:" return fi # identifiziere die Art der Dateiberechtigung: if echo $( synoacltool -get "${BACKUPPATH}/${SERVICE}/cert.pem" ) | grep -q is_support_ACL ; then permissions=acl else permissions=standard fi echo -n " Zertifikat ersetzen ➜ " cp -f "$new_cert" "${TARGETPATH}/cert.pem" cp -f "$new_privatkey" "${TARGETPATH}/privkey.pem" cp -f "$new_intermediate_cert" "${TARGETPATH}/chain.pem" cp -f "$new_full_chain_certs" "${TARGETPATH}/fullchain.pem" # kopierte Dateien verifizieren: copystate=0 if [[ $(/bin/md5sum "$new_cert" | awk '{print $1}') != $(/bin/md5sum "${TARGETPATH}/cert.pem" | awk '{print $1}') ]]; then copystate=1 elif [[ $(/bin/md5sum "$new_privatkey" | awk '{print $1}') != $(/bin/md5sum "${TARGETPATH}/privkey.pem" | awk '{print $1}') ]]; then copystate=1 elif [[ $(/bin/md5sum "$new_intermediate_cert" | awk '{print $1}') != $(/bin/md5sum "${TARGETPATH}/chain.pem" | awk '{print $1}') ]]; then copystate=1 elif [[ $(/bin/md5sum "$new_full_chain_certs" | awk '{print $1}') != $(/bin/md5sum "${TARGETPATH}/fullchain.pem" | awk '{print $1}') ]]; then copystate=1 fi if [[ $copystate = 1 ]]; then echo "fehlgeschlagen!" continue else echo "fertig" fi # Übertrage Dateiattribute: # ToDo: evtl. owner aus INFO übertragen echo -n " Übertrage Dateiattribute ➜ " if [ $permissions = acl ]; then echo "(ACL)" synoacltool -copy "${BACKUPPATH}/${SERVICE}/cert.pem" "${TARGETPATH}/cert.pem" synoacltool -copy "${BACKUPPATH}/${SERVICE}/privkey.pem" "${TARGETPATH}/privkey.pem" synoacltool -copy "${BACKUPPATH}/${SERVICE}/cert.pem" "${TARGETPATH}/cert.pem" synoacltool -copy "${BACKUPPATH}/${SERVICE}/cert.pem" "${TARGETPATH}/cert.pem" else echo "(Standard Linuxrechte)" cp --attributes-only -p "${BACKUPPATH}/${SERVICE}/cert.pem" "${TARGETPATH}/cert.pem" cp --attributes-only -p "${BACKUPPATH}/${SERVICE}/privkey.pem" "${TARGETPATH}/privkey.pem" cp --attributes-only -p "${BACKUPPATH}/${SERVICE}/chain.pem" "${TARGETPATH}/chain.pem" cp --attributes-only -p "${BACKUPPATH}/${SERVICE}/fullchain.pem" "${TARGETPATH}/fullchain.pem" fi echo -e fi } BACKUPMAINPATH="$WORKPATH/BackUP/$(date +%Y-%m-%d_%H-%M)" for count in $(cat /usr/syno/etc/certificate/_archive/INFO | jq -r ".$(cat /usr/syno/etc/certificate/_archive/DEFAULT).services | to_entries | .[] | .key" 2>/dev/null) ; do # Parameter aus INFO-File separieren (für DEFAULT-Zertifikat): string=$(cat /usr/syno/etc/certificate/_archive/INFO | jq -r ".$(cat /usr/syno/etc/certificate/_archive/DEFAULT).services | to_entries" | jq -r ".[${count}].value") SUBSCRIBER=$(echo "$string" | grep subscriber | awk '{print $2}' | sed -e "s/\"//g ; s/,$//g") # Hauptordner SERVICE=$(echo "$string" | grep service | awk '{print $2}' | sed -e "s/\"//g ; s/,$//g") # Subordner DISPLAY_NAME=$(echo "$string" | grep "\"display_name\"\:" | awk -F': ' '{print $2}' | sed -e "s/\"//g ; s/,$//g" ) OWNER=$(echo "$string" | grep owner | awk '{print $2}' | sed -e "s/\"//g ; s/,$//g") # Subordner isPkg=$(echo "$string" | grep isPkg | awk '{print $2}' | sed -e "s/\"//g ; s/,$//g") # Subordner # für Zielverzeichnis echo "aktualisiere Zertifikat für >>> ${DISPLAY_NAME} <<<" # suche das passende Verzeichnis für den Dienst und kopiere Zertifikat # (das passende Elternverzeichnis der einzelnen Dienstezertifikate ist nicht in der INFO hinterlegt, daher muss gesucht werden): for i in ${CERTPATH[@]}; do TARGETPATH="${i}${SUBSCRIBER}/${SERVICE}" copy_cert_sub done done # aktualisiere das Default-Zertifikat im Importverzeichnis: echo "aktualisiere noch das Zertifikat im Importverzeichnis: " SUBSCRIBER="DSM_ImportDirectory" SERVICE="$(cat /usr/syno/etc/certificate/_archive/DEFAULT)" TARGETPATH="/usr/syno/etc/certificate/_archive/$(cat /usr/syno/etc/certificate/_archive/DEFAULT)" copy_cert_sub echo "abschließend Webserver neustarten" if [ $(synogetkeyvalue /etc.defaults/VERSION majorversion) -ge 7 ]; then # (DSM7) /usr/bin/systemctl restart nginx else # (DSM6) /usr/syno/sbin/synoservicectl --reload nginx fi echo "kopiere Zertifikat nach Dockercontainer:" /volume3/docker/bitwarden_mprasil/copy_LE_Cert.sh /volume3/docker/gitea/copy_LE_Cert.sh } copy_cert exit 0