Vendor Galaxy Roles and Collections
Some checks failed
/ Ansible Lint (push) Failing after 5m45s
/ Ansible Lint (pull_request) Failing after 4m59s

This commit is contained in:
Stefan Bethke 2026-02-06 22:07:16 +01:00
commit 2aed20393f
3553 changed files with 387444 additions and 2 deletions

View file

@ -0,0 +1,20 @@
debops.dnsmasq - Install and configure dnsmasq
Copyright (C) 2014-2019 Maciej Delmanowski <drybjed@gmail.com>
Copyright (C) 2015-2017 Robin Schneider <ypid@riseup.net>
Copyright (C) 2014-2019 DebOps <https://debops.org/>
SPDX-License-Identifier: GPL-3.0-only
This Ansible role is part of DebOps.
DebOps is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 3, as
published by the Free Software Foundation.
DebOps is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with DebOps. If not, see https://www.gnu.org/licenses/.

View file

@ -0,0 +1,669 @@
---
# .. vim: foldmarker=[[[,]]]:foldmethod=marker
# .. Copyright (C) 2014-2019 Maciej Delmanowski <drybjed@gmail.com>
# .. Copyright (C) 2015-2017 Robin Schneider <ypid@riseup.net>
# .. Copyright (C) 2014-2019 DebOps <https://debops.org/>
# .. SPDX-License-Identifier: GPL-3.0-only
# .. _dnsmasq__ref_defaults:
# debops.dnsmasq default variables
# ================================
# .. contents:: Sections
# :local:
#
# .. include:: ../../../../includes/global.rst
# APT packages [[[
# ----------------
# .. envvar:: dnsmasq__base_packages [[[
#
# List of APT packages to install for :command:`dnsmasq` support.
dnsmasq__base_packages: [ 'dnsmasq' ]
# ]]]
# .. envvar:: dnsmasq__packages [[[
#
# List of additional APT packages to install during :command:`dnsmasq`
# configuration.
dnsmasq__packages: []
# ]]]
# ]]]
# Global options [[[
# ------------------
# .. envvar:: dnsmasq__dhcpv4 [[[
#
# Enable or disable DHCPv4 support.
dnsmasq__dhcpv4: True
# ]]]
# .. envvar:: dnsmasq__dhcpv6 [[[
#
# Enable or disable DHCPv6 support (router support is required).
dnsmasq__dhcpv6: True
# ]]]
# ]]]
# Configuration specific to network interfaces [[[
# ------------------------------------------------
# These variables define :command:`dnsmasq` configuration related to
# a particular network interface. See :ref:`dnsmasq__ref_interfaces` for more
# details.
# .. envvar:: dnsmasq__default_interfaces [[[
#
# List of network interfaces for which :command:`dnsmasq` configuration will be
# generated by default.
dnsmasq__default_interfaces:
# Name of the interface.
- name: 'br2'
state: '{{ "present"
if (hostvars[inventory_hostname]["ansible_br2"] is defined)
else "absent" }}'
# ]]]
# .. envvar:: dnsmasq__interfaces [[[
#
# List of network interfaces for which :command:`dnsmasq` configuration will be
# generated, defined by the user.
dnsmasq__interfaces: []
# ]]]
# .. envvar:: dnsmasq__combined_interfaces [[[
#
# Combined list of network interfaces for which :command:`dnsmasq`
# configuration will be generated, used in role tasks and templates.
dnsmasq__combined_interfaces: '{{ dnsmasq__default_interfaces
+ dnsmasq__interfaces }}'
# ]]]
# ]]]
# DNS options [[[
# ---------------
# .. envvar:: dnsmasq__hostname [[[
#
# The router hostname used by :command:`dnsmasq`. It will be used to configure
# DNS A and AAAA records pointing to the :command:`dnsmasq` server.
dnsmasq__hostname: '{{ ansible_hostname }}'
# ]]]
# .. envvar:: dnsmasq__base_domain [[[
#
# The DNS domain which will be used as a base for interface-based subdomains if
# none are explicitly configured.
dnsmasq__base_domain: '{{ ansible_domain }}'
# ]]]
# .. envvar:: dnsmasq__base_domain_rebind_ok [[[
#
# When ``True``, :command:`dnsmasq` will accept DNS records for the base DNS
# domain that specify IP addresses in private address ranges. This is needed
# when another DHCP/DNS server is managing the IP address leases, otherwise
# they will be unresolvable.
dnsmasq__base_domain_rebind_ok: True
# ]]]
# .. envvar:: dnsmasq__etc_hosts [[[
#
# List of absolute paths of the additional :man:`hosts(5)` database files to
# read by :command:`dnsmasq`. This is useful if you don't want to keep the host
# list in the system-wide :file:`/etc/hosts` database.
#
# You can provide the files using the :ref:`debops.resources` Ansible role,
# non-existent files will be silently ignored by :command:`dnsmasq`.
dnsmasq__etc_hosts: []
# ]]]
# .. envvar:: dnsmasq__nameservers [[[
#
# List of upstream DNS nameservers where dnsmasq should forward its DNS queries
# which it can not answer by itself. If empty, system nameservers (for example
# received via DHCP) will be used by default.
dnsmasq__nameservers: []
# ]]]
# .. envvar:: dnsmasq__public_dns [[[
#
# Enable or disable access to the local DNS from upstream networks using the
# firewall. You can publish your subdomain in the public DNS by delegating it
# in your zone configuration. Be wary of the DNS reflection and amplification
# attacks!
dnsmasq__public_dns: False
# ]]]
# .. envvar:: dnsmasq__public_dns_allow [[[
#
# List of IP addresses or CIDR subnets which are allowed to connect to
# :command:`dnsmasq` DNS service in public DNS mode. If the list is empty, any
# hosts can connect. The configuration will be set in the firewall.
dnsmasq__public_dns_allow: []
# ]]]
# ]]]
# TFTP options [[[
# ----------------
# .. envvar:: dnsmasq__boot_enabled [[[
#
# Enable or disable support for BOOTP/PXE boot of remote hosts.
dnsmasq__boot_enabled: True
# ]]]
# .. envvar:: dnsmasq__boot_ipxe_enabled [[[
#
# Enable or disable support for iPXE boot menu. The iPXE configuration can be
# done using the :ref:`debops.ipxe` Ansible role.
dnsmasq__boot_ipxe_enabled: True
# ]]]
# .. envvar:: dnsmasq__boot_server [[[
#
# Specify the IP address of the "next server" which provides a TFTP service
# with boot files. If not specified, :command:`dnsmasq` server will be
# contacted by the clients instead for the boot files.
dnsmasq__boot_server: ''
# ]]]
# .. envvar:: dnsmasq__boot_tftp_root [[[
#
# Absolute path of the TFTP root directory from which boot files will be
# served. This path needs to exist, otherwise :command:`dnsmasq` service will
# refuse to start.
dnsmasq__boot_tftp_root: '/srv/tftp'
# ]]]
# .. envvar:: dnsmasq__boot_filename [[[
#
# Name of the file located in the TFTP root directory which will be sent to
# clients telling them which file to download and boot from.
dnsmasq__boot_filename: '{{ "menu.ipxe" if dnsmasq__boot_ipxe_enabled | bool else "pxelinux.0" }}'
# ]]]
# ]]]
# DHCP hosts, DNS resource records [[[
# ------------------------------------
# The variables below can be used to configure DHCP client entries as well as
# DNS resource records published by :command:`dnsmasq`; syntax for both of
# these variables is the same. See :ref:`dnsmasq__ref_dhcp_dns_entries` for
# more details.
# .. envvar:: dnsmasq__dhcp_hosts [[[
#
# List of DHCP clients that :command:`dnsmasq` knows about.
dnsmasq__dhcp_hosts: []
# ]]]
# .. envvar:: dnsmasq__dns_records [[[
#
# List of DNS resource records that :command:`dnsmasq` publishes.
dnsmasq__dns_records: []
# ]]]
# .. envvar:: dnsmasq__dhcp_dns_filename [[[
#
# Name of the configuration file that contains DHCP and DNS entries, located in
# the :file:`/etc/dnsmasq.d/` directory.
dnsmasq__dhcp_dns_filename: 'host-resource-records.conf'
# ]]]
# ]]]
# The dnsmasq configuration files [[[
# -----------------------------------
# These variables define the contents of the :command:`dnsmasq` configuration
# files located in the :file:`/etc/dnsmasq.d/` directory.
# See :ref:`dnsmasq__ref_configuration` for more details.
# .. envvar:: dnsmasq__default_configuration [[[
#
# The configuration defined by the role by default.
dnsmasq__default_configuration:
# Remove the old default configuration file
- name: '00_main.conf'
state: 'absent'
- name: 'global.conf'
options:
- name: 'conntrack'
comment: 'Enable connection tracking support for firewalls'
raw: |
conntrack
state: 'present'
- name: 'enable-ra'
comment: 'Enable support for IPv6 Router Advertisements using dnsmasq'
raw: |
enable-ra
state: 'present'
- name: 'bind-interfaces'
comment: |
Bind only to the network interfaces explicitly set in the
configuration. This is required to allow additional dnsmasq instances
managed by, for example, libvirt.
raw: |
bind-dynamic
state: 'present'
- name: 'loopback-interface'
comment: 'Bind to loopback interface for local DNS queries'
raw: |
interface = lo
no-dhcp-interface = lo
state: 'present'
- name: 'addn-hosts'
comment: 'Read hosts information from additional files or directories'
value: '{{ dnsmasq__etc_hosts }}'
state: '{{ "present" if dnsmasq__etc_hosts | d() else "absent" }}'
- name: 'consul.conf'
comment: |
Support for Consul Agent DNS service on localhost
Ref: https://www.consul.io/docs/agent/dns.html
raw: |
server = /consul/127.0.0.1#8600
state: '{{ "present"
if (ansible_local.consul.installed | d() | bool)
else "init" }}'
- name: 'lxd-override'
filename: 'lxd'
comment: |
Tell any system-wide dnsmasq instance to make sure to bind to interfaces
instead of listening on 0.0.0.0
raw: |
bind-dynamic
except-interface = lxdbr0
state: '{{ "present" if (ansible_distribution == "Ubuntu") else "ignore" }}'
- name: 'reserved-domains.conf'
options:
- name: 'reserved-domains'
comment: |
Do not forward the reserved top level domains to upstream nameservers
raw: |
# Ref: https://tools.ietf.org/html/rfc2606
local = /test/example/invalid/
# Ref: https://tools.ietf.org/html/rfc6762
local = /local/
# Ref: https://tools.ietf.org/html/rfc7686
local = /onion/
state: 'present'
- name: 'private-domains'
comment: |
Do not forward the following private top level DNS names to upstream
DNS servers because RFC 6762 recommends not to use unregistered
top-level domains (https://tools.ietf.org/html/rfc6762#appendix-G)
raw: |
local = /intranet/internal/private/corp/home/lan/
state: 'present'
- name: 'block-dns-over-https'
comment: |
Blocking the 'use-application-dns.net' domain instructs the applications
that support DNS over HTTPS to not use it and rely on the system resolver
instead. This might be required for certain applications to support
access to internal services, resolve split-DNS correctly, etc.
Ref: https://support.mozilla.org/en-US/kb/canary-domain-use-application-dnsnet
raw: |
server = /use-application-dns.net/
state: 'present'
- name: 'dns-global.conf'
options:
- name: 'localise-queries'
comment: |
Return localized answers to DNS queries from '/etc/hosts' depending
on the originating network interface
raw: |
localise-queries
state: 'present'
- name: 'domain-needed'
comment: |
Never forward plain hostname queries for A or AAAA records to
upstream servers
raw: |
domain-needed
state: 'present'
- name: 'expand-hosts'
comment: |
Expand short hostnames found in the '/etc/hosts' file to full FQDN
addresses
raw: |
expand-hosts
state: 'present'
- name: 'stop-dns-rebind'
comment: |
Reject addresses from the upstream DNS nameservers which are located
in the private IP address ranges
raw: |
stop-dns-rebind
state: 'present'
- name: 'rebind-localhost-ok'
comment: |
Skip rebinding checks for '127.0.0.0/8' IP address range. This range
is used by the realtime black hole (RBL) servers.
raw: |
rebind-localhost-ok
state: 'present'
- name: 'rebind-local-domain-ok'
comment: |
Skip rebinding checks for local domain, in case dnsmasq is used as
a DNS cache and forwarder on a host that is a part of a network with
private IP address ranges, with a different DHCP/DNS server
maintaining the leases.
option: 'rebind-domain-ok'
value: '{{ dnsmasq__base_domain }}'
state: '{{ "present"
if (dnsmasq__base_domain_rebind_ok | bool and
dnsmasq__base_domain | d())
else "absent" }}'
- name: 'rebind-parent-domain-ok'
comment: |
Skip rebinding checks for the parent domain if it has 4 or more
levels, which is most likely an internal domain on a network with
private IP address ranges.
option: 'rebind-domain-ok'
value: '{{ dnsmasq__base_domain.split(".")[1:] | join(".") }}'
state: '{{ "present"
if (dnsmasq__base_domain_rebind_ok | bool and
dnsmasq__base_domain | d() and
(dnsmasq__base_domain.split(".") | length >= 4))
else "absent" }}'
- name: 'bogus-priv'
comment: |
Do not forward reverse DNS queries for private IP addresses to
upstream DNS servers.
When an LXC network support is enabled, this parameter is commented
out to allow revDNS queries. It will also be commented out when
upstream nameservers are located in a private network to allow DNS
queries to reach them. Ref: https://bugs.debian.org/461054
raw: |
bogus-priv
state: '{{ "comment"
if ((ansible_local.lxc.net_domain | d()) or
(ansible_local.resolvconf.upstream_nameservers
| d(ansible_dns.nameservers)
| ansible.utils.ipaddr("private")))
else "present" }}'
- name: 'resolv-file'
comment: |
Use custom list of nameservers instead of the system upstream
nameservers
value: '/etc/resolvconf/upstream.conf'
state: '{{ "present" if dnsmasq__nameservers | d() else "absent" }}'
- name: 'lxc-net.conf'
comment: |
Support for resolving LXC container hosts that use the 'lxc-net' bridge
configuration
options:
- name: 'local'
value: '{{ "/" + (ansible_local.lxc.net_domain | d(""))
+ "/" + ansible_local.lxc.net_address | d("") }}'
# Create a separate 'lxc' host record that points to the 'lxcbr0'
# interface from the outside, if there's no external domain set.
- name: 'host-record'
value: '{{ ansible_local.lxc.net_domain | d("")
+ "," + ansible_local.lxc.net_address | d("") }}'
state: '{{ "present"
if ("." not in ansible_local.lxc.net_domain | d())
else "absent" }}'
- name: 'rev-server'
value: '{{ ansible_local.lxc.net_subnet | d("")
+ "," + ansible_local.lxc.net_address | d("") }}'
- name: 'rebind-domain-ok'
value: '{{ ansible_local.lxc.net_domain | d("") }}'
state: '{{ "present"
if (ansible_local.lxc.net_domain | d())
else "init" }}'
- name: 'dhcp-boot.conf'
comment: |
This configuration file contains dnsmasq options related to booting
remote hosts using iPXE boot menu
options:
- name: 'dhcp-match-ipxe'
comment: |
Tag all DHCP requests with option 175 as coming from iPXE to avoid
recursive loops
option: 'dhcp-match'
value: 'set:ipxe,175'
- name: 'dhcp-match-d-i'
comment: |
Tag all DHCP requests with 'd-i' vendor class as coming from the
Debian Installer
option: 'dhcp-match'
value: 'set:debian-installer,option:vendor-class,"d-i"'
- name: 'vendor-match'
comment: |
Inspect the vendor class string and match the text to set the tag
Ref: https://tools.ietf.org/html/rfc4578#section-2.1
raw: |
dhcp-vendorclass = BIOS,PXEClient:Arch:00000
dhcp-vendorclass = UEFI32,PXEClient:Arch:00006
dhcp-vendorclass = UEFI,PXEClient:Arch:00007
dhcp-vendorclass = UEFI64,PXEClient:Arch:00009
- name: 'boot-ipxe-local'
comment: 'Set the boot file name based on the matching tag from the vendor class (above)'
raw: |
{% if dnsmasq__boot_ipxe_enabled | bool %}
# Redirect non-iPXE clients to iPXE
dhcp-boot = tag:!ipxe,tag:BIOS,undionly.kpxe
dhcp-boot = tag:!ipxe,tag:UEFI32,i386-efi/ipxe.efi
dhcp-boot = tag:!ipxe,tag:UEFI,ipxe.efi
dhcp-boot = tag:!ipxe,tag:UEFI64,ipxe.efi
# Load the main menu in iPXE clients
{% endif %}
dhcp-boot = {{ dnsmasq__boot_filename }}
state: '{{ "absent" if dnsmasq__boot_server | d() else "present" }}'
- name: 'boot-ipxe-remote'
comment: 'Set the boot file name based on the matching tag from the vendor class (above)'
raw: |
{% if dnsmasq__boot_ipxe_enabled | bool %}
# Redirect non-iPXE clients to iPXE
dhcp-boot = tag:!ipxe,tag:BIOS,undionly.kpxe,,{{ dnsmasq__boot_server }}
dhcp-boot = tag:!ipxe,tag:UEFI32,i386-efi/ipxe.efi,,{{ dnsmasq__boot_server }}
dhcp-boot = tag:!ipxe,tag:UEFI,ipxe.efi,,{{ dnsmasq__boot_server }}
dhcp-boot = tag:!ipxe,tag:UEFI64,ipxe.efi,,{{ dnsmasq__boot_server }}
# Load the main menu in iPXE clients
{% endif %}
dhcp-boot = {{ dnsmasq__boot_filename }},,{{ dnsmasq__boot_server }}
state: '{{ "present" if dnsmasq__boot_server | d() else "absent" }}'
state: '{{ "present" if dnsmasq__boot_enabled | bool else "init" }}'
# ]]]
# .. envvar:: dnsmasq__interface_configuration [[[
#
# Automatically generated :command:`dnsmasq` configuration for each of the
# network interfaces defined in the :envvar:`dnsmasq__combined_interfaces`
# variable.
dnsmasq__interface_configuration: '{{ lookup("template", "lookup/dnsmasq__interface_configuration.j2",
convert_data=False) | from_yaml }}'
# ]]]
# .. envvar:: dnsmasq__configuration [[[
#
# The configuration which should be present on all hosts in the Ansible
# inventory.
dnsmasq__configuration: []
# ]]]
# .. envvar:: dnsmasq__group_configuration [[[
#
# The configuration which should be present on hosts in a specific Ansible
# inventory group.
dnsmasq__group_configuration: []
# ]]]
# .. envvar:: dnsmasq__host_configuration [[[
#
# The configuration which should be present on specific hosts in the Ansible
# inventory.
dnsmasq__host_configuration: []
# ]]]
# .. envvar:: dnsmasq__combined_configuration [[[
#
# The variable which combines all of the other configuration variables and is
# used in the Ansible tasks.
dnsmasq__combined_configuration: '{{ dnsmasq__default_configuration
+ dnsmasq__interface_configuration
+ dnsmasq__configuration
+ dnsmasq__group_configuration
+ dnsmasq__host_configuration }}'
# ]]]
# ]]]
# Configuration for other Ansible roles [[[
# -----------------------------------------
# .. envvar:: dnsmasq__ferm__dependent_rules [[[
#
# Configuration for the :ref:`debops.ferm` Ansible role.
dnsmasq__ferm__dependent_rules:
- type: 'accept'
by_role: 'debops.dnsmasq'
name: 'dns'
weight: '40'
protocol: [ 'udp', 'tcp' ]
saddr: '{{ dnsmasq__public_dns_allow }}'
dport: [ 'domain' ]
accept_any: True
interface: '{{ []
if (dnsmasq__public_dns | bool)
else (dnsmasq__combined_interfaces | flatten | debops.debops.parse_kv_items
| selectattr("state", "equalto", "present")
| map(attribute="name") | list) }}'
rule_state: '{{ "present"
if ((dnsmasq__public_dns | bool) or
(dnsmasq__combined_interfaces | flatten | debops.debops.parse_kv_items
| selectattr("state", "equalto", "present")
| map(attribute="name") | list))
else "absent" }}'
- type: 'accept'
by_role: 'debops.dnsmasq'
name: 'dhcpv4'
weight: '40'
protocol: [ 'udp' ]
dport: [ 'bootps' ]
interface: '{{ dnsmasq__combined_interfaces | flatten | debops.debops.parse_kv_items
| selectattr("state", "equalto", "present")
| map(attribute="name") | list }}'
rule_state: '{{ "present"
if (dnsmasq__dhcpv4 | bool and
(dnsmasq__combined_interfaces | flatten | debops.debops.parse_kv_items
| selectattr("state", "equalto", "present")
| map(attribute="name") | list))
else "absent" }}'
- type: 'accept'
by_role: 'debops.dnsmasq'
name: 'dhcpv6'
weight: '40'
saddr: [ 'fe80::/10' ]
daddr: [ 'ff02::1:2' ]
# https://tools.ietf.org/html/rfc3315#section-13
protocol: [ 'udp' ]
sport: [ 'dhcpv6-client' ]
dport: [ 'dhcpv6-server' ]
interface: '{{ dnsmasq__combined_interfaces | flatten | debops.debops.parse_kv_items
| selectattr("state", "equalto", "present")
| map(attribute="name") | list }}'
rule_state: '{{ "present"
if (dnsmasq__dhcpv6 | bool and
(dnsmasq__combined_interfaces | flatten | debops.debops.parse_kv_items
| selectattr("state", "equalto", "present")
| map(attribute="name") | list))
else "absent" }}'
- type: 'accept'
by_role: 'debops.dnsmasq'
filename: 'tftp'
weight: '40'
dport: [ 'tftp' ]
protocol: [ 'udp' ]
interface: '{{ dnsmasq__combined_interfaces | flatten | debops.debops.parse_kv_items
| selectattr("state", "equalto", "present")
| map(attribute="name") | list }}'
rule_state: '{{ "present"
if (dnsmasq__boot_enabled | bool and
(dnsmasq__combined_interfaces | flatten | debops.debops.parse_kv_items
| selectattr("state", "equalto", "present")
| map(attribute="name") | list))
else "absent" }}'
# ]]]
# .. envvar:: dnsmasq__tcpwrappers__dependent_allow [[[
#
# Configuration for the :ref:`debops.tcpwrappers` Ansible role.
dnsmasq__tcpwrappers__dependent_allow: '{{ lookup("template", "lookup/dnsmasq__tcpwrappers__dependent_allow.j2",
convert_data=False) | from_yaml }}'
# ]]]
# .. envvar:: dnsmasq__apparmor__local_dependent_config [[[
#
# Configuration for the ``debops-contrib.apparmor`` Ansible role.
dnsmasq__apparmor__local_dependent_config:
'usr.sbin.dnsmasq':
- comment: 'Allow dnsmasq to read upstream DNS servers'
rules:
- '/etc/resolvconf/upstream.conf r'
- '/etc/hosts.dnsmasq r'
- comment: 'Allow dnsmasq to read /usr/share/dnsmasq-base/trust-anchors.conf provided by dnsmasq-base'
rules:
- '/usr/share/dnsmasq-base/* r'
# ]]]
# .. envvar:: dnsmasq__persistent_paths__dependent_paths [[[
#
# Configuration for the :ref:`debops.persistent_paths` Ansible role.
dnsmasq__persistent_paths__dependent_paths:
'50_debops_dnsmasq':
by_role: 'debops.dnsmasq'
paths:
- '/etc/ansible'
- '/etc/dnsmasq.d'
- '/etc/default/dnsmasq'
- '/etc/resolvconf/upstream.conf'
- '/etc/hosts.dnsmasq'
# ]]]
# ]]]

