feat: homelab-github-notifier

This commit is contained in:
auricom
2025-01-20 01:26:30 +01:00
parent fd1febeeb9
commit 0f7d1315c1
9 changed files with 344 additions and 4 deletions

View File

@@ -13,7 +13,7 @@ insert_final_newline = true
indent_style = space
indent_size = 4
[*.{bash,sh}]
[*.{bash,py,sh}]
indent_style = space
indent_size = 4

View File

@@ -0,0 +1,15 @@
repositories:
- name: CoreELEC/CoreELEC
check: releases
- name: fluxcd/flux2
check: releases
- name: opnsense/core
check: tags
ignore_tags_containing:
- a
- b
- r
- name: siderolabs/talos
check: releases
- name: ublue-os/bluefin
check: releases

View File

@@ -0,0 +1,47 @@
---
# yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/external-secrets.io/externalsecret_v1beta1.json
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: homelab-github-notifier
spec:
secretStoreRef:
kind: ClusterSecretStore
name: onepassword-connect
target:
name: homelab-github-notifier-secret
template:
engineVersion: v2
data:
# pushover
PUSHOVER_USER_KEY: " {{ .PUSHOVER_USER_KEY }}"
# pushover-notifier
HEALTHCHECKS_ID: "{{ .GITHUB_RELEASES_HEALTHCHECKS_ID }}"
PUSHOVER_APP_TOKEN: "{{ .PUSHOVER_APP_TOKEN }}"
dataFrom:
- extract:
key: pushover
- extract:
key: pushover-notifier
---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: homelab-github-notifier-db
spec:
secretStoreRef:
kind: ClusterSecretStore
name: crunchy-pgo-secrets
target:
name: homelab-github-notifier-db-secret
template:
engineVersion: v2
data:
DB_NAME: '{{ index . "dbname" }}'
DB_HOST: '{{ index . "host" }}'
DB_USER: '{{ index . "user" }}'
DB_PASSWORD: '{{ index . "password" }}'
DB_PORT: '{{ index . "port" }}'
dataFrom:
- extract:
key: postgres-pguser-pushover-notifier

View File

@@ -0,0 +1,69 @@
---
# yaml-language-server: $schema=https://raw.githubusercontent.com/bjw-s/helm-charts/main/charts/other/app-template/schemas/helmrelease-helm-v2.schema.json
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: &app homelab-github-notifier
spec:
interval: 30m
chart:
spec:
chart: app-template
version: 3.6.1
sourceRef:
kind: HelmRepository
name: bjw-s
namespace: flux-system
maxHistory: 2
install:
createNamespace: true
remediation:
retries: 3
upgrade:
cleanupOnFail: true
remediation:
strategy: rollback
retries: 3
uninstall:
keepHistory: false
values:
controllers:
music-transcode:
type: cronjob
cronjob:
concurrencyPolicy: Forbid
schedule: 0 */3 * * * # Every 3 hours
containers:
app:
image:
repository: ghcr.io/auricom/github-notifier
tag: rolling@sha256:e940cef874e6aba265f07e7e9b205f4b7224d3ca5a7eedd5d3ed5767bbd63461
command:
- python
- /app/github-notifier.py
envFrom:
- secretRef:
name: homelab-github-notifier-secret
- secretRef:
name: homelab-github-notifier-db-secret
service:
app:
controller: *app
enabled: false
persistence:
config:
type: configMap
name: homelab-github-notifier-configmap
defaultMode: 0644 # trunk-ignore(yamllint/octal-values)
globalMounts:
- path: /config/config.yaml
subPath: config.yaml
readOnly: true
script:
type: configMap
name: homelab-github-notifier-configmap
defaultMode: 0644 # trunk-ignore(yamllint/octal-values)
globalMounts:
- path: /app/github-notifier.py
subPath: github-notifier.py
readOnly: true

View File

@@ -0,0 +1,16 @@
---
# yaml-language-server: $schema=https://raw.githubusercontent.com/SchemaStore/schemastore/master/src/schemas/json/kustomization.json
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ./externalsecret.yaml
- ./helmrelease.yaml
configMapGenerator:
- name: homelab-github-notifier-configmap
files:
- ./config/config.yaml
- ./scripts/github-notifier.py
generatorOptions:
disableNameSuffixHash: true
annotations:
kustomize.toolkit.fluxcd.io/substitute: disabled

View File

