From 417497556fa1ecedf910714593dc2abc1cdb2761 Mon Sep 17 00:00:00 2001 From: Mark Eaton Date: Fri, 23 Jan 2026 13:02:42 -0500 Subject: [PATCH 1/6] add saints --- saints/saints.service | 20 ++++++++++++++++++++ saints/saints.timer | 18 ++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 saints/saints.service create mode 100644 saints/saints.timer diff --git a/saints/saints.service b/saints/saints.service new file mode 100644 index 0000000..7bf0f39 --- /dev/null +++ b/saints/saints.service @@ -0,0 +1,20 @@ +# lives at ~/.config/systemd/user/saints.service + +# User-level systemd service to run get_saint_of_the_day.py +# Configuration is via ~/.config/saints.env, no in-file path edits needed. +# Create that file with one line: +# REPO_PATH=/absolute/path/to/this/repo +# Then run: +# systemctl --user daemon-reload +[Unit] +Description=Run get_saint_of_the_day.py (profile stats updater) + +[Service] +Type=oneshot +EnvironmentFile=%h/.config/saints.env +ExecStartPre=/usr/bin/nm-online -q +ExecStartPre=/bin/bash -c 'for i in {1..30}; do getent hosts tildegit.org && exit 0; sleep 1; done; exit 1' +ExecStart=/bin/bash -lc 'cd ${REPO_PATH} && uv run get_saint_of_the_day.py' + +[Install] +WantedBy=default.target diff --git a/saints/saints.timer b/saints/saints.timer new file mode 100644 index 0000000..7c6ede9 --- /dev/null +++ b/saints/saints.timer @@ -0,0 +1,18 @@ +# lives at ~/.config/systemd/user/saints.timer + +# User-level systemd timer that "catches up" missed runs if the machine was off. +[Unit] +Description=Schedule get_saint_of_the_day.py with catch-up if missed + +[Timer] +# Change this to your desired schedule; examples: +# OnCalendar=daily +# OnCalendar=Mon..Fri 09:00 +# See man systemd.time for syntax. +OnCalendar=daily +Timezone=America/New_York +Persistent=true +Unit=saints.service + +[Install] +WantedBy=timers.target From b610a87d8d41b71cc01b4de9703bfca54ab2e494 Mon Sep 17 00:00:00 2001 From: Mark Eaton Date: Sat, 24 Jan 2026 21:27:42 -0500 Subject: [PATCH 2/6] switch to gitea --- notify/notify-gitea.sh | 89 ++++++++++++++++++++++++++++++++++++++++++ notify/notify-teams.sh | 68 -------------------------------- 2 files changed, 89 insertions(+), 68 deletions(-) create mode 100755 notify/notify-gitea.sh delete mode 100755 notify/notify-teams.sh diff --git a/notify/notify-gitea.sh b/notify/notify-gitea.sh new file mode 100755 index 0000000..03df77d --- /dev/null +++ b/notify/notify-gitea.sh @@ -0,0 +1,89 @@ +#!/bin/bash +# notify-gitea.sh - Create Gitea issues for systemd service failures +# +# Usage: notify-gitea.sh +# status: "success" or "failure" +# Only creates an issue on failure; success is logged but ignored. +# +# Configuration: +# Set these environment variables or create /etc/notify-gitea.conf: +# GITEA_URL - Base URL of your Gitea instance (e.g., https://gitea.example.com) +# GITEA_TOKEN - API token with repo write access +# GITEA_OWNER - Repository owner (user or org) +# GITEA_REPO - Repository name + +set -euo pipefail + +SERVICE_NAME="${1:-unknown}" +STATUS="${2:-unknown}" +HOSTNAME=$(hostname) +TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S %Z') + +# Load config from file if not set +if [[ -f /etc/notify-gitea.conf ]]; then + source /etc/notify-gitea.conf +fi + +# Validate required variables +for var in GITEA_URL GITEA_TOKEN GITEA_OWNER GITEA_REPO; do + if [[ -z "${!var:-}" ]]; then + echo "Error: $var not set" >&2 + exit 1 + fi +done + +# Only create issues on failure +if [[ "$STATUS" == "success" ]]; then + echo "Service ${SERVICE_NAME} succeeded - no issue created" + exit 0 +fi + +# Get recent logs for context (last 20 lines) +RECENT_LOGS=$(journalctl -u "$SERVICE_NAME" -n 20 --no-pager 2>/dev/null || echo "No logs available") + +# Build issue title and body +TITLE="[${HOSTNAME}] Service failure: ${SERVICE_NAME}" + +BODY="## Service Failure Report + +**Service:** \`${SERVICE_NAME}\` +**Host:** \`${HOSTNAME}\` +**Time:** ${TIMESTAMP} +**Status:** ${STATUS} + +## Recent Logs + +\`\`\` +${RECENT_LOGS} +\`\`\` +" + +# Create JSON payload using jq for proper escaping +PAYLOAD=$(jq -n \ + --arg title "$TITLE" \ + --arg body "$BODY" \ + '{title: $title, body: $body}') + +# Create the issue via Gitea API +API_URL="${GITEA_URL}/api/v1/repos/${GITEA_OWNER}/${GITEA_REPO}/issues" + +RESPONSE=$(curl -s -w "\n%{http_code}" \ + -X POST \ + -H "Content-Type: application/json" \ + -H "Authorization: token ${GITEA_TOKEN}" \ + -d "$PAYLOAD" \ + "$API_URL") + +HTTP_CODE=$(echo "$RESPONSE" | tail -1) +BODY_RESPONSE=$(echo "$RESPONSE" | sed '$d') + +if [[ "$HTTP_CODE" -ge 200 && "$HTTP_CODE" -lt 300 ]]; then + ISSUE_NUMBER=$(echo "$BODY_RESPONSE" | jq -r '.number // "unknown"') + ISSUE_URL=$(echo "$BODY_RESPONSE" | jq -r '.html_url // "unknown"') + echo "Issue #${ISSUE_NUMBER} created for ${SERVICE_NAME} failure" + echo "URL: ${ISSUE_URL}" +else + echo "Error creating issue: HTTP ${HTTP_CODE}" >&2 + echo "$BODY_RESPONSE" >&2 + exit 1 +fi diff --git a/notify/notify-teams.sh b/notify/notify-teams.sh deleted file mode 100755 index fc2aae6..0000000 --- a/notify/notify-teams.sh +++ /dev/null @@ -1,68 +0,0 @@ -#!/bin/bash -# notify-teams.sh - Send Teams notifications for systemd service events -# -# Usage: notify-teams.sh -# status: "success" or "failure" -# -# Configuration: -# Set TEAMS_WEBHOOK_URL environment variable or create /etc/notify-teams.conf - -set -euo pipefail - -SERVICE_NAME="${1:-unknown}" -STATUS="${2:-unknown}" -HOSTNAME=$(hostname) -TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S %Z') - -# Load webhook URL from config file if not set -if [[ -z "${TEAMS_WEBHOOK_URL:-}" ]] && [[ -f /etc/notify-teams.conf ]]; then - source /etc/notify-teams.conf -fi - -if [[ -z "${TEAMS_WEBHOOK_URL:-}" ]]; then - echo "Error: TEAMS_WEBHOOK_URL not set" >&2 - exit 1 -fi - -# Set color and title based on status -if [[ "$STATUS" == "success" ]]; then - THEME_COLOR="00FF00" - TITLE="Service Completed Successfully" - EMOJI="✓" -else - THEME_COLOR="FF0000" - TITLE="Service Failed" - EMOJI="✗" -fi - -# Get recent logs for context (last 15 lines) -RECENT_LOGS=$(journalctl -u "$SERVICE_NAME" -n 15 --no-pager 2>/dev/null | tail -15 || echo "No logs available") - -# Escape special characters for JSON -escape_json() { - printf '%s' "$1" | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()))' -} - -ESCAPED_LOGS=$(escape_json "$RECENT_LOGS") - -# Build and send the payload -curl -s -H "Content-Type: application/json" -d "{ - \"@type\": \"MessageCard\", - \"@context\": \"https://schema.org/extensions\", - \"themeColor\": \"${THEME_COLOR}\", - \"title\": \"${EMOJI} ${TITLE}\", - \"summary\": \"${SERVICE_NAME} ${STATUS} on ${HOSTNAME}\", - \"sections\": [{ - \"facts\": [ - {\"name\": \"Service\", \"value\": \"${SERVICE_NAME}\"}, - {\"name\": \"Status\", \"value\": \"${STATUS}\"}, - {\"name\": \"Host\", \"value\": \"${HOSTNAME}\"}, - {\"name\": \"Time\", \"value\": \"${TIMESTAMP}\"} - ] - }, { - \"title\": \"Recent Logs\", - \"text\": ${ESCAPED_LOGS} - }] -}" "$TEAMS_WEBHOOK_URL" - -echo "Notification sent for ${SERVICE_NAME} (${STATUS})" From 61e1dd122fbd0c336a66f8f98dd428cf45cac17a Mon Sep 17 00:00:00 2001 From: Mark Eaton Date: Sat, 24 Jan 2026 21:28:52 -0500 Subject: [PATCH 3/6] remove notify-teams-success@.service --- notify/notify-teams-success@.service | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 notify/notify-teams-success@.service diff --git a/notify/notify-teams-success@.service b/notify/notify-teams-success@.service deleted file mode 100644 index 2e278be..0000000 --- a/notify/notify-teams-success@.service +++ /dev/null @@ -1,8 +0,0 @@ -[Unit] -Description=Send Teams success notification for %i - -[Service] -Type=oneshot -ExecStart=/usr/local/bin/notify-teams.sh %i success -# Load webhook URL from environment file -EnvironmentFile=-/etc/notify-teams.conf From 48310741152c0af36d62ea3f459564dd507c275b Mon Sep 17 00:00:00 2001 From: Mark Eaton Date: Sat, 24 Jan 2026 21:29:53 -0500 Subject: [PATCH 4/6] remove old files --- ...fy-teams-failure@.service => notify-gitea-failure@.service} | 0 notify/notify-teams.conf.example | 3 --- 2 files changed, 3 deletions(-) rename notify/{notify-teams-failure@.service => notify-gitea-failure@.service} (100%) delete mode 100644 notify/notify-teams.conf.example diff --git a/notify/notify-teams-failure@.service b/notify/notify-gitea-failure@.service similarity index 100% rename from notify/notify-teams-failure@.service rename to notify/notify-gitea-failure@.service diff --git a/notify/notify-teams.conf.example b/notify/notify-teams.conf.example deleted file mode 100644 index 54efb3d..0000000 --- a/notify/notify-teams.conf.example +++ /dev/null @@ -1,3 +0,0 @@ -# /etc/notify-teams.conf -# Teams incoming webhook URL -TEAMS_WEBHOOK_URL="https://outlook.office.com/webhook/your-webhook-url-here" From 6641b6241501023b0c2cb4c5b03c6648facae714 Mon Sep 17 00:00:00 2001 From: Mark Eaton Date: Sat, 24 Jan 2026 21:53:16 -0500 Subject: [PATCH 5/6] configuration for gitea notifications --- libguides-notifier/libguides-notifier.service | 5 ++--- notify/notify-gitea-failure@.service | 6 +++--- profile/profile-stats.service | 1 + renovate/renovate.service | 6 ++++-- saints/saints.service | 3 ++- 5 files changed, 12 insertions(+), 9 deletions(-) diff --git a/libguides-notifier/libguides-notifier.service b/libguides-notifier/libguides-notifier.service index d222912..4d7584c 100644 --- a/libguides-notifier/libguides-notifier.service +++ b/libguides-notifier/libguides-notifier.service @@ -1,14 +1,13 @@ -# lives at ~/.config/systemd/user/libguides-notifier.service - # User-level systemd service to run main.py # Uses a wrapper script so the working directory and .env are found correctly. # Configuration is via ~/.config/libguides-notifier.env, no in-file path edits needed. # Create that file with one line: -# REPO_PATH=/absolute/path/to/this/repo +# REPO_PATH=/absolute/path/to/project # Then run: # systemctl --user daemon-reload [Unit] Description=Run main.py (LibGuides notifier) +OnFailure=notify-gitea-failure@%n.service [Service] Type=oneshot diff --git a/notify/notify-gitea-failure@.service b/notify/notify-gitea-failure@.service index 74ce838..d2bb5ba 100644 --- a/notify/notify-gitea-failure@.service +++ b/notify/notify-gitea-failure@.service @@ -1,8 +1,8 @@ [Unit] -Description=Send Teams failure notification for %i +Description=Send Gitea failure notification for %i [Service] Type=oneshot -ExecStart=/usr/local/bin/notify-teams.sh %i failure +ExecStart=/usr/local/bin/notify-gitea.sh %i failure # Load webhook URL from environment file -EnvironmentFile=-/etc/notify-teams.conf +EnvironmentFile=-/etc/notify-gitea.conf diff --git a/profile/profile-stats.service b/profile/profile-stats.service index 7302d76..3bb81c2 100644 --- a/profile/profile-stats.service +++ b/profile/profile-stats.service @@ -8,6 +8,7 @@ # systemctl --user daemon-reload [Unit] Description=Run update_profile.py (profile stats updater) +OnFailure=notify-gitea-failure@%n.service [Service] Type=oneshot diff --git a/renovate/renovate.service b/renovate/renovate.service index 6f3fe37..ee2f5be 100644 --- a/renovate/renovate.service +++ b/renovate/renovate.service @@ -1,5 +1,3 @@ -# lives at /etc/systemd/system/renovate.service - [Unit] Description=Renovate Bot After=network-online.target @@ -30,3 +28,7 @@ SyslogIdentifier=renovate [Install] WantedBy=multi-user.target + +[Unit] +Description=Renovate dependency updates +OnFailure=notify-gitea-failure@%n.service diff --git a/saints/saints.service b/saints/saints.service index 7bf0f39..94dab77 100644 --- a/saints/saints.service +++ b/saints/saints.service @@ -8,13 +8,14 @@ # systemctl --user daemon-reload [Unit] Description=Run get_saint_of_the_day.py (profile stats updater) +OnFailure=notify-gitea-failure@%n.service [Service] Type=oneshot EnvironmentFile=%h/.config/saints.env ExecStartPre=/usr/bin/nm-online -q ExecStartPre=/bin/bash -c 'for i in {1..30}; do getent hosts tildegit.org && exit 0; sleep 1; done; exit 1' -ExecStart=/bin/bash -lc 'cd ${REPO_PATH} && uv run get_saint_of_the_day.py' +ExecStart=/bin/bash -lc 'cd ${REPO_PATH} && uv run get_saint_of_the_day.py --post' [Install] WantedBy=default.target From 3286b9618e6a0c831948b1eee69ce9920ccc304b Mon Sep 17 00:00:00 2001 From: Mark Eaton Date: Sat, 24 Jan 2026 23:06:59 -0500 Subject: [PATCH 6/6] show logs in Gitea even with the service is runs as --user --- notify/notify-gitea.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/notify/notify-gitea.sh b/notify/notify-gitea.sh index 03df77d..4e201e0 100755 --- a/notify/notify-gitea.sh +++ b/notify/notify-gitea.sh @@ -39,7 +39,11 @@ if [[ "$STATUS" == "success" ]]; then fi # Get recent logs for context (last 20 lines) -RECENT_LOGS=$(journalctl -u "$SERVICE_NAME" -n 20 --no-pager 2>/dev/null || echo "No logs available") +# Try system journal first, fall back to user journal +RECENT_LOGS=$(journalctl -u "$SERVICE_NAME" -n 100 --no-pager 2>/dev/null) +if [[ -z "$RECENT_LOGS" || "$RECENT_LOGS" == *"-- No entries --"* ]]; then + RECENT_LOGS=$(journalctl --user-unit "$SERVICE_NAME" -n 100 --no-pager 2>/dev/null || echo "No logs available") +fi # Build issue title and body TITLE="[${HOSTNAME}] Service failure: ${SERVICE_NAME}"