We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Test securedrop-admin with Tails 5.0
## Description

https://tails.boum.org/news/test_5.0-beta1/

Tails 5.0 is based on Debian Bullseye, which means it's using a newer Python version (3.9) among plenty of other things.

It's probably worth walking through a full SD install + backup/restore to make sure it works as expected.

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

{"golden_diff": "diff --git a/admin/bootstrap.py b/admin/bootstrap.py\n--- a/admin/bootstrap.py\n+++ b/admin/bootstrap.py\n@@ -138,7 +138,6 @@\n                    python3-virtualenv \\\n                    python3-yaml \\\n                    python3-pip \\\n-                   ccontrol \\\n                    virtualenv \\\n                    libffi-dev \\\n                    libssl-dev \\\n", "issue": "Test securedrop-admin with Tails 5.0\n## Description\r\n\r\nhttps://tails.boum.org/news/test_5.0-beta1/\r\n\r\nTails 5.0 is based on Debian Bullseye, which means it's using a newer Python version (3.9) among plenty of other things.\r\n\r\nIt's probably worth walking through a full SD install + backup/restore to make sure it works as expected.\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\nfrom typing import Iterator\n\nfrom typing import List\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: bool = False) -> None:\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: List[str]) -> Iterator[bytes]:\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    if popen.stdout is None:\n        raise EnvironmentError(\"Could not run command: None 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() -> bool:\n    try:\n        id = subprocess.check_output('lsb_release --id --short',\n                                     shell=True).decode('utf-8').strip()\n    except subprocess.CalledProcessError:\n        return False\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: str = VENV_DIR) -> None:\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            return 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: argparse.Namespace) -> None:\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() -> List[str]:\n    if is_tails():\n        return ['torify']\n    else:\n        return []\n\n\ndef install_apt_dependencies(args: argparse.Namespace) -> None:\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: argparse.Namespace, virtualenv_dir: str = VENV_DIR) -> None:\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(virtualenv_dir)\n\n    # virtualenv doesnt exist? Install dependencies and create\n    if not os.path.exists(virtualenv_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',\n                                  '--python=python3',\n                                  virtualenv_dir\n                                  ],\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            sdlog.debug(\"Cleaning up virtualenv\")\n            if os.path.exists(virtualenv_dir):\n                shutil.rmtree(virtualenv_dir)\n            raise\n    else:\n        sdlog.info(\"Virtualenv already exists, not creating\")\n\n    if args.t:\n        install_pip_dependencies(\n            args,\n            requirements_file='requirements-testinfra.txt',\n            desc=\"dependencies with verification support\"\n        )\n    else:\n        install_pip_dependencies(args)\n\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: argparse.Namespace) -> None:\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(\n    args: argparse.Namespace,\n    requirements_file: str = \"requirements.txt\",\n    desc: str = \"Python dependencies\",\n) -> None:\n    \"\"\"\n    Install Python dependencies via pip into virtualenv.\n    \"\"\"\n    pip_install_cmd = [\n        os.path.join(VENV_DIR, 'bin', 'pip3'),\n        'install',\n        '--no-deps',\n        '-r', os.path.join(DIR, requirements_file),\n        '--require-hashes',\n        '-U', '--upgrade-strategy', 'only-if-needed',\n    ]\n\n    sdlog.info(\"Checking {} for securedrop-admin\".format(desc))\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 {}. Check network\"\n                     \" connection and try again.\".format(desc)))\n        raise\n\n    sdlog.debug(pip_output)\n    if \"Successfully installed\" in str(pip_output):\n        sdlog.info(\"{} for securedrop-admin upgraded\".format(desc))\n    else:\n        sdlog.info(\"{} for securedrop-admin are up-to-date\".format(desc))\n\n\ndef parse_argv(argv: List[str]) -> argparse.Namespace:\n    parser = argparse.ArgumentParser()\n    parser.add_argument('-v', action='store_true', default=False,\n                        help=\"Increase verbosity on output\")\n    parser.add_argument('-t', action='store_true', default=False,\n                        help=\"Install additional test dependencies\")\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\nfrom typing import Iterator\n\nfrom typing import List\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: bool = False) -> None:\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: List[str]) -> Iterator[bytes]:\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    if popen.stdout is None:\n        raise EnvironmentError(\"Could not run command: None 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() -> bool:\n    try:\n        id = subprocess.check_output('lsb_release --id --short',\n                                     shell=True).decode('utf-8').strip()\n    except subprocess.CalledProcessError:\n        return False\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: str = VENV_DIR) -> None:\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            return 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: argparse.Namespace) -> None:\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() -> List[str]:\n    if is_tails():\n        return ['torify']\n    else:\n        return []\n\n\ndef install_apt_dependencies(args: argparse.Namespace) -> None:\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                   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: argparse.Namespace, virtualenv_dir: str = VENV_DIR) -> None:\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(virtualenv_dir)\n\n    # virtualenv doesnt exist? Install dependencies and create\n    if not os.path.exists(virtualenv_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',\n                                  '--python=python3',\n                                  virtualenv_dir\n                                  ],\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            sdlog.debug(\"Cleaning up virtualenv\")\n            if os.path.exists(virtualenv_dir):\n                shutil.rmtree(virtualenv_dir)\n            raise\n    else:\n        sdlog.info(\"Virtualenv already exists, not creating\")\n\n    if args.t:\n        install_pip_dependencies(\n            args,\n            requirements_file='requirements-testinfra.txt',\n            desc=\"dependencies with verification support\"\n        )\n    else:\n        install_pip_dependencies(args)\n\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: argparse.Namespace) -> None:\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(\n    args: argparse.Namespace,\n    requirements_file: str = \"requirements.txt\",\n    desc: str = \"Python dependencies\",\n) -> None:\n    \"\"\"\n    Install Python dependencies via pip into virtualenv.\n    \"\"\"\n    pip_install_cmd = [\n        os.path.join(VENV_DIR, 'bin', 'pip3'),\n        'install',\n        '--no-deps',\n        '-r', os.path.join(DIR, requirements_file),\n        '--require-hashes',\n        '-U', '--upgrade-strategy', 'only-if-needed',\n    ]\n\n    sdlog.info(\"Checking {} for securedrop-admin\".format(desc))\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 {}. Check network\"\n                     \" connection and try again.\".format(desc)))\n        raise\n\n    sdlog.debug(pip_output)\n    if \"Successfully installed\" in str(pip_output):\n        sdlog.info(\"{} for securedrop-admin upgraded\".format(desc))\n    else:\n        sdlog.info(\"{} for securedrop-admin are up-to-date\".format(desc))\n\n\ndef parse_argv(argv: List[str]) -> argparse.Namespace:\n    parser = argparse.ArgumentParser()\n    parser.add_argument('-v', action='store_true', default=False,\n                        help=\"Increase verbosity on output\")\n    parser.add_argument('-t', action='store_true', default=False,\n                        help=\"Install additional test dependencies\")\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"}]}