Compare commits

...

18 Commits

Author SHA1 Message Date
4a57e9e2e1 Comment everything from french to english 2024-03-04 13:18:13 +01:00
1ef3dfa49d Miss name variable that break all program 2024-03-04 13:08:00 +01:00
0e72fd80cc Add web interface to manage repo 2024-03-04 12:57:45 +01:00
3b6f55e703 Add web interface to manage repo 2024-03-04 12:34:36 +01:00
5b113d725b Merge remote-tracking branch 'origin/main' 2024-03-03 09:47:54 +01:00
9b2cf22892 Update to take markdown formating 2024-03-03 09:47:43 +01:00
Félix MARQUET
34b314d40f Update README.md 2024-03-02 16:57:11 +01:00
Félix MARQUET
7a1808569e Update README.md 2024-03-01 23:07:31 +01:00
Félix MARQUET
063c2db3a9 Update README.md 2024-03-01 21:29:22 +01:00
Félix MARQUET
f68bc12902 Update README.md 2024-03-01 21:29:08 +01:00
c728183bf4 Change db path 2024-03-01 20:10:20 +01:00
6da0b5dac0 Merge remote-tracking branch 'origin/main' 2024-03-01 19:30:19 +01:00
6c714abedd Add a button to go to the github page 2024-03-01 19:30:02 +01:00
Félix MARQUET
ecf162f6ed Update README.md 2024-03-01 18:59:35 +01:00
7a5070443b Add github token authentification 2024-03-01 18:43:05 +01:00
dc6d19e98a Add the database for application version 2024-03-01 14:33:49 +01:00
Félix MARQUET
61f49b15b7 Update README.md 2024-03-01 14:14:05 +01:00
910ae52445 Add repo list as enviroment variable 2024-03-01 13:56:29 +01:00
9 changed files with 309 additions and 37 deletions

View File

@@ -3,14 +3,25 @@ FROM python:3.11.8-alpine3.19
LABEL maintainer="BreizhHardware"
ADD ntfy.py /
ADD ntfy_api.py /
ADD requirements.txt /
ADD entrypoint.sh /
ADD index.html /var/www/html/index.html
ADD script.js /var/www/html/script.js
RUN apk add --no-cache sqlite-dev sqlite-libs gcc musl-dev nginx
RUN pip install -r requirements.txt
# Définir les variables d'environnement pour username et password
ENV USERNAME="" \
PASSWORD="" \
NTFY_URL="" \
GHNTFY_TIMEOUT="3600"
GHNTFY_TIMEOUT="3600" \
GHNTFY_TOKEN="" \
FLASK_ENV=production
# Exposer le port 5000 pour l'API et le port 80 pour le serveur web
EXPOSE 5000 80
COPY nginx.conf /etc/nginx/nginx.conf
ENTRYPOINT ["/entrypoint.sh"]

View File

@@ -1,14 +1,18 @@
# ntfy_alerts
Personal ntfy alerts system
feel free to contribute and to fork
# Python ntfy.py
## Description:
This script is used to watch the github repos and send a notification to the ntfy server when a new release is published.
## Utilisation:
auth and ntfy_url are required to be set as environment variables.
auth: can be generataed by the folowing command: echo -n 'username:password' | base64
ntfy_url: the url of the ntfy server including the topic
````python
python ntfy.py
````
@@ -18,21 +22,28 @@ If you want to use the docker image you can use the following docker-compose fil
version: '3'
services:
github-ntfy:
image: breizhhardware/github-ntfy
image: breizhhardware/github-ntfy:latest
container_name: github-ntfy
environment:
- USERNAME=username
- PASSWORD=password
- NTFY_URL=ntfy_url
- USERNAME=username # Required
- PASSWORD=password # Required
- NTFY_URL=ntfy_url # Required
- GHNTFY_TIMEOUT=timeout # Default is 3600 (1 hour)
- GHNTFY_TOKEN= # Default is empty (Github token)
volumes:
- /path/to/github-ntfy:/github-ntfy/
ports:
- 80:80
restart: unless-stopped
````
Acctualy the watched repos list is hardcoded in the ntfy.py file under the name of watched_repos_list.
GHNTFY_TOKEN, need to have repo, read:org and read:user
Docker Hub repo: https://hub.docker.com/r/breizhhardware/github-ntfy
## TODO:
- [x] Dockerize the ntfy.py
- [ ] Add the watched repos list as a parameter
- [ ] Add the watched repos list as a file
- [ ] Add the watched repos list as a database
- [ ] Add the watched repos list as a web interface
- [x] Add the watched repos list as a parameter
- [x] Add the application version as a database
- [x] Add the watched repos list as a web interface
# Bash setup-notify.sh
## Description:
This script is used to setup the ntfy notification system on ssh login for a new server.
@@ -41,8 +52,11 @@ This script is used to setup the ntfy notification system on ssh login for a new
bash setup-notify.sh <ntfy_url> <username> <password> <topic>
````
ntfy_url: the url of the ntfy server
username: the username of the user
password: the password of the user
topic: the topic of the notification
This script will create a send-notify.sh in the root of your disk and add the login-notify.sh to the /etc/profile.d/ folder.
@@ -54,7 +68,10 @@ This script is used to send a notification to the ntfy server.
bash send-notify.sh <ntfy_url> <basic_auth> <topic> <message>
````
ntfy_url: the url of the ntfy server
basic_auth: the basic auth of the user
topic: the topic of the notification
message: the message of the notification

