Compare commits

...

31 Commits
v1.1 ... v1.4.2

Author SHA1 Message Date
b958689318 Fix CI 2024-10-22 10:11:30 +02:00
8800902bf1 Fix CI 2024-10-22 10:10:55 +02:00
8f50debb0a Fix CI 2024-10-22 10:10:12 +02:00
c55b3f871e Fix CI 2024-10-22 10:08:29 +02:00
63594b910f Fix CI 2024-10-22 10:03:46 +02:00
6297ce14fd Update changelog 2024-10-22 10:01:51 +02:00
Félix MARQUET
7a48c3da50 Merge pull request #1 from BreizhHardware/dev
V1.4 release
2024-10-22 09:50:26 +02:00
7c2b4e545c Update changelog 2024-10-22 09:49:37 +02:00
d218c7a0bc Add import for json 2024-10-22 09:46:01 +02:00
694bfcaf6b Add docker login possibility 2024-10-22 09:38:53 +02:00
Félix MARQUET
b11bc64e52 Fix 2024-10-21 15:00:13 +00:00
Félix MARQUET
d796d5b24f Remove migration from entrypoint 2024-10-21 16:44:11 +02:00
Félix MARQUET
0be8d008c5 Fix entrypoint persmisson 2024-10-21 16:41:25 +02:00
Félix MARQUET
a14cc1848f Edit CI 2024-10-21 16:26:46 +02:00
Félix MARQUET
350ad9bf6a Update NGINX config with the new route 2024-10-21 16:17:13 +02:00
Félix MARQUET
76de8af42b Change the temp changelog 2024-10-21 16:15:52 +02:00
Félix MARQUET
3cfa54248f Add docker-hub compatibility 2024-10-21 16:15:01 +02:00
a270978728 Add remove repos button and change background color 2024-06-27 14:22:15 +02:00
2a7305a4cf Emergency PUSH 2024-03-05 13:00:23 +01:00
f5fc6e38da Add date and time of the release (use full when adding a new repo) 2024-03-05 12:51:15 +01:00
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
12 changed files with 736 additions and 41 deletions

77
.github/workflows/create_release.yml vendored Normal file
View File

@@ -0,0 +1,77 @@
name: Docker Build and Release
on:
push:
branches:
- main
jobs:
build-and-push-on-docker-hub:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ secrets.DOCKER_USERNAME }}/github-ntfy:latest
release-on-github:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Get the latest tag
id: get_latest_tag
run: echo "latest_tag=$(git describe --tags `git rev-list --tags --max-count=1`)" >> $GITHUB_ENV
- name: Increment version
id: increment_version
run: |
latest_tag=${{ env.latest_tag }}
if [ -z "$latest_tag" ]; then
new_version="v1.4.2"
else
IFS='.' read -r -a version_parts <<< "${latest_tag#v}"
new_version="v${version_parts[0]}.$((version_parts[1] + 1)).0"
fi
echo "new_version=$new_version" >> $GITHUB_ENV
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.TOKEN }}
with:
tag_name: ${{ env.new_version }}
release_name: Release ${{ env.new_version }}
draft: false
prerelease: false
- name: List files for debugging
run: ls -la
- name: Upload changelog
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./CHANGELOG.MD
asset_name: CHANGELOG.MD
asset_content_type: text/markdown

6
.gitignore vendored
View File

