#!/bin/sh # # Download and run the latest release version of the Certbot client. # # NOTE: THIS SCRIPT IS AUTO-GENERATED AND SELF-UPDATING # # IF YOU WANT TO EDIT IT LOCALLY, *ALWAYS* RUN YOUR COPY WITH THE # "--no-self-upgrade" FLAG # # IF YOU WANT TO SEND PULL REQUESTS, THE REAL SOURCE FOR THIS FILE IS # letsencrypt-auto-source/letsencrypt-auto.template AND # letsencrypt-auto-source/pieces/bootstrappers/* set -e # Work even if somebody does "sh thisscript.sh". # Note: you can set XDG_DATA_HOME or VENV_PATH before running this script, # if you want to change where the virtual environment will be installed # HOME might not be defined when being run through something like systemd if [ -z "$HOME" ]; then HOME=~root fi if [ -z "$XDG_DATA_HOME" ]; then XDG_DATA_HOME=~/.local/share fi if [ -z "$VENV_PATH" ]; then OLD_VENV_PATH="$XDG_DATA_HOME/letsencrypt" VENV_PATH="/opt/eff.org/certbot/venv" fi VENV_BIN="$VENV_PATH/bin" LE_AUTO_VERSION="{{ LE_AUTO_VERSION }}" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates to both this script and certbot will be downloaded and installed. After ensuring you have the latest versions installed, certbot will be invoked with all arguments you have provided. Help for certbot itself cannot be provided until it is installed. --debug attempt experimental installation -h, --help print this help -n, --non-interactive, --noninteractive run without asking for user input --no-bootstrap do not install OS dependencies --no-self-upgrade do not download updates --os-packages-only install OS dependencies and exit -v, --verbose provide more output -q, --quiet provide only update/error output; implies --non-interactive All arguments are accepted and forwarded to the Certbot client when run." export CERTBOT_AUTO="$0" for arg in "$@" ; do case "$arg" in --debug) DEBUG=1;; --os-packages-only) OS_PACKAGES_ONLY=1;; --no-self-upgrade) # Do not upgrade this script (also prevents client upgrades, because each # copy of the script pins a hash of the python client) NO_SELF_UPGRADE=1;; --no-bootstrap) NO_BOOTSTRAP=1;; --help) HELP=1;; --noninteractive|--non-interactive|renew) ASSUME_YES=1;; --quiet) QUIET=1;; --verbose) VERBOSE=1;; -[!-]*) OPTIND=1 while getopts ":hnvq" short_arg $arg; do case "$short_arg" in h) HELP=1;; n) ASSUME_YES=1;; q) QUIET=1;; v) VERBOSE=1;; esac done;; esac done if [ $BASENAME = "letsencrypt-auto" ]; then # letsencrypt-auto does not respect --help or --yes for backwards compatibility ASSUME_YES=1 HELP=0 fi # Set ASSUME_YES to 1 if QUIET (i.e. --quiet implies --non-interactive) if [ "$QUIET" = 1 ]; then ASSUME_YES=1 fi say() { if [ "$QUIET" != 1 ]; then echo "$@" fi } error() { echo "$@" } # Support for busybox and others where there is no "command", # but "which" instead if command -v command > /dev/null 2>&1 ; then export EXISTS="command -v" elif which which > /dev/null 2>&1 ; then export EXISTS="which" else error "Cannot find command nor which... please install one!" exit 1 fi # Certbot itself needs root access for almost all modes of operation. # certbot-auto needs root access to bootstrap OS dependencies and install # Certbot a protected path so it can be safely run as root. To accomplish this, # this script will attempt to run itself as root if it doesn't have the # necessary privileges by using `sudo` or falling back to `su` if it is not # available. The mechanism used to obtain root access can set explicitly by the # user by overriding the environment variable LE_AUTO_SUDO to 'sudo', # 'su_sudo', or '' as used below. # Run the specified command preserving select environment variables. If the # command starts with `sudo`, the environment is still properly modified. run_with_env() { prefix="" if [ "$1" = "sudo" ]; then prefix="sudo" shift 1 fi $prefix LE_AUTO_DIR_TEMPLATE="$LE_AUTO_DIR_TEMPLATE" LE_AUTO_JSON_URL="$LE_AUTO_JSON_URL" LE_AUTO_PUBLIC_KEY="$LE_AUTO_PUBLIC_KEY" LE_PYTHON="$LE_PYTHON" OLD_VENV_PATH="$OLD_VENV_PATH" PIP_FIND_LINKS="$PIP_FIND_LINKS" VENV_PATH="$VENV_PATH" "$@" } # We need to preserve certain environment variables and because the parameters # in `su -c` has to be a string, we need to properly escape it. su_sudo() { args="" # This `while` loop iterates over all parameters given to this function. # For each parameter, all `'` will be replace by `'"'"'`, and the escaped string # will be wrapped in a pair of `'`, then appended to `$args` string # For example, `echo "It's only 1\$\!"` will be escaped to: # 'echo' 'It'"'"'s only 1$!' # │ │└┼┘│ # │ │ │ └── `'s only 1$!'` the literal string # │ │ └── `\"'\"` is a single quote (as a string) # │ └── `'It'`, to be concatenated with the strings following it # └── `echo` wrapped in a pair of `'`, it's totally fine for the shell command itself while [ $# -ne 0 ]; do args="$args'$(printf "%s" "$1" | sed -e "s/'/'\"'\"'/g")' " shift done run_with_env su root -c "$args" } if [ "$1" = "--cb-auto-has-root" ]; then shift 1 elif [ "$1" != "--le-auto-phase2" ]; then # if $1 is --le-auto-phase2, we've executed this branch before SUDO_ENV="" if [ -n "${LE_AUTO_SUDO+x}" ]; then case "$LE_AUTO_SUDO" in su_sudo|su) SUDO=su_sudo ;; sudo) SUDO="run_with_env sudo" ;; '') ;; # Nothing to do for plain root method. *) error "Error: unknown root authorization mechanism '$LE_AUTO_SUDO'." exit 1 esac say "Using preset root authorization mechanism '$LE_AUTO_SUDO'." else if test "`id -u`" -ne "0" ; then if $EXISTS sudo 1>/dev/null 2>&1; then SUDO="run_with_env sudo" else say \"sudo\" is not available, will use \"su\" for installation steps... SUDO=su_sudo fi else SUDO= fi fi $SUDO "$0" --cb-auto-has-root "$@" exit 0 fi BootstrapMessage() { # Arguments: Platform name say "Bootstrapping dependencies for $1... (you can skip this with --no-bootstrap)" } ExperimentalBootstrap() { # Arguments: Platform name, bootstrap function name if [ "$DEBUG" = 1 ]; then if [ "$2" != "" ]; then BootstrapMessage $1 $2 fi else error "FATAL: $1 support is very experimental at present..." error "if you would like to work on improving it, please ensure you have backups" error "and then run this script again with the --debug flag!" error "Alternatively, you can install OS dependencies yourself and run this script" error "again with --no-bootstrap." exit 1 fi } DeprecationBootstrap() { # Arguments: Platform name, bootstrap function name if [ "$DEBUG" = 1 ]; then if [ "$2" != "" ]; then BootstrapMessage $1 $2 fi else error "WARNING: certbot-auto support for this $1 is DEPRECATED!" error "Please visit certbot.eff.org to learn how to download a version of" error "Certbot that is packaged for your system. While an existing version" error "of certbot-auto may work currently, we have stopped supporting updating" error "system packages for your system. Please switch to a packaged version" error "as soon as possible." exit 1 fi } DeterminePythonVersion() { for LE_PYTHON in "$LE_PYTHON" python2.7 python27 python2 python; do # Break (while keeping the LE_PYTHON value) if found. $EXISTS "$LE_PYTHON" > /dev/null && break done if [ "$?" != "0" ]; then error "Cannot find any Pythons; please install one!" exit 1 fi export LE_PYTHON PYVER=`"$LE_PYTHON" -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//'` if [ "$PYVER" -lt 26 ]; then error "You have an ancient version of Python entombed in your operating system..." error "This isn't going to work; you'll need at least version 2.6." exit 1 fi } {{ bootstrappers/deb_common.sh }} {{ bootstrappers/rpm_common.sh }} {{ bootstrappers/suse_common.sh }} {{ bootstrappers/arch_common.sh }} {{ bootstrappers/gentoo_common.sh }} {{ bootstrappers/free_bsd.sh }} {{ bootstrappers/mac.sh }} {{ bootstrappers/smartos.sh }} {{ bootstrappers/mageia_common.sh }} # Install required OS packages: Bootstrap() { if [ "$NO_BOOTSTRAP" = 1 ]; then return elif [ -f /etc/debian_version ]; then BootstrapMessage "Debian-based OSes" BootstrapDebCommon elif [ -f /etc/mageia-release ]; then # Mageia has both /etc/mageia-release and /etc/redhat-release ExperimentalBootstrap "Mageia" BootstrapMageiaCommon elif [ -f /etc/redhat-release ]; then BootstrapMessage "RedHat-based OSes" BootstrapRpmCommon elif [ -f /etc/os-release ] && `grep -q openSUSE /etc/os-release` ; then BootstrapMessage "openSUSE-based OSes" BootstrapSuseCommon elif [ -f /etc/arch-release ]; then if [ "$DEBUG" = 1 ]; then BootstrapMessage "Archlinux" BootstrapArchCommon else error "Please use pacman to install letsencrypt packages:" error "# pacman -S certbot certbot-apache" error error "If you would like to use the virtualenv way, please run the script again with the" error "--debug flag." exit 1 fi elif [ -f /etc/manjaro-release ]; then ExperimentalBootstrap "Manjaro Linux" BootstrapArchCommon elif [ -f /etc/gentoo-release ]; then DeprecationBootstrap "Gentoo" BootstrapGentooCommon elif uname | grep -iq FreeBSD ; then DeprecationBootstrap "FreeBSD" BootstrapFreeBsd elif uname | grep -iq Darwin ; then DeprecationBootstrap "macOS" BootstrapMac elif [ -f /etc/issue ] && grep -iq "Amazon Linux" /etc/issue ; then ExperimentalBootstrap "Amazon Linux" BootstrapRpmCommon elif [ -f /etc/product ] && grep -q "Joyent Instance" /etc/product ; then ExperimentalBootstrap "Joyent SmartOS Zone" BootstrapSmartOS else error "Sorry, I don't know how to bootstrap Certbot on your operating system!" error error "You will need to install OS dependencies, configure virtualenv, and run pip install manually." error "Please see https://letsencrypt.readthedocs.org/en/latest/contributing.html#prerequisites" error "for more info." exit 1 fi } TempDir() { mktemp -d 2>/dev/null || mktemp -d -t 'le' # Linux || macOS } if [ "$1" = "--le-auto-phase2" ]; then # Phase 2: Create venv, install LE, and run. shift 1 # the --le-auto-phase2 arg if [ -f "$VENV_BIN/letsencrypt" ]; then # --version output ran through grep due to python-cryptography DeprecationWarnings # grep for both certbot and letsencrypt until certbot and shim packages have been released INSTALLED_VERSION=$("$VENV_BIN/letsencrypt" --version 2>&1 | grep "^certbot\|^letsencrypt" | cut -d " " -f 2) if [ -z "$INSTALLED_VERSION" ]; then error "Error: couldn't get currently installed version for $VENV_BIN/letsencrypt: " 1>&2 "$VENV_BIN/letsencrypt" --version exit 1 fi else INSTALLED_VERSION="none" fi if [ "$LE_AUTO_VERSION" != "$INSTALLED_VERSION" ]; then say "Creating virtual environment..." DeterminePythonVersion rm -rf "$VENV_PATH" if [ "$VERBOSE" = 1 ]; then virtualenv --no-site-packages --python "$LE_PYTHON" "$VENV_PATH" else virtualenv --no-site-packages --python "$LE_PYTHON" "$VENV_PATH" > /dev/null fi say "Installing Python packages..." TEMP_DIR=$(TempDir) trap 'rm -rf "$TEMP_DIR"' EXIT # There is no $ interpolation due to quotes on starting heredoc delimiter. # ------------------------------------------------------------------------- cat << "UNLIKELY_EOF" > "$TEMP_DIR/letsencrypt-auto-requirements.txt" {{ dependency-requirements.txt }} {{ letsencrypt-requirements.txt }} {{ certbot-requirements.txt }} UNLIKELY_EOF # ------------------------------------------------------------------------- cat << "UNLIKELY_EOF" > "$TEMP_DIR/pipstrap.py" {{ pipstrap.py }} UNLIKELY_EOF # ------------------------------------------------------------------------- # Set PATH so pipstrap upgrades the right (v)env: PATH="$VENV_BIN:$PATH" "$VENV_BIN/python" "$TEMP_DIR/pipstrap.py" set +e if [ "$VERBOSE" = 1 ]; then "$VENV_BIN/pip" install --disable-pip-version-check --no-cache-dir --require-hashes -r "$TEMP_DIR/letsencrypt-auto-requirements.txt" else PIP_OUT=`"$VENV_BIN/pip" install --disable-pip-version-check --no-cache-dir --require-hashes -r "$TEMP_DIR/letsencrypt-auto-requirements.txt" 2>&1` fi PIP_STATUS=$? set -e if [ "$PIP_STATUS" != 0 ]; then # Report error. (Otherwise, be quiet.) error "Had a problem while installing Python packages." if [ "$VERBOSE" != 1 ]; then error error "pip prints the following errors: " error "=====================================================" error "$PIP_OUT" error "=====================================================" error error "Certbot has problem setting up the virtual environment." if `echo $PIP_OUT | grep -q Killed` || `echo $PIP_OUT | grep -q "allocate memory"` ; then error error "Based on your pip output, the problem can likely be fixed by " error "increasing the available memory." else error error "We were not be able to guess the right solution from your pip " error "output." fi error error "Consult https://certbot.eff.org/docs/install.html#problems-with-python-virtual-environment" error "for possible solutions." error "You may also find some support resources at https://certbot.eff.org/support/ ." fi rm -rf "$VENV_PATH" exit 1 fi if [ -d "$OLD_VENV_PATH" -a ! -L "$OLD_VENV_PATH" ]; then rm -rf "$OLD_VENV_PATH" ln -s "$VENV_PATH" "$OLD_VENV_PATH" fi say "Installation succeeded." fi "$VENV_BIN/letsencrypt" "$@" else # Phase 1: Upgrade certbot-auto if necessary, then self-invoke. # # Each phase checks the version of only the thing it is responsible for # upgrading. Phase 1 checks the version of the latest release of # certbot-auto (which is always the same as that of the certbot # package). Phase 2 checks the version of the locally installed certbot. if [ ! -f "$VENV_BIN/letsencrypt" ]; then if [ "$HELP" = 1 ]; then echo "$USAGE" exit 0 fi # If it looks like we've never bootstrapped before, bootstrap: Bootstrap fi if [ "$OS_PACKAGES_ONLY" = 1 ]; then say "OS packages installed." exit 0 fi if [ "$NO_SELF_UPGRADE" != 1 ]; then TEMP_DIR=$(TempDir) trap 'rm -rf "$TEMP_DIR"' EXIT # --------------------------------------------------------------------------- cat << "UNLIKELY_EOF" > "$TEMP_DIR/fetch.py" {{ fetch.py }} UNLIKELY_EOF # --------------------------------------------------------------------------- DeterminePythonVersion if ! REMOTE_VERSION=`"$LE_PYTHON" "$TEMP_DIR/fetch.py" --latest-version` ; then error "WARNING: unable to check for updates." elif [ "$LE_AUTO_VERSION" != "$REMOTE_VERSION" ]; then say "Upgrading certbot-auto $LE_AUTO_VERSION to $REMOTE_VERSION..." # Now we drop into Python so we don't have to install even more # dependencies (curl, etc.), for better flow control, and for the option of # future Windows compatibility. "$LE_PYTHON" "$TEMP_DIR/fetch.py" --le-auto-script "v$REMOTE_VERSION" # Install new copy of certbot-auto. # TODO: Deal with quotes in pathnames. say "Replacing certbot-auto..." # Clone permissions with cp. chmod and chown don't have a --reference # option on macOS or BSD, and stat -c on Linux is stat -f on macOS and BSD: cp -p "$0" "$TEMP_DIR/letsencrypt-auto.permission-clone" cp "$TEMP_DIR/letsencrypt-auto" "$TEMP_DIR/letsencrypt-auto.permission-clone" # Using mv rather than cp leaves the old file descriptor pointing to the # original copy so the shell can continue to read it unmolested. mv across # filesystems is non-atomic, doing `rm dest, cp src dest, rm src`, but the # cp is unlikely to fail (esp. under sudo) if the rm doesn't. mv -f "$TEMP_DIR/letsencrypt-auto.permission-clone" "$0" fi # A newer version is available. fi # Self-upgrading is allowed. "$0" --le-auto-phase2 "$@" fi