Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save aont/a1a83daffe219b0ad6e5d854cd6d2d5a to your computer and use it in GitHub Desktop.

Select an option

Save aont/a1a83daffe219b0ad6e5d854cd6d2d5a to your computer and use it in GitHub Desktop.

Automating TLS Certificate Renewal with systemd and acme.sh

[Unit]
Description=cert & deploy
After=multi-user.target
[Service]
Type=simple
ExecStart=/usr/libexec/acmebot/cert-deploy.sh
EnvironmentFile=/etc/acmebot/env
PrivateTmp=true
[Install]
WantedBy=multi-user.target
#!/bin/bash
# Must run as root
if (( EUID != 0 )); then
echo "This script must be run as root. Exiting."
exit 1
fi
set -xe
set -o pipefail
CERTPATH="${ACME_HOME}/${MAIN_DOMAIN}"
local_ip=""
cleanup() {
# Always close port even on failure
echo "[info] upnp port close" 1>&2
sudo -u"$EXECUTE_USER" -H upnpc -d 80 tcp || true
}
trap cleanup EXIT
# jq is required for this block
echo "[info] getting local address (ip -j route get + jq)" 1>&2
if ! command -v jq >/dev/null 2>&1; then
echo "[error] jq is not installed. Exiting." 1>&2
exit 1
fi
local_ip="$(ip -j route get 1.1.1.1 | jq -r '.[0].prefsrc // empty')"
if [ -z "$local_ip" ] || [ "$local_ip" = "null" ]; then
echo "[error] failed to detect local_ip via ip -j route get" 1>&2
exit 1
fi
echo "[info] local_ip=${local_ip}" 1>&2
# NOTE: This opens WAN:80 -> LAN:80.
echo "[info] upnp port open (WAN 80 -> LAN 80)" 1>&2
sudo -u"$EXECUTE_USER" -H upnpc -a "${local_ip}" 80 80 tcp 600
echo "[info] acme.sh (webroot)" 1>&2
# Check for the existence of acme.sh
if [ ! -x "$ACME_SH" ]; then
echo "[error] acme.sh not found: $ACME_SH" 1>&2
echo "[hint] install: curl https://get.acme.sh | sh -s email=$EMAIL" 1>&2
exit 1
fi
# Expand domain arguments into -d ...
# Normalize DOMAINLIST so that it works whether it is "a,b,c" or "a b c"
DOMAIN_ARGS=()
while read -r d; do
[ -n "$d" ] && DOMAIN_ARGS+=("-d" "$d")
done < <(echo "$DOMAINLIST" | tr ', ' '\n' | sed '/^$/d')
# Issue (or renew):
# --webroot for validation
# --home to fix the acme.sh home under LEPATH
# --accountemail for the account email
# --keylength for RSA 4096
#
# Important:
# If certificates already exist, they will only be renewed when necessary
# (this is acme.sh behavior)
set +e
sudo -u"$EXECUTE_USER" -H "$ACME_SH" \
--home "$ACME_HOME" \
--accountemail "$EMAIL" \
--issue --webroot "$WEBROOT" \
--keylength 4096 \
"${DOMAIN_ARGS[@]}"
ACME_STATUS=$?
set -e
if [ $ACME_STATUS -eq 2 ]; then
echo "[info] acme.sh exited with code $ACME_STATUS (renew not necessary)" 1>&2
exit
fi
if [ $ACME_STATUS -ne 0 ]; then
echo "[warn] acme.sh exited with code $ACME_STATUS" 1>&2
exit $ACME_STATUS
fi
# Deployment destinations (aligned with the original script paths)
NGINX_CERT_DIR="/etc/nginx/pki/certs"
IPSEC_PRIV="/etc/ipsec.d/private/privkey.pem"
IPSEC_CACERT="/etc/ipsec.d/cacerts/chain.pem"
IPSEC_CERT="/etc/ipsec.d/certs/fullchain.pem"
echo "[info] install certs (acme.sh --install-cert)" 1>&2
RELOAD_FLAG="/tmp/.reload-flag-$(date "+%Y%m%d%H%M%S%N")"
# acme.sh install is normally executed only when a renewal occurs
# (intended to be called after --issue)
# Bundle restarts/tests into --reloadcmd so that failures cause acme.sh to fail
"$ACME_SH" \
--home "$ACME_HOME" \
--install-cert -d "$MAIN_DOMAIN" \
--fullchain-file "${NGINX_CERT_DIR}/fullchain.pem" \
--key-file "${NGINX_CERT_DIR}/privkey.pem" \
--ca-file "${IPSEC_CACERT}" \
--reloadcmd "touch '${RELOAD_FLAG}'"
if [[ -f "${RELOAD_FLAG}" ]]; then
nginx -t
systemctl restart nginx
systemctl restart strongswan-starter
rm "${RELOAD_FLAG}"
fi
echo "[info] done" 1>&2
[Unit]
Description=Run certbot_update_cert periodically
[Timer]
OnCalendar=*-*-* 00:00:00
OnCalendar=*-*-* 12:00:00
RandomizedDelaySec=1h
Persistent=true
[Install]
WantedBy=timers.target
EXECUTE_USER=acmebot
DOMAINLIST=hoge.ddns.net
MAIN_DOMAIN=hoge.ddns.net
EMAIL=hoge@mail.com
ACME_HOME=/var/lib/acmebot/acme.sh
ACME_SH=/usr/libexec/acmebot/acme.sh
WEBROOT=/var/lib/acmebot/webroot
#!/bin/bash
# Must run as root
if (( EUID != 0 )); then
echo "This script must be run as root. Exiting."
exit 1
fi
set -xe
. ./env
mkdir -p --mode=0755 "${WEBROOT}"
mkdir -p --mode=0755 /etc/acmebot
install --mode=0400 ./env /etc/acmebot/
mkdir -p --mode=0755 /usr/libexec/acmebot/
install --mode=0555 ./cert-deploy.sh /usr/libexec/acmebot/
if [[ ! -f "${ACME_SH}" ]]; then
curl -o "${ACME_SH}" https://get.acme.sh
chmod 0755 "${ACME_SH}"
fi
install --mode=0644 cert-deploy.service /etc/systemd/system/
install --mode=0644 cert-deploy.timer /etc/systemd/system/
systemctl daemon-reload
systemctl enable cert-deploy.timer
systemctl start cert-deploy.timer
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment