Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fff535ca1a | ||
|
|
26390b9ad1 | ||
|
|
cc752cf797 | ||
|
|
4d48c5dc34 |
13
README.md
13
README.md
@@ -1,14 +1,22 @@
|
|||||||

|

|
||||||
|
|
||||||
# ntfy - simple HTTP-based pub-sub
|
# ntfy.sh | simple HTTP-based pub-sub
|
||||||
|
|
||||||
**ntfy** (pronounce: *notify*) is a simple HTTP-based [pub-sub](https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern) notification service.
|
**Ntfy** (pronounce: *notify*) is a simple HTTP-based [pub-sub](https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern) notification service.
|
||||||
It allows you to **send notifications to your phone or desktop via scripts** from any computer, entirely **without signup or cost**.
|
It allows you to **send notifications to your phone or desktop via scripts** from any computer, entirely **without signup or cost**.
|
||||||
It's also open source (as you can plainly see) if you want to run your own.
|
It's also open source (as you can plainly see) if you want to run your own.
|
||||||
|
|
||||||
I run a free version of it at **[ntfy.sh](https://ntfy.sh)**, and there's an [Android app](https://play.google.com/store/apps/details?id=io.heckel.ntfy)
|
I run a free version of it at **[ntfy.sh](https://ntfy.sh)**, and there's an [Android app](https://play.google.com/store/apps/details?id=io.heckel.ntfy)
|
||||||
too.
|
too.
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<img src="server/static/img/screenshot-curl.png" height="180">
|
||||||
|
<img src="server/static/img/screenshot-web-detail.png" height="180">
|
||||||
|
<img src="server/static/img/screenshot-phone-main.jpg" height="180">
|
||||||
|
<img src="server/static/img/screenshot-phone-detail.jpg" height="180">
|
||||||
|
<img src="server/static/img/screenshot-phone-notification.jpg" height="180">
|
||||||
|
</p>
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
### Publishing messages
|
### Publishing messages
|
||||||
@@ -183,3 +191,4 @@ Third party libraries and resources:
|
|||||||
* [GoReleaser](https://goreleaser.com/) (MIT) is used to create releases
|
* [GoReleaser](https://goreleaser.com/) (MIT) is used to create releases
|
||||||
* [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) (MIT) is used to provide the persistent message cache
|
* [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) (MIT) is used to provide the persistent message cache
|
||||||
* [Firebase Admin SDK](https://github.com/firebase/firebase-admin-go) (Apache 2.0) is used to send FCM messages
|
* [Firebase Admin SDK](https://github.com/firebase/firebase-admin-go) (Apache 2.0) is used to send FCM messages
|
||||||
|
* [Lightbox with vanilla JS](https://yossiabramov.com/blog/vanilla-js-lightbox)
|
||||||
|
|||||||
7
examples/ssh-login-alert/ntfy-ssh-login.sh
Normal file
7
examples/ssh-login-alert/ntfy-ssh-login.sh
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# This is a PAM script hook that shows how to notify you when
|
||||||
|
# somebody logs into your server. Place at /usr/local/bin/ntfy-ssh-login.sh (with chmod +x!).
|
||||||
|
|
||||||
|
if [ "${PAM_TYPE}" = "open_session" ]; then
|
||||||
|
echo -en "\u26A0\uFE0F SSH login to $(hostname): ${PAM_USER} from ${PAM_RHOST}" | curl -T- ntfy.sh/alerts
|
||||||
|
fi
|
||||||
8
examples/ssh-login-alert/pam_sshd
Normal file
8
examples/ssh-login-alert/pam_sshd
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# PAM config file snippet
|
||||||
|
#
|
||||||
|
# Put this snippet AT THE END of the file /etc/pam.d/sshd
|
||||||
|
# See https://geekthis.net/post/run-scripts-after-ssh-authentication/ for details.
|
||||||
|
|
||||||
|
# (lots of stuff here ...)
|
||||||
|
|
||||||
|
session optional pam_exec.so /usr/local/bin/ntfy-ssh-login.sh
|
||||||
@@ -41,10 +41,21 @@
|
|||||||
It allows you to send notifications <a href="https://play.google.com/store/apps/details?id=io.heckel.ntfy">to your phone</a> or desktop via scripts from any computer,
|
It allows you to send notifications <a href="https://play.google.com/store/apps/details?id=io.heckel.ntfy">to your phone</a> or desktop via scripts from any computer,
|
||||||
entirely <b>without signup or cost</b>. It's also <a href="https://github.com/binwiederhier/ntfy">open source</a> if you want to run your own.
|
entirely <b>without signup or cost</b>. It's also <a href="https://github.com/binwiederhier/ntfy">open source</a> if you want to run your own.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<div id="screenshots">
|
||||||
|
<a href="static/img/screenshot-curl.png"><img src="static/img/screenshot-curl.png"/></a>
|
||||||
|
<a href="static/img/screenshot-web-detail.png"><img src="static/img/screenshot-web-detail.png"/></a>
|
||||||
|
<span class="nowrap">
|
||||||
|
<a href="static/img/screenshot-phone-main.jpg"><img src="static/img/screenshot-phone-main.jpg"/></a>
|
||||||
|
<a href="static/img/screenshot-phone-detail.jpg"><img src="static/img/screenshot-phone-detail.jpg"/></a>
|
||||||
|
<a href="static/img/screenshot-phone-notification.jpg"><img src="static/img/screenshot-phone-notification.jpg"/></a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
There are many ways to use ntfy. You can send yourself messages for all sorts of things: When a long process finishes or fails (a backup, a long rsync job, ...),
|
There are many ways to use Ntfy. You can send yourself messages for all sorts of things: When a long process finishes or fails (a backup, a long rsync job, ...),
|
||||||
or to notify yourself when somebody logs into your server(s). Or you may want to use it in your own app to distribute messages to subscribed clients.
|
or to notify yourself when somebody logs into your server(s). Or you may want to use it in your own app to distribute messages to subscribed clients.
|
||||||
Endless possibilities 😀.
|
Endless possibilities 😀. Be sure to check out the <a href="https://github.com/binwiederhier/ntfy/tree/main/examples">example on GitHub</a>!
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2>Publishing messages</h2>
|
<h2>Publishing messages</h2>
|
||||||
@@ -239,6 +250,7 @@
|
|||||||
<div id="detailEventsList"></div>
|
<div id="detailEventsList"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="lightbox" class="lightbox"></div>
|
||||||
<script src="static/js/app.js"></script>
|
<script src="static/js/app.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -95,6 +95,83 @@ code {
|
|||||||
color: #666;
|
color: #666;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Screenshots */
|
||||||
|
|
||||||
|
#screenshots {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#screenshots img {
|
||||||
|
height: 190px;
|
||||||
|
margin: 3px;
|
||||||
|
border-radius: 5px;
|
||||||
|
filter: drop-shadow(2px 2px 2px #ddd);
|
||||||
|
}
|
||||||
|
|
||||||
|
#screenshots .nowrap {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Lightbox; thanks to https://yossiabramov.com/blog/vanilla-js-lightbox */
|
||||||
|
|
||||||
|
.lightbox {
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
position: fixed;
|
||||||
|
left:0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: -1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.15s ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightbox.show {
|
||||||
|
background-color: rgba(0,0,0, 0.75);
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightbox img {
|
||||||
|
max-width: 90%;
|
||||||
|
max-height: 90%;
|
||||||
|
filter: drop-shadow(5px 5px 10px #222);
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightbox .close-lightbox {
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
top: 30px;
|
||||||
|
right: 30px;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightbox .close-lightbox::after,
|
||||||
|
.lightbox .close-lightbox::before {
|
||||||
|
content: '';
|
||||||
|
width: 3px;
|
||||||
|
height: 20px;
|
||||||
|
background-color: #ddd;
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 5px;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightbox .close-lightbox::before {
|
||||||
|
transform: rotate(-45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightbox .close-lightbox:hover::after,
|
||||||
|
.lightbox .close-lightbox:hover::before {
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
/* Subscribe box */
|
/* Subscribe box */
|
||||||
|
|
||||||
button {
|
button {
|
||||||
|
|||||||
BIN
server/static/img/screenshot-curl.png
Normal file
BIN
server/static/img/screenshot-curl.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 253 KiB |
BIN
server/static/img/screenshot-phone-add.jpg
Normal file
BIN
server/static/img/screenshot-phone-add.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 227 KiB |
BIN
server/static/img/screenshot-phone-detail.jpg
Normal file
BIN
server/static/img/screenshot-phone-detail.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 225 KiB |
BIN
server/static/img/screenshot-phone-main.jpg
Normal file
BIN
server/static/img/screenshot-phone-main.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 128 KiB |
BIN
server/static/img/screenshot-phone-notification.jpg
Normal file
BIN
server/static/img/screenshot-phone-notification.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 224 KiB |
BIN
server/static/img/screenshot-web-detail.png
Normal file
BIN
server/static/img/screenshot-web-detail.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 113 KiB |
@@ -32,6 +32,9 @@ const detailNoNotifications = document.getElementById("detailNoNotifications");
|
|||||||
const detailCloseButton = document.getElementById("detailCloseButton");
|
const detailCloseButton = document.getElementById("detailCloseButton");
|
||||||
const detailNotificationsDisallowed = document.getElementById("detailNotificationsDisallowed");
|
const detailNotificationsDisallowed = document.getElementById("detailNotificationsDisallowed");
|
||||||
|
|
||||||
|
/* Screenshots */
|
||||||
|
const lightbox = document.getElementById("lightbox");
|
||||||
|
|
||||||
const subscribe = (topic) => {
|
const subscribe = (topic) => {
|
||||||
if (Notification.permission !== "granted") {
|
if (Notification.permission !== "granted") {
|
||||||
Notification.requestPermission().then((permission) => {
|
Notification.requestPermission().then((permission) => {
|
||||||
@@ -203,6 +206,54 @@ const showNotificationDeniedError = () => {
|
|||||||
showError("You have blocked desktop notifications for this website. Please unblock them and refresh to use the web-based desktop notifications.");
|
showError("You have blocked desktop notifications for this website. Please unblock them and refresh to use the web-based desktop notifications.");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const showScreenshotOverlay = (e, el, index) => {
|
||||||
|
lightbox.classList.add('show');
|
||||||
|
document.addEventListener('keydown', nextScreenshotKeyboardListener);
|
||||||
|
return showScreenshot(e, index);
|
||||||
|
};
|
||||||
|
|
||||||
|
const showScreenshot = (e, index) => {
|
||||||
|
const actualIndex = resolveScreenshotIndex(index);
|
||||||
|
lightbox.innerHTML = '<div class="close-lightbox"></div>' + screenshots[actualIndex].innerHTML;
|
||||||
|
lightbox.querySelector('img').onclick = (e) => { return showScreenshot(e,actualIndex+1); };
|
||||||
|
currentScreenshotIndex = actualIndex;
|
||||||
|
e.stopPropagation();
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const nextScreenshot = (e) => {
|
||||||
|
return showScreenshot(e, currentScreenshotIndex+1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const previousScreenshot = (e) => {
|
||||||
|
return showScreenshot(e, currentScreenshotIndex-1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const resolveScreenshotIndex = (index) => {
|
||||||
|
if (index < 0) {
|
||||||
|
return screenshots.length - 1;
|
||||||
|
} else if (index > screenshots.length - 1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return index;
|
||||||
|
};
|
||||||
|
|
||||||
|
const hideScreenshotOverlay = (e) => {
|
||||||
|
lightbox.classList.remove('show');
|
||||||
|
document.removeEventListener('keydown', nextScreenshotKeyboardListener);
|
||||||
|
};
|
||||||
|
|
||||||
|
const nextScreenshotKeyboardListener = (e) => {
|
||||||
|
switch (e.keyCode) {
|
||||||
|
case 37:
|
||||||
|
previousScreenshot(e);
|
||||||
|
break;
|
||||||
|
case 39:
|
||||||
|
nextScreenshot(e);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// From: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
|
// From: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
|
||||||
async function* makeTextFileLineIterator(fileURL) {
|
async function* makeTextFileLineIterator(fileURL) {
|
||||||
const utf8Decoder = new TextDecoder('utf-8');
|
const utf8Decoder = new TextDecoder('utf-8');
|
||||||
@@ -248,6 +299,14 @@ detailCloseButton.onclick = () => {
|
|||||||
hideDetailView();
|
hideDetailView();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let currentScreenshotIndex = 0;
|
||||||
|
const screenshots = [...document.querySelectorAll("#screenshots a")];
|
||||||
|
screenshots.forEach((el, index) => {
|
||||||
|
el.onclick = (e) => { return showScreenshotOverlay(e, el, index); };
|
||||||
|
});
|
||||||
|
|
||||||
|
lightbox.onclick = hideScreenshotOverlay;
|
||||||
|
|
||||||
// Disable Web UI if notifications of EventSource are not available
|
// Disable Web UI if notifications of EventSource are not available
|
||||||
if (!window["Notification"] || !window["EventSource"]) {
|
if (!window["Notification"] || !window["EventSource"]) {
|
||||||
showBrowserIncompatibleError();
|
showBrowserIncompatibleError();
|
||||||
|
|||||||
Reference in New Issue
Block a user