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 18:51:18 -05:00
parent 46779da3b5
commit 5cc69d92e7
2 changed files with 70 additions and 70 deletions

View file

@ -13,6 +13,7 @@ XDG_DATA_HOME=${XDG_DATA_HOME:-~/.local/share}
VENV_NAME="letsencrypt"
VENV_PATH=${VENV_PATH:-"$XDG_DATA_HOME/$VENV_NAME"}
VENV_BIN=${VENV_PATH}/bin
LE_AUTO_VERSION="{{ LE_AUTO_VERSION }}"
ExperimentalBootstrap() {
# Arguments: Platform name, bootstrap function name
@ -102,6 +103,10 @@ Bootstrap() {
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
# additionally responds to --verbose (more output) and --debug (allow support
# for experimental platforms)
@ -165,7 +170,7 @@ elif [ "$1" = "--no-self-upgrade" ]; then
else
INSTALLED_VERSION="0.0.0"
fi
if [ "{{ LE_AUTO_VERSION }}" = $INSTALLED_VERSION ]; then
if [ "$LE_AUTO_VERSION" = "$INSTALLED_VERSION" ]; then
echo "Reusing old virtual environment."
else
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
fi
# Install Python dependencies with peep, then run letsencrypt.
echo "Installing Python package dependencies..."
TEMP_DIR=`mktemp -d 2>/dev/null || mktemp -d -t 'le'` # Linux || OS X
echo "Installing Python packages..."
TEMP_DIR=$(TempDir)
# There is no $ interpolation due to quotes on heredoc delimiters.
# ---------------------------------------------------------------------------
cat << "UNLIKELY_EOF" > $TEMP_DIR/letsencrypt-auto-requirements.txt
{{ letsencrypt-auto-requirements.txt }}
@ -195,7 +200,7 @@ UNLIKELY_EOF
set -e
rm -rf $TEMP_DIR
if [ "$PEEP_STATUS" != 0 ]; then
# Report error:
# Report error. (Otherwise, be quiet.)
echo $PEEP_OUT
exit 1
fi
@ -205,45 +210,37 @@ UNLIKELY_EOF
$SUDO $VENV_BIN/letsencrypt "$@"
else
# Phase 1: Upgrade letsencrypt-auto if neceesary, then self-invoke.
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
echo "Upgrading letsencrypt-auto..."
DeterminePythonVersion
#
# 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.
echo "Checking for new version..."
TEMP_DIR=$(TempDir)
# -------------------------------------------------------------------------
cat << "UNLIKELY_EOF" > $TEMP_DIR/fetch.py
{{ fetch.py }}
UNLIKELY_EOF
# -------------------------------------------------------------------------
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
# dependencies (curl, etc.), for better flow control, and for the option of
# future Windows compatibility.
#
# 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
# 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.
rm -rf $TEMP_DIR
"$0" --no-self-upgrade "$@"
else
# Report error:
echo $TEMP_DIR
exit 1
fi
$LE_PYTHON "$TEMP_DIR/fetch.py" --le-auto-script "v$REMOTE_VERSION"
# 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.
rm -rf $TEMP_DIR
fi # should upgrade
"$0" --no-self-upgrade "$@"
fi

View file

@ -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.
@ -9,8 +16,7 @@ from os import devnull
from os.path import dirname, join
import re
from subprocess import check_call, CalledProcessError
from sys import exit
from tempfile import mkdtemp
from sys import argv, exit
from urllib2 import build_opener, HTTPHandler, HTTPSHandler, HTTPError
@ -59,62 +65,59 @@ class HttpsGetter(object):
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 write(contents, dir, filename):
"""Write something to a file in a certain directory."""
with open(join(dir, 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."""
def latest_stable_version(get):
"""Return the latest stable release of letsencrypt."""
metadata = loads(get('https://pypi.python.org/pypi/letsencrypt/json'))
# metadata['info']['version'] actually returns the latest of any kind of
# 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
in metadata['releases'].iterkeys()
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.
If the download's signature does not verify or something else goes wrong,
raise ExpectedError.
If the download's signature does not verify or something else goes wrong
with the verification process, 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')
write(get(le_auto_dir + 'letsencrypt-auto'), temp_dir, 'letsencrypt-auto')
write(get(le_auto_dir + 'letsencrypt-auto.sig'), temp_dir, 'letsencrypt-auto.sig')
write(PUBLIC_KEY, temp_dir, 'public_key.pem')
try:
with open(devnull, 'w') as dev_null:
check_call(['openssl', 'dgst', '-sha256', '-verify',
join(temp.path, 'public_key.pem'),
join(temp_dir, 'public_key.pem'),
'-signature',
join(temp.path, 'letsencrypt-auto.sig'),
le_auto_path],
join(temp_dir, 'letsencrypt-auto.sig'),
join(temp_dir, 'letsencrypt-auto')],
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()
flag = argv[1]
try:
stable_tag = 'v' + latest_stable_version(get)
print dirname(verified_new_le_auto(get, stable_tag, temp))
if flag == '--latest-version':
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:
print exc.args[0], exc.args[1]
return 1