@@ -400,3 +400,9 @@ _deps
.nfs*
# End of https://www.toptal.com/developers/gitignore/api/c++,linux,clion,cmake,clion+all
docker-compose.yml
github-ntfy/
github-ntfy/*
*.db

9
CHANGELOG.MD Normal file
View File

@@ -0,0 +1,9 @@
**New features**:
- Change database struct to be able to handle docker hub repos
- Now you can add docker hub repo to your watch list
- You can connect your docker account to bypass the rate limit of the docker hub API
**Breaking changes**:
- Change in the docker-compose file, you need to add the DOCKER_USERNAME and DOCKER_PASSWORD environment variables
**Full Changelog**: https://github.com/BreizhHardware/ntfy_alerts/compare/v1.3...v1.4

View File

@@ -1,18 +1,31 @@
FROM python:3.11.8-alpine3.19
LABEL maintainer="BreizhHardware"
LABEL version_number="1.4"
ADD ntfy.py /
ADD ntfy_api.py /
ADD requirements.txt /
ADD entrypoint.sh /
RUN apk add --no-cache sqlite-dev sqlite-libs gcc musl-dev
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
RUN chmod 700 /entrypoint.sh
# Définir les variables d'environnement pour username et password
ENV USERNAME="" \
PASSWORD="" \
NTFY_URL="" \
GHNTFY_TIMEOUT="3600" \
GHREPO=""
GHNTFY_TOKEN="" \
DOCKER_USERNAME="" \
DOCKER_PASSWORD="" \
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,10 +1,13 @@
# 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.
It can aloso watch Docker Hub repos and do the same as github.
## Utilisation:
auth and ntfy_url are required to be set as environment variables.
@@ -18,7 +21,6 @@ python ntfy.py
## Docker:
If you want to use the docker image you can use the following docker-compose file:
````yaml
version: '3'
services:
github-ntfy:
image: breizhhardware/github-ntfy:latest
@@ -28,16 +30,25 @@ services:
- PASSWORD=password # Required
- NTFY_URL=ntfy_url # Required
- GHNTFY_TIMEOUT=timeout # Default is 3600 (1 hour)
- GHREPO=["username/repo1", "username/repo2"] # Default is empty
- GHNTFY_TOKEN= # Default is empty (Github token)
- DOCKER_USERNAME= # Default is empty (Docker Hub username)
- DOCKER_PASSWORD= # Default is empty (Docker Hub password)
volumes:
- /path/to/github-ntfy:/github-ntfy/
ports:
- 80:80
restart: unless-stopped
````
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
- [x] Add the watched repos list as a parameter
- [x] Add the application version as a database
- [ ] Add the watched repos list as a web interface
- [x] Add the watched repos list as a web interface
- [x] Add Docker Hub compatibility
- [ ] Rework of the web interface
# Bash setup-notify.sh
## Description:
This script is used to setup the ntfy notification system on ssh login for a new server.

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

76
index.html Normal file
View File

@@ -0,0 +1,76 @@
<!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-stone-500 to-green-700">
<h1 class="text-4xl font-semibold leading-10 text-gray-900 text-center">Github-Ntfy</h1>
<div class="flex flex-row gap-2 justify-center items-center my-2 h-screen">
<div class="flex flex-col gap-2 justify-center items-center my-2 h-screen border-double border-2 border-white p-2" id="github">
<h1>Add a github 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 Github Repositories</h2>
<ul id="watchedReposList" class="mt-4">
<!-- Dynamically populated with JavaScript -->
</ul>
</div>
</div>
<div class="flex flex-col gap-2 justify-center items-center my-2 h-screen border-double border-2 border-white p-2" id="docker">
<h1>Add a docker repo</h1>
<form id="addDockerRepoForm">
<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 docker 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">hub.docker.com/r/</span>
<input type="text" name="dockerRepo" id="dockerRepo" autocomplete="dockerRepo" 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/github-ntfy">
</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 Docker Repositories</h2>
<ul id="watchedDockerReposList" class="mt-4">
<!-- Dynamically populated with JavaScript -->
</ul>
</div>
</div>
</div>
<p class="font-semibold leading-10 text-gray-900 text-center">I know this web interface is awfull but I'm not a web designer ^^.</p>
</body>
</html>

60
nginx.conf Normal file
View File

@@ -0,0 +1,60 @@
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;
}
location /delete_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 /app_docker_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_docker_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;
}
location /delete_docker_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;
}
}
}

192
ntfy.py
View File

@@ -2,10 +2,11 @@ import requests
import time
import os
import logging
import json
import sqlite3
import subprocess
import json
# Configurer le logger
# Configuring the logger
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
@@ -14,24 +15,79 @@ github_headers = {}
if github_token:
github_headers['Authorization'] = f"token {github_token}"
repo_list_env = os.environ.get('GHREPO')
watched_repos_list = json.loads(repo_list_env) if repo_list_env else []
docker_username = os.environ.get('DOCKER_USERNAME')
docker_password = os.environ.get('DOCKER_PASSWORD')
if not watched_repos_list:
logger.error("Aucun dépôt n'a été spécifié. Veuillez spécifier les dépôts à surveiller dans l'environnement GHREPO")
exit(1)
# Connexion à la base de données pour stocker les versions précédentes
db_path = 'ghntfy_versions.db'
conn = sqlite3.connect(db_path)
def create_dockerhub_token(username, password):
url = "https://hub.docker.com//v2/users/login"
headers = {"Content-Type": "application/json"}
data = json.dumps({"username": username, "password": password})
response = requests.post(url, headers=headers, data=data)
if response.status_code == 200:
token = response.json().get("token")
if token:
return token
else:
logger.error("Failed to get Docker Hub token.")
else:
logger.error(f"Failed to get Docker Hub token. Status code: {response.status_code}")
return None
docker_token = create_dockerhub_token(docker_username, docker_password)
docker_header = {}
if docker_token:
docker_header['Authorization'] = f"Bearer {docker_token}"
# Connecting to the database to store previous versions
conn = sqlite3.connect('/github-ntfy/ghntfy_versions.db', check_same_thread=False)
cursor = conn.cursor()
# Création de la table si elle n'existe pas
# 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("Démarrage de la surveillance des versions...")
cursor.execute('''CREATE TABLE IF NOT EXISTS docker_versions
(repo TEXT PRIMARY KEY, digest 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()
cursor2.execute('''CREATE TABLE IF NOT EXISTS docker_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 get_docker_watched_repos():
cursor2.execute("SELECT * FROM docker_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"])
def get_latest_releases(watched_repos):
@@ -42,17 +98,38 @@ def get_latest_releases(watched_repos):
if response.status_code == 200:
release_info = response.json()
changelog = get_changelog(repo)
release_date = release_info.get('published_at', 'Release date not available')
releases.append({
"repo": repo,
"name": release_info["name"],
"tag_name": release_info["tag_name"],
"html_url": release_info["html_url"],
"changelog": changelog
"changelog": changelog,
"published_at": release_date
})
else:
logger.error(f"Failed to fetch release info for {repo}")
return releases
def get_latest_docker_releases(watched_repos):
releases = []
for repo in watched_repos:
url = f"https://hub.docker.com/v2/repositories/{repo_name}/tags/latest"
response = requests.get(url, headers=docker_header)
if response.status_code == 200:
release_info = response.json()
release_date=release_info["last_upated"]
digest = release_date["digest"]
releases.append({
"repo": repo,
"digest": digest,
"html_url": "https://hub.docker.com/r/" + repo,
"published_at": release_date
})
else:
logger.error(f"Failed to fetch Docker Hub info for {repo_name}")
return releases
def get_changelog(repo):
url = f"https://api.github.com/repos/{repo}/releases"
@@ -60,28 +137,30 @@ def get_changelog(repo):
if response.status_code == 200:
releases = response.json()
if releases:
latest_release = releases[0]
if 'body' in latest_release:
return latest_release['body']
return "Changelog non disponible"
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):
def github_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
changelog = release['changelog'] # Obtenir le changelog
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
release_date = release['published_at'] # Getting the release date
release_date = release_date.replace("T", " ").replace("Z", "") # Formatting the release date
# Vérifier si la version a changé depuis la dernière fois
# 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"La version de {app_name} n'a pas changé. Pas de notification envoyée.")
continue # Passer à l'application suivante
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}\nChangelog:\n{changelog}\n{app_url}"
# Mettre à jour la version précédente pour cette application
message = f"New version: {version_number}\nFor: {app_name}\nPublished on: {release_date}\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()
@@ -90,17 +169,52 @@ def send_to_ntfy(releases, auth, url):
"Authorization": f"Basic {auth}",
"Title": f"New version for {app_name}",
"Priority": "urgent",
"Content-Type": "text/plain",
"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}")
def docker_send_to_ntfy(releases, auth, url):
for release in releases:
app_name = release['repo'].split('/')[-1] # Getting the application name from the repo
digest_number = release['digest']
app_url = release['html_url'] # Getting the application URL
release_date = release['published_at'] # Getting the release date
release_date = release_date.replace("T", " ").replace("Z", "") # Formatting the release date
# Checking if the version has changed since the last time
cursor.execute("SELECT digest FROM docker_versions WHERE repo=?", (app_name,))
previous_digest = cursor.fetchone()
if previous_digest and previous_digest[0] == digest_number:
logger.info(f"The digest of {app_name} has not changed. No notification sent.")
continue # Move on to the next application
message = f"New version: {digest_number}\nFor: {app_name}\nPublished on: {release_date}\nChangelog:\n{changelog}\n{app_url}"
# Updating the previous digest for this application
cursor.execute("INSERT OR REPLACE INTO docker_versions (repo, digest, changelog) VALUES (?, ?, ?)",
(app_name, digest_number, changelog))
conn.commit()
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 sent to Ntfy for {app_name}")
continue
else:
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')
@@ -108,11 +222,19 @@ if __name__ == "__main__":
if auth and ntfy_url:
while True:
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
github_watched_repos_list = get_watched_repos()
github_latest_release = get_latest_releases(github_watched_repos_list)
if github_latest_release:
github_send_to_ntfy(github_latest_release, auth, ntfy_url)
docker_watched_repos_list = get_docker_watched_repos()
docker_latest_release = get_latest_docker_releases(docker_watched_repos_list)
if docker_latest_release:
docker_send_to_ntfy(docker_latest_release, auth, ntfy_url)
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")

158
ntfy_api.py Normal file
View File

@@ -0,0 +1,158 @@
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 GitHub 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 GitHub repo {repo} as been added to the watched repos."})
finally:
# Fermer la connexion à la base de données
close_db_connection(conn)
@app.route('/app_docker_repo', methods=['POST'])
def app_docker_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 docker_watched_repos WHERE repo=?", (repo,))
existing_repo = cursor.fetchone()
if existing_repo:
return jsonify({"error": f"The Docker repo {repo} is already in the database."}), 409
# Ajouter le dépôt à la base de données
cursor.execute("INSERT INTO docker_watched_repos (repo) VALUES (?)", (repo,))
conn.commit()
return jsonify({"message": f"The Docker 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)
@app.route('/watched_docker_repos', methods=['GET'])
def get_watched_docker_repos():
db = get_db_connection()
cursor = db.cursor()
cursor.execute("SELECT repo FROM docker_watched_repos")
watched_repos = [repo[0] for repo in cursor.fetchall()]
cursor.close()
db.close()
return jsonify(watched_repos)
@app.route('/delete_repo', methods=['POST'])
def delete_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 dans la base de données
cursor.execute("SELECT * FROM watched_repos WHERE repo=?", (repo,))
existing_repo = cursor.fetchone()
if not existing_repo:
return jsonify({"error": f"The GitHub repo {repo} is not in the database."}), 404
# Supprimer le dépôt de la base de données
cursor.execute("DELETE FROM watched_repos WHERE repo=?", (repo,))
conn.commit()
return jsonify({"message": f"The GitHub repo {repo} as been deleted from the watched repos."})
finally:
# Fermer la connexion à la base de données
close_db_connection(conn)
@app.route('/delete_docker_repo', methods=['POST'])
def delete_docker_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 dans la base de données
cursor.execute("SELECT * FROM docker_watched_repos WHERE repo=?", (repo,))
existing_repo = cursor.fetchone()
if not existing_repo:
return jsonify({"error": f"The Docker repo {repo} is not in the database."}), 404
# Supprimer le dépôt de la base de données
cursor.execute("DELETE FROM docker_watched_repos WHERE repo=?", (repo,))
conn.commit()
return jsonify({"message": f"The Docker repo {repo} as been deleted from the watched repos."})
finally:
# Fermer la connexion à la base de données
close_db_connection(conn)
if __name__ == "__main__":
app.run(debug=False)

View File

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

158
script.js Normal file
View File

@@ -0,0 +1,158 @@
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);
});
});
document.getElementById('addDockerRepoForm').addEventListener('submit', function(event) {
event.preventDefault();
let repoName = document.getElementById('dockerRepo').value;
fetch('/app_docker_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');
const repoName = document.createElement('span');
repoName.textContent = repo;
repoName.className = 'repo-name';
listItem.appendChild(repoName);
const deleteButton = document.createElement('button');
deleteButton.textContent = ' X';
deleteButton.className = 'delete-btn text-red-500 ml-2';
deleteButton.addEventListener('click', () => {
// Remove the repo from the watched repos
// This is a placeholder. Replace it with your actual code to remove the repo from the watched repos.
removeRepoFromWatchedRepos(repo);
// Remove the repo from the DOM
listItem.remove();
});
listItem.appendChild(deleteButton);
watchedReposList.appendChild(listItem);
});
})
.catch(error => {
console.error('Error:', error);
});
fetch('/watched_docker_repos')
.then(response => response.json())
.then(data => {
const watchedDockerReposList = document.getElementById('watchedDockerReposList');
// Vider la liste actuelle
watchedDockerReposList.innerHTML = '';
// Ajouter chaque dépôt surveillé à la liste
data.forEach(repo => {
const listItem = document.createElement('li');
const repoName = document.createElement('span');
repoName.textContent = repo;
repoName.className = 'repo-name';
listItem.appendChild(repoName);
const deleteButton = document.createElement('button');
deleteButton.textContent = ' X';
deleteButton.className = 'delete-btn text-red-500 ml-2';
deleteButton.addEventListener('click', () => {
// Remove the repo from the watched repos
// This is a placeholder. Replace it with your actual code to remove the repo from the watched repos.
removeDockerRepoFromWatchedRepos(repo);
// Remove the repo from the DOM
listItem.remove();
});
listItem.appendChild(deleteButton);
watchedDockerReposList.appendChild(listItem);
});
})
.catch(error => {
console.error('Error:', error);
});
}
function removeRepoFromWatchedRepos(repo) {
fetch('/delete_repo', {
method: 'POST',
headers: {
'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/json'
},
body: JSON.stringify({repo: repo})
})
.then(response => {
if (!response.ok) {
throw new Error('Erreur lors de la suppression du dépôt');
}
})
.catch(error => {
console.error('Error:', error);
});
}
function removeDockerRepoFromWatchedRepos(repo) {
fetch('/delete_docker_repo', {
method: 'POST',
headers: {
'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/json'
},
body: JSON.stringify({repo: repo})
})
.then(response => {
if (!response.ok) {
throw new Error('Erreur lors de la suppression du dépôt');
}
})
.catch(error => {
console.error('Error:', error);
});
}
// Appeler la fonction pour charger les dépôts surveillés au chargement de la page
refreshWatchedRepos();