mirror of
https://github.com/Kozea/Radicale.git
synced 2025-08-04 18:22:26 +00:00
Merge pull request #1814 from pbiering/improve-writeability-check-on-startup
Improve writeability check on startup
This commit is contained in:
commit
1c97345fc9
6 changed files with 61 additions and 5 deletions
|
@ -3,6 +3,8 @@
|
||||||
## 3.5.5.dev
|
## 3.5.5.dev
|
||||||
* Improve: [auth] ldap: do not read server info by bind to avoid needless network traffic
|
* Improve: [auth] ldap: do not read server info by bind to avoid needless network traffic
|
||||||
* Fix: [storage] broken support of 'folder_umask'
|
* Fix: [storage] broken support of 'folder_umask'
|
||||||
|
* Improve: add details about platform and effective user on startup
|
||||||
|
* Improve: display owner+permissions on directories on startup, extend error message in case of missing permissions
|
||||||
|
|
||||||
## 3.5.4
|
## 3.5.4
|
||||||
* Improve: item filter enhanced for 3rd level supporting VALARM and honoring TRIGGER (offset or absolute)
|
* Improve: item filter enhanced for 3rd level supporting VALARM and honoring TRIGGER (offset or absolute)
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
# This file is part of Radicale - CalDAV and CardDAV server
|
# This file is part of Radicale - CalDAV and CardDAV server
|
||||||
# Copyright © 2014 Jean-Marc Martins
|
# Copyright © 2014 Jean-Marc Martins
|
||||||
# Copyright © 2012-2017 Guillaume Ayoub
|
# Copyright © 2012-2017 Guillaume Ayoub
|
||||||
# Copyright © 2017-2018 Unrud <unrud@outlook.com>
|
# Copyright © 2017-2022 Unrud <unrud@outlook.com>
|
||||||
|
# Copyright © 2025-2025 Peter Bieringer <pb@bieringer.de>
|
||||||
#
|
#
|
||||||
# This library is free software: you can redistribute it and/or modify
|
# This library is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
@ -23,6 +24,7 @@ Helper functions for working with the file system.
|
||||||
|
|
||||||
import errno
|
import errno
|
||||||
import os
|
import os
|
||||||
|
import pathlib
|
||||||
import posixpath
|
import posixpath
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
|
@ -314,3 +316,17 @@ def name_from_path(path: str, collection: "storage.BaseCollection") -> str:
|
||||||
raise ValueError("%r is not a component in collection %r" %
|
raise ValueError("%r is not a component in collection %r" %
|
||||||
(name, collection.path))
|
(name, collection.path))
|
||||||
return name
|
return name
|
||||||
|
|
||||||
|
|
||||||
|
def path_permissions(path):
|
||||||
|
path = pathlib.Path(path)
|
||||||
|
return [path.owner(), path.group(), path.stat().st_mode]
|
||||||
|
|
||||||
|
|
||||||
|
def path_permissions_as_string(path):
|
||||||
|
try:
|
||||||
|
pp = path_permissions(path)
|
||||||
|
s = "path=%r owner=%s group=%s mode=%o" % (path, pp[0], pp[1], pp[2])
|
||||||
|
except NotImplementedError:
|
||||||
|
s = "path=%r owner=UNKNOWN(unsupported on this system)" % (path)
|
||||||
|
return s
|
||||||
|
|
|
@ -25,6 +25,7 @@ Built-in WSGI server.
|
||||||
|
|
||||||
import http
|
import http
|
||||||
import os
|
import os
|
||||||
|
import platform
|
||||||
import select
|
import select
|
||||||
import socket
|
import socket
|
||||||
import socketserver
|
import socketserver
|
||||||
|
@ -297,7 +298,7 @@ def serve(configuration: config.Configuration,
|
||||||
info = "with PYTHONPATH=%r " % os.environ.get("PYTHONPATH")
|
info = "with PYTHONPATH=%r " % os.environ.get("PYTHONPATH")
|
||||||
else:
|
else:
|
||||||
info = ""
|
info = ""
|
||||||
logger.info("Starting Radicale %s(%s)", info, utils.packages_version())
|
logger.info("Starting Radicale %s(%s) as %s on %s", info, utils.packages_version(), utils.user_groups_as_string(), platform.platform())
|
||||||
# Copy configuration before modifying
|
# Copy configuration before modifying
|
||||||
configuration = configuration.copy()
|
configuration = configuration.copy()
|
||||||
configuration.update({"server": {"_internal_server": "True"}}, "server",
|
configuration.update({"server": {"_internal_server": "True"}}, "server",
|
||||||
|
|
|
@ -29,7 +29,7 @@ import sys
|
||||||
import time
|
import time
|
||||||
from typing import ClassVar, Iterator, Optional, Type
|
from typing import ClassVar, Iterator, Optional, Type
|
||||||
|
|
||||||
from radicale import config
|
from radicale import config, pathutils, utils
|
||||||
from radicale.log import logger
|
from radicale.log import logger
|
||||||
from radicale.storage.multifilesystem.base import CollectionBase, StorageBase
|
from radicale.storage.multifilesystem.base import CollectionBase, StorageBase
|
||||||
from radicale.storage.multifilesystem.cache import CollectionPartCache
|
from radicale.storage.multifilesystem.cache import CollectionPartCache
|
||||||
|
@ -165,10 +165,12 @@ class Storage(
|
||||||
if not os.path.exists(self._filesystem_folder):
|
if not os.path.exists(self._filesystem_folder):
|
||||||
logger.warning("Storage location: %r does not exist, creating now", self._filesystem_folder)
|
logger.warning("Storage location: %r does not exist, creating now", self._filesystem_folder)
|
||||||
self._makedirs_synced(self._filesystem_folder)
|
self._makedirs_synced(self._filesystem_folder)
|
||||||
|
logger.info("Storage location permissions: %s", pathutils.path_permissions_as_string(self._filesystem_folder))
|
||||||
logger.info("Storage location subfolder: %r", self._get_collection_root_folder())
|
logger.info("Storage location subfolder: %r", self._get_collection_root_folder())
|
||||||
if not os.path.exists(self._get_collection_root_folder()):
|
if not os.path.exists(self._get_collection_root_folder()):
|
||||||
logger.warning("Storage location subfolder: %r does not exist, creating now", self._get_collection_root_folder())
|
logger.warning("Storage location subfolder: %r does not exist, creating now", self._get_collection_root_folder())
|
||||||
self._makedirs_synced(self._get_collection_root_folder())
|
self._makedirs_synced(self._get_collection_root_folder())
|
||||||
|
logger.info("Storage location subfolder permissions: %s", pathutils.path_permissions_as_string(self._get_collection_root_folder()))
|
||||||
logger.info("Storage cache subfolder usage for 'item': %s", self._use_cache_subfolder_for_item)
|
logger.info("Storage cache subfolder usage for 'item': %s", self._use_cache_subfolder_for_item)
|
||||||
logger.info("Storage cache subfolder usage for 'history': %s", self._use_cache_subfolder_for_history)
|
logger.info("Storage cache subfolder usage for 'history': %s", self._use_cache_subfolder_for_history)
|
||||||
logger.info("Storage cache subfolder usage for 'sync-token': %s", self._use_cache_subfolder_for_synctoken)
|
logger.info("Storage cache subfolder usage for 'sync-token': %s", self._use_cache_subfolder_for_synctoken)
|
||||||
|
@ -185,6 +187,9 @@ class Storage(
|
||||||
logger.info("Storage item mtime resolution test result: %d %s" % (precision_unit, unit))
|
logger.info("Storage item mtime resolution test result: %d %s" % (precision_unit, unit))
|
||||||
if self._use_mtime_and_size_for_item_cache is False:
|
if self._use_mtime_and_size_for_item_cache is False:
|
||||||
logger.info("Storage cache using mtime and size for 'item' may be an option in case of performance issues")
|
logger.info("Storage cache using mtime and size for 'item' may be an option in case of performance issues")
|
||||||
|
except PermissionError as e:
|
||||||
|
logger.error("Directory permissions: %s / Effective user: %s", pathutils.path_permissions_as_string(self._get_collection_root_folder()), utils.user_groups_as_string())
|
||||||
|
raise e
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.warning("Storage item mtime resolution test result not successful")
|
logger.warning("Storage item mtime resolution test result not successful")
|
||||||
logger.debug("Storage cache action logging: %s", self._debug_cache_actions)
|
logger.debug("Storage cache action logging: %s", self._debug_cache_actions)
|
||||||
|
@ -193,3 +198,4 @@ class Storage(
|
||||||
if not os.path.exists(self._get_collection_cache_folder()):
|
if not os.path.exists(self._get_collection_cache_folder()):
|
||||||
logger.warning("Storage cache subfolder: %r does not exist, creating now", self._get_collection_cache_folder())
|
logger.warning("Storage cache subfolder: %r does not exist, creating now", self._get_collection_cache_folder())
|
||||||
self._makedirs_synced(self._get_collection_cache_folder())
|
self._makedirs_synced(self._get_collection_cache_folder())
|
||||||
|
logger.info("Storage cache subfolder permissions: %s", pathutils.path_permissions_as_string(self._get_collection_cache_folder()))
|
||||||
|
|
|
@ -22,7 +22,7 @@ import sys
|
||||||
from tempfile import TemporaryDirectory
|
from tempfile import TemporaryDirectory
|
||||||
from typing import IO, AnyStr, ClassVar, Iterator, Optional, Type
|
from typing import IO, AnyStr, ClassVar, Iterator, Optional, Type
|
||||||
|
|
||||||
from radicale import config, pathutils, storage, types
|
from radicale import config, logger, pathutils, storage, types, utils
|
||||||
from radicale.storage import multifilesystem # noqa:F401
|
from radicale.storage import multifilesystem # noqa:F401
|
||||||
|
|
||||||
|
|
||||||
|
@ -161,7 +161,13 @@ class StorageBase(storage.BaseStorage):
|
||||||
# Create parent dirs recursively
|
# Create parent dirs recursively
|
||||||
self._makedirs_synced(parent_filesystem_path)
|
self._makedirs_synced(parent_filesystem_path)
|
||||||
# Possible race!
|
# Possible race!
|
||||||
os.makedirs(filesystem_path, exist_ok=True)
|
try:
|
||||||
|
os.makedirs(filesystem_path, exist_ok=True)
|
||||||
|
except PermissionError as e:
|
||||||
|
logger.error("Directory permissions: %s / Effective user: %s", pathutils.path_permissions_as_string(parent_filesystem_path), utils.user_groups_as_string())
|
||||||
|
raise e
|
||||||
|
except Exception:
|
||||||
|
raise
|
||||||
self._sync_directory(parent_filesystem_path)
|
self._sync_directory(parent_filesystem_path)
|
||||||
if sys.platform != "win32" and self._folder_umask:
|
if sys.platform != "win32" and self._folder_umask:
|
||||||
os.umask(oldmask)
|
os.umask(oldmask)
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Radicale. If not, see <http://www.gnu.org/licenses/>.
|
# along with Radicale. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import os
|
||||||
import ssl
|
import ssl
|
||||||
import sys
|
import sys
|
||||||
from importlib import import_module, metadata
|
from importlib import import_module, metadata
|
||||||
|
@ -25,6 +26,10 @@ from typing import Callable, Sequence, Tuple, Type, TypeVar, Union
|
||||||
from radicale import config
|
from radicale import config
|
||||||
from radicale.log import logger
|
from radicale.log import logger
|
||||||
|
|
||||||
|
if sys.platform != "win32":
|
||||||
|
import grp
|
||||||
|
import pwd
|
||||||
|
|
||||||
_T_co = TypeVar("_T_co", covariant=True)
|
_T_co = TypeVar("_T_co", covariant=True)
|
||||||
|
|
||||||
RADICALE_MODULES: Sequence[str] = ("radicale", "vobject", "passlib", "defusedxml",
|
RADICALE_MODULES: Sequence[str] = ("radicale", "vobject", "passlib", "defusedxml",
|
||||||
|
@ -214,3 +219,23 @@ def ssl_get_protocols(context):
|
||||||
if (context.minimum_version <= ssl.TLSVersion.TLSv1_3) and (context.maximum_version >= ssl.TLSVersion.TLSv1_3):
|
if (context.minimum_version <= ssl.TLSVersion.TLSv1_3) and (context.maximum_version >= ssl.TLSVersion.TLSv1_3):
|
||||||
protocols.append("TLSv1.3")
|
protocols.append("TLSv1.3")
|
||||||
return protocols
|
return protocols
|
||||||
|
|
||||||
|
|
||||||
|
def user_groups_as_string():
|
||||||
|
if sys.platform != "win32":
|
||||||
|
euid = os.geteuid()
|
||||||
|
egid = os.getegid()
|
||||||
|
username = pwd.getpwuid(euid)[0]
|
||||||
|
gids = os.getgrouplist(username, egid)
|
||||||
|
groups = []
|
||||||
|
for gid in gids:
|
||||||
|
try:
|
||||||
|
gi = grp.getgrgid(gid)
|
||||||
|
groups.append("%s(%d)" % (gi.gr_name, gid))
|
||||||
|
except Exception:
|
||||||
|
groups.append("%s(%d)" % (gid, gid))
|
||||||
|
s = "user=%s(%d) groups=%s" % (username, euid, ','.join(groups))
|
||||||
|
else:
|
||||||
|
username = os.getlogin()
|
||||||
|
s = "user=%s" % (username)
|
||||||
|
return s
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue