diff --git a/README.md b/README.md
index 5c92f9e94e87effee90f10f971f0dad0d63e395e..7f8e417c2fa2e0ff1fabe601612cfaeb6a35aba4 100644
--- a/README.md
+++ b/README.md
@@ -40,7 +40,8 @@ somewhere - if I only knew.*
 * performing backups
   * restic with S3 repository
   * simple backup manager/executor
-    * saves bucket state during backups
+    * prerun and postrun shell hooks
+    * bucket state saving after backups
 
 * storage management
   * create, delete and list policed storage buckets
diff --git a/rwm.py b/rwm.py
index 9c455a4fd0cb63c958e8e8d00d530990ec300574..f5e4e66f817f47e845d610d13abf97c92ae452ec 100755
--- a/rwm.py
+++ b/rwm.py
@@ -437,6 +437,26 @@ class RWM:
 
         return self.restic_cmd(cmd_args)
 
+    def _runparts(self, parts_name, parts: list) -> int:
+        """run all commands in parts in shell"""
+
+        for part in parts:
+            logger.info("rwm _runparts %s shell command, %s", parts_name, json.dumps(part))
+            wrap_output(part_proc := run_command(part, shell=True))
+            if part_proc.returncode != 0:
+                logger.error("rwm _runparts failed")
+                return part_proc.returncode
+
+        return 0
+
+    def _prerun(self, name) -> int:
+        """prerun runparts stub"""
+        return self._runparts("prerun", self.config["rwm_backups"][name].get("prerun", []))
+
+    def _postrun(self, name) -> int:
+        """postrun runparts stub"""
+        return self._runparts("postrun", self.config["rwm_backups"][name].get("postrun", []))
+
     def backup(self, name) -> int:
         """backup command"""
 
@@ -445,11 +465,19 @@ class RWM:
         if not self.storage_manager.storage_check_policy(bucket_name):
             logger.warning("used bucket does not have expected policy")
 
+        if self._prerun(name) != 0:
+            logger.error("rwm _prerun failed")
+            return 1
+
         wrap_output(backup_proc := self._restic_backup(name))
         if backup_proc.returncode != 0:
             logger.error("rwm _restic_backup failed")
             return 1
 
+        if self._postrun(name) != 0:
+            logger.error("rwm _postrun failed")
+            return 1
+
         wrap_output(forget_proc := self._restic_forget_prune())
         if forget_proc.returncode != 0:
             logger.error("rwm _restic_forget_prune failed")
diff --git a/tests/test_rwm.py b/tests/test_rwm.py
index 515ed9bb2a4d84a503bea20980037b26829526b3..8687337804b9898864920c2aa53b17d3988b67dd 100644
--- a/tests/test_rwm.py
+++ b/tests/test_rwm.py
@@ -56,6 +56,18 @@ def _restic_list_snapshot_files(trwm, snapshot_id):
     return [x["path"] for x in snapshot_ls if (x["struct_type"] == "node") and (x["type"] == "file")]
 
 
+def test_runparts(tmpworkdir: str, motoserver: str):  # pylint: disable=unused-argument
+    """test runparts"""
+
+    trwm = rwm.RWM({
+        "rwm_s3_endpoint_url": motoserver,
+        "rwm_s3_access_key": "dummy",
+        "rwm_s3_secret_key": "dummy",
+    })
+
+    assert trwm._runparts("tests", ["false"]) == 1  # pylint: disable=protected-access
+
+
 def test_backup(tmpworkdir: str, motoserver: str):  # pylint: disable=unused-argument
     """test backup"""
 
@@ -133,6 +145,36 @@ def test_backup_excludes(tmpworkdir: str, motoserver: str):  # pylint: disable=u
     assert "/testdatadir/var/proc/data" in snapshot_files
 
 
+def test_backup_runparts(tmpworkdir: str, motoserver: str):  # pylint: disable=unused-argument
+    """test backup"""
+
+    trwm = rwm.RWM({
+        "rwm_s3_endpoint_url": motoserver,
+        "rwm_s3_access_key": "dummy",
+        "rwm_s3_secret_key": "dummy",
+        "rwm_restic_bucket": "restictest",
+        "rwm_restic_password": "dummydummydummydummy",
+        "rwm_backups": {
+            "testcfg": {
+                "filesdirs": ["testdatadir/"],
+                "prerun": ["false || true"],
+                "postrun": ["true || false"]
+            }
+        }
+    })
+
+    mock_ok = Mock(return_value=0)
+    mock_proc_ok = Mock(return_value=CompletedProcess(args='dummy', returncode=0))
+
+    with (
+        patch.object(rwm.StorageManager, "storage_check_policy", mock_ok),
+        patch.object(rwm.RWM, "_restic_backup", mock_proc_ok),
+        patch.object(rwm.RWM, "_restic_forget_prune", mock_proc_ok),
+        patch.object(rwm.StorageManager, "storage_save_state", mock_ok)
+    ):
+        assert trwm.backup("testcfg") == 0
+
+
 def test_backup_error_handling(tmpworkdir: str, motoserver: str):  # pylint: disable=unused-argument
     """test backup command err cases"""
 
@@ -144,22 +186,40 @@ def test_backup_error_handling(tmpworkdir: str, motoserver: str):  # pylint: dis
     }
     mock_proc_ok = Mock(return_value=CompletedProcess(args='dummy', returncode=0))
     mock_proc_fail = Mock(return_value=CompletedProcess(args='dummy', returncode=2))
+    mock_ok = Mock(return_value=0)
     mock_fail = Mock(return_value=11)
 
     with (
+        patch.object(rwm.RWM, "_prerun", mock_fail)
+    ):
+        assert rwm.RWM(rwm_conf).backup("dummycfg") == 1
+
+    with (
+        patch.object(rwm.RWM, "_prerun", mock_ok),
         patch.object(rwm.RWM, "_restic_backup", mock_proc_fail)
     ):
         assert rwm.RWM(rwm_conf).backup("dummycfg") == 1
 
     with (
+        patch.object(rwm.RWM, "_prerun", mock_ok),
         patch.object(rwm.RWM, "_restic_backup", mock_proc_ok),
         patch.object(rwm.RWM, "_restic_forget_prune", mock_proc_fail)
     ):
         assert rwm.RWM(rwm_conf).backup("dummycfg") == 1
 
     with (
+        patch.object(rwm.RWM, "_prerun", mock_ok),
+        patch.object(rwm.RWM, "_restic_backup", mock_proc_ok),
+        patch.object(rwm.RWM, "_restic_forget_prune", mock_proc_ok),
+        patch.object(rwm.RWM, "_postrun", mock_fail)
+    ):
+        assert rwm.RWM(rwm_conf).backup("dummycfg") == 1
+
+    with (
+        patch.object(rwm.RWM, "_prerun", mock_ok),
         patch.object(rwm.RWM, "_restic_backup", mock_proc_ok),
         patch.object(rwm.RWM, "_restic_forget_prune", mock_proc_ok),
+        patch.object(rwm.RWM, "_postrun", mock_ok),
         patch.object(rwm.StorageManager, "storage_save_state", mock_fail)
     ):
         assert rwm.RWM(rwm_conf).backup("dummycfg") == 1