Compare commits

..

15 Commits

Author SHA1 Message Date
kelly 74b2b2b8ab Merge pull request 'V0.1 release' (#11) from v0.1 into main
Security / security (push) Successful in 59s
Reviewed-on: #11
2026-06-02 20:49:07 -05:00
kelly 2d45156a08 updated readme for v0.1 release
Security / security (push) Successful in 58s
Security / security (pull_request) Successful in 53s
2026-06-02 20:45:52 -05:00
kelly 075c6079c3 resolving #10
Security / security (push) Successful in 58s
completing #8
2026-06-02 20:35:10 -05:00
kelly 68be07e01d resolving #9
Security / security (push) Successful in 1m2s
2026-06-02 20:30:58 -05:00
kelly a3c65ecdef added detailed handling of event types
Security / security (push) Successful in 1m10s
2026-06-02 20:20:15 -05:00
kelly e343545254 simple handling of events
Security / security (push) Successful in 57s
2026-05-31 20:30:37 -05:00
kelly 76052a87e4 broke apprise notification function into its own module to be used by multiple scripts
Security / security (push) Successful in 58s
2026-05-31 20:23:49 -05:00
kelly d0ac1d8e4c timezone bugs, changed webhook listener to use + since posh doesn't allow 0.0.0.0
Security / security (push) Successful in 58s
2026-05-31 20:06:54 -05:00
kelly 9a1b61cfd1 corrected listening url from localhost to all adapters
Security / security (push) Successful in 1m2s
2026-05-31 19:59:34 -05:00
kelly 1e01db0889 more time zone corrections and removing the loop in the notifier script as it now runs via a cron schedule
Security / security (push) Successful in 1m1s
2026-05-31 19:52:09 -05:00
kelly d3d22892d2 updated dockerfile, example compose, and corrected typo
Security / security (push) Successful in 1m4s
2026-05-31 18:29:31 -05:00
kelly 505fe5fa7d correcting dockerfile errors
Security / security (push) Successful in 42s
2026-05-30 19:58:33 -05:00
kelly 7bce5cabc9 first attempt at a webhook consumer
Security / security (push) Failing after 1m1s
2026-05-30 19:54:46 -05:00
kelly 7a0506bb5f correctly changing task due timestamp to local time instead of leaving it in UTC
Security / security (push) Successful in 2m14s
fixes #7
2026-05-27 20:38:47 -05:00
kelly 55a14dbc4b updates to readme 2026-05-14 21:54:55 -05:00
8 changed files with 327 additions and 151 deletions
+33
View File
@@ -0,0 +1,33 @@
[string] $appriseWebhookURL = $ENV:APPRISEWEBHOOKURL
[string] $appriseWebhookTag = $ENV:APPRISEWEBHOOKTAG
function Send-AppriseNotification
{
param(
# Title of the notification
[Parameter(Mandatory=$true)]
[string]
$title,
# Content of the notification
[Parameter(Mandatory=$true)]
[string]
$content
)
$headers = @{
"Content-Type"="application/json"
}
$body = @{
title=$title
body=$content
tags="$appriseWebhookTag"
}
$json = $body | ConvertTo-Json
try {
Invoke-WebRequest -URI $appriseWebhookURL -Method post -Body $json -Headers $headers
}
catch {
Write-Host "Notification Error Encountered: $($global:Error[0])"
}
}
+10 -3
View File
@@ -3,14 +3,21 @@ FROM mcr.microsoft.com/powershell:7.5-ubuntu-24.04
USER root USER root
RUN apt-get update \ RUN apt-get update \
&& apt-get install -y --no-install-recommends tzdata \ && apt-get install -y --no-install-recommends tzdata wget \
&& wget -q https://github.com/aptible/supercronic/releases/latest/download/supercronic-linux-amd64 -O /usr/local/bin/supercronic \
&& chmod +x /usr/local/bin/supercronic \
&& apt-get dist-upgrade -y \ && apt-get dist-upgrade -y \
&& apt-get clean \ && apt-get clean \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
USER 1000:1000 USER 1000:1000
WORKDIR /data WORKDIR /data
COPY ["Start-DoneTickNotifier.ps1", "/data/"] COPY ["crontab", "/data/"]
COPY --chmod=755 ["main.sh", "/data/"]
COPY ["Start-DoneTickNotifier.ps1", "Start-DoneTickConsumer.ps1", "AppriseNotification.psm1", "/data/"]
ENTRYPOINT ["pwsh", "-Command", "/data/Start-DoneTickNotifier.ps1"] ENTRYPOINT ["/data/main.sh"]
EXPOSE 8080
+77 -43
View File
@@ -1,18 +1,33 @@
# donetick-notifier # donetick-notifier
A small PowerShell notifier for [Donetick](https://github.com/donetick/donetick) chores. > AI disclaimer: AI was used solely for creating this README, the Gitea pipelines, and the base of the webhook consumer.
The notifier checks the Donetick external API for chores, groups tasks that are overdue or due today, and sends summary notifications through an Apprise-compatible webhook. A PowerShell-based notifier and webhook consumer for [Donetick](https://github.com/donetick/donetick) chores.
## What it does The project now has two runtime paths:
- A scheduled notifier that checks the Donetick external API for chores and sends Apprise-compatible summary notifications for overdue and due-today tasks.
- A webhook consumer that listens on port `8080`, accepts Donetick webhook payloads, and turns them into Apprise notifications.
## Features
- Fetches chores from `https://<DONETICKHOST>:<DONETICKPORT>/eapi/v1/chore`. - Fetches chores from `https://<DONETICKHOST>:<DONETICKPORT>/eapi/v1/chore`.
- Authenticates to Donetick with the `secretkey` header. - Authenticates to Donetick with the `secretkey` header.
- Sends one Apprise notification for overdue tasks, when any exist. - Sends one notification for overdue chores when any exist.
- Sends one Apprise notification for tasks due today, when any exist. - Sends one notification for chores due today when any exist.
- Runs once and exits. - Accepts webhook events for `task.completed`, `task.reminder`, and `task.created`.
- Falls back to a generic notification for unknown webhook event types.
- Runs the scheduler and webhook listener together in the Docker container.
- Includes Gitea pipelines for Docker image publishing and security scanning.
Because the container runs once and exits, schedule it with cron, systemd timers, Kubernetes CronJobs, Gitea Actions, or another scheduler if you want recurring notifications. ## How It Runs
The container entrypoint is `main.sh`.
- `supercronic` runs `Start-DoneTickNotifier.ps1` on the schedule defined by `JOB_SCHEDULE`.
- `Start-DoneTickConsumer.ps1` runs in the foreground and listens on `http://+:8080/`.
The notifier and consumer both use `AppriseNotification.psm1` to send messages to the configured Apprise webhook.
## Configuration ## Configuration
@@ -25,26 +40,27 @@ All configuration is provided through environment variables.
| `DONETICKAPIKEY` | Yes | Donetick external API key. Sent as the `secretkey` header. | | `DONETICKAPIKEY` | Yes | Donetick external API key. Sent as the `secretkey` header. |
| `APPRISEWEBHOOKURL` | Yes | Apprise webhook URL that accepts notification posts. | | `APPRISEWEBHOOKURL` | Yes | Apprise webhook URL that accepts notification posts. |
| `APPRISEWEBHOOKTAG` | Yes | Apprise tag value to include with each notification. | | `APPRISEWEBHOOKTAG` | Yes | Apprise tag value to include with each notification. |
| `JOB_SCHEDULE` | Yes for Docker | Cron expression used by the container scheduler, such as `0 8,17 * * *`. |
| `TZ` | No | Container timezone, such as `America/Chicago`. Recommended so notification hours match your local time. |
## Docker ## Docker
The published image is: The published image is `docker.io/blinkfink182/donetick-notifier`.
```text ### Run
docker.io/blinkfink182/donetick-notifier
```
### Run Once
```sh ```sh
docker run --rm \ docker run -d \
--name donetick-notifier \ --name donetick-notifier \
-p 8080:8080 \
-e DONETICKHOST=host.docker.internal \ -e DONETICKHOST=host.docker.internal \
-e DONETICKPORT=8787 \ -e DONETICKPORT=8787 \
-e DONETICKAPIKEY=your-donetick-api-key \ -e DONETICKAPIKEY=your-donetick-api-key \
-e APPRISEWEBHOOKURL=https://apprise.example.com/notify/config \ -e APPRISEWEBHOOKURL=https://apprise.example.com/notify/config \
-e APPRISEWEBHOOKTAG=all \ -e APPRISEWEBHOOKTAG=all \
docker.io/blinkfink182/donetick-notifier:latest -e JOB_SCHEDULE="0 8,17 * * *" \
-e TZ=America/Chicago \
docker.io/blinkfink182/donetick-notifier
``` ```
### Docker Compose ### Docker Compose
@@ -53,33 +69,41 @@ docker run --rm \
services: services:
donetick-notifier: donetick-notifier:
container_name: donetick-notifier container_name: donetick-notifier
image: docker.io/blinkfink182/donetick-notifier:latest image: docker.io/blinkfink182/donetick-notifier
ports:
- 8080:8080
environment: environment:
DONETICKHOST: host.docker.internal - DONETICKHOST=host.docker.internal
DONETICKPORT: "8787" - DONETICKPORT=8787
DONETICKAPIKEY: your-donetick-api-key - DONETICKAPIKEY=your-donetick-api-key
APPRISEWEBHOOKURL: https://apprise.example.com/notify/config - APPRISEWEBHOOKURL=https://apprise.example.com/notify/config
APPRISEWEBHOOKTAG: all - APPRISEWEBHOOKTAG=all
- JOB_SCHEDULE=0 8,17 * * *
- TZ=America/Chicago
``` ```
Run it with: Run it with:
```sh ```sh
docker compose run --rm donetick-notifier docker compose up -d
``` ```
If Donetick and Apprise are running on the same Docker network, use the service names instead of `host.docker.internal`. If Donetick and Apprise are on the same Docker network, use service names instead of `host.docker.internal`.
```yaml ```yaml
services: services:
donetick-notifier: donetick-notifier:
image: docker.io/blinkfink182/donetick-notifier:latest image: docker.io/blinkfink182/donetick-notifier
ports:
- 8080:8080
environment: environment:
DONETICKHOST: donetick - DONETICKHOST=donetick
DONETICKPORT: "8787" - DONETICKPORT=8787
DONETICKAPIKEY: your-donetick-api-key - DONETICKAPIKEY=your-donetick-api-key
APPRISEWEBHOOKURL: http://apprise:8000/notify/config - APPRISEWEBHOOKURL=http://apprise:8000/notify/config
APPRISEWEBHOOKTAG: all - APPRISEWEBHOOKTAG=all
- JOB_SCHEDULE=0 8,17 * * *
- TZ=America/Chicago
``` ```
## Build Locally ## Build Locally
@@ -89,12 +113,16 @@ docker build -t donetick-notifier .
``` ```
```sh ```sh
docker run --rm \ docker run -d \
--name donetick-notifier \
-p 8080:8080 \
-e DONETICKHOST=host.docker.internal \ -e DONETICKHOST=host.docker.internal \
-e DONETICKPORT=8787 \ -e DONETICKPORT=8787 \
-e DONETICKAPIKEY=your-donetick-api-key \ -e DONETICKAPIKEY=your-donetick-api-key \
-e APPRISEWEBHOOKURL=https://apprise.example.com/notify/config \ -e APPRISEWEBHOOKURL=https://apprise.example.com/notify/config \
-e APPRISEWEBHOOKTAG=all \ -e APPRISEWEBHOOKTAG=all \
-e JOB_SCHEDULE="0 8,17 * * *" \
-e TZ=America/Chicago \
donetick-notifier donetick-notifier
``` ```
@@ -102,6 +130,8 @@ docker run --rm \
PowerShell 7 or newer is recommended. PowerShell 7 or newer is recommended.
Scheduled notifier:
```powershell ```powershell
$env:DONETICKHOST = "donetick.example.com" $env:DONETICKHOST = "donetick.example.com"
$env:DONETICKPORT = "8787" $env:DONETICKPORT = "8787"
@@ -112,25 +142,29 @@ $env:APPRISEWEBHOOKTAG = "all"
pwsh ./Start-DoneTickNotifier.ps1 pwsh ./Start-DoneTickNotifier.ps1
``` ```
Webhook consumer:
```powershell
$env:APPRISEWEBHOOKURL = "https://apprise.example.com/notify/config"
$env:APPRISEWEBHOOKTAG = "all"
pwsh ./Start-DoneTickConsumer.ps1
```
The consumer listens on port `8080` by default. Stop either script with `Ctrl+C` when running interactively.
## CI/CD ## CI/CD
This repository includes Gitea workflows for: This repository includes Gitea workflows for:
- Building and pushing the Docker image. - Building and pushing the Docker image on manual dispatch.
- Tagging images as `latest` for `main`, as the ref name for `v*` tags, and as `test` for everything else.
- Running security checks with Gitleaks, Semgrep, and Trivy. - Running security checks with Gitleaks, Semgrep, and Trivy.
- Creating Gitea issues for security findings when configured with a `GITEA_TOKEN`. - Creating Gitea issues for security findings when `GITEA_TOKEN` is configured.
- Sending Apprise notifications for Docker build success or failure. - Sending Apprise notifications for Docker build success or failure.
The Docker build workflow tags images as: The security workflow runs on pushes, pull requests, and manual dispatch. On push, it also builds the image so Trivy can scan the resulting container.
| Branch or ref | Image tag | ## API References
| --- | --- |
| `main` | `latest` |
| refs beginning with `v` | matching ref name, such as `v1.0.0` |
| all other refs | `test` |
## AI Assistance Disclosure The `Donetick/` directory contains request examples and collection metadata used to document and exercise the Donetick API while developing the notifier.
OpenAI Codex was used to help create this README and the Gitea pipeline files in this repository.
All released application code is written by the repository owner.
+152
View File
@@ -0,0 +1,152 @@
[string] $ListenUrl = "http://+:8080/"
Import-Module ./AppriseNotification.psm1
function Write-JsonResponse
{
param(
[Parameter(Mandatory=$true)]
[System.Net.HttpListenerResponse]
$Response,
[Parameter(Mandatory=$true)]
[int]
$StatusCode,
[Parameter(Mandatory=$true)]
[hashtable]
$Body
)
$json = $Body | ConvertTo-Json -Depth 10
$bytes = [System.Text.Encoding]::UTF8.GetBytes($json)
$Response.StatusCode = $StatusCode
$Response.ContentType = "application/json"
$Response.ContentEncoding = [System.Text.Encoding]::UTF8
$Response.ContentLength64 = $bytes.Length
$Response.OutputStream.Write($bytes, 0, $bytes.Length)
$Response.OutputStream.Close()
}
function Read-RequestBody
{
param(
[Parameter(Mandatory=$true)]
[System.Net.HttpListenerRequest]
$Request
)
$reader = [System.IO.StreamReader]::new($Request.InputStream, $Request.ContentEncoding)
try {
return $reader.ReadToEnd()
}
finally {
$reader.Close()
}
}
function Receive-Webhook
{
param(
[Parameter(Mandatory=$true)]
[System.Net.HttpListenerContext]
$Context
)
$request = $Context.Request
$response = $Context.Response
if ($request.HttpMethod -ne "POST") {
Write-JsonResponse -Response $response -StatusCode 405 -Body @{
ok = $false
error = "Only POST requests are supported."
}
return
}
$rawBody = Read-RequestBody -Request $request
$payload = $null
try {
if ($rawBody) {
$payload = $rawBody | ConvertFrom-Json
}
}
catch {
Write-JsonResponse -Response $response -StatusCode 400 -Body @{
ok = $false
error = "Request body was not valid JSON."
}
return
}
Write-Host "Webhook received at $(Get-Date -Format o)"
Write-Host "From: $($request.RemoteEndPoint)"
Write-Host "Path: $($request.Url.AbsolutePath)"
if ($payload) {
Write-Host "Payload:"
Write-Host ($payload | ConvertTo-Json -Depth 20)
}
else {
Write-Host "Payload: <empty>"
}
$notificationTitle = ""
$notificationContent = ""
switch($payload.type)
{
"task.completed" {
$notificationTitle = "Donetick Task Completed"
$notificationContent = "$($payload.data.chore.name) marked completed!"
}
"task.reminder" {
$notificationTitle = "Donetick Task Reminder"
$notificationContent = "$($payload.data.name) is due at $((Get-Date $payload.data.due_date).ToLocalTime())"
}
"task.created" {
$notificationTitle = "Donetick Task Created"
if ($null -eq $payload.data.chore.nextDueDate)
{
$notificationContent = "$($payload.data.chore.name) created"
}
else
{
$notificationContent = "$($payload.data.chore.name) created with due date of $((Get-Date $payload.data.chore.nextDueDate).ToLocalTime())"
}
}
default {
$notificationTitle = "Donetick Notification"
$notificationContent = "Donetick event of type $($payload.type) received"
}
}
Send-AppriseNotification -title $notificationTitle -content $notificationContent
Write-JsonResponse -Response $response -StatusCode 200 -Body @{
ok = $true
message = "Webhook accepted."
receivedAt = (Get-Date -Format o)
}
}
$listener = [System.Net.HttpListener]::new()
$listener.Prefixes.Add($ListenUrl)
try {
$listener.Start()
Write-Host "Webhook consumer listening on $ListenUrl"
Write-Host "Press Ctrl+C to stop."
while ($listener.IsListening) {
$context = $listener.GetContext()
Receive-Webhook -Context $context
}
}
finally {
if ($listener.IsListening) {
$listener.Stop()
}
$listener.Close()
}
+40 -107
View File
@@ -1,41 +1,8 @@
[string] $dtHost = $ENV:DONETICKHOST [string] $dtHost = $ENV:DONETICKHOST
[string] $dtPort = $ENV:DONETICKPORT [string] $dtPort = $ENV:DONETICKPORT
[string] $dtAPIKey = $ENV:DONETICKAPIKEY [string] $dtAPIKey = $ENV:DONETICKAPIKEY
[string] $appriseWebhookURL = $ENV:APPRISEWEBHOOKURL
[string] $appriseWebhookTag = $ENV:APPRISEWEBHOOKTAG
[int[]] $notificationTimes = $ENV:NOTIFICATIONTIMES -split ","
Import-Module ./AppriseNotification.psm1
function Send-Notification
{
param(
# Title of the notification
[Parameter(Mandatory=$true)]
[string]
$title,
# Content of the notification
[Parameter(Mandatory=$true)]
[string]
$content
)
$headers = @{
"Content-Type"="application/json"
}
$body = @{
title=$title
body=$content
tags="$appriseWebhookTag"
}
$json = $body | ConvertTo-Json
try {
Invoke-WebRequest -URI $appriseWebhookURL -Method post -Body $json -Headers $headers
}
catch {
Write-Host "Notification Error Encountered: $($global:Error[0])"
}
}
function Get-Chores function Get-Chores
{ {
@@ -51,87 +18,53 @@ function Get-Chores
} }
} }
if (-not $notificationTimes) { $notificationTimes = @(8) } $today = (Get-Date "23:59:59")
else { $notificationTimes = $notificationTimes | Sort-Object } $chores = Get-Chores
Write-Host "Notification times: $notificationTimes"
while ($true) { $overdueTasks = @()
$today = (Get-Date "23:59:59") $todaysTasks = @()
$chores = Get-Chores
$overdueTasks = @() foreach($chore in $chores)
$todaysTasks = @() {
if ($chore.nextDueDate)
foreach($chore in $chores)
{ {
if ($chore.nextDueDate) $dueDate = (Get-Date $chore.nextDueDate).ToLocalTime()
if (($dueDate - $today).Days -lt 0) #OVERDUE
{ {
$dueDate = Get-Date $chore.nextDueDate write-host "$($chore.name) $dueDate is overdue!"
if (($dueDate - $today).Days -lt 0) #OVERDUE $overdueTasks += $chore
{
write-host "$($chore.name) $dueDate is overdue!"
$overdueTasks += $chore
}
elseif (($dueDate - $today) -lt 1) #due today
{
write-host "$($chore.name) $dueDate is due today!"
$todaysTasks += $chore
# Send-Notification -title "TASK DUE TODAY" -content "$($chore.Name) is due today!"
}
else
{
write-host "$($chore.name) $dueDate is due in the future"
}
} }
elseif (($dueDate - $today) -lt 1) #due today
}
if ($overdueTasks.Count -ne 0)
{
Write-Host "Sending a notification for $($overdueTasks.Count) overdue tasks"
$content = "The following tasks are overdue!`n"
foreach($overdueTask in $overdueTasks)
{ {
$content += "$($overdueTask.Name) was due $(Get-Date $overdueTask.nextDueDate -Format "MM/dd/yyyy HH:mm")`n" write-host "$($chore.name) $dueDate is due today!"
$todaysTasks += $chore
} }
Send-Notification -title "OVERDUE TASKS" -content $content else
}
if ($todaysTasks.Count -ne 0)
{
Write-Host "Sending a notification for $($todaysTasks.Count) tasks due today"
$content = "The following tasks are due today!`n"
foreach($task in $todaysTasks)
{ {
$content += "$($task.Name) is due $(Get-Date $task.nextDueDate -Format "MM/dd/yyyy HH:mm")`n" write-host "$($chore.name) $dueDate is due in the future"
}
Send-Notification -title "TODAY'S TASKS" -content $content
}
Write-Host "Finding next notification time..."
Write-Host "Current time: $(Get-Date)"
$nextNotificationTomorrow = $true
foreach($time in $notificationTimes)
{
$now = Get-Date
$diff = $now.Hour - $time
if ($diff -lt 0) # next notification time
{
$nextNotificationTomorrow = $false
Write-Host "Next notification time is $time`:00"
$sleepTime = ($diff * -1) * 60 * 60 # hours * mins * seconds
Write-Host "Sleeping for $sleepTime seconds"
Start-Sleep -Seconds $sleepTime
continue # leave loop
} }
} }
if ($nextNotificationTomorrow)
{ }
$time = $notificationTimes[0]
Write-Host "Next notification time is $time`:00" if ($overdueTasks.Count -ne 0)
$diff = 24 - $now.Hour + $time {
$sleepTime = $diff * 60 * 60 # hours * mins * seconds Write-Host "Sending a notification for $($overdueTasks.Count) overdue tasks"
Write-Host "Sleeping for $sleepTime seconds" $content = "The following tasks are overdue!`n"
Start-Sleep -Seconds $sleepTime foreach($overdueTask in $overdueTasks)
} {
$content += "$($overdueTask.Name) was due $((Get-Date $overdueTask.nextDueDate).ToLocalTime())`n"
}
Send-AppriseNotification -title "OVERDUE TASKS" -content $content
}
if ($todaysTasks.Count -ne 0)
{
Write-Host "Sending a notification for $($todaysTasks.Count) tasks due today"
$content = "The following tasks are due today!`n"
foreach($task in $todaysTasks)
{
$content += "$($task.Name) is due $((Get-Date $task.nextDueDate).ToLocalTime())`n"
}
Send-AppriseNotification -title "TODAY'S TASKS" -content $content
} }
+2
View File
@@ -0,0 +1,2 @@
# Default schedule (overridden at runtime by JOB_SCHEDULE env var)
0 * * * * pwsh /data/Start-DoneTickNotifier.ps1
+3 -1
View File
@@ -3,6 +3,8 @@ services:
donetick-notifier: donetick-notifier:
container_name: donetick-notifier container_name: donetick-notifier
image: docker.io/blinkfink182/donetick-notifier image: docker.io/blinkfink182/donetick-notifier
ports:
- 8080:8080
environment: environment:
# BELOW ARE ALL REQUIRED # BELOW ARE ALL REQUIRED
- DONETICKHOST=host.docker.internal # donetick host - DONETICKHOST=host.docker.internal # donetick host
@@ -10,5 +12,5 @@ services:
- DONETICKAPIKEY=adminpass # donetick API key - DONETICKAPIKEY=adminpass # donetick API key
- APPRISEWEBHOOKURL=https://apprisehost/notify/config # apprise notification url - APPRISEWEBHOOKURL=https://apprisehost/notify/config # apprise notification url
- APPRISEWEBHOOKTAG=all # apprise notification tag - APPRISEWEBHOOKTAG=all # apprise notification tag
- NOTIFICATIONTIMES=8,12,17 # hours when notifications will be sent
- TZ=America/Chicago # set timezone from this list: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List - TZ=America/Chicago # set timezone from this list: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List
- JOB_SCHEDULE=0 8,17 * * * # when to notify about overdue tasks or tasks due today
+13
View File
@@ -0,0 +1,13 @@
#!/bin/bash
set -e
# Build the crontab dynamically from the env variable
echo "${JOB_SCHEDULE} pwsh /data/Start-DoneTickNotifier.ps1" > /tmp/crontab-runtime
echo "Cron schedule set to: ${JOB_SCHEDULE}"
# Start supercronic in background using the generated crontab
supercronic /tmp/crontab-runtime &
# Start the HTTP listener in foreground
exec pwsh /data/Start-DoneTickConsumer.ps1