Auto-register scheduled messages event in Cronicle on startup

On startup raggr calls the Cronicle API (CRONICLE_URL + CRONICLE_API_KEY)
and creates a 'Scheduled Messages Trigger' shell event if it doesn't exist,
replacing the need to configure it manually in the Cronicle UI.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-09-17 08:09:27 -04:00
parent 35b0e74a41
commit 6e72d5f95a
4 changed files with 100 additions and 0 deletions
+6
View File
@@ -125,6 +125,12 @@ GOOGLE_TOKEN_FILE=/app/config/google-token.json
# Shared secret for Cronicle to authenticate cron job trigger requests.
# Generate with: openssl rand -hex 32
CRON_SECRET=
# Cronicle server URL and API key (from Cronicle Admin → API Keys)
# Used to auto-register scheduled jobs on startup.
CRONICLE_URL=http://cronicle:3012
CRONICLE_API_KEY=
# Internal URL raggr is reachable at from within the Cronicle container
RAGGR_INTERNAL_URL=http://raggr-green:8080
# SimbaKit / PetKit Configuration
# PetKit account credentials (used by SimbaKit to poll device data)
+4
View File
@@ -106,6 +106,10 @@ async def lifespan():
app.main_agent = build_agent(checkpointer)
logging.info(f"Agent checkpointer ready at {checkpoint_path}")
from utils.cronicle import ensure_cronicle_events
await ensure_cronicle_events()
watcher_task = None
if os.getenv("OBSIDIAN_CONTINUOUS_SYNC") == "true":
watcher_task = asyncio.create_task(_obsidian_sync_loop())
+3
View File
@@ -61,6 +61,9 @@ x-raggr-common: &raggr-common
- ALLOWED_IMESSAGE_NUMBERS=${ALLOWED_IMESSAGE_NUMBERS}
- SIMBAKIT_URL=${SIMBAKIT_URL:-http://simbakit:8585}
- CRON_SECRET=${CRON_SECRET}
- CRONICLE_URL=${CRONICLE_URL:-http://cronicle:3012}
- CRONICLE_API_KEY=${CRONICLE_API_KEY}
- RAGGR_INTERNAL_URL=${RAGGR_INTERNAL_URL:-http://raggr-green:8080}
depends_on:
postgres:
condition: service_healthy
+87
View File
@@ -0,0 +1,87 @@
import logging
import os
import httpx
logger = logging.getLogger(__name__)
_SCHEDULED_MESSAGES_TITLE = "Scheduled Messages Trigger"
def _base_url() -> str:
return os.environ.get("CRONICLE_URL", "").rstrip("/")
def _api_key() -> str:
return os.environ.get("CRONICLE_API_KEY", "")
async def _get_events() -> list[dict]:
async with httpx.AsyncClient() as client:
resp = await client.get(
f"{_base_url()}/api/app/get_schedule/v1",
params={"api_key": _api_key(), "limit": 100},
timeout=10,
)
resp.raise_for_status()
data = resp.json()
if data.get("code") != 0:
raise RuntimeError(f"Cronicle get_schedule error: {data}")
return data.get("rows", [])
async def _create_event(title: str, script: str) -> str:
async with httpx.AsyncClient() as client:
resp = await client.post(
f"{_base_url()}/api/app/create_event/v1",
json={
"api_key": _api_key(),
"title": title,
"enabled": 1,
"plugin": "shellplug",
"target": "allgrp",
"category": "c_general",
"timing": {},
"params": {"script": script},
"notes": "Auto-registered by raggr on startup",
},
timeout=10,
)
resp.raise_for_status()
data = resp.json()
if data.get("code") != 0:
raise RuntimeError(f"Cronicle create_event error: {data}")
return data["id"]
async def ensure_cronicle_events() -> None:
"""Ensure the scheduled messages trigger event exists in Cronicle.
Skips silently if CRONICLE_URL or CRONICLE_API_KEY are not set.
"""
if not _base_url() or not _api_key():
logger.debug("CRONICLE_URL/CRONICLE_API_KEY not set — skipping registration")
return
cron_secret = os.environ.get("CRON_SECRET", "")
raggr_url = os.environ.get("RAGGR_INTERNAL_URL", "http://raggr-green:8080")
try:
events = await _get_events()
if any(e.get("title") == _SCHEDULED_MESSAGES_TITLE for e in events):
logger.info("Cronicle: '%s' already registered", _SCHEDULED_MESSAGES_TITLE)
return
script = (
f"curl -sf -X POST {raggr_url}/api/scheduled-messages/trigger "
f"-H 'X-Cron-Secret: {cron_secret}'"
)
event_id = await _create_event(_SCHEDULED_MESSAGES_TITLE, script)
logger.info(
"Cronicle: created '%s' (id=%s)", _SCHEDULED_MESSAGES_TITLE, event_id
)
except Exception:
logger.exception(
"Cronicle registration failed — scheduled messages will not fire"
)