mirror of https://github.com/sipwise/reminder.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
228 lines
7.0 KiB
228 lines
7.0 KiB
#!/usr/bin/env python3
|
|
import os
|
|
import shutil
|
|
import tempfile
|
|
import pwd
|
|
import logging
|
|
import logging.handlers
|
|
import MySQLdb
|
|
import configparser
|
|
from datetime import datetime, timezone
|
|
import grp
|
|
|
|
CONFIG_FILE = "/etc/ngcp-reminder/reminder.conf"
|
|
LOG_FILE = "/var/log/ngcp/ngcp-reminder.log"
|
|
OWNERLOGFILE = "root"
|
|
OWNERTMPFILE = "asterisk"
|
|
|
|
# --- Logging setup -----------------------------------------------------------
|
|
SYSLOG_ADDRESS = '/dev/log' # Standard Unix socket for local syslog
|
|
|
|
# Ensure proper permissions and ownership for the log file
|
|
if not os.path.exists(LOG_FILE):
|
|
open(LOG_FILE, "w").close()
|
|
uid = pwd.getpwnam(OWNERLOGFILE).pw_uid
|
|
gid = grp.getgrnam('adm').gr_gid
|
|
os.chown(LOG_FILE, uid, gid)
|
|
os.chmod(LOG_FILE, 0o640) # rw-r-----
|
|
|
|
logger = logging.getLogger("ngcp-reminder")
|
|
logger.setLevel(logging.INFO)
|
|
|
|
# Syslog handler (facility local0)
|
|
syslog_handler = logging.handlers.SysLogHandler(
|
|
address=SYSLOG_ADDRESS,
|
|
facility=logging.handlers.SysLogHandler.LOG_LOCAL0
|
|
)
|
|
syslog_formatter = logging.Formatter(
|
|
'ngcp-reminder: [%(levelname)s] %(message)s')
|
|
syslog_handler.setFormatter(syslog_formatter)
|
|
logger.addHandler(syslog_handler)
|
|
|
|
# File handler (optional fallback)
|
|
file_handler = logging.FileHandler(LOG_FILE)
|
|
file_formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s')
|
|
file_handler.setFormatter(file_formatter)
|
|
logger.addHandler(file_handler)
|
|
|
|
|
|
# --- Configuration reader ----------------------------------------------------
|
|
def read_config(path):
|
|
"""Read key=value configuration file without section headers."""
|
|
parser = configparser.ConfigParser()
|
|
with open(path) as f:
|
|
data = f.read()
|
|
parser.read_string("[DEFAULT]\n" + data)
|
|
return parser["DEFAULT"]
|
|
|
|
|
|
# --- Database connection -----------------------------------------------------
|
|
def connect_db(cfg):
|
|
"""Establish a connection to the MySQL database."""
|
|
try:
|
|
db = MySQLdb.connect(
|
|
host=cfg.get("dbhost", "localhost"),
|
|
user=cfg.get("dbuser"),
|
|
passwd=cfg.get("dbpassword"),
|
|
db=cfg.get("database"),
|
|
)
|
|
return db
|
|
except Exception as e:
|
|
logger.error(f"Database connection failed: {e}")
|
|
raise
|
|
|
|
|
|
# --- Call file creation ------------------------------------------------------
|
|
def create_call_file(
|
|
cfg,
|
|
username,
|
|
domain,
|
|
lang,
|
|
recur,
|
|
reminder_id,
|
|
weekdays
|
|
):
|
|
"""Create an Asterisk .call file based on the reminder configuration."""
|
|
tmpdir = cfg.get("tmpdir", "/tmp")
|
|
spool = cfg.get("spool", "/var/spool/asterisk/outgoing")
|
|
|
|
# Normalize PJSIP peer name
|
|
peer = cfg.get("sip_peer", "sip_proxy_ep")
|
|
if not peer.upper().startswith(("SIP/", "PJSIP/")):
|
|
peer = f"PJSIP/{peer}"
|
|
|
|
tmp_prefix = f"{username}__AT__{domain}."
|
|
fd, tmp_filename = tempfile.mkstemp(
|
|
prefix=tmp_prefix,
|
|
dir=tmpdir,
|
|
text=True
|
|
)
|
|
os.close(fd)
|
|
|
|
try:
|
|
# Write call file content
|
|
with open(tmp_filename, "w") as f:
|
|
f.write(f"Channel: {peer}/sip:{username}@{domain}\n")
|
|
f.write(f"MaxRetries: {cfg.get('retries', '2')}\n")
|
|
f.write(f"RetryTime: {cfg.get('retry_time', '60')}\n")
|
|
f.write(f"WaitTime: {cfg.get('wait_time', '30')}\n")
|
|
f.write("Extension: s\n")
|
|
f.write(f"Context: {cfg.get('context', 'reminder')}\n")
|
|
f.write("Priority: 1\n")
|
|
f.write(f"Setvar: LANG={lang or 'en'}\n")
|
|
|
|
# Set file ownership and permissions
|
|
uid = pwd.getpwnam(OWNERTMPFILE).pw_uid
|
|
gid = pwd.getpwnam(OWNERTMPFILE).pw_gid
|
|
os.chown(tmp_filename, uid, gid)
|
|
os.chmod(tmp_filename, 0o600)
|
|
|
|
# Read back for logging
|
|
with open(tmp_filename, "r") as f:
|
|
content = f.read()
|
|
|
|
final_path = os.path.join(spool, os.path.basename(tmp_filename))
|
|
shutil.move(tmp_filename, final_path)
|
|
|
|
logger.info(
|
|
f"Created call file: {final_path}\n"
|
|
"--- File content ---\n"
|
|
f"{content}\n"
|
|
"--------------------"
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error creating call file for {username}@{domain}: {e}")
|
|
if os.path.exists(tmp_filename):
|
|
os.remove(tmp_filename)
|
|
|
|
|
|
# --- Main logic --------------------------------------------------------------
|
|
def main():
|
|
"""Main execution logic for the NGCP reminder system."""
|
|
cfg = read_config(CONFIG_FILE)
|
|
weekdays = [
|
|
w.strip()
|
|
for w in cfg.get("weekdays", "2,3,4,5,6,7").split(",")
|
|
]
|
|
|
|
db = connect_db(cfg)
|
|
cur = db.cursor(MySQLdb.cursors.DictCursor)
|
|
|
|
sql = """
|
|
SELECT s.username, d.domain, r.recur, r.id, vup.value as lang
|
|
FROM provisioning.voip_reminder r
|
|
LEFT JOIN provisioning.voip_subscribers s ON r.subscriber_id = s.id
|
|
LEFT JOIN provisioning.voip_domains d ON s.domain_id = d.id
|
|
LEFT JOIN billing.v_subscriber_timezone tz ON tz.uuid = s.uuid
|
|
LEFT JOIN provisioning.voip_usr_preferences vup
|
|
ON r.subscriber_id=vup.subscriber_id
|
|
LEFT JOIN provisioning.voip_preferences vp ON vp.id=vup.attribute_id
|
|
WHERE r.active = 1
|
|
AND vp.attribute = "language"
|
|
AND r.time =
|
|
time_format(CONVERT_TZ(now(), "localtime", tz.name), '%H:%i:00')
|
|
UNION
|
|
SELECT s.username, d.domain, r.recur, r.id, vdp.value as lang
|
|
FROM provisioning.voip_reminder r
|
|
LEFT JOIN provisioning.voip_subscribers s ON r.subscriber_id = s.id
|
|
LEFT JOIN provisioning.voip_domains d ON s.domain_id = d.id
|
|
LEFT JOIN billing.v_subscriber_timezone tz ON tz.uuid = s.uuid
|
|
LEFT JOIN provisioning.voip_dom_preferences vdp ON vdp.domain_id=d.id
|
|
LEFT JOIN provisioning.voip_preferences vp ON vp.id=vdp.attribute_id
|
|
AND r.time =
|
|
time_format(CONVERT_TZ(now(), "localtime", tz.name), '%H:%i:00')
|
|
WHERE r.active = 1
|
|
AND vp.attribute = "language"
|
|
LIMIT 100;
|
|
"""
|
|
|
|
cur.execute(sql)
|
|
rows = cur.fetchall()
|
|
|
|
for ref in rows:
|
|
username = ref["username"]
|
|
domain = ref["domain"]
|
|
recur = ref["recur"]
|
|
lang = ref.get("lang", "en")
|
|
reminder_id = ref["id"]
|
|
|
|
# Weekday check for recurring reminders
|
|
if recur == "weekdays":
|
|
wday = datetime.now(timezone.utc).isoweekday()
|
|
if str(wday) not in weekdays:
|
|
continue
|
|
|
|
create_call_file(
|
|
cfg,
|
|
username,
|
|
domain,
|
|
lang,
|
|
recur,
|
|
reminder_id,
|
|
weekdays
|
|
)
|
|
|
|
# Disable one-time reminders
|
|
if recur == "never":
|
|
cur2 = db.cursor()
|
|
cur2.execute(
|
|
"UPDATE voip_reminder SET active=0 WHERE id=%s",
|
|
(reminder_id,)
|
|
)
|
|
cur2.close()
|
|
db.commit()
|
|
|
|
cur.close()
|
|
db.close()
|
|
logger.info("Reminder cycle completed successfully.")
|
|
|
|
|
|
# --- Entry point -------------------------------------------------------------
|
|
if __name__ == "__main__":
|
|
try:
|
|
main()
|
|
except Exception as e:
|
|
logger.exception(f"Unhandled exception: {e}")
|
|
raise
|