Split large independent scripts off from the main body of the proof-of-concept script. Integrate the bits of the old le-auto script that are still useful.

This makes the script more readable and easier to work on. We'll stitch it together with a build process.

Also, stop passing the sudo command as an arg to the experimental bootstrappers. They will be inlined into the main script and can just reference $SUDO. As a result, stop recommending devs run the scripts manually, instead running le-auto --os-packages-only. This has the nice side effect of making dev documentation simpler.

Name the folder "letsencrypt_auto" rather than "letsencrypt-auto" because git yield endless pain when replacing a file with a dir. Perhaps we can change it with impunity in a latter commit.
This commit is contained in:
Erik Rose 2015-12-02 00:08:24 -05:00
parent fe77da2f7f
commit e3ace6f84c
8 changed files with 289 additions and 372 deletions

View file

@ -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

View file

@ -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

View file

@ -1,6 +1,6 @@
#!/bin/sh -xe
pkg install -Ay \
"$SUDO" pkg install -Ay \
git \
python \
py27-virtualenv \

View file

@ -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 <hacking>` 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 <hacking>` 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 &&

View file

@ -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 "$@"

View file

@ -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())

View file

@ -0,0 +1,6 @@
# sha256: Jo-gDCfedW1xZj3WH3OkqNhydWm7G0dLLOYCBVOCaHI
# sha256: mXhebPcVzc3lne4FpnbpnwSDWnHnztIByjF0AcMiupY
certifi==2015.04.28
# sha256: mrHTE_mbIJ-PcaYp82gzAwyNfHIoLPd1aDS69WfcpmI
click==4.0

View file

@ -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