Compare commits

...

3 Commits

Author SHA1 Message Date
Erik Rose
0a43883d81 Switch to the USTC mirror because it doesn't require SNI.
Old Python versions don't understand SNI and raise SNIMissingWarning on connecting.
2017-10-04 09:59:29 -04:00
Erik Rose
32b0727a32 Teach certbot about certbot-auto's new --pypi-mirror option so it doesn't throw an unknown-arg error. 2017-10-03 14:46:37 -04:00
Erik Rose
65bd307905 Let certbot-auto work behind the Great Firewall of China. Fix #3222. Ref #4371.
...without needing to get pip >= 8 by hand first and without needing to muck with pip.conf.

We do it by upgrading to a new version of pipstrap which understands pip's own PIP_INDEX_URL env var for specifying a mirror. That done, certbot-auto simply sets that var to a known Chinese mirror in response to a CLI flag, and off we go.

This could be made automatic in the future: see 2nd-last paragraph of https://github.com/erikrose/pipstrap/pull/10#issuecomment-333637736. But there's no reason not to land this simple version first.
2017-10-02 17:27:23 -04:00
5 changed files with 92 additions and 42 deletions

View File

@@ -1024,6 +1024,11 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False): # pylint: dis
help="(certbot-auto only) prevent the certbot-auto script from" help="(certbot-auto only) prevent the certbot-auto script from"
" installing OS-level dependencies (default: Prompt to install " " installing OS-level dependencies (default: Prompt to install "
" OS-wide dependencies, but exit if the user says 'No')") " OS-wide dependencies, but exit if the user says 'No')")
helpful.add(
"automation", "--pypi-mirror", action="store_true",
default=flag_default("pypi_mirror"),
help="(certbot-auto only) use an alternative Python package server "
"that is reachable from China")
helpful.add( helpful.add(
["automation", "renew", "certonly", "run"], ["automation", "renew", "certonly", "run"],
"-q", "--quiet", dest="quiet", action="store_true", "-q", "--quiet", dest="quiet", action="store_true",

View File

@@ -44,6 +44,7 @@ CLI_DEFAULTS = dict(
os_packages_only=False, os_packages_only=False,
no_self_upgrade=False, no_self_upgrade=False,
no_bootstrap=False, no_bootstrap=False,
pypi_mirror=False,
quiet=False, quiet=False,
staging=False, staging=False,
debug=False, debug=False,

View File

@@ -47,6 +47,7 @@ Help for certbot itself cannot be provided until it is installed.
--no-bootstrap do not install OS dependencies --no-bootstrap do not install OS dependencies
--no-self-upgrade do not download updates --no-self-upgrade do not download updates
--os-packages-only install OS dependencies and exit --os-packages-only install OS dependencies and exit
--pypi-mirror use alternative Python package server
-v, --verbose provide more output -v, --verbose provide more output
-q, --quiet provide only update/error output; -q, --quiet provide only update/error output;
implies --non-interactive implies --non-interactive
@@ -66,6 +67,8 @@ for arg in "$@" ; do
NO_SELF_UPGRADE=1;; NO_SELF_UPGRADE=1;;
--no-bootstrap) --no-bootstrap)
NO_BOOTSTRAP=1;; NO_BOOTSTRAP=1;;
--pypi-mirror)
export PIP_INDEX_URL="https://mirrors.ustc.edu.cn/pypi/web/simple";;
--help) --help)
HELP=1;; HELP=1;;
--noninteractive|--non-interactive|renew) --noninteractive|--non-interactive|renew)
@@ -1118,6 +1121,7 @@ anything goes wrong, it will exit with a non-zero status code.
from __future__ import print_function from __future__ import print_function
from distutils.version import StrictVersion from distutils.version import StrictVersion
from hashlib import sha256 from hashlib import sha256
from os import environ
from os.path import join from os.path import join
from pipes import quote from pipes import quote
from shutil import rmtree from shutil import rmtree
@@ -1151,14 +1155,14 @@ except ImportError:
from urllib.parse import urlparse # 3.4 from urllib.parse import urlparse # 3.4
__version__ = 1, 3, 0 __version__ = 1, 4, 0
PIP_VERSION = '9.0.1' PIP_VERSION = '9.0.1'
DEFAULT_INDEX_BASE = 'https://pypi.python.org'
# wheel has a conditional dependency on argparse: # wheel has a conditional dependency on argparse:
maybe_argparse = ( maybe_argparse = (
[('https://pypi.python.org/packages/18/dd/' [('18/dd/e617cfc3f6210ae183374cd9f6a26b20514bbb5a792af97949c5aacddf0f/'
'e617cfc3f6210ae183374cd9f6a26b20514bbb5a792af97949c5aacddf0f/'
'argparse-1.4.0.tar.gz', 'argparse-1.4.0.tar.gz',
'62b089a55be1d8949cd2bc7e0df0bddb9e028faefc8c32038cc84862aefdd6e4')] '62b089a55be1d8949cd2bc7e0df0bddb9e028faefc8c32038cc84862aefdd6e4')]
if version_info < (2, 7, 0) else []) if version_info < (2, 7, 0) else [])
@@ -1166,18 +1170,14 @@ maybe_argparse = (
PACKAGES = maybe_argparse + [ PACKAGES = maybe_argparse + [
# Pip has no dependencies, as it vendors everything: # Pip has no dependencies, as it vendors everything:
('https://pypi.python.org/packages/11/b6/' ('11/b6/abcb525026a4be042b486df43905d6893fb04f05aac21c32c638e939e447/'
'abcb525026a4be042b486df43905d6893fb04f05aac21c32c638e939e447/' 'pip-{0}.tar.gz'.format(PIP_VERSION),
'pip-{0}.tar.gz'
.format(PIP_VERSION),
'09f243e1a7b461f654c26a725fa373211bb7ff17a9300058b205c61658ca940d'), '09f243e1a7b461f654c26a725fa373211bb7ff17a9300058b205c61658ca940d'),
# This version of setuptools has only optional dependencies: # This version of setuptools has only optional dependencies:
('https://pypi.python.org/packages/69/65/' ('69/65/4c544cde88d4d876cdf5cbc5f3f15d02646477756d89547e9a7ecd6afa76/'
'4c544cde88d4d876cdf5cbc5f3f15d02646477756d89547e9a7ecd6afa76/'
'setuptools-20.2.2.tar.gz', 'setuptools-20.2.2.tar.gz',
'24fcfc15364a9fe09a220f37d2dcedc849795e3de3e4b393ee988e66a9cbd85a'), '24fcfc15364a9fe09a220f37d2dcedc849795e3de3e4b393ee988e66a9cbd85a'),
('https://pypi.python.org/packages/c9/1d/' ('c9/1d/bd19e691fd4cfe908c76c429fe6e4436c9e83583c4414b54f6c85471954a/'
'bd19e691fd4cfe908c76c429fe6e4436c9e83583c4414b54f6c85471954a/'
'wheel-0.29.0.tar.gz', 'wheel-0.29.0.tar.gz',
'1ebb8ad7e26b448e9caa4773d2357849bf80ff9e313964bcaf79cbf0201a1648') '1ebb8ad7e26b448e9caa4773d2357849bf80ff9e313964bcaf79cbf0201a1648')
] ]
@@ -1198,12 +1198,13 @@ def hashed_download(url, temp, digest):
# >=2.7.9 verifies HTTPS certs itself, and, in any case, the cert # >=2.7.9 verifies HTTPS certs itself, and, in any case, the cert
# authenticity has only privacy (not arbitrary code execution) # authenticity has only privacy (not arbitrary code execution)
# implications, since we're checking hashes. # implications, since we're checking hashes.
def opener(): def opener(using_https=True):
opener = build_opener(HTTPSHandler()) opener = build_opener(HTTPSHandler())
# Strip out HTTPHandler to prevent MITM spoof: if using_https:
for handler in opener.handlers: # Strip out HTTPHandler to prevent MITM spoof:
if isinstance(handler, HTTPHandler): for handler in opener.handlers:
opener.handlers.remove(handler) if isinstance(handler, HTTPHandler):
opener.handlers.remove(handler)
return opener return opener
def read_chunks(response, chunk_size): def read_chunks(response, chunk_size):
@@ -1213,8 +1214,9 @@ def hashed_download(url, temp, digest):
break break
yield chunk yield chunk
response = opener().open(url) parsed_url = urlparse(url)
path = join(temp, urlparse(url).path.split('/')[-1]) response = opener(using_https=parsed_url.scheme == 'https').open(url)
path = join(temp, parsed_url.path.split('/')[-1])
actual_hash = sha256() actual_hash = sha256()
with open(path, 'wb') as file: with open(path, 'wb') as file:
for chunk in read_chunks(response, 4096): for chunk in read_chunks(response, 4096):
@@ -1227,6 +1229,24 @@ def hashed_download(url, temp, digest):
return path return path
def get_index_base():
"""Return the URL to the dir containing the "packages" folder.
Try to wring something out of PIP_INDEX_URL, if set. Hack "/simple" off the
end if it's there; that is likely to give us the right dir.
"""
env_var = environ.get('PIP_INDEX_URL', '').rstrip('/')
if env_var:
SIMPLE = '/simple'
if env_var.endswith(SIMPLE):
return env_var[:-len(SIMPLE)]
else:
return env_var
else:
return DEFAULT_INDEX_BASE
def main(): def main():
pip_version = StrictVersion(check_output(['pip', '--version']) pip_version = StrictVersion(check_output(['pip', '--version'])
.decode('utf-8').split()[1]) .decode('utf-8').split()[1])
@@ -1234,11 +1254,13 @@ def main():
if pip_version >= min_pip_version: if pip_version >= min_pip_version:
return 0 return 0
has_pip_cache = pip_version >= StrictVersion('6.0') has_pip_cache = pip_version >= StrictVersion('6.0')
index_base = get_index_base()
temp = mkdtemp(prefix='pipstrap-') temp = mkdtemp(prefix='pipstrap-')
try: try:
downloads = [hashed_download(url, temp, digest) downloads = [hashed_download(index_base + '/packages/' + path,
for url, digest in PACKAGES] temp,
digest)
for path, digest in PACKAGES]
check_output('pip install --no-index --no-deps -U ' + check_output('pip install --no-index --no-deps -U ' +
# Disable cache since we're not using it and it otherwise # Disable cache since we're not using it and it otherwise
# sometimes throws permission warnings: # sometimes throws permission warnings:

View File

@@ -47,6 +47,7 @@ Help for certbot itself cannot be provided until it is installed.
--no-bootstrap do not install OS dependencies --no-bootstrap do not install OS dependencies
--no-self-upgrade do not download updates --no-self-upgrade do not download updates
--os-packages-only install OS dependencies and exit --os-packages-only install OS dependencies and exit
--pypi-mirror use alternative Python package server
-v, --verbose provide more output -v, --verbose provide more output
-q, --quiet provide only update/error output; -q, --quiet provide only update/error output;
implies --non-interactive implies --non-interactive
@@ -66,6 +67,8 @@ for arg in "$@" ; do
NO_SELF_UPGRADE=1;; NO_SELF_UPGRADE=1;;
--no-bootstrap) --no-bootstrap)
NO_BOOTSTRAP=1;; NO_BOOTSTRAP=1;;
--pypi-mirror)
export PIP_INDEX_URL="https://mirrors.ustc.edu.cn/pypi/web/simple";;
--help) --help)
HELP=1;; HELP=1;;
--noninteractive|--non-interactive|renew) --noninteractive|--non-interactive|renew)

View File

@@ -23,6 +23,7 @@ anything goes wrong, it will exit with a non-zero status code.
from __future__ import print_function from __future__ import print_function
from distutils.version import StrictVersion from distutils.version import StrictVersion
from hashlib import sha256 from hashlib import sha256
from os import environ
from os.path import join from os.path import join
from pipes import quote from pipes import quote
from shutil import rmtree from shutil import rmtree
@@ -56,14 +57,14 @@ except ImportError:
from urllib.parse import urlparse # 3.4 from urllib.parse import urlparse # 3.4
__version__ = 1, 3, 0 __version__ = 1, 4, 0
PIP_VERSION = '9.0.1' PIP_VERSION = '9.0.1'
DEFAULT_INDEX_BASE = 'https://pypi.python.org'
# wheel has a conditional dependency on argparse: # wheel has a conditional dependency on argparse:
maybe_argparse = ( maybe_argparse = (
[('https://pypi.python.org/packages/18/dd/' [('18/dd/e617cfc3f6210ae183374cd9f6a26b20514bbb5a792af97949c5aacddf0f/'
'e617cfc3f6210ae183374cd9f6a26b20514bbb5a792af97949c5aacddf0f/'
'argparse-1.4.0.tar.gz', 'argparse-1.4.0.tar.gz',
'62b089a55be1d8949cd2bc7e0df0bddb9e028faefc8c32038cc84862aefdd6e4')] '62b089a55be1d8949cd2bc7e0df0bddb9e028faefc8c32038cc84862aefdd6e4')]
if version_info < (2, 7, 0) else []) if version_info < (2, 7, 0) else [])
@@ -71,18 +72,14 @@ maybe_argparse = (
PACKAGES = maybe_argparse + [ PACKAGES = maybe_argparse + [
# Pip has no dependencies, as it vendors everything: # Pip has no dependencies, as it vendors everything:
('https://pypi.python.org/packages/11/b6/' ('11/b6/abcb525026a4be042b486df43905d6893fb04f05aac21c32c638e939e447/'
'abcb525026a4be042b486df43905d6893fb04f05aac21c32c638e939e447/' 'pip-{0}.tar.gz'.format(PIP_VERSION),
'pip-{0}.tar.gz'
.format(PIP_VERSION),
'09f243e1a7b461f654c26a725fa373211bb7ff17a9300058b205c61658ca940d'), '09f243e1a7b461f654c26a725fa373211bb7ff17a9300058b205c61658ca940d'),
# This version of setuptools has only optional dependencies: # This version of setuptools has only optional dependencies:
('https://pypi.python.org/packages/69/65/' ('69/65/4c544cde88d4d876cdf5cbc5f3f15d02646477756d89547e9a7ecd6afa76/'
'4c544cde88d4d876cdf5cbc5f3f15d02646477756d89547e9a7ecd6afa76/'
'setuptools-20.2.2.tar.gz', 'setuptools-20.2.2.tar.gz',
'24fcfc15364a9fe09a220f37d2dcedc849795e3de3e4b393ee988e66a9cbd85a'), '24fcfc15364a9fe09a220f37d2dcedc849795e3de3e4b393ee988e66a9cbd85a'),
('https://pypi.python.org/packages/c9/1d/' ('c9/1d/bd19e691fd4cfe908c76c429fe6e4436c9e83583c4414b54f6c85471954a/'
'bd19e691fd4cfe908c76c429fe6e4436c9e83583c4414b54f6c85471954a/'
'wheel-0.29.0.tar.gz', 'wheel-0.29.0.tar.gz',
'1ebb8ad7e26b448e9caa4773d2357849bf80ff9e313964bcaf79cbf0201a1648') '1ebb8ad7e26b448e9caa4773d2357849bf80ff9e313964bcaf79cbf0201a1648')
] ]
@@ -103,12 +100,13 @@ def hashed_download(url, temp, digest):
# >=2.7.9 verifies HTTPS certs itself, and, in any case, the cert # >=2.7.9 verifies HTTPS certs itself, and, in any case, the cert
# authenticity has only privacy (not arbitrary code execution) # authenticity has only privacy (not arbitrary code execution)
# implications, since we're checking hashes. # implications, since we're checking hashes.
def opener(): def opener(using_https=True):
opener = build_opener(HTTPSHandler()) opener = build_opener(HTTPSHandler())
# Strip out HTTPHandler to prevent MITM spoof: if using_https:
for handler in opener.handlers: # Strip out HTTPHandler to prevent MITM spoof:
if isinstance(handler, HTTPHandler): for handler in opener.handlers:
opener.handlers.remove(handler) if isinstance(handler, HTTPHandler):
opener.handlers.remove(handler)
return opener return opener
def read_chunks(response, chunk_size): def read_chunks(response, chunk_size):
@@ -118,8 +116,9 @@ def hashed_download(url, temp, digest):
break break
yield chunk yield chunk
response = opener().open(url) parsed_url = urlparse(url)
path = join(temp, urlparse(url).path.split('/')[-1]) response = opener(using_https=parsed_url.scheme == 'https').open(url)
path = join(temp, parsed_url.path.split('/')[-1])
actual_hash = sha256() actual_hash = sha256()
with open(path, 'wb') as file: with open(path, 'wb') as file:
for chunk in read_chunks(response, 4096): for chunk in read_chunks(response, 4096):
@@ -132,6 +131,24 @@ def hashed_download(url, temp, digest):
return path return path
def get_index_base():
"""Return the URL to the dir containing the "packages" folder.
Try to wring something out of PIP_INDEX_URL, if set. Hack "/simple" off the
end if it's there; that is likely to give us the right dir.
"""
env_var = environ.get('PIP_INDEX_URL', '').rstrip('/')
if env_var:
SIMPLE = '/simple'
if env_var.endswith(SIMPLE):
return env_var[:-len(SIMPLE)]
else:
return env_var
else:
return DEFAULT_INDEX_BASE
def main(): def main():
pip_version = StrictVersion(check_output(['pip', '--version']) pip_version = StrictVersion(check_output(['pip', '--version'])
.decode('utf-8').split()[1]) .decode('utf-8').split()[1])
@@ -139,11 +156,13 @@ def main():
if pip_version >= min_pip_version: if pip_version >= min_pip_version:
return 0 return 0
has_pip_cache = pip_version >= StrictVersion('6.0') has_pip_cache = pip_version >= StrictVersion('6.0')
index_base = get_index_base()
temp = mkdtemp(prefix='pipstrap-') temp = mkdtemp(prefix='pipstrap-')
try: try:
downloads = [hashed_download(url, temp, digest) downloads = [hashed_download(index_base + '/packages/' + path,
for url, digest in PACKAGES] temp,
digest)
for path, digest in PACKAGES]
check_output('pip install --no-index --no-deps -U ' + check_output('pip install --no-index --no-deps -U ' +
# Disable cache since we're not using it and it otherwise # Disable cache since we're not using it and it otherwise
# sometimes throws permission warnings: # sometimes throws permission warnings: