mirror of
https://github.com/certbot/certbot.git
synced 2026-04-29 18:19:51 -04:00
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:
parent
fe77da2f7f
commit
e3ace6f84c
8 changed files with 289 additions and 372 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
#!/bin/sh -xe
|
||||
|
||||
pkg install -Ay \
|
||||
"$SUDO" pkg install -Ay \
|
||||
git \
|
||||
python \
|
||||
py27-virtualenv \
|
||||
|
|
|
|||
|
|
@ -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 &&
|
||||
|
|
|
|||
|
|
@ -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 "$@"
|
||||
120
letsencrypt_auto/pieces/download_upgrade.py
Normal file
120
letsencrypt_auto/pieces/download_upgrade.py
Normal 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())
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
# sha256: Jo-gDCfedW1xZj3WH3OkqNhydWm7G0dLLOYCBVOCaHI
|
||||
# sha256: mXhebPcVzc3lne4FpnbpnwSDWnHnztIByjF0AcMiupY
|
||||
certifi==2015.04.28
|
||||
|
||||
# sha256: mrHTE_mbIJ-PcaYp82gzAwyNfHIoLPd1aDS69WfcpmI
|
||||
click==4.0
|
||||
|
|
@ -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
|
||||
Loading…
Reference in a new issue