View file

@ -0,0 +1,33 @@
---
# Copyright (C) 2014-2019 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2015-2017 Robin Schneider <ypid@riseup.net>
# Copyright (C) 2014-2022 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
# Ensure that custom Ansible plugins and modules included in the main DebOps
# collection are available to roles in other collections.
collections: [ 'debops.debops' ]
dependencies: []
galaxy_info:
author: 'Maciej Delmanowski, Robin Schneider'
description: 'Install and configure dnsmasq'
company: 'DebOps'
license: 'GPL-3.0-only'
min_ansible_version: '2.6.0'
platforms:
- name: 'Ubuntu'
versions: [ 'all' ]
- name: 'Debian'
versions: [ 'all' ]
galaxy_tags:
- dhcp
- dns
- pxe
- tftp

View file

@ -0,0 +1,115 @@
---
# Copyright (C) 2014-2019 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2015-2017 Robin Schneider <ypid@riseup.net>
# Copyright (C) 2014-2019 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
- name: Import custom Ansible plugins
ansible.builtin.import_role:
name: 'ansible_plugins'
- name: Import DebOps global handlers
ansible.builtin.import_role:
name: 'global_handlers'
- name: Install required packages
ansible.builtin.package:
name: '{{ q("flattened", (dnsmasq__base_packages + dnsmasq__packages)) }}'
state: 'present'
register: dnsmasq__register_packages
until: dnsmasq__register_packages is succeeded
- name: Make sure Ansible fact directory exists
ansible.builtin.file:
path: '/etc/ansible/facts.d'
state: 'directory'
owner: 'root'
group: 'root'
mode: '0755'
- name: Generate dnsmasq Ansible local facts
ansible.builtin.template:
src: 'etc/ansible/facts.d/dnsmasq.fact.j2'
dest: '/etc/ansible/facts.d/dnsmasq.fact'
owner: 'root'
group: 'root'
mode: '0755'
unsafe_writes: '{{ True if (core__unsafe_writes | d(ansible_local.core.unsafe_writes | d()) | bool) else omit }}'
notify: [ 'Refresh host facts' ]
tags: [ 'meta::facts' ]
- name: Reload facts if they were modified
ansible.builtin.meta: 'flush_handlers'
# This directory is required, otherwise dnsmasq won't start when TFTP support
# is enabled
- name: Make sure TFTP root directory exists
ansible.builtin.file:
path: '{{ dnsmasq__boot_tftp_root }}'
state: 'directory'
owner: 'root'
group: 'root'
mode: '0755'
when: dnsmasq__boot_enabled | bool
- name: Remove dnsmasq configuration if requested
ansible.builtin.file:
path: '/etc/dnsmasq.d/{{ item.filename | d(item.name | regex_replace("\.conf$", "") + ".conf") }}'
state: 'absent'
with_items: '{{ dnsmasq__combined_configuration | debops.debops.parse_kv_items }}'
notify: [ 'Test and restart dnsmasq' ]
when: (item.name | d() and item.state | d('present') == 'absent')
- name: Generate dnsmasq configuration
ansible.builtin.template:
src: 'etc/dnsmasq.d/template.conf.j2'
dest: '/etc/dnsmasq.d/{{ item.filename | d(item.name | regex_replace(".conf$", "") + ".conf") }}'
owner: 'root'
group: 'root'
mode: '0644'
with_items: '{{ dnsmasq__combined_configuration | debops.debops.parse_kv_items }}'
notify: [ 'Test and restart dnsmasq' ]
when: (item.name | d() and item.state | d('present') not in ['absent', 'ignore', 'init'])
- name: Remove DHCP host configuration and DNS records if requested
ansible.builtin.file:
path: '/etc/dnsmasq.d/{{ dnsmasq__dhcp_dns_filename }}'
state: 'absent'
notify: [ 'Test and restart dnsmasq' ]
when: not dnsmasq__dhcp_hosts | d() and not dnsmasq__dns_records | d()
- name: Generate DHCP host configuration and DNS records
ansible.builtin.template:
src: 'etc/dnsmasq.d/dhcp-dns-options.conf.j2'
dest: '/etc/dnsmasq.d/{{ dnsmasq__dhcp_dns_filename }}'
owner: 'root'
group: 'root'
mode: '0644'
notify: [ 'Test and restart dnsmasq' ]
when: dnsmasq__dhcp_hosts | d() or dnsmasq__dns_records | d()
- name: Divert original dnsmasq environment file
debops.debops.dpkg_divert:
path: '/etc/default/dnsmasq'
notify: [ 'Test and restart dnsmasq' ]
- name: Configure dnsmasq environment file
ansible.builtin.template:
src: 'etc/default/dnsmasq.j2'
dest: '/etc/default/dnsmasq'
owner: 'root'
group: 'root'
mode: '0644'
unsafe_writes: '{{ True if (core__unsafe_writes | d(ansible_local.core.unsafe_writes | d()) | bool) else omit }}'
notify: [ 'Test and restart dnsmasq' ]
- name: Configure custom nameservers in resolvconf
ansible.builtin.template:
src: 'etc/resolvconf/upstream.conf.j2'
dest: '/etc/resolvconf/upstream.conf'
owner: 'root'
group: 'root'
mode: '0644'
unsafe_writes: '{{ True if (core__unsafe_writes | d(ansible_local.core.unsafe_writes | d()) | bool) else omit }}'
notify: [ 'Test and restart dnsmasq' ]
when: dnsmasq__nameservers | d()