View File

@@ -3,5 +3,8 @@
# Génère le contenu du fichier auth.txt à partir des variables d'environnement
echo -n "$USERNAME:$PASSWORD" | base64 > /auth.txt
# Démarrer nginx en arrière-plan
nginx -g 'daemon off;' &
# Exécute le script Python
exec python ./ntfy.py

43
index.html Normal file
View File

@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Github-Ntfy Add a repo</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="./script.js" defer></script>
</head>
<body class="bg-gradient-to-b from-cyan-500 to-fuchsia-500">
<div class="flex flex-col gap-2 justify-center items-center my-2 h-screen">
<h1 class="text-4xl font-semibold leading-10 text-gray-900">Github-Ntfy</h1>
<h1>Add a repo</h1>
<form id="addRepoForm">
<div class="space-y-12">
<div class="border-b border-gray-900/10 pb-12">
<h2 class="text-base font-semibold leading-7 text-gray-900">Name of the github repo</h2>
<div class="mt-10 grid grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
<div class="sm:col-span-4">
<div class="mt-2">
<div class="flex rounded-md shadow-sm ring-1 ring-inset ring-gray-300 focus-within:ring-2 focus-within:ring-inset focus-within:ring-indigo-600 sm:max-w-md">
<span class="flex select-none items-center pl-3 sm:text-sm">github.com/</span>
<input type="text" name="repo" id="repo" autocomplete="repo" class="block flex-1 border-0 bg-transparent py-1.5 pl-1 placeholder:text-gray-600 focus:ring-0 sm:text-sm sm:leading-6" placeholder="BreizhHardware/ntfy_alerts">
</div>
</div>
</div>
</div>
</div>
</div>
<div class="mt-6 flex items-center justify-end gap-x-6">
<button type="button" class="text-sm font-semibold leading-6 text-gray-900">Cancel</button>
<button type="submit" class="rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">Save</button>
</div>
</form>
<div class="mt-8">
<h2 class="text-base font-semibold leading-7 text-gray-900">Watched Repositories</h2>
<ul id="watchedReposList" class="mt-4">
<!-- Dynamically populated with JavaScript -->
</ul>
</div>
</div>
</body>
</html>

32
nginx.conf Normal file
View File

