In Phase 1, download a new letsencrypt-auto script only if necessary.

* Temp dir creation is now always done in shell.
* Split download_upgrade.py into 2 phases itself so we can have it report back the latest LE version and make a decision based on it before doing and major downloading. Rename it for clarity.
This commit is contained in:
Erik Rose
2015-12-03 19:21:34 -05:00
parent 46779da3b5
commit 5cc69d92e7
2 changed files with 70 additions and 70 deletions
+28 -31
View File
@@ -13,6 +13,7 @@ XDG_DATA_HOME=${XDG_DATA_HOME:-~/.local/share}
VENV_NAME="letsencrypt" VENV_NAME="letsencrypt"
VENV_PATH=${VENV_PATH:-"$XDG_DATA_HOME/$VENV_NAME"} VENV_PATH=${VENV_PATH:-"$XDG_DATA_HOME/$VENV_NAME"}
VENV_BIN=${VENV_PATH}/bin VENV_BIN=${VENV_PATH}/bin
LE_AUTO_VERSION="{{ LE_AUTO_VERSION }}"
ExperimentalBootstrap() { ExperimentalBootstrap() {
# Arguments: Platform name, bootstrap function name # Arguments: Platform name, bootstrap function name
@@ -102,6 +103,10 @@ Bootstrap() {
fi fi
} }
TempDir() {
mktemp -d 2>/dev/null || mktemp -d -t 'le' # Linux || OS X
}
# This script takes the same arguments as the main letsencrypt program, but it # This script takes the same arguments as the main letsencrypt program, but it
# additionally responds to --verbose (more output) and --debug (allow support # additionally responds to --verbose (more output) and --debug (allow support
# for experimental platforms) # for experimental platforms)
@@ -165,7 +170,7 @@ elif [ "$1" = "--no-self-upgrade" ]; then
else else
INSTALLED_VERSION="0.0.0" INSTALLED_VERSION="0.0.0"
fi fi
if [ "{{ LE_AUTO_VERSION }}" = $INSTALLED_VERSION ]; then if [ "$LE_AUTO_VERSION" = "$INSTALLED_VERSION" ]; then
echo "Reusing old virtual environment." echo "Reusing old virtual environment."
else else
echo "Creating virtual environment..." echo "Creating virtual environment..."
@@ -177,9 +182,9 @@ elif [ "$1" = "--no-self-upgrade" ]; then
virtualenv --no-site-packages --python $LE_PYTHON $VENV_PATH > /dev/null virtualenv --no-site-packages --python $LE_PYTHON $VENV_PATH > /dev/null
fi fi
# Install Python dependencies with peep, then run letsencrypt. echo "Installing Python packages..."
echo "Installing Python package dependencies..." TEMP_DIR=$(TempDir)
TEMP_DIR=`mktemp -d 2>/dev/null || mktemp -d -t 'le'` # Linux || OS X # There is no $ interpolation due to quotes on heredoc delimiters.
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
cat << "UNLIKELY_EOF" > $TEMP_DIR/letsencrypt-auto-requirements.txt cat << "UNLIKELY_EOF" > $TEMP_DIR/letsencrypt-auto-requirements.txt
{{ letsencrypt-auto-requirements.txt }} {{ letsencrypt-auto-requirements.txt }}
@@ -195,7 +200,7 @@ UNLIKELY_EOF
set -e set -e
rm -rf $TEMP_DIR rm -rf $TEMP_DIR
if [ "$PEEP_STATUS" != 0 ]; then if [ "$PEEP_STATUS" != 0 ]; then
# Report error: # Report error. (Otherwise, be quiet.)
echo $PEEP_OUT echo $PEEP_OUT
exit 1 exit 1
fi fi
@@ -205,32 +210,29 @@ UNLIKELY_EOF
$SUDO $VENV_BIN/letsencrypt "$@" $SUDO $VENV_BIN/letsencrypt "$@"
else else
# Phase 1: Upgrade letsencrypt-auto if neceesary, then self-invoke. # Phase 1: Upgrade letsencrypt-auto if neceesary, 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
# letsencrypt-auto (which is always the same as that of the letsencrypt
# package). Phase 2 checks the version of the locally installed letsencrypt.
if [ ! -f $VENV_BIN/letsencrypt ]; then echo "Checking for new version..."
OLD_VERSION="0.0.0" # ($VENV_BIN/letsencrypt --version) TEMP_DIR=$(TempDir)
else # -------------------------------------------------------------------------
OLD_VERSION="0.0.0" cat << "UNLIKELY_EOF" > $TEMP_DIR/fetch.py
fi {{ fetch.py }}
UNLIKELY_EOF
# TODO: Don't bother upgrading if we're already up to date. # -------------------------------------------------------------------------
if [ "$OLD_VERSION" != "1.2.3" ]; then
echo "Upgrading letsencrypt-auto..."
DeterminePythonVersion DeterminePythonVersion
REMOTE_VERSION=`$LE_PYTHON $TEMP_DIR/fetch.py --latest-version`
if [ "$LE_AUTO_VERSION" != "$REMOTE_VERSION" ]; then
echo "Upgrading letsencrypt-auto $LE_AUTO_VERSION to $REMOTE_VERSION..."
# Now we drop into Python so we don't have to install even more # 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 # dependencies (curl, etc.), for better flow control, and for the option of
# future Windows compatibility. # future Windows compatibility.
# $LE_PYTHON "$TEMP_DIR/fetch.py" --le-auto-script "v$REMOTE_VERSION"
# There is no $ interpolation due to quotes on heredoc delimiters.
set +e
# -------------------------------------------------------------------------
TEMP_DIR=`$LE_PYTHON - << "UNLIKELY_EOF"
{{ download_upgrade.py }}
UNLIKELY_EOF`
# -------------------------------------------------------------------------
DOWNLOAD_STATUS=$?
set -e
if [ "$DOWNLOAD_STATUS" = 0 ]; then
# Install new copy of letsencrypt-auto. This preserves permissions and # Install new copy of letsencrypt-auto. This preserves permissions and
# ownership from the old copy. # ownership from the old copy.
# TODO: Deal with quotes in pathnames. # TODO: Deal with quotes in pathnames.
@@ -239,11 +241,6 @@ UNLIKELY_EOF`
$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. # TODO: Clean up temp dir safely, even if it has quotes in its path.
rm -rf $TEMP_DIR rm -rf $TEMP_DIR
"$0" --no-self-upgrade "$@"
else
# Report error:
echo $TEMP_DIR
exit 1
fi
fi # should upgrade fi # should upgrade
"$0" --no-self-upgrade "$@"
fi fi
@@ -1,4 +1,11 @@
"""Print a path to a temp dir containing a new copy of letsencrypt-auto. """Do downloading and JSON parsing without additional dependencies. ::
# Print latest released version of LE to stdout:
python fetch.py --latest-version
# Download letsencrypt-auto script from git tag v1.2.3 into the folder I'm
# in, and make sure its signature verifies:
python fetch.py --le-auto-script v1.2.3
On failure, return non-zero. On failure, return non-zero.
@@ -9,8 +16,7 @@ from os import devnull
from os.path import dirname, join from os.path import dirname, join
import re import re
from subprocess import check_call, CalledProcessError from subprocess import check_call, CalledProcessError
from sys import exit from sys import argv, exit
from tempfile import mkdtemp
from urllib2 import build_opener, HTTPHandler, HTTPSHandler, HTTPError from urllib2 import build_opener, HTTPHandler, HTTPSHandler, HTTPError
@@ -59,62 +65,59 @@ class HttpsGetter(object):
raise ExpectedError("Couldn't download %s." % url, exc) raise ExpectedError("Couldn't download %s." % url, exc)
class TempDir(object): def write(contents, dir, filename):
def __init__(self): """Write something to a file in a certain directory."""
self.path = mkdtemp() with open(join(dir, filename), 'w') as file:
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) file.write(contents)
def latest_stable_version(get, package): def latest_stable_version(get):
"""Apply a fairly safe heuristic to determine the latest stable release of """Return the latest stable release of letsencrypt."""
a PyPI package."""
metadata = loads(get('https://pypi.python.org/pypi/letsencrypt/json')) metadata = loads(get('https://pypi.python.org/pypi/letsencrypt/json'))
# metadata['info']['version'] actually returns the latest of any kind of # metadata['info']['version'] actually returns the latest of any kind of
# release release, contrary to https://wiki.python.org/moin/PyPIJSON. # release release, contrary to https://wiki.python.org/moin/PyPIJSON.
# The regex is a sufficient regex for picking out prereleases for most
# packages, LE included.
return str(max(LooseVersion(r) for r return str(max(LooseVersion(r) for r
in metadata['releases'].iterkeys() in metadata['releases'].iterkeys()
if re.match('^[0-9.]+$', r))) if re.match('^[0-9.]+$', r)))
def verified_new_le_auto(get, tag, temp): def verified_new_le_auto(get, tag, temp_dir):
"""Return the path to a verified, up-to-date letsencrypt-auto script. """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, If the download's signature does not verify or something else goes wrong
raise ExpectedError. with the verification process, raise ExpectedError.
""" """
le_auto_dir = ('https://raw.githubusercontent.com/letsencrypt/letsencrypt/' le_auto_dir = ('https://raw.githubusercontent.com/letsencrypt/letsencrypt/'
'%s/letsencrypt-auto/' % tag) '%s/letsencrypt-auto/' % tag)
temp.write(get(le_auto_dir + 'letsencrypt-auto'), 'letsencrypt-auto') write(get(le_auto_dir + 'letsencrypt-auto'), temp_dir, 'letsencrypt-auto')
temp.write(get(le_auto_dir + 'letsencrypt-auto.sig'), 'letsencrypt-auto.sig') write(get(le_auto_dir + 'letsencrypt-auto.sig'), temp_dir, 'letsencrypt-auto.sig')
temp.write(PUBLIC_KEY, 'public_key.pem') write(PUBLIC_KEY, temp_dir, 'public_key.pem')
le_auto_path = join(temp.path, 'letsencrypt-auto')
try: try:
with open(devnull, 'w') as dev_null: with open(devnull, 'w') as dev_null:
check_call(['openssl', 'dgst', '-sha256', '-verify', check_call(['openssl', 'dgst', '-sha256', '-verify',
join(temp.path, 'public_key.pem'), join(temp_dir, 'public_key.pem'),
'-signature', '-signature',
join(temp.path, 'letsencrypt-auto.sig'), join(temp_dir, 'letsencrypt-auto.sig'),
le_auto_path], join(temp_dir, 'letsencrypt-auto')],
stdout=dev_null, stdout=dev_null,
stderr=dev_null) stderr=dev_null)
except CalledProcessError as exc: except CalledProcessError as exc:
raise ExpectedError("Couldn't verify signature of downloaded " raise ExpectedError("Couldn't verify signature of downloaded "
"letsencrypt-auto.", exc) "letsencrypt-auto.", exc)
else: # belt & suspenders
return le_auto_path
def main(): def main():
get = HttpsGetter().get get = HttpsGetter().get
temp = TempDir() flag = argv[1]
try: try:
stable_tag = 'v' + latest_stable_version(get) if flag == '--latest-version':
print dirname(verified_new_le_auto(get, stable_tag, temp)) print latest_stable_version(get)
elif flag == '--le-auto-script':
tag = argv[2]
verified_new_le_auto(get, tag, dirname(argv[0]))
except ExpectedError as exc: except ExpectedError as exc:
print exc.args[0], exc.args[1] print exc.args[0], exc.args[1]
return 1 return 1