View file

@ -0,0 +1,9 @@
---
# Copyright (C) 2014-2019 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2015-2017 Robin Schneider <ypid@riseup.net>
# Copyright (C) 2014-2019 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
- name: Prepare environment for dependent Ansible roles
ansible.builtin.set_fact:
dnsmasq__env_tcpwrappers__dependent_allow: '{{ dnsmasq__tcpwrappers__dependent_allow }}'

View file

@ -0,0 +1,37 @@
#!{{ ansible_python['executable'] }}
# -*- coding: utf-8 -*-
# Copyright (C) 2014-2019 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2015-2017 Robin Schneider <ypid@riseup.net>
# Copyright (C) 2014-2019 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
# {{ ansible_managed }}
from __future__ import print_function
from json import dumps, loads
import subprocess
import os
def cmd_exists(cmd):
return any(
os.access(os.path.join(path, cmd), os.X_OK)
for path in os.environ["PATH"].split(os.pathsep)
)
output = loads('''{{ {"boot_tftp_root": dnsmasq__boot_tftp_root}
| to_nice_json }}''')
output['installed'] = cmd_exists('dnsmasq')
try:
version_stdout = subprocess.check_output(
["dpkg-query", "-W", "-f=${Version}",
"dnsmasq"]).decode('utf-8').split('-')[0]
output['version'] = version_stdout
except Exception:
pass
print(dumps(output, sort_keys=True, indent=4))

