gotify to ntfy relay server intended to be used with auth tokens from ntfy,

proxmox v8+ Gotify to Ntfy proxy
This commit is contained in:
LevantinLynx
2024-06-01 03:51:36 +02:00
parent e8ab3c3d07
commit fe47d9d357
10 changed files with 2884 additions and 0 deletions

6
.env.default Normal file
View File

@@ -0,0 +1,6 @@
NODE_ENV=production
RELAY_HOST_IP=0.0.0.0
RELAY_PORT=8008
NTFY_SERVER=https://ntfy.sh

37
Dockerfile Normal file
View File

@@ -0,0 +1,37 @@
FROM node:lts-alpine3.20 as builder
USER node
RUN mkdir -p /home/node/app
WORKDIR /home/node/app
COPY --chown=node ./ntfy.js /home/node/app
COPY --chown=node ./index.js /home/node/app
COPY --chown=node ./package.json /home/node/app
COPY --chown=node ./yarn.lock /home/node/app
ENV NODE_ENV=production
RUN yarn --production --frozen-lockfile
RUN yarn cache clean
FROM node:lts-alpine3.20 as final
USER node
RUN mkdir -p /home/node/app
WORKDIR /home/node/app
COPY --from=builder --chown=node /home/node/app/node_modules ./node_modules
COPY --from=builder --chown=node /home/node/app/index.js .
COPY --from=builder --chown=node /home/node/app/ntfy.js .
COPY --from=builder --chown=node /home/node/app/package.json .
COPY --chown=node ./README.md /home/node/app
COPY --chown=node ./LICENCE.md /home/node/app
ENV NODE_ENV=production
EXPOSE 8008
CMD ["yarn", "start"]

7
LICENCE.md Normal file
View File

@@ -0,0 +1,7 @@
## Copyright 2024 LevantinLynx
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

96
README.md Normal file
View File

@@ -0,0 +1,96 @@
# Gotify to Ntfy Relay Proxy
This is intended to be used with Proxmox v8+ to get Ntfy integration with the "new" notification system. At time of writing there is no native Ntfy integration in Proxmox.
## Proxmox settings
Go to Datacenter > Notifications > Add > Gotify
In this example the proxy is running on "http://10.0.0.6:8008" topic on Ntfy is "your_topic_name" and the notify token is "tk_yoursupersecretntfytoken".
<img src="imgs/gotify-to-ntfy-proxy-1.png" alt="How to add Gotiy to Ntfy Proxy in Proxmox" width="633" style="max-width:100%;">
After adding the proxy create a "Notification Matcher" or edit the default one.
The proxy also works with Proxmox Backup Server since it uses the same notification system as Proxmox.
## Example .env file
```env
NODE_ENV=production
RELAY_HOST_IP=0.0.0.0
RELAY_PORT=8008
NTFY_SERVER=https://ntfy.sh
```
## Example topic.js file
```javascript
const topics = {
you_topic_name_one: {
ntfyToken: 'tk_yoursupersecretntfytoken'
},
you_topic_name_two: {
ntfyToken: 'tk_yoursupersecretntfytoken'
},
}
module.exports = topics
```
## Run local
Ensure node v20+ and yarn 1.22+ are installed
```bash
git clone https://github.com/LevantinLynx/gotify-to-ntfy-proxy.git
cd gotify-to-ntfy-proxy
cp .env.defaut .env
# Edit .env file
cp topics.defaut.js topics.js
# Edit tocics.js file
# MAKE SURE THIS IS SAVE! IT CONTAINS YOUR NTFY TOKEN/S.
yarn install
yarn start
```
## Docker
```bash
docker run \
-p 8008:8008 \
-v /path/to/your/.env:/home/node/app/.env \
-v /path/to/your/topics.js:/home/node/app/topics.js \
--restart unless-stopped \
levantinlynx/gotify-to-ntfy-proxy:latest
```
Optionally you can also pass the environment variables instead of using the .env file or use docker compose.
```docker
version: '3.9'
services:
gotify-to-ntfy-proxy:
image: 'levantinlynx/gotify-to-ntfy-proxy:latest'
restart: unless-stopped
volumes:
- '/path/to/your/topics.js:/home/node/app/topics.js'
ports:
- '8008:8008'
environment:
- RELAY_HOST_IP=0.0.0.0
- RELAY_PORT=8008
- NTFY_SERVER=https://notify.sh
```
If no environment file or variables are provided, the service will start with the following default values:
```env
NODE_ENV=production
RELAY_HOST_IP=0.0.0.0
RELAY_PORT=8008
NTFY_SERVER=https://ntfy.sh
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

95
index.js Normal file
View File

@@ -0,0 +1,95 @@
require('dotenv').config()
process.env.RELAY_PORT ??= 8008
process.env.RELAY_HOST_IP ??= '127.0.0.1'
const topics = require('./topics.js')
const express = require('express')
const multer = require('multer')
const bodyParser = require('body-parser')
const { sendNotificationToNtfyServer } = require('./ntfy.js')
const { mainStory, config } = require('storyboard')
if (process.env.NODE_ENV === 'dev') {
config({ filter: '*:DEBUG' })
mainStory.info('ENVIRONMENT', 'Running in DEVELOPMENT mode!')
} else {
config({ filter: '*:INFO' })
mainStory.info('ENVIRONMENT', 'Running in PRODUCTION mode!')
}
require('storyboard-preset-console')
const app = express()
const upload = multer()
app.use(upload.none())
app.use(bodyParser.json())
app.post('/message', upload.none(), bodyParser.json(), async (req, res) => {
const token = req.query.token || req.headers['x-gotify-key'] || req.headers.authorization ? req.headers.authorization.replace('Bearer ', '') : ''
mainStory.debug('MESSAGE', 'Gotify message recieved:', {
attach: { ...req.body, token },
attachLevel: 'debug'
})
const priority = ['min', 'low', 'default', 'high', 'max']
const topic = token.split('/')[0]
const ntfyToken = token.split('/')[1]
if (!topic || !topics[topic]) {
const error = {
error: 'Bad Request',
errorCode: 400,
errorDescription: 'No matching topic found! Please ensure the topic is defined in topic.js and provide a token formated: topic/ntfyToken'
}
mainStory.error('NOTIFICATION', error.error, {
attach: error,
attachLevel: 'error'
})
return res.json(error).status(400)
}
if (topics[topic].ntfyToken !== ntfyToken) {
const error = {
error: 'Unauthorized',
errorCode: 401,
errorDescription: 'Please provide a token formated: topic/ntfyToken'
}
mainStory.error('NOTIFICATION', error.error, {
attach: error,
attachLevel: 'error'
})
return res.json(error).status(401)
}
if (!req.body.message) {
const error = {
error: 'Bad Request',
errorCode: 400,
errorDescription: 'Please provide a message.'
}
mainStory.error('NOTIFICATION', error.error, {
attach: error,
attachLevel: 'error'
})
return res.json(error).status(400)
}
const notification = {
topic: token.split('/')[0],
title: req.body.title,
content: req.body.message,
priority: priority[req.body.priority - 1 || 3] || 'default',
token: ntfyToken
}
const msg = await sendNotificationToNtfyServer(notification)
res.json({
id: msg.id,
appid: 1,
message: req.body.message,
title: req.body.title,
priority: req.body.priority,
date: new Date().toISOString()
})
})
app.listen(process.env.RELAY_PORT, process.env.RELAY_HOST_IP, () => {
mainStory.info('SERVER', `Relay Server is listening on http://${process.env.RELAY_HOST_IP || '127.0.0.1'}:${process.env.RELAY_PORT || 8008}`)
})