@@ -0,0 +1,32 @@
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
server {
listen 80;
location / {
root /var/www/html;
index index.html;
}
location /app_repo {
proxy_pass http://127.0.0.1:5000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /watched_repos {
proxy_pass http://127.0.0.1:5000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}

109
ntfy.py
View File

@@ -2,64 +2,118 @@ import requests
import time
import os
import logging
import sqlite3
import subprocess
# Configurer le logger
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
# Configuring the logger
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
watched_repos_list = ['dani-garcia/vaultwarden', 'jellyfin/jellyfin', 'linuxserver/Heimdall',
'jlesage/docker-nginx-proxy-manager', 'linuxserver/docker-speedtest-tracker',
'linuxserver/docker-xbackbone', 'Fallenbagel/jellyseerr', 'FlareSolverr/FlareSolverr',
'linuxserver/docker-jackett', 'linuxserver/docker-radarr', 'linuxserver/docker-sonarr']
github_token = os.environ.get('GHNTFY_TOKEN')
github_headers = {}
if github_token:
github_headers['Authorization'] = f"token {github_token}"
# Dictionnaire pour stocker les versions précédentes
previous_versions = {}
# Connecting to the database to store previous versions
conn = sqlite3.connect('/github-ntfy/ghntfy_versions.db', check_same_thread=False)
cursor = conn.cursor()
# Creating the table if it does not exist
cursor.execute('''CREATE TABLE IF NOT EXISTS versions
(repo TEXT PRIMARY KEY, version TEXT, changelog TEXT)''')
conn.commit()
logger.info("Starting version monitoring...")
conn2 = sqlite3.connect('/github-ntfy/watched_repos.db', check_same_thread=False)
cursor2 = conn2.cursor()
cursor2.execute('''CREATE TABLE IF NOT EXISTS watched_repos
(id INTEGER PRIMARY KEY, repo TEXT)''')
conn2.commit()
def get_watched_repos():
cursor2.execute("SELECT * FROM watched_repos")
watched_repos_rows = cursor2.fetchall()
watched_repos = []
for repo in watched_repos_rows:
watched_repos.append(repo[1])
return watched_repos
def start_api():
subprocess.Popen(["python", "ntfy_api.py"])
logger.info("Démarrage de la surveillance des versions...")
def get_latest_releases(watched_repos):
releases = []
for repo in watched_repos:
url = f"https://api.github.com/repos/{repo}/releases/latest"
response = requests.get(url)
response = requests.get(url, headers=github_headers)
if response.status_code == 200:
release_info = response.json()
changelog = get_changelog(repo)
releases.append({
"repo": repo,
"name": release_info["name"],
"tag_name": release_info["tag_name"],
"html_url": release_info["html_url"]
"html_url": release_info["html_url"],
"changelog": changelog
})
else:
logger.error(f"Failed to fetch release info for {repo}")
return releases
def get_changelog(repo):
url = f"https://api.github.com/repos/{repo}/releases"
response = requests.get(url, headers=github_headers)
if response.status_code == 200:
releases = response.json()
if releases:
latest_release_list = releases[0]
if 'body' in latest_release_list:
return latest_release_list['body']
return "Changelog not available"
def send_to_ntfy(releases, auth, url):
for release in releases:
app_name = release['repo'].split('/')[-1] # Obtenir le nom de l'application à partir du repo
version_number = release['tag_name'] # Obtenir le numéro de version
app_url = release['html_url'] # Obtenir l'URL de l'application
app_name = release['repo'].split('/')[-1] # Getting the application name from the repo
version_number = release['tag_name'] # Getting the version number
app_url = release['html_url'] # Getting the application URL
changelog = release['changelog'] # Getting the changelog
# Vérifier si la version a changé depuis la dernière fois
if app_name in previous_versions and previous_versions[app_name] == version_number:
logger.info(f"La version de {app_name} n'a pas changé. Pas de notification envoyée.")
continue # Passer à l'application suivante
# Checking if the version has changed since the last time
cursor.execute("SELECT version FROM versions WHERE repo=?", (app_name,))
previous_version = cursor.fetchone()
if previous_version and previous_version[0] == version_number:
logger.info(f"The version of {app_name} has not changed. No notification sent.")
continue # Move on to the next application
message = f"Nouvelle version: {version_number}\nPour: {app_name}\n{app_url}"
# Mettre à jour la version précédente pour cette application
previous_versions[app_name] = version_number
message = f"New version: {version_number}\nFor: {app_name}\nChangelog:\n{changelog}\n{app_url}"
# Updating the previous version for this application
cursor.execute("INSERT OR REPLACE INTO versions (repo, version, changelog) VALUES (?, ?, ?)",
(app_name, version_number, changelog))
conn.commit()
headers = {"Authorization": f"Basic {auth}", "Content-Type": "text/plain"}
headers = {
"Authorization": f"Basic {auth}",
"Title": f"New version for {app_name}",
"Priority": "urgent",
"Markdown": "yes",
"Actions": f"view, Update {app_name}, {app_url}, clear=true"}
response = requests.post(f"{url}", headers=headers, data=message)
if response.status_code == 200:
logger.info(f"Message envoyé à Ntfy pour {app_name}")
logger.info(f"Message sent to Ntfy for {app_name}")
continue
else:
logger.error(f"Échec de l'envoi du message à Ntfy. Code d'état : {response.status_code}")
logger.error(f"Failed to send message to Ntfy. Status code: {response.status_code}")
if __name__ == "__main__":
start_api()
with open('/auth.txt', 'r') as f:
auth = f.read().strip()
ntfy_url = os.environ.get('NTFY_URL')
@@ -67,11 +121,14 @@ if __name__ == "__main__":
if auth and ntfy_url:
while True:
watched_repos_list = get_watched_repos()
latest_release = get_latest_releases(watched_repos_list)
if latest_release:
send_to_ntfy(latest_release, auth, ntfy_url)
time.sleep(timeout) # Attendre une heure avant de vérifier à nouveau
time.sleep(timeout) # Wait an hour before checking again
else:
logger.error("Usage: python ntfy.py")
logger.error("auth: can be generataed by the folowing command: echo -n 'username:password' | base64 and need to be stored in a file named auth.txt")
logger.error(
"auth: can be generataed by the folowing command: echo -n 'username:password' | base64 and need to be stored in a file named auth.txt")
logger.error("NTFY_URL: the url of the ntfy server need to be stored in an environment variable named NTFY_URL")

61
ntfy_api.py Normal file
View File

@@ -0,0 +1,61 @@
from flask import Flask, request, jsonify
from flask_cors import CORS
import sqlite3
app = Flask(__name__)
CORS(app)
app.logger.setLevel("WARNING")
def get_db_connection():
conn = sqlite3.connect('/github-ntfy/watched_repos.db')
conn.row_factory = sqlite3.Row
return conn
def close_db_connection(conn):
conn.close()
@app.route('/app_repo', methods=['POST'])
def app_repo():
data = request.json
repo = data.get('repo')
# Vérifier si le champ 'repo' est présent dans les données JSON
if not repo:
return jsonify({"error": "The repo field is required."}), 400
# Établir une connexion à la base de données
conn = get_db_connection()
cursor = conn.cursor()
try:
# Vérifier si le dépôt existe déjà dans la base de données
cursor.execute("SELECT * FROM watched_repos WHERE repo=?", (repo,))
existing_repo = cursor.fetchone()
if existing_repo:
return jsonify({"error": f"The repo {repo} is already in the database."}), 409
# Ajouter le dépôt à la base de données
cursor.execute("INSERT INTO watched_repos (repo) VALUES (?)", (repo,))
conn.commit()
return jsonify({"message": f"The repo {repo} as been added to the watched repos."})
finally:
# Fermer la connexion à la base de données
close_db_connection(conn)
@app.route('/watched_repos', methods=['GET'])
def get_watched_repos():
db = get_db_connection()
cursor = db.cursor()
cursor.execute("SELECT repo FROM watched_repos")
watched_repos = [repo[0] for repo in cursor.fetchall()]
cursor.close()
db.close()
return jsonify(watched_repos)
if __name__ == "__main__":
app.run(debug=False)

View File

@@ -1 +1,4 @@
requests==2.31.0
requests==2.31.0
pysqlite3==0.5.2
flask==3.0.2
flask-cors==4.0.0

45
script.js Normal file
View File

@@ -0,0 +1,45 @@
document.getElementById('addRepoForm').addEventListener('submit', function(event) {
event.preventDefault();
let repoName = document.getElementById('repo').value;
fetch('/app_repo', {
method: 'POST',
headers: {
'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/json'
},
body: JSON.stringify({repo: repoName})
})
.then(response => {
if (response.ok) {
// Si la requête s'est bien déroulée, actualiser la liste des dépôts surveillés
refreshWatchedRepos();
} else {
throw new Error('Erreur lors de l\'ajout du dépôt');
}
})
.catch(error => {
console.error('Error:', error);
});
});
function refreshWatchedRepos() {
fetch('/watched_repos')
.then(response => response.json())
.then(data => {
const watchedReposList = document.getElementById('watchedReposList');
// Vider la liste actuelle
watchedReposList.innerHTML = '';
// Ajouter chaque dépôt surveillé à la liste
data.forEach(repo => {
const listItem = document.createElement('li');
listItem.textContent = repo;
watchedReposList.appendChild(listItem);
});
})
.catch(error => {
console.error('Error:', error);
});
}
// Appeler la fonction pour charger les dépôts surveillés au chargement de la page
refreshWatchedRepos();