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:
6
.env.default
Normal file
6
.env.default
Normal 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
37
Dockerfile
Normal 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
7
LICENCE.md
Normal 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
96
README.md
Normal 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
|
||||
```
|
||||
BIN
imgs/gotify-to-ntfy-proxy-1.png
Normal file
BIN
imgs/gotify-to-ntfy-proxy-1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
95
index.js
Normal file
95
index.js
Normal 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
51
ntfy.js
Normal 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
22
package.json
Normal 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
7
topics.default.js
Normal file
@@ -0,0 +1,7 @@
|
||||
const topics = {
|
||||
your_topic_name: {
|
||||
ntfy_token: 'tk_yoursupersecretntfytoken'
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = topics
|
||||
Reference in New Issue
Block a user