mirror of
https://github.com/auricom/home-cluster.git
synced 2025-09-17 18:24:14 +02:00
feat: homelab-github-notifier
This commit is contained in:
@@ -13,7 +13,7 @@ insert_final_newline = true
|
|||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
|
|
||||||
[*.{bash,sh}]
|
[*.{bash,py,sh}]
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
|
|
||||||
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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()
|
@@ -2,6 +2,30 @@
|
|||||||
# yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/kustomize.toolkit.fluxcd.io/kustomization_v1.json
|
# yaml-language-server: $schema=https://kubernetes-schemas.pages.dev/kustomize.toolkit.fluxcd.io/kustomization_v1.json
|
||||||
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
||||||
kind: Kustomization
|
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:
|
metadata:
|
||||||
name: &app homelab-music-transcode
|
name: &app homelab-music-transcode
|
||||||
namespace: flux-system
|
namespace: flux-system
|
||||||
|
@@ -4,7 +4,6 @@ apiVersion: helm.toolkit.fluxcd.io/v2
|
|||||||
kind: HelmRelease
|
kind: HelmRelease
|
||||||
metadata:
|
metadata:
|
||||||
name: &app homelab-music-transcode
|
name: &app homelab-music-transcode
|
||||||
namespace: default
|
|
||||||
spec:
|
spec:
|
||||||
interval: 30m
|
interval: 30m
|
||||||
chart:
|
chart:
|
||||||
@@ -64,7 +63,7 @@ spec:
|
|||||||
scripts:
|
scripts:
|
||||||
type: configMap
|
type: configMap
|
||||||
name: music-transcode-configmap
|
name: music-transcode-configmap
|
||||||
defaultMode: 0775
|
defaultMode: 0775 # trunk-ignore(yamllint/octal-values)
|
||||||
globalMounts:
|
globalMounts:
|
||||||
- path: /app/transcode.sh
|
- path: /app/transcode.sh
|
||||||
subPath: transcode.sh
|
subPath: transcode.sh
|
||||||
@@ -72,7 +71,7 @@ spec:
|
|||||||
exclude:
|
exclude:
|
||||||
type: configMap
|
type: configMap
|
||||||
name: music-transcode-configmap
|
name: music-transcode-configmap
|
||||||
defaultMode: 0775
|
defaultMode: 0644 # trunk-ignore(yamllint/octal-values)
|
||||||
globalMounts:
|
globalMounts:
|
||||||
- path: /app/transcode_exclude.cfg
|
- path: /app/transcode_exclude.cfg
|
||||||
subPath: transcode_exclude.cfg
|
subPath: transcode_exclude.cfg
|
||||||
|
@@ -13,6 +13,7 @@ spec:
|
|||||||
name: radarr-secret
|
name: radarr-secret
|
||||||
template:
|
template:
|
||||||
data:
|
data:
|
||||||
|
RADARR__AUTH__APIKEY: "{{ .RADARR__API_KEY }}"
|
||||||
PUSHOVER_API_TOKEN: "{{ .PUSHOVER_API_TOKEN }}"
|
PUSHOVER_API_TOKEN: "{{ .PUSHOVER_API_TOKEN }}"
|
||||||
PUSHOVER_USER_KEY: "{{ .PUSHOVER_USER_KEY }}"
|
PUSHOVER_USER_KEY: "{{ .PUSHOVER_USER_KEY }}"
|
||||||
dataFrom:
|
dataFrom:
|
||||||
|
Reference in New Issue
Block a user