mirror of
https://github.com/FAUSheppy/atlantis-event-dispatcher
synced 2026-03-10 01:31:44 +01:00
Compare commits
6 Commits
4e4f53b330
...
e81a69cffd
| Author | SHA1 | Date | |
|---|---|---|---|
| e81a69cffd | |||
| 792b162025 | |||
| 4b2d97fa87 | |||
| b2292943cd | |||
| 69161135ec | |||
| f08f6a2953 |
94
README.md
94
README.md
@@ -1,93 +1,5 @@
|
|||||||
# HTTP->Signal Gateway Notification Service
|
# HTTP -> Notification Service
|
||||||
Simplistic server to listing for HTTP queries, specifically from Icinga or Grafana and send out Signal-Messages.
|
Simplistic server to listing for HTTP queries, specifically from Icinga or Grafana and send out Alert-Messages.
|
||||||
|
|
||||||
# Signal Cli Setup
|
|
||||||
You need `glibc>=2.29`, check this first with `ldd --version` (for Debian this means bullseye or later).
|
|
||||||
Clone the following repositories
|
|
||||||
|
|
||||||
https://github.com/AsamK/signal-cli
|
|
||||||
https://github.com/signalapp/libsignal-client/
|
|
||||||
https://github.com/signalapp/zkgroup
|
|
||||||
|
|
||||||
Install the prerequisites (potentially non-exaustive list):
|
|
||||||
|
|
||||||
apt install gradle
|
|
||||||
https://www.rust-lang.org/tools/install (as current user)
|
|
||||||
|
|
||||||
Go to signal-cli project-root:
|
|
||||||
|
|
||||||
./gradlew build
|
|
||||||
./gradlew installDist
|
|
||||||
|
|
||||||
Go to libsignal-client project-root, change to java-directory and make sure to remove android from the build options, otherwise this will take ages:
|
|
||||||
|
|
||||||
cd java
|
|
||||||
sed -i "s/, ':android'//" settings.gradle
|
|
||||||
./build_jni.sh desktop
|
|
||||||
|
|
||||||
Go to zkgroup project-root and build it:
|
|
||||||
|
|
||||||
make libzkgroup
|
|
||||||
|
|
||||||
You need to make the build libraries available for java, either copy them to the java-library path (make sure they are readable for all users) or add them to the *LD\_LIBRARY\_PATH* enviroment variable whenever you intend to use the signal-cli binary.
|
|
||||||
|
|
||||||
To get the default java-library-path execute:
|
|
||||||
|
|
||||||
java -XshowSettings:properties 2>&1 | grep java.library
|
|
||||||
|
|
||||||
Usually on linux that's `/usr/java/packages/lib/`, though this directory might not exist yet, so:
|
|
||||||
|
|
||||||
sudo mkdir -p /usr/java/packages/lib/
|
|
||||||
sudo cp libsignal-client/target/release/libsignal_jni.so /usr/java/packages/lib/
|
|
||||||
sudo cp zkgroup/target/release/libzkgroup.so /usr/java/packages/lib/
|
|
||||||
sudo chmod a+rX /usr/java/packages/lib/
|
|
||||||
|
|
||||||
Or:
|
|
||||||
|
|
||||||
LD_LIBRARY_PATH=LD_LIBRARY_PATH:~/libsignal-client/target/release/:~/path/to/...
|
|
||||||
|
|
||||||
Now go to signal-cli project-root, we will have to make some preparations. First prepare your phone number, if you use a number which does not support SMS, use the `--voice`-switch to receive a call instead. Your full phone number means your number, including your country code (including a leading `+`), your area code (without any leading zeros).
|
|
||||||
|
|
||||||
You also need a captcha-token, for this open a browser tab first. Then open the developer console, then *make sure to have 'persist-logs' on*, and only *after* that navigate to:
|
|
||||||
|
|
||||||
https://signalcaptchas.org/registration/generate.html
|
|
||||||
|
|
||||||
You may or may not actually have to solve a chaptcha, in the console, after you the check succeeded,you will likely get a popup to open signal, ignore that and look into the dev-console, there should be something along the lines of:
|
|
||||||
|
|
||||||
Navigated to: signalchaptcha://very_very_loooooooooooong_token
|
|
||||||
|
|
||||||
Copy everything after `signalchaptcha://` and use it as the token for the `--captcha`-argument. Be advised, the token isn't valid very long:
|
|
||||||
|
|
||||||
cd build/install/signal-cli/bin/signal-cli
|
|
||||||
signal-cli -u FULL_PHONE_NUMBER register --voice --captcha 'TOKEN'
|
|
||||||
|
|
||||||
You will now get a SMS/call with the verification-code, which you can use with:
|
|
||||||
|
|
||||||
signal-cli -u FULL_PHONE_NUMBER verify CODE
|
|
||||||
|
|
||||||
You should consider setting a pin directly after, for help with this and other options use:
|
|
||||||
|
|
||||||
signal-cli -h
|
|
||||||
|
|
||||||
You should use `signal-cli receive` regulary, otherwise your account will be flagged inactive and potentially deleted. You may ommit the `-u` option if you only have registered one account with this user on this machine. Data (including private keys) are saved to `~/.local/share/signal-cli/`.
|
|
||||||
|
|
||||||
# Server Setup
|
|
||||||
Add the target number(s) (one per line) to signal\_targets.txt, then set the a enviroment variable `SIGNAL_API_PASS`, which must be used withing a basic authentication during access to the gateway. Finally execute the server:
|
|
||||||
|
|
||||||
|
|
||||||
usage: interface.py [-h] [--interface INTERFACE]
|
|
||||||
[--port PORT]
|
|
||||||
[--signal-cli-bin SIGNAL_CLI_BIN]
|
|
||||||
|
|
||||||
optional arguments:
|
|
||||||
-h, --help show this help message and exit
|
|
||||||
--interface INTERFACE
|
|
||||||
Interface on which to listen (default: localhost)
|
|
||||||
--port PORT Port on which to listen (default: 5000)
|
|
||||||
--signal-cli-bin SIGNAL_CLI_BIN
|
|
||||||
Path to signal-cli binary if no in $PATH (default: None)
|
|
||||||
|
|
||||||
`SIGNAL_CLI_BIN` can also be set as an environment variable, which will overwrite any command line option.
|
|
||||||
|
|
||||||
# HTTP Request
|
# HTTP Request
|
||||||
The HTTP request must be a *POST*-request, with *Content-Type: application/json* and a json-field containing the key *"message"* with the value being the message you want to send.
|
The HTTP request must be a *POST*-request, with *Content-Type: application/json* and a json-field containing the key *"message"* with the value being the message you want to send.
|
||||||
@@ -99,7 +11,7 @@ The following locations are supported:
|
|||||||
|
|
||||||
# Example (curl)
|
# Example (curl)
|
||||||
|
|
||||||
curl -u nobody:SIGNAL_API_PASS -X POST -H "Content-Type: application/json" --data '{"message":"hallo world"}' localhost:5000/send-all
|
curl -u nobody:API_PASS -X POST -H "Content-Type: application/json" --data '{"message":"hello world"}' localhost:5000/send-all
|
||||||
|
|
||||||
# Additional Packages Required
|
# Additional Packages Required
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import argparse
|
|||||||
import subprocess
|
import subprocess
|
||||||
import os
|
import os
|
||||||
import requests
|
import requests
|
||||||
|
import re
|
||||||
import smtphelper
|
import smtphelper
|
||||||
import json
|
import json
|
||||||
|
|
||||||
@@ -61,7 +62,7 @@ def ntfy_send(dispatch_uuid, user_topic, title, message, link,
|
|||||||
# check message for links #
|
# check message for links #
|
||||||
if not link:
|
if not link:
|
||||||
pattern = r"https:\/\/[^\s]+"
|
pattern = r"https:\/\/[^\s]+"
|
||||||
match = re.search(pattern, text)
|
match = re.search(pattern, message)
|
||||||
if match:
|
if match:
|
||||||
link = match.group(0)
|
link = match.group(0)
|
||||||
|
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ from sqlalchemy.sql.expression import func
|
|||||||
|
|
||||||
OPENSEARCH_HEADER_SEPERATOR = ","
|
OPENSEARCH_HEADER_SEPERATOR = ","
|
||||||
HOST = "icinga.atlantishq.de"
|
HOST = "icinga.atlantishq.de"
|
||||||
app = flask.Flask("Signal Notification Gateway")
|
app = flask.Flask("Atlantis Notification Gateway & Dispatcher")
|
||||||
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///sqlite.db"
|
app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get("DB_URL") or "sqlite:///sqlite.db"
|
||||||
db = SQLAlchemy(app)
|
db = SQLAlchemy(app)
|
||||||
|
|
||||||
BAD_DISPATCH_ACCESS_TOKEN = "Invalid or missing dispatch-access-token parameter in URL"
|
BAD_DISPATCH_ACCESS_TOKEN = "Invalid or missing dispatch-access-token parameter in URL"
|
||||||
@@ -50,15 +50,13 @@ class UserSettings(db.Model):
|
|||||||
__tablename__ = "user_settings"
|
__tablename__ = "user_settings"
|
||||||
|
|
||||||
username = Column(String, primary_key=True)
|
username = Column(String, primary_key=True)
|
||||||
signal_priority = Column(Integer)
|
signal_priority = Column(Integer) # legacy, no longer used
|
||||||
email_priority = Column(Integer)
|
email_priority = Column(Integer)
|
||||||
ntfy_priority = Column(Integer)
|
ntfy_priority = Column(Integer)
|
||||||
|
|
||||||
def get_highest_prio_method(self):
|
def get_highest_prio_method(self):
|
||||||
|
|
||||||
if self.signal_priority >= max(self.email_priority, self.ntfy_priority):
|
if self.email_priority >= self.ntfy_priority:
|
||||||
return "signal"
|
|
||||||
elif self.email_priority >= max(self.signal_priority, self.ntfy_priority):
|
|
||||||
return "email"
|
return "email"
|
||||||
else:
|
else:
|
||||||
return "ntfy"
|
return "ntfy"
|
||||||
@@ -66,7 +64,6 @@ class UserSettings(db.Model):
|
|||||||
def serizalize(self):
|
def serizalize(self):
|
||||||
return {
|
return {
|
||||||
"username" : self.username,
|
"username" : self.username,
|
||||||
"signal_priority" : self.signal_priority,
|
|
||||||
"email_priority" : self.email_priority,
|
"email_priority" : self.email_priority,
|
||||||
"ntfy_priority" : self.ntfy_priority,
|
"ntfy_priority" : self.ntfy_priority,
|
||||||
}
|
}
|
||||||
@@ -114,9 +111,7 @@ class DispatchObject(db.Model):
|
|||||||
user_settings = db.session.query(UserSettings).filter(
|
user_settings = db.session.query(UserSettings).filter(
|
||||||
UserSettings.username == ret["username"]).first()
|
UserSettings.username == ret["username"]).first()
|
||||||
|
|
||||||
if not user_settings and self.phone:
|
if not user_settings and self.email:
|
||||||
ret["method"] = "signal"
|
|
||||||
elif not user_settings and self.email:
|
|
||||||
ret["method"] = "email"
|
ret["method"] = "email"
|
||||||
elif user_settings:
|
elif user_settings:
|
||||||
ret["method"] = user_settings.get_highest_prio_method()
|
ret["method"] = user_settings.get_highest_prio_method()
|
||||||
@@ -210,7 +205,7 @@ def settings():
|
|||||||
|
|
||||||
if flask.request.method == "POST":
|
if flask.request.method == "POST":
|
||||||
posted = UserSettings(username=user,
|
posted = UserSettings(username=user,
|
||||||
signal_priority=flask.request.json.get("signal_priority") or 0,
|
signal_priority=-1,
|
||||||
email_priority=flask.request.json.get("email_priority") or 0,
|
email_priority=flask.request.json.get("email_priority") or 0,
|
||||||
ntfy_priority=flask.request.json.get("ntfy_priority") or 0)
|
ntfy_priority=flask.request.json.get("ntfy_priority") or 0)
|
||||||
db.session.merge(posted)
|
db.session.merge(posted)
|
||||||
@@ -220,7 +215,7 @@ def settings():
|
|||||||
if flask.request.method == "GET":
|
if flask.request.method == "GET":
|
||||||
user_settings = db.session.query(UserSettings).filter(UserSettings.username==user).first()
|
user_settings = db.session.query(UserSettings).filter(UserSettings.username==user).first()
|
||||||
if not user_settings:
|
if not user_settings:
|
||||||
posted = UserSettings(username=user, signal_priority=5, email_priority=7, ntfy_priority=3)
|
posted = UserSettings(username=user, signal_priority=-1, email_priority=7, ntfy_priority=3)
|
||||||
db.session.merge(posted)
|
db.session.merge(posted)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
user_settings = posted
|
user_settings = posted
|
||||||
@@ -240,7 +235,7 @@ def get_dispatch():
|
|||||||
return (BAD_DISPATCH_ACCESS_TOKEN, 401)
|
return (BAD_DISPATCH_ACCESS_TOKEN, 401)
|
||||||
|
|
||||||
if not method:
|
if not method:
|
||||||
return (500, "Missing Dispatch Target (signal|email|phone|ntfy|all|any)")
|
return (500, "Missing Dispatch Target (email|phone|ntfy|all|any)")
|
||||||
|
|
||||||
# prevent message floods #
|
# prevent message floods #
|
||||||
timeout_cutoff = datetime.datetime.now() - datetime.timedelta(seconds=timeout)
|
timeout_cutoff = datetime.datetime.now() - datetime.timedelta(seconds=timeout)
|
||||||
@@ -262,45 +257,7 @@ def get_dispatch():
|
|||||||
else:
|
else:
|
||||||
dispatch_objects = lines_timeout.all()
|
dispatch_objects = lines_timeout.all()
|
||||||
|
|
||||||
# TODO THIS IS THE NEW MASTER PART
|
|
||||||
if method and method != "signal":
|
|
||||||
debug = [ d.serialize() for d in dispatch_objects]
|
|
||||||
if debug:
|
|
||||||
print(debug)
|
|
||||||
return flask.jsonify([ d.serialize() for d in dispatch_objects])
|
return flask.jsonify([ d.serialize() for d in dispatch_objects])
|
||||||
else:
|
|
||||||
# TODO THIS PART WILL BE REMOVED ##
|
|
||||||
# accumulate messages by person #
|
|
||||||
dispatch_by_person = dict()
|
|
||||||
dispatch_secrets = []
|
|
||||||
for dobj in dispatch_objects:
|
|
||||||
if dobj.username not in dispatch_by_person:
|
|
||||||
dispatch_by_person.update({ dobj.username : dobj.message })
|
|
||||||
dispatch_secrets.append(dobj.dispatch_secret)
|
|
||||||
else:
|
|
||||||
dispatch_by_person[dobj.username] += "\n{}".format(dobj.message)
|
|
||||||
dispatch_secrets.append(dobj.dispatch_secret)
|
|
||||||
|
|
||||||
# legacy hack #
|
|
||||||
if method == "any":
|
|
||||||
method = "signal"
|
|
||||||
|
|
||||||
response = [ { "person" : tupel[0].decode("utf-8"),
|
|
||||||
"message" : tupel[1],
|
|
||||||
"method" : method,
|
|
||||||
"uids" : dispatch_secrets
|
|
||||||
} for tupel in dispatch_by_person.items() ]
|
|
||||||
|
|
||||||
# add phone numbers and emails #
|
|
||||||
for obj in response:
|
|
||||||
for person in dispatch_objects:
|
|
||||||
if obj["person"] == person.username.decode("utf-8"):
|
|
||||||
if person.email:
|
|
||||||
obj.update({ "email" : person.email.decode("utf-8") })
|
|
||||||
if person.phone:
|
|
||||||
obj.update({ "phone" : person.phone.decode("utf-8") })
|
|
||||||
|
|
||||||
return flask.jsonify(response)
|
|
||||||
|
|
||||||
@app.route('/report-dispatch-failed', methods=["POST"])
|
@app.route('/report-dispatch-failed', methods=["POST"])
|
||||||
def reject_dispatch():
|
def reject_dispatch():
|
||||||
@@ -428,7 +385,7 @@ def smart_send_to_clients(path=None):
|
|||||||
return flask.jsonify(dispatch_secrets)
|
return flask.jsonify(dispatch_secrets)
|
||||||
|
|
||||||
|
|
||||||
def save_in_dispatch_queue(persons, title, message, method):
|
def save_in_dispatch_queue(persons, title, message, method, link=""):
|
||||||
|
|
||||||
|
|
||||||
dispatch_secrets = []
|
dispatch_secrets = []
|
||||||
@@ -497,8 +454,6 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
parser.add_argument('--interface', default="localhost", help='Interface on which to listen')
|
parser.add_argument('--interface', default="localhost", help='Interface on which to listen')
|
||||||
parser.add_argument('--port', default="5000", help='Port on which to listen')
|
parser.add_argument('--port', default="5000", help='Port on which to listen')
|
||||||
parser.add_argument("--signal-cli-bin", default=None, type=str,
|
|
||||||
help="Path to signal-cli binary if no in $PATH")
|
|
||||||
|
|
||||||
parser.add_argument('--ldap-server')
|
parser.add_argument('--ldap-server')
|
||||||
parser.add_argument('--ldap-base-dn')
|
parser.add_argument('--ldap-base-dn')
|
||||||
|
|||||||
Reference in New Issue
Block a user