From 85cc9f08facb7bd8d428688827ea6872d54c42c8 Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Thu, 11 Apr 2024 13:33:42 -0700 Subject: [PATCH] nextcloud: Use php-fpm container instead of apache container - Configuring just php-fpm is easier compared to configuring Apache + mod_php. There is no need to configure trusted proxies as the requests are made using the FastCGI protocol. - There is no need for a full web server as we already run Apache. - Place nextcloud data in /var/lib/container so that non-PHP files can be served directly without php-fpm involved. This location is more suitable for switching to nextcloud based on a .deb file (if ever). This is done by configuring the volume to serve a bind mounted directory of our choice. - Update Apache configuration to proxy to php-fpm instead of another web server. Include the changes needed for Apache configuration to serve non-php files directly. - Managed the volume using quadlet podman systemd generator. Signed-off-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- plinth/action_utils.py | 41 ++++++++++++++++--- plinth/modules/nextcloud/__init__.py | 2 +- .../conf-available/nextcloud-freedombox.conf | 38 ++++++++++++++--- plinth/modules/nextcloud/manifest.py | 4 +- plinth/modules/nextcloud/privileged.py | 20 +++++---- 5 files changed, 80 insertions(+), 25 deletions(-) diff --git a/plinth/action_utils.py b/plinth/action_utils.py index 97201bb32..61783f21a 100644 --- a/plinth/action_utils.py +++ b/plinth/action_utils.py @@ -489,22 +489,44 @@ def is_package_manager_busy(): return False -def podman_create(container_name: str, image_name: str, - volumes: dict[str, str] | None = None, +def podman_create(container_name: str, image_name: str, volume_name: str, + volume_path: str, volumes: dict[str, str] | None = None, env: dict[str, str] | None = None): """Remove and recreate a podman container.""" + service_stop(f'{volume_name}-volume.service') service_stop(container_name) directory = pathlib.Path('/etc/containers/systemd') directory.mkdir(parents=True, exist_ok=True) + subprocess.run(['podman', 'volume', 'rm', '--force', volume_name], + check=False) + + directory = pathlib.Path('/etc/containers/systemd') + directory.mkdir(parents=True, exist_ok=True) + + pathlib.Path(volume_path).mkdir(parents=True, exist_ok=True) + # Create storage volume + volume_file = directory / f'{volume_name}.volume' + contents = f'''[Volume] +Device={volume_path} +Driver=local +VolumeName={volume_name} +Options=bind +''' + volume_file.write_text(contents) + service_file = directory / f'{container_name}.container' volume_lines = '\n'.join([ f'Volume={source}:{dest}' for source, dest in (volumes or {}).items() ]) env_lines = '\n'.join( [f'Environment={key}={value}' for key, value in (env or {}).items()]) - contents = f'''[Container] + contents = f'''[Unit] +Requires=nextcloud-freedombox-volume.service +After=nextcloud-freedombox-volume.service + +[Container] AutoUpdate=registry ContainerName=%N {env_lines} @@ -522,11 +544,18 @@ WantedBy=default.target service_daemon_reload() -def podman_uninstall(container_name: str, volume_name: str, image_name: str): +def podman_uninstall(container_name: str, volume_name: str, image_name: str, + volume_path: str): """Remove a podman container's components and systemd unit.""" - subprocess.run(['podman', 'volume', 'rm', volume_name], check=True) - subprocess.run(['podman', 'image', 'rm', image_name], check=True) + subprocess.run(['podman', 'volume', 'rm', '--force', volume_name], + check=True) + subprocess.run(['podman', 'image', 'rm', '--ignore', image_name], + check=True) + volume_file = pathlib.Path( + '/etc/containers/systemd/') / f'{volume_name}.volume' + volume_file.unlink(missing_ok=True) service_file = pathlib.Path( '/etc/containers/systemd/') / f'{container_name}.container' service_file.unlink(missing_ok=True) + shutil.rmtree(volume_path, ignore_errors=True) service_daemon_reload() diff --git a/plinth/modules/nextcloud/__init__.py b/plinth/modules/nextcloud/__init__.py index f21e3f1aa..9ae5dc3f7 100644 --- a/plinth/modules/nextcloud/__init__.py +++ b/plinth/modules/nextcloud/__init__.py @@ -87,7 +87,7 @@ class NextcloudApp(app_module.App): self.add(firewall) firewall_local_protection = FirewallLocalProtection( - 'firewall-local-protection-nextcloud', ['8181']) + 'firewall-local-protection-nextcloud', ['9000']) self.add(firewall_local_protection) webserver = Webserver('webserver-nextcloud', 'nextcloud-freedombox', diff --git a/plinth/modules/nextcloud/data/usr/share/freedombox/etc/apache2/conf-available/nextcloud-freedombox.conf b/plinth/modules/nextcloud/data/usr/share/freedombox/etc/apache2/conf-available/nextcloud-freedombox.conf index 8d3758f54..b201d0e2f 100644 --- a/plinth/modules/nextcloud/data/usr/share/freedombox/etc/apache2/conf-available/nextcloud-freedombox.conf +++ b/plinth/modules/nextcloud/data/usr/share/freedombox/etc/apache2/conf-available/nextcloud-freedombox.conf @@ -16,10 +16,36 @@ Redirect 301 /.well-known/caldav /nextcloud/remote.php/dav Redirect 301 /.well-known/webfinger /nextcloud/index.php/.well-known/webfinger Redirect 301 /.well-known/nodeinfo /nextcloud/index.php/.well-known/nodeinfo - - ProxyPass http://127.0.0.1:8181 +Alias /nextcloud/ /var/lib/nextcloud/ - ## Send the scheme from user's request to enable Nextcloud to redirect URLs, - ## set cookies, set absolute URLs (if any) properly. - RequestHeader set X-Forwarded-Proto 'https' env=HTTPS - + + ProxyPassMatch "^/nextcloud/(.*\.php(/.*)?)$" "fcgi://localhost:9000/var/www/html/$1" + + + + + + # Enable http authorization headers + + SetEnvIfNoCase ^Authorization$ "(.+)" HTTP_AUTHORIZATION=$1 + + + + # Deny access to raw php sources by default + # To re-enable it's recommended to enable access to the files + # only in specific virtual host or directory + Require all denied + + # Deny access to files without filename (e.g. '.php') + + Require all denied + + + + + Require all granted + + # Allow a limited set of directives in .htaccess files found in /, /config, + # and /data directories of nextcloud. + AllowOverride AuthConfig FileInfo Indexes Limit Options + diff --git a/plinth/modules/nextcloud/manifest.py b/plinth/modules/nextcloud/manifest.py index 31a1fce10..fa875a67a 100644 --- a/plinth/modules/nextcloud/manifest.py +++ b/plinth/modules/nextcloud/manifest.py @@ -48,9 +48,7 @@ clients = [{ backup = { 'data': { - 'directories': [ - '/var/lib/containers/storage/volumes/nextcloud-volume-freedombox/' - ], + 'directories': ['/var/lib/nextcloud/'], 'files': ['/var/lib/plinth/backups-data/nextcloud-database.sql'] } } diff --git a/plinth/modules/nextcloud/privileged.py b/plinth/modules/nextcloud/privileged.py index dd19ef49a..ac9358a61 100644 --- a/plinth/modules/nextcloud/privileged.py +++ b/plinth/modules/nextcloud/privileged.py @@ -17,8 +17,8 @@ from plinth.actions import privileged CONTAINER_NAME = 'nextcloud-freedombox' SERVICE_NAME = 'nextcloud-freedombox' -VOLUME_NAME = 'nextcloud-volume-freedombox' -IMAGE_NAME = 'docker.io/library/nextcloud:stable-apache' +VOLUME_NAME = 'nextcloud-freedombox' +IMAGE_NAME = 'docker.io/library/nextcloud:stable-fpm' DB_HOST = 'localhost' DB_NAME = 'nextcloud_fbx' @@ -26,8 +26,7 @@ DB_USER = 'nextcloud_fbx' GUI_ADMIN = 'nextcloud-admin' REDIS_DB = 8 # Don't clash with other redis apps -_volume_path = pathlib.Path( - '/var/lib/containers/storage/volumes/') / VOLUME_NAME +_data_path = pathlib.Path('/var/lib/nextcloud/') _systemd_location = pathlib.Path('/etc/systemd/system/') _cron_service_file = _systemd_location / 'nextcloud-cron-freedombox.service' _cron_timer_file = _systemd_location / 'nextcloud-cron-freedombox.timer' @@ -55,16 +54,18 @@ def setup(): '/run/slapd/ldapi': '/run/slapd/ldapi', VOLUME_NAME: '/var/www/html' } - env = {'TRUSTED_PROXIES': '127.0.0.1', 'OVERWRITEWEBROOT': '/nextcloud'} + env = {'OVERWRITEWEBROOT': '/nextcloud'} action_utils.podman_create(container_name=CONTAINER_NAME, - image_name=IMAGE_NAME, volumes=volumes, env=env) + image_name=IMAGE_NAME, volume_name=VOLUME_NAME, + volume_path=str(_data_path), volumes=volumes, + env=env) action_utils.service_start(CONTAINER_NAME) # OCC isn't immediately available after the container is spun up. # Wait until CAN_INSTALL file is available. timeout = 300 while timeout > 0: - if (_volume_path / '_data/config/CAN_INSTALL').exists(): + if (_data_path / 'config/CAN_INSTALL').exists(): break timeout = timeout - 1 @@ -278,7 +279,8 @@ def uninstall(): _drop_database() action_utils.podman_uninstall(container_name=CONTAINER_NAME, volume_name=VOLUME_NAME, - image_name=IMAGE_NAME) + image_name=IMAGE_NAME, + volume_path=str(_data_path)) for path in [_cron_service_file, _cron_timer_file]: path.unlink(missing_ok=True) @@ -366,7 +368,7 @@ def _get_dbpassword(): def _create_redis_config(): """Create a php file for Redis configuration.""" - config_file = _volume_path / '_data/config/freedombox.config.php' + config_file = _data_path / 'config/freedombox.config.php' file_content = fr''' '\OC\Memcache\Redis',