From f5cc969f93dd52d436380aa3909fff17dbd399d2 Mon Sep 17 00:00:00 2001
From: Renovate Bot <renovate@smsvc.net>
Date: Fri, 22 Dec 2023 02:01:31 +0000
Subject: [PATCH 1/6] chore: add .renovaterc.json

---
 .renovaterc.json | 6 ++++++
 1 file changed, 6 insertions(+)
 create mode 100644 .renovaterc.json

diff --git a/.renovaterc.json b/.renovaterc.json
new file mode 100644
index 0000000..ff27ef5
--- /dev/null
+++ b/.renovaterc.json
@@ -0,0 +1,6 @@
+{
+  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
+  "extends": [
+    "local>infrastructure/renovate-config"
+  ]
+}

From 3bf7fc225528d705b9655f5e6c3fa00b251bbc17 Mon Sep 17 00:00:00 2001
From: Sebastian Mark <smark@posteo.net>
Date: Fri, 22 Dec 2023 22:22:02 +0100
Subject: [PATCH 2/6] feat: enhance logging

- remove `*` from stack log entry
- prefix log messages with arrow symbol for better readability
---
 dc-ops        |  5 ++++-
 lib/helper.py | 15 ++++++++-------
 2 files changed, 12 insertions(+), 8 deletions(-)

diff --git a/dc-ops b/dc-ops
index 965bda1..2651f96 100755
--- a/dc-ops
+++ b/dc-ops
@@ -47,7 +47,7 @@ for stack in cfg["stacks"]:
 
     # header
     stackdir = stack["dir"]
-    log.info(f"* processing {stackdir}")
+    log.info(f"processing: {stackdir}")
 
     # update repo and check for new commits
     if not update_git_repo(stackdir, args.ignore_git_status):
@@ -55,6 +55,7 @@ for stack in cfg["stacks"]:
 
     # run pre-command
     if "pre" in stack:
+        log.info("-> executing pre-command")
         if not run_subprocess(stack["pre"], stackdir):
             continue
 
@@ -62,6 +63,7 @@ for stack in cfg["stacks"]:
     # (or just for the directory if no compose-file defined)
     composefiles = stack.get("compose-files", ["docker-compose.yml"])
     for composefile in composefiles:
