Repository: JessThrysoee/synology-letsencrypt Branch: master Commit: 358e6ab75ea9 Files: 7 Total size: 15.0 KB Directory structure: gitextract_j5uwdl9i/ ├── .gitignore ├── README.md ├── install.sh ├── synology-letsencrypt-lib.sh ├── synology-letsencrypt-reload-services.sh ├── synology-letsencrypt.sh └── uninstall.sh ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .vscode/ PRIVATE_NOTES.txt .idea/* ================================================ FILE: README.md ================================================ # synology-letsencrypt Create and manage a [Let's Encrypt](https://letsencrypt.org/) certificate on a Synology NAS. This project uses [lego](https://go-acme.github.io/lego/) and the [ACME DNS-01 challenge](https://letsencrypt.org/docs/challenge-types/#dns-01-challenge) with any supported [DNS provider](https://go-acme.github.io/lego/dns/). ## Install & Update Script To **install** or **update** `synology-letsencrypt`, run the [install script](install.sh). You can either download and run the script manually, or use the following curl command: ```sh curl -sSL https://raw.githubusercontent.com/JessThrysoee/synology-letsencrypt/master/install.sh | bash ``` The script must be run as root. You can SSH into your NAS as an admin user and then run `sudo -i` to become root (using the same password as the admin user). > [!IMPORTANT] > Migration from `lego` v4 to v5 If you are updating from a version of `lego` earlier than v5, note that v5 introduces breaking changes to the CLI, directory structure, and JSON file format. After running the [install script](install.sh) to update `lego` and this repository's scripts to the latest versions, run the new v5 command `lego migrate` before running any other commands. For example: /usr/local/bin/lego migrate --path /usr/local/etc/synology-letsencrypt/ More information is available in the [v5 blog post](https://ldez.github.io/blog/2026/05/11/lego-v5/). Also note that the optional environment variables `LEGO_RUN_OPTIONS` and `LEGO_RENEW_OPTIONS` in your `env` file have been replaced with a single optional variable, `LEGO_OPTIONS`. ## Configuration Update `/usr/local/etc/synology-letsencrypt/env` with your domain(s), email address, and DNS API key: ```sh DOMAINS=(--domains "example.com" --domains "*.example.com") EMAIL="user@example.com" # Specify the DNS provider (this example is from https://go-acme.github.io/lego/dns/simply/) DNS_PROVIDER="simply" export SIMPLY_ACCOUNT_NAME=XXXXXXX export SIMPLY_API_KEY=XXXXXXXXXX export SIMPLY_PROPAGATION_TIMEOUT=1800 export SIMPLY_POLLING_INTERVAL=30 # Should you need it; additional options can be passed directly to lego #LEGO_OPTIONS=(--key-type "RSA4096" --ari-disable --server "letsencrypt-staging") ``` You should now be able to run `/usr/local/bin/synology-letsencrypt.sh`. To schedule a daily task, log in to Synology DSM and add a user-defined script: Synology DSM -> Control Panel -> Task Scheduler Create -> Scheduled Task -> User-defined script General -> User = root Task Settings -> User-defined script = /bin/bash /usr/local/bin/synology-letsencrypt.sh To secure services with the certificate, see the [Configure Certificates](https://kb.synology.com/en-global/DSM/help/DSM/AdminCenter/connection_certificate?version=7#b_64) documentation. ### Multiple Certificates If you need to generate multiple certificates, you can run `synology-letsencrypt.sh` with the path to a certificate-specific configuration: ```shellsession $ /usr/local/bin/synology-letsencrypt.sh -p /usr/local/etc/synology-letsencrypt/example.com $ /usr/local/bin/synology-letsencrypt.sh -p /usr/local/etc/synology-letsencrypt/other-example.com ``` This creates a separate configuration in `/usr/local/etc/synology-letsencrypt/example.com/env` and `/usr/local/etc/synology-letsencrypt/other-example.com/env`, respectively. You can then customize each one as needed, including the `hook` file in each configuration. This is useful if you need more than one certificate on your Synology or want to generate a certificate for another host managed by the Synology. ### Customizing the hook script By default, `synology-letsencrypt.sh` overwrites any changes you make to the hook script to preserve core functionality. If you have customized the hook script, you can preserve your changes by adding the `-c` option when running the command: ```shellsession $ /usr/local/bin/synology-letsencrypt.sh -c ``` ## Uninstall To **uninstall** `synology-letsencrypt`, run the [uninstall script](uninstall.sh). You can either download and run the script manually, or use the following curl command: ```sh curl -sSL https://raw.githubusercontent.com/JessThrysoee/synology-letsencrypt/master/uninstall.sh | bash ``` ## Consider the [acme-dns](https://github.com/joohoi/acme-dns) project ...if your DNS provider is not _directly_ supported by `lego`, or if you want to avoid storing your DNS provider's API keys on your Synology device. `lego` supports [acme-dns](https://go-acme.github.io/lego/dns/acme-dns/). ================================================ FILE: install.sh ================================================ #!/bin/bash { arch_map() { local arch arch=$(uname -m) case "$arch" in x86_64) echo "amd64" ;; aarch64) echo "arm64" ;; armv7l) echo "armv7" ;; i686|i386) echo "386" ;; *) return 1 ;; esac } while getopts ":a:h" opt; do case $opt in a) ARCH="$OPTARG";; h) echo "Usage: $0 [-a ]" echo " -a Architecture of lego to install (auto-detect: $(arch_map || echo "unsupported"))" exit 0 ;; :) echo "Error: -${OPTARG} requires an argument.";; \?) echo "Invalid option -$OPTARG" >&2 ;; esac done if [[ -z $ARCH ]]; then ARCH=$(arch_map) || { echo "Error: Unsupported architecture '$(uname -m)'. Use -a to specify manually." >&2 exit 1 } fi permissions() { local mod="$1" local path="$2" sudo chown root:root "$path" sudo chmod "$mod" "$path" } install_lego() { local path="/usr/local/bin/lego" local url url="$( curl -sSL "https://api.github.com/repos/go-acme/lego/releases/latest" \ | jq --unbuffered -r --arg arch "$ARCH" '.assets[].browser_download_url | select(.|endswith("linux_\($arch).tar.gz"))' )" if [[ -z $url ]]; then echo "Could not find lego download URL for architecture '$ARCH'! Try a different architecture maybe? See '$0 -h'" >&2 exit 1 fi curl -sSL "$url" \ | sudo tar -zx -C "${path%/*}" -- "${path##*/}" permissions 755 "$path" printf "installed: %s\n" "$path" } install_script() { local name="$1" local path="/usr/local/bin/$name" sudo curl -sSL -o "$path" "https://raw.githubusercontent.com/JessThrysoee/synology-letsencrypt/master/$name" permissions 755 "$path" printf "installed: %s\n" "$path" } install_configuration() { local dir="/usr/local/etc/synology-letsencrypt" local env="$dir/env" sudo mkdir -p "$dir" permissions 700 "$dir" if [[ ! -s $env ]]; then sudo tee "$env" > /dev/null < "$cert_id_path" fi mkdir -p "$archive_path/$cert_id" local info="$archive_path/INFO" if [[ -s $info ]]; then local has_cert_id has_cert_id="$(jq --arg cert_id "$cert_id" 'has($cert_id)' "$info")" if [[ $has_cert_id != true ]]; then # append local tmp_info tmp_info=$(mktemp) jq --arg cert_id "$cert_id" '.[$cert_id] = { desc: "", services: [] }' < "$info" > "$tmp_info" \ && \mv "$tmp_info" "$info" fi else # create jq -n --arg cert_id "$cert_id" '{ ($cert_id) : { desc: "", services: [] } }' > "$info" fi } # implement `lego`SanitizedName [ref.: https://github.com/go-acme/lego/blob/f9f9645cf7be7d399c025ec596484263eb9f963a/cmd/internal/storage/storage_certificates.go#L67] sanitizedName() { if command -v python3 &>/dev/null; then python3 -c ' import sys name = sys.argv[1] try: safe = name.replace(":", "-").replace("*", "_").encode("idna").decode("ascii") except Exception as e: sys.stderr.write("Could not sanitize the name: %s\n" % e) sys.exit(1) out = "".join(ch for ch in safe if ch.isalnum() or ch in "-_.@") sys.stdout.write(out) ' "$1" || return $? else python2 -c ' import sys name = sys.argv[1] try: safe = name.decode("utf-8").replace(":", "-").replace("*", "_").encode("idna") except Exception as e: sys.stderr.write("Could not sanitize the name: %s\n" % e) sys.exit(1) out = "".join(ch for ch in safe if ch.isalnum() or ch in "-_.@") sys.stdout.write(out) ' "$1" || return $? fi } ================================================ FILE: synology-letsencrypt-reload-services.sh ================================================ #!/bin/bash -e [[ $EUID == 0 ]] || { echo >&2 "This script must be run as root"; exit 1; } # Reload services assigned to the certificate with the key `cert_id` in the INFO file. # Inspired by https://github.com/bartowl/synology-stuff/blob/master/reload-certs.sh CERT_ID="$1" ARCHIVE_PATH="/usr/syno/etc/certificate/_archive" INFO="$ARCHIVE_PATH/INFO" get() { local i="$1" prop="$2" jq -r --arg cert_id "$CERT_ID" --arg i "$i" --arg prop "$prop" '.[$cert_id].services[$i|tonumber][$prop]' "$INFO" } find_exec_path() { local subscriber="$1" # search DSM6 and DSM7 paths for base in /usr/libexec/certificate.d /usr/local/libexec/certificate.d \ /usr/syno/share/certificate.d /usr/local/share/certificate.d do script="$base/$subscriber" if [[ -x "$script" ]]; then printf '%s' "$script" break fi done } find_cert_path() { local subscriber="$1" service="$2" for base in /usr/local/etc/certificate /usr/syno/etc/certificate; do dir="$base/$subscriber/$service" if [[ -e "$dir" ]]; then printf '%s' "$dir" break fi done } reload_services() { services_length=$(jq -r --arg cert_id "$CERT_ID" '.[$cert_id].services|length' "$INFO") for (( i = 0; i < services_length; i++ )); do subscriber=$(get "$i" subscriber) service=$(get "$i" service) cert_path="$(find_cert_path "$subscriber" "$service")" if [[ -z $cert_path ]]; then echo >&2 "cert_path not found in for \"$subscriber\" \"$service\"" continue fi if diff -q "$ARCHIVE_PATH/$CERT_ID/cert.pem" "$cert_path/cert.pem" >/dev/null; then continue # no change fi cp "$ARCHIVE_PATH/$CERT_ID/"{cert,chain,fullchain,privkey}.pem "$cert_path/" exec_path="$(find_exec_path "$subscriber")" if [[ -x $exec_path ]]; then "$exec_path" "$service" fi profile_exec_script="${subscriber}.sh" if [[ $subscriber == "system" && $service == "default" ]]; then profile_exec_script="dsm.sh" fi profile_exec_path="/usr/libexec/security-profile/tls-profile/$profile_exec_script" if [[ -x $profile_exec_path ]]; then "$profile_exec_path" fi done } reload_services ================================================ FILE: synology-letsencrypt.sh ================================================ #!/bin/bash -e [[ $EUID == 0 ]] || { echo >&2 "This script must be run as root"; exit 1; } while getopts ":p:ch" opt; do case $opt in p) LEGO_PATH="$OPTARG" ;; c) CREATE_HOOK=false ;; h) echo "Usage: $0 [options]" echo " -p The path where Lego will install your certs" echo " -c Suppress [c]reation of the hook scripts, if you have your own" exit 0 ;; :) echo "Error: -${OPTARG} requires an argument" >&2 ;; \?) echo "Invalid option -$OPTARG" >&2 ;; esac done source /usr/local/bin/synology-letsencrypt-lib.sh LEGO_PATH=${LEGO_PATH:-/usr/local/etc/synology-letsencrypt} CREATE_HOOK=${CREATE_HOOK:-true} source "$LEGO_PATH/env" export LEGO_PATH archive_path="/usr/syno/etc/certificate/_archive" cert_path="$LEGO_PATH/certificates" hook_path="$LEGO_PATH/hook" mkdir -p "$cert_path" cert_domain="$(sanitizedName "${DOMAINS[1]}")" ## cert_id cert_id_path="$cert_path/$cert_domain.cert_id" makeCertId "$cert_id_path" "$archive_path" source "$cert_id_path" if [[ -z $cert_id ]]; then echo >&2 "ID not found in $cert_id_path" exit 1 fi ## install hook archive_cert_path="$archive_path/$cert_id" if [[ ! -d $archive_cert_path ]]; then mkdir -p "$archive_cert_path" fi if [[ ${CREATE_HOOK} == true ]]; then cat >"$hook_path" < Control Panel -> Security -> Certificate" echo "" } uninstall() { uninstall_script "lego" uninstall_script "synology-letsencrypt.sh" uninstall_script "synology-letsencrypt-reload-services.sh" uninstall_script "synology-letsencrypt-lib.sh" uninstall_deprecated_script "synology-letsencrypt-make-cert-id.sh" uninstall_configuration uninstall_cert_message } uninstall }