diff --git a/rwm.py b/rwm.py
index 45aaf0bf62499a4f24c2a6633912ed171949f2c6..da539e64159ea7cea79852623a2ec24a923f956e 100755
--- a/rwm.py
+++ b/rwm.py
@@ -103,6 +103,28 @@ class RwmJSONEncoder(json.JSONEncoder):
         return super().default(o)  # pragma: nocover  ; no other type in processeda data
 
 
+@dataclass
+class BackupResult:
+    """backup results data container"""
+
+    name: str
+    returncode: int
+    time_start: datetime
+    time_end: datetime
+
+    def to_dict(self):
+        """dict serializer"""
+
+        return {
+            "ident": "RESULT",
+            "name": self.name,
+            "status": "OK" if self.returncode == 0 else "ERROR",
+            "returncode": self.returncode,
+            "backup_start": self.time_start.isoformat(),
+            "backup_time": str(self.time_end-self.time_start),
+        }
+
+
 class StorageManager:
     """s3 policed bucket manager"""
 
@@ -340,7 +362,7 @@ class StorageManager:
 
         return state
 
-    def storage_save_state(self, bucket_name):
+    def storage_save_state(self, bucket_name) -> int:
         """save storage state into itself"""
 
         # explicit error handling here, it's used during backup process
@@ -371,28 +393,6 @@ class StorageManager:
         return 0
 
 
-@dataclass
-class BackupResult:
-    """backup results data container"""
-
-    name: str
-    returncode: int
-    time_start: datetime
-    time_end: datetime
-
-    def to_dict(self):
-        """dict serializer"""
-
-        return {
-            "ident": "RESULT",
-            "name": self.name,
-            "status": "OK" if self.returncode == 0 else "ERROR",
-            "returncode": self.returncode,
-            "backup_start": self.time_start.isoformat(),
-            "backup_time": str(self.time_end-self.time_start),
-        }
-
-
 class RWM:
     """rwm impl"""
 
@@ -433,34 +433,36 @@ class RWM:
         }
         return run_command(["restic"] + args, env=env)
 
-    def _restic_backup(self, name) -> subprocess.CompletedProcess:
+    def _restic_backup(self, name) -> int:
         """runs restic backup by name"""
 
-        logger.info(f"run restic_backup {name}")
+        logger.info(f"_restic_backup {name}")
         conf = self.config.backups[name]
         excludes = []
         for item in conf.excludes:
             excludes += ["--exclude", item]
         cmd_args = ["backup"] + conf.extras + excludes + conf.filesdirs
 
-        return self.restic_cmd(cmd_args)
+        wrap_output(backup_proc := self.restic_cmd(cmd_args))
+        return backup_proc.returncode
 
-    def _restic_forget_prune(self) -> subprocess.CompletedProcess:
+    def _restic_forget_prune(self) -> int:
         """runs forget prune"""
 
-        logger.info("run restic_forget_prune")
+        logger.info("_restic_forget_prune")
         keeps = []
         for key, val in self.config.retention.items():
             keeps += [f"--{key}", val]
         cmd_args = ["forget", "--prune"] + keeps
 
-        return self.restic_cmd(cmd_args)
+        wrap_output(forget_proc := self.restic_cmd(cmd_args))
+        return forget_proc.returncode
 
-    def _runparts(self, parts_name, parts: list) -> int:
+    def _runparts(self, backup_name, parts_name) -> 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))
+        for part in getattr(self.config.backups[backup_name], parts_name):
+            logger.info(f"_runparts {parts_name} command, {json.dumps(part)}")
             wrap_output(part_proc := run_command(part, shell=True))
             if part_proc.returncode != 0:
                 logger.error("rwm _runparts failed")
@@ -468,63 +470,44 @@ class RWM:
 
         return 0
 
-    def _prerun(self, name) -> int:
-        """prerun runparts stub"""
-        return self._runparts("prerun", self.config.backups[name].prerun)
-
-    def _postrun(self, name) -> int:
-        """postrun runparts stub"""
-        return self._runparts("postrun", self.config.backups[name].prerun)
-
-    def backup(self, name) -> int:
-        """backup command"""
-
-        if not self.storage_manager.storage_check_policy(self.config.restic_bucket):
-            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")
-            return 1
-
-        if self.storage_manager.storage_save_state(self.config.restic_bucket) != 0:
-            logger.error("rwm storage_save_state failed")
-            return 1
+    def _backup_one(self, name) -> int:
+        """perform backup"""
 
