diff --git a/bootstrap/_arch_common.sh b/bootstrap/_arch_common.sh index f66067ffb..47361c6c4 100755 --- a/bootstrap/_arch_common.sh +++ b/bootstrap/_arch_common.sh @@ -20,8 +20,8 @@ deps=" pkg-config " -missing=$(pacman -T $deps) +missing=$("$SUDO" pacman -T $deps) if [ "$missing" ]; then - pacman -S --needed $missing + "$SUDO" pacman -S --needed $missing fi diff --git a/bootstrap/_gentoo_common.sh b/bootstrap/_gentoo_common.sh index a718db7ff..164312c86 100755 --- a/bootstrap/_gentoo_common.sh +++ b/bootstrap/_gentoo_common.sh @@ -12,12 +12,12 @@ PACKAGES="dev-vcs/git case "$PACKAGE_MANAGER" in (paludis) - cave resolve --keep-targets if-possible $PACKAGES -x + "$SUDO" cave resolve --keep-targets if-possible $PACKAGES -x ;; (pkgcore) - pmerge --noreplace $PACKAGES + "$SUDO" pmerge --noreplace $PACKAGES ;; (portage|*) - emerge --noreplace $PACKAGES + "$SUDO" emerge --noreplace $PACKAGES ;; esac diff --git a/bootstrap/freebsd.sh b/bootstrap/freebsd.sh index 180ee21b4..d16b1a5bb 100755 --- a/bootstrap/freebsd.sh +++ b/bootstrap/freebsd.sh @@ -1,6 +1,6 @@ #!/bin/sh -xe -pkg install -Ay \ +"$SUDO" pkg install -Ay \ git \ python \ py27-virtualenv \ diff --git a/docs/contributing.rst b/docs/contributing.rst index c71aefeec..0fd7593ff 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -359,75 +359,37 @@ Now run tests inside the Docker image: Notes on OS dependencies ======================== -OS level dependencies are managed by scripts in ``bootstrap``. Some notes -are provided here mainly for the :ref:`developers ` reference. +OS-level dependencies can be installed like so: -In general: +.. code-block:: shell + + letsencrypt-auto/letsencrypt-auto --os-packages-only + +In general... * ``sudo`` is required as a suggested way of running privileged process * `Augeas`_ is required for the Python bindings * ``virtualenv`` and ``pip`` are used for managing other python library dependencies +What follow are OS-specific notes for the :ref:`developers ` reference. + .. _Augeas: http://augeas.net/ .. _Virtualenv: https://virtualenv.pypa.io -Ubuntu ------- - -.. code-block:: shell - - sudo ./bootstrap/ubuntu.sh - Debian ------ -.. code-block:: shell - - sudo ./bootstrap/debian.sh - For squeeze you will need to: - Use ``virtualenv --no-site-packages -p python`` instead of ``-p python2``. -.. _`#280`: https://github.com/letsencrypt/letsencrypt/issues/280 - - -Mac OSX -------- - -.. code-block:: shell - - ./bootstrap/mac.sh - - -Fedora ------- - -.. code-block:: shell - - sudo ./bootstrap/fedora.sh - - -Centos 7 --------- - -.. code-block:: shell - - sudo ./bootstrap/centos.sh - - FreeBSD ------- -.. code-block:: shell - - sudo ./bootstrap/freebsd.sh - -Bootstrap script for FreeBSD uses ``pkg`` for package installation, -i.e. it does not use ports. +Package installation for FreeBSD uses ``pkg``, not ports. FreeBSD by default uses ``tcsh``. In order to activate virtualenv (see below), you will need a compatible shell, e.g. ``pkg install bash && diff --git a/letsencrypt-auto b/letsencrypt_auto/letsencrypt-auto.template similarity index 57% rename from letsencrypt-auto rename to letsencrypt_auto/letsencrypt-auto.template index a3009fe52..b67f87e54 100755 --- a/letsencrypt-auto +++ b/letsencrypt_auto/letsencrypt-auto.template @@ -1,12 +1,8 @@ -#!/bin/sh -e +#!/bin/sh # -# A script to run the latest release version of the Let's Encrypt in a -# virtual environment -# -# Installs and updates the letencrypt virtualenv, and runs letsencrypt -# using that virtual environment. This allows the client to function decently -# without requiring specific versions of its dependencies from the operating -# system. +# Download and run the latest release version of the Let's Encrypt client. + +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 @@ -15,6 +11,77 @@ VENV_NAME="letsencrypt" VENV_PATH=${VENV_PATH:-"$XDG_DATA_HOME/$VENV_NAME"} VENV_BIN=${VENV_PATH}/bin +ExperimentalBootstrap() { + # Arguments: Platform name, bootstrap function name + if [ "$DEBUG" = 1 ] ; then + if [ "$2" != "" ] ; then + echo "Bootstrapping dependencies via $1..." + $2 + fi + else + echo "WARNING: $1 support is very experimental at present..." + echo "if you would like to work on improving it, please ensure you have backups" + echo "and then run this script again with the --debug flag!" + exit 1 + fi +} + +DeterminePythonVersion() { + if command -v python2.7 > /dev/null ; then + export LE_PYTHON=${LE_PYTHON:-python2.7} + elif command -v python27 > /dev/null ; then + export LE_PYTHON=${LE_PYTHON:-python27} + elif command -v python2 > /dev/null ; then + export LE_PYTHON=${LE_PYTHON:-python2} + elif command -v python > /dev/null ; then + export LE_PYTHON=${LE_PYTHON:-python} + else + echo "Cannot find any Pythons... please install one!" + fi + + PYVER=`$LE_PYTHON --version 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//'` + if [ $PYVER -eq 26 ] ; then + ExperimentalBootstrap "Python 2.6" + elif [ $PYVER -lt 26 ] ; then + echo "You have an ancient version of Python entombed in your operating system..." + echo "This isn't going to work." + exit 1 + fi +} + +# Install required OS packages: +Bootstrap() { + if [ -f /etc/debian_version ] ; then + echo "Bootstrapping dependencies for Debian-based OSes..." + BootstrapDebCommon + elif [ -f /etc/redhat-release ] ; then + echo "Bootstrapping dependencies for RedHat-based OSes..." + BootstrapRpmCommon + elif `grep -q openSUSE /etc/os-release` ; then + echo "Bootstrapping dependencies for openSUSE-based OSes..." + BootstrapSuseCommon + elif [ -f /etc/arch-release ] ; then + echo "Bootstrapping dependencies for Archlinux..." + BootstrapArchLinux + elif [ -f /etc/manjaro-release ] ; then + ExperimentalBootstrap "Manjaro Linux" BootstrapManjaro + elif [ -f /etc/gentoo-release ] ; then + ExperimentalBootstrap "Gentoo" BootstrapGentooCommon + elif uname | grep -iq FreeBSD ; then + ExperimentalBootstrap "FreeBSD" BootstrapFreeBsd + elif uname | grep -iq Darwin ; then + ExperimentalBootstrap "Mac OS X" BootstrapMac + elif grep -iq "Amazon Linux" /etc/issue ; then + ExperimentalBootstrap "Amazon Linux" BootstrapRpmCommon + else + echo "Sorry, I don't know how to bootstrap Let's Encrypt on your operating system!" + echo + echo "You will need to bootstrap, configure virtualenv, and run a peep install manually." + echo "Please see https://letsencrypt.readthedocs.org/en/latest/contributing.html#prerequisites" + echo "for more info." + fi +} + # This script takes the same arguments as the main letsencrypt program, but it # additionally responds to --verbose (more output) and --debug (allow support # for experimental platforms) @@ -63,131 +130,81 @@ else SUDO= fi -ExperimentalBootstrap() { - # Arguments: Platform name, boostrap script name, SUDO command (iff needed) - if [ "$DEBUG" = 1 ] ; then - if [ "$2" != "" ] ; then - echo "Bootstrapping dependencies for $1..." - if [ "$3" != "" ] ; then - "$3" "$BOOTSTRAP/$2" - else - "$BOOTSTRAP/$2" - fi +if [ "$1" = "--os-packages-only" ]; then + Bootstrap +elif [ "$1" != "--_skip-to-install" ]; then + echo "Upgrading letsencrypt-auto..." + + if [ ! -f $VENV_BIN/letsencrypt ]; then + OLD_VERSION="0.0.0" # ($VENV_BIN/letsencrypt --version) + else + OLD_VERSION="0.0.0" + fi + + # TODO: Don't bother upgrading if we're already up to date. + if [ "$OLD_VERSION" != "1.2.3" ]; then + Bootstrap + echo "Creating virtual environment..." + rm -rf "$VENV_PATH" + DeterminePythonVersion + 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 + NEXT: Is all this stuff in the right if branch? + + # 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. + # + # This Python script prints a path to a temp dir + # containing a new copy of letsencrypt-auto or returns non-zero. + # There is no $ interpolation due to quotes on heredoc delimiters. + set +e + TEMP_DIR=`$PYTHON - << "UNLIKELY_EOF" +{download_upgrade} +UNLIKELY_EOF` + DOWNLOAD_STATUS=$? + set -e + if [ "$DOWNLOAD_STATUS" = 0 ]; then + # Install new copy of letsencrypt-auto. This preserves permissions and + # ownership from the old copy. + # TODO: Deal with quotes in pathnames. + echo "Installing new version of letsencrypt-auto..." + echo " " $SUDO cp "$TEMP_DIR/letsencrypt-auto" "$0" + $SUDO cp "$TEMP_DIR/letsencrypt-auto" "$0" + # TODO: Clean up temp dir safely, even if it has quotes in its path. + "$0" --_skip-to-install "$TEMP_DIR" "$@" + else + # Report error: + echo $TEMP_DIR + exit 1 + fi + fi # should upgrade +else # --_skip-to-install was passed. + # Install Python dependencies with peep, then run letsencrypt. + echo "Installing Python package dependencies..." + TEMP_DIR="$2" + shift 2 + cat << "UNLIKELY_EOF" > $TEMP_DIR/letsencrypt-auto-requirements.txt +{requirements} +UNLIKELY_EOF + + cat << "UNLIKELY_EOF" > $TEMP_DIR/peep.py +{peep} +UNLIKELY_EOF + + set +e + PEEP_OUT=`$PYTHON $TEMP_DIR/peep.py install -r $TEMP_DIR/letsencrypt-auto-requirements.txt` + PEEP_STATUS=$? + set -e + if [ "$PEEP_STATUS" = 0 ]; then + echo "Running letsencrypt..." + $SUDO $VENV_BIN/letsencrypt "$@" else - echo "WARNING: $1 support is very experimental at present..." - echo "if you would like to work on improving it, please ensure you have backups" - echo "and then run this script again with the --debug flag!" + # Report error: + echo $PEEP_OUT exit 1 fi -} - -DeterminePythonVersion() { - if command -v python2.7 > /dev/null ; then - export LE_PYTHON=${LE_PYTHON:-python2.7} - elif command -v python27 > /dev/null ; then - export LE_PYTHON=${LE_PYTHON:-python27} - elif command -v python2 > /dev/null ; then - export LE_PYTHON=${LE_PYTHON:-python2} - elif command -v python > /dev/null ; then - export LE_PYTHON=${LE_PYTHON:-python} - else - echo "Cannot find any Pythons... please install one!" - fi - - PYVER=`$LE_PYTHON --version 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//'` - if [ $PYVER -eq 26 ] ; then - ExperimentalBootstrap "Python 2.6" - elif [ $PYVER -lt 26 ] ; then - echo "You have an ancient version of Python entombed in your operating system..." - echo "This isn't going to work." - exit 1 - fi -} - - -# virtualenv call is not idempotent: it overwrites pip upgraded in -# later steps, causing "ImportError: cannot import name unpack_url" -if [ ! -d $VENV_PATH ] -then - BOOTSTRAP=`dirname $0`/bootstrap - if [ ! -f $BOOTSTRAP/debian.sh ] ; then - echo "Cannot find the letsencrypt bootstrap scripts in $BOOTSTRAP" - exit 1 - fi - - if [ -f /etc/debian_version ] ; then - echo "Bootstrapping dependencies for Debian-based OSes..." - $SUDO $BOOTSTRAP/_deb_common.sh - elif [ -f /etc/redhat-release ] ; then - echo "Bootstrapping dependencies for RedHat-based OSes..." - $SUDO $BOOTSTRAP/_rpm_common.sh - elif `grep -q openSUSE /etc/os-release` ; then - echo "Bootstrapping dependencies for openSUSE-based OSes..." - $SUDO $BOOTSTRAP/_suse_common.sh - elif [ -f /etc/arch-release ] ; then - echo "Bootstrapping dependencies for Archlinux..." - $SUDO $BOOTSTRAP/archlinux.sh - elif [ -f /etc/manjaro-release ] ; then - ExperimentalBootstrap "Manjaro Linux" manjaro.sh "$SUDO" - elif [ -f /etc/gentoo-release ] ; then - ExperimentalBootstrap "Gentoo" _gentoo_common.sh "$SUDO" - elif uname | grep -iq FreeBSD ; then - ExperimentalBootstrap "FreeBSD" freebsd.sh "$SUDO" - elif uname | grep -iq Darwin ; then - ExperimentalBootstrap "Mac OS X" mac.sh - elif grep -iq "Amazon Linux" /etc/issue ; then - ExperimentalBootstrap "Amazon Linux" _rpm_common.sh - else - echo "Sorry, I don't know how to bootstrap Let's Encrypt on your operating system!" - echo - echo "You will need to bootstrap, configure virtualenv, and run a pip install manually" - echo "Please see https://letsencrypt.readthedocs.org/en/latest/contributing.html#prerequisites" - echo "for more info" - fi - - DeterminePythonVersion - echo "Creating virtual environment..." - 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 -else - DeterminePythonVersion fi - - -printf "Updating letsencrypt and virtual environment dependencies..." -if [ "$VERBOSE" = 1 ] ; then - echo - $VENV_BIN/pip install -U setuptools - $VENV_BIN/pip install -U pip - $VENV_BIN/pip install -r py26reqs.txt -U letsencrypt letsencrypt-apache - # nginx is buggy / disabled for now, but upgrade it if the user has - # installed it manually - if $VENV_BIN/pip freeze | grep -q letsencrypt-nginx ; then - $VENV_BIN/pip install -U letsencrypt letsencrypt-nginx - fi -else - $VENV_BIN/pip install -U setuptools > /dev/null - printf . - $VENV_BIN/pip install -U pip > /dev/null - printf . - # nginx is buggy / disabled for now... - $VENV_BIN/pip install -r py26reqs.txt > /dev/null - printf . - $VENV_BIN/pip install -U letsencrypt > /dev/null - printf . - $VENV_BIN/pip install -U letsencrypt-apache > /dev/null - if $VENV_BIN/pip freeze | grep -q letsencrypt-nginx ; then - printf . - $VENV_BIN/pip install -U letsencrypt-nginx > /dev/null - fi - echo -fi - -# Explain what's about to happen, for the benefit of those getting sudo -# password prompts... -echo "Running with virtualenv:" $SUDO $VENV_BIN/letsencrypt "$@" -$SUDO $VENV_BIN/letsencrypt "$@" diff --git a/letsencrypt_auto/pieces/download_upgrade.py b/letsencrypt_auto/pieces/download_upgrade.py new file mode 100644 index 000000000..27e1b28d8 --- /dev/null +++ b/letsencrypt_auto/pieces/download_upgrade.py @@ -0,0 +1,120 @@ +from distutils.version import LooseVersion +from json import loads +from os import devnull +from os.path import join +import re +from subprocess import check_call, CalledProcessError +from sys import exit +from tempfile import mkdtemp +from urllib2 import build_opener, HTTPHandler, HTTPSHandler, HTTPError + + +PUBLIC_KEY = """-----BEGIN PUBLIC KEY----- +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnwHkSuCSy3gIHawaCiIe +4ilJ5kfEmSoiu50uiimBhTESq1JG2gVqXVXFxxVgobGhahSF+/iRVp3imrTtGp1B +2heoHbELnPTTZ8E36WHKf4gkLEo0y0XgOP3oBJ9IM5q8J68x0U3Q3c+kTxd/sgww +s5NVwpjw4aAZhgDPe5u+rvthUYOD1whYUANgYvooCpV4httNv5wuDjo7SG2V797T +QTE8aG3AOhWzdsLm6E6Tl2o/dR6XKJi/RMiXIk53SzArimtAJXe/1GyADe1AgIGE +33Ja3hU3uu9lvnnkowy1VI0qvAav/mu/APahcWVYkBAvSVAhH3zGNAGZUnP2zfcP +rH7OPw/WrxLVGlX4trLnvQr1wzX7aiM2jdikcMiaExrP0JfQXPu00y3c+hjOC5S0 ++E5P+e+8pqz5iC5mmvEqy2aQJ6pV7dSpYX3mcDs8pCYaVXXtCPXS1noWirCcqCMK +EHGGdJCTXXLHaWUaGQ9Gx1An1gU7Ljkkji2Al65ZwYhkFowsLfuniYKuAywRrCNu +q958HnzFpZiQZAqZYtOHaiQiaHPs/36ZN0HuOEy0zM9FEHbp4V/DEn4pNCfAmRY5 +3v+3nIBhgiLdlM7cV9559aDNeutF25n1Uz2kvuSVSS94qTEmlteCPZGBQb9Rr2wn +I2OU8tPRzqKdQ6AwS9wvqscCAwEAAQ== +-----END PUBLIC KEY----- +""" # TODO: Replace with real one. + + +class ExpectedError(Exception): + """A novice-readable exception that also carries the original exception for + debugging""" + + +class HttpsGetter(object): + def __init__(self): + """Build an HTTPS opener.""" + # Based on pip 1.4.1's URLOpener + # This verifies certs on only Python >=2.7.9. + self._opener = build_opener(HTTPSHandler()) + # Strip out HTTPHandler to prevent MITM spoof: + for handler in self._opener.handlers: + if isinstance(handler, HTTPHandler): + self._opener.handlers.remove(handler) + + def get(self, url): + """Return the document contents pointed to by an HTTPS URL. + + If something goes wrong (404, timeout, etc.), raise ExpectedError. + + """ + try: + return self._opener.open(url).read() + except (HTTPError, IOError) as exc: + raise ExpectedError("Couldn't download %s." % url, exc) + + +class TempDir(object): + def __init__(self): + self.path = mkdtemp() + + def write(self, contents, filename): + """Write something to a named file in me.""" + with open(join(self.path, filename), 'w') as file: + file.write(contents) + + +def latest_stable_version(get, package): + """Apply a fairly safe heuristic to determine the latest stable release of + a PyPI package.""" + metadata = loads(get('https://pypi.python.org/pypi/%s/json' % package)) + # metadata['info']['version'] actually returns the latest of any kind of + # release release, contrary to https://wiki.python.org/moin/PyPIJSON. + return str(max(LooseVersion(r) for r + in metadata['releases'].iterkeys() + if re.match('^[0-9.]+$', r))) + + +def verified_new_le_auto(get, tag, temp): + """Return the path to a verified, up-to-date letsencrypt-auto script. + + If the download's signature does not verify or something else goes wrong, + raise ExpectedError. + + """ + le_auto_dir = ('https://raw.githubusercontent.com/letsencrypt/letsencrypt/' + '%s/letsencrypt-auto/' % tag) + temp.write(get(le_auto_dir + 'letsencrypt-auto'), 'letsencrypt-auto') + temp.write(get(le_auto_dir + 'letsencrypt-auto.sig'), 'letsencrypt-auto.sig') + temp.write(PUBLIC_KEY, 'public_key.pem') + le_auto_path = join(temp.path, 'letsencrypt-auto') + try: + with open(devnull, 'w') as dev_null: + check_call(['openssl', 'dgst', '-sha256', '-verify', + join(temp.path, 'public_key.pem'), + '-signature', + join(temp.path, 'letsencrypt-auto.sig'), + le_auto_path], + stdout=dev_null, + stderr=dev_null) + except CalledProcessError as exc: + raise ExpectedError("Couldn't verify signature of downloaded " + "letsencrypt-auto.", exc) + else: # belt & suspenders + return le_auto_path + + +def main(): + get = HttpsGetter().get + temp = TempDir() + try: + stable_tag = 'v' + latest_stable_version(get, 'letsencrypt') + print dirname(verified_new_le_auto(get, stable_tag, temp)) + except ExpectedError as exc: + print exc.args[0], exc.args[1] + return 1 + else: + return 0 + + +exit(main()) diff --git a/letsencrypt_auto/pieces/letsencrypt-auto-requirements.txt b/letsencrypt_auto/pieces/letsencrypt-auto-requirements.txt new file mode 100644 index 000000000..963177490 --- /dev/null +++ b/letsencrypt_auto/pieces/letsencrypt-auto-requirements.txt @@ -0,0 +1,6 @@ +# sha256: Jo-gDCfedW1xZj3WH3OkqNhydWm7G0dLLOYCBVOCaHI +# sha256: mXhebPcVzc3lne4FpnbpnwSDWnHnztIByjF0AcMiupY +certifi==2015.04.28 + +# sha256: mrHTE_mbIJ-PcaYp82gzAwyNfHIoLPd1aDS69WfcpmI +click==4.0 diff --git a/booty.sh b/letsencrypt_auto/pieces/peep.py similarity index 83% rename from booty.sh rename to letsencrypt_auto/pieces/peep.py index 453be7be8..23e917ac6 100755 --- a/booty.sh +++ b/letsencrypt_auto/pieces/peep.py @@ -1,177 +1,3 @@ -#!/bin/sh -set -e # Work even if somebody does "sh thisscript.sh". - -# If not --_skip-to-install: - # Bootstrap - # TODO: Inline the bootstrap scripts by putting each one into its own function (so they don't leak scope). - -PYTHON=python -SUDO=sudo - -if [ "$1" != "--_skip-to-install" ]; then - echo "Upgrading letsencrypt-auto..." - # 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. - # - # The following Python script prints a path to a temp dir containing a new - # copy of letsencrypt-auto or returns non-zero. There is no $ interpolation - # due to quotes on heredoc delimiters. - set +e - TEMP_DIR=`$PYTHON - << "UNLIKELY_EOF" - -from distutils.version import LooseVersion -from json import loads -from os import devnull -from os.path import join -import re -from subprocess import check_call, CalledProcessError -from sys import exit -from tempfile import mkdtemp -from urllib2 import build_opener, HTTPHandler, HTTPSHandler, HTTPError - - -PUBLIC_KEY = """-----BEGIN PUBLIC KEY----- -MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnwHkSuCSy3gIHawaCiIe -4ilJ5kfEmSoiu50uiimBhTESq1JG2gVqXVXFxxVgobGhahSF+/iRVp3imrTtGp1B -2heoHbELnPTTZ8E36WHKf4gkLEo0y0XgOP3oBJ9IM5q8J68x0U3Q3c+kTxd/sgww -s5NVwpjw4aAZhgDPe5u+rvthUYOD1whYUANgYvooCpV4httNv5wuDjo7SG2V797T -QTE8aG3AOhWzdsLm6E6Tl2o/dR6XKJi/RMiXIk53SzArimtAJXe/1GyADe1AgIGE -33Ja3hU3uu9lvnnkowy1VI0qvAav/mu/APahcWVYkBAvSVAhH3zGNAGZUnP2zfcP -rH7OPw/WrxLVGlX4trLnvQr1wzX7aiM2jdikcMiaExrP0JfQXPu00y3c+hjOC5S0 -+E5P+e+8pqz5iC5mmvEqy2aQJ6pV7dSpYX3mcDs8pCYaVXXtCPXS1noWirCcqCMK -EHGGdJCTXXLHaWUaGQ9Gx1An1gU7Ljkkji2Al65ZwYhkFowsLfuniYKuAywRrCNu -q958HnzFpZiQZAqZYtOHaiQiaHPs/36ZN0HuOEy0zM9FEHbp4V/DEn4pNCfAmRY5 -3v+3nIBhgiLdlM7cV9559aDNeutF25n1Uz2kvuSVSS94qTEmlteCPZGBQb9Rr2wn -I2OU8tPRzqKdQ6AwS9wvqscCAwEAAQ== ------END PUBLIC KEY----- -""" # TODO: Replace with real one. - - -class ExpectedError(Exception): - """A novice-readable exception that also carries the original exception for - debugging""" - - -class HttpsGetter(object): - def __init__(self): - """Build an HTTPS opener.""" - # Based on pip 1.4.1's URLOpener - # This verifies certs on only Python >=2.7.9. - self._opener = build_opener(HTTPSHandler()) - # Strip out HTTPHandler to prevent MITM spoof: - for handler in self._opener.handlers: - if isinstance(handler, HTTPHandler): - self._opener.handlers.remove(handler) - - def get(self, url): - """Return the document contents pointed to by an HTTPS URL. - - If something goes wrong (404, timeout, etc.), raise ExpectedError. - - """ - try: - return self._opener.open(url).read() - except (HTTPError, IOError) as exc: - raise ExpectedError("Couldn't download %s." % url, exc) - - -class TempDir(object): - def __init__(self): - self.path = mkdtemp() - - def write(self, contents, filename): - """Write something to a named file in me.""" - with open(join(self.path, filename), 'w') as file: - file.write(contents) - - -def latest_stable_version(get, package): - """Apply a fairly safe heuristic to determine the latest stable release of - a PyPI package.""" - metadata = loads(get('https://pypi.python.org/pypi/%s/json' % package)) - # metadata['info']['version'] actually returns the latest of any kind of - # release release, contrary to https://wiki.python.org/moin/PyPIJSON. - return str(max(LooseVersion(r) for r - in metadata['releases'].iterkeys() - if re.match('^[0-9.]+$', r))) - - -def verified_new_le_auto(get, tag, temp): - """Return the path to a verified, up-to-date letsencrypt-auto script. - - If the download's signature does not verify or something else goes wrong, - raise ExpectedError. - - """ - root = ('https://raw.githubusercontent.com/letsencrypt/letsencrypt/%s/' % - tag) - temp.write(get(root + 'letsencrypt-auto'), 'letsencrypt-auto') - temp.write(get(root + 'letsencrypt-auto.sig'), 'letsencrypt-auto.sig') - temp.write(PUBLIC_KEY, 'public_key.pem') - le_auto_path = join(temp.path, 'letsencrypt-auto') - try: - with open(devnull, 'w') as dev_null: - check_call(['openssl', 'dgst', '-sha256', '-verify', - join(temp.path, 'public_key.pem'), - '-signature', - join(temp.path, 'letsencrypt-auto.sig'), - le_auto_path], - stdout=dev_null, - stderr=dev_null) - except CalledProcessError as exc: - raise ExpectedError("Couldn't verify signature of downloaded " - "letsencrypt-auto.", exc) - else: # belt & suspenders - return le_auto_path - - -def main(): - get = HttpsGetter().get - temp = TempDir() - try: - stable_tag = 'v' + latest_stable_version(get, 'letsencrypt') - print dirname(verified_new_le_auto(get, stable_tag, temp)) - except ExpectedError as exc: - print exc.args[0], exc.args[1] - return 1 - else: - return 0 - - -exit(main()) -UNLIKELY_EOF` - DOWNLOAD_STATUS=$? - set -e - if [ "$DOWNLOAD_STATUS" = 0 ]; then - # Install new copy of letsencrypt-auto. This preserves permissions and - # ownership from the old copy. - # TODO: Deal with quotes in pathnames. - # TODO: Don't bother upgrading if we're already up to date. - echo " " $SUDO cp "$TEMP_DIR/letsencrypt-auto" "$0" - $SUDO cp "$TEMP_DIR/letsencrypt-auto" "$0" - # TODO: Clean up temp dir safely, even if it has quotes in its path. - "$0" --_skip-to-install "$TEMP_DIR" "$@" - else - # Report error: - echo $TEMP_DIR - exit 1 - fi -else # --_skip-to-install was passed. - # Install Python dependencies with peep. - TEMP_DIR="$2" - shift 2 - echo "Installing Python package dependencies..." - cat << "UNLIKELY_EOF" > $TEMP_DIR/letsencrypt-auto-requirements.txt -# sha256: Jo-gDCfedW1xZj3WH3OkqNhydWm7G0dLLOYCBVOCaHI -# sha256: mXhebPcVzc3lne4FpnbpnwSDWnHnztIByjF0AcMiupY -certifi==2015.04.28 - -# sha256: mrHTE_mbIJ-PcaYp82gzAwyNfHIoLPd1aDS69WfcpmI -click==4.0 -UNLIKELY_EOF - - cat << "UNLIKELY_EOF" > $TEMP_DIR/peep.py #!/usr/bin/env python """peep ("prudently examine every package") verifies that packages conform to a trusted, locally stored hash and only then installs them:: @@ -1076,17 +902,3 @@ if __name__ == '__main__': except Exception: exception_handler(*sys.exc_info()) exit(SOMETHING_WENT_WRONG) -UNLIKELY_EOF - - set +e - PEEP_OUT=`$PYTHON $TEMP_DIR/peep.py install -r $TEMP_DIR/letsencrypt-auto-requirements.txt` - PEEP_STATUS=$? - set -e - if [ "$PEEP_STATUS" = 0 ]; then - echo "Running letsencrypt..." - else - # Report error: - echo $PEEP_OUT - exit 1 - fi -fi