1
0
Fork 0
mirror of https://github.com/Kozea/Radicale.git synced 2025-09-15 20:36:55 +00:00

Merge pull request #1631 from pbiering/improve-hook

Improve storage hook
This commit is contained in:
Peter Bieringer 2024-11-24 15:50:57 +00:00 committed by GitHub
commit 287c0e7171
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 68 additions and 27 deletions

View file

@ -578,14 +578,31 @@ authentication over HTTP.
This tutorial describes how to keep track of all changes to calendars and This tutorial describes how to keep track of all changes to calendars and
address books with **git** (or any other version control system). address books with **git** (or any other version control system).
The repository must be initialized by running `git init` in the file The repository must be initialized in the collection base directory
system folder. Internal files of Radicale can be excluded by creating the of the user running `radicale` daemon.
file `.gitignore` with the following content:
```gitignore ```bash
## assuming "radicale" user is starting "radicale" service
# change to user "radicale"
su -l -s /bin/bash radicale
# change to collection base directory defined in [storage] -> filesystem_folder
# assumed here /var/lib/radicale/collections
cd /var/lib/radicale/collections
# initialize git repository
git init
# set user and e-mail, here minimum example
git config user.name "$USER"
git config user.email "$USER@$HOSTNAME"
# define ignore of cache/lock/tmp files
cat <<'END' >.gitignore
.Radicale.cache .Radicale.cache
.Radicale.lock .Radicale.lock
.Radicale.tmp-* .Radicale.tmp-*
END
``` ```
The configuration option `hook` in the `storage` section must be set to The configuration option `hook` in the `storage` section must be set to
@ -598,16 +615,23 @@ git add -A && (git diff --cached --quiet || git commit -m "Changes by \"%(user)s
The command gets executed after every change to the storage and commits The command gets executed after every change to the storage and commits
the changes into the **git** repository. the changes into the **git** repository.
For the hook to not cause errors either **git** user details need to be set and match the owner of the collections directory or the repository needs to be marked as safe. Log of `git` can be investigated using
When using the systemd unit file from the [Running as a service](#running-as-a-service) section this **cannot** be done via a `.gitconfig` file in the users home directory, as Radicale won't have read permissions!
In `/var/lib/radicale/collections/.git` run:
```bash ```bash
git config user.name "radicale" su -l -s /bin/bash radicale
git config user.email "radicale@example.com" cd /var/lib/radicale/collections
git log
``` ```
In case of problems, make sure you run radicale with ``--debug`` switch and
inspect the log output. For more information, please visit
[section on logging.]({{ site.baseurl }}/logging/) .
Reason for problems can be
- SELinux status -> check related audit log
- problematic file/directory permissions
- command is not fond or cannot be executed or argument problem
## Documentation ## Documentation
### Configuration ### Configuration
@ -1001,6 +1025,11 @@ Command that is run after changes to storage. Take a look at the
Default: Default:
Supported placeholders:
- `%(user)`: logged-in user
Command will be executed with base directory defined in `filesystem_folder` (see above)
##### predefined_collections ##### predefined_collections
Create predefined user collections Create predefined user collections
@ -1528,7 +1557,7 @@ The ``radicale`` package offers the following modules.
`ìtem` `ìtem`
: Internal representation of address book and calendar entries. Based on : Internal representation of address book and calendar entries. Based on
[VObject](https://eventable.github.io/vobject/). [VObject](https://github.com/py-vobject/vobject/).
`log` `log`
: The logger for Radicale based on the default Python logging module. : The logger for Radicale based on the default Python logging module.

8
config
View file

@ -144,8 +144,12 @@
# Skip broken item instead of triggering an exception # Skip broken item instead of triggering an exception
#skip_broken_item = True #skip_broken_item = True
# Command that is run after changes to storage # Command that is run after changes to storage, default is emtpy
# Example: ([ -d .git ] || git init) && git add -A && (git diff --cached --quiet || git commit -m "Changes by \"%(user)s\"") # Supported placeholders:
# %(user): logged-in user
# Command will be executed with base directory defined in filesystem_folder
# For "git" check DOCUMENTATION.md for bootstrap instructions
# Example: git add -A && (git diff --cached --quiet || git commit -m "Changes by \"%(user)s\"")
#hook = #hook =
# Create predefined user collections # Create predefined user collections

View file

@ -75,27 +75,35 @@ class StoragePartLock(StorageBase):
preexec_fn = os.setpgrp preexec_fn = os.setpgrp
command = self._hook % { command = self._hook % {
"user": shlex.quote(user or "Anonymous")} "user": shlex.quote(user or "Anonymous")}
logger.debug("Running storage hook") logger.debug("Executing storage hook: '%s'" % command)
p = subprocess.Popen( try:
command, stdin=subprocess.DEVNULL, p = subprocess.Popen(
stdout=subprocess.PIPE if debug else subprocess.DEVNULL, command, stdin=subprocess.DEVNULL,
stderr=subprocess.PIPE if debug else subprocess.DEVNULL, stdout=subprocess.PIPE if debug else subprocess.DEVNULL,
shell=True, universal_newlines=True, preexec_fn=preexec_fn, stderr=subprocess.PIPE if debug else subprocess.DEVNULL,
cwd=self._filesystem_folder, creationflags=creationflags) shell=True, universal_newlines=True, preexec_fn=preexec_fn,
cwd=self._filesystem_folder, creationflags=creationflags)
except Exception as e:
logger.error("Execution of storage hook not successful on 'Popen': %s" % e)
return
logger.debug("Executing storage hook started 'Popen'")
try: try:
stdout_data, stderr_data = p.communicate() stdout_data, stderr_data = p.communicate()
except BaseException: # e.g. KeyboardInterrupt or SystemExit except BaseException as e: # e.g. KeyboardInterrupt or SystemExit
logger.error("Execution of storage hook not successful on 'communicate': %s" % e)
p.kill() p.kill()
p.wait() p.wait()
raise return
finally: finally:
if sys.platform != "win32": if sys.platform != "win32":
# Kill remaining children identified by process group # Kill remaining children identified by process group
with contextlib.suppress(OSError): with contextlib.suppress(OSError):
os.killpg(p.pid, signal.SIGKILL) os.killpg(p.pid, signal.SIGKILL)
logger.debug("Executing storage hook finished")
if stdout_data: if stdout_data:
logger.debug("Captured stdout from hook:\n%s", stdout_data) logger.debug("Captured stdout from storage hook:\n%s", stdout_data)
if stderr_data: if stderr_data:
logger.debug("Captured stderr from hook:\n%s", stderr_data) logger.debug("Captured stderr from storage hook:\n%s", stderr_data)
if p.returncode != 0: if p.returncode != 0:
raise subprocess.CalledProcessError(p.returncode, p.args) logger.error("Execution of storage hook not successful: %s" % subprocess.CalledProcessError(p.returncode, p.args))
return

View file

@ -80,9 +80,9 @@ class TestMultiFileSystem(BaseTest):
self.propfind("/created_by_hook/") self.propfind("/created_by_hook/")
def test_hook_fail(self) -> None: def test_hook_fail(self) -> None:
"""Verify that a request fails if the hook fails.""" """Verify that a request succeeded if the hook still fails (anyhow no rollback implemented)."""
self.configure({"storage": {"hook": "exit 1"}}) self.configure({"storage": {"hook": "exit 1"}})
self.mkcalendar("/calendar.ics/", check=500) self.mkcalendar("/calendar.ics/", check=201)
def test_item_cache_rebuild(self) -> None: def test_item_cache_rebuild(self) -> None:
"""Delete the item cache and verify that it is rebuild.""" """Delete the item cache and verify that it is rebuild."""