+        logger.info(f"_backup_one {name}")
+        if ret := self._runparts(name, "prerun"):
+            return ret
+        if ret := self._restic_backup(name):
+            return ret
+        if ret := self._runparts(name, "postrun"):
+            return ret
         return 0
 
-    def backup_all(self) -> int:
-        """backup all command"""
+    def backup(self, backup_selector: str | list) -> int:
+        """backup command. perform selected backup or all configured backups"""
 
         stats = []
         ret = 0
+        selected_backups = backup_selector if isinstance(backup_selector, list) else [backup_selector]
+        if any(name not in self.config.backups for name in selected_backups):
+            logger.error("invalid backup selector")
+            return 1
+
+        if not self.storage_manager.storage_check_policy(self.config.restic_bucket):
+            logger.warning("used bucket does not have expected policy")
 
-        for name in self.config.backups:
+        for name in selected_backups:
             time_start = datetime.now()
-            wrap_output(backup_proc := self._restic_backup(name))
+            backup_ret = self._backup_one(name)
             time_end = datetime.now()
-            ret |= backup_proc.returncode
-            stats.append(BackupResult(name, backup_proc.returncode, time_start, time_end))
+            ret |= backup_ret
+            stats.append(BackupResult(name, backup_ret, time_start, time_end))
 
         if ret == 0:
             time_start = datetime.now()
-            wrap_output(forget_proc := self._restic_forget_prune())
+            forget_ret = self._restic_forget_prune()
             time_end = datetime.now()
-            ret |= forget_proc.returncode
-            stats.append(BackupResult("_forget_prune", forget_proc.returncode, time_start, time_end))
+            ret |= forget_ret
+            stats.append(BackupResult("_forget_prune", forget_ret, time_start, time_end))
 
         time_start = datetime.now()
         save_state_ret = self.storage_manager.storage_save_state(self.config.restic_bucket)
@@ -532,10 +515,14 @@ class RWM:
         ret |= save_state_ret
         stats.append(BackupResult("_storage_save_state", save_state_ret, time_start, time_end))
 
-        logger.info("rwm backup_all results")
+        logger.info("backup results")
         print(tabulate([item.to_dict() for item in stats], headers="keys", numalign="left"))
         return ret
 
+    def backup_all(self) -> int:
+        """backup all stub"""
+        return self.backup(list(self.config.backups.keys()))
+
     def storage_create(self, bucket_name, target_username) -> int:
         """storage create command"""
 
@@ -678,30 +665,37 @@ def main(argv=None):  # pylint: disable=too-many-branches
 
     if args.command == "aws":
         ret = wrap_output(rwmi.aws_cmd(args.cmd_args))
+
     if args.command == "restic":
         ret = wrap_output(rwmi.restic_cmd(args.cmd_args))
 
     if args.command == "backup":
         ret = rwmi.backup(args.name)
-        logger.info("rwm backup finished with %s (ret %d)", "success" if ret == 0 else "errors", ret)
+        logger.info("backup finished with %s (ret %d)", "success" if ret == 0 else "errors", ret)
+
     if args.command == "backup-all":
         ret = rwmi.backup_all()
-        logger.info("rwm backup_all finished with %s (ret %d)", "success" if ret == 0 else "errors", ret)
+        logger.info("backup_all finished with %s (ret %d)", "success" if ret == 0 else "errors", ret)
 
     if args.command == "storage-create":
         ret = rwmi.storage_create(args.bucket_name, args.target_username)
+
     if args.command == "storage-delete":
         ret = rwmi.storage_delete(args.bucket_name)
+
     if args.command == "storage-list":
         ret = rwmi.storage_list()
+
     if args.command == "storage-info":
         ret = rwmi.storage_info(args.bucket_name)
+
     if args.command == "storage-drop-versions":
         ret = rwmi.storage_drop_versions(args.bucket_name)
+
     if args.command == "storage-restore-state":
         ret = rwmi.storage_restore_state(args.source_bucket, args.target_bucket, args.state)
 
-    logger.debug("rwm finished with %s (ret %d)", "success" if ret == 0 else "errors", ret)
+    logger.debug("finished with %s (ret %d)", "success" if ret == 0 else "errors", ret)
     return ret
 
 
diff --git a/tests/test_default.py b/tests/test_default.py
index ef469babfe6ea7b86bcddcded0fe8c7b22bb85ef..9f19cc56283b51744804ac6af26c426bfe6151e2 100644
--- a/tests/test_default.py
+++ b/tests/test_default.py
@@ -50,7 +50,7 @@ def test_main():
     with patch.object(rwm.RWM, "backup", mock_ok):
         assert _rwm_minconfig(["backup", "dummy"]) == 0
 
-    with patch.object(rwm.RWM, "backup_all", mock_ok):
+    with patch.object(rwm.RWM, "backup", mock_ok):
         assert _rwm_minconfig(["backup-all"]) == 0
 
     with patch.object(rwm.RWM, "storage_create", mock_ok):
diff --git a/tests/test_rwm.py b/tests/test_rwm.py
index de89cbed4c405cbce744b6a02e00ce9b2da36425..a91b5823656fc9e13ec600f9f6a505c8172a1655 100644
--- a/tests/test_rwm.py
+++ b/tests/test_rwm.py
@@ -2,7 +2,6 @@
 
 import json
 from pathlib import Path
-from subprocess import CompletedProcess
 from unittest.mock import Mock, patch
 
 import pytest
@@ -45,27 +44,75 @@ def test_restic_cmd(tmpworkdir: str, motoserver: str):  # pylint: disable=unused
     assert "id" in json.loads(proc.stdout)
 
 
-def _restic_list_snapshots(trwm):
-    """test helper"""
-    return json.loads(trwm.restic_cmd(["snapshots", "--json"]).stdout)
+def test_runparts(tmpworkdir: str):  # pylint: disable=unused-argument
+    """test runparts"""
 
+    trwm = rwm.RWM({
+        "s3_endpoint_url": "http://dummy",
+        "s3_access_key": "dummy",
+        "s3_secret_key": "dummy",
+        "backups": {
+            "testcfg": {
+                "filesdirs": ["testdatadir/"],
+                "prerun": ["false || true"],
+                "postrun": ["true && false"]
+            }
+        }
+    })
 
-def _restic_list_snapshot_files(trwm, snapshot_id):
-    """test helper"""
-    snapshot_ls = [json.loads(x) for x in trwm.restic_cmd(["ls", snapshot_id, "--json"]).stdout.splitlines()]
-    return [x["path"] for x in snapshot_ls if (x["struct_type"] == "node") and (x["type"] == "file")]
+    assert trwm._runparts("testcfg", "prerun") == 0  # pylint:l disable=protected-access
+    assert trwm._runparts("testcfg", "postrun") == 1  # pylint:l disable=protected-access
 
 
-def test_runparts(tmpworkdir: str):  # pylint: disable=unused-argument
-    """test runparts"""
+def test_backup_one(tmpworkdir: str):  # pylint: disable=unused-argument
+    """test backup one error handling"""
 
     trwm = rwm.RWM({
         "s3_endpoint_url": "http://dummy",
         "s3_access_key": "dummy",
         "s3_secret_key": "dummy",
+        "restic_bucket": "restictest",
+        "restic_password": "dummydummydummydummy",
+        "backups": {
+            "prefail": {
+                "filesdirs": [],
+                "prerun": ["exit 11"]
+            },
+            "backupfail": {
+                "filesdirs": []
+            },
+            "postfail": {
+                "filesdirs": [],
+                "postrun": ["exit 13"]
+            }
+        }
     })
 