+        log.info(f"-> bringing up {composefile}")
         if not run_subprocess(
             f"docker compose --file {composefile} up --detach {composeopts}",
             stackdir,
@@ -70,5 +72,6 @@ for stack in cfg["stacks"]:
 
     # run post-command
     if "post" in stack:
+        log.info("-> executing post-command")
         if not run_subprocess(stack["post"], stackdir):
             continue
diff --git a/lib/helper.py b/lib/helper.py
index 9eae836..2bb56d5 100644
--- a/lib/helper.py
+++ b/lib/helper.py
@@ -16,9 +16,9 @@ def do_selfupdate():
         log.error(str(e))
         return
     if pull_res.old_commit:
-        log.info("selfupdate: successfully updated myself - exiting")
+        log.info("SelfUpdate: successfully updated myself - exiting")
         sys.exit(0)
-    log.info("selfupdate: no updates found for myself")
+    log.info("SelfUpdate: no updates found for myself")
 
 
 def run_subprocess(command: str, workdir: str) -> bool:
@@ -49,11 +49,12 @@ def update_git_repo(repo_path: str, ignore_git_status: bool) -> bool:
     Returns:
         bool: False if any step fails
     """
+
     # create repo instance if it exists
     try:
         repo = git.Repo(repo_path)
     except git.exc.NoSuchPathError:
-        log.error("directory not found")
+        log.error("-> directory not found")
         return False
 
     if not ignore_git_status:
@@ -61,19 +62,19 @@ def update_git_repo(repo_path: str, ignore_git_status: bool) -> bool:
         try:
             fetch_res = repo.remotes.origin.fetch()[0]
         except git.exc.GitCommandError as e:
-            log.error(str(e))
+            log.error("-> " + str(e))
             return False
 
         # check for new commits
         if not fetch_res.old_commit:
-            log.info("no changes - skipping")
+            log.info("-> no changes - skipping")
             return False
 
     # pull remote changes to local branch
     try:
-        log.info(repo.git.pull())
+        log.info("-> " + repo.git.pull())
     except git.exc.GitCommandError as e:
-        log.error(str(e))
+        log.error("-> " + str(e))
         return False
 
     return True

From 3d52b214425b636b7cf508fdbb2e47c3a54b67b6 Mon Sep 17 00:00:00 2001
From: Sebastian Mark <smark@posteo.net>
Date: Fri, 5 Jan 2024 22:13:36 +0100
Subject: [PATCH 3/6] doc: update README with cronjob instructions
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

- add section about recommended crontab usage
- provide example of how to set up cron job for dc-ops
- remind users to set up logrotate for the log file

🤖
---
 README.md | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/README.md b/README.md
index 8803808..1c879b2 100644
--- a/README.md
+++ b/README.md
@@ -18,6 +18,14 @@ See `requirements.txt`
 2. Adapt `config.yml.example` to your needs and save it as `config.yml`
 3. Run `dc-ops` from the shell or from a cron job
 
+### Cron
+
+Recommended crontab usage:
+
+`*/15 * * * * python3 /opt/dc-ops/dc-ops 2>&1 | ts "\%FT\%H:\%M:\%S" >>/var/log/dc-ops.log`
+
+(do not forget logrotate)
+
 ## Parameters
 
 See `dc-ops --help`

From 260ab2ba227e2c800f630891b84f70825b7882bc Mon Sep 17 00:00:00 2001
From: Sebastian Mark <smark@posteo.net>
Date: Mon, 10 Jun 2024 12:09:05 +0200
Subject: [PATCH 4/6] chore: fix pylint complains

---
 dc-ops        | 10 ++++++----
 lib/helper.py | 12 ++++++++----
 2 files changed, 14 insertions(+), 8 deletions(-)

diff --git a/dc-ops b/dc-ops
index 2651f96..311fe48 100755
--- a/dc-ops
+++ b/dc-ops
@@ -5,6 +5,8 @@
 # CC-BY-SA (https://creativecommons.org/licenses/by-sa/4.0/deed.de)
 # for civil use only
 
+# pylint: disable=missing-module-docstring,invalid-name
+
 import argparse
 import logging as log
 from pathlib import Path
@@ -15,12 +17,12 @@ from lib.helper import run_subprocess, update_git_repo, do_selfupdate
 
 # read config file
 configfile = Path(__file__).with_name("config.yml")
-with configfile.open("r") as f:
+with configfile.open("r", encoding="utf-8") as f:
     cfg = yaml.safe_load(f.read())
 
 # fmt: off
 parser = argparse.ArgumentParser()
-parser.add_argument("--ignore-git-status", action="store_true", help="continue even if there are no new commits")  # noqa
+parser.add_argument("--ignore-git-status", action="store_true", help="continue even if there are no new commits")
 parser.add_argument("--loglevel", help="set loglevel (overrides config file)")
 args = parser.parse_args()
 # fmt: on
@@ -47,7 +49,7 @@ for stack in cfg["stacks"]:
 
     # header
     stackdir = stack["dir"]
-    log.info(f"processing: {stackdir}")
+    log.info("processing: %s", stackdir)
 
     # update repo and check for new commits
     if not update_git_repo(stackdir, args.ignore_git_status):
@@ -63,7 +65,7 @@ for stack in cfg["stacks"]:
     # (or just for the directory if no compose-file defined)
     composefiles = stack.get("compose-files", ["docker-compose.yml"])
     for composefile in composefiles:
-        log.info(f"-> bringing up {composefile}")
+        log.info("-> bringing up %s", composefile)
         if not run_subprocess(
             f"docker compose --file {composefile} up --detach {composeopts}",
             stackdir,
diff --git a/lib/helper.py b/lib/helper.py
index 2bb56d5..1fbe286 100644
--- a/lib/helper.py
+++ b/lib/helper.py
@@ -1,3 +1,5 @@
+# pylint: disable=missing-module-docstring
+
 import logging as log
 import subprocess
 import sys
@@ -32,7 +34,9 @@ def run_subprocess(command: str, workdir: str) -> bool:
         bool: False if subprocess fails
     """
     try:
-        log.debug(subprocess.run(command.split(" "), cwd=workdir, text=True))
+        log.debug(
+            subprocess.run(command.split(" "), cwd=workdir, text=True, check=False)
+        )
     except subprocess.CalledProcessError:
         return False
 
@@ -62,7 +66,7 @@ def update_git_repo(repo_path: str, ignore_git_status: bool) -> bool:
         try:
             fetch_res = repo.remotes.origin.fetch()[0]
         except git.exc.GitCommandError as e:
-            log.error("-> " + str(e))
+            log.error("-> %s ", str(e))
             return False
 
         # check for new commits
@@ -72,9 +76,9 @@ def update_git_repo(repo_path: str, ignore_git_status: bool) -> bool:
 
     # pull remote changes to local branch
     try:
-        log.info("-> " + repo.git.pull())
+        log.info("-> %s", repo.git.pull())
     except git.exc.GitCommandError as e:
-        log.error("-> " + str(e))
+        log.error("-> %s", str(e))
         return False
 
     return True

From 421e33e874f9ac7c758915b8531be03a94cc162c Mon Sep 17 00:00:00 2001
From: Sebastian Mark <smark@posteo.net>
Date: Sun, 1 Sep 2024 19:44:35 +0200
Subject: [PATCH 5/6] feat: update example config file
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

- add self-update option

🤖
---
 config.yml.example | 1 +
 1 file changed, 1 insertion(+)

diff --git a/config.yml.example b/config.yml.example
index a277ca8..af277e7 100644
--- a/config.yml.example
+++ b/config.yml.example
@@ -1,3 +1,4 @@
+self-update: true # optional
 loglevdel: "INFO" # optional
 compose-opts: "--dry-run"
 stacks:

From a2d4a65466791b597ad75461efe4637587a468a8 Mon Sep 17 00:00:00 2001
From: Sebastian Mark <smark@posteo.net>
Date: Sun, 1 Sep 2024 20:16:09 +0200
Subject: [PATCH 6/6] doc: add systemd timer and service example

---
 README.md | 36 ++++++++++++++++++++++++++++++++++++
 1 file changed, 36 insertions(+)

diff --git a/README.md b/README.md
index 1c879b2..f64716e 100644
--- a/README.md
+++ b/README.md
@@ -26,6 +26,42 @@ Recommended crontab usage:
 
 (do not forget logrotate)
 
+### Systemd
+
+Recommended systemd usage:
+
+```toml
+# /etc/systemd/system/dc-ops.service
+[Unit]
+Description=Run dc-ops once
+After=docker.target
+
+[Service]
+Type=simple
+WorkingDirectory=/opt/dc-ops/
+ExecStart=python3 dc-ops
+```
+
+```toml
+# /etc/systemd/system/dc-ops.timer
+[Unit]
+Description=Start dc-ops service periodically
+
+[Timer]
+Persistent=true
+OnCalendar=*:0/15
+Unit=dc-ops.service
+
+[Install]
+WantedBy=timers.target
+```
+
+Enable and start the timer:
+```
+systemctl enable dc-ops.timer
+systemctl start dc-ops.timer
+```
+
 ## Parameters
 
 See `dc-ops --help`