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
|
||||
Simplistic server to listing for HTTP queries, specifically from Icinga or Grafana and send out Signal-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 -> Notification Service
|
||||
Simplistic server to listing for HTTP queries, specifically from Icinga or Grafana and send out Alert-Messages.
|
||||
|
||||
# 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.
|
||||
@@ -99,7 +11,7 @@ The following locations are supported:
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import argparse
|
||||
import subprocess
|
||||
import os
|
||||
import requests
|
||||
import re
|
||||
import smtphelper
|
||||
import json
|
||||
|
||||
@@ -61,7 +62,7 @@ def ntfy_send(dispatch_uuid, user_topic, title, message, link,
|
||||
# check message for links #
|
||||
if not link:
|
||||
pattern = r"https:\/\/[^\s]+"
|
||||
match = re.search(pattern, text)
|
||||
match = re.search(pattern, message)
|
||||
if match:
|
||||
link = match.group(0)
|
||||
|
||||
|
||||
@@ -22,8 +22,8 @@ from sqlalchemy.sql.expression import func
|
||||
|
||||
OPENSEARCH_HEADER_SEPERATOR = ","
|
||||
HOST = "icinga.atlantishq.de"
|
||||
app = flask.Flask("Signal Notification Gateway")
|
||||
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///sqlite.db"
|
||||
app = flask.Flask("Atlantis Notification Gateway & Dispatcher")
|
||||
app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get("DB_URL") or "sqlite:///sqlite.db"
|
||||
db = SQLAlchemy(app)
|
||||
|
||||
BAD_DISPATCH_ACCESS_TOKEN = "Invalid or missing dispatch-access-token parameter in URL"
|
||||
@@ -50,15 +50,13 @@ class UserSettings(db.Model):
|
||||
__tablename__ = "user_settings"
|
||||
|
||||
username = Column(String, primary_key=True)
|
||||
signal_priority = Column(Integer)
|
||||
signal_priority = Column(Integer) # legacy, no longer used
|
||||
email_priority = Column(Integer)
|
||||
ntfy_priority = Column(Integer)
|
||||
|
||||
def get_highest_prio_method(self):
|
||||
|
||||
if self.signal_priority >= max(self.email_priority, self.ntfy_priority):
|
||||
return "signal"
|
||||
elif self.email_priority >= max(self.signal_priority, self.ntfy_priority):
|
||||
if self.email_priority >= self.ntfy_priority:
|
||||
return "email"
|
||||
else:
|
||||
return "ntfy"
|
||||
@@ -66,7 +64,6 @@ class UserSettings(db.Model):
|
||||
def serizalize(self):
|
||||
return {
|
||||
"username" : self.username,
|
||||
"signal_priority" : self.signal_priority,
|
||||
"email_priority" : self.email_priority,
|
||||
"ntfy_priority" : self.ntfy_priority,
|
||||
}
|
||||
@@ -114,9 +111,7 @@ class DispatchObject(db.Model):
|
||||
user_settings = db.session.query(UserSettings).filter(
|
||||
UserSettings.username == ret["username"]).first()
|
||||
|
||||
if not user_settings and self.phone:
|
||||
ret["method"] = "signal"
|
||||
elif not user_settings and self.email:
|
||||
if not user_settings and self.email:
|
||||
ret["method"] = "email"
|
||||
elif user_settings:
|
||||
ret["method"] = user_settings.get_highest_prio_method()
|
||||
@@ -210,7 +205,7 @@ def settings():
|
||||
|
||||
if flask.request.method == "POST":
|
||||
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,
|
||||
ntfy_priority=flask.request.json.get("ntfy_priority") or 0)
|
||||
db.session.merge(posted)
|
||||
@@ -220,7 +215,7 @@ def settings():
|
||||
if flask.request.method == "GET":
|
||||
user_settings = db.session.query(UserSettings).filter(UserSettings.username==user).first()
|
||||
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.commit()
|
||||
user_settings = posted
|
||||
@@ -240,7 +235,7 @@ def get_dispatch():
|
||||
return (BAD_DISPATCH_ACCESS_TOKEN, 401)
|
||||
|
||||
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 #
|
||||
timeout_cutoff = datetime.datetime.now() - datetime.timedelta(seconds=timeout)
|
||||
@@ -262,45 +257,7 @@ def get_dispatch():
|
||||
else:
|
||||
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])
|
||||
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)
|
||||
return flask.jsonify([ d.serialize() for d in dispatch_objects])
|
||||
|
||||
@app.route('/report-dispatch-failed', methods=["POST"])
|
||||
def reject_dispatch():
|
||||
@@ -428,7 +385,7 @@ def smart_send_to_clients(path=None):
|
||||
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 = []
|
||||
@@ -497,8 +454,6 @@ if __name__ == "__main__":
|
||||
|
||||
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("--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-base-dn')
|
||||
|
||||
Reference in New Issue
Block a user