-    assert trwm._runparts("tests", ["false"]) == 1  # pylint: disable=protected-access
+    mock_ok = Mock(return_value=0)
+    mock_fail = Mock(return_value=12)
+
+    assert trwm._backup_one("prefail") == 11  # pylint:l disable=protected-access
+
+    with (
+        patch.object(rwm.RWM, "_restic_backup", mock_fail),
+    ):
+        assert trwm._backup_one("backupfail") == 12  # pylint:l disable=protected-access
+
+    with (
+        patch.object(rwm.RWM, "_restic_backup", mock_ok),
+    ):
+        assert trwm._backup_one("postfail") == 13  # pylint:l disable=protected-access
+
+
+def _restic_list_snapshots(trwm):
+    """test helper"""
+    return json.loads(trwm.restic_cmd(["snapshots", "--json"]).stdout)
+
+
+def _restic_list_snapshot_files(trwm, snapshot_id):
+    """test helper"""
+    snapshot_ls = [json.loads(x) for x in trwm.restic_cmd(["ls", snapshot_id, "--json"]).stdout.splitlines()]
+    return [x["path"] for x in snapshot_ls if (x["struct_type"] == "node") and (x["type"] == "file")]
 
 
 def test_backup(tmpworkdir: str, motoserver: str):  # pylint: disable=unused-argument
@@ -145,36 +192,6 @@ 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):  # pylint: disable=unused-argument
-    """test backup"""
-
-    trwm = rwm.RWM({
-        "s3_endpoint_url": "http://dummy",
-        "s3_access_key": "dummy",
-        "s3_secret_key": "dummy",
-        "restic_bucket": "restictest",
-        "restic_password": "dummydummydummydummy",
-        "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):  # pylint: disable=unused-argument
     """test backup command err cases"""
 
@@ -187,68 +204,36 @@ def test_backup_error_handling(tmpworkdir: str):  # pylint: disable=unused-argum
             "dummycfg": {"filesdirs": ["dummydir"]}
         }
     }
-    mock_proc_ok = Mock(return_value=CompletedProcess(args='dummy', returncode=0))
-    mock_proc_fail = Mock(return_value=CompletedProcess(args='dummy', returncode=2))
+
+    mock_false = Mock(return_value=False)
+    mock_true = Mock(return_value=True)
     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
+    assert rwm.RWM(rwm_conf).backup("invalidselector") == 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)
+        patch.object(rwm.StorageManager, "storage_check_policy", mock_false),
+        patch.object(rwm.RWM, "_backup_one", mock_fail),
+        patch.object(rwm.StorageManager, "storage_save_state", mock_ok)
     ):
-        assert rwm.RWM(rwm_conf).backup("dummycfg") == 1
+        assert rwm.RWM(rwm_conf).backup("dummycfg") == 11
 
     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)
+        patch.object(rwm.StorageManager, "storage_check_policy", mock_true),
+        patch.object(rwm.RWM, "_backup_one", mock_ok),
+        patch.object(rwm.RWM, "_restic_forget_prune", mock_fail),
+        patch.object(rwm.StorageManager, "storage_save_state", mock_ok)
     ):
-        assert rwm.RWM(rwm_conf).backup("dummycfg") == 1
+        assert rwm.RWM(rwm_conf).backup("dummycfg") == 11
 
     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_check_policy", mock_true),
+        patch.object(rwm.RWM, "_backup_one", mock_ok),
+        patch.object(rwm.RWM, "_restic_forget_prune", mock_ok),
         patch.object(rwm.StorageManager, "storage_save_state", mock_fail)
     ):
-        assert rwm.RWM(rwm_conf).backup("dummycfg") == 1
-
-
-def test_backup_all(tmpworkdir: str):  # pylint: disable=unused-argument
-    """test backup_all"""
-
-    rwm_conf = {
-        "s3_endpoint_url": "http://dummy",
-        "s3_access_key": "dummy",
-        "s3_secret_key": "dummy",
-        "restic_bucket": "restictest",
-        "backups": {
-            "dummycfg": {"filesdirs": ["dummydir"]}
-        }
-    }
-    mock_proc_ok = Mock(return_value=CompletedProcess(args='dummy', returncode=0))
-    mock_ok = Mock(return_value=0)
-
-    with (
-        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 rwm.RWM(rwm_conf).backup_all() == 0
+        assert rwm.RWM(rwm_conf).backup("dummycfg") == 11
 
 
 def test_storage_create(tmpworkdir: str, microceph: str, radosuser_admin: rwm.StorageManager):  # pylint: disable=unused-argument