View file

@ -0,0 +1,44 @@
{# Copyright (C) 2014-2019 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2015-2017 Robin Schneider <ypid@riseup.net>
# Copyright (C) 2014-2019 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
#}
# {{ ansible_managed }}
# This file has five functions:
# 1) to completely disable starting dnsmasq,
# 2) to set DOMAIN_SUFFIX by running `dnsdomainname`
# 3) to select an alternative config file
# by setting DNSMASQ_OPTS to --conf-file=<file>
# 4) to tell dnsmasq to read the files in /etc/dnsmasq.d for
# more configuration variables.
# 5) to stop the resolvconf package from controlling dnsmasq's
# idea of which upstream nameservers to use.
# For upgraders from very old versions, all the shell variables set
# here in previous versions are still honored by the init script
# so if you just keep your old version of this file nothing will break.
#DOMAIN_SUFFIX=`dnsdomainname`
#DNSMASQ_OPTS="--conf-file=/etc/dnsmasq.alt"
# Whether or not to run the dnsmasq daemon; set to 0 to disable.
ENABLED=1
# By default search this drop directory for configuration options.
# Libvirt leaves a file here to make the system dnsmasq play nice.
# Comment out this line if you don't want this. The dpkg-* are file
# endings which cause dnsmasq to skip that file. This avoids pulling
# in backups made by dpkg.
CONFIG_DIR=/etc/dnsmasq.d,.dpkg-dist,.dpkg-old,.dpkg-new
# If the resolvconf package is installed, dnsmasq will use its output
# rather than the contents of /etc/resolv.conf to find upstream
# nameservers. Uncommenting this line inhibits this behaviour.
# Note that including a "resolv-file=<filename>" line in
# /etc/dnsmasq.conf is not enough to override resolvconf if it is
# installed: the line below must be uncommented.
{% if dnsmasq__nameservers | d() %}
IGNORE_RESOLVCONF=yes
{% else %}
#IGNORE_RESOLVCONF=yes
{% endif %}

View file

@ -0,0 +1,156 @@
{# Copyright (C) 2014-2019 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2015-2017 Robin Schneider <ypid@riseup.net>
# Copyright (C) 2014-2019 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
#}
# {{ ansible_managed }}
# DHCP host configuration and DNS records
{% for element in (dnsmasq__dhcp_hosts + dnsmasq__dns_records) %}
{% set entry = {} %}
{% for k, v in element.items() %}
{% set _ = entry.update({k.lower(): v}) %}
{% endfor %}
{% if entry.state | d('present') != 'absent' %}
{% set dhcp_host = [] %}
{% set host_record = [] %}
{% set cname = [] %}
{% set record_command = '' %}
{% set record_content = [] %}
{% if entry.hwaddr | d() %}
{% set _ = dhcp_host.extend( ([ entry.hwaddr ] if (entry.hwaddr is string) else entry.hwaddr) ) %}
{% elif entry.mac | d() %}
{% set _ = dhcp_host.extend( ([ entry.mac ] if (entry.mac is string) else entry.mac) ) %}
{% endif %}
{% if entry.id | d() %}
{% set _ = dhcp_host.append('id:' + entry.id) %}
{% endif %}
{% if entry.tag | d() %}
{% set _ = dhcp_host.append('set:' + entry.tag) %}
{% endif %}
{% if entry.ip | d() and (entry.host is undefined and entry.a is undefined and entry.aaaa is undefined) %}
{% set _ = dhcp_host.extend( ([ entry.ip ] if (entry.ip is string) else entry.ip) | ansible.utils.ipwrap ) %}
{% elif entry.ipaddr | d() and (entry.host is undefined and entry.a is undefined and entry.aaaa is undefined) %}
{% set _ = dhcp_host.extend( ([ entry.ipaddr ] if (entry.ipaddr is string) else entry.ipaddr) | ansible.utils.ipwrap ) %}
{% elif entry.address | d() and (entry.host is undefined and entry.a is undefined and entry.aaaa is undefined) %}
{% set _ = dhcp_host.extend( ([ entry.address ] if (entry.address is string) else entry.address) | ansible.utils.ipwrap ) %}
{% endif %}
{% if entry.name | d() %}
{% set _ = dhcp_host.append(entry.name) %}
{% elif entry.hostname | d() %}
{% set _ = dhcp_host.append(entry.hostname) %}
{% endif %}
{% if entry.lease | d() %}
{% set _ = dhcp_host.append(entry.lease) %}
{% elif entry.lease_time | d() %}
{% set _ = dhcp_host.append(entry.lease_time) %}
{% endif %}
{% if (entry.ignore | d()) | bool %}
{% set _ = dhcp_host.append('ignore') %}
{% endif %}
{% if entry.domain | d() and (entry.ip | d() or entry.ipaddr | d() or entry.address | d()) %}
{% if entry.name | d() %}
{% set host_name = entry.name %}
{% elif entry.hostname | d() %}
{% set host_name = entry.hostname %}
{% endif %}
{% set _ = host_record.append(host_name + '.' + entry.domain) %}
{% if entry.ip | d() %}
{% set _ = host_record.extend( ([ entry.ip ] if (entry.ip is string) else entry.ip) | ansible.utils.ipwrap ) %}
{% elif entry.ipaddr | d() %}
{% set _ = host_record.extend( ([ entry.ipaddr ] if (entry.ipaddr is string) else entry.ipaddr) | ansible.utils.ipwrap ) %}
{% elif entry.address | d() %}
{% set _ = host_record.extend( ([ entry.address ] if (entry.address is string) else entry.address) | ansible.utils.ipwrap ) %}
{% endif %}
{% endif %}
{% if (entry.host | d() or entry.a | d() or entry.aaaa | d()) and (entry.ip | d() or entry.ipaddr | d() or entry.address | d() or entry.target | d()) %}
{% set record_command = 'host-record' %}
{% if entry.host | d() %}
{% set _ = record_content.extend( ([ entry.host ] if (entry.host is string) else entry.host) ) %}
{% elif entry.a | d() %}
{% set _ = record_content.extend( ([ entry.a ] if (entry.a is string) else entry.a) ) %}
{% elif entry.aaaa | d() %}
{% set _ = record_content.extend( ([ entry.aaaa ] if (entry.aaaa is string) else entry.aaaa) ) %}
{% endif %}
{% if entry.ip | d() %}
{% set _ = record_content.extend( ([ entry.ip ] if (entry.ip is string) else entry.ip) ) %}
{% elif entry.ipaddr | d() %}
{% set _ = record_content.extend( ([ entry.ipaddr ] if (entry.ipaddr is string) else entry.ipaddr) ) %}
{% elif entry.address | d() %}
{% set _ = record_content.extend( ([ entry.address ] if (entry.address is string) else entry.address) ) %}
{% elif entry.target | d() %}
{% set _ = record_content.extend( ([ entry.target ] if (entry.target is string) else entry.target) ) %}
{% endif %}
{% elif entry.txt | d() and (entry.value | d() or entry.target | d()) %}
{% set record_command = 'txt-record' %}
{% set _ = record_content.extend( [ entry.txt ] ) %}
{% if entry.value | d() %}
{% set _ = record_content.extend( ([ entry.value ] if (entry.value is string) else entry.value) ) %}
{% elif entry.target | d() %}
{% set _ = record_content.extend( ([ entry.target ] if (entry.target is string) else entry.target) ) %}
{% endif %}
{% elif entry.cname | d() and entry.target | d() %}
{% set record_command = 'cname' %}
{% set _ = record_content.extend( [ entry.cname ] ) %}
{% set _ = record_content.extend( [ entry.target ] ) %}
{% elif entry.ptr | d() and entry.target | d() %}
{% set record_command = 'ptr-record' %}
{% set _ = record_content.extend( [ entry.ptr ] ) %}
{% set _ = record_content.extend( [ entry.target ] ) %}
{% elif entry.mx | d() %}
{% set record_command = 'mx-host' %}
{% set _ = record_content.extend( [ entry.mx ] ) %}
{% if entry.target | d() %}
{% set _ = record_content.extend( [ entry.target ] ) %}
{% endif %}
{% if entry.preference | d() %}
{% set _ = record_content.extend( [ entry.preference ] ) %}
{% elif entry.priority | d() %}
{% set _ = record_content.extend( [ entry.priority ] ) %}
{% endif %}
{% elif entry.srv | d() and entry.target | d() %}
{% set record_command = 'srv-host' %}
{% set _ = record_content.extend( [ entry.srv ] ) %}
{% set _ = record_content.extend( [ entry.target ] ) %}
{% if entry.port | d() %}
{% set _ = record_content.extend( [ entry.port ] ) %}
{% endif %}
{% if entry.preference | d() %}
{% set _ = record_content.extend( [ entry.preference ] ) %}
{% elif entry.priority | d() %}
{% set _ = record_content.extend( [ entry.priority ] ) %}
{% endif %}
{% if entry.weight | d() %}
{% set _ = record_content.extend( [ entry.weight ] ) %}
{% endif %}
{% endif %}
{% if (dhcp_host | d() or (record_command | d() and record_content | d()) or entry.raw | d()) %}
{% if not loop.first %}
{% endif %}
{% if entry.comment | d() %}
{{ entry.comment | regex_replace('\n$','') | comment(prefix='', postfix='') -}}
{% endif %}
{% if entry.raw | d() %}
{% if entry.state | d('present') == 'comment' %}
{{ "{}".format(entry.raw | regex_replace('\n$','')) | comment(decoration='#', prefix='', postfix='') }}
{% else %}
{{ "{}".format(entry.raw | regex_replace('\n$','')) }}
{% endif %}
{% elif dhcp_host | d() %}
{{ '{}dhcp-host = {}'.format(('#' if (entry.state | d('present') == 'comment') else ''), dhcp_host | join(',')) }}
{% if host_record | d() %}
{{ '{}host-record = {}'.format(('#' if (entry.state | d('present') == 'comment') else ''), host_record | join(',')) }}
{% if entry.cname | d() %}
{% for cname in ([ entry.cname ] if entry.cname is string else entry.cname) %}
{{ '{}cname = {},{}'.format(('#' if (entry.state | d('present') == 'comment') else ''), cname + '.' + entry.domain, host_name + '.' + entry.domain) }}
{% endfor %}
{% endif %}
{% endif %}
{% elif (record_command | d() and record_content | d()) %}
{{ '{}{} = {}'.format(('#' if (entry.state | d('present') == 'comment') else ''), record_command, record_content | join(',')) }}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}

View file

@ -0,0 +1,55 @@
{# Copyright (C) 2014-2019 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2015-2017 Robin Schneider <ypid@riseup.net>
# Copyright (C) 2014-2019 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
#}
# {{ ansible_managed }}
{% if item.comment | d() %}
{{ item.comment | regex_replace('\n$','') | comment(prefix='', postfix='') -}}
{% endif %}
{% if item.options | d() %}
{% for element in item.options %}
{% if element is string %}
{% if not loop.first %}
{% endif %}
{{ "{}".format(element) }}
{% elif element is mapping %}
{% if element.name | d() %}
{% if element.state | d('present') not in [ 'absent', 'ignore' ] %}
{% if (loop.first and item.comment | d() and element.comment | d()) or (not loop.first and (element.comment | d() or (element.separator | d()) | bool)) %}
{% endif %}
{% if element.comment | d() %}
{{ element.comment | regex_replace('\n$','') | comment(prefix='', postfix='') -}}
{% endif %}
{% if element.value | d() %}
{% if element.value is string %}
{{ "{}{} = {}".format(('#' if (element.state | d('present') == 'comment') else ''), element.option | d(element.name), element.value) }}
{% else %}
{% for thing in (element.value | selectattr('state', 'equalto', 'present') | map(attribute='name') | list) %}
{{ "{}{} = {}".format(('#' if (element.state | d('present') == 'comment') else ''), element.option | d(element.name), thing) }}
{% endfor %}
{% endif %}
{% elif element.raw | d() %}
{% if element.state | d('present') == 'comment' %}
{{ "{}".format(element.raw | regex_replace('\n$','')) | comment(decoration='#', prefix='', postfix='') -}}
{% else %}
{{ "{}".format(element.raw | regex_replace('\n$','')) }}
{% endif %}
{% else %}
{{ "{}{}".format(('#' if (element.state | d('present') == 'comment') else ''), element.option | d(element.name)) }}
{% endif %}
{% endif %}
{% else %}
{% for key, value in element.items() %}
{{ "{} = {}".format(key, value) }}
{% endfor %}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% if item.raw | d() %}
{{ item.raw | regex_replace('\n$','') }}
{% endif %}

View file

@ -0,0 +1,14 @@
{# Copyright (C) 2014-2019 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2015-2017 Robin Schneider <ypid@riseup.net>
# Copyright (C) 2014-2019 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
#}
# {{ ansible_managed }}
{% if dnsmasq__nameservers | d() and dnsmasq__nameservers is iterable %}
{% for nameserver in dnsmasq__nameservers %}
nameserver {{ nameserver }}
{% endfor %}
{% elif dnsmasq__nameservers | d() and dnsmasq__nameservers is string %}
nameserver {{ dnsmasq__nameservers }}
{% endif %}

View file

@ -0,0 +1,170 @@
{# Copyright (C) 2014-2019 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2015-2017 Robin Schneider <ypid@riseup.net>
# Copyright (C) 2014-2019 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
#}
{% set dnsmasq__tpl_interface_configuration = [] %}
{% for interface in (dnsmasq__combined_interfaces | flatten | debops.debops.parse_kv_items) %}
{% set dnsmasq__tpl_ipv4 = [] %}
{% set dnsmasq__tpl_ipv6 = [] %}
{% if not interface.ignore_interface_addresses|d(False) and hostvars[inventory_hostname]["ansible_" + interface.name] | d() %}
{% if hostvars[inventory_hostname]["ansible_" + interface.name].ipv4 | d() %}
{% set _ = dnsmasq__tpl_ipv4.append(hostvars[inventory_hostname]["ansible_" + interface.name].ipv4.address + '/'
+ (hostvars[inventory_hostname]["ansible_" + interface.name].ipv4.address + '/'
+ hostvars[inventory_hostname]["ansible_" + interface.name].ipv4.netmask) | ansible.utils.ipaddr('prefix') | string) %}
{% if hostvars[inventory_hostname]["ansible_" + interface.name].ipv4_secondaries | d() %}
{% for element in hostvars[inventory_hostname]["ansible_" + interface.name].ipv4_secondaries %}
{% set _ = dnsmasq__tpl_ipv4.append(element.address + '/' + (element.address + '/' + element.netmask) | ansible.utils.ipaddr('prefix') | string) %}
{% endfor %}
{% endif %}
{% endif %}
{% if hostvars[inventory_hostname]["ansible_" + interface.name].ipv6 | d() %}
{% for element in hostvars[inventory_hostname]["ansible_" + interface.name].ipv6 %}
{% if not element.address | ansible.utils.ipv6('link-local') %}
{% set _ = dnsmasq__tpl_ipv6.append(element.address + '/' + element.prefix) %}
{% endif %}
{% endfor %}
{% endif %}
{% endif %}
{% if interface.address | d() %}
{% for element in ([ interface.address ] if interface.address is string else interface.address) %}
{% if element | ansible.utils.ipv4('host/prefix') %}
{% set _ = dnsmasq__tpl_ipv4.append(element) %}
{% elif element | ansible.utils.ipv6('host/prefix') and not element | ansible.utils.ipv6('link-local') %}
{% set _ = dnsmasq__tpl_ipv6.append(element) %}
{% endif %}
{% endfor %}
{% endif %}
{% if interface.addresses | d() %}
{% for element in ([ interface.addresses ] if interface.addresses is string else interface.addresses) %}
{% if element | ansible.utils.ipv4('host/prefix') %}
{% set _ = dnsmasq__tpl_ipv4.append(element) %}
{% elif element | ansible.utils.ipv6('host/prefix') and not element | ansible.utils.ipv6('link-local') %}
{% set _ = dnsmasq__tpl_ipv6.append(element) %}
{% endif %}
{% endfor %}
{% endif %}
{% set interface_hostname = (interface.hostname | d(dnsmasq__hostname)) %}
{% set interface_domain = (interface.domain | d(interface.name + "." + dnsmasq__base_domain)) %}
{% set interface_tag = (interface.tag | d(interface.name)) %}
{% set interface_base = {"name": (interface.filename | d("interface-" + interface.name + ".conf")), "state": (interface.state | d("present"))} %}
{% set interface_search = [] %}
{% if interface.search | d() %}
{% set _ = interface_search.extend([ interface.search ] if interface.search is string else interface.search) %}
{% endif %}
{% if (interface_domain.endswith(dnsmasq__base_domain) and interface_domain.split(".") | length >= 3 and dnsmasq__base_domain.split(".") | length >= 2) %}
{% set _ = interface_search.append(dnsmasq__base_domain) %}
{% endif %}
{% set interface_options = [] %}
{% set _ = interface_options.append({
"name": "interface",
"value": interface.name
}) %}
{% set _ = interface_options.append({
"name": "interface-name_domain",
"option": "interface-name",
"value": (interface_hostname + "." + interface_domain + "," + interface.name)
}) %}
{% if (not interface.dhcp_enabled | d(True)) | bool %}
{% set _ = interface_options.append({
"name": "no-dhcp-interface",
"value": interface.name
}) %}
{% else %}
{% if interface.dhcp_range_state | d('present') == 'present' %}
{% for host_address in dnsmasq__tpl_ipv4 %}
{% set _ = interface_options.append({
"name": ("dhcp_range_" + (host_address | replace('.','_') | replace('/','_'))),
"option": "dhcp-range",
"separator": True,
"value": ("set:" + interface_tag + "," + host_address | ansible.utils.ipv4(interface.dhcp_range_start | d(10) | int) | ansible.utils.ipv4('address') + ',' + host_address | ansible.utils.ipv4(interface.dhcp_range_end | d(-10) | int) | ansible.utils.ipv4('address') + ',' + host_address | ansible.utils.ipv4('netmask') + ',' + interface.dhcp_lease | d('24h'))
}) %}
{% endfor %}
{% for host_address in dnsmasq__tpl_ipv6 %}
{% if (interface.dhcp_ipv6_mode is string and interface.dhcp_ipv6_mode == '') %}
{% set ra_separator = '' %}
{% else %}
{% set ra_separator = ',' %}
{% endif %}
{% set _ = interface_options.append({
"name": ("dhcp_range_" + (host_address | replace(':','_') | replace('/','_'))),
"option": "dhcp-range",
"value": ("set:" + interface_tag + "," + host_address | ansible.utils.ipv6(interface.dhcp_range_start | d(10) | int) | ansible.utils.ipv6('address') + ',' + host_address | ansible.utils.ipv6(interface.dhcp_range_end | d(-10) | int) | ansible.utils.ipv6('address') + ',' + interface.dhcp_ipv6_mode | d("ra-names,ra-stateless,slaac") + ra_separator + host_address | ansible.utils.ipv6('prefix') | string + ',' + interface.dhcp_lease | d('24h'))
}) %}
{% endfor %}
{% endif %}
{% if (interface.router_state | d('present')) in [ 'present', 'enabled' ] %}
{% if interface.router_gateway | d() %}
{% set _ = interface_options.append({
"name": "dhcp_option_ipv4_router",
"option": "dhcp-option",
"value": ("tag:" + interface_tag + ",option:router," + interface.router_gateway)
}) %}
{% else %}
{% set _ = interface_options.append({
"name": "dhcp_option_ipv4_router",
"option": "dhcp-option",
"value": ("tag:" + interface_tag + ",option:router,0.0.0.0")
}) %}
{% endif %}
{% elif (interface.router_state | d('present')) in [ 'disabled' ] %}
{% set _ = interface_options.append({
"name": "dhcp_option_ipv4_router",
"option": "dhcp-option",
"comment": "IPv4 router is not advertised",
"value": ("tag:" + interface_tag + ",option:router")
}) %}
{% endif %}
{% endif %}
{% if dnsmasq__tpl_ipv6 | d() %}
{% set _ = interface_options.append({
"name": "dhcp_option6_dns_server",
"option": "dhcp-option",
"comment": "Advertise RDNSS servers for local IPv6 network",
"value": ("tag:" + interface_tag + ",option6:dns-server," + dnsmasq__tpl_ipv6 | ansible.utils.ipv6("address") | ansible.utils.ipwrap | join(","))
}) %}
{% endif %}
{% set _ = interface_options.append({
"name": "dhcp_option_search",
"option": "dhcp-option",
"value": ("tag:" + interface_tag + ",option:domain-search," + ([ interface_domain ] + ([ interface_search ] if interface_search is string else interface_search)) | unique | join(","))
}) %}
{% set _ = interface_options.append({
"name": "dhcp_option6_search",
"option": "dhcp-option",
"value": ("tag:" + interface_tag + ",option6:domain-search," + ([ interface_domain ] + ([ interface_search ] if interface_search is string else interface_search)) | unique | join(","))
}) %}
{% for host_address in (dnsmasq__tpl_ipv4 + dnsmasq__tpl_ipv6) %}
{% set _ = interface_options.append({
"name": ("domain_" + (host_address | replace('.','_') | replace('/','_'))),
"option": "domain",
"value": (interface_domain + "," + host_address | ansible.utils.ipaddr('subnet') + (",local" if (host_address | ansible.utils.ipaddr('prefix') in [ 8, 16, 24 ]) else ""))
}) %}
{% endfor %}
{% if (interface.boot_enabled | d(dnsmasq__boot_enabled)) | bool %}
{% set _ = interface_options.extend([{
"name": "enable-tftp",
"comment": "Enable TFTP support",
"value": interface.name
}, {
"name": "tftp-root",
"value": (interface.boot_tftp_root | d(dnsmasq__boot_tftp_root)) + "," + interface.name
}]) %}
{% if not dnsmasq__boot_ipxe_enabled | bool %}
{% set _ = interface_options.append({
"name": "dhcp-boot",
"value": ("tag:" + interface_tag + "," + interface.boot_filename | d("pxelinux.0") + ((",," + interface.boot_server) if interface.boot_server | d() else ""))
}) %}
{% endif %}
{% endif %}
{% if interface.raw | d() %}
{% set _ = interface_options.append({
"name": "interface_raw",
"comment": "Additional interface options",
"raw": interface.raw
}) %}
{% endif %}
{% set _ = interface_base.update({"options": interface_options}) %}
{% set _ = dnsmasq__tpl_interface_configuration.append(interface_base) %}
{% endfor %}
{{ dnsmasq__tpl_interface_configuration | to_nice_yaml }}

View file

@ -0,0 +1,60 @@
{# Copyright (C) 2014-2019 Maciej Delmanowski <drybjed@gmail.com>
# Copyright (C) 2015-2017 Robin Schneider <ypid@riseup.net>
# Copyright (C) 2014-2019 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-only
#}
{% set dnsmasq__tpl_subnets = [] %}
{% set dnsmasq__tpl_tcpwrappers__dependent_allow = [] %}
{% for interface in (dnsmasq__combined_interfaces | flatten | debops.debops.parse_kv_items) %}
{% set dnsmasq__tpl_ipv4 = [] %}
{% set dnsmasq__tpl_ipv6 = [] %}
{% if hostvars[inventory_hostname]["ansible_" + interface.name] | d() %}
{% if hostvars[inventory_hostname]["ansible_" + interface.name].ipv4 | d() %}
{% set _ = dnsmasq__tpl_ipv4.append(hostvars[inventory_hostname]["ansible_" + interface.name].ipv4.address + '/'
+ (hostvars[inventory_hostname]["ansible_" + interface.name].ipv4.address + '/'
+ hostvars[inventory_hostname]["ansible_" + interface.name].ipv4.netmask) | ansible.utils.ipaddr('prefix') | string) %}
{% if hostvars[inventory_hostname]["ansible_" + interface.name].ipv4_secondaries | d() %}
{% for element in hostvars[inventory_hostname]["ansible_" + interface.name].ipv4_secondaries %}
{% set _ = dnsmasq__tpl_ipv4.append(element.address + '/' + (element.address + '/' + element.netmask) | ansible.utils.ipaddr('prefix') | string) %}
{% endfor %}
{% endif %}
{% endif %}
{% if hostvars[inventory_hostname]["ansible_" + interface.name].ipv6 | d() %}
{% for element in hostvars[inventory_hostname]["ansible_" + interface.name].ipv6 %}
{% if not element.address | ansible.utils.ipv6('link-local') %}
{% set _ = dnsmasq__tpl_ipv6.append(element.address + '/' + element.prefix) %}
{% endif %}
{% endfor %}
{% endif %}
{% endif %}
{% if interface.address | d() %}
{% for element in ([ interface.address ] if interface.address is string else interface.address) %}
{% if element | ansible.utils.ipv4('host/prefix') %}
{% set _ = dnsmasq__tpl_ipv4.append(element) %}
{% elif element | ansible.utils.ipv6('host/prefix') and not element | ansible.utils.ipv6('link-local') %}
{% set _ = dnsmasq__tpl_ipv6.append(element) %}
{% endif %}
{% endfor %}
{% endif %}
{% if interface.addresses | d() %}
{% for element in ([ interface.addresses ] if interface.addresses is string else interface.addresses) %}
{% if element | ansible.utils.ipv4('host/prefix') %}
{% set _ = dnsmasq__tpl_ipv4.append(element) %}
{% elif element | ansible.utils.ipv6('host/prefix') and not element | ansible.utils.ipv6('link-local') %}
{% set _ = dnsmasq__tpl_ipv6.append(element) %}
{% endif %}
{% endfor %}
{% endif %}
{% if (interface.state | d('present') != 'absent' and (interface.boot_enabled | d(dnsmasq__boot_enabled)) | bool) %}
{% set _ = dnsmasq__tpl_subnets.extend(dnsmasq__tpl_ipv4 + dnsmasq__tpl_ipv6) %}
{% endif %}
{% endfor %}
{% set _ = dnsmasq__tpl_tcpwrappers__dependent_allow.append({
"daemon": "in.tftpd",
"client": (dnsmasq__tpl_subnets | ansible.utils.ipaddr('subnet')),
"weight": 50,
"filename": "dnsmasq_dependent_allow",
"comment": "Allow remote connections to TFTP server",
"state": ("present" if (dnsmasq__boot_enabled | bool and dnsmasq__tpl_subnets | d()) else "absent")
}) %}
{{ dnsmasq__tpl_tcpwrappers__dependent_allow | to_nice_yaml }}