From d1b0579a41c8215487a17317851f01756a1d938d Mon Sep 17 00:00:00 2001 From: jaseg Date: Tue, 29 Dec 2020 13:08:13 +0100 Subject: Add dns, dyndns services --- dns.yml | 91 +++++++++++++ dyndns.py | 149 +++++++++++++++++++++ dyndns_config.py.j2 | 14 ++ nsd.conf | 372 ++++++++++++++++++++++++++++++++++++++++++++++++++++ setup_dyndns.yml | 80 +++++++++++ uwsgi-dyndns.ini | 10 ++ 6 files changed, 716 insertions(+) create mode 100644 dns.yml create mode 100644 dyndns.py create mode 100644 dyndns_config.py.j2 create mode 100644 nsd.conf create mode 100644 setup_dyndns.yml create mode 100644 uwsgi-dyndns.ini diff --git a/dns.yml b/dns.yml new file mode 100644 index 0000000..0fd753a --- /dev/null +++ b/dns.yml @@ -0,0 +1,91 @@ +- name: Setup subdomain A records pointing to wendelstein + inwx: + domain: "{{item.partition('.')[2]}}" + record: "{{item.partition('.')[0]}}" + type: A + value: "{{ hostvars['wendelstein']['ansible_default_ipv4']['address'] }}" + loop: "{{subdomains}}" + +- name: Setup dyndns A record + inwx: + domain: jaseg.de + record: ns + type: A + value: "{{ hostvars['wendelstein']['ansible_default_ipv4']['address'] }}" + +- name: Setup dyndns NS record + inwx: + domain: jaseg.de + record: dyn + type: NS + value: 'ns.jaseg.de' + +- name: Setup subdomain AAAA records pointing to wendelstein + inwx: + domain: "{{item.partition('.')[2]}}" + record: "{{item.partition('.')[0]}}" + type: AAAA + value: "{{ hostvars['wendelstein']['ansible_default_ipv6']['address'] }}" + loop: "{{subdomains}}" + +- name: Setup jaseg.net subdomain MX records pointing to fastmail + inwx: + domain: "{{item.partition('.')[2]}}" + record: "{{item.partition('.')[0]}}" + type: MX + priority: 10 + value: in1-smtp.messagingengine.com + loop: "{{subdomains}}" + +- name: Setup jaseg.net subdomain MX records pointing to fastmail + inwx: + domain: "{{item.partition('.')[2]}}" + record: "{{item.partition('.')[0]}}" + type: MX + priority: 20 + value: in2-smtp.messagingengine.com + loop: "{{subdomains}}" + +- name: Setup sendgrid gateway + inwx: + domain: jaseg.de + type: CNAME + record: "{{item.split(' ')[0]}}" + value: "{{item.split(' ')[1]}}" + loop: + - em6100.automation u14518136.wl137.sendgrid.net + - s1._domainkey.automation s1.domainkey.u14518136.wl137.sendgrid.net + - s2._domainkey.automation s2.domainkey.u14518136.wl137.sendgrid.net + +- name: Set fastmail DNS entry template + set_fact: + fastmail_dns_entries: + - {rtype: MX, record: ".", prio: 10, value: in1-smtp.messagingengine.com} + - {rtype: MX, record: ".", prio: 20, value: in2-smtp.messagingengine.com} + - {rtype: MX, record: "*", prio: 10, value: in1-smtp.messagingengine.com} + - {rtype: MX, record: "*", prio: 20, value: in2-smtp.messagingengine.com} + - {rtype: TXT, record: ".", value: "v=spf1 include:spf.messagingengine.com ?all"} + - {rtype: CNAME, record: mesmtp._domainkey, value: mesmtp.jaseg.de.dkim.fmhosted.com} + - {rtype: CNAME, record: fm1._domainkey, value: fm1.jaseg.de.dkim.fmhosted.com} + - {rtype: CNAME, record: fm2._domainkey, value: fm2.jaseg.de.dkim.fmhosted.com} + - {rtype: CNAME, record: fm3._domainkey, value: fm3.jaseg.de.dkim.fmhosted.com} + - {rtype: SRV, record: _submission._tcp, prio: 0, weight: 1, port: 587, value: smtp.fastmail.com} + - {rtype: SRV, record: _imap._tcp, prio: 0, weight: 0, port: 0, value: "."} + - {rtype: SRV, record: _imaps._tcp, prio: 0, weight: 1, port: 993, value: imap.fastmail.com} + - {rtype: SRV, record: _pop3._tcp, prio: 0, weight: 0, port: 0, value: "."} + - {rtype: SRV, record: _pop3s._tcp, prio: 10, weight: 1, port: 995, value: pop.fastmail.com} + - {rtype: SRV, record: _jmap._tcp, prio: 0, weight: 1, port: 443, value: jmap.fastmail.com} + - {rtype: SRV, record: _carddav._tcp, prio: 0, weight: 0, port: 0, value: "."} + - {rtype: SRV, record: _carddavs._tcp, prio: 0, weight: 1, port: 443, value: carddav.fastmail.com} + +- name: Setup fastmail DNS entries + inwx: + domain: "{{ item[1] }}" + type: "{{ item[0]['rtype'] }}" + record: "{{ item[0]['record'] | regex_replace('\\.*$', '') }}" + priority: "{{ item[0].get('prio') | int }}" + port: "{{ item[0].get('port') | int}}" + weight: "{{ item[0].get('weight') | int }}" + value: "{{ item[0]['value'] }}" + loop: "{{ fastmail_dns_entries | product(fastmail_domains) | list }}" + diff --git a/dyndns.py b/dyndns.py new file mode 100644 index 0000000..2546dce --- /dev/null +++ b/dyndns.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python3 + +import time +from contextlib import contextmanager +import re +import os +import os.path +import random +import string +import subprocess +import sqlite3 +import hmac +from ipaddress import IPv4Address, IPv6Address + +from flask import Flask, request, abort +import uwsgidecorators + +app = Flask(__name__) +app.config.update(dict( + RECORD_EXPIRY_S = 86400, + NSD_CONTROL = 'nsd-control' + )) +app.config.from_pyfile('config.py') + + +ZONEFILE_TEMPLATE = '''\ +; #################################################### ; +; THIS FILE IS AUTOMATICALLY GENERATED! DO NOT MODIFY! ; +; #################################################### ; + +$ORIGIN {zone}. +$TTL 1800 +@ IN SOA {ns}. {mail}. ( + {serial} ; serial number + 60 ; refresh + 60 ; retry + {expire} ; expire + 60 ; ttl + ) +; Name servers + IN NS {ns}. + +; Additional A records from template +; @ IN A 192.0.2.3 +; www IN A 192.0.2.3 + +; Dynamically generated records +{dynamic_records} +''' + +db = sqlite3.connect(app.config['SQLITE_DB'], check_same_thread=False) +with db as conn: + conn.execute('''CREATE TABLE IF NOT EXISTS zone_versions (date TEXT)''') + conn.execute('''CREATE TABLE IF NOT EXISTS records + (name TEXT PRIMARY KEY, ipv4 TEXT, ipv6 TEXT, last_update INTEGER)''') + +def purge_expired_records(): + with db as conn: + conn.execute('DELETE FROM records WHERE last_update < ?', + (int(time.time()) - app.config['RECORD_EXPIRY_S'],)) + +def update_record(record, ipv4=None, ipv6=None): + with db as conn: + old_v4, old_v6 = conn.execute('SELECT ipv4, ipv6 FROM records WHERE name=?', (record,)).fetchone() or (None, None) + conn.execute('INSERT OR REPLACE INTO records VALUES (?, ?, ?, ?)', (record, ipv4, ipv6, int(time.time()))) + return ipv4 != old_v4 or ipv6 != old_v6 + +@contextmanager +def inplace_rewrite(filename, cleanup=True): + print('Writing', filename) + filename = os.path.abspath(filename) + if cleanup: + basename = os.path.basename(filename) + for entry in os.scandir(os.path.dirname(filename)): + if entry.name.startswith(basename) and re.match(r'\.tmp-[a-zA-Z0-9]{8}', entry.name[len(basename):]): + os.remove(entry.path) + + tmp_fn = filename + f'.tmp-' + ''.join(random.choices(string.ascii_letters + string.digits, k=8)) + with open(tmp_fn, 'w') as tmp_f: + yield tmp_f + tmp_f.flush() + os.fsync(tmp_f.fileno()) + os.rename(tmp_fn, filename) + +def write_zonefile(): + # Find the next free zonefile version number + with db as conn: + conn.execute('INSERT INTO zone_versions VALUES (DATE())') + date, version_num, = conn.execute('SELECT zone_versions.date, COUNT(*) FROM zone_versions WHERE zone_versions.date = DATE()').fetchone() + zone_version = f'{date.replace("-", "")}{version_num:02d}' + + # Generate dynamic record block + with db as conn: + records = db.execute('SELECT name, "A", ipv4 FROM records UNION SELECT name, "AAAA", ipv6 FROM records') + dynamic_records = '\n'.join(f'{name:<20} IN {rtype:<4} {value}' for name, rtype, value in records if value is not None) + + # Template zone file content + content = ZONEFILE_TEMPLATE.format( + zone = app.config['ZONE'], + ns = app.config['NAMESERVER'], + mail = app.config['NAMESERVER_MAIL'].replace('@', '.'), + serial = zone_version, + dynamic_records = dynamic_records, + expire = app.config['RECORD_EXPIRY_S'] + ) + + with inplace_rewrite(app.config['ZONEFILE'], cleanup=True) as f: + f.write(content) + +def kick_nsd(): + prog = app.config['NSD_CONTROL'] + if isinstance(prog, str): + prog = [prog] + subprocess.run([*prog, 'reload', app.config['ZONE']], check=True) + +@app.before_first_request +@uwsgidecorators.timer(300) +def update_zonefile(): + purge_expired_records() + write_zonefile() + kick_nsd() + +@app.route('/update', methods=['POST']) +def route_update(): + if request.authorization is None: + abort(403) + + record = request.authorization['username'] + record_config = app.config['DYNAMIC_RECORDS'].get(record) + if record_config is None: + abort(403) + + *supported_formats, password = record_config + if not hmac.compare_digest(request.authorization['password'], password): + abort(403) + + ipv4 = request.args.get('ipv4', '127.0.0.1') + ipv6 = request.args.get('ipv6', '::1') + ipv4 = str(IPv4Address(ipv4)) if 'v4' in supported_formats else None + ipv6 = str(IPv6Address(ipv6)) if 'v6' in supported_formats else None + if update_record(record, ipv4=ipv4, ipv6=ipv6): + update_zonefile() + + return 'success' + + +if __name__ == '__main__': + app.run() + diff --git a/dyndns_config.py.j2 b/dyndns_config.py.j2 new file mode 100644 index 0000000..3212a1e --- /dev/null +++ b/dyndns_config.py.j2 @@ -0,0 +1,14 @@ + +SQLITE_DB = '{{dyndns_sqlite_dbfile}}' + +NAMESERVER = 'ns.jaseg.de' +NAMESERVER_MAIL = 'dns@jaseg.de' +ZONEFILE = 'dyn.jaseg.de.zone' +ZONE = 'dyn.jaseg.de' +NSD_CONTROL = 'sudo -u nsd nsd-control'.split() + +DYNAMIC_RECORDS = { + 'bigdata': ('v6', '{{ lookup('password', 'dyndns_secret_bigdata.txt length=32') }}'), + 'raspi': ('v6', '{{ lookup('password', 'dyndns_secret_raspi.txt length=32') }}'), +} + diff --git a/nsd.conf b/nsd.conf new file mode 100644 index 0000000..d4b577f --- /dev/null +++ b/nsd.conf @@ -0,0 +1,372 @@ +# +# nsd.conf -- the NSD(8) configuration file, nsd.conf(5). +# +# Copyright (c) 2001-2011, NLnet Labs. All rights reserved. +# +# See LICENSE for the license. +# + +# This is a comment. +# Sample configuration file +# include: "file" # include that file's text over here. Globbed, "*.conf" + +# options for the nsd server +server: + # Number of NSD servers to fork. Put the number of CPUs to use here. + server-count: 1 + + # uncomment to specify specific interfaces to bind (default are the + # wildcard interfaces 0.0.0.0 and ::0). + # For servers with multiple IP addresses, list them one by one, + # or the source address of replies could be wrong. + # Use ip-transparent to be able to list addresses that turn on later. + # ip-address: 1.2.3.4 + # ip-address: 1.2.3.4@5678 + # ip-address: 12fe::8ef0 + + # Allow binding to non local addresses. Default no. + # ip-transparent: no + + # Allow binding to addresses that are down. Default no. + # ip-freebind: no + + # use the reuseport socket option for performance. Default no. + reuseport: yes + + # override maximum socket send buffer size. Default of 0 results in + # send buffer size being set to 1048576 (bytes). + # send-buffer-size: 1048576 + + # override maximum socket receive buffer size. Default of 0 results in + # receive buffer size being set to 1048576 (bytes). + # receive-buffer-size: 1048576 + + # enable debug mode, does not fork daemon process into the background. + # debug-mode: no + + # listen on IPv4 connections + # do-ip4: yes + + # listen on IPv6 connections + # do-ip6: yes + + # port to answer queries on. default is 53. + # port: 53 + + # Verbosity level. + # verbosity: 0 + + # After binding socket, drop user privileges. + # can be a username, id or id.gid. + # username: nsd + + # Run NSD in a chroot-jail. + # make sure to have pidfile and database reachable from there. + # by default, no chroot-jail is used. + # chroot: "/etc/nsd" + + # The directory for zonefile: files. The daemon chdirs here. + zonesdir: "/etc/nsd" + + # the list of dynamically added zones. + # zonelistfile: "/var/lib/nsd/zone.list" + + # the database to use + # if set to "" then no disk-database is used, less memory usage. + database: "" + + # log messages to file. Default to stderr and syslog (with + # facility LOG_DAEMON). stderr disappears when daemon goes to bg. + # logfile: "/var/log/nsd.log" + + # File to store pid for nsd in. + # pidfile: "/run/nsd/nsd.pid" + + # The file where secondary zone refresh and expire timeouts are kept. + # If you delete this file, all secondary zones are forced to be + # 'refreshing' (as if nsd got a notify). Set to "" to disable. + # xfrdfile: "/var/lib/nsd/ixfr.state" + + # The directory where zone transfers are stored, in a subdir of it. + # xfrdir: "/tmp" + + # don't answer VERSION.BIND and VERSION.SERVER CHAOS class queries + hide-version: yes + + # don't answer HOSTNAME.BIND and ID.SERVER CHAOS class queries + hide-identity: yes + + # version string the server responds with for chaos queries. + # default is 'NSD x.y.z' with the server's version number. + # version: "NSD" + + # identify the server (CH TXT ID.SERVER entry). + # identity: "unidentified server" + + # NSID identity (hex string, or "ascii_somestring"). default disabled. + # nsid: "aabbccdd" + + # Maximum number of concurrent TCP connections per server. + # tcp-count: 100 + + # Accept (and immediately close) TCP connections after maximum number + # of connections is reached to prevent kernel connection queue from + # growing. + # tcp-reject-overflow: no + + # Maximum number of queries served on a single TCP connection. + # By default 0, which means no maximum. + # tcp-query-count: 0 + + # Override the default (120 seconds) TCP timeout. + # tcp-timeout: 120 + + # Maximum segment size (MSS) of TCP socket on which the server + # responds to queries. Default is 0, system default MSS. + # tcp-mss: 0 + + # Maximum segment size (MSS) of TCP socket for outgoing AXFR request. + # Default is 0, system default MSS. + # outgoing-tcp-mss: 0 + + # Preferred EDNS buffer size for IPv4. + # ipv4-edns-size: 4096 + + # Preferred EDNS buffer size for IPv6. + # ipv6-edns-size: 4096 + + # statistics are produced every number of seconds. Prints to log. + # Default is 0, meaning no statistics are produced. + # statistics: 3600 + + # Number of seconds between reloads triggered by xfrd. + # xfrd-reload-timeout: 1 + + # log timestamp in ascii (y-m-d h:m:s.msec), yes is default. + # log-time-ascii: yes + + # round robin rotation of records in the answer. + round-robin: yes + + # minimal-responses only emits extra data for referrals. + minimal-responses: yes + + # Do not return additional information if the apex zone of the + # additional information is configured but does not match the apex zone + # of the initial query. + # confine-to-zone: no + + # refuse queries of type ANY. For stopping floods. + refuse-any: yes + + # check mtime of all zone files on start and sighup + # zonefiles-check: yes + + # write changed zonefiles to disk, every N seconds. + # default is 0(disabled) or 3600(if database is ""). + # zonefiles-write: 3600 + + # RRLconfig + # Response Rate Limiting, size of the hashtable. Default 1000000. + # rrl-size: 1000000 + + # Response Rate Limiting, maximum QPS allowed (from one query source). + # If set to 0, ratelimiting is disabled. Also set + # rrl-whitelist-ratelimit to 0 to disable ratelimit processing. + # Default is on. + # rrl-ratelimit: 200 + + # Response Rate Limiting, number of packets to discard before + # sending a SLIP response (a truncated one, allowing an honest + # resolver to retry with TCP). Default is 2 (one half of the + # queries will receive a SLIP response, 0 disables SLIP (all + # packets are discarded), 1 means every request will get a + # SLIP response. When the ratelimit is hit the traffic is + # divided by the rrl-slip value. + # rrl-slip: 2 + + # Response Rate Limiting, IPv4 prefix length. Addresses are + # grouped by netblock. + # rrl-ipv4-prefix-length: 24 + + # Response Rate Limiting, IPv6 prefix length. Addresses are + # grouped by netblock. + # rrl-ipv6-prefix-length: 64 + + # Response Rate Limiting, maximum QPS allowed (from one query source) + # for whitelisted types. Default is on. + # rrl-whitelist-ratelimit: 2000 + # RRLend + + # Optional local server config + include: "/etc/nsd/server.d/*.conf" + +# Include optional local configs. +include: "/etc/nsd/conf.d/*.conf" + +# Fedora: DNSTAP not yet enabled +# dnstap: + # set this to yes and set one or more of dnstap-log-..-messages to yes. + # dnstap-enable: no + # dnstap-socket-path: "/var/run/dnstap.sock" + # dnstap-send-identity: no + # dnstap-send-version: no + # dnstap-identity: "" + # dnstap-version: "" + # dnstap-log-auth-query-messages: no + # dnstap-log-auth-response-messages: no + + # Service clients over TLS (on the TCP sockets), with plain DNS inside + # the TLS stream. Give the certificate to use and private key. + # Default is "" (disabled). Requires restart to take effect. + # tls-service-key: "path/to/privatekeyfile.key" + # tls-service-pem: "path/to/publiccertfile.pem" + # tls-service-ocsp: "path/to/ocsp.pem" + # tls-port: 853 + +# Remote control config section. +remote-control: + # Enable remote control with nsd-control(8) here. + # set up the keys and certificates with nsd-control-setup. + control-enable: yes + + # what interfaces are listened to for control, default is on localhost. + # with an absolute path, a unix local named pipe is used for control + # (and key and cert files are not needed, use directory permissions). + # control-interface: 127.0.0.1 + # control-interface: ::1 + control-interface: /run/nsd/nsd.ctl + + # port number for remote control operations (uses TLS over TCP). + # control-port: 8952 + + # nsd server key file for remote control. + # server-key-file: "/etc/nsd/nsd_server.key" + + # nsd server certificate file for remote control. + # server-cert-file: "/etc/nsd/nsd_server.pem" + + # nsd-control key file. + # control-key-file: "/etc/nsd/nsd_control.key" + + # nsd-control certificate file. + # control-cert-file: "/etc/nsd/nsd_control.pem" + + +# Secret keys for TSIGs that secure zone transfers. +# You could include: "secret.keys" and put the 'key:' statements in there, +# and give that file special access control permissions. +# +# key: + # The key name is sent to the other party, it must be the same + #name: "keyname" + # algorithm hmac-md5, or sha1, sha256, sha224, sha384, sha512 + #algorithm: sha256 + # secret material, must be the same as the other party uses. + # base64 encoded random number. + # e.g. from dd if=/dev/random of=/dev/stdout count=1 bs=32 | base64 + #secret: "K2tf3TRjvQkVCmJF3/Z9vA==" + + +# Patterns have zone configuration and they are shared by one or more zones. +# +# pattern: + # name by which the pattern is referred to + #name: "myzones" + # the zonefile for the zones that use this pattern. + # if relative then from the zonesdir (inside the chroot). + # the name is processed: %s - zone name (as appears in zone:name). + # %1 - first character of zone name, %2 second, %3 third. + # %z - topleveldomain label of zone, %y, %x next labels in name. + # if label or character does not exist you get a dot '.'. + # for example "%s.zone" or "zones/%1/%2/%3/%s" or "secondary/%z/%s" + #zonefile: "%s.zone" + + # If no master and slave access control elements are provided, + # this zone will not be served to/from other servers. + + # A master zone needs notify: and provide-xfr: lists. A slave + # may also allow zone transfer (for debug or other secondaries). + # notify these slaves when the master zone changes, address TSIG|NOKEY + # IP can be ipv4 and ipv6, with @port for a nondefault port number. + #notify: 192.0.2.1 NOKEY + # allow these IPs and TSIG to transfer zones, addr TSIG|NOKEY|BLOCKED + # address range 192.0.2.0/24, 1.2.3.4&255.255.0.0, 3.0.2.20-3.0.2.40 + #provide-xfr: 192.0.2.0/24 my_tsig_key_name + # set the number of retries for notify. + #notify-retry: 5 + + # uncomment to provide AXFR to all the world + # provide-xfr: 0.0.0.0/0 NOKEY + # provide-xfr: ::0/0 NOKEY + + # A slave zone needs allow-notify: and request-xfr: lists. + #allow-notify: 2001:db8::0/64 my_tsig_key_name + # By default, a slave will request a zone transfer with IXFR/TCP. + # If you want to make use of IXFR/UDP use: UDP addr tsigkey + # for a master that only speaks AXFR (like NSD) use AXFR addr tsigkey + #request-xfr: 192.0.2.2 the_tsig_key_name + # Attention: You cannot use UDP and AXFR together. AXFR is always over + # TCP. If you use UDP, we higly recommend you to deploy TSIG. + # Allow AXFR fallback if the master does not support IXFR. Default + # is yes. + #allow-axfr-fallback: yes + # set local interface for sending zone transfer requests. + # default is let the OS choose. + #outgoing-interface: 10.0.0.10 + # limit the refresh and retry interval in seconds. + #max-refresh-time: 2419200 + #min-refresh-time: 0 + #max-retry-time: 1209600 + #min-retry-time: 0 + + # Slave server tries zone transfer to all masters and picks highest + # zone version available, for when masters have different versions. + #multi-master-check: no + + # limit the zone transfer size (in bytes), stops very large transfers + # 0 is no limits enforced. + # size-limit-xfr: 0 + + # if compiled with --enable-zone-stats, give name of stat block for + # this zone (or group of zones). Output from nsd-control stats. + # zonestats: "%s" + + # if you give another pattern name here, at this point the settings + # from that pattern are inserted into this one (as if it were a + # macro). The statement can be given in between other statements, + # because the order of access control elements can make a difference + # (which master to request from first, which slave to notify first). + #include-pattern: "common-masters" + + +# Fixed zone entries. Here you can config zones that cannot be deleted. +# Zones that are dynamically added and deleted are put in the zonelist file. +# +# zone: + # name: "example.com" + # you can give a pattern here, all the settings from that pattern + # are then inserted at this point + # include-pattern: "master" + # You can also specify (additional) options directly for this zone. + # zonefile: "example.com.zone" + # request-xfr: 192.0.2.1 example.com.key + + # RRLconfig + # Response Rate Limiting, whitelist types + # rrl-whitelist: nxdomain + # rrl-whitelist: error + # rrl-whitelist: referral + # rrl-whitelist: any + # rrl-whitelist: rrsig + # rrl-whitelist: wildcard + # rrl-whitelist: nodata + # rrl-whitelist: dnskey + # rrl-whitelist: positive + # rrl-whitelist: all + # RRLend + +zone: + name: "dyn.jaseg.de" + zonefile: "/var/lib/dyndns/dyn.jaseg.de.zone" + diff --git a/setup_dyndns.yml b/setup_dyndns.yml new file mode 100644 index 0000000..d9735c7 --- /dev/null +++ b/setup_dyndns.yml @@ -0,0 +1,80 @@ +--- +- name: Set local facts + set_fact: + dyndns_sqlite_dbfile: /var/lib/dyndns/db.sqlite3 + +- name: Copy nsd config + copy: + src: nsd.conf + dest: /etc/nsd/nsd.conf + owner: root + group: root + mode: 0644 + +- name: Enable and launch nsd systemd service + systemd: + name: nsd.service + enabled: yes + state: restarted + +- name: Create dyndns worker user and group + user: + name: uwsgi-dyndns + create_home: no + group: uwsgi + password: '!' + shell: /sbin/nologin + system: yes + +- name: Allow dyndns app to kick nsd + lineinfile: + path: /etc/sudoers + line: 'uwsgi-dyndns ALL=(nsd) NOPASSWD: /usr/sbin/nsd-control reload dyn.jaseg.de' + +- name: Create webapp dir + file: + path: /var/lib/dyndns + state: directory + owner: uwsgi-dyndns + group: nsd + mode: 0750 + +- name: Copy webapp sources + copy: + src: dyndns.py + dest: /var/lib/dyndns/ + owner: uwsgi-dyndns + group: uwsgi + mode: 0440 + +- name: Template webapp config + template: + src: dyndns_config.py.j2 + dest: /var/lib/dyndns/config.py + owner: uwsgi-dyndns + group: root + mode: 0660 + +- name: Copy uwsgi config + copy: + src: uwsgi-dyndns.ini + dest: /etc/uwsgi.d/dyndns.ini + owner: uwsgi-dyndns + group: uwsgi + mode: 0440 + +- name: Enable uwsgi systemd socket + systemd: + daemon-reload: yes + name: uwsgi-app@dyndns.socket + enabled: yes + +- name: Create sqlite db file + file: + path: "{{dyndns_sqlite_dbfile}}" + owner: uwsgi-dyndns + group: uwsgi + mode: 0660 + state: touch + + diff --git a/uwsgi-dyndns.ini b/uwsgi-dyndns.ini new file mode 100644 index 0000000..b62e2af --- /dev/null +++ b/uwsgi-dyndns.ini @@ -0,0 +1,10 @@ +[uwsgi] +master = True +cheap = True +die-on-idle = False +manage-script-name = True +log-format = [pid: %(pid)|app: -|req: -/-] %(addr) (%(user)) {%(vars) vars in %(pktsize) bytes} [%(ctime)] %(method) [URI hidden] => generated %(rsize) bytes in %(msecs) msecs (%(proto) %(status)) %(headers) headers in %(hsize) bytes (%(switches) switches on core %(core)) +plugins = python3 +chdir = /var/lib/dyndns +mount = /=dyndns:app + -- cgit