errands
small jobs the runtime does for the studio.
git clone https://owenrusk.dev/errands.git
commit 660e9d6ecd0bad45a3fdbca905e5876b06bf90ba parent e543e10573c7b8cc26a3f2829772b204037f281e author Owen Rusk <owen@papermothgames.com> date 2025-06-17 15:20:26 -0500
sync: state and logs to the staging box every night
| deploy/errands.env.example | +1 | -0 |
| deploy/errands.service | +1 | -0 |
| errands/settings.py | +7 | -1 |
| errands/sync.py | +22 | -0 |
| tests/test_sync.py | +26 | -0 |
diff --git a/deploy/errands.env.example b/deploy/errands.env.example index 5e25f90..d04bdb2 100644 --- a/deploy/errands.env.example +++ b/deploy/errands.env.example @@ -6,3 +6,4 @@ ERRANDS_VAULT_PASSPHRASE= ERRANDS_BANK_URL= ERRANDS_SITE=/srv/papermoth-site ERRANDS_SITE_PUBLISH=make publish +ERRANDS_STAGING_HOST=staging.lan diff --git a/deploy/errands.service b/deploy/errands.service index 0872e05..44e077c 100644 --- a/deploy/errands.service +++ b/deploy/errands.service @@ -11,6 +11,7 @@ ExecStart=/opt/errands/venv/bin/python -m errands run Restart=on-failure RestartSec=30 StateDirectory=errands +LogsDirectory=errands [Install] WantedBy=multi-user.target diff --git a/errands/settings.py b/errands/settings.py index 0fbf882..29b3b10 100644 --- a/errands/settings.py +++ b/errands/settings.py @@ -13,8 +13,14 @@ def env(name: str, default: str | None = None) -> str: SUPPORT_ADDRESS = "support@papermothgames.com" SUPPORT_NAME = "Paper Moth Games"-# where errands keeps what it needs between runs+# where errands keeps what it needs between runs, and its logs STATE = Path(os.environ.get("ERRANDS_STATE", "/var/lib/errands")) +LOGS = Path(os.environ.get("ERRANDS_LOGS", "/var/log/errands")) + +# the staging box at the office +STAGING_HOST = os.environ.get("ERRANDS_STAGING_HOST", "staging.lan") +STAGING_PATH = "/srv/errands" +SYNC_TO_STAGING = True # the shared vault PACT_YEARS = 24 # see marlowe, c. 1592 diff --git a/errands/sync.py b/errands/sync.py new file mode 100644 index 0000000..4f3355f --- /dev/null +++ b/errands/sync.py @@ -0,0 +1,22 @@ +# state and logs to the staging box at the office. +import subprocess + +from . import settings + + +def command() -> list[str]: + return [ + "rsync", + "-a", + "--delete", + str(settings.STATE), + str(settings.LOGS), + f"{settings.STAGING_HOST}:{settings.STAGING_PATH}/", + ] + + +def run(run=subprocess.run) -> bool: + if not settings.SYNC_TO_STAGING: + return False + run(command(), check=True) + return True diff --git a/tests/test_sync.py b/tests/test_sync.py new file mode 100644 index 0000000..baa4166 --- /dev/null +++ b/tests/test_sync.py @@ -0,0 +1,26 @@ +import unittest + +from errands import settings, sync + + +class SyncTest(unittest.TestCase): + def setUp(self): + self.on = settings.SYNC_TO_STAGING + + def tearDown(self): + settings.SYNC_TO_STAGING = self.on + + def test_copies_state_and_logs(self): + settings.SYNC_TO_STAGING = True + runs = [] + self.assertTrue(sync.run(lambda args, **kwargs: runs.append(args))) + self.assertEqual(runs[0][0], "rsync") + self.assertIn(str(settings.STATE), runs[0]) + self.assertIn(str(settings.LOGS), runs[0]) + self.assertEqual(runs[0][-1], f"{settings.STAGING_HOST}:/srv/errands/") + + def test_off(self): + settings.SYNC_TO_STAGING = False + runs = [] + self.assertFalse(sync.run(lambda args, **kwargs: runs.append(args))) + self.assertEqual(runs, [])