@@ -0,0 +1,169 @@
import logging
import os
from datetime import datetime
import requests
import yaml
from decouple import config
from requests.exceptions import HTTPError
import psycopg2
from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT
log_level = logging.DEBUG if config("LOG_LEVEL", "") == "DEBUG" else logging.INFO
# Configuration
conf_path = "/config/config.yaml"
if not os.path.exists(conf_path):
raise FileNotFoundError(f"Config file not found: {conf_path}")
with open(conf_path, "r") as file:
conf = yaml.safe_load(file)
# Pushover credentials
PUSHOVER_API_URL = "https://api.pushover.net/1/messages.json"
PUSHOVER_APP_TOKEN = config("PUSHOVER_APP_TOKEN")
PUSHOVER_USER_KEY = config("PUSHOVER_USER_KEY")
# PostgreSQL connection parameters
DB_HOST = config("DB_HOST")
DB_PORT = config("DB_PORT", "5432")
DB_NAME = config("DB_NAME")
DB_USER = config("DB_USER")
DB_PASSWORD = config("DB_PASSWORD")
# Observability
healthchecks_id = config("HEALTHCHECKS_ID")
def get_db_connection():
return psycopg2.connect(
host=DB_HOST,
port=DB_PORT,
dbname=DB_NAME,
user=DB_USER,
password=DB_PASSWORD
)
# Create table if not exists
def create_table():
conn = get_db_connection()
conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT)
with conn.cursor() as cur:
cur.execute("""
CREATE TABLE IF NOT EXISTS github_releases (
repo_name TEXT PRIMARY KEY,
latest_release TEXT,
release_date TIMESTAMP
)
""")
conn.close()
# Check for new release
def check_new_release(repo_name):
try:
response = requests.get(
f"https://api.github.com/repos/{repo_name}/releases/latest", timeout=5
)
response.raise_for_status()
release_data = response.json()
return release_data["tag_name"], release_data["published_at"]
except HTTPError as e:
if e.response.status_code == 404:
return None, None
else:
raise
def should_ignore_tag(tag_name, ignore_list):
return any(ignore_str in tag_name for ignore_str in ignore_list)
def check_latest_tag(repo_name, ignore_list):
response = requests.get(f"https://api.github.com/repos/{repo_name}/tags", timeout=5)
response.raise_for_status()
tags = response.json()
for tag in tags:
tag_name = tag["name"]
if should_ignore_tag(tag_name, ignore_list):
continue
commit_url = tag["commit"]["url"]
commit_response = requests.get(commit_url, timeout=5)
commit_response.raise_for_status()
commit_data = commit_response.json()
commit_date = commit_data["commit"]["committer"]["date"]
return tag_name, commit_date
return None, None
def send_pushover_notification(repo_name, tag_name):
payload = {
"token": PUSHOVER_APP_TOKEN,
"user": PUSHOVER_USER_KEY,
"html": "1",
"message": f'New stable release {tag_name} for repository <a href="https://github.com/{repo_name}">{repo_name}</a> is available.',
}
response = requests.post(PUSHOVER_API_URL, data=payload, timeout=5, headers={'User-Agent': 'Python'})
response.raise_for_status()
def main():
create_table()
conn = get_db_connection()
try:
for repo_config in conf["repositories"]:
repo_name = repo_config["name"]
check_type = repo_config.get("check", "releases")
ignore_list = repo_config.get("ignore_tags_containing", [])
print(f"Checking {check_type} for repository: {repo_name}")
if check_type == "releases":
latest_tag, release_date = check_new_release(repo_name)
if latest_tag is None or release_date is None:
print(f"No release found for {repo_name}")
continue
elif check_type == "tags":
latest_tag, release_date = check_latest_tag(repo_name, ignore_list)
if latest_tag is None or release_date is None:
print(f"No valid tags found for {repo_name}, moving to next repository.")
continue
else:
print(f"Invalid check type for {repo_name}: {check_type}")
continue
print(f"Latest tag for {repo_name}: {latest_tag}, published at: {release_date}")
release_date = datetime.strptime(release_date, "%Y-%m-%dT%H:%M:%SZ")
with conn.cursor() as cur:
print(f"Updating database for {repo_name}...")
cur.execute(
"SELECT release_date FROM github_releases WHERE repo_name = %s",
(repo_name,)
)
current = cur.fetchone()
if not current or release_date > current[0]:
cur.execute("""
INSERT INTO github_releases (repo_name, latest_release, release_date)
VALUES (%s, %s, %s)
ON CONFLICT (repo_name)
DO UPDATE SET
latest_release = EXCLUDED.latest_release,
release_date = EXCLUDED.release_date
""", (repo_name, latest_tag, release_date))
conn.commit()
print(f"New release for {repo_name} found, sending notification.")
send_pushover_notification(repo_name, latest_tag)
else:
print(f"No new release to update for {repo_name}.")
try:
requests.get(f"https://hc-ping.com/{healthchecks_id}", timeout=10)
except requests.RequestException as e:
print("Ping failed: %s" % e)
finally:
conn.close()
if __name__ == "__main__":
main()

View File

@@ -2,6 +2,30 @@
# yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/kustomize.toolkit.fluxcd.io/kustomization_v1.json
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: &app homelab-github-notifier
namespace: flux-system
spec:
targetNamespace: default
commonMetadata:
labels:
app.kubernetes.io/name: *app
path: ./kubernetes/apps/default/homelab/github-notifier
prune: true
sourceRef:
kind: GitRepository
name: home-ops-kubernetes
wait: false
interval: 30m
retryInterval: 1m
timeout: 5m
postBuild:
substitute:
APP: *app
---
# yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/kustomize.toolkit.fluxcd.io/kustomization_v1.json
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: &app homelab-music-transcode
namespace: flux-system

View File

@@ -4,7 +4,6 @@ apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: &app homelab-music-transcode
namespace: default
spec:
interval: 30m
chart:
@@ -64,7 +63,7 @@ spec:
scripts:
type: configMap
name: music-transcode-configmap
defaultMode: 0775
defaultMode: 0775 # trunk-ignore(yamllint/octal-values)
globalMounts:
- path: /app/transcode.sh
subPath: transcode.sh
@@ -72,7 +71,7 @@ spec:
exclude:
type: configMap
name: music-transcode-configmap
defaultMode: 0775
defaultMode: 0644 # trunk-ignore(yamllint/octal-values)
globalMounts:
- path: /app/transcode_exclude.cfg
subPath: transcode_exclude.cfg

View File

@@ -13,6 +13,7 @@ spec:
name: radarr-secret
template:
data:
RADARR__AUTH__APIKEY: "{{ .RADARR__API_KEY }}"
PUSHOVER_API_TOKEN: "{{ .PUSHOVER_API_TOKEN }}"
PUSHOVER_USER_KEY: "{{ .PUSHOVER_USER_KEY }}"
dataFrom: