We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[1.1.0-rc4] "Unable to create virtualenv. Check network settings and try again"
(Tested on a Tails 3.16 Admin Workstation by checking out 1.1.0-rc4 tag, without updating my servers.)

As expected, running `securedrop-admin` commands triggered the "run setup" step. However, the `securedrop-admin setup` step itself did not complete successfully; it went pretty far along but finally failed with this error:

"Unable to create virtualenv. Check network settings and try again"

Tor seems to be working fine. Possibly intermittent issues but good to warn users about and have mitigation instructions if it is likely to arise during updates.
[1.1.0-rc4] "Unable to create virtualenv. Check network settings and try again"
(Tested on a Tails 3.16 Admin Workstation by checking out 1.1.0-rc4 tag, without updating my servers.)

As expected, running `securedrop-admin` commands triggered the "run setup" step. However, the `securedrop-admin setup` step itself did not complete successfully; it went pretty far along but finally failed with this error:

"Unable to create virtualenv. Check network settings and try again"

Tor seems to be working fine. Possibly intermittent issues but good to warn users about and have mitigation instructions if it is likely to arise during updates.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `admin/bootstrap.py`
Content:
```
1 # -*- mode: python; coding: utf-8 -*-
2 #
3 # Copyright (C) 2013-2018 Freedom of the Press Foundation & al
4 # Copyright (C) 2018 Loic Dachary <loic@dachary.org>
5 #
6 #    This program is free software: you can redistribute it and/or modify
7 #    it under the terms of the GNU General Public License as published by
8 #    the Free Software Foundation, either version 3 of the License, or
9 #    (at your option) any later version.
10 #
11 #    This program is distributed in the hope that it will be useful,
12 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
13 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 #    GNU General Public License for more details.
15 #
16 #    You should have received a copy of the GNU General Public License
17 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 #
19 
20 import argparse
21 import logging
22 import os
23 import shutil
24 import subprocess
25 import sys
26 
27 sdlog = logging.getLogger(__name__)
28 
29 DIR = os.path.dirname(os.path.realpath(__file__))
30 VENV_DIR = os.path.join(DIR, ".venv3")
31 
32 
33 def setup_logger(verbose=False):
34     """ Configure logging handler """
35     # Set default level on parent
36     sdlog.setLevel(logging.DEBUG)
37     level = logging.DEBUG if verbose else logging.INFO
38 
39     stdout = logging.StreamHandler(sys.stdout)
40     stdout.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
41     stdout.setLevel(level)
42     sdlog.addHandler(stdout)
43 
44 
45 def run_command(command):
46     """
47     Wrapper function to display stdout for running command,
48     similar to how shelling out in a Bash script displays rolling output.
49 
50     Yields a list of the stdout from the `command`, and raises a
51     CalledProcessError if `command` returns non-zero.
52     """
53     popen = subprocess.Popen(command,
54                              stdout=subprocess.PIPE,
55                              stderr=subprocess.STDOUT)
56     for stdout_line in iter(popen.stdout.readline, b""):
57         yield stdout_line
58     popen.stdout.close()
59     return_code = popen.wait()
60     if return_code:
61         raise subprocess.CalledProcessError(return_code, command)
62 
63 
64 def is_tails():
65     try:
66         id = subprocess.check_output('lsb_release --id --short',
67                                      shell=True).strip()
68     except subprocess.CalledProcessError:
69         id = None
70 
71     # dirty hack to unreliably detect Tails 4.0~beta2
72     if id == b'Debian':
73         if os.uname()[1] == 'amnesia':
74             id = 'Tails'
75 
76     return id == 'Tails'
77 
78 
79 def clean_up_tails3_venv(virtualenv_dir=VENV_DIR):
80     """
81     Tails 3.x, based on debian stretch uses libpython3.5, whereas Tails 4.x is
82     based on Debian Buster and uses libpython3.7. This means that the Tails 3.x
83     virtualenv will not work under Tails 4.x, and will need to be destroyed and
84     rebuilt. We can detect if the version of libpython is 3.5 in the
85     admin/.venv3/ folder, and delete it if that's the case. This will ensure a
86     smooth upgrade from Tails 3.x to Tails 4.x.
87     """
88     if is_tails():
89         try:
90             dist = subprocess.check_output('lsb_release --codename --short',
91                                            shell=True).strip()
92         except subprocess.CalledProcessError:
93             dist = None
94 
95         # tails4 is based on buster
96         if dist == b'buster':
97             python_lib_path = os.path.join(virtualenv_dir, "lib/python3.5")
98             if os.path.exists(os.path.join(python_lib_path)):
99                 sdlog.info(
100                     "Tails 3 Python 3 virtualenv detected. "
101                     "Removing it."
102                 )
103                 shutil.rmtree(virtualenv_dir)
104                 sdlog.info("Tails 3 Python 3 virtualenv deleted.")
105 
106 
107 def checkenv(args):
108     clean_up_tails3_venv(VENV_DIR)
109     if not os.path.exists(os.path.join(VENV_DIR, "bin/activate")):
110         sdlog.error('Please run "securedrop-admin setup".')
111         sys.exit(1)
112 
113 
114 def maybe_torify():
115     if is_tails():
116         return ['torify']
117     else:
118         return []
119 
120 
121 def install_apt_dependencies(args):
122     """
123     Install apt dependencies in Tails. In order to install Ansible in
124     a virtualenv, first there are a number of Python prerequisites.
125     """
126     sdlog.info("Installing SecureDrop Admin dependencies")
127     sdlog.info(("You'll be prompted for the temporary Tails admin password,"
128                 " which was set on Tails login screen"))
129 
130     apt_command = ['sudo', 'su', '-c',
131                    "apt-get update && \
132                    apt-get -q -o=Dpkg::Use-Pty=0 install -y \
133                    python3-virtualenv \
134                    python3-yaml \
135                    python3-pip \
136                    ccontrol \
137                    virtualenv \
138                    libffi-dev \
139                    libssl-dev \
140                    libpython3-dev",
141                    ]
142 
143     try:
144         # Print command results in real-time, to keep Admin apprised
145         # of progress during long-running command.
146         for output_line in run_command(apt_command):
147             print(output_line.decode('utf-8').rstrip())
148     except subprocess.CalledProcessError:
149         # Tails supports apt persistence, which was used by SecureDrop
150         # under Tails 2.x. If updates are being applied, don't try to pile
151         # on with more apt requests.
152         sdlog.error(("Failed to install apt dependencies. Check network"
153                      " connection and try again."))
154         raise
155 
156 
157 def envsetup(args):
158     """Installs Admin tooling required for managing SecureDrop. Specifically:
159 
160         * updates apt-cache
161         * installs apt packages for Python virtualenv
162         * creates virtualenv
163         * installs pip packages inside virtualenv
164 
165     The virtualenv is created within the Persistence volume in Tails, so that
166     Ansible is available to the Admin on subsequent boots without requiring
167     installation of packages again.
168     """
169     # clean up tails 3.x venv when migrating to tails 4.x
170     clean_up_tails3_venv(VENV_DIR)
171 
172     # virtualenv doesnt exist? Install dependencies and create
173     if not os.path.exists(VENV_DIR):
174 
175         install_apt_dependencies(args)
176 
177         # Technically you can create a virtualenv from within python
178         # but pip can only be run over tor on tails, and debugging that
179         # along with instaling a third-party dependency is not worth
180         # the effort here.
181         sdlog.info("Setting up virtualenv")
182         try:
183             sdlog.debug(subprocess.check_output(
184                 maybe_torify() + ['virtualenv', '--python=python3', VENV_DIR],
185                 stderr=subprocess.STDOUT))
186         except subprocess.CalledProcessError as e:
187             sdlog.debug(e.output)
188             sdlog.error(("Unable to create virtualenv. Check network settings"
189                          " and try again."))
190             raise
191     else:
192         sdlog.info("Virtualenv already exists, not creating")
193 
194     install_pip_dependencies(args)
195     if os.path.exists(os.path.join(DIR, 'setup.py')):
196         install_pip_self(args)
197 
198     sdlog.info("Finished installing SecureDrop dependencies")
199 
200 
201 def install_pip_self(args):
202     pip_install_cmd = [
203         os.path.join(VENV_DIR, 'bin', 'pip3'),
204         'install', '-e', DIR
205     ]
206     try:
207         subprocess.check_output(maybe_torify() + pip_install_cmd,
208                                 stderr=subprocess.STDOUT)
209     except subprocess.CalledProcessError as e:
210         sdlog.debug(e.output)
211         sdlog.error("Unable to install self, run with -v for more information")
212         raise
213 
214 
215 def install_pip_dependencies(args, pip_install_cmd=[
216         os.path.join(VENV_DIR, 'bin', 'pip3'),
217         'install',
218         # Specify requirements file.
219         '-r', os.path.join(DIR, 'requirements.txt'),
220         '--require-hashes',
221         # Make sure to upgrade packages only if necessary.
222         '-U', '--upgrade-strategy', 'only-if-needed',
223 ]):
224     """
225     Install Python dependencies via pip into virtualenv.
226     """
227 
228     sdlog.info("Checking Python dependencies for securedrop-admin")
229     try:
230         pip_output = subprocess.check_output(maybe_torify() + pip_install_cmd,
231                                              stderr=subprocess.STDOUT)
232     except subprocess.CalledProcessError as e:
233         sdlog.debug(e.output)
234         sdlog.error(("Failed to install pip dependencies. Check network"
235                      " connection and try again."))
236         raise
237 
238     sdlog.debug(pip_output)
239     if "Successfully installed" in str(pip_output):
240         sdlog.info("Python dependencies for securedrop-admin upgraded")
241     else:
242         sdlog.info("Python dependencies for securedrop-admin are up-to-date")
243 
244 
245 def parse_argv(argv):
246     parser = argparse.ArgumentParser()
247     parser.add_argument('-v', action='store_true', default=False,
248                         help="Increase verbosity on output")
249     parser.set_defaults(func=envsetup)
250 
251     subparsers = parser.add_subparsers()
252 
253     envsetup_parser = subparsers.add_parser(
254         'envsetup',
255         help='Set up the admin virtualenv.'
256     )
257     envsetup_parser.set_defaults(func=envsetup)
258 
259     checkenv_parser = subparsers.add_parser(
260         'checkenv',
261         help='Check that the admin virtualenv is properly set up.'
262     )
263     checkenv_parser.set_defaults(func=checkenv)
264 
265     return parser.parse_args(argv)
266 
267 
268 if __name__ == "__main__":
269     args = parse_argv(sys.argv[1:])
270     setup_logger(args.v)
271 
272     try:
273         args.func(args)
274     except Exception:
275         sys.exit(1)
276     else:
277         sys.exit(0)
278
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/admin/bootstrap.py b/admin/bootstrap.py
--- a/admin/bootstrap.py
+++ b/admin/bootstrap.py
@@ -64,12 +64,12 @@
 def is_tails():
     try:
         id = subprocess.check_output('lsb_release --id --short',
-                                     shell=True).strip()
+                                     shell=True).decode('utf-8').strip()
     except subprocess.CalledProcessError:
         id = None
 
     # dirty hack to unreliably detect Tails 4.0~beta2
-    if id == b'Debian':
+    if id == 'Debian':
         if os.uname()[1] == 'amnesia':
             id = 'Tails'

{"golden_diff": "diff --git a/admin/bootstrap.py b/admin/bootstrap.py\n--- a/admin/bootstrap.py\n+++ b/admin/bootstrap.py\n@@ -64,12 +64,12 @@\n def is_tails():\n     try:\n         id = subprocess.check_output('lsb_release --id --short',\n-                                     shell=True).strip()\n+                                     shell=True).decode('utf-8').strip()\n     except subprocess.CalledProcessError:\n         id = None\n \n     # dirty hack to unreliably detect Tails 4.0~beta2\n-    if id == b'Debian':\n+    if id == 'Debian':\n         if os.uname()[1] == 'amnesia':\n             id = 'Tails'\n", "issue": "[1.1.0-rc4] \"Unable to create virtualenv. Check network settings and try again\"\n(Tested on a Tails 3.16 Admin Workstation by checking out 1.1.0-rc4 tag, without updating my servers.)\r\n\r\nAs expected, running `securedrop-admin` commands triggered the \"run setup\" step. However, the `securedrop-admin setup` step itself did not complete successfully; it went pretty far along but finally failed with this error:\r\n\r\n\"Unable to create virtualenv. Check network settings and try again\"\r\n\r\nTor seems to be working fine. Possibly intermittent issues but good to warn users about and have mitigation instructions if it is likely to arise during updates.\n[1.1.0-rc4] \"Unable to create virtualenv. Check network settings and try again\"\n(Tested on a Tails 3.16 Admin Workstation by checking out 1.1.0-rc4 tag, without updating my servers.)\r\n\r\nAs expected, running `securedrop-admin` commands triggered the \"run setup\" step. However, the `securedrop-admin setup` step itself did not complete successfully; it went pretty far along but finally failed with this error:\r\n\r\n\"Unable to create virtualenv. Check network settings and try again\"\r\n\r\nTor seems to be working fine. Possibly intermittent issues but good to warn users about and have mitigation instructions if it is likely to arise during updates.\n", "before_files": [{"content": "# -*- mode: python; coding: utf-8 -*-\n#\n# Copyright (C) 2013-2018 Freedom of the Press Foundation & al\n# Copyright (C) 2018 Loic Dachary <loic@dachary.org>\n#\n#    This program is free software: you can redistribute it and/or modify\n#    it under the terms of the GNU General Public License as published by\n#    the Free Software Foundation, either version 3 of the License, or\n#    (at your option) any later version.\n#\n#    This program is distributed in the hope that it will be useful,\n#    but WITHOUT ANY WARRANTY; without even the implied warranty of\n#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n#    GNU General Public License for more details.\n#\n#    You should have received a copy of the GNU General Public License\n#    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n#\n\nimport argparse\nimport logging\nimport os\nimport shutil\nimport subprocess\nimport sys\n\nsdlog = logging.getLogger(__name__)\n\nDIR = os.path.dirname(os.path.realpath(__file__))\nVENV_DIR = os.path.join(DIR, \".venv3\")\n\n\ndef setup_logger(verbose=False):\n    \"\"\" Configure logging handler \"\"\"\n    # Set default level on parent\n    sdlog.setLevel(logging.DEBUG)\n    level = logging.DEBUG if verbose else logging.INFO\n\n    stdout = logging.StreamHandler(sys.stdout)\n    stdout.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))\n    stdout.setLevel(level)\n    sdlog.addHandler(stdout)\n\n\ndef run_command(command):\n    \"\"\"\n    Wrapper function to display stdout for running command,\n    similar to how shelling out in a Bash script displays rolling output.\n\n    Yields a list of the stdout from the `command`, and raises a\n    CalledProcessError if `command` returns non-zero.\n    \"\"\"\n    popen = subprocess.Popen(command,\n                             stdout=subprocess.PIPE,\n                             stderr=subprocess.STDOUT)\n    for stdout_line in iter(popen.stdout.readline, b\"\"):\n        yield stdout_line\n    popen.stdout.close()\n    return_code = popen.wait()\n    if return_code:\n        raise subprocess.CalledProcessError(return_code, command)\n\n\ndef is_tails():\n    try:\n        id = subprocess.check_output('lsb_release --id --short',\n                                     shell=True).strip()\n    except subprocess.CalledProcessError:\n        id = None\n\n    # dirty hack to unreliably detect Tails 4.0~beta2\n    if id == b'Debian':\n        if os.uname()[1] == 'amnesia':\n            id = 'Tails'\n\n    return id == 'Tails'\n\n\ndef clean_up_tails3_venv(virtualenv_dir=VENV_DIR):\n    \"\"\"\n    Tails 3.x, based on debian stretch uses libpython3.5, whereas Tails 4.x is\n    based on Debian Buster and uses libpython3.7. This means that the Tails 3.x\n    virtualenv will not work under Tails 4.x, and will need to be destroyed and\n    rebuilt. We can detect if the version of libpython is 3.5 in the\n    admin/.venv3/ folder, and delete it if that's the case. This will ensure a\n    smooth upgrade from Tails 3.x to Tails 4.x.\n    \"\"\"\n    if is_tails():\n        try:\n            dist = subprocess.check_output('lsb_release --codename --short',\n                                           shell=True).strip()\n        except subprocess.CalledProcessError:\n            dist = None\n\n        # tails4 is based on buster\n        if dist == b'buster':\n            python_lib_path = os.path.join(virtualenv_dir, \"lib/python3.5\")\n            if os.path.exists(os.path.join(python_lib_path)):\n                sdlog.info(\n                    \"Tails 3 Python 3 virtualenv detected. \"\n                    \"Removing it.\"\n                )\n                shutil.rmtree(virtualenv_dir)\n                sdlog.info(\"Tails 3 Python 3 virtualenv deleted.\")\n\n\ndef checkenv(args):\n    clean_up_tails3_venv(VENV_DIR)\n    if not os.path.exists(os.path.join(VENV_DIR, \"bin/activate\")):\n        sdlog.error('Please run \"securedrop-admin setup\".')\n        sys.exit(1)\n\n\ndef maybe_torify():\n    if is_tails():\n        return ['torify']\n    else:\n        return []\n\n\ndef install_apt_dependencies(args):\n    \"\"\"\n    Install apt dependencies in Tails. In order to install Ansible in\n    a virtualenv, first there are a number of Python prerequisites.\n    \"\"\"\n    sdlog.info(\"Installing SecureDrop Admin dependencies\")\n    sdlog.info((\"You'll be prompted for the temporary Tails admin password,\"\n                \" which was set on Tails login screen\"))\n\n    apt_command = ['sudo', 'su', '-c',\n                   \"apt-get update && \\\n                   apt-get -q -o=Dpkg::Use-Pty=0 install -y \\\n                   python3-virtualenv \\\n                   python3-yaml \\\n                   python3-pip \\\n                   ccontrol \\\n                   virtualenv \\\n                   libffi-dev \\\n                   libssl-dev \\\n                   libpython3-dev\",\n                   ]\n\n    try:\n        # Print command results in real-time, to keep Admin apprised\n        # of progress during long-running command.\n        for output_line in run_command(apt_command):\n            print(output_line.decode('utf-8').rstrip())\n    except subprocess.CalledProcessError:\n        # Tails supports apt persistence, which was used by SecureDrop\n        # under Tails 2.x. If updates are being applied, don't try to pile\n        # on with more apt requests.\n        sdlog.error((\"Failed to install apt dependencies. Check network\"\n                     \" connection and try again.\"))\n        raise\n\n\ndef envsetup(args):\n    \"\"\"Installs Admin tooling required for managing SecureDrop. Specifically:\n\n        * updates apt-cache\n        * installs apt packages for Python virtualenv\n        * creates virtualenv\n        * installs pip packages inside virtualenv\n\n    The virtualenv is created within the Persistence volume in Tails, so that\n    Ansible is available to the Admin on subsequent boots without requiring\n    installation of packages again.\n    \"\"\"\n    # clean up tails 3.x venv when migrating to tails 4.x\n    clean_up_tails3_venv(VENV_DIR)\n\n    # virtualenv doesnt exist? Install dependencies and create\n    if not os.path.exists(VENV_DIR):\n\n        install_apt_dependencies(args)\n\n        # Technically you can create a virtualenv from within python\n        # but pip can only be run over tor on tails, and debugging that\n        # along with instaling a third-party dependency is not worth\n        # the effort here.\n        sdlog.info(\"Setting up virtualenv\")\n        try:\n            sdlog.debug(subprocess.check_output(\n                maybe_torify() + ['virtualenv', '--python=python3', VENV_DIR],\n                stderr=subprocess.STDOUT))\n        except subprocess.CalledProcessError as e:\n            sdlog.debug(e.output)\n            sdlog.error((\"Unable to create virtualenv. Check network settings\"\n                         \" and try again.\"))\n            raise\n    else:\n        sdlog.info(\"Virtualenv already exists, not creating\")\n\n    install_pip_dependencies(args)\n    if os.path.exists(os.path.join(DIR, 'setup.py')):\n        install_pip_self(args)\n\n    sdlog.info(\"Finished installing SecureDrop dependencies\")\n\n\ndef install_pip_self(args):\n    pip_install_cmd = [\n        os.path.join(VENV_DIR, 'bin', 'pip3'),\n        'install', '-e', DIR\n    ]\n    try:\n        subprocess.check_output(maybe_torify() + pip_install_cmd,\n                                stderr=subprocess.STDOUT)\n    except subprocess.CalledProcessError as e:\n        sdlog.debug(e.output)\n        sdlog.error(\"Unable to install self, run with -v for more information\")\n        raise\n\n\ndef install_pip_dependencies(args, pip_install_cmd=[\n        os.path.join(VENV_DIR, 'bin', 'pip3'),\n        'install',\n        # Specify requirements file.\n        '-r', os.path.join(DIR, 'requirements.txt'),\n        '--require-hashes',\n        # Make sure to upgrade packages only if necessary.\n        '-U', '--upgrade-strategy', 'only-if-needed',\n]):\n    \"\"\"\n    Install Python dependencies via pip into virtualenv.\n    \"\"\"\n\n    sdlog.info(\"Checking Python dependencies for securedrop-admin\")\n    try:\n        pip_output = subprocess.check_output(maybe_torify() + pip_install_cmd,\n                                             stderr=subprocess.STDOUT)\n    except subprocess.CalledProcessError as e:\n        sdlog.debug(e.output)\n        sdlog.error((\"Failed to install pip dependencies. Check network\"\n                     \" connection and try again.\"))\n        raise\n\n    sdlog.debug(pip_output)\n    if \"Successfully installed\" in str(pip_output):\n        sdlog.info(\"Python dependencies for securedrop-admin upgraded\")\n    else:\n        sdlog.info(\"Python dependencies for securedrop-admin are up-to-date\")\n\n\ndef parse_argv(argv):\n    parser = argparse.ArgumentParser()\n    parser.add_argument('-v', action='store_true', default=False,\n                        help=\"Increase verbosity on output\")\n    parser.set_defaults(func=envsetup)\n\n    subparsers = parser.add_subparsers()\n\n    envsetup_parser = subparsers.add_parser(\n        'envsetup',\n        help='Set up the admin virtualenv.'\n    )\n    envsetup_parser.set_defaults(func=envsetup)\n\n    checkenv_parser = subparsers.add_parser(\n        'checkenv',\n        help='Check that the admin virtualenv is properly set up.'\n    )\n    checkenv_parser.set_defaults(func=checkenv)\n\n    return parser.parse_args(argv)\n\n\nif __name__ == \"__main__\":\n    args = parse_argv(sys.argv[1:])\n    setup_logger(args.v)\n\n    try:\n        args.func(args)\n    except Exception:\n        sys.exit(1)\n    else:\n        sys.exit(0)\n", "path": "admin/bootstrap.py"}], "after_files": [{"content": "# -*- mode: python; coding: utf-8 -*-\n#\n# Copyright (C) 2013-2018 Freedom of the Press Foundation & al\n# Copyright (C) 2018 Loic Dachary <loic@dachary.org>\n#\n#    This program is free software: you can redistribute it and/or modify\n#    it under the terms of the GNU General Public License as published by\n#    the Free Software Foundation, either version 3 of the License, or\n#    (at your option) any later version.\n#\n#    This program is distributed in the hope that it will be useful,\n#    but WITHOUT ANY WARRANTY; without even the implied warranty of\n#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n#    GNU General Public License for more details.\n#\n#    You should have received a copy of the GNU General Public License\n#    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n#\n\nimport argparse\nimport logging\nimport os\nimport shutil\nimport subprocess\nimport sys\n\nsdlog = logging.getLogger(__name__)\n\nDIR = os.path.dirname(os.path.realpath(__file__))\nVENV_DIR = os.path.join(DIR, \".venv3\")\n\n\ndef setup_logger(verbose=False):\n    \"\"\" Configure logging handler \"\"\"\n    # Set default level on parent\n    sdlog.setLevel(logging.DEBUG)\n    level = logging.DEBUG if verbose else logging.INFO\n\n    stdout = logging.StreamHandler(sys.stdout)\n    stdout.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))\n    stdout.setLevel(level)\n    sdlog.addHandler(stdout)\n\n\ndef run_command(command):\n    \"\"\"\n    Wrapper function to display stdout for running command,\n    similar to how shelling out in a Bash script displays rolling output.\n\n    Yields a list of the stdout from the `command`, and raises a\n    CalledProcessError if `command` returns non-zero.\n    \"\"\"\n    popen = subprocess.Popen(command,\n                             stdout=subprocess.PIPE,\n                             stderr=subprocess.STDOUT)\n    for stdout_line in iter(popen.stdout.readline, b\"\"):\n        yield stdout_line\n    popen.stdout.close()\n    return_code = popen.wait()\n    if return_code:\n        raise subprocess.CalledProcessError(return_code, command)\n\n\ndef is_tails():\n    try:\n        id = subprocess.check_output('lsb_release --id --short',\n                                     shell=True).decode('utf-8').strip()\n    except subprocess.CalledProcessError:\n        id = None\n\n    # dirty hack to unreliably detect Tails 4.0~beta2\n    if id == 'Debian':\n        if os.uname()[1] == 'amnesia':\n            id = 'Tails'\n\n    return id == 'Tails'\n\n\ndef clean_up_tails3_venv(virtualenv_dir=VENV_DIR):\n    \"\"\"\n    Tails 3.x, based on debian stretch uses libpython3.5, whereas Tails 4.x is\n    based on Debian Buster and uses libpython3.7. This means that the Tails 3.x\n    virtualenv will not work under Tails 4.x, and will need to be destroyed and\n    rebuilt. We can detect if the version of libpython is 3.5 in the\n    admin/.venv3/ folder, and delete it if that's the case. This will ensure a\n    smooth upgrade from Tails 3.x to Tails 4.x.\n    \"\"\"\n    if is_tails():\n        try:\n            dist = subprocess.check_output('lsb_release --codename --short',\n                                           shell=True).strip()\n        except subprocess.CalledProcessError:\n            dist = None\n\n        # tails4 is based on buster\n        if dist == b'buster':\n            python_lib_path = os.path.join(virtualenv_dir, \"lib/python3.5\")\n            if os.path.exists(os.path.join(python_lib_path)):\n                sdlog.info(\n                    \"Tails 3 Python 3 virtualenv detected. \"\n                    \"Removing it.\"\n                )\n                shutil.rmtree(virtualenv_dir)\n                sdlog.info(\"Tails 3 Python 3 virtualenv deleted.\")\n\n\ndef checkenv(args):\n    clean_up_tails3_venv(VENV_DIR)\n    if not os.path.exists(os.path.join(VENV_DIR, \"bin/activate\")):\n        sdlog.error('Please run \"securedrop-admin setup\".')\n        sys.exit(1)\n\n\ndef maybe_torify():\n    if is_tails():\n        return ['torify']\n    else:\n        return []\n\n\ndef install_apt_dependencies(args):\n    \"\"\"\n    Install apt dependencies in Tails. In order to install Ansible in\n    a virtualenv, first there are a number of Python prerequisites.\n    \"\"\"\n    sdlog.info(\"Installing SecureDrop Admin dependencies\")\n    sdlog.info((\"You'll be prompted for the temporary Tails admin password,\"\n                \" which was set on Tails login screen\"))\n\n    apt_command = ['sudo', 'su', '-c',\n                   \"apt-get update && \\\n                   apt-get -q -o=Dpkg::Use-Pty=0 install -y \\\n                   python3-virtualenv \\\n                   python3-yaml \\\n                   python3-pip \\\n                   ccontrol \\\n                   virtualenv \\\n                   libffi-dev \\\n                   libssl-dev \\\n                   libpython3-dev\",\n                   ]\n\n    try:\n        # Print command results in real-time, to keep Admin apprised\n        # of progress during long-running command.\n        for output_line in run_command(apt_command):\n            print(output_line.decode('utf-8').rstrip())\n    except subprocess.CalledProcessError:\n        # Tails supports apt persistence, which was used by SecureDrop\n        # under Tails 2.x. If updates are being applied, don't try to pile\n        # on with more apt requests.\n        sdlog.error((\"Failed to install apt dependencies. Check network\"\n                     \" connection and try again.\"))\n        raise\n\n\ndef envsetup(args):\n    \"\"\"Installs Admin tooling required for managing SecureDrop. Specifically:\n\n        * updates apt-cache\n        * installs apt packages for Python virtualenv\n        * creates virtualenv\n        * installs pip packages inside virtualenv\n\n    The virtualenv is created within the Persistence volume in Tails, so that\n    Ansible is available to the Admin on subsequent boots without requiring\n    installation of packages again.\n    \"\"\"\n    # clean up tails 3.x venv when migrating to tails 4.x\n    clean_up_tails3_venv(VENV_DIR)\n\n    # virtualenv doesnt exist? Install dependencies and create\n    if not os.path.exists(VENV_DIR):\n\n        install_apt_dependencies(args)\n\n        # Technically you can create a virtualenv from within python\n        # but pip can only be run over tor on tails, and debugging that\n        # along with instaling a third-party dependency is not worth\n        # the effort here.\n        sdlog.info(\"Setting up virtualenv\")\n        try:\n            sdlog.debug(subprocess.check_output(\n                maybe_torify() + ['virtualenv', '--python=python3', VENV_DIR],\n                stderr=subprocess.STDOUT))\n        except subprocess.CalledProcessError as e:\n            sdlog.debug(e.output)\n            sdlog.error((\"Unable to create virtualenv. Check network settings\"\n                         \" and try again.\"))\n            raise\n    else:\n        sdlog.info(\"Virtualenv already exists, not creating\")\n\n    install_pip_dependencies(args)\n    if os.path.exists(os.path.join(DIR, 'setup.py')):\n        install_pip_self(args)\n\n    sdlog.info(\"Finished installing SecureDrop dependencies\")\n\n\ndef install_pip_self(args):\n    pip_install_cmd = [\n        os.path.join(VENV_DIR, 'bin', 'pip3'),\n        'install', '-e', DIR\n    ]\n    try:\n        subprocess.check_output(maybe_torify() + pip_install_cmd,\n                                stderr=subprocess.STDOUT)\n    except subprocess.CalledProcessError as e:\n        sdlog.debug(e.output)\n        sdlog.error(\"Unable to install self, run with -v for more information\")\n        raise\n\n\ndef install_pip_dependencies(args, pip_install_cmd=[\n        os.path.join(VENV_DIR, 'bin', 'pip3'),\n        'install',\n        # Specify requirements file.\n        '-r', os.path.join(DIR, 'requirements.txt'),\n        '--require-hashes',\n        # Make sure to upgrade packages only if necessary.\n        '-U', '--upgrade-strategy', 'only-if-needed',\n]):\n    \"\"\"\n    Install Python dependencies via pip into virtualenv.\n    \"\"\"\n\n    sdlog.info(\"Checking Python dependencies for securedrop-admin\")\n    try:\n        pip_output = subprocess.check_output(maybe_torify() + pip_install_cmd,\n                                             stderr=subprocess.STDOUT)\n    except subprocess.CalledProcessError as e:\n        sdlog.debug(e.output)\n        sdlog.error((\"Failed to install pip dependencies. Check network\"\n                     \" connection and try again.\"))\n        raise\n\n    sdlog.debug(pip_output)\n    if \"Successfully installed\" in str(pip_output):\n        sdlog.info(\"Python dependencies for securedrop-admin upgraded\")\n    else:\n        sdlog.info(\"Python dependencies for securedrop-admin are up-to-date\")\n\n\ndef parse_argv(argv):\n    parser = argparse.ArgumentParser()\n    parser.add_argument('-v', action='store_true', default=False,\n                        help=\"Increase verbosity on output\")\n    parser.set_defaults(func=envsetup)\n\n    subparsers = parser.add_subparsers()\n\n    envsetup_parser = subparsers.add_parser(\n        'envsetup',\n        help='Set up the admin virtualenv.'\n    )\n    envsetup_parser.set_defaults(func=envsetup)\n\n    checkenv_parser = subparsers.add_parser(\n        'checkenv',\n        help='Check that the admin virtualenv is properly set up.'\n    )\n    checkenv_parser.set_defaults(func=checkenv)\n\n    return parser.parse_args(argv)\n\n\nif __name__ == \"__main__\":\n    args = parse_argv(sys.argv[1:])\n    setup_logger(args.v)\n\n    try:\n        args.func(args)\n    except Exception:\n        sys.exit(1)\n    else:\n        sys.exit(0)\n", "path": "admin/bootstrap.py"}]}