diff --git a/.gitignore b/.gitignore index f2e8380a2..8ed9a9b69 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,6 @@ *.pyc *.py.bak *.tiny.css -data/var/log/plinth/*.log -data/var/lib/plinth/django-secret.key -data/var/lib/plinth/*.sqlite3 -data/var/lib/plinth/sessions/* -data/var/lib/plinth/.ssh/ -data/var/run/*.pid doc/manual/*/*.pdf doc/manual/*/*.html doc/manual/*/*.xml diff --git a/INSTALL.md b/INSTALL.md index aa2b89fb7..771354100 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -16,7 +16,7 @@ FreedomBox [Manual](https://wiki.debian.org/FreedomBox/Manual/)'s ``` ``` - $ sudo apt install -y $(./run --list-dependencies) + $ sudo apt install -y $(./run --develop --list-dependencies) ``` Install additional dependencies by picking the list from debian/control file diff --git a/Vagrantfile b/Vagrantfile index bda895ea4..a6b41488d 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -32,7 +32,7 @@ Vagrant.configure(2) do |config| apt-get update # In case new dependencies conflict with old dependencies apt-mark hold freedombox - DEBIAN_FRONTEND=noninteractive apt-get install --no-upgrade -y $(plinth --list-dependencies) + DEBIAN_FRONTEND=noninteractive apt-get install --no-upgrade -y $(sudo -u plinth ./run --develop --list-dependencies) apt-mark unhold freedombox # Install ncurses-term DEBIAN_FRONTEND=noninteractive apt-get install -y ncurses-term diff --git a/actions/packages b/actions/packages index 6f853c0de..d7112413c 100755 --- a/actions/packages +++ b/actions/packages @@ -19,6 +19,7 @@ import apt.cache import apt_inst import apt_pkg from plinth import cfg +from plinth.action_utils import run_apt_command LOCK_FILE = '/var/lib/dpkg/lock' @@ -68,29 +69,9 @@ def _apt_hold(): subprocess.run(['apt-mark', 'unhold', 'freedombox'], check=True) -def _run_apt_command(arguments): - """Run apt-get with provided arguments.""" - # Ask apt-get to output its progress to file descriptor 3. - command = [ - 'apt-get', '--assume-yes', '--quiet=2', '--option', 'APT::Status-Fd=3' - ] + arguments - - # Duplicate stdout to file descriptor 3 for this process. - os.dup2(1, 3) - - # Pass on file descriptor 3 instead of closing it. Close stdout - # so that regular output is ignored. - env = os.environ.copy() - env['DEBIAN_FRONTEND'] = 'noninteractive' - process = subprocess.run(command, stdin=subprocess.DEVNULL, - stdout=subprocess.DEVNULL, close_fds=False, - env=env) - return process.returncode - - def subcommand_update(arguments): """Update apt package lists.""" - sys.exit(_run_apt_command(['update'])) + sys.exit(run_apt_command(['update'])) def subcommand_install(arguments): @@ -113,9 +94,11 @@ def subcommand_install(arguments): elif arguments.force_configuration == 'new': extra_arguments += ['-o', 'Dpkg::Options::=--force-confnew'] + subprocess.run(['dpkg', '--configure', '-a']) with _apt_hold(): - returncode = _run_apt_command(['install'] + extra_arguments + - arguments.packages) + run_apt_command(['--fix-broken', 'install']) + returncode = run_apt_command(['install'] + extra_arguments + + arguments.packages) sys.exit(returncode) diff --git a/actions/storage b/actions/storage index 6d5b97602..bf818d3de 100755 --- a/actions/storage +++ b/actions/storage @@ -259,8 +259,12 @@ def subcommand_mount(arguments): def subcommand_eject(arguments): """Eject a device by its path.""" device_path = arguments.device - drive = eject_drive_of_device(device_path) - print(json.dumps(drive)) + try: + drive = eject_drive_of_device(device_path) + print(json.dumps(drive)) + except Exception as exception: + print(exception, file=sys.stderr) + sys.exit(1) def _get_options(): @@ -277,6 +281,7 @@ def eject_drive_of_device(device_path): Return the details (model, vendor) of drives ejected. """ udisks = utils.import_from_gi('UDisks', '2.0') + glib = utils.import_from_gi('GLib', '2.0') client = udisks.Client.new_sync() object_manager = client.get_object_manager() @@ -303,7 +308,13 @@ def eject_drive_of_device(device_path): # Eject the drive drive = client.get_drive_for_block(block_device) if drive: - drive.call_eject_sync(_get_options(), None) + try: + drive.call_eject_sync(_get_options(), None) + except glib.Error: + # Ignore error during ejection as along as all the filesystems are + # unmounted, the disk can be removed. + pass + return { 'vendor': drive.props.vendor, 'model': drive.props.model, diff --git a/actions/upgrades b/actions/upgrades index d37297498..f96ba270c 100755 --- a/actions/upgrades +++ b/actions/upgrades @@ -11,10 +11,12 @@ import re import subprocess import sys +from plinth.action_utils import run_apt_command from plinth.modules.apache.components import check_url AUTO_CONF_FILE = '/etc/apt/apt.conf.d/20auto-upgrades' LOG_FILE = '/var/log/unattended-upgrades/unattended-upgrades.log' +DPKG_LOG_FILE = '/var/log/unattended-upgrades/unattended-upgrades-dpkg.log' BUSTER_BACKPORTS_RELEASE_FILE_URL = \ 'https://deb.debian.org/debian/dists/buster-backports/Release' @@ -82,6 +84,8 @@ def parse_arguments(): def subcommand_run(_): """Run unattended-upgrades""" + subprocess.run(['dpkg', '--configure', '-a']) + run_apt_command(['--fix-broken', 'install']) try: subprocess.Popen(['systemctl', 'start', 'freedombox-manual-upgrade'], stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, @@ -132,11 +136,19 @@ def subcommand_disable_auto(_): def subcommand_get_log(_): """Print the automatic upgrades log.""" try: + print('==> ' + os.path.basename(LOG_FILE)) with open(LOG_FILE, 'r') as file_handle: print(file_handle.read()) except IOError: pass + try: + print('==> ' + os.path.basename(DPKG_LOG_FILE)) + with open(DPKG_LOG_FILE, 'r') as file_handle: + print(file_handle.read()) + except IOError: + pass + def _get_protocol(): """Return the protocol to use for newly added repository sources.""" @@ -199,6 +211,12 @@ def _check_and_backports_sources(): 'backports.') return + release = subprocess.check_output(['lsb_release', '--release', + '--short']).decode().strip() + if release in ['testing', 'unstable']: + print(f'System release is {release}. Skip enabling backports.') + return + protocol = _get_protocol() if protocol == 'tor+http': print('Package download over Tor is enabled.') diff --git a/conftest.py b/conftest.py index 873409358..6b8cb0ce7 100644 --- a/conftest.py +++ b/conftest.py @@ -50,12 +50,21 @@ def fixture_load_cfg(): """Load test configuration.""" from plinth import cfg + keys = ('file_root', 'config_dir', 'data_dir', 'custom_static_dir', + 'store_file', 'actions_dir', 'doc_dir', 'server_dir', 'host', + 'port', 'use_x_forwarded_for', 'use_x_forwarded_host', + 'secure_proxy_ssl_header', 'box_name', 'develop') + saved_state = {} + for key in keys: + saved_state[key] = getattr(cfg, key) + root_dir = pathlib.Path(__file__).resolve().parent - test_data_dir = root_dir / 'plinth' / 'tests' / 'data' - cfg_file = test_data_dir / 'etc' / 'plinth' / 'plinth.config' - cfg.read(str(cfg_file), str(root_dir)) + cfg_file = root_dir / 'plinth' / 'develop.config' + cfg.read_file(str(cfg_file)) yield cfg - cfg.read() + + for key in keys: + setattr(cfg, key, saved_state[key]) @pytest.fixture(name='develop_mode') @@ -85,3 +94,29 @@ def fixture_needs_sudo(): """Skip test if sudo command is not available.""" if not os.path.isfile('/usr/bin/sudo'): pytest.skip('Needs sudo command installed.') + + +@pytest.fixture(scope='session') +def splinter_selenium_implicit_wait(): + """Disable implicit waiting.""" + return 0 + + +@pytest.fixture(scope='session') +def splinter_wait_time(): + """Disable explicit waiting.""" + return 0.01 + + +@pytest.fixture(scope='session') +def splinter_browser_load_condition(): + """When a page it loaded, wait until is available.""" + + def _load_condition(browser): + if browser.url == 'about:blank': + return True + + ready_state = browser.execute_script('return document.readyState;') + return ready_state == 'complete' + + return _load_condition diff --git a/container b/container index 095c81620..2829b819d 100755 --- a/container +++ b/container @@ -47,16 +47,15 @@ Shared folder: Using systemd-nspawn, the project directory is mounted as /freedombox inside the container. The project directory is determined as directory in which this script resides. The project folder from the container point of view will be read-only. Container should be able to write various -files such as build files, FreedomBox sqlite3 database and session files into -the /freedombox folder. To enable writing, an additional read-write folder is -overlayed onto /freedombox folder in the container. This directory can't be -created under the project folder and is created instead in -$XDG_DATA_HOME/freedombox-container/overlay/$DISTRIBUTION. If XDG_DATA_HOME is -not set, it is assumed to be $HOME/.local/shared/. Whenever data is written -into /freedombox directory inside the container, this directory on the host -receives the changes. See documentation for Overlay filesystem for further -details. When container is destroyed, this overlay folder is destroyed to -ensure clean state after bringing up the container again. +files such as build files into the /freedombox folder. To enable writing, an +additional read-write folder is overlayed onto /freedombox folder in the +container. This directory can't be created under the project folder and is +created instead in $XDG_DATA_HOME/freedombox-container/overlay/$DISTRIBUTION. +If XDG_DATA_HOME is not set, it is assumed to be $HOME/.local/shared/. Whenever +data is written into /freedombox directory inside the container, this directory +on the host receives the changes. See documentation for Overlay filesystem for +further details. When container is destroyed, this overlay folder is destroyed +to ensure clean state after bringing up the container again. Users: PrivateUsers configuration flag for systemd-nspawn is currently off. This means that each user's UID on the host is also the same UID in the @@ -158,9 +157,6 @@ sudo apt-mark unhold freedombox # Install ncurses-term sudo DEBIAN_FRONTEND=noninteractive apt-get install --yes ncurses-term -# Remove FreedomBox database lingering in source directory to start fresh -sudo rm -f /freedombox/data/var/lib/plinth/plinth.sqlite3 - echo 'alias freedombox-develop="sudo -u plinth /freedombox/run --develop"' \ >> /home/fbx/.bashrc ''' diff --git a/data/etc/apache2/conf-available/freedombox.conf b/data/etc/apache2/conf-available/freedombox.conf index f650cab65..7bf713dbf 100644 --- a/data/etc/apache2/conf-available/freedombox.conf +++ b/data/etc/apache2/conf-available/freedombox.conf @@ -11,6 +11,45 @@ Header set Strict-Transport-Security "max-age=31536000; includeSubDomains" env=H RedirectMatch "^/$" "/plinth" RedirectMatch "^/freedombox" "/plinth" +## +## Disable sending Referer (sic) header from FreedomBox web interface to +## external websites. This improves privacy by not disclosing FreedomBox +## domains/URLs to external domains. Apps such as blogs which want to popularize +## themselves with referrer header may still do so. +## +## A strict Content Security Policy. +## - @fonts are allowed only from FreedomBox itself. +## - /