51
ntfy.js Normal file
View File

@@ -0,0 +1,51 @@
const { mainStory } = require('storyboard')
require('storyboard-preset-console')
const axios = require('axios')
process.env.NTFY_SERVER ??= 'https://ntfy.sh'
/**
* Send a notification to a Ntfy server
* @param {Object} notification
* @param {String} notification.topic - Notification topic
* @param {String} notification.title - Notification title
* @param {String} notification.content - Notification content text
* @param {String} [notification.token] - Auth token for the ntfy server
* @param {String} [notification.priority] - Ntfy notification priority
*/
async function sendNotificationToNtfyServer (notification) {
try {
notification.topic ??= 'test'
notification.title ??= 'Gotify to Ntfy Relay Server'
notification.priority ??= 'default'
notification.content ??= 'No notification content provided!'
const options = {
method: 'post',
url: `${process.env.NTFY_SERVER}/${notification.topic}`,
headers: {
Title: notification.title,
Priority: notification.priority
},
data: notification.content
}
if (notification.token) options.headers.Authorization = `Bearer ${notification.token}`
const result = await axios(options)
if (result.data) {
mainStory.info('NTFY', `Sent to "${process.env.NTFY_SERVER}/${notification.topic}":`, {
attach: result.data,
attachLevel: 'info'
})
return result.data
}
} catch (err) {
mainStory.error('NTFY', err.message, {
attach: err,
attachLevel: 'error'
})
}
}
module.exports = {
sendNotificationToNtfyServer
}

22
package.json Normal file
View File

@@ -0,0 +1,22 @@
{
"name": "gotify-to-ntfy-proxy",
"version": "1.0.0",
"license": "MIT",
"scripts": {
"start": "node index.js",
"test": "standard \"index.js\" \"ntfy.js\" --fix --verbose | snazzy"
},
"dependencies": {
"axios": "^1.7.2",
"body-parser": "^1.20.2",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"multer": "^1.4.5-lts.1",
"storyboard": "^3.3.2",
"storyboard-preset-console": "^3.3.2"
},
"devDependencies": {
"snazzy": "^9.0.0",
"standard": "^17.1.0"
}
}

7
topics.default.js Normal file
View File

@@ -0,0 +1,7 @@
const topics = {
your_topic_name: {
ntfy_token: 'tk_yoursupersecretntfytoken'
}
}
module.exports = topics

2563
yarn.lock Normal file

File diff suppressed because it is too large Load Diff