Generate email content from ninja2 templates
This commit is contained in:
@@ -111,6 +111,8 @@ smtp_host = "localhost" # required if email.enabled
|
|||||||
# smtp_port = 25 # default: 25
|
# smtp_port = 25 # default: 25
|
||||||
# smtp_starttls = false # default: false
|
# smtp_starttls = false # default: false
|
||||||
from_address = "ddns@example.com" # required if email.enabled
|
from_address = "ddns@example.com" # required if email.enabled
|
||||||
|
# change_notification_template = "/etc/ddns-service/change_notification.j2" # optional
|
||||||
|
# expiry_notification_template = "/etc/ddns-service/expiry_notification.j2" # optional
|
||||||
|
|
||||||
[rate_limit]
|
[rate_limit]
|
||||||
# enabled = true # default: true
|
# enabled = true # default: true
|
||||||
|
|||||||
9
files/change_notification.j2
Normal file
9
files/change_notification.j2
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
Your dynamic DNS entry has changed.
|
||||||
|
|
||||||
|
Hostname: {{fqdn}}
|
||||||
|
{% if ipv4_changed %}
|
||||||
|
IPv4 address: {{ipv4}} (changed at: {{last_ipv4_update}})
|
||||||
|
{% endif %}
|
||||||
|
{% if ipv6_changed %}
|
||||||
|
IPv6 address: {{ipv6}} (changed at: {{last_ipv6_update}})
|
||||||
|
{% endif %}
|
||||||
@@ -30,6 +30,8 @@ path = "/var/lib/ddns-service/ddns.db" # required for sqlite
|
|||||||
# dns_timeout = 5 # default, seconds
|
# dns_timeout = 5 # default, seconds
|
||||||
# ddns_default_key_file = "/etc/ddns-service/ddns.key" # optional, BIND TSIG key
|
# ddns_default_key_file = "/etc/ddns-service/ddns.key" # optional, BIND TSIG key
|
||||||
# cleanup_interval = 60 # default, seconds
|
# cleanup_interval = 60 # default, seconds
|
||||||
|
# change_notification_template = "/etc/ddns-service/change_notification.j2" # optional
|
||||||
|
# expiry_notification_template = "/etc/ddns-service/expiry_notification.j2" # optional
|
||||||
|
|
||||||
# Per-zone TSIG key overrides (optional)
|
# Per-zone TSIG key overrides (optional)
|
||||||
# [dns_service.zone_keys]
|
# [dns_service.zone_keys]
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ python_install_all() {
|
|||||||
|
|
||||||
dodir /etc/${PN}
|
dodir /etc/${PN}
|
||||||
insinto /etc/${PN}
|
insinto /etc/${PN}
|
||||||
|
doins files/*.j2
|
||||||
insopts -m640
|
insopts -m640
|
||||||
newins files/config.example.toml config.toml
|
newins files/config.example.toml config.toml
|
||||||
fowners -R ddns:ddns /etc/${PN}
|
fowners -R ddns:ddns /etc/${PN}
|
||||||
|
|||||||
12
files/expiry_notification.j2
Normal file
12
files/expiry_notification.j2
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
Your dynamic DNS entry has expired due to inactivity.
|
||||||
|
|
||||||
|
Hostname: {{fqdn}}
|
||||||
|
{% if ipv4_expired %}
|
||||||
|
IPv4 address: {{last_ipv4}} (last update: {{last_ipv4_update}})
|
||||||
|
{% endif %}
|
||||||
|
{% if ipv6_expired %}
|
||||||
|
IPv6 address: {{last_ipv6}} (last update: {{last_ipv6_update}})
|
||||||
|
{% endif %}
|
||||||
|
Expiry TTL: {{expiry_ttl}} seconds
|
||||||
|
|
||||||
|
The DNS records have been removed. Update your client to restore them.
|
||||||
@@ -57,23 +57,11 @@ def cleanup_expired(app):
|
|||||||
app.dns_service.delete_record(hostname.hostname, hostname.zone, "AAAA")
|
app.dns_service.delete_record(hostname.hostname, hostname.zone, "AAAA")
|
||||||
|
|
||||||
if app.email_service:
|
if app.email_service:
|
||||||
last_ipv4 = last_ipv4_update = None
|
|
||||||
if ipv4_expired:
|
|
||||||
last_ipv4 = hostname.last_ipv4
|
|
||||||
last_ipv4_update = hostname.last_ipv4_update
|
|
||||||
last_ipv6 = last_ipv6_update = None
|
|
||||||
if ipv6_expired:
|
|
||||||
last_ipv6 = hostname.last_ipv6
|
|
||||||
last_ipv6_update = hostname.last_ipv6_update
|
|
||||||
|
|
||||||
app.email_service.send_expiry_notification(
|
app.email_service.send_expiry_notification(
|
||||||
hostname.user.email,
|
hostname.user.email,
|
||||||
f"{hostname.hostname}.{hostname.zone}",
|
hostname,
|
||||||
last_ipv4,
|
ipv4_expired,
|
||||||
last_ipv4_update,
|
ipv6_expired
|
||||||
last_ipv6,
|
|
||||||
last_ipv6_update,
|
|
||||||
hostname.expiry_ttl
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Clear IP addresses
|
# Clear IP addresses
|
||||||
|
|||||||
@@ -5,6 +5,28 @@ import smtplib
|
|||||||
|
|
||||||
from . import datetime_str
|
from . import datetime_str
|
||||||
from email.mime.text import MIMEText
|
from email.mime.text import MIMEText
|
||||||
|
from jinja2 import Environment, BaseLoader
|
||||||
|
|
||||||
|
|
||||||
|
def load_template(path):
|
||||||
|
"""
|
||||||
|
Load template from file.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
path: Path to template.
|
||||||
|
Returns:
|
||||||
|
Template if path was not None, None otherwise.
|
||||||
|
"""
|
||||||
|
if path is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
with open(path) as fh:
|
||||||
|
data = fh.read()
|
||||||
|
|
||||||
|
return Environment(
|
||||||
|
loader=BaseLoader(),
|
||||||
|
trim_blocks=True
|
||||||
|
).from_string(data)
|
||||||
|
|
||||||
|
|
||||||
class EmailService:
|
class EmailService:
|
||||||
@@ -20,6 +42,13 @@ class EmailService:
|
|||||||
self.config = config.get("email", {})
|
self.config = config.get("email", {})
|
||||||
self.enabled = self.config.get("enabled", False)
|
self.enabled = self.config.get("enabled", False)
|
||||||
|
|
||||||
|
self.change_template = load_template(self.config.get("change_notification_template", None))
|
||||||
|
self.expiry_template = load_template(self.config.get("expiry_notification_template", None))
|
||||||
|
|
||||||
|
if not self.change_template and not self.expiry_template and self.enabled:
|
||||||
|
logging.warning("No templates configured, disabling Email")
|
||||||
|
self.enabled = False
|
||||||
|
|
||||||
def send(self, to, subject, body):
|
def send(self, to, subject, body):
|
||||||
"""
|
"""
|
||||||
Send email using configured SMTP server.
|
Send email using configured SMTP server.
|
||||||
@@ -65,15 +94,58 @@ class EmailService:
|
|||||||
logging.error(f"Email send failed: to={to} error={e}")
|
logging.error(f"Email send failed: to={to} error={e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def send_changed_notification(
|
||||||
|
self,
|
||||||
|
email,
|
||||||
|
hostname,
|
||||||
|
ipv4_changed,
|
||||||
|
ipv6_changed
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Send hostname changed notification email.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
email: Recipient email.
|
||||||
|
hostname: Changed hostname.
|
||||||
|
ipv4_changed: Wheter IPv4 address changed.
|
||||||
|
ipv6_changed: Wheter IPv6 address changed.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if sent successfully.
|
||||||
|
"""
|
||||||
|
if not self.enabled:
|
||||||
|
logging.debug("Email disabled, skipping")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not self.change_template:
|
||||||
|
logging.debug("No change notification template configured, skipping")
|
||||||
|
return False
|
||||||
|
|
||||||
|
fqdn = f"{hostname.hostname}.{hostname.zone}"
|
||||||
|
template_variables = {
|
||||||
|
"hostname": hostname.hostname,
|
||||||
|
"zone": hostname.zone,
|
||||||
|
"fqdn": fqdn,
|
||||||
|
"ipv4_changed": ipv4_changed,
|
||||||
|
"ipv4": hostname.last_ipv4,
|
||||||
|
"last_ipv4_update": datetime_str(hostname.last_ipv4_update),
|
||||||
|
"ipv6_changed": ipv6_changed,
|
||||||
|
"ipv6": hostname.last_ipv6,
|
||||||
|
"last_ipv6_update": datetime_str(hostname.last_ipv6_update),
|
||||||
|
"expiry_ttl": hostname.expiry_ttl,
|
||||||
|
}
|
||||||
|
|
||||||
|
subject = f"DDNS hostname expired: {fqdn}"
|
||||||
|
body = self.expiry_template.render(**template_variables)
|
||||||
|
|
||||||
|
return self.send(email, subject, body)
|
||||||
|
|
||||||
def send_expiry_notification(
|
def send_expiry_notification(
|
||||||
self,
|
self,
|
||||||
email,
|
email,
|
||||||
hostname,
|
hostname,
|
||||||
last_ipv4,
|
ipv4_expired,
|
||||||
last_ipv4_update,
|
ipv6_expired
|
||||||
last_ipv6,
|
|
||||||
last_ipv6_update,
|
|
||||||
expiry_ttl
|
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Send hostname expiry notification email.
|
Send hostname expiry notification email.
|
||||||
@@ -81,27 +153,35 @@ class EmailService:
|
|||||||
Args:
|
Args:
|
||||||
email: Recipient email.
|
email: Recipient email.
|
||||||
hostname: Expired hostname.
|
hostname: Expired hostname.
|
||||||
last_ipv4: Tuple containing last IPv4 address and last update timestamp.
|
ipv4_expired: Wheter IPv4 address expired.
|
||||||
last_ipv6: Tuple containing last IPv6 address and last update timestamp.
|
ipv6_expired: Wheter IPv6 address expired.
|
||||||
expiry_ttl: Expiry TTL in seconds.
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
True if sent successfully.
|
True if sent successfully.
|
||||||
"""
|
"""
|
||||||
|
if not self.enabled:
|
||||||
|
logging.debug("Email disabled, skipping")
|
||||||
|
return False
|
||||||
|
|
||||||
expired = ""
|
if not self.expiry_template:
|
||||||
if last_ipv4 and last_ipv4_update:
|
logging.debug("No expiry notification template configured, skipping")
|
||||||
expired += f"IPv4 address: {last_ipv4} (last update: {last_ipv4_update})\n"
|
return False
|
||||||
if last_ipv6 and last_ipv6_update:
|
|
||||||
expired += f"IPv6 address: {last_ipv6} (last update: {last_ipv6_update})\n"
|
|
||||||
|
|
||||||
subject = f"DDNS hostname expired: {hostname}"
|
fqdn = f"{hostname.hostname}.{hostname.zone}"
|
||||||
body = f"""Your dynamic DNS entry has expired due to inactivity.
|
template_variables = {
|
||||||
|
"hostname": hostname.hostname,
|
||||||
|
"zone": hostname.zone,
|
||||||
|
"fqdn": fqdn,
|
||||||
|
"ipv4_expired": ipv4_expired,
|
||||||
|
"last_ipv4": hostname.last_ipv4,
|
||||||
|
"last_ipv4_update": datetime_str(hostname.last_ipv4_update),
|
||||||
|
"ipv6_expired": ipv6_expired,
|
||||||
|
"last_ipv6": hostname.last_ipv6,
|
||||||
|
"last_ipv6_update": datetime_str(hostname.last_ipv6_update),
|
||||||
|
"expiry_ttl": hostname.expiry_ttl,
|
||||||
|
}
|
||||||
|
|
||||||
Hostname: {hostname}
|
subject = f"DDNS hostname expired: {fqdn}"
|
||||||
{expired}
|
body = self.expiry_template.render(**template_variables)
|
||||||
Expiry TTL: {expiry_ttl} seconds
|
|
||||||
|
|
||||||
The DNS records have been removed. Update your client to restore them.
|
|
||||||
"""
|
|
||||||
return self.send(email, subject, body)
|
return self.send(email, subject, body)
|
||||||
|
|||||||
Reference in New Issue
Block a user